Compare commits
No commits in common. "a23224279ef9355a5d86f39b8a724f61887c390d" and "c90e8ce0e89c902acace67ccad6404922d434a7f" have entirely different histories.
a23224279e
...
c90e8ce0e8
@ -30,7 +30,6 @@
|
||||
"@replit/codemirror-lang-csharp": "^6.2.0",
|
||||
"@uiw/codemirror-theme-tomorrow-night-blue": "^4.25.9",
|
||||
"@uiw/react-codemirror": "^4.25.9",
|
||||
"lucide-react": "^1.16.0",
|
||||
"marked": "^16.4.2",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
|
||||
@ -5,7 +5,6 @@ interface ExpandedPromptEditorProps {
|
||||
onPromptChange: (value: string) => void;
|
||||
onSubmit: (e: React.FormEvent) => void;
|
||||
onClose: () => void;
|
||||
onClearPrompt: () => void;
|
||||
disabled: boolean;
|
||||
placeholder: string;
|
||||
isProcessing: boolean;
|
||||
@ -18,7 +17,6 @@ export default function ExpandedPromptEditor({
|
||||
onPromptChange,
|
||||
onSubmit,
|
||||
onClose,
|
||||
onClearPrompt,
|
||||
disabled,
|
||||
placeholder,
|
||||
isProcessing,
|
||||
@ -108,15 +106,6 @@ export default function ExpandedPromptEditor({
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-bg-tertiary border-b border-border-subtle shrink-0">
|
||||
<span className="text-sm font-medium text-text-secondary">Prompt Editor</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClearPrompt}
|
||||
disabled={!promptInput}
|
||||
className="px-4 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-secondary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed text-sm"
|
||||
title="Clear prompt text"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
{isProcessing ? (
|
||||
<button
|
||||
type="button"
|
||||
@ -164,7 +153,7 @@ export default function ExpandedPromptEditor({
|
||||
|
||||
{/* Footer — Keyboard Shortcuts */}
|
||||
<div className="px-4 py-2 text-xs text-text-muted border-t border-border-subtle shrink-0 flex items-center justify-between">
|
||||
<span>Esc to close · Ctrl+Enter to send · Clear to reset</span>
|
||||
<span>Esc to close · Ctrl+Enter to send</span>
|
||||
<span className="font-mono">{promptInput.length} chars</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
||||
import { useParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { BrushCleaning, Maximize2 } from 'lucide-react';
|
||||
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';
|
||||
@ -67,13 +66,7 @@ export default function ChatSessionView() {
|
||||
const [project, setProject] = useState<Project | null>(null);
|
||||
const [session, setSession] = useState<ChatSession | null>(null);
|
||||
const [turns, setTurns] = useState<ChatTurn[]>([]);
|
||||
const [promptInput, setPromptInput] = useState(() => {
|
||||
if (sessionId) {
|
||||
const saved = localStorage.getItem(`dtp_draft_prompt_${sessionId}`);
|
||||
if (saved) return saved;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const [promptInput, setPromptInput] = useState('');
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [isAborting, setIsAborting] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@ -132,17 +125,6 @@ export default function ChatSessionView() {
|
||||
scrollToBottom();
|
||||
}, [turns]);
|
||||
|
||||
// Persist draft prompt to localStorage so it survives page refreshes and navigation
|
||||
useEffect(() => {
|
||||
if (!sessionId) return;
|
||||
const key = `dtp_draft_prompt_${sessionId}`;
|
||||
if (promptInput) {
|
||||
localStorage.setItem(key, promptInput);
|
||||
} else {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}, [promptInput, sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (toastTimerRef.current) {
|
||||
@ -1301,7 +1283,6 @@ export default function ChatSessionView() {
|
||||
setIsExpandedEditor(false);
|
||||
}}
|
||||
onClose={() => setIsExpandedEditor(false)}
|
||||
onClearPrompt={() => setPromptInput('')}
|
||||
disabled={promptDisabled}
|
||||
placeholder={promptPlaceholder}
|
||||
isProcessing={isProcessing}
|
||||
@ -1334,26 +1315,17 @@ export default function ChatSessionView() {
|
||||
{/* Prompt Input */}
|
||||
<div className="border-t border-border-subtle p-4 bg-bg-secondary shrink-0">
|
||||
<form onSubmit={handleSubmitPrompt} className="flex gap-2">
|
||||
<div className="flex flex-col self-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsExpandedEditor(true)}
|
||||
disabled={promptDisabled || !!editorFilePath}
|
||||
className="p-2 text-text-muted hover:text-text-primary hover:bg-bg-tertiary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Open expanded prompt editor"
|
||||
>
|
||||
<Maximize2 size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPromptInput('')}
|
||||
disabled={!promptInput}
|
||||
className="p-2 text-text-muted hover:text-text-primary hover:bg-bg-tertiary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Clear prompt"
|
||||
>
|
||||
<BrushCleaning size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsExpandedEditor(true)}
|
||||
disabled={promptDisabled || !!editorFilePath}
|
||||
className="p-2 text-text-muted hover:text-text-primary hover:bg-bg-tertiary rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed self-end"
|
||||
title="Open expanded prompt editor"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
||||
</svg>
|
||||
</button>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
value={promptInput}
|
||||
|
||||
@ -285,15 +285,9 @@ importers:
|
||||
'@uiw/react-codemirror':
|
||||
specifier: ^4.25.9
|
||||
version: 4.25.9(@babel/runtime@7.29.2)(@codemirror/autocomplete@6.20.2)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.6)(@codemirror/search@6.7.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.42.1)(codemirror@6.0.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
lucide-react:
|
||||
specifier: ^1.16.0
|
||||
version: 1.16.0(react@19.2.5)
|
||||
marked:
|
||||
specifier: ^16.4.2
|
||||
version: 16.4.2
|
||||
react:
|
||||
specifier: ^19.2.5
|
||||
version: 19.2.5
|
||||
slug:
|
||||
specifier: ^11.0.1
|
||||
version: 11.0.1
|
||||
@ -2338,11 +2332,6 @@ packages:
|
||||
resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
lucide-react@1.16.0:
|
||||
resolution: {integrity: sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
luxon@3.6.1:
|
||||
resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -5498,10 +5487,6 @@ snapshots:
|
||||
|
||||
lru-cache@11.3.5: {}
|
||||
|
||||
lucide-react@1.16.0(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
|
||||
luxon@3.6.1: {}
|
||||
|
||||
lz-string@1.5.0: {}
|
||||
|
||||
@ -10,8 +10,6 @@ import path from "node:path";
|
||||
import { io, ManagerOptions, SocketOptions, Socket } from "socket.io-client";
|
||||
import { input as inqInput, password as inqPassword } from "@inquirer/prompts";
|
||||
|
||||
import { detectWorkspace } from "@gadget/api";
|
||||
|
||||
import AgentService, { IAgentWorkOrder } from "./services/agent.ts";
|
||||
import AiService from "./services/ai.ts";
|
||||
import PlatformService from "./services/platform.ts";
|
||||
@ -64,7 +62,6 @@ class GadgetDrone extends GadgetProcess {
|
||||
private socket: ClientSocket | undefined;
|
||||
private isShuttingDown: boolean = false;
|
||||
private heartbeatTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private startupDir: string | undefined = undefined;
|
||||
|
||||
get name(): string {
|
||||
return "GadgetDrone";
|
||||
@ -86,38 +83,11 @@ class GadgetDrone extends GadgetProcess {
|
||||
this.hookProcessSignals();
|
||||
await this.startServices();
|
||||
|
||||
/*
|
||||
* Detect workspace directory by walking up the directory tree.
|
||||
* This allows starting the drone from anywhere within a workspace's hierarchy.
|
||||
*/
|
||||
|
||||
const detection = await detectWorkspace();
|
||||
this.startupDir = detection.startupDir;
|
||||
|
||||
if (!detection.found) {
|
||||
this.log.error("no workspace found", {
|
||||
startupDir: detection.startupDir,
|
||||
message: "Start the drone from within a managed workspace or its subdirectories. No .gadget/workspace.json found in any parent directory.",
|
||||
});
|
||||
throw new Error(
|
||||
"No workspace found. Cannot start drone in an unmanaged directory.",
|
||||
);
|
||||
}
|
||||
|
||||
// Change to the workspace directory for all operations
|
||||
const workspaceDir = detection.workspaceDir!;
|
||||
process.chdir(workspaceDir);
|
||||
|
||||
this.log.info("workspace detected", {
|
||||
workspaceDir,
|
||||
startupDir: detection.startupDir,
|
||||
navigated: workspaceDir !== detection.startupDir,
|
||||
});
|
||||
|
||||
/*
|
||||
* Initialize workspace directory structure and load/create workspace identity.
|
||||
*/
|
||||
|
||||
const workspaceDir = process.cwd();
|
||||
await WorkspaceService.initialize(workspaceDir);
|
||||
this.log.info("workspace initialized", {
|
||||
workspaceId: WorkspaceService.workspaceId,
|
||||
@ -200,16 +170,6 @@ class GadgetDrone extends GadgetProcess {
|
||||
|
||||
await this.stopServices();
|
||||
|
||||
/*
|
||||
* Restore the original startup directory before exiting.
|
||||
* This ensures the shell returns to the directory where the
|
||||
* drone was originally started.
|
||||
*/
|
||||
if (this.startupDir) {
|
||||
process.chdir(this.startupDir);
|
||||
this.log.info("restored startup directory", { startupDir: this.startupDir });
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import PlatformService from "./services/platform.ts";
|
||||
import SchedulerService from "./services/scheduler.ts";
|
||||
import TaskLockService from "./services/lock.ts";
|
||||
|
||||
import { GadgetLog, detectWorkspace, type IDroneRegistration, type IProject } from "@gadget/api";
|
||||
import { GadgetLog, type IDroneRegistration, type IProject } from "@gadget/api";
|
||||
import { GadgetProcess } from "./lib/process.ts";
|
||||
|
||||
const log = new GadgetLog({ name: "GadgetTasks", slug: "gadget-tasks" });
|
||||
@ -20,7 +20,6 @@ interface UserCredentials {
|
||||
|
||||
class GadgetTasks extends GadgetProcess {
|
||||
private selectedDrone: IDroneRegistration | null = null;
|
||||
private startupDir: string | undefined = undefined;
|
||||
|
||||
get name(): string {
|
||||
return "GadgetTasks";
|
||||
@ -37,34 +36,6 @@ class GadgetTasks extends GadgetProcess {
|
||||
async start(): Promise<void> {
|
||||
this.hookProcessSignals();
|
||||
|
||||
/*
|
||||
* Detect workspace directory by walking up the directory tree.
|
||||
* This allows starting from anywhere within a workspace's hierarchy.
|
||||
*/
|
||||
|
||||
const detection = await detectWorkspace();
|
||||
this.startupDir = detection.startupDir;
|
||||
|
||||
if (!detection.found) {
|
||||
this.log.error("no workspace found", {
|
||||
startupDir: detection.startupDir,
|
||||
message: "Start gadget-tasks from within a managed workspace or its subdirectories. No .gadget/workspace.json found in any parent directory.",
|
||||
});
|
||||
throw new Error(
|
||||
"No workspace found. Cannot start gadget-tasks in an unmanaged directory.",
|
||||
);
|
||||
}
|
||||
|
||||
// Change to the workspace directory for all operations
|
||||
const workspaceDir = detection.workspaceDir!;
|
||||
process.chdir(workspaceDir);
|
||||
|
||||
this.log.info("workspace detected", {
|
||||
workspaceDir,
|
||||
startupDir: detection.startupDir,
|
||||
navigated: workspaceDir !== detection.startupDir,
|
||||
});
|
||||
|
||||
// 1. Acquire singleton lock via Redis
|
||||
const lockAcquired = await TaskLockService.acquire();
|
||||
if (!lockAcquired) {
|
||||
@ -107,14 +78,6 @@ class GadgetTasks extends GadgetProcess {
|
||||
await PlatformService.stop();
|
||||
await TaskLockService.release();
|
||||
|
||||
/*
|
||||
* Restore the original startup directory before exiting.
|
||||
*/
|
||||
if (this.startupDir) {
|
||||
process.chdir(this.startupDir);
|
||||
this.log.info("restored startup directory", { startupDir: this.startupDir });
|
||||
}
|
||||
|
||||
this.log.info("shutdown complete");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ export * from "./lib/log-transport-console.ts";
|
||||
export * from "./lib/log-transport-file.ts";
|
||||
export * from "./lib/log-transport-socket.ts";
|
||||
export * from "./lib/log-file.ts";
|
||||
export * from "./lib/workspace-detector.ts";
|
||||
|
||||
/*
|
||||
* Data Model Interfaces
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
// src/lib/workspace-detector.ts
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
export interface WorkspaceDetectionResult {
|
||||
found: boolean;
|
||||
workspaceDir?: string;
|
||||
startupDir: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks up the directory tree from startDir looking for .gadget/workspace.json.
|
||||
* Returns the workspace directory if found, or { found: false } if not.
|
||||
*
|
||||
* @param startDir - The directory to start searching from (defaults to process.cwd())
|
||||
* @returns Promise resolving to workspace detection result
|
||||
*/
|
||||
export async function detectWorkspace(
|
||||
startDir: string = process.cwd(),
|
||||
): Promise<WorkspaceDetectionResult> {
|
||||
const startupDir = path.resolve(startDir);
|
||||
let currentDir = startupDir;
|
||||
|
||||
// Walk up the directory tree until we hit root
|
||||
while (true) {
|
||||
const workspaceFile = path.join(currentDir, ".gadget", "workspace.json");
|
||||
|
||||
try {
|
||||
await fs.access(workspaceFile);
|
||||
// Found the workspace
|
||||
return {
|
||||
found: true,
|
||||
workspaceDir: currentDir,
|
||||
startupDir,
|
||||
};
|
||||
} catch {
|
||||
// workspace.json not found in this directory, go up one level
|
||||
}
|
||||
|
||||
const parentDir = path.dirname(currentDir);
|
||||
|
||||
// Check if we've reached the root (parent equals current)
|
||||
if (parentDir === currentDir) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
// No workspace.json found in any parent directory
|
||||
return {
|
||||
found: false,
|
||||
startupDir,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user