neural-loom 0.2.60 → 0.2.62

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 (186) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +3 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/cache/.previewinfo +1 -1
  5. package/.next/cache/.rscinfo +1 -1
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/diagnostics/route-bundle-stats.json +2 -2
  8. package/.next/fallback-build-manifest.json +3 -3
  9. package/.next/prerender-manifest.json +3 -3
  10. package/.next/routes-manifest.json +18 -0
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/api/configs/route/app-paths-manifest.json +3 -0
  27. package/.next/server/app/api/configs/route/build-manifest.json +9 -0
  28. package/.next/server/app/api/configs/route/server-reference-manifest.json +4 -0
  29. package/.next/server/app/api/configs/route.js +6 -0
  30. package/.next/server/app/api/configs/route.js.map +5 -0
  31. package/.next/server/app/api/configs/route.js.nft.json +1 -0
  32. package/.next/server/app/api/configs/route_client-reference-manifest.js +3 -0
  33. package/.next/server/app/api/context/aider/route.js.nft.json +1 -1
  34. package/.next/server/app/api/context/claude/route.js.nft.json +1 -1
  35. package/.next/server/app/api/context/route.js.nft.json +1 -1
  36. package/.next/server/app/api/context/ssh/route.js.nft.json +1 -1
  37. package/.next/server/app/api/recents/route.js.nft.json +1 -1
  38. package/.next/server/app/api/sessions/inject/route.js +2 -2
  39. package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
  40. package/.next/server/app/api/sessions/input/route.js +2 -2
  41. package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
  42. package/.next/server/app/api/sessions/launch/route.js +2 -2
  43. package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
  44. package/.next/server/app/api/sessions/route.js +2 -2
  45. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  46. package/.next/server/app/api/sessions/scope/route.js +2 -2
  47. package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -1
  48. package/.next/server/app/api/sessions/start/route.js +2 -2
  49. package/.next/server/app/api/sessions/start/route.js.nft.json +1 -1
  50. package/.next/server/app/api/sessions/stats/route.js +2 -2
  51. package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
  52. package/.next/server/app/api/sessions/stop/route.js +2 -2
  53. package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
  54. package/.next/server/app/api/sessions/upload-image/route/app-paths-manifest.json +3 -0
  55. package/.next/server/app/api/sessions/upload-image/route/build-manifest.json +9 -0
  56. package/.next/server/app/api/sessions/upload-image/route/server-reference-manifest.json +4 -0
  57. package/.next/server/app/api/sessions/upload-image/route.js +7 -0
  58. package/.next/server/app/api/sessions/upload-image/route.js.map +5 -0
  59. package/.next/server/app/api/sessions/upload-image/route.js.nft.json +1 -0
  60. package/.next/server/app/api/sessions/upload-image/route_client-reference-manifest.js +3 -0
  61. package/.next/server/app/api/sql/connections/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sql/mcp/route.js.nft.json +1 -1
  63. package/.next/server/app/api/sql/query/route.js.nft.json +1 -1
  64. package/.next/server/app/api/sql/schema/route.js.nft.json +1 -1
  65. package/.next/server/app/api/telemetry/route/app-paths-manifest.json +3 -0
  66. package/.next/server/app/api/telemetry/route/build-manifest.json +9 -0
  67. package/.next/server/app/api/telemetry/route/server-reference-manifest.json +4 -0
  68. package/.next/server/app/api/telemetry/route.js +6 -0
  69. package/.next/server/app/api/telemetry/route.js.map +5 -0
  70. package/.next/server/app/api/telemetry/route.js.nft.json +1 -0
  71. package/.next/server/app/api/telemetry/route_client-reference-manifest.js +3 -0
  72. package/.next/server/app/index.html +1 -1
  73. package/.next/server/app/index.rsc +3 -3
  74. package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
  75. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  76. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  77. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  78. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/page/react-loadable-manifest.json +5 -5
  80. package/.next/server/app/page_client-reference-manifest.js +1 -1
  81. package/.next/server/app-paths-manifest.json +3 -0
  82. package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js +1 -1
  83. package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js.map +1 -1
  84. package/.next/server/chunks/[root-of-the-server]__00-sqou._.js +15 -0
  85. package/.next/server/chunks/{[root-of-the-server]__0thsozo._.js.map → [root-of-the-server]__00-sqou._.js.map} +1 -1
  86. package/.next/server/chunks/[root-of-the-server]__00l5btc._.js +1 -1
  87. package/.next/server/chunks/[root-of-the-server]__00l5btc._.js.map +1 -1
  88. package/.next/server/chunks/[root-of-the-server]__01621nz._.js +3 -0
  89. package/.next/server/chunks/[root-of-the-server]__01621nz._.js.map +1 -0
  90. package/.next/server/chunks/[root-of-the-server]__06men8g._.js +1 -1
  91. package/.next/server/chunks/[root-of-the-server]__06men8g._.js.map +1 -1
  92. package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js +3 -0
  93. package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js.map +1 -0
  94. package/.next/server/chunks/[root-of-the-server]__097vtn1._.js +15 -0
  95. package/.next/server/chunks/{[root-of-the-server]__0em6a~~._.js.map → [root-of-the-server]__097vtn1._.js.map} +1 -1
  96. package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js +15 -0
  97. package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js.map +1 -0
  98. package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js +1 -1
  99. package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js.map +1 -1
  100. package/.next/server/chunks/[root-of-the-server]__0e03ite._.js +15 -0
  101. package/.next/server/chunks/{[root-of-the-server]__10wwca8._.js.map → [root-of-the-server]__0e03ite._.js.map} +1 -1
  102. package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js +15 -0
  103. package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js.map +1 -0
  104. package/.next/server/chunks/[root-of-the-server]__0h.~hv6._.js +15 -0
  105. package/.next/server/chunks/{[root-of-the-server]__0s0is_9._.js.map → [root-of-the-server]__0h.~hv6._.js.map} +1 -1
  106. package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js +33 -0
  107. package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js.map +1 -0
  108. package/.next/server/chunks/[root-of-the-server]__0s1axek._.js +15 -0
  109. package/.next/server/chunks/{[root-of-the-server]__0i~01cv._.js.map → [root-of-the-server]__0s1axek._.js.map} +1 -1
  110. package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js +15 -0
  111. package/.next/server/chunks/{[root-of-the-server]__0_o7_jj._.js.map → [root-of-the-server]__0tq0maq._.js.map} +1 -1
  112. package/.next/server/chunks/[root-of-the-server]__0viil6b._.js +15 -0
  113. package/.next/server/chunks/{[root-of-the-server]__07zx04e._.js.map → [root-of-the-server]__0viil6b._.js.map} +1 -1
  114. package/.next/server/chunks/_next-internal_server_app_api_configs_route_actions_087k_4j.js +3 -0
  115. package/.next/server/chunks/_next-internal_server_app_api_configs_route_actions_087k_4j.js.map +1 -0
  116. package/.next/server/chunks/_next-internal_server_app_api_sessions_upload-image_route_actions_09tfn3a.js +3 -0
  117. package/.next/server/chunks/_next-internal_server_app_api_sessions_upload-image_route_actions_09tfn3a.js.map +1 -0
  118. package/.next/server/chunks/_next-internal_server_app_api_telemetry_route_actions_0.yo5qr.js +3 -0
  119. package/.next/server/chunks/_next-internal_server_app_api_telemetry_route_actions_0.yo5qr.js.map +1 -0
  120. package/.next/server/chunks/ssr/_0t7oqy6._.js +3 -3
  121. package/.next/server/chunks/ssr/_0t7oqy6._.js.map +1 -1
  122. package/.next/server/middleware-build-manifest.js +3 -3
  123. package/.next/server/pages/404.html +1 -1
  124. package/.next/server/pages/500.html +1 -1
  125. package/.next/server/server-reference-manifest.js +1 -1
  126. package/.next/server/server-reference-manifest.json +1 -1
  127. package/.next/static/chunks/0qu1p~ny_bvko.css +1 -0
  128. package/.next/static/chunks/0sr3d9iqny2rf.js +4 -0
  129. package/.next/static/chunks/0xhaghhxllgzs.js +1 -0
  130. package/.next/static/chunks/{0y~7jjqbr14.5.js → 15_baizhaiq8c.js} +10 -7
  131. package/.next/static/chunks/15sigqif_v.mu.js +1 -0
  132. package/.next/static/chunks/16rky3981raz5.js +11 -0
  133. package/.next/trace +1 -1
  134. package/.next/trace-build +1 -1
  135. package/.next/types/routes.d.ts +4 -1
  136. package/.next/types/validator.ts +27 -0
  137. package/bin/cli.js +4 -4
  138. package/bin/fix-pwa-ubuntu.sh +50 -0
  139. package/package.json +1 -1
  140. package/src/app/api/configs/route.ts +40 -0
  141. package/src/app/api/sessions/launch/route.ts +3 -3
  142. package/src/app/api/sessions/route.ts +2 -2
  143. package/src/app/api/sessions/start/route.ts +2 -2
  144. package/src/app/api/sessions/upload-image/route.ts +57 -0
  145. package/src/app/api/sql/connections/route.ts +9 -6
  146. package/src/app/api/sql/mcp/route.ts +3 -2
  147. package/src/app/api/sql/query/route.ts +3 -2
  148. package/src/app/api/sql/schema/route.ts +3 -2
  149. package/src/app/api/telemetry/route.ts +18 -0
  150. package/src/app/components/AiderWizard.tsx +46 -2
  151. package/src/app/components/ClaudeWizard.tsx +30 -0
  152. package/src/app/components/DbExplorer.tsx +37 -4
  153. package/src/app/components/IdeLayout.tsx +577 -72
  154. package/src/app/components/TerminalConsole.tsx +54 -0
  155. package/src/app/page.module.css +12 -5
  156. package/src/app/page.tsx +534 -401
  157. package/src/lib/agents/AgentRunner.ts +98 -12
  158. package/src/lib/agents/AiderRunner.ts +4 -18
  159. package/src/lib/agents/ClaudeRunner.ts +16 -26
  160. package/src/lib/agents/DockerRunner.ts +16 -26
  161. package/src/lib/agents/MockRunner.ts +4 -16
  162. package/src/lib/agents/SSHRunner.ts +16 -26
  163. package/src/lib/agents/SessionConfig.ts +79 -0
  164. package/src/lib/agents/SessionManager.ts +16 -16
  165. package/src/lib/agents/SessionPersister.ts +13 -9
  166. package/src/lib/agents/ShellRunner.ts +14 -21
  167. package/src/lib/agents/WebSocketServer.ts +2 -2
  168. package/.next/server/chunks/[root-of-the-server]__01.zh3b._.js +0 -15
  169. package/.next/server/chunks/[root-of-the-server]__01.zh3b._.js.map +0 -1
  170. package/.next/server/chunks/[root-of-the-server]__07zx04e._.js +0 -15
  171. package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js +0 -15
  172. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +0 -33
  173. package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +0 -1
  174. package/.next/server/chunks/[root-of-the-server]__0em6a~~._.js +0 -15
  175. package/.next/server/chunks/[root-of-the-server]__0i~01cv._.js +0 -15
  176. package/.next/server/chunks/[root-of-the-server]__0s0is_9._.js +0 -15
  177. package/.next/server/chunks/[root-of-the-server]__0thsozo._.js +0 -15
  178. package/.next/server/chunks/[root-of-the-server]__10wwca8._.js +0 -15
  179. package/.next/static/chunks/076_b43_kienv.js +0 -1
  180. package/.next/static/chunks/07r_au7dcyu8l.js +0 -11
  181. package/.next/static/chunks/0hoyc~vbuqe0t.css +0 -1
  182. package/.next/static/chunks/0k2co3uj4w81_.js +0 -1
  183. package/.next/static/chunks/0sqeixl35qoac.js +0 -1
  184. /package/.next/static/{PdRX_M_W5hqhNmHxYt-oH → K1_el6-FnhIqHfvSMHXCm}/_buildManifest.js +0 -0
  185. /package/.next/static/{PdRX_M_W5hqhNmHxYt-oH → K1_el6-FnhIqHfvSMHXCm}/_clientMiddlewareManifest.js +0 -0
  186. /package/.next/static/{PdRX_M_W5hqhNmHxYt-oH → K1_el6-FnhIqHfvSMHXCm}/_ssgManifest.js +0 -0
