Add web terminal, git credentials, OpenSCAD, review loops, automations
- Web terminal (PTY over WebSocket, xterm.js, sandboxed, reattach with replay) - Per-user git credentials and commit identity (credential store in the user's home) - OpenSCAD tool (STL/3MF + preview renders, bounding box) and cad skill; 3D model card - Craft phase inferred from tool activity when the agent does not report it - Configurable review loop per mode (rounds, reviewer model, criteria) - Delete config and Markdown agents from the agent editor - Automations: cron-scheduled agents from Markdown job files, schedule tool, page - change_directory tool (only on the user's request) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0d0e707f2
commit
df1924b0a1
41 files changed
+2809
-34
No files matched your search
@@ -85,6 +85,12 @@ const ICONS: Record<string, ReactNode> = {
|
||||
<path d="m20 20-4.5-4.5M9 9l4 4M13 9l-4 4" />
|
||||
</>,
|
||||
),
|
||||
cad: svg(
|
||||
<>
|
||||
<path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z" />
|
||||
<path d="m4 7.5 8 4.5 8-4.5M12 12v9" />
|
||||
</>,
|
||||
),
|
||||
folder: svg(<path d="M3 6a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z" />),
|
||||
git: svg(
|
||||
<>
|
||||
@@ -190,12 +196,12 @@ export function NewSession({ onMenu, onCreated }: { onMenu: () => void; onCreate
|
||||
}, [target]);
|
||||
// Working on existing code usually happens in an existing folder.
|
||||
useEffect(() => {
|
||||
setFolderMode((m) => (m === "git" ? m : task === "craft" ? "new" : "existing"));
|
||||
setFolderMode((m) => (m === "git" ? m : task === "craft" || task === "cad" ? "new" : "existing"));
|
||||
}, [task]);
|
||||
|
||||
const repoName = repoUrl.trim().replace(/\/+$/, "").replace(/\.git$/, "").split(/[/:]/).pop() ?? "";
|
||||
const autoName = useMemo(
|
||||
() => (folderMode === "git" && repoName ? slug(repoName) : "") || slug(target === "website" && url ? url : text) || (task === "craft" ? `${target}-project` : "project"),
|
||||
() => (folderMode === "git" && repoName ? slug(repoName) : "") || slug(target === "website" && url ? url : text) || (task === "craft" ? `${target}-project` : task === "cad" ? "3d-model" : "project"),
|
||||
[text, url, target, task, folderMode, repoName],
|
||||
);
|
||||
useEffect(() => {
|
||||
@@ -203,7 +209,9 @@ export function NewSession({ onMenu, onCreated }: { onMenu: () => void; onCreate
|
||||
}, [autoName, nameTouched]);
|
||||
|
||||
const ideas: Key[] =
|
||||
task !== "craft"
|
||||
task === "cad"
|
||||
? ["wizard.idea.cadBox", "wizard.idea.cadBracket"]
|
||||
: task !== "craft"
|
||||
? task === "plan"
|
||||
? ["wizard.idea.planFeature", "wizard.idea.planRefactor"]
|
||||
: ["wizard.idea.buildTests", "wizard.idea.buildBug"]
|
||||
@@ -238,6 +246,7 @@ export function NewSession({ onMenu, onCreated }: { onMenu: () => void; onCreate
|
||||
const prompts = (uploaded: string[]): string[] => {
|
||||
const desc = text.trim();
|
||||
const fileList = uploaded.length ? `\n\nFiles: ${uploaded.join(", ")}` : "";
|
||||
if (task === "cad") return [`/cad ${desc}${images.length ? " (sketch attached)" : ""}${fileList}`];
|
||||
if (task !== "craft") return [desc + fileList];
|
||||
switch (target) {
|
||||
case "website":
|
||||
@@ -311,7 +320,7 @@ export function NewSession({ onMenu, onCreated }: { onMenu: () => void; onCreate
|
||||
const s = await post<{ id: string }>("/sessions", {
|
||||
cwd,
|
||||
model: model || undefined,
|
||||
agent: task,
|
||||
agent: task === "cad" ? "build" : task,
|
||||
clean_room: isCraft ? !deep : undefined,
|
||||
});
|
||||
const msgs = prompts(uploaded).filter((m) => m.trim());
|
||||
@@ -343,6 +352,7 @@ export function NewSession({ onMenu, onCreated }: { onMenu: () => void; onCreate
|
||||
<Step n={n++} title={t("wizard.whatToDo")}>
|
||||
<div className="wiz-cards">
|
||||
<Card icon="craft" title={t("wizard.task.craft")} desc={t("wizard.task.craftDesc")} active={task === "craft"} onClick={() => setTask("craft")} />
|
||||
<Card icon="cad" title={t("wizard.task.cad")} desc={t("wizard.task.cadDesc")} active={task === "cad"} onClick={() => setTask("cad")} />
|
||||
<Card icon="build" title={t("wizard.task.build")} desc={t("wizard.task.buildDesc")} active={task === "build"} onClick={() => setTask("build")} />
|
||||
<Card icon="plan" title={t("wizard.task.plan")} desc={t("wizard.task.planDesc")} active={task === "plan"} onClick={() => setTask("plan")} />
|
||||
{custom.map((a) => (
|
||||
@@ -436,7 +446,7 @@ export function NewSession({ onMenu, onCreated }: { onMenu: () => void; onCreate
|
||||
>
|
||||
<textarea
|
||||
value={text}
|
||||
placeholder={t(task === "craft" ? (`wizard.placeholder.${target}` as Key) : "wizard.placeholder.task")}
|
||||
placeholder={t(task === "craft" ? (`wizard.placeholder.${target}` as Key) : task === "cad" ? "wizard.placeholder.cad" : "wizard.placeholder.task")}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onPaste={(e) => {
|
||||
if (e.clipboardData.files.length) {
|
||||
|
||||
Reference in new issue
Block a user