vibepulse 0.2.2 → 0.3.0-beta.0
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +1 -0
- package/.next/build-manifest.json +2 -2
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/fallback-build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +8 -0
- package/.next/server/app/_global-error/page.js +1 -1
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/server/app/api/node/sessions/route.js +5 -3
- package/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
- package/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
- package/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
- package/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
- package/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/sessions/route.js +4 -2
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__56f5f249._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__98073dd6._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__d8e61048._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__f441109e._.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js.map +1 -0
- package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
- package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js.map +1 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js.map → [root-of-the-server]__c91a8380._.js.map} +1 -1
- package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
- package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js.map +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +8 -0
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/node/sessions/route.js +5 -3
- package/.next/standalone/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
- package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
- package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/sessions/route.js +4 -2
- package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
- package/.next/standalone/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/b3bc362202331708.css +3 -0
- package/.next/standalone/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
- package/.next/standalone/README.md +29 -5
- package/.next/standalone/docs/session-status-detection.md +36 -0
- package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md +39 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.test.ts +60 -1
- package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.ts +77 -22
- package/.next/standalone/src/app/api/node/sessions/route.test.ts +282 -0
- package/.next/standalone/src/app/api/node/sessions/route.ts +141 -17
- package/.next/standalone/src/app/api/opencode-events/route.test.ts +3 -1
- package/.next/standalone/src/app/api/sessions/[id]/archive/route.test.ts +101 -0
- package/.next/standalone/src/app/api/sessions/[id]/archive/route.ts +47 -12
- package/.next/standalone/src/app/api/sessions/[id]/delete/route.test.ts +92 -0
- package/.next/standalone/src/app/api/sessions/[id]/delete/route.ts +45 -10
- package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.test.ts +74 -0
- package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.ts +22 -2
- package/.next/standalone/src/app/api/sessions/[id]/restore/route.test.ts +186 -0
- package/.next/standalone/src/app/api/sessions/[id]/restore/route.ts +184 -0
- package/.next/standalone/src/app/api/sessions/route.test.ts +1889 -107
- package/.next/standalone/src/app/api/sessions/route.ts +365 -981
- package/.next/standalone/src/components/KanbanBoard.test.tsx +307 -1
- package/.next/standalone/src/components/KanbanBoard.tsx +105 -18
- package/.next/standalone/src/components/ProjectCard.test.tsx +416 -2
- package/.next/standalone/src/components/ProjectCard.tsx +238 -86
- package/.next/standalone/src/components/SessionCard.test.tsx +253 -2
- package/.next/standalone/src/components/SessionCard.tsx +182 -76
- package/.next/standalone/src/hooks/useOpencodeSync.test.ts +321 -1
- package/.next/standalone/src/hooks/useOpencodeSync.ts +16 -12
- package/.next/standalone/src/lib/claudeSessionOverrides.test.ts +75 -0
- package/.next/standalone/src/lib/claudeSessionOverrides.ts +169 -0
- package/.next/standalone/src/lib/session-providers/claudeCode.test.ts +2288 -0
- package/.next/standalone/src/lib/session-providers/claudeCode.ts +1083 -0
- package/.next/standalone/src/lib/session-providers/localAggregator.test.ts +322 -0
- package/.next/standalone/src/lib/session-providers/localAggregator.ts +302 -0
- package/.next/standalone/src/lib/session-providers/opencodeProvider.ts +723 -0
- package/.next/standalone/src/lib/session-providers/providerIds.test.ts +337 -0
- package/.next/standalone/src/lib/session-providers/providerIds.ts +176 -0
- package/.next/standalone/src/lib/session-providers/types.ts +131 -0
- package/.next/standalone/src/lib/transform.test.ts +253 -0
- package/.next/standalone/src/lib/transform.ts +96 -37
- package/.next/standalone/src/types/index.ts +23 -17
- package/.next/static/chunks/b3bc362202331708.css +3 -0
- package/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/README.md +29 -5
- package/package.json +1 -1
- package/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__2f981540._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__3745b314._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__6c428a24._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__73a00b88._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__db285678._.js.map +0 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
- package/.next/standalone/.next/static/chunks/f42202943f6742e5.css +0 -3
- package/.next/static/chunks/f42202943f6742e5.css +0 -3
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
|
@@ -8,23 +8,32 @@ A tiny dashboard that sits in your browser tab — tired of switching IDE tabs j
|
|
|
8
8
|
|
|
9
9
|
## What It Does
|
|
10
10
|
|
|
11
|
-
- **Kanban board** — Auto-discovers OpenCode sessions, organizes them into Idle / Busy / Review / Done
|
|
11
|
+
- **Kanban board** — Auto-discovers OpenCode sessions and host-global Claude Code sessions, organizes them into Idle / Busy / Review / Done
|
|
12
12
|
- **Remote Nodes** — Connect multiple VibePulse instances to a single hub for a unified view
|
|
13
13
|
- **Audio alerts** — Makes a sound when sessions complete or need attention
|
|
14
14
|
- **Zero setup** — No manual card creation; auto-scans ports and processes
|
|
15
15
|
- **Profile switcher** — Flip between Oh My OpenAgent presets without touching config files
|
|
16
16
|
|
|
17
|
+
## Claude Code Support
|
|
18
|
+
VibePulse includes experimental, capability-aware support for tracking local [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) sessions:
|
|
19
|
+
- **Host-Global Discovery:** Automatically detects all Claude Code sessions running on the machine, aggregating projects seamlessly.
|
|
20
|
+
- **Explicit Capabilities:** Supported actions are explicitly modeled per provider. Claude now supports VibePulse-managed `archive` and `delete`, while `openEditor` remains disabled until a provider-safe execution path is defined.
|
|
21
|
+
- **Visual Differentiation:** Mixed environments feature distinct visual indicators distinguishing OpenCode and Claude groups inside projects.
|
|
22
|
+
- **Robust Liveness Semantics:** Stale busy states and zombie parsing are prevented at the provider boundary using stricter liveness verification.
|
|
23
|
+
- **Polling Integration:** Claude status updates via polling only locally and remotely. Live SSE streams and real-time event parity are not supported.
|
|
24
|
+
- **Artifact-Backed Child Topology:** Child sessions are exposed only when verified by authoritative artifact-backed linkage. No transcript rendering is supported.
|
|
25
|
+
|
|
17
26
|
## Quick Start
|
|
18
27
|
|
|
19
28
|
### Hub Mode (Default)
|
|
20
|
-
Run VibePulse locally to monitor your local
|
|
29
|
+
Run VibePulse locally to monitor your local sessions and manage remote nodes.
|
|
21
30
|
```bash
|
|
22
31
|
npx vibepulse
|
|
23
32
|
```
|
|
24
33
|
Open http://localhost:3456
|
|
25
34
|
|
|
26
35
|
### Node Mode
|
|
27
|
-
Run VibePulse on a remote server to expose its OpenCode sessions to a hub.
|
|
36
|
+
Run VibePulse on a remote server to expose its local OpenCode and Claude Code sessions to a hub.
|
|
28
37
|
```bash
|
|
29
38
|
npx vibepulse --serve
|
|
30
39
|
```
|
|
@@ -43,9 +52,9 @@ Node mode requires an access token for security. See [Architecture](#architectur
|
|
|
43
52
|
|
|
44
53
|
## Architecture
|
|
45
54
|
|
|
46
|
-
VibePulse uses a Hub-and-Node architecture to aggregate OpenCode sessions across different machines.
|
|
55
|
+
VibePulse uses a Hub-and-Node architecture to aggregate OpenCode and Claude Code sessions across different machines.
|
|
47
56
|
|
|
48
|
-
1. **Node**: A VibePulse instance running with `--serve`. It interacts directly with the local OpenCode SDK and exposes an API.
|
|
57
|
+
1. **Node**: A VibePulse instance running with `--serve`. It interacts directly with the local OpenCode SDK and Claude Code file-system artifacts and exposes an API.
|
|
49
58
|
2. **Hub**: The primary VibePulse instance (default mode). It connects to one or more Nodes to collect session data.
|
|
50
59
|
|
|
51
60
|
### Connecting a Remote Node
|
|
@@ -64,6 +73,21 @@ npm install
|
|
|
64
73
|
npm run dev
|
|
65
74
|
```
|
|
66
75
|
|
|
76
|
+
### Claude Integration Verification
|
|
77
|
+
If modifying Claude Code integration, run the targeted regression matrix to ensure discovery order, bounded idle fallback, capability alignment, artifact-backed child topology, stronger liveness semantics, and mixed Claude/OpenCode visual behavior all stay intact:
|
|
78
|
+
|
|
79
|
+
| Area | Command |
|
|
80
|
+
|------|---------|
|
|
81
|
+
| Claude discovery + topology rules | `npm run test:run -- src/lib/session-providers/claudeCode.test.ts` |
|
|
82
|
+
| Capability alignment + rejection | `npm run test:run -- src/lib/session-providers/providerIds.test.ts src/app/api/sessions/route.test.ts` |
|
|
83
|
+
| Mixed project-group visual behavior | `npm run test:run -- src/components/ProjectCard.test.tsx src/components/SessionCard.test.tsx` |
|
|
84
|
+
| Local provider aggregation and route wiring | `npm run test:run -- src/lib/transform.test.ts src/hooks/useOpencodeSync.test.ts src/app/api/node/sessions/route.test.ts` |
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm run test:run -- src/lib/session-providers/providerIds.test.ts src/lib/session-providers/claudeCode.test.ts src/components/ProjectCard.test.tsx src/components/SessionCard.test.tsx src/app/api/sessions/route.test.ts src/app/api/node/sessions/route.test.ts src/lib/transform.test.ts src/hooks/useOpencodeSync.test.ts
|
|
88
|
+
npm run lint && npm run build
|
|
89
|
+
```
|
|
90
|
+
|
|
67
91
|
## Tech Stack
|
|
68
92
|
|
|
69
93
|
- Next.js (App Router) + TypeScript
|
|
@@ -158,6 +158,42 @@ sequenceDiagram
|
|
|
158
158
|
|
|
159
159
|
---
|
|
160
160
|
|
|
161
|
+
## Claude Code Support Boundary
|
|
162
|
+
|
|
163
|
+
VibePulse includes experimental host-global discovery for Claude Code sessions (`provider = 'claude-code'`). The detection mechanisms used for OpenCode differ for Claude Code due to its specific provider boundaries:
|
|
164
|
+
|
|
165
|
+
- **Host-Global Discovery:** Uses file-system artifacts (`~/.claude/projects/`, `~/.claude/sessions/`) across the entire host to automatically detect active projects without requiring current-repo constraints.
|
|
166
|
+
- **Artifact-Backed Child Topology:** Nested child sessions are fully supported via local polling and remote node-payload propagation. They roll up naturally into parent card logic. This topology relies entirely on explicit artifact-backed relationships—preventing "guessed" relationships.
|
|
167
|
+
- **Polling Isolation & No SSE Parity:** Claude status updates, including child topology, work via polling only. There is no Claude SSE stream, and therefore no event-level parity with OpenCode's real-time channels.
|
|
168
|
+
- **Robust Stale-Busy Mitigation:**
|
|
169
|
+
- Status inference uses explicit liveness checks (pid polling/verification) at the provider boundary to ensure zombie processes do not stall the UI in a "busy" state.
|
|
170
|
+
- Active pid mapping yields `realTimeStatus = 'busy'`.
|
|
171
|
+
- Artifact-only fallback with dead pids maps to `realTimeStatus = 'idle'`.
|
|
172
|
+
- Claude never emits `retry`, but it can emit `waitingForUser = true` when fresh transcript evidence shows either a direct assistant question or a pending `tool_use` approval with no later user/tool_result resolution.
|
|
173
|
+
- **Capability-Aware Contracts:** Claude sessions have discrete capability scopes. Claude now supports VibePulse-managed `archive` and `delete`, while `open-editor` remains explicitly disabled until a provider-safe execution path is available.
|
|
174
|
+
|
|
175
|
+
### Claude "Waiting for User" Decision Rules (Detailed)
|
|
176
|
+
|
|
177
|
+
Claude detection is transcript-tail based and event-order sensitive:
|
|
178
|
+
|
|
179
|
+
1. **Freshness gate**
|
|
180
|
+
- If the transcript artifact is older than the waiting window (currently 10 minutes), `waitingForUser = false`.
|
|
181
|
+
2. **Scan from newest to oldest event**
|
|
182
|
+
- If the first relevant newest event is a **user** turn, `waitingForUser = false`.
|
|
183
|
+
- If the first relevant newest event is an **assistant** turn:
|
|
184
|
+
- `stop_reason === tool_use` or message content contains `tool_use` → `waitingForUser = true`
|
|
185
|
+
- `stop_reason === end_turn` and assistant text ends with `?` / `?` → `waitingForUser = true`
|
|
186
|
+
- otherwise → `waitingForUser = false`
|
|
187
|
+
3. **Restore suppression**
|
|
188
|
+
- Immediately after restore, stale waiting signals can be suppressed until new transcript activity appears.
|
|
189
|
+
|
|
190
|
+
Practical implication for board status:
|
|
191
|
+
|
|
192
|
+
- Claude can be `realTimeStatus = idle` **and** `waitingForUser = true` at the same time.
|
|
193
|
+
- In that case, Kanban maps the session to `review` ("Needs Attention") by design, because the session is waiting for user input/approval rather than actively running.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
161
197
|
## Detection Limitations (Shortcomings)
|
|
162
198
|
|
|
163
199
|
### Root Cause: Unreliable Signal Source
|
package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Claude Capability Alignment Design
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Align Claude Code and OpenCode capabilities in VibePulse through an explicit capability matrix. We also need to fix stale busy status prediction and clearly differentiate providers in the UI.
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
VibePulse recently added global Claude discovery alongside read-only Claude cards. The footer open state is now restored. We still have a known stale-busy problem rooted in weak process ID validation. Current documentation describes Claude as read-only with no action parity. This specification defines the next expansion iteration to replace blunt read-only gating with specific capability flags.
|
|
8
|
+
|
|
9
|
+
## Non-goals
|
|
10
|
+
We won't build full feature parity for Claude if the underlying CLI lacks support. We also won't rewrite the core Next.js aggregation engine.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
The architecture relies on our existing separation of concerns. Provider contracts live in `src/lib/session-providers/*` and `src/types/index.ts`. Transformation logic resides in `src/lib/transform.ts`. UI action gating happens in `src/components/ProjectCard.tsx`, `src/components/SessionCard.tsx`, and `src/components/KanbanBoard.tsx`. Action routes remain in `src/app/api/sessions/[id]/*`. Implementation is strictly scoped to these boundaries.
|
|
14
|
+
|
|
15
|
+
## Capability Model
|
|
16
|
+
We replace `readOnly` as our sole capability proxy with explicit action capabilities. A new capability matrix covers `openProject`, `openEditor`, `archive`, and `delete`. We keep the `readOnly` flag but narrow its meaning to indicate basic UI protection.
|
|
17
|
+
|
|
18
|
+
Current evidence shows Claude archive and delete official support is unclear. The capability matrix must allow staged enablement. We won't pretend parity exists. If Claude cannot safely archive a workspace, the matrix explicitly sets `archive: false`.
|
|
19
|
+
|
|
20
|
+
## Status Model
|
|
21
|
+
The busy status fix lives at the Claude provider boundary instead of being patched in the UI. When liveness evidence is weak, we prefer false-idle over false-busy for Claude. This specific logic prevents sessions from getting stuck in a busy state when the underlying process might actually be dead or sleeping.
|
|
22
|
+
|
|
23
|
+
## UI Design
|
|
24
|
+
Visual differentiation uses a mixed strategy. The interface will show a project-level primary provider identity. Individual session rows receive a lightweight indicator. Projects with sessions from multiple providers get explicit mixed-group treatment to avoid user confusion.
|
|
25
|
+
|
|
26
|
+
## API and Route Enforcement
|
|
27
|
+
Action routes in `src/app/api/sessions/[id]/*` must check the capability matrix before executing destructive actions. If a provider lacks the `archive` capability, the API must reject the request and return a clear error code.
|
|
28
|
+
|
|
29
|
+
## Testing Strategy
|
|
30
|
+
Tests will verify correct capability matrix enforcement in both UI components and API routes. We will add unit tests for the Claude provider status logic. This ensures the false-idle fallback works correctly under weak liveness conditions.
|
|
31
|
+
|
|
32
|
+
## Rollout Order
|
|
33
|
+
1. Provider contracts and status logic
|
|
34
|
+
2. UI action gating and visual differentiation
|
|
35
|
+
3. API action-route guards
|
|
36
|
+
4. Tests and documentation
|
|
37
|
+
|
|
38
|
+
## Risks
|
|
39
|
+
Staged capabilities might frustrate users if missing actions are not explained clearly in the UI. The strict idle fallback could mask active processes if PID checks fail due to obscure OS-level permission boundaries.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibepulse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "vibepulse",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.3.0-beta.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@dnd-kit/core": "^6.3.1",
|
|
12
12
|
"@dnd-kit/sortable": "^10.0.0",
|
|
@@ -5,16 +5,28 @@ vi.mock('@/lib/opencodeDiscovery', () => ({
|
|
|
5
5
|
}));
|
|
6
6
|
|
|
7
7
|
vi.mock('@/lib/sessionArchiveOverrides', () => ({
|
|
8
|
+
clearSessionStickyStatusBlocked: vi.fn(),
|
|
8
9
|
clearSessionForceUnarchived: vi.fn(),
|
|
10
|
+
markSessionForceUnarchived: vi.fn(),
|
|
9
11
|
markSessionStickyStatusBlocked: vi.fn(),
|
|
10
12
|
}));
|
|
11
13
|
|
|
12
14
|
import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
|
|
13
15
|
import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
|
|
16
|
+
import {
|
|
17
|
+
clearSessionStickyStatusBlocked,
|
|
18
|
+
clearSessionForceUnarchived,
|
|
19
|
+
markSessionForceUnarchived,
|
|
20
|
+
markSessionStickyStatusBlocked,
|
|
21
|
+
} from '@/lib/sessionArchiveOverrides';
|
|
14
22
|
|
|
15
|
-
import { POST } from './route';
|
|
23
|
+
import { DELETE, POST } from './route';
|
|
16
24
|
|
|
17
25
|
const mockDiscoverOpencodePortsWithMeta: any = discoverOpencodePortsWithMeta;
|
|
26
|
+
const mockClearSessionStickyStatusBlocked: any = clearSessionStickyStatusBlocked;
|
|
27
|
+
const mockClearSessionForceUnarchived: any = clearSessionForceUnarchived;
|
|
28
|
+
const mockMarkSessionForceUnarchived: any = markSessionForceUnarchived;
|
|
29
|
+
const mockMarkSessionStickyStatusBlocked: any = markSessionStickyStatusBlocked;
|
|
18
30
|
|
|
19
31
|
describe('/api/node/sessions/[id]/archive', () => {
|
|
20
32
|
const originalRuntimeRole = process.env.VIBEPULSE_RUNTIME_ROLE;
|
|
@@ -52,6 +64,53 @@ describe('/api/node/sessions/[id]/archive', () => {
|
|
|
52
64
|
'http://localhost:7777/session/ses_123',
|
|
53
65
|
expect.objectContaining({ method: 'PATCH' })
|
|
54
66
|
);
|
|
67
|
+
expect(mockClearSessionForceUnarchived).toHaveBeenCalledWith('ses_123');
|
|
68
|
+
expect(mockMarkSessionStickyStatusBlocked).toHaveBeenCalledWith('ses_123');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('restores a node-local session with valid auth', async () => {
|
|
72
|
+
const response = await DELETE(
|
|
73
|
+
new Request('http://localhost/api/node/sessions/ses_123/archive', {
|
|
74
|
+
method: 'DELETE',
|
|
75
|
+
headers: createNodeRequestHeaders('shared-secret'),
|
|
76
|
+
}),
|
|
77
|
+
{ params: Promise.resolve({ id: 'ses_123' }) }
|
|
78
|
+
);
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
|
|
81
|
+
expect(response.status).toBe(200);
|
|
82
|
+
expect(data).toEqual({ success: true });
|
|
83
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
84
|
+
'http://localhost:7777/session/ses_123',
|
|
85
|
+
expect.objectContaining({ method: 'PATCH', body: JSON.stringify({ time: { archived: null } }) })
|
|
86
|
+
);
|
|
87
|
+
expect(mockMarkSessionForceUnarchived).toHaveBeenCalledWith('ses_123');
|
|
88
|
+
expect(mockClearSessionStickyStatusBlocked).toHaveBeenCalledWith('ses_123');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('does not mutate restore overrides when upstream restore fails', async () => {
|
|
92
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
93
|
+
value: vi.fn(async () => new Response(JSON.stringify({ error: 'boom' }), { status: 500 })),
|
|
94
|
+
configurable: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const response = await DELETE(
|
|
98
|
+
new Request('http://localhost/api/node/sessions/ses_123/archive', {
|
|
99
|
+
method: 'DELETE',
|
|
100
|
+
headers: createNodeRequestHeaders('shared-secret'),
|
|
101
|
+
}),
|
|
102
|
+
{ params: Promise.resolve({ id: 'ses_123' }) }
|
|
103
|
+
);
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
|
|
106
|
+
expect(response.status).toBe(500);
|
|
107
|
+
expect(data).toEqual({
|
|
108
|
+
error: 'Failed to restore session',
|
|
109
|
+
reason: 'node_request_failed_500',
|
|
110
|
+
message: JSON.stringify({ error: 'boom' }),
|
|
111
|
+
});
|
|
112
|
+
expect(mockMarkSessionForceUnarchived).not.toHaveBeenCalled();
|
|
113
|
+
expect(mockClearSessionStickyStatusBlocked).not.toHaveBeenCalled();
|
|
55
114
|
});
|
|
56
115
|
|
|
57
116
|
it('rejects invalid auth before mutating', async () => {
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
toNodeRequestGuardResponse,
|
|
6
6
|
} from '@/lib/nodeProtocol';
|
|
7
7
|
import {
|
|
8
|
+
clearSessionStickyStatusBlocked,
|
|
8
9
|
clearSessionForceUnarchived,
|
|
10
|
+
markSessionForceUnarchived,
|
|
9
11
|
markSessionStickyStatusBlocked,
|
|
10
12
|
} from '@/lib/sessionArchiveOverrides';
|
|
11
13
|
|
|
@@ -20,28 +22,34 @@ function resolveNodeLocalSessionId(id: string): string | null {
|
|
|
20
22
|
|
|
21
23
|
export const dynamic = 'force-dynamic';
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return toNodeRequestGuardResponse(guardResult);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const { id } = await params;
|
|
30
|
-
const sessionId = resolveNodeLocalSessionId(id);
|
|
25
|
+
function createInvalidNodeSessionIdResponse() {
|
|
26
|
+
return Response.json({ error: 'Invalid node session id' }, { status: 400 });
|
|
27
|
+
}
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
function createNodeUpstreamUnavailableResponse(timedOut: boolean) {
|
|
30
|
+
return createNodeFailureResponse(timedOut ? 'upstream_timeout' : 'upstream_unreachable', {
|
|
31
|
+
role: 'node',
|
|
32
|
+
upstream: {
|
|
33
|
+
kind: 'opencode',
|
|
34
|
+
reachable: false,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
35
38
|
|
|
39
|
+
async function runArchiveMutation({
|
|
40
|
+
sessionId,
|
|
41
|
+
archived,
|
|
42
|
+
failureMessage,
|
|
43
|
+
onSuccess,
|
|
44
|
+
}: {
|
|
45
|
+
sessionId: string;
|
|
46
|
+
archived: number | null;
|
|
47
|
+
failureMessage: string;
|
|
48
|
+
onSuccess: () => void;
|
|
49
|
+
}): Promise<Response> {
|
|
36
50
|
const { ports, timedOut } = discoverOpencodePortsWithMeta();
|
|
37
51
|
if (!ports.length) {
|
|
38
|
-
return
|
|
39
|
-
role: 'node',
|
|
40
|
-
upstream: {
|
|
41
|
-
kind: 'opencode',
|
|
42
|
-
reachable: false,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
52
|
+
return createNodeUpstreamUnavailableResponse(timedOut);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
let sawNotFound = false;
|
|
@@ -55,12 +63,11 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
|
|
55
63
|
headers: {
|
|
56
64
|
'Content-Type': 'application/json',
|
|
57
65
|
},
|
|
58
|
-
body: JSON.stringify({ time: { archived
|
|
66
|
+
body: JSON.stringify({ time: { archived } }),
|
|
59
67
|
});
|
|
60
68
|
|
|
61
69
|
if (response.ok) {
|
|
62
|
-
|
|
63
|
-
markSessionStickyStatusBlocked(sessionId);
|
|
70
|
+
onSuccess();
|
|
64
71
|
return Response.json({ success: true });
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -81,7 +88,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
|
|
81
88
|
if (lastFailureStatus !== null) {
|
|
82
89
|
return Response.json(
|
|
83
90
|
{
|
|
84
|
-
error:
|
|
91
|
+
error: failureMessage,
|
|
85
92
|
reason: lastFailureStatus === 503 ? 'upstream_unreachable' : `node_request_failed_${lastFailureStatus}`,
|
|
86
93
|
...(lastFailureMessage ? { message: lastFailureMessage } : {}),
|
|
87
94
|
},
|
|
@@ -95,3 +102,51 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
|
|
95
102
|
|
|
96
103
|
return Response.json({ error: 'Session not found', reason: 'session_not_found' }, { status: 404 });
|
|
97
104
|
}
|
|
105
|
+
|
|
106
|
+
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
107
|
+
const guardResult = guardNodeRequest(request);
|
|
108
|
+
if (!guardResult.ok) {
|
|
109
|
+
return toNodeRequestGuardResponse(guardResult);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { id } = await params;
|
|
113
|
+
const sessionId = resolveNodeLocalSessionId(id);
|
|
114
|
+
|
|
115
|
+
if (!sessionId) {
|
|
116
|
+
return createInvalidNodeSessionIdResponse();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return runArchiveMutation({
|
|
120
|
+
sessionId,
|
|
121
|
+
archived: Date.now(),
|
|
122
|
+
failureMessage: 'Failed to archive session',
|
|
123
|
+
onSuccess: () => {
|
|
124
|
+
clearSessionForceUnarchived(sessionId);
|
|
125
|
+
markSessionStickyStatusBlocked(sessionId);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
131
|
+
const guardResult = guardNodeRequest(request);
|
|
132
|
+
if (!guardResult.ok) {
|
|
133
|
+
return toNodeRequestGuardResponse(guardResult);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const { id } = await params;
|
|
137
|
+
const sessionId = resolveNodeLocalSessionId(id);
|
|
138
|
+
|
|
139
|
+
if (!sessionId) {
|
|
140
|
+
return createInvalidNodeSessionIdResponse();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return runArchiveMutation({
|
|
144
|
+
sessionId,
|
|
145
|
+
archived: null,
|
|
146
|
+
failureMessage: 'Failed to restore session',
|
|
147
|
+
onSuccess: () => {
|
|
148
|
+
markSessionForceUnarchived(sessionId);
|
|
149
|
+
clearSessionStickyStatusBlocked(sessionId);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|