package/src/app/page.tsx CHANGED
@@ -8,7 +8,7 @@ import DbExplorer from "./components/DbExplorer";
8
8
  interface RunnerStatus {
9
9
  id: string;
10
10
  name: string;
11
- type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker';
11
+ type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell';
12
12
  status: 'idle' | 'running' | 'stopped';
13
13
  createdAt: string;
14
14
  logsCount: number;
@@ -87,6 +87,16 @@ const IdeLayout = dynamic(() => import("./components/IdeLayout"), {
87
87
  ssr: false,
88
88
  });
89
89
 
90
+ interface SavedConfig {
91
+ id: string;
92
+ name: string;
93
+ type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell';
94
+ workspaceRoot?: string;
95
+ scopedDirs?: string[];
96
+ extraFlags?: string;
97
+ createdAt: string;
98
+ }
99
+
90
100
  export default function Home() {
91
101
  const [sessions, setSessions] = useState<RunnerStatus[]>([]);
92
102
  const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
@@ -97,7 +107,7 @@ export default function Home() {
97
107
  const [isClaudeWizardOpen, setIsClaudeWizardOpen] = useState<boolean>(false);
98
108
  const [isClaudeDockerLaunch, setIsClaudeDockerLaunch] = useState<boolean>(false);
99
109
  const [serverCwd, setServerCwd] = useState<string>("");
100
- const [wsPort, setWsPort] = useState<number>(3001);
110
+ const [wsPort, setWsPort] = useState<number>(9601);
101
111
  const [appVersion, setAppVersion] = useState<string>("0.2.19");
102
112
  const [lastSessionConfig, setLastSessionConfig] = useState<{
103
113
  type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker';
@@ -105,6 +115,7 @@ export default function Home() {
105
115
  workspaceRoot?: string;
106
116
  scopedDirs?: string[];
107
117
  } | null>(null);
118
+ const [configs, setConfigs] = useState<SavedConfig[]>([]);
108
119
 
109
120
  const activeAgents = sessions.filter(s => s.id !== "workspace-terminal");
110
121
  const activeSession = sessions.find(s => s.id === activeSessionId);
@@ -147,10 +158,25 @@ export default function Home() {
147
158
  }
148
159
  };
149
160
 
161
+ const fetchConfigs = async () => {
162
+ try {
163
+ const res = await fetch("/api/configs");
164
+ if (res.ok) {
165
+ const data = await res.json();
166
+ if (data.configs) {
167
+ setConfigs(data.configs);
168
+ }
169
+ }
170
+ } catch (err) {
171
+ console.error("Failed to fetch configs:", err);
172
+ }
173
+ };
174
+
150
175
  useEffect(() => {
151
176
  const initFetch = async () => {
152
177
  await fetchSessions();
153
178
  await fetchRecents();
179
+ await fetchConfigs();
154
180
  };
155
181
  initFetch();
156
182
 
@@ -219,6 +245,7 @@ export default function Home() {
219
245
  const data = await res.json();
220
246
  if (data.success) {
221
247
  await fetchSessions();
248
+ await fetchConfigs(); // Refresh configs list in case a template was saved
222
249
  setActiveSessionId(data.session.id);
223
250
  setLastSessionConfig({ type, name: sessionName, workspaceRoot: customWorkspaceRoot, scopedDirs: customScopedDirs });
224
251
  }
@@ -229,6 +256,55 @@ export default function Home() {
229
256
  }
230
257
  };
231
258
 
259
+ const handleLaunchFromConfig = async (config: SavedConfig) => {
260
+ setIsLoading(true);
261
+ try {
262
+ const res = await fetch("/api/sessions/launch", {
263
+ method: "POST",
264
+ headers: { "Content-Type": "application/json" },
265
+ body: JSON.stringify({
266
+ type: config.type,
267
+ name: config.name,
268
+ workspaceRoot: config.workspaceRoot,
269
+ scopedDirs: config.scopedDirs
270
+ })
271
+ });
272
+ const data = await res.json();
273
+ if (data.success) {
274
+ await fetchSessions();
275
+ setActiveSessionId(data.session.id);
276
+ } else {
277
+ alert(data.error || "Failed to launch configuration.");
278
+ }
279
+ } catch (err) {
280
+ console.error(err);
281
+ alert("Network error: failed to launch configuration.");
282
+ } finally {
283
+ setIsLoading(false);
284
+ }
285
+ };
286
+
287
+ const handleDeleteConfig = async (id: string) => {
288
+ if (!window.confirm("Are you sure you want to delete this configuration template?")) {
289
+ return;
290
+ }
291
+ try {
292
+ const res = await fetch("/api/configs", {
293
+ method: "POST",
294
+ headers: { "Content-Type": "application/json" },
295
+ body: JSON.stringify({ action: "delete", id })
296
+ });
297
+ if (res.ok) {
298
+ await fetchConfigs();
299
+ } else {
300
+ alert("Failed to delete configuration template.");
301
+ }
302
+ } catch (err) {
303
+ console.error(err);
304
+ alert("Network error: failed to delete configuration template.");
305
+ }
306
+ };
307
+
232
308
  // Stop/Disconnect Session
233
309
  const handleStopSession = async (id: string, deletePermanently = false) => {
234
310
  try {
@@ -283,447 +359,504 @@ export default function Home() {
283
359
  ) : (
284
360
  <div className={styles.dashboardGrid}>
285
361
 
286
- {/* Left Panel: Active Agents & Interactive Terminal */}
287
- <main className={styles.mainPanel}>
288
- <section>
289
- <h2 className={styles.sectionTitle}>Active AI Agents</h2>
290
-
291
- {activeAgents.length === 0 ? (
292
- <div className="card" style={{ padding: "3rem 1.5rem", textAlign: "center" }}>
293
- <p style={{ color: "var(--muted)", marginBottom: "1.5rem" }}>
294
- No active agent sessions in this workspace. Launch a Claude or Aider agent session to begin.
295
- </p>
296
- <div style={{ display: "flex", gap: "1rem", justifyContent: "center", flexWrap: "wrap" }}>
297
- {lastSessionConfig && (
298
- <button
299
- className={styles.quickLaunchBtn}
300
- onClick={() => {
301
- handleLaunchSession(
302
- lastSessionConfig.type,
303
- lastSessionConfig.name,
304
- lastSessionConfig.workspaceRoot,
305
- lastSessionConfig.scopedDirs
306
- );
307
- }}
308
- disabled={isLoading}
309
- style={{
310
- background: "linear-gradient(135deg, hsl(38, 92%, 50%) 0%, hsl(0, 84%, 60%) 100%)",
311
- border: "none",
312
- maxWidth: "200px"
313
- }}
314
- title={`Re-launch: ${lastSessionConfig.name || lastSessionConfig.type}`}
315
- >
316
- ⚡ Re-launch Last Session
317
- </button>
318
- )}
319
- <button
320
- className={styles.quickLaunchBtn}
321
- onClick={() => {
322
- setIsClaudeDockerLaunch(false);
323
- setIsClaudeWizardOpen(true);
362
+ {/* Column 1: Built-in Database Explorer & Shared Context Hub */}
363
+ <div className={styles.mainPanel}>
364
+ {/* Database Explorer Section */}
365
+ <section>
366
+ <h2 className={styles.sectionTitle}>Built-in Database Explorer & SQL Client</h2>
367
+ <div className="card" style={{ padding: 0, overflow: "hidden", height: "450px", border: "1px solid var(--border)" }}>
368
+ <DbExplorer workspaceRoot={serverCwd || ""} />
369
+ </div>
370
+ </section>
371
+
372
+ {/* Context Hub Staging section */}
373
+ <section style={{ marginTop: "1.5rem" }}>
374
+ <h2 className={styles.sectionTitle}>Shared Context Hub & Slash Commands</h2>
375
+ <div className={styles.contextList}>
376
+ <div className={`${styles.contextItem} card`}>
377
+ <div className={styles.contextMeta}>
378
+ <span className={styles.contextTitle}>Staged Context & System Rules (/ccc)</span>
379
+ <span className={styles.contextDetails}>Compiles AGENTS.md guidelines and files map.</span>
380
+ </div>
381
+ <button
382
+ onClick={async () => {
383
+ if (!activeSessionId) return;
384
+ await fetch("/api/sessions/inject", {
385
+ method: "POST",
386
+ headers: { "Content-Type": "application/json" },
387
+ body: JSON.stringify({ id: activeSessionId, command: "/ccc" })
388
+ });
324
389
  }}
325
- disabled={isLoading}
390
+ disabled={!activeSessionId}
326
391
  style={{
327
- background: "linear-gradient(135deg, hsl(262, 83%, 60%) 0%, hsl(195, 100%, 45%) 100%)",
328
- border: "none",
329
- maxWidth: "200px"
392
+ padding: "0.4rem 0.8rem",
393
+ borderRadius: "6px",
394
+ border: "1px solid var(--primary)",
395
+ backgroundColor: "transparent",
396
+ color: "var(--primary)",
397
+ fontSize: "0.75rem",
398
+ fontWeight: 600,
399
+ opacity: activeSessionId ? 1 : 0.5,
400
+ cursor: activeSessionId ? "pointer" : "not-allowed"
330
401
  }}
331
402
  >
332
- Launch Claude PTY
403
+ 🚀 Mount (/ccc)
333
404
  </button>
334
- <button
335
- className={styles.quickLaunchBtn}
336
- onClick={() => {
337
- setIsAiderDockerLaunch(false);
338
- setIsAiderWizardOpen(true);
405
+ </div>
406
+
407
+ <div className={`${styles.contextItem} card`}>
408
+ <div className={styles.contextMeta}>
409
+ <span className={styles.contextTitle}>Staged Developer Prompt (/ccp)</span>
410
+ <span className={styles.contextDetails}>Executes prompt in .ai/neural-loom/cprompt.md.</span>
411
+ </div>
412
+ <button
413
+ onClick={async () => {
414
+ if (!activeSessionId) return;
415
+ await fetch("/api/sessions/inject", {
416
+ method: "POST",
417
+ headers: { "Content-Type": "application/json" },
418
+ body: JSON.stringify({ id: activeSessionId, command: "/ccp" })
419
+ });
339
420
  }}
340
- disabled={isLoading}
421
+ disabled={!activeSessionId}
341
422
  style={{
342
- background: "linear-gradient(135deg, hsl(210, 80%, 45%) 0%, hsl(262, 80%, 55%) 100%)",
343
- border: "none",
344
- maxWidth: "200px"
423
+ padding: "0.4rem 0.8rem",
424
+ borderRadius: "6px",
425
+ border: "1px solid var(--success)",
426
+ backgroundColor: "transparent",
427
+ color: "var(--success)",
428
+ fontSize: "0.75rem",
429
+ fontWeight: 600,
430
+ opacity: activeSessionId ? 1 : 0.5,
431
+ cursor: activeSessionId ? "pointer" : "not-allowed"
345
432
  }}
346
433
  >
347
- Launch Aider PTY
434
+ Run (/ccp)
348
435
  </button>
349
436
  </div>
350
- <div style={{ marginTop: "1.5rem", fontSize: "0.75rem", color: "var(--muted)" }}>
351
- Want to verify layout features first?{" "}
352
- <span
353
- onClick={() => handleLaunchSession('mock')}
354
- style={{ color: "var(--primary)", cursor: "pointer", textDecoration: "underline" }}
355
- >
356
- Start simulated sandbox session
357
- </span>
358
- </div>
359
437
  </div>
360
- ) : (
361
- <div className={styles.agentGrid}>
362
- {activeAgents.map(session => (
363
- <div key={session.id} className={`${styles.agentCard} card ${activeSessionId === session.id ? styles.activeBorder : ""}`}>
364
- <div className={styles.agentHeader}>
365
- <span className={styles.agentName}>{session.name}</span>
366
- <div style={{ display: "flex", gap: "0.35rem", alignItems: "center" }}>
367
- {session.status === 'running' && session.agentState && (
368
- <span className={`${styles.agentStateBadge} ${styles['state-' + session.agentState]}`}>
369
- {session.agentState === 'thinking' && `${getModeIcon(session.type)} 💭 thinking`}
370
- {session.agentState === 'needs-input' && `${getModeIcon(session.type)} ⚠️ needs input`}
371
- {session.agentState === 'complete' && `${getModeIcon(session.type)} ✅ complete`}
372
- {session.agentState === 'idle' && `${getModeIcon(session.type)} 💤 idle`}
438
+ <ContextEditor />
439
+ </section>
440
+ </div>
441
+
442
+ {/* Column 2: Active Agents & Console Panel */}
443
+ <div className={styles.mainPanel}>
444
+ <section>
445
+ <h2 className={styles.sectionTitle}>Active AI Agents</h2>
446
+
447
+ {activeAgents.length === 0 ? (
448
+ <div className="card" style={{ padding: "3rem 1.5rem", textAlign: "center" }}>
449
+ <p style={{ color: "var(--muted)", marginBottom: "1.5rem" }}>
450
+ No active agent sessions. Launch a Claude or Aider agent session to begin.
451
+ </p>
452
+ <div style={{ display: "flex", gap: "0.75rem", flexDirection: "column" }}>
453
+ {lastSessionConfig && (
454
+ <button
455
+ className={styles.quickLaunchBtn}
456
+ onClick={() => {
457
+ handleLaunchSession(
458
+ lastSessionConfig.type,
459
+ lastSessionConfig.name,
460
+ lastSessionConfig.workspaceRoot,
461
+ lastSessionConfig.scopedDirs
462
+ );
463
+ }}
464
+ disabled={isLoading}
465
+ style={{
466
+ background: "linear-gradient(135deg, hsl(38, 92%, 50%) 0%, hsl(0, 84%, 60%) 100%)",
467
+ border: "none"
468
+ }}
469
+ title={`Re-launch: ${lastSessionConfig.name || lastSessionConfig.type}`}
470
+ >
471
+ ⚡ Re-launch Last Session
472
+ </button>
473
+ )}
474
+ <div style={{ display: "flex", gap: "0.5rem" }}>
475
+ <button
476
+ className={styles.quickLaunchBtn}
477
+ onClick={() => {
478
+ setIsClaudeDockerLaunch(false);
479
+ setIsClaudeWizardOpen(true);
480
+ }}
481
+ disabled={isLoading}
482
+ style={{
483
+ background: "linear-gradient(135deg, hsl(262, 83%, 60%) 0%, hsl(195, 100%, 45%) 100%)",
484
+ border: "none",
485
+ flex: 1
486
+ }}
487
+ >
488
+ Launch Claude
489
+ </button>
490
+ <button
491
+ className={styles.quickLaunchBtn}
492
+ onClick={() => {
493
+ setIsAiderDockerLaunch(false);
494
+ setIsAiderWizardOpen(true);
495
+ }}
496
+ disabled={isLoading}
497
+ style={{
498
+ background: "linear-gradient(135deg, hsl(210, 80%, 45%) 0%, hsl(262, 80%, 55%) 100%)",
499
+ border: "none",
500
+ flex: 1
501
+ }}
502
+ >
503
+ Launch Aider
504
+ </button>
505
+ </div>
506
+ </div>
507
+ <div style={{ marginTop: "1.5rem", fontSize: "0.75rem", color: "var(--muted)" }}>
508
+ Need a simulator?{" "}
509
+ <span
510
+ onClick={() => handleLaunchSession('mock')}
511
+ style={{ color: "var(--primary)", cursor: "pointer", textDecoration: "underline" }}
512
+ >
513
+ Start mock playground
514
+ </span>
515
+ </div>
516
+ </div>
517
+ ) : (
518
+ <div className={styles.agentGrid}>
519
+ {activeAgents.map(session => (
520
+ <div key={session.id} className={`${styles.agentCard} card ${activeSessionId === session.id ? styles.activeBorder : ""}`}>
521
+ <div className={styles.agentHeader}>
522
+ <span className={styles.agentName}>{session.name}</span>
523
+ <div style={{ display: "flex", gap: "0.35rem", alignItems: "center" }}>
524
+ {session.status === 'running' && session.agentState && (
525
+ <span className={`${styles.agentStateBadge} ${styles['state-' + session.agentState]}`}>
526
+ {session.agentState === 'thinking' && `${getModeIcon(session.type)} 💭`}
527
+ {session.agentState === 'needs-input' && `${getModeIcon(session.type)} ⚠️`}
528
+ {session.agentState === 'complete' && `${getModeIcon(session.type)} ✅`}
529
+ {session.agentState === 'idle' && `${getModeIcon(session.type)} 💤`}
530
+ </span>
531
+ )}
532
+ <span className={`${styles.agentBadge} ${session.status === 'stopped' ? styles.stoppedBadge : ""}`}>
533
+ {session.status}
373
534
  </span>
374
- )}
375
- <span className={`${styles.agentBadge} ${session.status === 'stopped' ? styles.stoppedBadge : ""}`}>
376
- {session.status}
377
- </span>
535
+ </div>
378
536
  </div>
379
- </div>
380
- <div className={styles.cardBody}>
381
- <p style={{ fontSize: "0.825rem", color: "var(--muted)" }}>
382
- ID: <code style={{ fontSize: "0.75rem" }}>{session.id.substring(0, 8)}</code><br />
383
- Type: {
384
- session.type === 'claude' ? "Claude Local PTY" :
385
- session.type === 'claude-docker' ? "Claude Docker PTY" :
386
- session.type === 'claude-ssh' ? "Claude SSH PTY" :
387
- session.type === 'aider' ? "Aider Local PTY" :
388
- session.type === 'aider-docker' ? "Aider Docker PTY" :
389
- session.type === 'mock' ? "Simulated Mock PTY" : "Unknown PTY"
390
- }<br />
391
- Logs: {session.logsCount} captured lines
392
- </p>
393
-
394
- <div style={{ display: "flex", gap: "0.5rem", marginTop: "1rem" }}>
395
- {session.status === 'running' ? (
537
+ <div className={styles.cardBody} style={{ padding: "1rem" }}>
538
+ <p style={{ fontSize: "0.8rem", color: "var(--muted)", margin: 0 }}>
539
+ ID: <code style={{ fontSize: "0.72rem" }}>{session.id.substring(0, 8)}</code> | Type: {session.type}<br />
540
+ Logs: {session.logsCount} lines
541
+ </p>
542
+
543
+ <div style={{ display: "flex", gap: "0.5rem", marginTop: "0.75rem" }}>
544
+ {session.status === 'running' ? (
545
+ <button
546
+ onClick={() => handleStopSession(session.id, false)}
547
+ style={{
548
+ flex: 1,
549
+ padding: "0.35rem",
550
+ borderRadius: "6px",
551
+ border: "1px solid var(--danger)",
552
+ backgroundColor: "transparent",
553
+ fontSize: "0.75rem",
554
+ color: "var(--danger)",
555
+ cursor: "pointer"
556
+ }}
557
+ >Stop</button>
558
+ ) : (
559
+ <button
560
+ onClick={() => handleStopSession(session.id, true)}
561
+ style={{
562
+ flex: 1,
563
+ padding: "0.35rem",
564
+ borderRadius: "6px",
565
+ border: "1px solid var(--border)",
566
+ backgroundColor: "transparent",
567
+ fontSize: "0.75rem",
568
+ color: "var(--muted)",
569
+ cursor: "pointer"
570
+ }}
571
+ >Delete</button>
572
+ )}
396
573
  <button
397
- onClick={() => handleStopSession(session.id, false)}
574
+ onClick={() => setActiveSessionId(session.id)}
398
575
  style={{
399
- flex: 1,
400
- padding: "0.4rem 0.6rem",
576
+ flex: 2,
577
+ padding: "0.35rem",
401
578
  borderRadius: "6px",
402
- border: "1px solid var(--danger)",
403
- backgroundColor: "transparent",
404
- fontSize: "0.75rem",
405
- fontWeight: 600,
406
- color: "var(--danger)"
407
- }}
408
- >Stop</button>
409
- ) : (
410
- <button
411
- onClick={() => handleStopSession(session.id, true)}
412
- style={{
413
- flex: 1,
414
- padding: "0.4rem 0.6rem",
415
- borderRadius: "6px",
416
- border: "1px solid var(--border)",
417
- backgroundColor: "transparent",
579
+ border: "none",
580
+ backgroundColor: activeSessionId === session.id ? "var(--border-focus)" : "var(--primary)",
418
581
  fontSize: "0.75rem",
419
582
  fontWeight: 600,
420
- color: "var(--muted)"
583
+ color: "#fff",
584
+ cursor: "pointer"
421
585
  }}
422
- >Delete</button>
423
- )}
424
- <button
425
- onClick={() => setActiveSessionId(session.id)}
426
- style={{
427
- flex: 2,
428
- padding: "0.4rem 0.6rem",
429
- borderRadius: "6px",
430
- border: "none",
431
- backgroundColor: activeSessionId === session.id ? "var(--border-focus)" : "var(--primary)",
432
- fontSize: "0.75rem",
433
- fontWeight: 600,
434
- color: "#fff"
435
- }}
436
- >
437
- {activeSessionId === session.id ? "Connected" : "Open Session"}
438
- </button>
586
+ >
587
+ {activeSessionId === session.id ? "Connected" : "Open Console"}
588
+ </button>
589
+ </div>
439
590
  </div>
440
591
  </div>
441
- </div>
442
- ))}
443
- </div>
444
- )}
445
- </section>
446
-
447
- {/* Interactive Session Terminal Display */}
448
- {activeSessionId && activeSessionId !== "workspace-terminal" && (
449
- <section className="card" style={{ display: "flex", flexDirection: "column", border: "1px solid var(--border-focus)" }}>
450
- <div className={styles.agentHeader} style={{ justifyContent: "space-between", background: "rgba(109, 40, 217, 0.08)" }}>
451
- <span style={{ fontWeight: 600, display: "flex", alignItems: "center", gap: "0.5rem" }}>
452
- 🖥️ Console Bridge — {sessions.find(s => s.id === activeSessionId)?.name}
453
- </span>
454
- <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
455
- {sessions.find(s => s.id === activeSessionId)?.status === "stopped" && (
456
- <button
457
- onClick={async () => {
458
- try {
459
- const res = await fetch("/api/sessions/start", {
460
- method: "POST",
461
- headers: { "Content-Type": "application/json" },
462
- body: JSON.stringify({ id: activeSessionId }),
463
- });
464
- if (res.ok) {
465
- await fetchSessions();
466
- } else {
467
- const data = await res.json();
468
- alert(data.error || "Failed to restart session.");
592
+ ))}
593
+ </div>
594
+ )}
595
+ </section>
596
+
597
+ {/* Interactive Session Terminal Display */}
598
+ {activeSessionId && activeSessionId !== "workspace-terminal" && (
599
+ <section className="card" style={{ display: "flex", flexDirection: "column", border: "1px solid var(--border-focus)", marginTop: "1rem" }}>
600
+ <div className={styles.agentHeader} style={{ justifyContent: "space-between", background: "rgba(109, 40, 217, 0.08)", padding: "0.5rem 1rem" }}>
601
+ <span style={{ fontWeight: 600, fontSize: "0.85rem", display: "flex", alignItems: "center", gap: "0.5rem" }}>
602
+ 🖥️ Console {sessions.find(s => s.id === activeSessionId)?.name}
603
+ </span>
604
+ <div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
605
+ {sessions.find(s => s.id === activeSessionId)?.status === "stopped" && (
606
+ <button
607
+ onClick={async () => {
608
+ try {
609
+ const activeSess = sessions.find(s => s.id === activeSessionId);
610
+ const isClaude = activeSess && (activeSess.type === 'claude' || activeSess.type === 'claude-docker' || activeSess.type === 'claude-ssh');
611
+ let resume = false;
612
+ if (isClaude) {
613
+ resume = window.confirm("Would you like to resume your previous Claude session?");
614
+ }
615
+
616
+ const res = await fetch("/api/sessions/start", {
617
+ method: "POST",
618
+ headers: { "Content-Type": "application/json" },
619
+ body: JSON.stringify({ id: activeSessionId, resume }),
620
+ });
621
+ if (res.ok) {
622
+ await fetchSessions();
623
+ } else {
624
+ const data = await res.json();
625
+ alert(data.error || "Failed to restart session.");
626
+ }
627
+ } catch (err) {
628
+ console.error("Failed to restart session:", err);
469
629
  }
470
- } catch (err) {
471
- console.error("Failed to restart session:", err);
472
- alert("Network error: failed to restart session.");
473
- }
474
- }}
630
+ }}
631
+ style={{
632
+ padding: "0.15rem 0.4rem",
633
+ borderRadius: "4px",
634
+ border: "1px solid #eab308",
635
+ backgroundColor: "rgba(234, 179, 8, 0.15)",
636
+ color: "#eab308",
637
+ fontSize: "0.65rem",
638
+ fontWeight: 600,
639
+ cursor: "pointer",
640
+ }}
641
+ >
642
+ ⚡ Restart
643
+ </button>
644
+ )}
645
+ <button
646
+ onClick={() => setActiveSessionId(null)}
475
647
  style={{
476
- padding: "0.2rem 0.5rem",
477
- borderRadius: "4px",
478
- border: "1px solid #eab308",
479
- backgroundColor: "rgba(234, 179, 8, 0.15)",
480
- color: "#eab308",
481
- fontSize: "0.7rem",
482
- fontWeight: 600,
648
+ border: "none",
649
+ background: "none",
650
+ color: "var(--muted)",
483
651
  cursor: "pointer",
484
- boxShadow: "0 0 8px rgba(234, 179, 8, 0.2)",
652
+ fontSize: "0.75rem"
485
653
  }}
486
- title="Restart the stopped session process runner"
487
- >
488
- ⚡ Restart Session
489
- </button>
490
- )}
491
- <button
492
- onClick={() => setActiveSessionId(null)}
493
- style={{
494
- border: "none",
495
- background: "none",
496
- color: "var(--muted)",
497
- cursor: "pointer",
498
- fontSize: "0.75rem"
499
- }}
500
- >Close Panel ✖</button>
654
+ >✖</button>
655
+ </div>
656
+ </div>
657
+ <TerminalConsole sessionId={activeSessionId} wsPort={wsPort} />
658
+ </section>
659
+ )}
660
+ </div>
661
+
662
+ {/* Column 3: Environments, Saved Templates, Workspace Terminal, Quick Actions */}
663
+ <div className={styles.sidePanel}>
664
+ {/* Execution Environments */}
665
+ <section>
666
+ <h2 className={styles.sectionTitle}>Execution Environments</h2>
667
+ <div className={styles.envGrid}>
668
+ <div className={styles.envRow} style={{ padding: "0.5rem 0.75rem" }}>
669
+ <span className={styles.envName}>💻 Local Host</span>
670
+ <span className={styles.envStatus} style={{ color: "var(--success)" }}>Active</span>
671
+ </div>
672
+ <div className={styles.envRow} style={{ opacity: isDockerActive ? 1 : 0.6, padding: "0.5rem 0.75rem" }}>
673
+ <span className={styles.envName}>🐳 Docker Sandbox</span>
674
+ <span className={styles.envStatus} style={{ color: isDockerActive ? "var(--success)" : "var(--muted)" }}>
675
+ {isDockerActive ? "Active" : "Not Mounted"}
676
+ </span>
677
+ </div>
678
+ <div className={styles.envRow} style={{ opacity: isSshActive ? 1 : 0.6, padding: "0.5rem 0.75rem" }}>
679
+ <span className={styles.envName}>🌐 Remote VM (SSH)</span>
680
+ <span className={styles.envStatus} style={{ color: isSshActive ? "var(--success)" : "var(--muted)" }}>
681
+ {isSshActive ? "Active" : "Offline"}
682
+ </span>
501
683
  </div>
502
684
  </div>
503
- <TerminalConsole sessionId={activeSessionId} wsPort={wsPort} />
504
685
  </section>
505
- )}
506
-
507
- {/* Workspace Shell Terminal */}
508
- <section>
509
- <h2 className={styles.sectionTitle}>Workspace Shell Terminal</h2>
510
- <div className="card" style={{ display: "flex", flexDirection: "column", border: "1px solid var(--border)", padding: 0 }}>
511
- <div className={styles.agentHeader} style={{ justifyContent: "space-between", background: "rgba(255, 255, 255, 0.02)" }}>
512
- <span style={{ fontWeight: 600, display: "flex", alignItems: "center", gap: "0.5rem", fontSize: "0.85rem" }}>
513
- 💻 Local Workspace Shell (CWD: {serverCwd || "root"})
514
- </span>
515
- <span style={{ fontSize: "0.7rem", color: "var(--muted)" }}>Pre-configured environment</span>
686
+
687
+ {/* Saved Configurations Templates */}
688
+ <section>
689
+ <h2 className={styles.sectionTitle}>Saved Configurations</h2>
690
+ <div className="card" style={{ padding: "1rem", display: "flex", flexDirection: "column", gap: "0.75rem" }}>
691
+ {configs.length === 0 ? (
692
+ <p style={{ color: "var(--muted)", fontSize: "0.8rem", textAlign: "center", margin: "1rem 0" }}>
693
+ No configuration templates saved yet. Save a template from the Claude/Aider launch wizards.
694
+ </p>
695
+ ) : (
696
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem", maxHeight: "200px", overflowY: "auto" }}>
697
+ {configs.map((config) => (
698
+ <div key={config.id} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", background: "rgba(255, 255, 255, 0.02)", padding: "0.5rem", borderRadius: "6px", border: "1px solid var(--border)" }}>
699
+ <div style={{ display: "flex", flexDirection: "column", gap: "0.15rem", minWidth: 0 }}>
700
+ <span style={{ fontSize: "0.8rem", fontWeight: 600, color: "var(--foreground)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{config.name}</span>
701
+ <span style={{ fontSize: "0.65rem", color: "var(--muted)", textTransform: "uppercase" }}>{config.type}</span>
702
+ </div>
703
+ <div style={{ display: "flex", gap: "0.25rem" }}>
704
+ <button
705
+ onClick={() => handleLaunchFromConfig(config)}
706
+ disabled={isLoading}
707
+ style={{
708
+ padding: "0.2rem 0.4rem",
709
+ borderRadius: "4px",
710
+ backgroundColor: "var(--primary)",
711
+ color: "#fff",
712
+ border: "none",
713
+ fontSize: "0.7rem",
714
+ cursor: "pointer"
715
+ }}
716
+ >Launch</button>
717
+ <button
718
+ onClick={() => handleDeleteConfig(config.id)}
719
+ style={{
720
+ padding: "0.2rem 0.4rem",
721
+ borderRadius: "4px",
722
+ backgroundColor: "transparent",
723
+ color: "var(--danger)",
724
+ border: "1px solid var(--danger)",
725
+ fontSize: "0.7rem",
726
+ cursor: "pointer"
727
+ }}
728
+ >Delete</button>
729
+ </div>
730
+ </div>
731
+ ))}
732
+ </div>
733
+ )}
516
734
  </div>
517
- <TerminalConsole sessionId="workspace-terminal" height="300px" wsPort={wsPort} />
518
- </div>
519
- </section>
520
-
521
- {/* Context Hub Staging section */}
522
- <section style={{ marginTop: "2rem" }}>
523
- <h2 className={styles.sectionTitle}>Shared Context Hub & Slash Commands</h2>
524
- <div className={styles.contextList}>
525
- <div className={`${styles.contextItem} card`}>
526
- <div className={styles.contextMeta}>
527
- <span className={styles.contextTitle}>Staged Context & System Rules (/ccc)</span>
528
- <span className={styles.contextDetails}>Compiles AGENTS.md guidelines and project files folder hierarchy map.</span>
529
- </div>
530
- <button
531
- onClick={async () => {
532
- if (!activeSessionId) return;
533
- await fetch("/api/sessions/inject", {
534
- method: "POST",
535
- headers: { "Content-Type": "application/json" },
536
- body: JSON.stringify({ id: activeSessionId, command: "/ccc" })
537
- });
735
+ </section>
736
+
737
+ {/* Launch wizards buttons */}
738
+ <section style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
739
+ <div style={{ display: "flex", gap: "0.5rem" }}>
740
+ <button
741
+ className={styles.quickLaunchBtn}
742
+ onClick={() => {
743
+ setIsClaudeDockerLaunch(false);
744
+ setIsClaudeWizardOpen(true);
745
+ }}
746
+ disabled={isLoading}
747
+ style={{
748
+ background: "linear-gradient(135deg, hsl(262, 83%, 60%) 0%, hsl(195, 100%, 45%) 100%)",
749
+ border: "none",
750
+ padding: "0.6rem",
751
+ fontSize: "0.8rem",
752
+ flex: 1
538
753
  }}
539
- disabled={!activeSessionId}
754
+ >
755
+ 🤖 Claude Local
756
+ </button>
757
+ <button
758
+ className={styles.quickLaunchBtn}
759
+ onClick={() => {
760
+ setIsClaudeDockerLaunch(true);
761
+ setIsClaudeWizardOpen(true);
762
+ }}
763
+ disabled={isLoading}
540
764
  style={{
541
- padding: "0.4rem 0.8rem",
542
- borderRadius: "6px",
543
- border: "1px solid var(--primary)",
544
- backgroundColor: "transparent",
545
- color: "var(--primary)",
546
- fontSize: "0.75rem",
547
- fontWeight: 600,
548
- opacity: activeSessionId ? 1 : 0.5,
549
- cursor: activeSessionId ? "pointer" : "not-allowed"
765
+ background: "linear-gradient(135deg, hsl(170, 72%, 40%) 0%, hsl(195, 100%, 40%) 100%)",
766
+ border: "none",
767
+ padding: "0.6rem",
768
+ fontSize: "0.8rem",
769
+ flex: 1
550
770
  }}
551
771
  >
552
- 🚀 Mount Context (/ccc)
772
+ 🐳 Claude Docker
553
773
  </button>
554
774
  </div>
555
-
556
- <div className={`${styles.contextItem} card`}>
557
- <div className={styles.contextMeta}>
558
- <span className={styles.contextTitle}>Staged Developer Prompt (/ccp)</span>
559
- <span className={styles.contextDetails}>Executes instructions written in .ai/neural-loom/cprompt.md.</span>
560
- </div>
561
- <button
562
- onClick={async () => {
563
- if (!activeSessionId) return;
564
- await fetch("/api/sessions/inject", {
565
- method: "POST",
566
- headers: { "Content-Type": "application/json" },
567
- body: JSON.stringify({ id: activeSessionId, command: "/ccp" })
568
- });
775
+ <div style={{ display: "flex", gap: "0.5rem" }}>
776
+ <button
777
+ className={styles.quickLaunchBtn}
778
+ onClick={() => {
779
+ setIsAiderDockerLaunch(false);
780
+ setIsAiderWizardOpen(true);
781
+ }}
782
+ disabled={isLoading}
783
+ style={{
784
+ background: "linear-gradient(135deg, hsl(210, 80%, 45%) 0%, hsl(262, 80%, 55%) 100%)",
785
+ border: "none",
786
+ padding: "0.6rem",
787
+ fontSize: "0.8rem",
788
+ flex: 1
569
789
  }}
570
- disabled={!activeSessionId}
790
+ >
791
+ 🐍 Aider Local
792
+ </button>
793
+ <button
794
+ className={styles.quickLaunchBtn}
795
+ onClick={() => {
796
+ setIsAiderDockerLaunch(true);
797
+ setIsAiderWizardOpen(true);
798
+ }}
799
+ disabled={isLoading}
571
800
  style={{
572
- padding: "0.4rem 0.8rem",
573
- borderRadius: "6px",
574
- border: "1px solid var(--success)",
575
- backgroundColor: "transparent",
576
- color: "var(--success)",
577
- fontSize: "0.75rem",
578
- fontWeight: 600,
579
- opacity: activeSessionId ? 1 : 0.5,
580
- cursor: activeSessionId ? "pointer" : "not-allowed"
801
+ background: "linear-gradient(135deg, hsl(195, 100%, 40%) 0%, hsl(262, 80%, 55%) 100%)",
802
+ border: "none",
803
+ padding: "0.6rem",
804
+ fontSize: "0.8rem",
805
+ flex: 1
581
806
  }}
582
807
  >
583
- Trigger Prompt (/ccp)
808
+ 🐳 Aider Docker
584
809
  </button>
585
810
  </div>
586
- </div>
587
- <ContextEditor />
588
- </section>
589
-
590
- {/* Database Explorer Section */}
591
- <section style={{ marginTop: "2rem" }}>
592
- <h2 className={styles.sectionTitle}>Built-in Database Explorer & SQL Client</h2>
593
- <div className="card" style={{ padding: 0, overflow: "hidden", height: "480px", border: "1px solid var(--border)" }}>
594
- <DbExplorer workspaceRoot={serverCwd || ""} />
595
- </div>
596
- </section>
597
- </main>
598
-
599
- {/* Right Panel: Environments & Actions */}
600
- <aside className={styles.sidePanel}>
601
- <section>
602
- <h2 className={styles.sectionTitle}>Execution Environments</h2>
603
- <div className={styles.envGrid}>
604
- <div className={styles.envRow}>
605
- <span className={styles.envName}>💻 Local host OS</span>
606
- <span className={styles.envStatus} style={{ color: "var(--success)" }}>Active</span>
607
- </div>
608
- <div className={styles.envRow} style={{ opacity: isDockerActive ? 1 : 0.6 }}>
609
- <span className={styles.envName}>🐳 Docker Sandbox</span>
610
- <span className={styles.envStatus} style={{ color: isDockerActive ? "var(--success)" : "var(--muted)" }}>
611
- {isDockerActive ? "Active" : "Not Mounted"}
612
- </span>
613
- </div>
614
- <div className={styles.envRow} style={{ opacity: isSshActive ? 1 : 0.6 }}>
615
- <span className={styles.envName}>🌐 Remote VM (SSH)</span>
616
- <span className={styles.envStatus} style={{ color: isSshActive ? "var(--success)" : "var(--muted)" }}>
617
- {isSshActive ? "Active" : "Disconnected"}
618
- </span>
619
- </div>
620
- </div>
621
- </section>
622
-
623
- <section style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
624
- <button
625
- className={styles.quickLaunchBtn}
626
- onClick={() => {
627
- setIsClaudeDockerLaunch(false);
628
- setIsClaudeWizardOpen(true);
629
- }}
630
- disabled={isLoading}
631
- style={{
632
- background: "linear-gradient(135deg, hsl(262, 83%, 60%) 0%, hsl(195, 100%, 45%) 100%)",
633
- border: "none"
634
- }}
635
- >
636
- <span>🤖</span>
637
- <span>Launch Claude PTY</span>
638
- </button>
639
- <button
640
- className={styles.quickLaunchBtn}
641
- onClick={() => {
642
- setIsClaudeDockerLaunch(true);
643
- setIsClaudeWizardOpen(true);
644
- }}
645
- disabled={isLoading}
646
- style={{
647
- background: "linear-gradient(135deg, hsl(170, 72%, 40%) 0%, hsl(195, 100%, 40%) 100%)",
648
- border: "none"
649
- }}
650
- >
651
- <span>🐳</span>
652
- <span>Launch Docker PTY</span>
653
- </button>
654
- <button
655
- className={styles.quickLaunchBtn}
656
- onClick={() => setIsSshWizardOpen(true)}
657
- disabled={isLoading}
658
- style={{
659
- background: "linear-gradient(135deg, hsl(38, 92%, 50%) 0%, hsl(0, 84%, 60%) 100%)",
660
- border: "none"
661
- }}
662
- >
663
- <span>🌐</span>
664
- <span>Launch SSH PTY</span>
665
- </button>
666
- <button
667
- className={styles.quickLaunchBtn}
668
- onClick={() => {
669
- setIsAiderDockerLaunch(false);
670
- setIsAiderWizardOpen(true);
671
- }}
672
- disabled={isLoading}
673
- style={{
674
- background: "linear-gradient(135deg, hsl(210, 80%, 45%) 0%, hsl(262, 80%, 55%) 100%)",
675
- border: "none"
676
- }}
677
- >
678
- <span>🐍</span>
679
- <span>Launch Aider PTY</span>
680
- </button>
681
- <button
682
- className={styles.quickLaunchBtn}
683
- onClick={() => {
684
- setIsAiderDockerLaunch(true);
685
- setIsAiderWizardOpen(true);
686
- }}
687
- disabled={isLoading}
688
- style={{
689
- background: "linear-gradient(135deg, hsl(195, 100%, 40%) 0%, hsl(262, 80%, 55%) 100%)",
690
- border: "none"
691
- }}
692
- >
693
- <span>🐳</span>
694
- <span>Launch Aider Docker</span>
695
- </button>
696
- </section>
697
-
698
- <section className="card" style={{ padding: "1.5rem", display: "flex", flexDirection: "column", gap: "0.75rem" }}>
699
- <h3 style={{ fontSize: "1rem", fontWeight: 700 }}>Quick Actions</h3>
700
- <ul style={{ listStyle: "none", fontSize: "0.875rem", display: "flex", flexDirection: "column", gap: "0.5rem", color: "var(--muted)" }}>
701
- <li style={{ display: "flex", justifyContent: "space-between" }}>
702
- <span>Configure global API Keys</span>
703
- <span style={{ color: "var(--primary)", cursor: "pointer" }}>→</span>
704
- </li>
705
- <li style={{ display: "flex", justifyContent: "space-between" }}>
706
- <span>Edit rules templates</span>
707
- <span style={{ color: "var(--primary)", cursor: "pointer" }}>→</span>
708
- </li>
709
- <li style={{ display: "flex", justifyContent: "space-between" }}>
710
- <span>Inspect process locks</span>
711
- <span style={{ color: "var(--primary)", cursor: "pointer" }}>→</span>
712
- </li>
713
- </ul>
714
- <div style={{ marginTop: "1.2rem", textAlign: "center", fontSize: "0.72rem", color: "var(--muted)" }}>
715
- Need to troubleshoot?{" "}
716
- <span
717
- onClick={() => handleLaunchSession('mock')}
718
- style={{ color: "var(--primary)", cursor: "pointer", textDecoration: "underline" }}
811
+ <button
812
+ className={styles.quickLaunchBtn}
813
+ onClick={() => setIsSshWizardOpen(true)}
814
+ disabled={isLoading}
815
+ style={{
816
+ background: "linear-gradient(135deg, hsl(38, 92%, 50%) 0%, hsl(0, 84%, 60%) 100%)",
817
+ border: "none",
818
+ padding: "0.6rem",
819
+ fontSize: "0.8rem"
820
+ }}
719
821
  >
720
- Launch simulated playground sandbox
721
- </span>
722
- </div>
723
- </section>
724
- </aside>
822
+ 🌐 Connect Remote SSH
823
+ </button>
824
+ </section>
725
825
 
726
- </div>
826
+ {/* Workspace Shell */}
827
+ <section>
828
+ <h2 className={styles.sectionTitle}>Workspace Terminal</h2>
829
+ <div className="card" style={{ display: "flex", flexDirection: "column", border: "1px solid var(--border)", padding: 0 }}>
830
+ <div className={styles.agentHeader} style={{ justifyContent: "space-between", background: "rgba(255, 255, 255, 0.02)", padding: "0.4rem 0.75rem" }}>
831
+ <span style={{ fontWeight: 600, fontSize: "0.75rem" }}>
832
+ 💻 Local Shell (CWD: {serverCwd.split(/[/\\]/).pop() || "root"})
833
+ </span>
834
+ </div>
835
+ <TerminalConsole sessionId="workspace-terminal" height="180px" wsPort={wsPort} />
836
+ </div>
837
+ </section>
838
+
839
+ {/* Quick Actions */}
840
+ <section className="card" style={{ padding: "1rem", display: "flex", flexDirection: "column", gap: "0.5rem" }}>
841
+ <h3 style={{ fontSize: "0.9rem", fontWeight: 700, margin: 0 }}>Quick Actions</h3>
842
+ <ul style={{ listStyle: "none", fontSize: "0.75rem", display: "flex", flexDirection: "column", gap: "0.35rem", color: "var(--muted)", padding: 0, margin: 0 }}>
843
+ <li style={{ display: "flex", justifyContent: "space-between" }}>
844
+ <span>Configure API Keys</span>
845
+ <span style={{ color: "var(--primary)", cursor: "pointer" }}>→</span>
846
+ </li>
847
+ <li style={{ display: "flex", justifyContent: "space-between" }}>
848
+ <span>Edit rules templates</span>
849
+ <span style={{ color: "var(--primary)", cursor: "pointer" }}>→</span>
850
+ </li>
851
+ <li style={{ display: "flex", justifyContent: "space-between" }}>
852
+ <span>Inspect process locks</span>
853
+ <span style={{ color: "var(--primary)", cursor: "pointer" }}>→</span>
854
+ </li>
855
+ </ul>
856
+ </section>
857
+ </div>
858
+
859
+ </div>
727
860
  )}
728
861
 
729
862
  {/* Footer */}
@@ -746,9 +879,9 @@ export default function Home() {
746
879
  <AiderWizard
747
880
  onClose={() => setIsAiderWizardOpen(false)}
748
881
  isDocker={isAiderDockerLaunch}
749
- onConfirmLaunch={() => {
882
+ onConfirmLaunch={(name, workspaceRoot, scopedDirs) => {
750
883
  setIsAiderWizardOpen(false);
751
- handleLaunchSession(isAiderDockerLaunch ? 'aider-docker' : 'aider');
884
+ handleLaunchSession(isAiderDockerLaunch ? 'aider-docker' : 'aider', name, workspaceRoot, scopedDirs);
752
885
  }}
753
886
  />
754
887
  )}