conductor-oss 0.2.16 → 0.2.18

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.
Files changed (182) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +4 -2
  3. package/dist/index.js.map +1 -1
  4. package/node_modules/@conductor-oss/plugin-agent-amp/package.json +1 -1
  5. package/node_modules/@conductor-oss/plugin-agent-ccr/package.json +1 -1
  6. package/node_modules/@conductor-oss/plugin-agent-claude-code/package.json +1 -1
  7. package/node_modules/@conductor-oss/plugin-agent-codex/package.json +1 -1
  8. package/node_modules/@conductor-oss/plugin-agent-cursor-cli/package.json +1 -1
  9. package/node_modules/@conductor-oss/plugin-agent-droid/package.json +1 -1
  10. package/node_modules/@conductor-oss/plugin-agent-gemini/package.json +1 -1
  11. package/node_modules/@conductor-oss/plugin-agent-github-copilot/package.json +1 -1
  12. package/node_modules/@conductor-oss/plugin-agent-opencode/package.json +1 -1
  13. package/node_modules/@conductor-oss/plugin-agent-qwen-code/package.json +1 -1
  14. package/node_modules/@conductor-oss/plugin-mcp-server/package.json +1 -1
  15. package/node_modules/@conductor-oss/plugin-notifier-desktop/package.json +1 -1
  16. package/node_modules/@conductor-oss/plugin-notifier-discord/package.json +1 -1
  17. package/node_modules/@conductor-oss/plugin-runtime-tmux/package.json +1 -1
  18. package/node_modules/@conductor-oss/plugin-scm-github/package.json +1 -1
  19. package/node_modules/@conductor-oss/plugin-terminal-web/package.json +1 -1
  20. package/node_modules/@conductor-oss/plugin-tracker-github/package.json +1 -1
  21. package/node_modules/@conductor-oss/plugin-webhook/package.json +1 -1
  22. package/node_modules/@conductor-oss/plugin-workspace-worktree/package.json +1 -1
  23. package/package.json +21 -21
  24. package/web/.next/standalone/packages/web/.next/BUILD_ID +1 -1
  25. package/web/.next/standalone/packages/web/.next/app-path-routes-manifest.json +1 -0
  26. package/web/.next/standalone/packages/web/.next/build-manifest.json +2 -2
  27. package/web/.next/standalone/packages/web/.next/prerender-manifest.json +3 -3
  28. package/web/.next/standalone/packages/web/.next/routes-manifest.json +8 -0
  29. package/web/.next/standalone/packages/web/.next/server/app/_global-error.html +2 -2
  30. package/web/.next/standalone/packages/web/.next/server/app/_global-error.rsc +1 -1
  31. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  32. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  33. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/web/.next/standalone/packages/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page/server-reference-manifest.json +7 -7
  37. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  38. package/web/.next/standalone/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  39. package/web/.next/standalone/packages/web/.next/server/app/_not-found.html +1 -1
  40. package/web/.next/standalone/packages/web/.next/server/app/_not-found.rsc +3 -3
  41. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  42. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  43. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  44. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  45. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  46. package/web/.next/standalone/packages/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  47. package/web/.next/standalone/packages/web/.next/server/app/api/access/route.js.nft.json +1 -1
  48. package/web/.next/standalone/packages/web/.next/server/app/api/attachments/route.js.nft.json +1 -1
  49. package/web/.next/standalone/packages/web/.next/server/app/api/boards/route.js.nft.json +1 -1
  50. package/web/.next/standalone/packages/web/.next/server/app/api/config/route.js.nft.json +1 -1
  51. package/web/.next/standalone/packages/web/.next/server/app/api/context-files/route.js.nft.json +1 -1
  52. package/web/.next/standalone/packages/web/.next/server/app/api/events/route.js.nft.json +1 -1
  53. package/web/.next/standalone/packages/web/.next/server/app/api/filesystem/directory/route.js.nft.json +1 -1
  54. package/web/.next/standalone/packages/web/.next/server/app/api/github/repos/route.js.nft.json +1 -1
  55. package/web/.next/standalone/packages/web/.next/server/app/api/health/boards/route.js.nft.json +1 -1
  56. package/web/.next/standalone/packages/web/.next/server/app/api/health/sessions/route.js.nft.json +1 -1
  57. package/web/.next/standalone/packages/web/.next/server/app/api/notifications/route.js.nft.json +1 -1
  58. package/web/.next/standalone/packages/web/.next/server/app/api/preferences/route.js.nft.json +1 -1
  59. package/web/.next/standalone/packages/web/.next/server/app/api/repositories/route.js.nft.json +1 -1
  60. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/actions/route.js.nft.json +1 -1
  61. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/checks/route.js.nft.json +1 -1
  62. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/diff/route.js.nft.json +1 -1
  63. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route/app-paths-manifest.json +3 -0
  64. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route/build-manifest.json +11 -0
  65. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route/server-reference-manifest.json +4 -0
  66. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js +10 -0
  67. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.map +5 -0
  68. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route.js.nft.json +1 -0
  69. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feed/route_client-reference-manifest.js +2 -0
  70. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/feedback/route.js.nft.json +1 -1
  71. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/files/route.js.nft.json +1 -1
  72. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/keys/route.js.nft.json +1 -1
  73. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/kill/route.js.nft.json +1 -1
  74. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/route.js.nft.json +1 -1
  75. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/output/stream/route.js.nft.json +1 -1
  76. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -1
  77. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  78. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/[id]/send/route.js.nft.json +1 -1
  79. package/web/.next/standalone/packages/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
  80. package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js +1 -1
  81. package/web/.next/standalone/packages/web/.next/server/app/api/spawn/route.js.nft.json +1 -1
  82. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/branches/route.js.nft.json +1 -1
  83. package/web/.next/standalone/packages/web/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  84. package/web/.next/standalone/packages/web/.next/server/app/index.html +1 -1
  85. package/web/.next/standalone/packages/web/.next/server/app/index.rsc +4 -4
  86. package/web/.next/standalone/packages/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  87. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  88. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  89. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
  90. package/web/.next/standalone/packages/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  91. package/web/.next/standalone/packages/web/.next/server/app/page/react-loadable-manifest.json +1 -1
  92. package/web/.next/standalone/packages/web/.next/server/app/page/server-reference-manifest.json +7 -7
  93. package/web/.next/standalone/packages/web/.next/server/app/page.js.nft.json +1 -1
  94. package/web/.next/standalone/packages/web/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page/server-reference-manifest.json +7 -7
  96. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  97. package/web/.next/standalone/packages/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  98. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page/server-reference-manifest.json +7 -7
  99. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page.js.nft.json +1 -1
  100. package/web/.next/standalone/packages/web/.next/server/app/sign-in/[[...sign-in]]/page_client-reference-manifest.js +1 -1
  101. package/web/.next/standalone/packages/web/.next/server/app/unlock/page/server-reference-manifest.json +7 -7
  102. package/web/.next/standalone/packages/web/.next/server/app/unlock/page.js.nft.json +1 -1
  103. package/web/.next/standalone/packages/web/.next/server/app/unlock/page_client-reference-manifest.js +1 -1
  104. package/web/.next/standalone/packages/web/.next/server/app-paths-manifest.json +1 -0
  105. package/web/.next/standalone/packages/web/.next/server/chunks/730ea_web__next-internal_server_app_api_sessions_[id]_feed_route_actions_d88aaa25.js +3 -0
  106. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__346d9484._.js +3 -0
  107. package/web/.next/standalone/packages/web/.next/server/chunks/[root-of-the-server]__53385c40._.js +3 -0
  108. package/web/.next/standalone/packages/web/.next/server/chunks/{[root-of-the-server]__98c6a6f3._.js → [root-of-the-server]__6c10c03b._.js} +2 -2
  109. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{[root-of-the-server]__9726f664._.js → [root-of-the-server]__12eb9005._.js} +2 -2
  110. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__6622b514._.js +1 -1
  111. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__869d9ac0._.js +1 -1
  112. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__9dc23e5a._.js +1 -1
  113. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/[root-of-the-server]__b388693f._.js +1 -1
  114. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_0e1412de._.js +1 -1
  115. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_b0abbdd9._.js → _20a4007d._.js} +2 -2
  116. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_69e05fca._.js +1 -1
  117. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_80efe193._.js +1 -1
  118. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_b6d31783._.js +1 -1
  119. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{_0973acf3._.js → _b88bcf2c._.js} +2 -2
  120. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_c0f0e227._.js +1 -1
  121. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/_f36ddaa9._.js +1 -1
  122. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{node_modules_@clerk_nextjs_dist_esm_app-router_bf11b471._.js → node_modules_@clerk_nextjs_dist_esm_app-router_78af9fdf._.js} +2 -2
  123. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_c4bad84a._.js +3 -0
  124. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_f2ebd7a9._.js +1 -1
  125. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_79316445._.js +1 -1
  126. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_a078c137._.js +1 -1
  127. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/packages_web_src_app_page_tsx_cd282e82._.js +1 -1
  128. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/{packages_web_src_components_dd296b40._.js → packages_web_src_components_3809c507._.js} +1 -1
  129. package/web/.next/standalone/packages/web/.next/server/pages/404.html +1 -1
  130. package/web/.next/standalone/packages/web/.next/server/pages/500.html +2 -2
  131. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.js +1 -1
  132. package/web/.next/standalone/packages/web/.next/server/server-reference-manifest.json +8 -8
  133. package/web/.next/standalone/packages/web/.next/static/chunks/1e67fbc3874d3f51.js +1 -0
  134. package/web/.next/{static/chunks/e88fa6d41d743b9e.js → standalone/packages/web/.next/static/chunks/28dd6ef2af62b509.js} +2 -2
  135. package/web/.next/standalone/packages/web/.next/static/chunks/2b2a24dff50e7dc9.js +1 -0
  136. package/web/.next/standalone/packages/web/.next/static/chunks/483eb2824f5282c7.js +1 -0
  137. package/web/.next/standalone/packages/web/.next/static/chunks/695b7cb206c6dadd.js +1 -0
  138. package/web/.next/standalone/packages/web/.next/static/chunks/860d84e1f09476a4.css +3 -0
  139. package/web/.next/standalone/packages/web/.next/static/chunks/{86bce89c37cf6212.js → c959976264f14eba.js} +1 -1
  140. package/web/.next/standalone/packages/web/.next/static/chunks/{4c566fd1e4a92935.js → d9d05e7b540400af.js} +1 -1
  141. package/web/.next/standalone/packages/web/.next/static/chunks/e862e73b22fe29c2.js +1 -0
  142. package/web/.next/{static/chunks/719697e99b51d55b.js → standalone/packages/web/.next/static/chunks/f5d9ad0f62ede339.js} +1 -1
  143. package/web/.next/standalone/packages/web/src/app/api/sessions/[id]/feed/route.ts +97 -0
  144. package/web/.next/standalone/packages/web/src/app/page.tsx +3 -4555
  145. package/web/.next/standalone/packages/web/src/app/sessions/[id]/page.tsx +2 -115
  146. package/web/.next/standalone/packages/web/src/components/layout/AppShell.tsx +62 -2
  147. package/web/.next/standalone/packages/web/src/components/layout/TopBar.tsx +13 -11
  148. package/web/.next/standalone/packages/web/src/components/layout/WorkspaceSidebarPanel.tsx +68 -10
  149. package/web/.next/standalone/packages/web/src/features/dashboard/DashboardClient.tsx +4571 -0
  150. package/web/.next/standalone/packages/web/src/features/dashboard/components/WorkspaceOverview.tsx +296 -0
  151. package/web/.next/standalone/packages/web/src/features/sessions/SessionPageClient.tsx +125 -0
  152. package/web/.next/standalone/packages/web/src/hooks/useSessionFeed.ts +17 -13
  153. package/web/.next/standalone/packages/web/src/hooks/useSessions.ts +37 -7
  154. package/web/.next/static/chunks/1e67fbc3874d3f51.js +1 -0
  155. package/web/.next/{standalone/packages/web/.next/static/chunks/e88fa6d41d743b9e.js → static/chunks/28dd6ef2af62b509.js} +2 -2
  156. package/web/.next/static/chunks/2b2a24dff50e7dc9.js +1 -0
  157. package/web/.next/static/chunks/483eb2824f5282c7.js +1 -0
  158. package/web/.next/static/chunks/695b7cb206c6dadd.js +1 -0
  159. package/web/.next/static/chunks/860d84e1f09476a4.css +3 -0
  160. package/web/.next/static/chunks/{86bce89c37cf6212.js → c959976264f14eba.js} +1 -1
  161. package/web/.next/static/chunks/{4c566fd1e4a92935.js → d9d05e7b540400af.js} +1 -1
  162. package/web/.next/static/chunks/e862e73b22fe29c2.js +1 -0
  163. package/web/.next/{standalone/packages/web/.next/static/chunks/719697e99b51d55b.js → static/chunks/f5d9ad0f62ede339.js} +1 -1
  164. package/web/.next/standalone/packages/web/.next/server/chunks/ssr/node_modules_@clerk_nextjs_dist_esm_app-router_8a444735._.js +0 -3
  165. package/web/.next/standalone/packages/web/.next/static/chunks/1867dba46fcc022e.js +0 -1
  166. package/web/.next/standalone/packages/web/.next/static/chunks/19d7d4e2cfe3261a.js +0 -1
  167. package/web/.next/standalone/packages/web/.next/static/chunks/ab8ef6c7dfc78082.js +0 -1
  168. package/web/.next/standalone/packages/web/.next/static/chunks/b5e2d1ef92e508a0.js +0 -1
  169. package/web/.next/standalone/packages/web/.next/static/chunks/e8283780c26eaa91.js +0 -1
  170. package/web/.next/standalone/packages/web/.next/static/chunks/fe557eb4039d8a8d.css +0 -3
  171. package/web/.next/static/chunks/1867dba46fcc022e.js +0 -1
  172. package/web/.next/static/chunks/19d7d4e2cfe3261a.js +0 -1
  173. package/web/.next/static/chunks/ab8ef6c7dfc78082.js +0 -1
  174. package/web/.next/static/chunks/b5e2d1ef92e508a0.js +0 -1
  175. package/web/.next/static/chunks/e8283780c26eaa91.js +0 -1
  176. package/web/.next/static/chunks/fe557eb4039d8a8d.css +0 -3
  177. /package/web/.next/standalone/packages/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_buildManifest.js +0 -0
  178. /package/web/.next/standalone/packages/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_clientMiddlewareManifest.json +0 -0
  179. /package/web/.next/standalone/packages/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_ssgManifest.js +0 -0
  180. /package/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_buildManifest.js +0 -0
  181. /package/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_clientMiddlewareManifest.json +0 -0
  182. /package/web/.next/static/{7KQ5wRS3BC3qvrkz8yxuS → eSF3qxz6RT8UXiwr-uibJ}/_ssgManifest.js +0 -0
