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:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 22:00:38 +01:00
1 parent a0d0e707f2
commit df1924b0a1
41 files changed
+2809 -34

No files matched your search

+9
View File
@@ -11,11 +11,13 @@ import { NewSession } from "./components/NewSession";
import { Settings } from "./components/Settings";
import { Stats } from "./components/Stats";
import { Images } from "./components/Images";
import { Jobs } from "./components/Jobs";
import { Spinner, useTitleActivity } from "./components/Activity";
import { DialogHost } from "./dialogs";
// The file manager (editor, git) is loaded on demand to keep the chat small.
const Files = lazy(() => import("./components/Files").then((m) => ({ default: m.Files })));
const TerminalPage = lazy(() => import("./components/Terminal").then((m) => ({ default: m.TerminalPage })));
export function App() {
const t = useT();
@@ -73,6 +75,13 @@ export function App() {
else if (section === "settings") content = <Settings tab={arg} onMenu={() => setMenuOpen(true)} />;
else if (section === "stats") content = <Stats onMenu={() => setMenuOpen(true)} />;
else if (section === "images") content = <Images onMenu={() => setMenuOpen(true)} />;
else if (section === "jobs") content = <Jobs onMenu={() => setMenuOpen(true)} />;
else if (section === "terminal")
content = (
<Suspense fallback={<div className="empty"><Spinner /></div>}>
<TerminalPage id={arg} onMenu={() => setMenuOpen(true)} />
</Suspense>
);
else if (section === "files")
content = (
<Suspense fallback={<div className="empty"><Spinner /></div>}>