feat(bf-4sdu): add worker memory bar to worker cards
Some checks are pending
CI / test (18.x) (push) Waiting to run
CI / test (20.x) (push) Waiting to run
CI / test (22.x) (push) Waiting to run

- Add rssKb, peakRssKb, rssLimitBytes, rssPercent, swapKb, pid fields to frontend WorkerInfo type
- Create WorkerMemoryBar component displaying:
  - Proportional RSS memory bar (4 GB ceiling default, or per-worker limit)
  - Peak RSS watermark marker
  - Text label showing current/limit (e.g., "1.2 GB / 4.0 GB")
  - Swap indicator if swap usage > 0
- Integrate WorkerMemoryBar into WorkerGrid component
- Hide bar when rssKb is null (worker not sampled yet or exited)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-06-07 10:19:27 -04:00
parent 81b57e66b5
commit ea6e270960
9 changed files with 5653 additions and 5 deletions

View file

@ -142,7 +142,7 @@
{"id":"bd-zci","title":"Map OTLP metrics → analytics DB instruments (tokens, cost, durations)","description":"## Gap\nplan.md §Architecture: 'Analytics Writer prefers OTLP metric instruments over log-derived values when both are present.' Current src/historicalStore.ts + src/workerAnalytics.ts derive these from log messages.\n\n## Work\n1. Accept OTLP metrics (Sum, Histogram, Gauge) through the receiver.\n2. Define the canonical instrument names in docs/schema.md: \\`needle.worker.tokens.in\\`, \\`needle.worker.tokens.out\\`, \\`needle.worker.cost.usd\\`, \\`needle.bead.duration\\`, etc. Align with NEEDLE's telemetry module.\n3. Persist aggregates to ~/.needle/fabric.db (see src/historicalStore.ts schema) and make analytics queries prefer metric-sourced rows when both exist.\n\n## Done when\n- fabric.db session_summary rows populated from OTLP metrics for a live NEEDLE worker.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"bravo","owner":"","created_at":"2026-04-21T12:46:21.386361740Z","created_by":"coding","updated_at":"2026-04-21T22:22:13.308294490Z","closed_at":"2026-04-21T22:22:13.308050161Z","close_reason":"Verified complete implementation: OTLP metric pipeline maps Sum/Histogram/Gauge data points through receivers -> normalizer -> MetricAccumulator -> fabric.db (metric_samples + session_worker_summaries). Canonical instruments defined in docs/schema.md. Alias resolution for NEEDLE naming conventions. Source-priority upserts ensure otlp-metric rows override log-derived estimates. All 177 tests pass.","closed_by_session":"","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"original_size":0,"sender":"","labels":["analytics","deferred","otlp","phase-1-5"]}
{"id":"bf-1373","title":"Fix: DiffView.parseDiff broken (added/removed line parsing fails)","description":"3 tests in src/tui/components/DiffView.test.ts fail in the parseDiff suite:\n- \"should parse added lines\"\n- \"should parse removed lines\"\n- \"should handle empty diff\"\n\nThe parseDiff function (exported from DiffView.ts) is not correctly identifying + and - lines in unified diff output, or the parsing of the old_string/new_string diff computation is incorrect.\n\nFix: audit parseDiff() in src/tui/components/DiffView.ts, ensure it correctly:\n- Splits diff on newlines\n- Classifies lines starting with '+' as added (excluding '+++')\n- Classifies lines starting with '-' as removed (excluding '---')\n- Returns empty array for empty/null input","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","created_at":"2026-05-02T18:18:17.589533196Z","updated_at":"2026-05-02T20:04:01.643730649Z","closed_at":"2026-05-02T20:04:01.643730649Z","close_reason":"Completed","source_repo":".","compaction_level":0}
{"id":"bf-1nah","title":"Fix bin/fabric entrypoint and install systemd service","description":"Two infrastructure gaps preventing fabric from running:\n\n1. bin/fabric is an empty file (0 bytes). The package.json declares bin: {'fabric': './dist/cli.js'}, so npm install -g should create a 'fabric' executable — but the bin/ directory entry is empty/unused (dist/cli.js IS the real entrypoint). Fix: either remove the empty bin/fabric file and rely solely on the package.json bin declaration, or make bin/fabric a shebang wrapper:\n #!/usr/bin/env node\n require('../dist/cli.js');\n Either way, verify that 'node dist/cli.js --help' works and that the bin entry actually resolves.\n\n2. The systemd user service has never been installed. The unit file exists at scripts/fabric-web.service. Install it:\n mkdir -p ~/.config/systemd/user\n cp scripts/fabric-web.service ~/.config/systemd/user/\n Also install the prune timer:\n cp scripts/fabric-prune.service ~/.config/systemd/user/\n cp scripts/fabric-prune.timer ~/.config/systemd/user/\n systemctl --user daemon-reload\n systemctl --user enable fabric-web.service\n systemctl --user start fabric-web.service\n Verify: systemctl --user status fabric-web.service shows 'active (running)' and curl -s http://localhost:3000/api/workers returns JSON.\n Also verify the secrets file exists at ~/.config/fabric/secrets.env with FABRIC_AUTH_TOKEN set (create with a random token if missing).\n\nAcceptance: systemctl --user status fabric-web.service is active, curl http://localhost:3000/api/workers returns valid JSON.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","assignee":"marathon","created_at":"2026-05-26T21:00:11.965674425Z","updated_at":"2026-05-26T21:05:46.348779251Z","closed_at":"2026-05-26T21:05:46.348779251Z","close_reason":"Commit 67f991a: Fixed systemd service node paths for NixOS (/usr/bin/node -> /home/coding/.nix-profile/bin/node), removed empty bin/fabric file, created ~/.config/fabric/secrets.env, installed and enabled fabric-web.service and fabric-prune.timer. Acceptance verified: systemctl --user status fabric-web.service active (running), curl http://localhost:3000/api/workers returns valid JSON.","source_repo":".","compaction_level":0}
{"id":"bf-1uu9","title":"E2E OTLP integration test: POST mock NEEDLE spans to :4318 and verify /api/workers updates","description":"## Goal\nAdd a vitest integration test that:\n1. Starts the FABRIC web server with --otlp-http on a random port\n2. POSTs a realistic NEEDLE OTLP payload (spans + metrics with needle.worker.id, needle.bead.id, needle.session.id attributes) to /v1/traces and /v1/metrics\n3. Asserts GET /api/workers returns a worker entry with the correct worker ID and a non-STOPPED needleState\n4. Asserts GET /api/summary returns workers_active >= 1\n\n## Why\nThe OTLP receiver (otlpHttpReceiver.ts + normalizer.ts) is implemented but has no integration-level test covering the full HTTP → normalizer → store → API response path.\n\n## Acceptance criteria\n- Test lives in src/ or tests/ (vitest compatible)\n- Uses real HTTP — start server, POST protobuf or JSON OTLP, check API\n- npm test passes","design":"","acceptance_criteria":"","notes":"","status":"in_progress","priority":1,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-30T13:52:38.619910418Z","updated_at":"2026-06-07T13:36:49.481384592Z","source_repo":".","compaction_level":0}
{"id":"bf-1uu9","title":"E2E OTLP integration test: POST mock NEEDLE spans to :4318 and verify /api/workers updates","description":"## Goal\nAdd a vitest integration test that:\n1. Starts the FABRIC web server with --otlp-http on a random port\n2. POSTs a realistic NEEDLE OTLP payload (spans + metrics with needle.worker.id, needle.bead.id, needle.session.id attributes) to /v1/traces and /v1/metrics\n3. Asserts GET /api/workers returns a worker entry with the correct worker ID and a non-STOPPED needleState\n4. Asserts GET /api/summary returns workers_active >= 1\n\n## Why\nThe OTLP receiver (otlpHttpReceiver.ts + normalizer.ts) is implemented but has no integration-level test covering the full HTTP → normalizer → store → API response path.\n\n## Acceptance criteria\n- Test lives in src/ or tests/ (vitest compatible)\n- Uses real HTTP — start server, POST protobuf or JSON OTLP, check API\n- npm test passes","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-30T13:52:38.619910418Z","updated_at":"2026-06-07T13:50:35.990123171Z","closed_at":"2026-06-07T13:50:35.990123171Z","close_reason":"Completed: Added active workers count test case to existing OTLP E2E integration test. The test file already existed with 6 passing tests; added 1 more to count non-STOPPED workers. All tests pass. Note: /api/summary endpoint does not exist; used /api/workers instead.","source_repo":".","compaction_level":0}
{"id":"bf-27e4","title":"Fix beadsCompleted vs stuck detection metric discrepancy in /api/workers response","description":"## Problem\n/api/workers returns contradictory data per worker:\n- beadsCompleted: 285 (counts bead.released events)\n- stuck: true, stuckReason: 'Running for 2311m with only 1 completion(s)'\n\nThe stuck detection counts a different metric (outcome.success events or similar) while beadsCompleted counts bead.released. When all beads time out and are deferred, beadsCompleted increments but the stuck detector sees zero success outcomes and flags the worker as stuck.\n\n## Fix options\n1. Unify the metric — stuck detection should use the same counter as beadsCompleted\n2. Or: update stuckReason to clarify it means 'zero successful completions' not 'zero total processed'\n3. Or: add a separate 'beadsTimedOut' counter and show it in the worker card\n\n## Acceptance criteria\n- A worker that processes 100 beads (all timed out) shows clearly in the UI that it processed 100 but completed 0 successfully — not a confusing mix of 100 and 'only 1 completion'\n- The stuck flag should still fire but the reason text should be accurate","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-05-30T13:52:47.322897128Z","updated_at":"2026-05-30T13:52:47.322897128Z","source_repo":".","compaction_level":0}
{"id":"bf-2q9r","title":"Add per-needle-worker MemoryMax ceiling (4 GB) so no single worker can exhaust the cgroup","description":"Problem: with only a cgroup-level soft limit, one runaway worker can still consume all available memory before pressure kills it.\n\nSolution: apply a per-process MemoryMax to each needle worker. Options:\n1. systemd transient scope: needle spawns workers with systemd-run --scope -p MemoryMax=4G\n2. needle config: check if needle supports resource limits in its worker launch config\n3. cgroup v2 direct: write 4G to memory.max for each worker cgroup after spawn\n\nTarget: each Claude Code session bounded at 4 GB RSS. With 6 workers + fabric-web + VSCode that stays well under 32 GB.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":0,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-27T11:11:08.152673301Z","updated_at":"2026-06-07T13:24:49.273630596Z","closed_at":"2026-06-07T13:24:49.273630596Z","close_reason":"Completed","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":"","comments":[{"id":15,"issue_id":"bf-2q9r","author":"cli","text":"## Summary\n\nImplemented per-needle-worker MemoryMax ceiling (4 GB) using cgroup v2 direct approach (writing to memory.max). Each needle worker is now bounded at 4 GB RSS, preventing runaway workers from exhausting the cgroup's memory.\n\n## Implementation\n\n- Created src/workerMemoryLimiter.ts with core logic for:\n - Finding worker PIDs by reading /proc cmdline and matching needle run --identifier <value>\n - Getting cgroup v2 paths from /proc/<pid>/cgroup\n - Applying memory limits by writing to memory.max\n - Cache of limited workers to avoid redundant work\n - Helper functions for reading memory usage/limits\n\n- Integrated into src/cli.ts:\n - Apply limits at startup for both tui and web commands (directory source only)\n - Log count of limited workers to stderr\n\n- Integrated into src/directoryTailer.ts:\n - Apply limits when new log files are detected\n - Auto-limits workers as they come online\n\n## Retrospective\n\n- **What worked:** The cgroup v2 direct approach is simple and effective. Writing to memory.max requires no systemd integration and works immediately. The process discovery via /proc/<pid>/cmdline reliably finds needle workers by matching the --identifier argument.\n\n- **What didn't:** Initial approach considered systemd transient scope (systemd-run --scope -p MemoryMax=4G) but would require needle to spawn workers differently. Cgroup v2 direct approach works without changing needle's launch process.\n\n- **Surprise:** The random 8-char hex suffix in log filenames (e.g., claude-code-glm-4.7-alpha-2wf3a1b2.jsonl) required careful parsing to extract the worker identifier for PID matching.\n\n- **Reusable pattern:** Per-process resource limiting via cgroup v2 is a general pattern. Reading /proc/<pid>/cgroup to get the path and writing to the appropriate controller file works for memory, cpu, io, etc.","created_at":"2026-06-07T13:22:21.566362525Z"}],"annotations":{"completion":"Summary of work completed: Added per-needle-worker MemoryMax ceiling (4 GB) to prevent single worker from exhausting the cgroup.\n\n## Implementation\n- Created `workerMemoryLimiter.ts` implementing cgroup v2 direct approach (writing to memory.max)\n- Default limit: 4 GB per worker (configurable)\n- Integration at two points:\n 1. CLI startup: `applyAllWorkerLimits()` called in both tui and web commands\n 2. DirectoryTailer: `applyLimitForLogFile()` called when new log files are activated\n- Worker discovery via log file naming pattern and PID lookup in /proc\n\n## Retrospective\n- **What worked:** Cgroup v2 direct approach is clean and requires no external dependencies\n- **What didn't:** systemd-run --scope would require needle changes\n- **Surprise:** PID discovery via /proc/cmdline is more reliable than log file parsing alone\n- **Reusable pattern:** For cgroup v2 resource limits: read /proc/<pid>/cgroup, then write to /sys/fs/cgroup/<path>/memory.max"}}
{"id":"bf-2wf","title":"Phase 9: Productivity Analytics — remaining gaps","description":"Tracks unfinished Phase 9 items from docs/plan.md (Productivity Analytics). DONE already (verified in code 2026-05-22): beadsCompleted fires on bead.released/release_success (store.ts), worker sort by state, test-worker filter (isTestWorker + hideTestWorkers toggle), Productivity panel daily-throughput chart + worker leaderboard, GET /api/productivity. REMAINING (this epic): currentBead field, fleet summary bar, worker-card enrichment, bead workspace scanner + project breakdown. See docs/plan.md section Phase 9.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"epic","assignee":"claude-code-glm-4.7-echo","created_at":"2026-05-22T19:19:43.513243795Z","updated_at":"2026-05-22T22:02:57.997168418Z","closed_at":"2026-05-22T22:02:57.997168418Z","close_reason":"Phase 9 Productivity Analytics verification complete. All items were already implemented in prior sessions. See notes/bf-2wf.md for details.","source_repo":".","compaction_level":0,"labels":["phase9"],"dependencies":[{"issue_id":"bf-2wf","depends_on_id":"bf-60j","type":"blocks","created_at":"2026-05-22T19:21:15.280109842Z","created_by":"cli","thread_id":""},{"issue_id":"bf-2wf","depends_on_id":"bf-3xp","type":"blocks","created_at":"2026-05-22T19:21:15.283895541Z","created_by":"cli","thread_id":""},{"issue_id":"bf-2wf","depends_on_id":"bf-4f3","type":"blocks","created_at":"2026-05-22T19:21:15.287460586Z","created_by":"cli","thread_id":""},{"issue_id":"bf-2wf","depends_on_id":"bf-3t8","type":"blocks","created_at":"2026-05-22T19:21:15.291058758Z","created_by":"cli","thread_id":""}]}
@ -166,8 +166,8 @@
{"id":"bf-50m5","title":"fix: infinite recursion in TimelineView getEventTime","description":"","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":0,"issue_type":"bug","assignee":"marathon","created_at":"2026-05-27T01:28:01.566632096Z","updated_at":"2026-05-27T01:29:54.656004852Z","closed_at":"2026-05-27T01:29:54.656004852Z","close_reason":"Fixed infinite recursion bug in TimelineView.tsx getEventTime. Changed recursive call to Date.parse(event.timestamp). All 26 failing tests now pass. Commit 10533b0 pushed.","source_repo":".","compaction_level":0}
{"id":"bf-517j","title":"test","description":"","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-27T11:09:52.311179341Z","updated_at":"2026-05-27T11:09:56.315323571Z","closed_at":"2026-05-27T11:09:56.315323571Z","close_reason":"Completed","closed_by_session":"","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
{"id":"bf-51lx","title":"plan-gap: Final audit — FABRIC implementation complete","description":"Comprehensive plan-vs-artifacts audit performed 2026-05-26. All Phases 1-9 verified complete. No gaps found. Type-check, all tests, and builds pass. 2484 unit tests passing. All planned features implemented in both TUI and web modes.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":3,"issue_type":"task","created_at":"2026-05-27T01:21:22.232492638Z","updated_at":"2026-05-27T01:21:30.342580744Z","closed_at":"2026-05-27T01:21:30.342580744Z","close_reason":"Comprehensive audit verified all Phases 1-9 complete. No gaps found. All tests pass (2484), type-check and builds succeed. FABRIC implementation is complete per docs/plan.md.","source_repo":".","compaction_level":0}
{"id":"bf-53q6","title":"Add system cgroup memory panel to web dashboard (real-time utilization + OOM event counter)","description":"New panel in the FABRIC web dashboard showing:\n- Current cgroup memory usage vs MemoryHigh (progress bar: green <70%, yellow 70-90%, red >90%)\n- 5-minute sparkline of memory.current sampled every 10s\n- oom_kill counter from /sys/fs/cgroup/user.slice/user-1001.slice/memory.events\n- Swap usage if enabled\n\nBackend: new GET /api/system/memory endpoint reading from /sys/fs/cgroup/user.slice/user-1001.slice/memory.{current,high,max,events,swap.current}.\n\nFrontend: React component alongside the fleet summary bar.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-05-27T11:11:08.152758167Z","updated_at":"2026-05-27T11:11:08.152758167Z","close_reason":"","closed_by_session":"","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
{"id":"bf-5cdj","title":"Sample per-worker process RSS from /proc and expose via GET /api/workers memory fields","description":"FABRIC tracks needle workers by PID (from OTLP telemetry). Use those PIDs to periodically sample /proc/<pid>/status for VmRSS, VmPeak, VmSwap.\n\nChanges:\n- Add a MemorySampler that polls active worker PIDs every 10s\n- Attach rss_kb, peak_rss_kb, swap_kb to WorkerState\n- Expose on GET /api/workers response\n- Broadcast updated memory fields via WebSocket\n\nFallback: if /proc/<pid>/status is unreadable (worker exited), emit null fields.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-05-27T11:11:08.152717167Z","updated_at":"2026-05-27T11:11:08.152717167Z","close_reason":"","closed_by_session":"","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
{"id":"bf-53q6","title":"Add system cgroup memory panel to web dashboard (real-time utilization + OOM event counter)","description":"New panel in the FABRIC web dashboard showing:\n- Current cgroup memory usage vs MemoryHigh (progress bar: green <70%, yellow 70-90%, red >90%)\n- 5-minute sparkline of memory.current sampled every 10s\n- oom_kill counter from /sys/fs/cgroup/user.slice/user-1001.slice/memory.events\n- Swap usage if enabled\n\nBackend: new GET /api/system/memory endpoint reading from /sys/fs/cgroup/user.slice/user-1001.slice/memory.{current,high,max,events,swap.current}.\n\nFrontend: React component alongside the fleet summary bar.","design":"","acceptance_criteria":"","notes":"","status":"in_progress","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-27T11:11:08.152758167Z","updated_at":"2026-06-07T14:09:20.186793550Z","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
{"id":"bf-5cdj","title":"Sample per-worker process RSS from /proc and expose via GET /api/workers memory fields","description":"FABRIC tracks needle workers by PID (from OTLP telemetry). Use those PIDs to periodically sample /proc/<pid>/status for VmRSS, VmPeak, VmSwap.\n\nChanges:\n- Add a MemorySampler that polls active worker PIDs every 10s\n- Attach rss_kb, peak_rss_kb, swap_kb to WorkerState\n- Expose on GET /api/workers response\n- Broadcast updated memory fields via WebSocket\n\nFallback: if /proc/<pid>/status is unreadable (worker exited), emit null fields.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-27T11:11:08.152717167Z","updated_at":"2026-06-07T13:59:00.328629453Z","closed_at":"2026-06-07T13:59:00.328629453Z","close_reason":"Summary of work completed.\n\n## Retrospective\n- **What worked:** The MemorySampler class was cleanly designed with a singleton pattern, making integration straightforward. Using protected methods for test override (sampleProcStatus) allowed comprehensive unit testing without mocking complexity.\n- **What didn't:** Initially had duplicate MemorySampler setup code in server.ts (lines 189-232 and 234-277) which caused confusion - fixed by removing the duplicate block.\n- **Surprise:** The WorkerInfo type already had pid, rssKb, peakRssKb, swapKb fields defined from previous work, simplifying the integration significantly.\n- **Reusable pattern:** For polling services, use a singleton with start/stop methods and integrate lifecycle cleanup in the server's stop() function to prevent resource leaks. Protected methods enable clean test override without complex mocking.","closed_by_session":"","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
{"id":"bf-5klc","title":"Fix: CrossReferencePanel and WorkerAnalyticsPanel vi.mock hoisting errors","description":"Two test files fail to load entirely due to vitest mock hoisting issues:\n\nsrc/tui/components/CrossReferencePanel.test.ts:\n Error: Cannot access 'MockCrossReferenceManager' before initialization (line 79)\n Cause: vi.mock() factory references a const declared after it — vitest hoists vi.mock() calls to top of file, but the factory captures the const by reference before it is initialized.\n\nsrc/tui/components/WorkerAnalyticsPanel.test.ts:\n Error: Cannot access 'MockWorkerAnalytics' before initialization (line 72)\n Same cause.\n\nFix: In both test files, convert the vi.mock() factory to use inline mock definitions (no top-level const references), or use vi.hoisted() to declare the mock variables so they are available when the factory runs.\n\nReference: https://vitest.dev/api/vi.html#vi-mock (hoisting rules)","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","assignee":"claude-code-glm-4.7-echo","created_at":"2026-05-02T18:18:48.874294693Z","updated_at":"2026-05-22T21:49:56.075000556Z","closed_at":"2026-05-22T21:49:56.075000556Z","close_reason":"Completed","source_repo":".","compaction_level":0}
{"id":"bf-5r8a","title":"Fix: missing src/memoryProfiler.ts breaks server.ts and all web server tests","description":"src/web/server.ts imports { getMemoryProfiler } from '../memoryProfiler.js' at line 24, but the file src/memoryProfiler.ts does not exist. This causes src/web/server.test.ts to fail at import with: \"Cannot find module '../memoryProfiler.js'\". All web server tests are blocked.\n\nThe memoryProfiler module was planned in bd-ch6.7 (memory profiling / leak hunt) but the source file was never created. A stub or full implementation is needed.\n\nRelated: src/heapDiff.ts exists and is imported by server.ts at line 25 — the memoryProfiler likely wraps or supplements heapDiff functionality.\n\nFix: create src/memoryProfiler.ts exporting getMemoryProfiler() that returns a profiler object compatible with how server.ts uses it.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":0,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-02T18:18:03.468692907Z","updated_at":"2026-05-02T19:29:03.954818488Z","closed_at":"2026-05-02T19:29:03.954818488Z","close_reason":"Completed","source_repo":".","compaction_level":0,"comments":[{"id":13,"issue_id":"bf-5r8a","author":"cli","text":"MemoryProfiler module added to fix missing import in server.ts.\n\n## Retrospective\n- **What worked:** The file src/memoryProfiler.ts was already created (likely by another agent before I picked up the bead). All 90 web server tests pass, confirming the implementation is complete and correct.\n- **What didn't:** N/A — the file existed and was functional when I picked up the bead.\n- **Surprise:** The bead was opened for a missing file, but the file was already present (untracked) with a complete implementation. The file timestamp (May 2 14:27) suggests it was created after the bead was opened but before I picked it up.\n- **Reusable pattern:** For missing module imports, verify the current state first — files may be created by other agents while a bead is in the queue.","created_at":"2026-05-02T18:30:23.269027772Z"}]}
{"id":"bf-5u6j","title":"plan-gap: store.test.ts — add afterEach hook to reset global singletons","description":"Plan: Phase 1-9 implementation. Gap evidence: src/store.test.ts test 'should use default maxEvents of 10000' times out when run with full test suite, passes in isolation. Root cause: global singletons (WorkerAnalytics, CrossReferenceManager, HistoricalStore, SemanticNarrative, ErrorGroupManager, RecoveryManager, CostTracker) retain state across tests in the main describe block. Acceptance: add afterEach hook in main 'InMemoryEventStore' describe block that calls all available reset* functions; test suite passes with 0 timeouts.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","assignee":"marathon","created_at":"2026-05-26T21:38:27.737513760Z","updated_at":"2026-05-26T21:40:03.272386257Z","closed_at":"2026-05-26T21:40:03.272386257Z","close_reason":"Added afterEach hook to main InMemoryEventStore describe block that calls all available reset* functions (resetStore, resetWorkerAnalytics, resetCrossReferenceManager, resetHistoricalStore, resetErrorGroupManager, resetRecoveryManager, resetCostTracker). Test suite now passes with 2484 tests passed, 0 failures. Type-check and build also pass. Commit 8d75e48 pushed.","source_repo":".","compaction_level":0}

View file

@ -0,0 +1,16 @@
{
"bead_id": "bf-53q6",
"agent": "claude-code-glm-4.7",
"provider": "zai",
"model": "glm-4.7",
"exit_code": 0,
"outcome": "success",
"duration_ms": 204588,
"input_tokens": null,
"output_tokens": null,
"cost_usd": null,
"captured_at": "2026-06-07T14:15:39.628357775Z",
"trace_format": "claude_json",
"pruned": false,
"template_version": null
}

View file

@ -0,0 +1,3 @@
Running as unit: run-p956538-i9344013.scope; invocation ID: 0e5dbd7119064f0091c98bf2ad5bde2a
SessionEnd hook [/home/coding/.ccdash/hooks/session-end.sh] failed: /bin/sh: line 1: /home/coding/.ccdash/hooks/session-end.sh: cannot execute: required file not found

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
86d1d17e51916cef6f2bd6ad7fcfa09ce78ece81
e4569644ed8485a2a6d20f2f7e408c4c4956fac8

View file

@ -1,5 +1,6 @@
import React from 'react';
import { WorkerInfo, NeedleState } from '../types';
import WorkerMemoryBar from './WorkerMemoryBar';
const NEEDLE_STATE_LABELS: Record<NeedleState, string> = {
BOOTING: 'BOOTING',
@ -175,6 +176,13 @@ const WorkerGrid: React.FC<WorkerGridProps> = ({
</span>
<span>{formatLastActivity(worker.lastActivity)}</span>
</div>
<WorkerMemoryBar
rssKb={worker.rssKb}
peakRssKb={worker.peakRssKb}
rssLimitBytes={worker.rssLimitBytes}
rssPercent={worker.rssPercent}
swapKb={worker.swapKb}
/>
{worker.hasCollision && worker.activeFiles && worker.activeFiles.length > 0 && (
<div className="collision-warning">
<span style={{ fontSize: '0.7rem', color: '#ff9800' }}>

View file

@ -0,0 +1,141 @@
import React from 'react';
interface WorkerMemoryBarProps {
rssKb?: number | null;
peakRssKb?: number | null;
rssLimitBytes?: number | null;
rssPercent?: number | null;
swapKb?: number | null;
}
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)}GB`;
}
function getProgressColor(percent: number): string {
if (percent < 70) return '#4caf50'; // green
if (percent < 90) return '#ff9800'; // yellow
return '#f44336'; // red
}
export const WorkerMemoryBar: React.FC<WorkerMemoryBarProps> = ({
rssKb,
peakRssKb,
rssLimitBytes,
rssPercent,
swapKb,
}) => {
// Hide bar if no RSS data yet
if (rssKb === null || rssKb === undefined) {
return null;
}
const currentRssBytes = rssKb * 1024;
const peakRssBytes = peakRssKb ? peakRssKb * 1024 : null;
// Determine the ceiling for the bar
// If per-worker limit is set, use that; otherwise default to 4 GB
const limitBytes = rssLimitBytes || 4 * 1024 * 1024 * 1024;
const limitRssKb = limitBytes / 1024;
// Calculate percentages
const currentPercent = (currentRssBytes / limitBytes) * 100;
const peakPercent = peakRssBytes ? (peakRssBytes / limitBytes) * 100 : null;
const color = rssPercent !== null && rssPercent !== undefined
? getProgressColor(rssPercent)
: getProgressColor(currentPercent);
const hasSwap = swapKb !== null && swapKb !== undefined && swapKb > 0;
return (
<div className="worker-memory-bar">
<div
className="worker-memory-bar-track"
style={{ backgroundColor: 'rgba(255, 255, 255, 0.1)' }}
>
<div
className="worker-memory-bar-fill"
style={{
width: `${Math.min(100, currentPercent)}%`,
backgroundColor: color,
}}
/>
{peakPercent !== null && peakPercent !== undefined && peakPercent > currentPercent && (
<div
className="worker-memory-bar-peak"
style={{ left: `${Math.min(100, peakPercent)}%` }}
title={`Peak: ${formatBytes(peakRssBytes!)}`}
/>
)}
</div>
<div className="worker-memory-bar-info">
<span className="worker-memory-bar-text">
{formatBytes(currentRssBytes)} / {formatBytes(limitBytes)}
</span>
{hasSwap && (
<span
className="worker-memory-bar-swap"
title={`Swap: ${formatBytes(swapKb! * 1024)}`}
>
🔁
</span>
)}
</div>
<style>{`
.worker-memory-bar {
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.worker-memory-bar-track {
position: relative;
width: 100%;
height: 6px;
border-radius: 3px;
overflow: hidden;
}
.worker-memory-bar-fill {
height: 100%;
transition: width 0.5s ease, background-color 0.5s ease;
}
.worker-memory-bar-peak {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 4px rgba(255, 255, 255, 0.5);
}
.worker-memory-bar-info {
display: flex;
align-items: center;
gap: 0.375rem;
}
.worker-memory-bar-text {
font-size: 0.7rem;
color: var(--text-secondary, #aaa);
font-family: 'SF Mono', Monaco, monospace;
}
.worker-memory-bar-swap {
font-size: 0.65rem;
opacity: 0.7;
}
`}</style>
</div>
);
};
export default WorkerMemoryBar;

View file

@ -64,6 +64,13 @@ export interface WorkerInfo {
activeFiles?: string[];
stuck?: boolean;
stuckReason?: string;
// Memory fields from RSS sampling
rssKb?: number;
peakRssKb?: number;
rssLimitBytes?: number;
rssPercent?: number;
swapKb?: number;
pid?: number;
}
export interface FileCollision {

File diff suppressed because one or more lines are too long