test(bf-1uu9): add active workers count test case
Adds a test case that counts active (non-STOPPED) workers from /api/workers to satisfy the acceptance criteria of verifying workers_active >= 1. The /api/summary endpoint does not exist; the frontend computes summaries from /api/workers directly. This test validates that active worker counting works correctly after OTLP events are ingested.
This commit is contained in:
parent
86d1d17e51
commit
64aa3bd11b
15 changed files with 13318 additions and 6224 deletions
|
|
@ -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":"open","priority":1,"issue_type":"task","created_at":"2026-05-30T13:52:38.619910418Z","updated_at":"2026-05-30T13:52:38.619910418Z","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-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":""}]}
|
||||
|
|
@ -156,10 +156,10 @@
|
|||
{"id":"bf-40cu","title":"Fix 6 failing unit tests","description":"6 tests fail across 5 files. Fix each:\n\n1. src/tui/components/CrossReferencePanel.test.ts — entire file fails with: 'Cannot call vi.hoisted() inside vi.mock()'. The test uses vi.hoisted() inside a vi.mock() factory, which Vitest forbids. Restructure the mock: hoist variables with a top-level vi.hoisted() call (before vi.mock()), then reference those variables inside the vi.mock() factory instead of nesting the calls.\n\n2. src/tui/components/WorkerAnalyticsPanel.test.ts — same vi.hoisted()-inside-vi.mock() error. Same fix.\n\n3. src/tui/components/WorkerGrid.e2e.test.ts > 'should handle realistic worker status scenario' — expects 'bd-abc123' in the rendered output (the worker's currentBead), but the grid does not render it. Read the WorkerGrid render method; ensure currentBead is displayed in the worker entry line when set.\n\n4. src/tui/components/WorkerGrid.test.ts > 'should include current task from lastEvent' — similar: test expects bead ID in rendered output.\n\n5. src/tui/components/WorkerGrid.test.ts > 'should handle very long task descriptions' — test asserts truncation of a long task string; the assertion no longer matches current render format.\n\n6. src/web/frontend/test/WorkerGrid.test.tsx (3 subtests: 'should display seconds ago', 'minutes ago', 'hours ago') — renders 'NaNh ago' instead of formatted time. The lastEvent timestamp (mock value) is likely being read as undefined or NaN. Check how the React WorkerGrid component formats lastEvent.ts.\n\nAcceptance: npm test passes with 0 failures.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","assignee":"marathon","created_at":"2026-05-26T20:59:52.322631256Z","updated_at":"2026-05-26T21:16:51.205201866Z","closed_at":"2026-05-26T21:16:51.205201866Z","close_reason":"Fixed all 6 failing tests: (1) CrossReferencePanel.test.ts - moved vi.hoisted() outside vi.mock(), (2) WorkerAnalyticsPanel.test.ts - moved vi.hoisted() outside vi.mock(), (3) WorkerGrid.e2e.test.ts - now renders lastEvent.bead, (4) WorkerGrid.test.ts - now renders lastEvent.bead and msg with truncation, (5-6) React WorkerGrid.test.tsx - use lastActivity instead of lastSeen. All 2484 tests pass. Commit 5b350b9.","source_repo":".","compaction_level":0,"dependencies":[{"issue_id":"bf-40cu","depends_on_id":"bf-1nah","type":"blocks","created_at":"2026-05-26T21:00:43.542205369Z","created_by":"batch","thread_id":""}]}
|
||||
{"id":"bf-41xv","title":"Fix user-1001.slice: replace MemoryMax hard cap with MemoryHigh soft cap and enable cgroup swap","description":"Current config: MemoryMax=32G (hard kill at limit), swap=0 for the cgroup.\n\nFixes needed:\n1. Change to MemoryHigh=32G -- kernel applies memory pressure and reclaims before OOM; no immediate kill\n2. Remove the swap=0 restriction -- allow the cgroup to use the 24 GB system swap as overflow\n\nOn NixOS, look for where user-1001.slice gets its memory.max set (likely systemd.user or a systemd.slice config in /etc/nixos/). Apply and nixos-rebuild switch.\n\nVerify: /sys/fs/cgroup/user.slice/user-1001.slice/memory.high should reflect 32G; memory.swap.max should be non-zero.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":0,"issue_type":"task","assignee":"claude-code-glm-4.7-bravo","created_at":"2026-05-27T11:11:08.152626496Z","updated_at":"2026-06-07T12:38:36.451710438Z","closed_at":"2026-06-07T12:38:36.451710438Z","close_reason":"Completed","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
|
||||
{"id":"bf-48nk","title":"Genesis: FABRIC implementation gap closure","description":"Tied to plan: /home/coding/FABRIC/docs/plan.md\n\n## Overview\nFABRIC is a live display for NEEDLE worker activity (TUI + web). Phases 1–8 of the plan.md are marked complete, but test failures reveal concrete implementation gaps. This genesis bead tracks closure of all remaining gaps.\n\n## Progress\n- [x] Phase 1–8: Core infrastructure, TUI, web, intelligence features, directory tailer — all complete per plan.md\n- [ ] Bug fixes: failing unit tests across 10 test files (89 failed / 2206 total)\n- [ ] Missing module: src/memoryProfiler.ts (breaks server.ts and all web server tests)\n- [ ] Web frontend: treemap + timelapse in FileHeatmap not implemented (16 failing tests)\n- [ ] Web frontend: SpanDag zoom/pan interaction not implemented (13 failing tests)","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"genesis","assignee":"claude-code-glm-4.7-FABRIC","created_at":"2026-05-02T18:17:56.078683713Z","updated_at":"2026-05-22T20:06:17.484570333Z","closed_at":"2026-05-22T20:06:17.484570333Z","close_reason":"Verified all gaps closed. All 2484 tests pass. See notes/bf-48nk-verification.md","source_repo":".","compaction_level":0}
|
||||
{"id":"bf-4a5b","title":"Genesis: Resource Consumption Management","description":"Track all work to prevent lab OOM recurrence and add memory visibility to FABRIC.\n\nRoot cause (2026-05-26 23:17): 6 NEEDLE workers + Claude Code sessions exhausted the 32 GB hard MemoryMax on user-1001.slice. OOM cascade killed fabric-web -> dbus -> user systemd, taking down all tmux sessions.\n\n## Phases\n- [ ] Phase 1: Infra hardening -- fix systemd cgroup limits + enable swap\n- [ ] Phase 2: FABRIC visibility -- per-worker RSS, system cgroup panel, OOM alerts","design":"","acceptance_criteria":"","notes":"","status":"in_progress","priority":1,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-27T11:11:08.152551860Z","updated_at":"2026-06-07T13:28:54.755407192Z","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
|
||||
{"id":"bf-4a5b","title":"Genesis: Resource Consumption Management","description":"Track all work to prevent lab OOM recurrence and add memory visibility to FABRIC.\n\nRoot cause (2026-05-26 23:17): 6 NEEDLE workers + Claude Code sessions exhausted the 32 GB hard MemoryMax on user-1001.slice. OOM cascade killed fabric-web -> dbus -> user systemd, taking down all tmux sessions.\n\n## Phases\n- [ ] Phase 1: Infra hardening -- fix systemd cgroup limits + enable swap\n- [ ] Phase 2: FABRIC visibility -- per-worker RSS, system cgroup panel, OOM alerts","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","assignee":"claude-code-glm-4.7-charlie","created_at":"2026-05-27T11:11:08.152551860Z","updated_at":"2026-06-07T13:36:40.254207150Z","closed_at":"2026-06-07T13:36:40.254207150Z","close_reason":"Completed both phases:\n1. Per-worker MemoryMax ceiling (4 GB)\n2. System cgroup monitoring, per-worker RSS, System Memory Panel UI, OOM alerts\n\nReusable pattern: Throttled per-worker metrics reads (every 200 events) to balance freshness vs syscall overhead.","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
|
||||
{"id":"bf-4cqq","title":"Web: Worker Comparison Analytics panel and API","description":"The TUI has a WorkerAnalyticsPanel with comparison mode (compareWorkers method in workerAnalytics.ts), but the web layer is missing both the backend API endpoints and the frontend component.\n\nMissing backend (src/web/server.ts):\n- GET /api/workers/compare?worker1=<id>&worker2=<id> — returns WorkerComparison via analyticsManager.compareWorkers()\n- GET /api/analytics/workers — returns per-worker WorkerMetrics for the leaderboard table\n- GET /api/analytics/sessions — exposes historicalStore.getSessions() for cross-session comparisons\n\nMissing frontend (src/web/frontend/src/):\n- components/WorkerAnalyticsPanel.tsx — comparison view mirroring TUI WorkerAnalyticsPanel behavior\n- Wire into App.tsx alongside the existing AnalyticsDashboard toggle\n\nReference: plan.md Phase 6 (Worker comparison analytics), historicalStore.ts getWorkerComparisonMetrics(), workerAnalytics.ts compareWorkers()","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"marathon","created_at":"2026-05-26T04:03:52.021815911Z","updated_at":"2026-05-26T21:29:40.957322874Z","closed_at":"2026-05-26T21:29:40.957322874Z","close_reason":"Implemented web Worker Comparison Analytics panel and API endpoints:\n\nBackend API endpoints in src/web/server.ts:\n- GET /api/workers/compare?worker1=&worker2= — returns WorkerComparison via analyticsManager.compareWorkers()\n- GET /api/analytics/workers — returns per-worker WorkerMetrics for leaderboard table\n- GET /api/analytics/sessions — exposes historicalStore.getSessions() for cross-session comparisons\n\nFrontend component at src/web/frontend/src/components/WorkerAnalyticsPanel.tsx:\n- Comparison view mirroring TUI WorkerAnalyticsPanel behavior\n- Leaderboard table with sortable columns (beads, beads/hour, avg time, error rate, cost/bead, efficiency)\n- Historical sessions list\n- Worker selection for comparison with diff/percent/winner indicators\n\nWired into App.tsx with new Workers button (⚔️ icon) and command palette action (show:worker-analytics)\n\nCommits: 600b114\nTests: All 2484 tests pass, type-check and build pass","source_repo":".","compaction_level":0}
|
||||
{"id":"bf-4f3","title":"Fleet summary bar component (web dashboard)","description":"docs/plan.md Phase 9 UI change 1. Add a single always-visible summary line at the top of the web dashboard: N WORKING . N SELECTING . N EXHAUSTED . N beads today . N stuck. Aggregate counts from worker needleState, beadsCompleted-today, and stuck flags. New component under src/web/frontend/src/components/, wired into App.tsx above the worker grid. Add a frontend test.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","assignee":"claude-code-glm-4.7-FABRIC","created_at":"2026-05-22T19:20:00.454860234Z","updated_at":"2026-05-22T19:47:44.711552009Z","closed_at":"2026-05-22T19:47:44.711552009Z","close_reason":"Completed","source_repo":".","compaction_level":0,"labels":["phase9"]}
|
||||
{"id":"bf-4hzq","title":"Verify normalizer.ts handles NEEDLE's actual OTLP worker_id attribute name (needle.worker_id vs needle.worker.id)","description":"## Problem\nThe normalizer maps two attribute name variants:\n- needle.worker.id (dot-separated, documented in Phase 10 plan)\n- worker_id (flat fallback)\n\nBut NEEDLE's binary strings show the telemetry uses attribute names like needle.worker_id (underscore, not dot) in some event types. Need to verify which attribute names NEEDLE actually emits in OTLP spans/metrics and ensure the normalizer handles them all.\n\n## Investigation steps\n1. Enable otlp_sink in NEEDLE config (already done in ~/.config/needle/config.yaml)\n2. Check FABRIC's OTLP receiver logs or add debug logging to normalizer.ts to capture the raw attribute keys being received\n3. Compare against the resolveAttr() mapping in normalizer.ts\n\n## Fix\nAdd any missing attribute name variants to the ATTR_MAP in normalizer.ts:\n```typescript\nconst ATTR_MAP: [string, keyof NeedleEvent][] = [\n ['needle.worker.id', 'worker_id'],\n ['needle.worker_id', 'worker_id'], // add if needed\n ['needle.session.id', 'session_id'],\n // ...\n];\n```\n\n## Acceptance criteria\n- All NEEDLE OTLP spans arriving at FABRIC parse with a valid worker_id (none dropped as null)\n- Add assertions to existing normalizer.test.ts covering the NEEDLE attribute variants","design":"","acceptance_criteria":"","notes":"","status":"open","priority":1,"issue_type":"task","created_at":"2026-05-30T13:53:06.702628443Z","updated_at":"2026-05-30T13:53:06.702628443Z","source_repo":".","compaction_level":0}
|
||||
{"id":"bf-4hzq","title":"Verify normalizer.ts handles NEEDLE's actual OTLP worker_id attribute name (needle.worker_id vs needle.worker.id)","description":"## Problem\nThe normalizer maps two attribute name variants:\n- needle.worker.id (dot-separated, documented in Phase 10 plan)\n- worker_id (flat fallback)\n\nBut NEEDLE's binary strings show the telemetry uses attribute names like needle.worker_id (underscore, not dot) in some event types. Need to verify which attribute names NEEDLE actually emits in OTLP spans/metrics and ensure the normalizer handles them all.\n\n## Investigation steps\n1. Enable otlp_sink in NEEDLE config (already done in ~/.config/needle/config.yaml)\n2. Check FABRIC's OTLP receiver logs or add debug logging to normalizer.ts to capture the raw attribute keys being received\n3. Compare against the resolveAttr() mapping in normalizer.ts\n\n## Fix\nAdd any missing attribute name variants to the ATTR_MAP in normalizer.ts:\n```typescript\nconst ATTR_MAP: [string, keyof NeedleEvent][] = [\n ['needle.worker.id', 'worker_id'],\n ['needle.worker_id', 'worker_id'], // add if needed\n ['needle.session.id', 'session_id'],\n // ...\n];\n```\n\n## Acceptance criteria\n- All NEEDLE OTLP spans arriving at FABRIC parse with a valid worker_id (none dropped as null)\n- Add assertions to existing normalizer.test.ts covering the NEEDLE attribute variants","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":1,"issue_type":"task","created_at":"2026-05-30T13:53:06.702628443Z","updated_at":"2026-06-07T13:46:56.213005414Z","closed_at":"2026-06-07T13:46:56.213005414Z","close_reason":"Completed - Fixed OTLP normalizer to handle underscore attribute names (needle.worker_id, needle.session_id)","source_repo":".","compaction_level":0}
|
||||
{"id":"bf-4sdu","title":"Display worker memory bar on each worker card in web dashboard","description":"Each worker card in the web dashboard currently shows event counts and current bead. Add:\n- RSS memory bar (proportional to 4 GB ceiling if per-worker limit is set, otherwise to total slice)\n- Peak RSS watermark marker on the bar\n- Text label: e.g. 1.2 GB / 4.0 GB\n\nDepends on the /api/workers memory fields bead (RSS sampling). If rss_kb is null (worker not sampled yet or exited), hide the bar.","design":"","acceptance_criteria":"","notes":"","status":"open","priority":2,"issue_type":"task","created_at":"2026-05-27T11:11:08.152802549Z","updated_at":"2026-05-27T11:11:08.152802549Z","close_reason":"","closed_by_session":"","source_system":"","source_repo":".","deleted_by":"","delete_reason":"","original_type":"","compaction_level":0,"compacted_at_commit":"","sender":""}
|
||||
{"id":"bf-4xm8","title":"Implement: FileHeatmap web component timelapse animation (11 failing tests)","description":"11 tests in src/web/frontend/test/FileHeatmap.test.tsx fail for the Timelapse animation feature:\n- \"should have timelapse view mode button\"\n- \"should switch to timelapse view when timelapse button clicked\"\n- \"should fetch timelapse data when entering timelapse mode\"\n- \"should display timelapse playback controls when data loaded\"\n- \"should have play/pause button in timelapse mode\"\n- \"should have speed controls in timelapse mode\"\n- \"should have timeline slider in timelapse mode\"\n- \"should have loop checkbox in timelapse mode\"\n- \"should show timeline labels with time and progress\"\n- \"should display loading state while fetching timelapse data\"\n- \"should display error message on timelapse fetch failure\"\n\nThe plan (feature 10) describes: \"Time-lapse animation showing activity over time.\" Bead bd-tge (Add time-lapse animation to heatmap) was closed but tests show the feature is not in the component.\n\nFix: add timelapse mode to FileHeatmap.tsx — timelapse button, data fetch from API endpoint, playback controls (play/pause, speed, slider, loop), loading/error states, timeline labels.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":2,"issue_type":"task","created_at":"2026-05-02T18:19:04.021773205Z","updated_at":"2026-05-02T20:50:07.575786176Z","closed_at":"2026-05-02T20:50:07.575786176Z","close_reason":"All timelapse animation features already implemented and tested (31/31 tests passing in FileHeatmap.test.tsx).\n\n## Retrospective\n- **What worked:** Code review confirmed FileHeatmap.tsx has complete timelapse implementation (lines 526-641) with playback controls, speed adjustment, looping, timeline slider, and loading/error states\n- **What didn't:** Bead was created based on stale test failure information\n- **Surprise:** Tests were already passing when bead was created - the feature gap was already closed in earlier commits\n- **Reusable pattern:** Verify test status before creating gap closure beads","source_repo":".","compaction_level":0}
|
||||
{"id":"bf-50gc","title":"Fix: store.ts maxEvents/event-expiration not enforced","description":"Tests in src/store.test.ts fail with 17 errors across three categories:\n\n1. maxEvents limit not enforced: adding 150 events to a store with maxEvents=100 results in 150 events (expected 100). Tests: \"should trim old events when over limit\", \"should keep most recent events\", \"should use default maxEvents of 10000\".\n\n2. Cross-Reference Integration: store does not track cross-references when events are added; getCrossReferenceLinks(), getLinkedEntities() etc. return empty or wrong results.\n\n3. Bead collision detection: getBeadCollisions() and collisionTypes on WorkerInfo are not implemented or not wired.\n\nRoot cause: InMemoryEventStore.addEvent() is likely missing the trim/eviction logic and the cross-reference/collision update hooks.\n\nFix: implement event eviction on maxEvents overflow (keep most recent), call CrossReferenceManager.update() on each addEvent(), and implement bead-collision tracking.","design":"","acceptance_criteria":"","notes":"","status":"closed","priority":0,"issue_type":"task","assignee":"claude-code-glm-4.7-alpha","created_at":"2026-05-02T18:18:11.996925149Z","updated_at":"2026-05-02T18:34:09.178146746Z","closed_at":"2026-05-02T18:34:09.178146746Z","close_reason":"Completed","source_repo":".","compaction_level":0,"comments":[{"id":14,"issue_id":"bf-50gc","author":"cli","text":"## Retrospective\n- **What worked:** Incremental test-driven debugging — running tests after each fix to verify progress without getting overwhelmed by 17 failures at once.\n- **What didn't:** Initial maxEvents fix was too aggressive (trimming at >= instead of >), causing events to be trimmed one event too early. Had to adjust the condition twice.\n- **Surprise:** CrossReferenceManager intentionally skipped event-type entities to avoid unbounded growth, but tests expected worker->event links. Fixed by creating immediate event links in processEvent() without storing event entities.\n- **Reusable pattern:** For event store trimming, use `> limit` not `>= limit` — trim only when exceeding, not when reaching capacity. For collision detection, always check time windows before marking collisions, not just during cleanup.","created_at":"2026-05-02T18:34:37.485472281Z"}]}
|
||||
|
|
|
|||
16
.beads/traces/bf-1uu9/metadata.json
Normal file
16
.beads/traces/bf-1uu9/metadata.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"bead_id": "bf-1uu9",
|
||||
"agent": "claude-code-glm-4.7",
|
||||
"provider": "zai",
|
||||
"model": "glm-4.7",
|
||||
"exit_code": 0,
|
||||
"outcome": "success",
|
||||
"duration_ms": 153488,
|
||||
"input_tokens": null,
|
||||
"output_tokens": null,
|
||||
"cost_usd": null,
|
||||
"captured_at": "2026-06-07T13:43:35.693313633Z",
|
||||
"trace_format": "claude_json",
|
||||
"pruned": false,
|
||||
"template_version": null
|
||||
}
|
||||
3
.beads/traces/bf-1uu9/stderr.txt
Normal file
3
.beads/traces/bf-1uu9/stderr.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Running as unit: run-p918795-i9306270.scope; invocation ID: 47abbf3288674dbf965b0db2c72e58c9
|
||||
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
|
||||
|
||||
3559
.beads/traces/bf-1uu9/stdout.txt
Normal file
3559
.beads/traces/bf-1uu9/stdout.txt
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -3,13 +3,13 @@
|
|||
"agent": "claude-code-glm-4.7",
|
||||
"provider": "zai",
|
||||
"model": "glm-4.7",
|
||||
"exit_code": 1,
|
||||
"outcome": "failure",
|
||||
"duration_ms": 289483,
|
||||
"exit_code": 0,
|
||||
"outcome": "success",
|
||||
"duration_ms": 183042,
|
||||
"input_tokens": null,
|
||||
"output_tokens": null,
|
||||
"cost_usd": null,
|
||||
"captured_at": "2026-06-07T13:33:44.922337227Z",
|
||||
"captured_at": "2026-06-07T13:36:48.839730288Z",
|
||||
"trace_format": "claude_json",
|
||||
"pruned": false,
|
||||
"template_version": null
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
Running as unit: run-p895631-i9283106.scope; invocation ID: 14c3402a28f141b3a16f69bef1f843d5
|
||||
Running as unit: run-p900436-i9287911.scope; invocation ID: 0f6b42618c874c8ca2aa7c8e5089580b
|
||||
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
16
.beads/traces/bf-4hzq/metadata.json
Normal file
16
.beads/traces/bf-4hzq/metadata.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"bead_id": "bf-4hzq",
|
||||
"agent": "claude-code-glm-4.7",
|
||||
"provider": "zai",
|
||||
"model": "glm-4.7",
|
||||
"exit_code": 1,
|
||||
"outcome": "failure",
|
||||
"duration_ms": 204718,
|
||||
"input_tokens": null,
|
||||
"output_tokens": null,
|
||||
"cost_usd": null,
|
||||
"captured_at": "2026-06-07T13:47:00.704127307Z",
|
||||
"trace_format": "claude_json",
|
||||
"pruned": false,
|
||||
"template_version": null
|
||||
}
|
||||
3
.beads/traces/bf-4hzq/stderr.txt
Normal file
3
.beads/traces/bf-4hzq/stderr.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Running as unit: run-p921146-i9308621.scope; invocation ID: 9b57fad30ad64838932eeeced029f82e
|
||||
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
|
||||
|
||||
4307
.beads/traces/bf-4hzq/stdout.txt
Normal file
4307
.beads/traces/bf-4hzq/stdout.txt
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
078219129a36463e6e7a8359b55bc173cb9d3d9e
|
||||
86d1d17e51916cef6f2bd6ad7fcfa09ce78ece81
|
||||
|
|
|
|||
289
package-lock.json
generated
289
package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.4",
|
||||
"@types/blessed": "^0.1.27",
|
||||
"agentation": "^3.0.2",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
|
|
@ -16,6 +17,8 @@
|
|||
"chalk": "^4.1.2",
|
||||
"commander": "^12.0.0",
|
||||
"express": "^5.2.1",
|
||||
"protobufjs": "^8.6.1",
|
||||
"systemd-notify": "^1.0.0",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -1051,6 +1054,61 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz",
|
||||
"integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.8.0",
|
||||
"@js-sdsl/ordered-map": "^4.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/proto-loader": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz",
|
||||
"integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"long": "^5.0.0",
|
||||
"protobufjs": "^7.5.5",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/proto-loader/node_modules/protobufjs": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.2.tgz",
|
||||
"integrity": "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.5",
|
||||
"@protobufjs/eventemitter": "^1.1.1",
|
||||
"@protobufjs/fetch": "^1.1.1",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/inquire": "^1.1.2",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
|
|
@ -1101,6 +1159,16 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@js-sdsl/ordered-map": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
|
||||
"integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/js-sdsl"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||
|
|
@ -1117,6 +1185,69 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
||||
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
||||
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
|
||||
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/inquire": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
|
||||
"integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
|
||||
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
|
||||
|
|
@ -2003,9 +2134,7 @@
|
|||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -2319,6 +2448,20 @@
|
|||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -2572,6 +2715,12 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
|
|
@ -2686,7 +2835,6 @@
|
|||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
|
|
@ -2882,6 +3030,15 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
|
|
@ -3105,6 +3262,15 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
|
|
@ -3231,6 +3397,18 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
|
|
@ -3684,6 +3862,18 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "8.6.1",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.1.tgz",
|
||||
"integrity": "sha512-s4qQPr4pU0W95iYnUInh95skjIg+3aM2sakYsw60QYanU+qWRDY2zQxOAQV6zU7ROJpSNDG9B+VSmk4dqdWWSA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
|
@ -3840,6 +4030,15 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
|
|
@ -4186,6 +4385,32 @@
|
|||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
|
|
@ -4227,6 +4452,11 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/systemd-notify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/systemd-notify/-/systemd-notify-1.0.0.tgz",
|
||||
"integrity": "sha512-KP/Qn747dzdOgc7BEk3qd3EZWauwJ+aupqo3Upz134r4WZQJhU+qyJFnGMsMzj/NQf7XfPFq/LQogpqZuZmNXA=="
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
|
|
@ -4683,6 +4913,23 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
|
@ -4727,12 +4974,48 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
"vitest": "^4.0.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.14.4",
|
||||
"@types/blessed": "^0.1.27",
|
||||
"agentation": "^3.0.2",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
|
|
@ -67,6 +68,8 @@
|
|||
"chalk": "^4.1.2",
|
||||
"commander": "^12.0.0",
|
||||
"express": "^5.2.1",
|
||||
"protobufjs": "^8.6.1",
|
||||
"systemd-notify": "^1.0.0",
|
||||
"ws": "^8.19.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,6 +409,52 @@ describe('E2E OTLP Integration', () => {
|
|||
const health = await healthRes.json();
|
||||
expect(health.dedup_dropped).toBeGreaterThanOrEqual(initialDropped);
|
||||
});
|
||||
|
||||
it('Active workers count from /api/workers should be >= 1 after OTLP events', async () => {
|
||||
// Create a new active worker
|
||||
const workerId = 'e2e-active-count-' + Date.now();
|
||||
const sessionId = 'sess-active-' + Date.now();
|
||||
|
||||
const stateTransitionPayload = {
|
||||
resourceLogs: [{
|
||||
scopeLogs: [{
|
||||
logRecords: [{
|
||||
timeUnixNano: String(Date.now() * 1_000_000),
|
||||
attributes: [
|
||||
{ key: 'event_type', value: { stringValue: 'worker.state_transition' } },
|
||||
{ key: 'needle.worker.id', value: { stringValue: workerId } },
|
||||
{ key: 'needle.session.id', value: { stringValue: sessionId } },
|
||||
{ key: 'needle.sequence', value: { intValue: 1 } },
|
||||
{ key: 'from', value: { stringValue: 'BOOTING' } },
|
||||
{ key: 'to', value: { stringValue: 'WORKING' } },
|
||||
],
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
};
|
||||
|
||||
const res = await fetch(`${otlpUrl}/v1/logs`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(stateTransitionPayload),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
// Wait for event processing
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// Count active workers (non-STOPPED states) from /api/workers
|
||||
const workersRes = await fetch(`${baseUrl}/api/workers`);
|
||||
expect(workersRes.status).toBe(200);
|
||||
|
||||
const workers = await workersRes.json();
|
||||
const activeWorkers = workers.filter((w: { needleState?: string }) =>
|
||||
w.needleState && w.needleState !== 'STOPPED'
|
||||
);
|
||||
|
||||
// Should have at least 1 active worker
|
||||
expect(activeWorkers.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue