diff --git a/.gitignore b/.gitignore
index 3c7950e..f6cb181 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ gadget-drone.*.log
logfetch
.gadget
+gadget/
node_modules
diff --git a/gadget-code/frontend/src/components/ProjectPanel.tsx b/gadget-code/frontend/src/components/ProjectPanel.tsx
new file mode 100644
index 0000000..ed5e1fb
--- /dev/null
+++ b/gadget-code/frontend/src/components/ProjectPanel.tsx
@@ -0,0 +1,43 @@
+import type { Project } from '../lib/api';
+
+interface ProjectPanelProps {
+ project: Project | null;
+ onNavigateToProject: () => void;
+}
+
+export default function ProjectPanel({ project, onNavigateToProject }: ProjectPanelProps) {
+ return (
+
+
+
+ Project
+
+
+
+
+ {project ? (
+ <>
+
+
Name
+
{project.name}
+
+
+
Slug
+
{project.slug}
+
+ >
+ ) : (
+
Loading...
+ )}
+
+
+ );
+}
diff --git a/gadget-code/frontend/src/components/SessionPanel.tsx b/gadget-code/frontend/src/components/SessionPanel.tsx
new file mode 100644
index 0000000..9a78eb3
--- /dev/null
+++ b/gadget-code/frontend/src/components/SessionPanel.tsx
@@ -0,0 +1,267 @@
+import { ChatSessionMode, type AiProvider, type ChatSession } from '../lib/api';
+import { WorkspaceMode } from '../lib/types';
+import WorkspaceModeIndicator from './WorkspaceModeIndicator';
+
+interface SessionPanelProps {
+ session: ChatSession | null;
+ workspaceMode: WorkspaceMode;
+ sessionLocked: boolean;
+ providers: AiProvider[];
+ selectedProviderId: string;
+ selectedModelId: string;
+ sessionReasoningEffort: string;
+ isEditingProvider: boolean;
+ editProviderId: string;
+ editModelId: string;
+ isUpdatingProvider: boolean;
+ isEditingName: boolean;
+ editName: string;
+ isUpdatingName: boolean;
+ isUpdatingMode: boolean;
+ isUpdatingReasoning: boolean;
+ onWorkspaceModeChange: (mode: WorkspaceMode) => void;
+ onModeChange: (e: React.ChangeEvent) => void;
+ onReasoningChange: (e: React.ChangeEvent) => void;
+ onStartEditProvider: () => void;
+ onSaveProvider: () => void;
+ onCancelEditProvider: () => void;
+ onEditProviderIdChange: (id: string) => void;
+ onEditModelIdChange: (id: string) => void;
+ onStartEditName: () => void;
+ onSaveName: () => void;
+ onCancelEditName: () => void;
+ onEditNameChange: (name: string) => void;
+ getProviderName: (id: string) => string;
+ getModelName: (providerId: string, modelId: string) => string;
+ getSortedModels: (providerId: string) => AiProvider['models'];
+ truncateModelName: (name: string, maxLength?: number) => string;
+ getSelectedModelCapabilities: () => { hasThinking: boolean } | null;
+}
+
+export default function SessionPanel({
+ session,
+ workspaceMode,
+ sessionLocked,
+ providers,
+ selectedProviderId,
+ selectedModelId,
+ sessionReasoningEffort,
+ isEditingProvider,
+ editProviderId,
+ editModelId,
+ isUpdatingProvider,
+ isEditingName,
+ editName,
+ isUpdatingName,
+ isUpdatingMode,
+ isUpdatingReasoning,
+ onWorkspaceModeChange,
+ onModeChange,
+ onReasoningChange,
+ onStartEditProvider,
+ onSaveProvider,
+ onCancelEditProvider,
+ onEditProviderIdChange,
+ onEditModelIdChange,
+ onStartEditName,
+ onSaveName,
+ onCancelEditName,
+ onEditNameChange,
+ getProviderName,
+ getModelName,
+ getSortedModels,
+ truncateModelName,
+ getSelectedModelCapabilities,
+}: SessionPanelProps) {
+ return (
+
+
+
+ Session
+
+
+
+
+
+
Name
+ {isEditingName ? (
+
+
onEditNameChange(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') { e.preventDefault(); onSaveName(); }
+ if (e.key === 'Escape') { e.preventDefault(); onCancelEditName(); }
+ }}
+ className="flex-1 px-2 py-1.5 bg-bg-tertiary border border-border-default rounded text-text-primary text-sm focus:border-brand focus:outline-none"
+ autoFocus
+ />
+
+
+
+ ) : (
+
+
{session?.name}
+
+
+ )}
+
+ {isEditingProvider ? (
+
+
+
Provider
+
+
+
+
Model
+
+
+
+
+ ) : (
+
+
+
Provider
+
{getProviderName(selectedProviderId)}
+
+
+
Model
+
+ {getModelName(selectedProviderId, selectedModelId)}
+
+
+
+
+ )}
+
+
Mode
+
+
+
+
Reasoning
+
+
+
+
+ );
+}
diff --git a/gadget-code/frontend/src/pages/ChatSessionView.tsx b/gadget-code/frontend/src/pages/ChatSessionView.tsx
index 252c84c..bfe6b7c 100644
--- a/gadget-code/frontend/src/pages/ChatSessionView.tsx
+++ b/gadget-code/frontend/src/pages/ChatSessionView.tsx
@@ -3,7 +3,8 @@ import { useParams, useNavigate } from 'react-router-dom';
import { socketClient } from '../lib/socket';
import { chatSessionApi, projectApi, providerApi, type ChatSession, type ChatTurn, type ChatTurnBlock, ChatTurnStats, ChatSessionMode, type AiProvider, type Project } from '../lib/api';
import { WorkspaceMode } from '../lib/types';
-import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
+import SessionPanel from '../components/SessionPanel';
+import ProjectPanel from '../components/ProjectPanel';
import FilesPanel from '../components/FilesPanel';
import LogPanel from '../components/LogPanel';
import ChatTurnComponent from '../components/ChatTurn';
@@ -1164,232 +1165,47 @@ export default function ChatSessionView() {
{/* Sidebar */}
- {/* SESSION Panel */}
-
-
-
- Session
-
-
-
-
-
-
Name
- {isEditingName ? (
-
-
setEditName(e.target.value)}
- onKeyDown={(e) => {
- if (e.key === 'Enter') { e.preventDefault(); handleSaveName(); }
- if (e.key === 'Escape') { e.preventDefault(); handleCancelEditName(); }
- }}
- className="flex-1 px-2 py-1.5 bg-bg-tertiary border border-border-default rounded text-text-primary text-sm focus:border-brand focus:outline-none"
- autoFocus
- />
-
-
-
- ) : (
-
-
{session?.name}
-
-
- )}
-
- {isEditingProvider ? (
-
-
-
Provider
-
-
-
-
Model
-
-
-
-
- ) : (
-
-
-
Provider
-
{getProviderName(selectedProviderId)}
-
-
-
Model
-
- {getModelName(selectedProviderId, selectedModelId)}
-
-
-
-
- )}
-
-
Mode
-
-
-
-
Reasoning
-
-
-
-
+
- {/* PROJECT Panel */}
-
-
-
- Project
-
-
-
-
- {project ? (
- <>
-
-
Name
-
{project.name}
-
-
-
Slug
-
{project.slug}
-
- >
- ) : (
-
Loading...
- )}
-
-
+
navigate(`/projects/${project?.slug}`)}
+ />
- {/* FILES Panel */}
{
diff --git a/scripts/forever-sleep.js b/scripts/forever-sleep.js
new file mode 100644
index 0000000..4e4d694
--- /dev/null
+++ b/scripts/forever-sleep.js
@@ -0,0 +1,27 @@
+#!/usr/bin/env node
+
+/**
+ * forever-sleep.js
+ * A script that sleeps, wakes up every 5 seconds, logs to stdout, then goes back to sleep.
+ */
+
+const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
+
+async function main() {
+ console.log('forever-sleep: Starting...');
+
+ while (true) {
+ const timestamp = new Date().toISOString();
+ console.log(`forever-sleep: ${timestamp} - Waking up (sleeping for 5 seconds)...`);
+
+ await sleep(5000);
+
+ const timestampAfter = new Date().toISOString();
+ console.log(`forever-sleep: ${timestampAfter} - Back to sleep...`);
+ }
+}
+
+main().catch(err => {
+ console.error('forever-sleep: Error:', err);
+ process.exit(1);
+});