@@ -0,0 +1,296 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import {
5
+ ArrowRight,
6
+ FolderGit2,
7
+ FolderKanban,
8
+ GitBranch,
9
+ Layers3,
10
+ Sparkles,
11
+ } from "lucide-react";
12
+ import { Button } from "@/components/ui/Button";
13
+ import { Badge } from "@/components/ui/Badge";
14
+ import { Card, CardContent, CardHeader } from "@/components/ui/Card";
15
+ import { getAttentionLevel, type DashboardSession } from "@/lib/types";
16
+ import type { ConfigProject } from "@/hooks/useConfig";
17
+
18
+ type WorkspaceView = "chat" | "board";
19
+
20
+ interface WorkspaceOverviewProps {
21
+ projects: ConfigProject[];
22
+ sessions: DashboardSession[];
23
+ selectedProjectId: string | null;
24
+ workspaceView: WorkspaceView;
25
+ agentCount: number;
26
+ onCreateWorkspace: () => void;
27
+ onSelectProject: (projectId: string | null) => void;
28
+ onSelectSession: (sessionId: string) => void;
29
+ onShowView: (view: WorkspaceView) => void;
30
+ }
31
+
32
+ type ProjectSummary = {
33
+ id: string;
34
+ description: string | null;
35
+ branch: string;
36
+ totalSessions: number;
37
+ activeSessions: number;
38
+ };
39
+
40
+ function formatRelativeTime(isoDate: string): string {
41
+ const diffMs = Date.now() - new Date(isoDate).getTime();
42
+ if (!Number.isFinite(diffMs) || diffMs < 60_000) return "Updated now";
43
+ const minutes = Math.floor(diffMs / 60_000);
44
+ if (minutes < 60) return `Updated ${minutes}m ago`;
45
+ const hours = Math.floor(minutes / 60);
46
+ if (hours < 24) return `Updated ${hours}h ago`;
47
+ const days = Math.floor(hours / 24);
48
+ return `Updated ${days}d ago`;
49
+ }
50
+
51
+ function getStatusTone(session: DashboardSession): "success" | "warning" | "error" | "outline" {
52
+ const level = getAttentionLevel(session);
53
+ if (level === "merge") return "success";
54
+ if (level === "review" || level === "respond") return "warning";
55
+ if (session.status === "errored" || session.status === "killed") return "error";
56
+ return "outline";
57
+ }
58
+
59
+ function getStatusLabel(session: DashboardSession): string {
60
+ const level = getAttentionLevel(session);
61
+ if (level === "merge") return "Ready";
62
+ if (level === "respond") return "Needs input";
63
+ if (level === "review") return "Review";
64
+ if (level === "pending") return "Pending";
65
+ if (level === "done") return "Done";
66
+ return "Running";
67
+ }
68
+
69
+ export function WorkspaceOverview({
70
+ projects,
71
+ sessions,
72
+ selectedProjectId,
73
+ workspaceView,
74
+ agentCount,
75
+ onCreateWorkspace,
76
+ onSelectProject,
77
+ onSelectSession,
78
+ onShowView,
79
+ }: WorkspaceOverviewProps) {
80
+ const visibleSessions = useMemo(
81
+ () => sessions.filter((session) => session.status !== "archived"),
82
+ [sessions],
83
+ );
84
+
85
+ const projectSummaries = useMemo<ProjectSummary[]>(() => {
86
+ return projects
87
+ .map((project) => {
88
+ const projectSessions = visibleSessions.filter((session) => session.projectId === project.id);
89
+ const activeSessions = projectSessions.filter((session) => getAttentionLevel(session) !== "done").length;
90
+
91
+ return {
92
+ id: project.id,
93
+ description: project.description,
94
+ branch: project.defaultBranch || "main",
95
+ totalSessions: projectSessions.length,
96
+ activeSessions,
97
+ };
98
+ })
99
+ .sort((left, right) => right.activeSessions - left.activeSessions || right.totalSessions - left.totalSessions || left.id.localeCompare(right.id));
100
+ }, [projects, visibleSessions]);
101
+
102
+ const recentSessions = useMemo(() => {
103
+ return [...visibleSessions]
104
+ .sort((left, right) => new Date(right.lastActivityAt).getTime() - new Date(left.lastActivityAt).getTime())
105
+ .slice(0, 5);
106
+ }, [visibleSessions]);
107
+
108
+ const activeSessions = visibleSessions.filter((session) => getAttentionLevel(session) !== "done").length;
109
+ const needsAttention = visibleSessions.filter((session) => {
110
+ const level = getAttentionLevel(session);
111
+ return level === "merge" || level === "respond" || level === "review";
112
+ }).length;
113
+ const mergeReady = visibleSessions.filter((session) => getAttentionLevel(session) === "merge").length;
114
+ const selectedProject = projectSummaries.find((project) => project.id === selectedProjectId) ?? null;
115
+
116
+ const statCards = [
117
+ { label: "Projects", value: String(projects.length), icon: FolderGit2 },
118
+ { label: "Active sessions", value: String(activeSessions), icon: Layers3 },
119
+ { label: "Need attention", value: String(needsAttention), icon: Sparkles },
120
+ { label: "Merge ready", value: String(mergeReady), icon: GitBranch },
121
+ ];
122
+
123
+ return (
124
+ <div className="border-b border-[var(--vk-border)] bg-[linear-gradient(180deg,rgba(255,255,255,0.03),rgba(255,255,255,0))]">
125
+ <div className="mx-auto flex w-full max-w-[1440px] flex-col gap-4 px-3 py-3 sm:px-4">
126
+ <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
127
+ <div className="max-w-3xl">
128
+ <p className="text-[11px] uppercase tracking-[0.12em] text-[var(--vk-text-muted)]">
129
+ Frontend Control Surface
130
+ </p>
131
+ <h1 className="mt-1 text-[24px] font-semibold leading-[1.05] tracking-[-0.04em] text-[var(--vk-text-strong)] sm:text-[30px]">
132
+ Operate workspaces, sessions, and agents from one surface.
133
+ </h1>
134
+ <p className="mt-2 max-w-2xl text-[13px] leading-6 text-[var(--vk-text-muted)]">
135
+ The current design language stays intact, but the workspace entrypoint now exposes status, recent activity,
136
+ and project context without forcing you into a blank composer first.
137
+ </p>
138
+ </div>
139
+
140
+ <div className="flex flex-wrap items-center gap-2">
141
+ <div className="inline-flex rounded-[6px] border border-[var(--vk-border)] p-1">
142
+ <button
143
+ type="button"
144
+ onClick={() => onShowView("chat")}
145
+ className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
146
+ workspaceView === "chat"
147
+ ? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
148
+ : "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
149
+ }`}
150
+ >
151
+ Chat launchpad
152
+ </button>
153
+ <button
154
+ type="button"
155
+ onClick={() => onShowView("board")}
156
+ className={`min-h-[32px] rounded-[4px] px-3 text-[13px] ${
157
+ workspaceView === "board"
158
+ ? "bg-[var(--vk-bg-active)] text-[var(--vk-text-strong)]"
159
+ : "text-[var(--vk-text-muted)] hover:bg-[var(--vk-bg-hover)]"
160
+ }`}
161
+ >
162
+ Board view
163
+ </button>
164
+ </div>
165
+
166
+ <Button variant="outline" size="md" onClick={onCreateWorkspace}>
167
+ Add workspace
168
+ </Button>
169
+ </div>
170
+ </div>
171
+
172
+ <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
173
+ {statCards.map(({ label, value, icon: Icon }) => (
174
+ <Card key={label} className="bg-[color:color-mix(in_srgb,var(--vk-bg-panel)_86%,transparent)]">
175
+ <CardContent className="flex items-center gap-3 py-3">
176
+ <span className="inline-flex h-10 w-10 items-center justify-center rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] text-[var(--vk-text-normal)]">
177
+ <Icon className="h-4 w-4" />
178
+ </span>
179
+ <div>
180
+ <p className="text-[11px] uppercase tracking-[0.08em] text-[var(--vk-text-muted)]">{label}</p>
181
+ <p className="mt-1 text-[22px] font-semibold text-[var(--vk-text-strong)]">{value}</p>
182
+ </div>
183
+ </CardContent>
184
+ </Card>
185
+ ))}
186
+ </div>
187
+
188
+ <div className="grid gap-3 xl:grid-cols-[1.35fr_0.95fr]">
189
+ <Card>
190
+ <CardHeader className="justify-between">
191
+ <div>
192
+ <p className="text-[14px] font-semibold text-[var(--vk-text-strong)]">Recent sessions</p>
193
+ <p className="text-[12px] text-[var(--vk-text-muted)]">Jump back into active work without hunting through the sidebar.</p>
194
+ </div>
195
+ <Badge variant="outline">{visibleSessions.length}</Badge>
196
+ </CardHeader>
197
+ <CardContent className="space-y-2">
198
+ {recentSessions.length === 0 ? (
199
+ <p className="text-[13px] text-[var(--vk-text-muted)]">No sessions yet. Create one from the launchpad below.</p>
200
+ ) : recentSessions.map((session) => (
201
+ <button
202
+ key={session.id}
203
+ type="button"
204
+ onClick={() => onSelectSession(session.id)}
205
+ className="flex w-full items-center gap-3 rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] px-3 py-3 text-left transition-colors hover:bg-[var(--vk-bg-hover)]"
206
+ >
207
+ <div className="min-w-0 flex-1">
208
+ <div className="flex items-center gap-2">
209
+ <p className="truncate text-[14px] font-medium text-[var(--vk-text-strong)]">
210
+ {session.summary?.trim() || session.projectId}
211
+ </p>
212
+ <Badge variant={getStatusTone(session)}>{getStatusLabel(session)}</Badge>
213
+ </div>
214
+ <p className="mt-1 truncate text-[12px] text-[var(--vk-text-muted)]">
215
+ {session.projectId}
216
+ {session.branch ? ` · ${session.branch}` : ""}
217
+ {session.issueId ? ` · ${session.issueId}` : ""}
218
+ </p>
219
+ </div>
220
+ <div className="shrink-0 text-right">
221
+ <p className="text-[11px] text-[var(--vk-text-muted)]">{formatRelativeTime(session.lastActivityAt)}</p>
222
+ <ArrowRight className="ml-auto mt-2 h-4 w-4 text-[var(--vk-text-muted)]" />
223
+ </div>
224
+ </button>
225
+ ))}
226
+ </CardContent>
227
+ </Card>
228
+
229
+ <Card>
230
+ <CardHeader className="justify-between">
231
+ <div>
232
+ <p className="text-[14px] font-semibold text-[var(--vk-text-strong)]">Project focus</p>
233
+ <p className="text-[12px] text-[var(--vk-text-muted)]">
234
+ {selectedProject ? `Selected: ${selectedProject.id}` : "Select a project to scope new work."}
235
+ </p>
236
+ </div>
237
+ <Badge variant="outline">{agentCount} agents</Badge>
238
+ </CardHeader>
239
+ <CardContent className="space-y-2">
240
+ <button
241
+ type="button"
242
+ onClick={() => onSelectProject(null)}
243
+ className={`flex w-full items-center justify-between rounded-[6px] border px-3 py-2 text-left ${
244
+ selectedProjectId === null
245
+ ? "border-[var(--vk-border)] bg-[var(--vk-bg-hover)] text-[var(--vk-text-strong)]"
246
+ : "border-[var(--vk-border)] bg-[var(--vk-bg-main)] text-[var(--vk-text-normal)] hover:bg-[var(--vk-bg-hover)]"
247
+ }`}
248
+ >
249
+ <span>All projects</span>
250
+ <Badge variant="outline">{visibleSessions.length}</Badge>
251
+ </button>
252
+
253
+ {projectSummaries.length === 0 ? (
254
+ <p className="text-[13px] text-[var(--vk-text-muted)]">No configured projects yet.</p>
255
+ ) : projectSummaries.slice(0, 6).map((project) => (
256
+ <button
257
+ key={project.id}
258
+ type="button"
259
+ onClick={() => onSelectProject(project.id)}
260
+ className={`flex w-full items-start justify-between gap-3 rounded-[6px] border px-3 py-2.5 text-left ${
261
+ selectedProjectId === project.id
262
+ ? "border-[var(--vk-border)] bg-[var(--vk-bg-hover)]"
263
+ : "border-[var(--vk-border)] bg-[var(--vk-bg-main)] hover:bg-[var(--vk-bg-hover)]"
264
+ }`}
265
+ >
266
+ <div className="min-w-0">
267
+ <p className="truncate text-[13px] font-medium text-[var(--vk-text-strong)]">{project.id}</p>
268
+ <p className="truncate text-[12px] text-[var(--vk-text-muted)]">
269
+ {project.description?.trim() || `Default branch: ${project.branch}`}
270
+ </p>
271
+ </div>
272
+ <div className="shrink-0 text-right">
273
+ <p className="text-[12px] text-[var(--vk-text-strong)]">{project.activeSessions} active</p>
274
+ <p className="text-[11px] text-[var(--vk-text-muted)]">{project.totalSessions} total</p>
275
+ </div>
276
+ </button>
277
+ ))}
278
+
279
+ {selectedProject ? (
280
+ <div className="rounded-[6px] border border-[var(--vk-border)] bg-[var(--vk-bg-main)] px-3 py-3">
281
+ <div className="flex items-center gap-2">
282
+ <FolderKanban className="h-4 w-4 text-[var(--vk-text-muted)]" />
283
+ <p className="text-[13px] font-medium text-[var(--vk-text-strong)]">{selectedProject.id}</p>
284
+ </div>
285
+ <p className="mt-2 text-[12px] leading-5 text-[var(--vk-text-muted)]">
286
+ {selectedProject.description?.trim() || "This project is ready to launch work from chat or board mode."}
287
+ </p>
288
+ </div>
289
+ ) : null}
290
+ </CardContent>
291
+ </Card>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ );
296
+ }
@@ -0,0 +1,125 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { useParams, useRouter } from "next/navigation";
5
+ import { AppShell } from "@/components/layout/AppShell";
6
+ import { TopBar } from "@/components/layout/TopBar";
7
+ import { WorkspaceSidebarPanel } from "@/components/layout/WorkspaceSidebarPanel";
8
+ import { SessionDetail } from "@/components/sessions/SessionDetail";
9
+ import { useConfig } from "@/hooks/useConfig";
10
+ import { useSessions } from "@/hooks/useSessions";
11
+ import type { DashboardSession } from "@/lib/types";
12
+
13
+ export default function SessionPageClient() {
14
+ const params = useParams<{ id: string }>();
15
+ const router = useRouter();
16
+ const { projects } = useConfig();
17
+ const { sessions, refresh } = useSessions();
18
+ const dashboardSessions = sessions as unknown as DashboardSession[];
19
+ const [sidebarOpen, setSidebarOpen] = useState(true);
20
+ const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
21
+
22
+ useEffect(() => {
23
+ if (typeof window === "undefined") return;
24
+ if (window.innerWidth < 1024) {
25
+ setSidebarOpen(false);
26
+ }
27
+ }, []);
28
+
29
+ const selectedSession = useMemo(
30
+ () => dashboardSessions.find((session) => session.id === params.id) ?? null,
31
+ [dashboardSessions, params.id],
32
+ );
33
+
34
+ useEffect(() => {
35
+ if (selectedSession?.projectId) {
36
+ setSelectedProjectId(selectedSession.projectId);
37
+ return;
38
+ }
39
+ if (!selectedProjectId && projects.length > 0) {
40
+ setSelectedProjectId(projects[0]?.id ?? null);
41
+ }
42
+ }, [projects, selectedProjectId, selectedSession?.projectId]);
43
+
44
+ const toggleSidebar = () => setSidebarOpen((prev) => !prev);
45
+
46
+ const closeSidebarOnMobile = () => {
47
+ if (typeof window !== "undefined" && window.innerWidth < 1024) {
48
+ setSidebarOpen(false);
49
+ }
50
+ };
51
+
52
+ async function handleArchiveSession(sessionId: string) {
53
+ let res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/archive`, {
54
+ method: "POST",
55
+ });
56
+ let data = (await res.json().catch(() => null)) as
57
+ | { ok?: boolean; error?: string }
58
+ | null;
59
+
60
+ if (res.status === 404) {
61
+ res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/actions`, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/json" },
64
+ body: JSON.stringify({ action: "archive" }),
65
+ });
66
+ data = (await res.json().catch(() => null)) as
67
+ | { ok?: boolean; error?: string }
68
+ | null;
69
+ }
70
+
71
+ if (!res.ok) {
72
+ throw new Error(data?.error ?? `Failed to archive session: ${res.status}`);
73
+ }
74
+
75
+ if (sessionId === params.id) {
76
+ router.push("/");
77
+ return;
78
+ }
79
+
80
+ await refresh();
81
+ }
82
+
83
+ if (!params.id) {
84
+ return (
85
+ <div className="flex h-dvh min-h-[100dvh] items-center justify-center">
86
+ <span className="text-[13px] text-[var(--text-muted)]">No session ID provided</span>
87
+ </div>
88
+ );
89
+ }
90
+
91
+ return (
92
+ <AppShell
93
+ sidebarOpen={sidebarOpen}
94
+ onToggleSidebar={toggleSidebar}
95
+ sidebar={
96
+ <WorkspaceSidebarPanel
97
+ orgLabel="conductor-oss"
98
+ projects={projects}
99
+ selectedProjectId={selectedProjectId}
100
+ onSelectProject={(projectId) => {
101
+ setSelectedProjectId(projectId);
102
+ }}
103
+ sessions={dashboardSessions}
104
+ selectedSessionId={params.id}
105
+ onSelectSession={(sessionId) => {
106
+ router.push(`/sessions/${encodeURIComponent(sessionId)}?tab=chat`);
107
+ closeSidebarOnMobile();
108
+ }}
109
+ onArchiveSession={handleArchiveSession}
110
+ onCreateWorkspace={() => {
111
+ router.push("/");
112
+ }}
113
+ />
114
+ }
115
+ >
116
+ <TopBar
117
+ session={selectedSession}
118
+ fallbackTitle={selectedSession?.projectId ?? "Session"}
119
+ />
120
+ <div className="min-h-0 flex-1 overflow-hidden">
121
+ <SessionDetail sessionId={params.id} />
122
+ </div>
123
+ </AppShell>
124
+ );
125
+ }
@@ -4,9 +4,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import type { NormalizedChatEntry } from "@/lib/chatFeed";
5
5
  import { subscribeToSnapshotEvents } from "@/lib/liveEvents";
6
6
  import { TERMINAL_STATUSES, type SSESnapshotEvent } from "@/lib/types";
7
- const ACTIVE_POLL_INTERVAL_MS = 1000;
8
- const HIDDEN_POLL_INTERVAL_MS = 1000;
9
- const TERMINAL_POLL_INTERVAL_MS = 10_000;
7
+ const ACTIVE_POLL_INTERVAL_MS = 4_000;
8
+ const HIDDEN_POLL_INTERVAL_MS = 15_000;
9
+ const TERMINAL_POLL_INTERVAL_MS = 30_000;
10
10
 
11
11
  interface SessionFeedResponse {
12
12
  entries?: NormalizedChatEntry[];
@@ -209,26 +209,29 @@ export function useSessionFeed(sessionId: string | null | undefined): UseSession
209
209
  window.addEventListener("focus", handleWindowFocus);
210
210
  document.addEventListener("visibilitychange", handleVisibilityChange);
211
211
 
212
- let intervalId: number | null = null;
212
+ let pollTimeoutId: number | null = null;
213
+
214
+ const stopPolling = () => {
215
+ if (pollTimeoutId === null) return;
216
+ window.clearTimeout(pollTimeoutId);
217
+ pollTimeoutId = null;
218
+ };
213
219
 
214
220
  const startPolling = () => {
215
- if (intervalId !== null) return;
221
+ stopPolling();
216
222
  const interval = terminalRef.current
217
223
  ? TERMINAL_POLL_INTERVAL_MS
218
224
  : document.visibilityState === "visible"
219
225
  ? ACTIVE_POLL_INTERVAL_MS
220
226
  : HIDDEN_POLL_INTERVAL_MS;
221
- intervalId = window.setInterval(() => {
222
- void refresh();
227
+ pollTimeoutId = window.setTimeout(async () => {
228
+ await refresh();
229
+ if (mountedRef.current) {
230
+ startPolling();
231
+ }
223
232
  }, interval);
224
233
  };
225
234
 
226
- const stopPolling = () => {
227
- if (intervalId === null) return;
228
- window.clearInterval(intervalId);
229
- intervalId = null;
230
- };
231
-
232
235
  startPolling();
233
236
  const unsubscribe = subscribeToSnapshotEvents((payload: SSESnapshotEvent) => {
234
237
  if (!mountedRef.current) return;
@@ -247,6 +250,7 @@ export function useSessionFeed(sessionId: string | null | undefined): UseSession
247
250
  }
248
251
  snapshotSignatureRef.current = signature;
249
252
  void refresh();
253
+ startPolling();
250
254
  });
251
255
 
252
256
  return () => {
@@ -18,6 +18,9 @@ interface UseSessionsReturn {
18
18
  refresh: () => Promise<void>;
19
19
  }
20
20
 
21
+ const ACTIVE_FALLBACK_POLL_INTERVAL_MS = 15_000;
22
+ const HIDDEN_FALLBACK_POLL_INTERVAL_MS = 45_000;
23
+
21
24
  function sessionsEqual(left: Session[], right: Session[]): boolean {
22
25
  if (left === right) return true;
23
26
  if (left.length !== right.length) return false;
@@ -90,19 +93,28 @@ export function useSessions(projectId?: string | null): UseSessionsReturn {
90
93
 
91
94
  let pollingId: number | null = null;
92
95
 
93
- const startPolling = () => {
94
- if (pollingId !== null) return;
95
- pollingId = window.setInterval(() => {
96
- if (mountedRef.current) void fetchSessions();
97
- }, 3000);
98
- };
96
+ const getPollDelay = () =>
97
+ document.visibilityState === "visible"
98
+ ? ACTIVE_FALLBACK_POLL_INTERVAL_MS
99
+ : HIDDEN_FALLBACK_POLL_INTERVAL_MS;
99
100
 
100
101
  const stopPolling = () => {
101
102
  if (pollingId === null) return;
102
- window.clearInterval(pollingId);
103
+ window.clearTimeout(pollingId);
103
104
  pollingId = null;
104
105
  };
105
106
 
107
+ const startPolling = () => {
108
+ stopPolling();
109
+ pollingId = window.setTimeout(async () => {
110
+ if (!mountedRef.current) return;
111
+ await fetchSessions();
112
+ if (mountedRef.current) {
113
+ startPolling();
114
+ }
115
+ }, getPollDelay());
116
+ };
117
+
106
118
  startPolling();
107
119
  const unsubscribe = subscribeToSnapshotEvents((data: SSESnapshotEvent) => {
108
120
  if (!mountedRef.current) return;
@@ -112,10 +124,28 @@ export function useSessions(projectId?: string | null): UseSessionsReturn {
112
124
  setLoading(false);
113
125
  });
114
126
 
127
+ const refresh = () => {
128
+ void fetchSessions();
129
+ startPolling();
130
+ };
131
+
132
+ const handleVisibilityChange = () => {
133
+ if (document.visibilityState === "visible") {
134
+ refresh();
135
+ } else {
136
+ startPolling();
137
+ }
138
+ };
139
+
140
+ window.addEventListener("focus", refresh);
141
+ document.addEventListener("visibilitychange", handleVisibilityChange);
142
+
115
143
  return () => {
116
144
  mountedRef.current = false;
117
145
  unsubscribe();
118
146
  stopPolling();
147
+ window.removeEventListener("focus", refresh);
148
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
119
149
  };
120
150
  }, [applySessions, fetchSessions, projectId]);
121
151
 
@@ -0,0 +1 @@
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,69971,e=>{"use strict";var c=e.i(95187);let r=(0,c.createServerReference)("00c0a601f70ee423bfba0ca90a896e1583105820a8",c.callServer,void 0,c.findSourceMapURL,"createOrReadKeylessAction");e.s(["createOrReadKeylessAction",()=>r])},23151,e=>{"use strict";e.s([],29135),e.i(29135);var c=e.i(95187);let r=(0,c.createServerReference)("401ec4cb0b784bfb9223c4bd7b67f802fc28f88168",c.callServer,void 0,c.findSourceMapURL,"syncKeylessConfigAction");var t=e.i(69971);let s=(0,c.createServerReference)("006b576f68969d56679602f2efafd04f16e06733df",c.callServer,void 0,c.findSourceMapURL,"deleteKeylessAction");var i=e.i(51688);e.s(["createOrReadKeylessAction",()=>t.createOrReadKeylessAction,"deleteKeylessAction",()=>s,"detectKeylessEnvDriftAction",()=>i.detectKeylessEnvDriftAction,"syncKeylessConfigAction",()=>r],23151)}]);