@aion0/forge 0.5.26 → 0.5.28
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.
- package/.forge/worktrees/pipeline-4dd8dc2d/CLAUDE.md +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/README.md +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/RELEASE_NOTES.md +36 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/agents/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/[...nextauth]/route.ts +3 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/auth/verify/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/[id]/stream/route.ts +63 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude/route.ts +28 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/entries/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/[projectName]/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-sessions/sync/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/claude-templates/route.ts +145 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/code/route.ts +299 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/[id]/route.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/delivery/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/detect-cli/route.ts +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/route.ts +176 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/docs/sessions/route.ts +54 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/favorites/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/flows/run/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/git/route.ts +149 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/help/route.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/issue-scanner/route.ts +116 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/logs/route.ts +100 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/mobile-chat/route.ts +115 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/monitor/route.ts +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notifications/route.ts +42 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/notify/test/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/online/route.ts +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/[id]/route.ts +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/pipelines/route.ts +90 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/plugins/route.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/[...path]/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/preview/route.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-pipelines/route.ts +91 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/project-sessions/route.ts +61 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/projects/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/chat/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/messages/route.ts +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/[id]/route.ts +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/sessions/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/settings/route.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/local/route.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/skills/route.ts +182 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/smith-templates/route.ts +81 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/status/route.ts +12 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tabs/route.ts +25 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/route.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/[id]/stream/route.ts +77 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/link/route.ts +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/route.ts +44 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tasks/session/route.ts +14 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/telegram/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/templates/route.ts +6 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-bell/route.ts +39 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-cwd/route.ts +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/terminal-state/route.ts +15 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/tunnel/route.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/upgrade/route.ts +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/usage/route.ts +20 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/version/route.ts +78 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/watchers/route.ts +33 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/agents/route.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/memory/route.ts +23 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/smith/route.ts +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/[id]/stream/route.ts +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/api/workspace/route.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/global-error.tsx +21 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/globals.css +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.ico +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/icon.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/layout.tsx +17 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/LoginForm.tsx +96 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/login/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/mobile/page.tsx +10 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/app/page.tsx +22 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/bin/forge-server.mjs +484 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/check-forge-status.sh +71 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/cli/mw.ts +579 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/BrowserPanel.tsx +175 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ChatPanel.tsx +191 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ClaudeTerminal.tsx +267 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/CodeViewer.tsx +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationEditor.tsx +411 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationGraphView.tsx +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ConversationTerminalView.tsx +303 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/Dashboard.tsx +807 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DashboardWrapper.tsx +9 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryFlowEditor.tsx +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryList.tsx +230 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DeliveryWorkspace.tsx +589 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocTerminal.tsx +187 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/DocsViewer.tsx +574 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpDialog.tsx +169 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/HelpTerminal.tsx +141 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/InlinePipelineView.tsx +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/LogViewer.tsx +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MarkdownContent.tsx +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MobileView.tsx +385 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/MonitorPanel.tsx +122 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewSessionModal.tsx +93 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/NewTaskModal.tsx +492 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineEditor.tsx +570 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PipelineView.tsx +1018 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/PluginsPanel.tsx +472 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectDetail.tsx +1618 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectList.tsx +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/ProjectManager.tsx +401 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionList.tsx +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SessionView.tsx +726 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SettingsModal.tsx +1647 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/SkillsPanel.tsx +969 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/StatusBar.tsx +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TabBar.tsx +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskBoard.tsx +113 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TaskDetail.tsx +372 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TerminalLauncher.tsx +398 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/TunnelToggle.tsx +206 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/UsagePanel.tsx +207 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WebTerminal.tsx +1743 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceTree.tsx +221 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/components/WorkspaceView.tsx +4048 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/dev-test.sh +5 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Memory_Layer_Design.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/Forge_Strategy_Research_2026.docx +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/LOCAL-DEPLOY.md +144 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/docs/roadmap-multi-agent-workflow.md +330 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.png +0 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/forge-logo.svg +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/hooks/useSidebarResize.ts +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/install.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/instrumentation.ts +35 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/claude-adapter.ts +104 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/generic-adapter.ts +64 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/index.ts +245 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/agents/types.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/artifacts.ts +106 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/auth.ts +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/docker.yaml +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/http.yaml +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/jenkins.yaml +92 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/llm-vision.yaml +85 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/playwright.yaml +111 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/shell-command.yaml +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/slack.yaml +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/builtin-plugins/webhook.yaml +56 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-process.ts +361 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-sessions.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/claude-templates.ts +227 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/cloudflared.ts +424 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/crypto.ts +67 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/delivery.ts +787 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/dirs.ts +99 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/flows.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-mcp-server.ts +732 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-inbox.md +38 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-send.md +47 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-status.md +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/00-overview.md +40 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +194 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/02-telegram.md +41 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/03-tunnel.md +31 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/04-tasks.md +52 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/05-pipelines.md +460 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/06-skills.md +43 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +73 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/08-rules.md +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/09-issue-autofix.md +55 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/10-troubleshooting.md +89 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/11-workspace.md +810 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/CLAUDE.md +62 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/init.ts +266 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/issue-scanner.ts +298 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/logger.ts +79 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notifications.ts +75 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/notify.ts +108 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/password.ts +97 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline-scheduler.ts +373 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/pipeline.ts +1565 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/executor.ts +347 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/registry.ts +228 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/plugins/types.ts +103 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/project-sessions.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/projects.ts +86 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-manager.ts +156 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-utils.ts +53 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/session-watcher.ts +345 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/settings.ts +195 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/skills.ts +458 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/task-manager.ts +951 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-bot.ts +1477 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/telegram-standalone.ts +83 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-server.ts +70 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/terminal-standalone.ts +438 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/usage-scanner.ts +249 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-bus.ts +416 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/agent-worker.ts +655 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/api-backend.ts +262 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/backends/cli-backend.ts +491 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/index.ts +84 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/manager.ts +136 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/orchestrator.ts +3415 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/persistence.ts +309 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/presets.ts +649 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/requests.ts +287 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/session-monitor.ts +240 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/skill-installer.ts +275 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/smith-memory.ts +498 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/types.ts +241 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace/watch-manager.ts +560 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/lib/workspace-standalone.ts +978 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/middleware.ts +51 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/next.config.ts +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/package.json +74 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-lock.yaml +3719 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/pnpm-workspace.yaml +1 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/postcss.config.mjs +7 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/publish.sh +133 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/README.md +66 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/results/.gitignore +2 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/run.ts +635 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/task.md +26 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/01-text-utils/validator.sh +46 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/setup.sh +19 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/task.md +48 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/02-pagination/validator.sh +69 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/setup.sh +82 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/task.md +30 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/bench/tasks/03-bug-fix/validator.sh +29 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/scripts/verify-usage.ts +178 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/config/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/db/database.ts +259 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/memory/strategy.ts +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/chat.ts +65 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/providers/registry.ts +60 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/core/session/manager.ts +190 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/src/types/index.ts +129 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/start.sh +32 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/templates/smith-lead.json +45 -0
- package/.forge/worktrees/pipeline-4dd8dc2d/tsconfig.json +42 -0
- package/RELEASE_NOTES.md +10 -29
- package/app/api/terminal-bell/route.ts +6 -2
- package/app/api/terminal-cwd/route.ts +7 -4
- package/components/CodeViewer.tsx +3 -31
- package/components/Dashboard.tsx +34 -20
- package/components/WebTerminal.tsx +36 -2
- package/lib/terminal-standalone.ts +19 -2
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js instrumentation — runs once when the server starts.
|
|
3
|
+
* Loads .env.local and prints login password.
|
|
4
|
+
*/
|
|
5
|
+
export async function register() {
|
|
6
|
+
// Only run on server, not Edge
|
|
7
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
8
|
+
// Load ~/.forge/.env.local if it exists (works for both pnpm dev and forge-server)
|
|
9
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
10
|
+
const { join } = await import('node:path');
|
|
11
|
+
const { getDataDir } = await import('./lib/dirs');
|
|
12
|
+
const dataDir = getDataDir();
|
|
13
|
+
const envFile = join(dataDir, '.env.local');
|
|
14
|
+
if (existsSync(envFile)) {
|
|
15
|
+
for (const line of readFileSync(envFile, 'utf-8').split('\n')) {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
18
|
+
const eq = trimmed.indexOf('=');
|
|
19
|
+
if (eq === -1) continue;
|
|
20
|
+
const key = trimmed.slice(0, eq).trim();
|
|
21
|
+
const val = trimmed.slice(eq + 1).trim();
|
|
22
|
+
if (!process.env[key]) process.env[key] = val;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Print password info
|
|
27
|
+
const { getAdminPassword } = await import('./lib/password');
|
|
28
|
+
const admin = getAdminPassword();
|
|
29
|
+
if (admin) {
|
|
30
|
+
console.log(`[init] Admin password: configured`);
|
|
31
|
+
} else {
|
|
32
|
+
console.log('[init] No admin password set — configure in Settings');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code adapter — handles Claude CLI specifics.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import { realpathSync } from 'node:fs';
|
|
7
|
+
import type { AgentAdapter, AgentConfig, AgentSpawnOptions, AgentSpawnResult } from './types';
|
|
8
|
+
|
|
9
|
+
const CAPABILITIES = {
|
|
10
|
+
supportsResume: true,
|
|
11
|
+
supportsStreamJson: true,
|
|
12
|
+
supportsModel: true,
|
|
13
|
+
supportsSkipPermissions: true,
|
|
14
|
+
hasSessionFiles: true,
|
|
15
|
+
requiresTTY: false,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Resolve claude binary path (symlink → real .js → node) */
|
|
19
|
+
function resolveClaudePath(claudePath: string): { cmd: string; prefix: string[] } {
|
|
20
|
+
try {
|
|
21
|
+
let resolved = claudePath;
|
|
22
|
+
try {
|
|
23
|
+
const which = execSync(`which ${claudePath}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
24
|
+
resolved = realpathSync(which);
|
|
25
|
+
} catch {
|
|
26
|
+
resolved = realpathSync(claudePath);
|
|
27
|
+
}
|
|
28
|
+
if (resolved.endsWith('.js') || resolved.endsWith('.mjs')) {
|
|
29
|
+
return { cmd: process.execPath, prefix: [resolved] };
|
|
30
|
+
}
|
|
31
|
+
return { cmd: resolved, prefix: [] };
|
|
32
|
+
} catch {
|
|
33
|
+
return { cmd: process.execPath, prefix: [claudePath] };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createClaudeAdapter(config: AgentConfig): AgentAdapter {
|
|
38
|
+
return {
|
|
39
|
+
id: 'claude',
|
|
40
|
+
config: { ...config, capabilities: CAPABILITIES },
|
|
41
|
+
|
|
42
|
+
buildTaskSpawn(opts: AgentSpawnOptions): AgentSpawnResult {
|
|
43
|
+
const resolved = resolveClaudePath(config.path);
|
|
44
|
+
const args = [...resolved.prefix, '-p', '--verbose'];
|
|
45
|
+
|
|
46
|
+
if (opts.outputFormat === 'stream-json' || opts.outputFormat === undefined) {
|
|
47
|
+
args.push('--output-format', 'stream-json');
|
|
48
|
+
} else if (opts.outputFormat === 'json') {
|
|
49
|
+
args.push('--output-format', 'json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (opts.skipPermissions !== false) {
|
|
53
|
+
const flag = config.skipPermissionsFlag || '--dangerously-skip-permissions';
|
|
54
|
+
if (flag) args.push(...flag.split(/\s+/));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (opts.model && opts.model !== 'default') {
|
|
58
|
+
args.push('--model', opts.model);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (opts.conversationId) {
|
|
62
|
+
args.push('--resume', opts.conversationId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (opts.extraFlags) {
|
|
66
|
+
args.push(...opts.extraFlags);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
args.push(opts.prompt);
|
|
70
|
+
|
|
71
|
+
return { cmd: resolved.cmd, args };
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
buildTerminalCommand(opts) {
|
|
75
|
+
const flag = config.skipPermissionsFlag || '--dangerously-skip-permissions';
|
|
76
|
+
const skipFlag = opts.skipPermissions && flag ? ` ${flag}` : '';
|
|
77
|
+
if (opts.sessionId) {
|
|
78
|
+
return `cd "${opts.projectPath}" && claude --resume ${opts.sessionId}${skipFlag}\n`;
|
|
79
|
+
}
|
|
80
|
+
const resumeFlag = opts.resume ? ' -c' : '';
|
|
81
|
+
return `cd "${opts.projectPath}" && claude${resumeFlag}${skipFlag}\n`;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Detect if claude is installed and return config */
|
|
87
|
+
export function detectClaude(customPath?: string): AgentConfig | null {
|
|
88
|
+
const paths = customPath ? [customPath] : ['claude'];
|
|
89
|
+
for (const p of paths) {
|
|
90
|
+
try {
|
|
91
|
+
execSync(`which ${p}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
92
|
+
return {
|
|
93
|
+
id: 'claude',
|
|
94
|
+
name: 'Claude Code',
|
|
95
|
+
path: p,
|
|
96
|
+
enabled: true,
|
|
97
|
+
type: 'claude-code',
|
|
98
|
+
capabilities: CAPABILITIES,
|
|
99
|
+
skipPermissionsFlag: '--dangerously-skip-permissions',
|
|
100
|
+
};
|
|
101
|
+
} catch {}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic agent adapter — works with any CLI agent that takes a prompt via args or stdin.
|
|
3
|
+
* Supports: codex, aider, or any custom agent binary.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import type { AgentAdapter, AgentConfig, AgentSpawnOptions, AgentSpawnResult } from './types';
|
|
8
|
+
|
|
9
|
+
export function createGenericAdapter(config: AgentConfig): AgentAdapter {
|
|
10
|
+
return {
|
|
11
|
+
id: config.id,
|
|
12
|
+
config,
|
|
13
|
+
|
|
14
|
+
buildTaskSpawn(opts: AgentSpawnOptions): AgentSpawnResult {
|
|
15
|
+
const args: string[] = [];
|
|
16
|
+
|
|
17
|
+
// Add configured flags (e.g., ['--message'] for aider)
|
|
18
|
+
if (config.flags) {
|
|
19
|
+
args.push(...config.flags);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Add skip permissions flag if configured
|
|
23
|
+
if (opts.skipPermissions !== false && config.skipPermissionsFlag) {
|
|
24
|
+
args.push(...config.skipPermissionsFlag.split(/\s+/));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Add prompt
|
|
28
|
+
args.push(opts.prompt);
|
|
29
|
+
|
|
30
|
+
if (opts.extraFlags) {
|
|
31
|
+
args.push(...opts.extraFlags);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { cmd: config.path, args };
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
buildTerminalCommand(opts) {
|
|
38
|
+
return `cd "${opts.projectPath}" && ${config.path}\n`;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Detect known agents */
|
|
44
|
+
export function detectAgent(id: string, name: string, binaryName: string, flags?: string[]): AgentConfig | null {
|
|
45
|
+
try {
|
|
46
|
+
execSync(`which ${binaryName}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
name,
|
|
50
|
+
path: binaryName,
|
|
51
|
+
enabled: true,
|
|
52
|
+
type: 'generic',
|
|
53
|
+
flags,
|
|
54
|
+
capabilities: {
|
|
55
|
+
supportsResume: false,
|
|
56
|
+
supportsStreamJson: false,
|
|
57
|
+
supportsModel: false,
|
|
58
|
+
supportsSkipPermissions: false,
|
|
59
|
+
hasSessionFiles: false,
|
|
60
|
+
requiresTTY: false,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
} catch { return null; }
|
|
64
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Registry — manages available agents and provides adapters.
|
|
3
|
+
* Agents coexist (not mutually exclusive). Each entry point can select any agent.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadSettings } from '../settings';
|
|
7
|
+
import type { AgentAdapter, AgentConfig, AgentId } from './types';
|
|
8
|
+
import { createClaudeAdapter, detectClaude } from './claude-adapter';
|
|
9
|
+
import { createGenericAdapter, detectAgent } from './generic-adapter';
|
|
10
|
+
|
|
11
|
+
export type { AgentAdapter, AgentConfig, AgentId } from './types';
|
|
12
|
+
|
|
13
|
+
// Module-level cache
|
|
14
|
+
const adapterCache = new Map<AgentId, AgentAdapter>();
|
|
15
|
+
|
|
16
|
+
/** Get all configured agents */
|
|
17
|
+
export function listAgents(): AgentConfig[] {
|
|
18
|
+
const settings = loadSettings();
|
|
19
|
+
const agents: AgentConfig[] = [];
|
|
20
|
+
|
|
21
|
+
// Claude (always check — primary agent)
|
|
22
|
+
const claudeConfig = settings.agents?.claude;
|
|
23
|
+
const claude = detectClaude(claudeConfig?.path || settings.claudePath);
|
|
24
|
+
if (claude) {
|
|
25
|
+
agents.push({ ...claude, enabled: claudeConfig?.enabled !== false, detected: true, skipPermissionsFlag: claudeConfig?.skipPermissionsFlag || '--dangerously-skip-permissions', cliType: 'claude-code' } as any);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Codex
|
|
29
|
+
const codexConfig = settings.agents?.codex;
|
|
30
|
+
const codex = detectAgent('codex', 'OpenAI Codex', codexConfig?.path || 'codex', ['exec']);
|
|
31
|
+
if (codex) {
|
|
32
|
+
codex.capabilities.requiresTTY = false; // exec subcommand is non-interactive
|
|
33
|
+
agents.push({ ...codex, enabled: codexConfig?.enabled !== false, detected: true, skipPermissionsFlag: codexConfig?.skipPermissionsFlag || '--full-auto', cliType: 'codex' } as any);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Aider
|
|
37
|
+
const aiderConfig = settings.agents?.aider;
|
|
38
|
+
const aider = detectAgent('aider', 'Aider', aiderConfig?.path || 'aider', ['--message']);
|
|
39
|
+
if (aider) {
|
|
40
|
+
agents.push({ ...aider, enabled: aiderConfig?.enabled !== false, detected: true, skipPermissionsFlag: aiderConfig?.skipPermissionsFlag || '--yes', cliType: 'aider' } as any);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Custom agents + profiles from settings
|
|
44
|
+
if (settings.agents) {
|
|
45
|
+
for (const [id, cfg] of Object.entries(settings.agents)) {
|
|
46
|
+
if (['claude', 'codex', 'aider'].includes(id)) continue;
|
|
47
|
+
|
|
48
|
+
// API profile — no CLI detection needed
|
|
49
|
+
if (cfg.type === 'api') {
|
|
50
|
+
agents.push({
|
|
51
|
+
id,
|
|
52
|
+
name: cfg.name || id,
|
|
53
|
+
path: '',
|
|
54
|
+
enabled: cfg.enabled !== false,
|
|
55
|
+
type: 'generic' as const,
|
|
56
|
+
capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: false },
|
|
57
|
+
isProfile: true,
|
|
58
|
+
backendType: 'api',
|
|
59
|
+
provider: cfg.provider,
|
|
60
|
+
model: cfg.model,
|
|
61
|
+
apiKey: cfg.apiKey,
|
|
62
|
+
} as any);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// CLI profile (has base) — inherit from base agent
|
|
67
|
+
if (cfg.base) {
|
|
68
|
+
const baseAgent = agents.find(a => a.id === cfg.base);
|
|
69
|
+
agents.push({
|
|
70
|
+
...(baseAgent || { type: 'generic' as const, capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: false } }),
|
|
71
|
+
id,
|
|
72
|
+
name: cfg.name || id,
|
|
73
|
+
path: baseAgent?.path || '',
|
|
74
|
+
enabled: cfg.enabled !== false,
|
|
75
|
+
base: cfg.base,
|
|
76
|
+
isProfile: true,
|
|
77
|
+
backendType: 'cli',
|
|
78
|
+
model: cfg.model || cfg.models?.task,
|
|
79
|
+
skipPermissionsFlag: cfg.skipPermissionsFlag || baseAgent?.skipPermissionsFlag,
|
|
80
|
+
env: cfg.env,
|
|
81
|
+
cliType: cfg.cliType || (baseAgent as any)?.cliType || 'generic',
|
|
82
|
+
} as any);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Custom agent (not a profile) — detect binary
|
|
87
|
+
if (!cfg.path) continue;
|
|
88
|
+
const flags = cfg.taskFlags ? cfg.taskFlags.split(/\s+/).filter(Boolean) : cfg.flags;
|
|
89
|
+
const detected = detectAgent(id, cfg.name || id, cfg.path, flags);
|
|
90
|
+
agents.push({
|
|
91
|
+
...(detected || {
|
|
92
|
+
id, name: cfg.name || id, path: cfg.path, type: 'generic' as const, flags,
|
|
93
|
+
capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: !!cfg.requiresTTY },
|
|
94
|
+
}),
|
|
95
|
+
flags,
|
|
96
|
+
enabled: cfg.enabled !== false,
|
|
97
|
+
detected: !!detected,
|
|
98
|
+
cliType: cfg.cliType,
|
|
99
|
+
} as any);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return agents;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Get the default agent ID */
|
|
107
|
+
export function getDefaultAgentId(): AgentId {
|
|
108
|
+
const settings = loadSettings();
|
|
109
|
+
return settings.defaultAgent || 'claude';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Get an agent adapter by ID (falls back to default). For profiles, returns base agent's adapter. */
|
|
113
|
+
export function getAgent(id?: AgentId): AgentAdapter {
|
|
114
|
+
const agentId = id || getDefaultAgentId();
|
|
115
|
+
|
|
116
|
+
// Return cached adapter
|
|
117
|
+
if (adapterCache.has(agentId)) return adapterCache.get(agentId)!;
|
|
118
|
+
|
|
119
|
+
const agents = listAgents();
|
|
120
|
+
const config = agents.find(a => a.id === agentId && a.enabled);
|
|
121
|
+
|
|
122
|
+
// Profile with base → get base agent's adapter
|
|
123
|
+
if (config?.base) {
|
|
124
|
+
const baseAdapter = getAgent(config.base);
|
|
125
|
+
// Wrap adapter with profile's model override
|
|
126
|
+
const profileAdapter: AgentAdapter = {
|
|
127
|
+
...baseAdapter,
|
|
128
|
+
id: agentId,
|
|
129
|
+
config: { ...baseAdapter.config, ...config, id: agentId },
|
|
130
|
+
};
|
|
131
|
+
adapterCache.set(agentId, profileAdapter);
|
|
132
|
+
return profileAdapter;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!config) {
|
|
136
|
+
// If specifically requested agent not found, only fallback for 'claude' (default)
|
|
137
|
+
if (agentId === 'claude' || agentId === getDefaultAgentId()) {
|
|
138
|
+
const fallback = detectClaude() || {
|
|
139
|
+
id: 'claude', name: 'Claude Code', path: 'claude', enabled: true,
|
|
140
|
+
type: 'claude-code' as const,
|
|
141
|
+
capabilities: { supportsResume: true, supportsStreamJson: true, supportsModel: true, supportsSkipPermissions: true, hasSessionFiles: true, requiresTTY: false },
|
|
142
|
+
};
|
|
143
|
+
const adapter = createClaudeAdapter(fallback);
|
|
144
|
+
adapterCache.set(agentId, adapter);
|
|
145
|
+
return adapter;
|
|
146
|
+
}
|
|
147
|
+
// Non-default agent not found — create generic with the ID as path (will fail if not installed)
|
|
148
|
+
const notFound: AgentConfig = {
|
|
149
|
+
id: agentId, name: agentId, path: agentId, enabled: true, type: 'generic',
|
|
150
|
+
capabilities: { supportsResume: false, supportsStreamJson: false, supportsModel: false, supportsSkipPermissions: false, hasSessionFiles: false, requiresTTY: false },
|
|
151
|
+
};
|
|
152
|
+
const adapter = createGenericAdapter(notFound);
|
|
153
|
+
adapterCache.set(agentId, adapter);
|
|
154
|
+
return adapter;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const adapter = config.type === 'claude-code'
|
|
158
|
+
? createClaudeAdapter(config)
|
|
159
|
+
: createGenericAdapter(config);
|
|
160
|
+
|
|
161
|
+
adapterCache.set(agentId, adapter);
|
|
162
|
+
return adapter;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Clear adapter cache (call after settings change) */
|
|
166
|
+
export function clearAgentCache(): void {
|
|
167
|
+
adapterCache.clear();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Auto-detect all available agents (called on startup) */
|
|
171
|
+
export function autoDetectAgents(): AgentConfig[] {
|
|
172
|
+
const detected: AgentConfig[] = [];
|
|
173
|
+
|
|
174
|
+
const claude = detectClaude();
|
|
175
|
+
if (claude) detected.push(claude);
|
|
176
|
+
|
|
177
|
+
const codex = detectAgent('codex', 'OpenAI Codex', 'codex');
|
|
178
|
+
if (codex) detected.push(codex);
|
|
179
|
+
|
|
180
|
+
const aider = detectAgent('aider', 'Aider', 'aider', ['--message']);
|
|
181
|
+
if (aider) detected.push(aider);
|
|
182
|
+
|
|
183
|
+
if (detected.length > 0) {
|
|
184
|
+
console.log(`[agents] Detected: ${detected.map(a => a.name).join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return detected;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Resolve terminal launch info for an agent — used by both VibeCoding and Workspace */
|
|
191
|
+
export interface TerminalLaunchInfo {
|
|
192
|
+
cliCmd: string; // actual binary: claude, codex, aider
|
|
193
|
+
cliType: string; // claude-code, codex, aider, generic
|
|
194
|
+
supportsSession: boolean; // has session files to resume
|
|
195
|
+
resumeFlag: string; // -c, --resume, etc.
|
|
196
|
+
env?: Record<string, string>; // profile env vars to export
|
|
197
|
+
model?: string; // profile model override (--model flag)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function resolveTerminalLaunch(agentId?: string): TerminalLaunchInfo {
|
|
201
|
+
const settings = loadSettings();
|
|
202
|
+
const agentCfg = settings.agents?.[agentId || 'claude'] || {};
|
|
203
|
+
// Resolve cliType: own cliType → base agent's cliType → base agent name guessing → agentId name guessing
|
|
204
|
+
const baseId = agentCfg.base;
|
|
205
|
+
const baseCfg = baseId ? (settings.agents?.[baseId] || {}) : {};
|
|
206
|
+
const cliType = agentCfg.cliType || baseCfg.cliType
|
|
207
|
+
|| (baseId === 'codex' ? 'codex' : baseId === 'aider' ? 'aider' : undefined)
|
|
208
|
+
|| (agentId === 'codex' ? 'codex' : agentId === 'aider' ? 'aider' : 'claude-code');
|
|
209
|
+
|
|
210
|
+
// Determine CLI command and capabilities from cliType
|
|
211
|
+
const cliMap: Record<string, { cmd: string; session: boolean; resume: string }> = {
|
|
212
|
+
'claude-code': { cmd: 'claude', session: true, resume: '-c' },
|
|
213
|
+
'codex': { cmd: 'codex', session: false, resume: '' },
|
|
214
|
+
'aider': { cmd: 'aider', session: false, resume: '' },
|
|
215
|
+
'generic': { cmd: agentCfg.path || agentId || 'claude', session: false, resume: '' },
|
|
216
|
+
};
|
|
217
|
+
const cli = cliMap[cliType] || cliMap['claude-code'];
|
|
218
|
+
|
|
219
|
+
// Resolve env/model: either from this agent's own profile fields, or from linked profile
|
|
220
|
+
let env: Record<string, string> | undefined;
|
|
221
|
+
let model: string | undefined;
|
|
222
|
+
if (agentCfg.base || agentCfg.env || agentCfg.model || agentCfg.models) {
|
|
223
|
+
// This agent has profile-like config — read env/model directly
|
|
224
|
+
if (agentCfg.env) env = { ...agentCfg.env };
|
|
225
|
+
model = agentCfg.model || agentCfg.models?.terminal;
|
|
226
|
+
if (model === 'default') model = undefined; // 'default' means no override
|
|
227
|
+
} else if (agentCfg.profile) {
|
|
228
|
+
// Agent links to a separate profile — read from that
|
|
229
|
+
const profileCfg = settings.agents?.[agentCfg.profile];
|
|
230
|
+
if (profileCfg) {
|
|
231
|
+
if (profileCfg.env) env = { ...profileCfg.env };
|
|
232
|
+
model = profileCfg.model || profileCfg.models?.terminal;
|
|
233
|
+
if (model === 'default') model = undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
cliCmd: cli.cmd,
|
|
239
|
+
cliType,
|
|
240
|
+
supportsSession: cli.session,
|
|
241
|
+
resumeFlag: agentCfg.resumeFlag || cli.resume,
|
|
242
|
+
env,
|
|
243
|
+
model,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent types — shared interfaces for multi-agent support.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type AgentId = string; // 'claude' | 'codex' | 'aider' | custom
|
|
6
|
+
|
|
7
|
+
export interface AgentCapabilities {
|
|
8
|
+
supportsResume: boolean; // -c / --resume (continue session)
|
|
9
|
+
supportsStreamJson: boolean; // structured output parsing
|
|
10
|
+
supportsModel: boolean; // --model flag
|
|
11
|
+
supportsSkipPermissions: boolean; // --dangerously-skip-permissions or equivalent
|
|
12
|
+
hasSessionFiles: boolean; // on-disk session files (JSONL etc.)
|
|
13
|
+
requiresTTY: boolean; // needs pseudo-terminal (e.g., codex)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AgentConfig {
|
|
17
|
+
id: AgentId;
|
|
18
|
+
name: string; // display name: "Claude Code", "OpenAI Codex", "Aider"
|
|
19
|
+
path: string; // binary path
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
type: 'claude-code' | 'generic'; // adapter type
|
|
22
|
+
flags?: string[]; // extra CLI flags
|
|
23
|
+
capabilities: AgentCapabilities;
|
|
24
|
+
version?: string;
|
|
25
|
+
skipPermissionsFlag?: string; // e.g., "--dangerously-skip-permissions", "--full-auto"
|
|
26
|
+
// Profile fields
|
|
27
|
+
base?: string; // base agent ID — makes this a profile
|
|
28
|
+
isProfile?: boolean; // true if this is a profile (not a base agent)
|
|
29
|
+
backendType?: 'cli' | 'api'; // 'api' for API profiles
|
|
30
|
+
provider?: string; // API provider (anthropic, google, openai, grok)
|
|
31
|
+
model?: string; // model override for profiles
|
|
32
|
+
apiKey?: string; // per-profile API key
|
|
33
|
+
env?: Record<string, string>; // env vars injected on spawn
|
|
34
|
+
cliType?: 'claude-code' | 'codex' | 'aider' | 'generic'; // CLI tool type
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AgentSpawnOptions {
|
|
38
|
+
projectPath: string;
|
|
39
|
+
prompt: string;
|
|
40
|
+
model?: string;
|
|
41
|
+
conversationId?: string; // for resume
|
|
42
|
+
skipPermissions?: boolean;
|
|
43
|
+
outputFormat?: 'stream-json' | 'json' | 'text';
|
|
44
|
+
extraFlags?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface AgentSpawnResult {
|
|
48
|
+
cmd: string;
|
|
49
|
+
args: string[];
|
|
50
|
+
env?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AgentAdapter {
|
|
54
|
+
id: AgentId;
|
|
55
|
+
config: AgentConfig;
|
|
56
|
+
|
|
57
|
+
/** Build spawn command + args for non-interactive task execution */
|
|
58
|
+
buildTaskSpawn(opts: AgentSpawnOptions): AgentSpawnResult;
|
|
59
|
+
|
|
60
|
+
/** Build the terminal command string (e.g., "cd /path && claude -c") */
|
|
61
|
+
buildTerminalCommand(opts: {
|
|
62
|
+
projectPath: string;
|
|
63
|
+
resume?: boolean;
|
|
64
|
+
sessionId?: string;
|
|
65
|
+
skipPermissions?: boolean;
|
|
66
|
+
}): string;
|
|
67
|
+
|
|
68
|
+
/** Parse a line of output into normalized events (for stream-json agents) */
|
|
69
|
+
parseOutputLine?(line: string): any[];
|
|
70
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact system — structured data passing between delivery agents.
|
|
3
|
+
* Each artifact is a named document (requirements, architecture, test-plan, etc.)
|
|
4
|
+
* stored as a separate JSON file for lazy loading.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { getDataDir } from './dirs';
|
|
11
|
+
|
|
12
|
+
export type ArtifactType = 'requirements' | 'architecture' | 'test-plan' | 'code-diff' | 'review-report' | 'custom';
|
|
13
|
+
|
|
14
|
+
export interface Artifact {
|
|
15
|
+
id: string;
|
|
16
|
+
deliveryId: string;
|
|
17
|
+
type: ArtifactType;
|
|
18
|
+
name: string; // e.g., "requirements.md"
|
|
19
|
+
content: string;
|
|
20
|
+
producedBy: string; // phase name or 'user'
|
|
21
|
+
consumedBy: string[]; // phases that consumed this
|
|
22
|
+
createdAt: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function artifactsDir(deliveryId: string): string {
|
|
26
|
+
const dir = join(getDataDir(), 'deliveries', deliveryId, 'artifacts');
|
|
27
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createArtifact(deliveryId: string, opts: {
|
|
32
|
+
type: ArtifactType;
|
|
33
|
+
name: string;
|
|
34
|
+
content: string;
|
|
35
|
+
producedBy: string;
|
|
36
|
+
}): Artifact {
|
|
37
|
+
const id = randomUUID().slice(0, 8);
|
|
38
|
+
const artifact: Artifact = {
|
|
39
|
+
id,
|
|
40
|
+
deliveryId,
|
|
41
|
+
type: opts.type,
|
|
42
|
+
name: opts.name,
|
|
43
|
+
content: opts.content,
|
|
44
|
+
producedBy: opts.producedBy,
|
|
45
|
+
consumedBy: [],
|
|
46
|
+
createdAt: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
writeFileSync(join(artifactsDir(deliveryId), `${id}.json`), JSON.stringify(artifact, null, 2));
|
|
49
|
+
return artifact;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getArtifact(deliveryId: string, id: string): Artifact | null {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(readFileSync(join(artifactsDir(deliveryId), `${id}.json`), 'utf-8'));
|
|
55
|
+
} catch { return null; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function listArtifacts(deliveryId: string): Artifact[] {
|
|
59
|
+
const dir = artifactsDir(deliveryId);
|
|
60
|
+
if (!existsSync(dir)) return [];
|
|
61
|
+
return readdirSync(dir)
|
|
62
|
+
.filter(f => f.endsWith('.json'))
|
|
63
|
+
.map(f => {
|
|
64
|
+
try { return JSON.parse(readFileSync(join(dir, f), 'utf-8')) as Artifact; } catch { return null; }
|
|
65
|
+
})
|
|
66
|
+
.filter(Boolean) as Artifact[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function deleteArtifact(deliveryId: string, id: string): void {
|
|
70
|
+
try { unlinkSync(join(artifactsDir(deliveryId), `${id}.json`)); } catch {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Extract artifacts from agent output using ===ARTIFACT:name=== markers */
|
|
74
|
+
export function extractArtifacts(output: string, deliveryId: string, producedBy: string): Artifact[] {
|
|
75
|
+
const artifacts: Artifact[] = [];
|
|
76
|
+
const regex = /===ARTIFACT:([\w.-]+)===\n([\s\S]*?)(?=\n===ARTIFACT:|$)/g;
|
|
77
|
+
let match;
|
|
78
|
+
|
|
79
|
+
while ((match = regex.exec(output)) !== null) {
|
|
80
|
+
const name = match[1];
|
|
81
|
+
const content = match[2].trim();
|
|
82
|
+
if (!content) continue;
|
|
83
|
+
|
|
84
|
+
// Infer type from name
|
|
85
|
+
let type: ArtifactType = 'custom';
|
|
86
|
+
if (name.includes('requirement')) type = 'requirements';
|
|
87
|
+
else if (name.includes('architect') || name.includes('design')) type = 'architecture';
|
|
88
|
+
else if (name.includes('test')) type = 'test-plan';
|
|
89
|
+
else if (name.includes('review')) type = 'review-report';
|
|
90
|
+
else if (name.includes('diff')) type = 'code-diff';
|
|
91
|
+
|
|
92
|
+
artifacts.push(createArtifact(deliveryId, { type, name, content, producedBy }));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fallback: if no markers found, don't create any artifact — let the engine decide
|
|
96
|
+
return artifacts;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Write artifact content to the project directory */
|
|
100
|
+
export function writeArtifactToProject(artifact: Artifact, projectPath: string, subDir = '.forge-delivery'): string {
|
|
101
|
+
const dir = join(projectPath, subDir);
|
|
102
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
103
|
+
const filePath = join(dir, artifact.name);
|
|
104
|
+
writeFileSync(filePath, artifact.content, 'utf-8');
|
|
105
|
+
return filePath;
|
|
106
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import NextAuth from 'next-auth';
|
|
2
|
+
import Google from 'next-auth/providers/google';
|
|
3
|
+
import Credentials from 'next-auth/providers/credentials';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
// Ensure AUTH_SECRET exists before NextAuth initializes
|
|
7
|
+
if (!process.env.AUTH_SECRET) {
|
|
8
|
+
process.env.AUTH_SECRET = randomBytes(32).toString('hex');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
12
|
+
trustHost: true,
|
|
13
|
+
logger: {
|
|
14
|
+
error: () => {}, // Suppress noisy CredentialsSignin stack traces
|
|
15
|
+
},
|
|
16
|
+
providers: [
|
|
17
|
+
// Google OAuth — for production use
|
|
18
|
+
Google({
|
|
19
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
20
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
21
|
+
}),
|
|
22
|
+
// Local: admin password only
|
|
23
|
+
// Remote (tunnel): admin password + session code (2FA)
|
|
24
|
+
Credentials({
|
|
25
|
+
name: 'Local',
|
|
26
|
+
credentials: {
|
|
27
|
+
password: { label: 'Password', type: 'password' },
|
|
28
|
+
sessionCode: { label: 'Session Code', type: 'text' },
|
|
29
|
+
isRemote: { label: 'Remote', type: 'text' },
|
|
30
|
+
},
|
|
31
|
+
async authorize(credentials) {
|
|
32
|
+
const { verifyLogin } = await import('./password');
|
|
33
|
+
const password = (credentials?.password ?? '') as string;
|
|
34
|
+
const sessionCode = (credentials?.sessionCode ?? '') as string;
|
|
35
|
+
const isRemote = String(credentials?.isRemote) === 'true';
|
|
36
|
+
|
|
37
|
+
if (verifyLogin(password, sessionCode, isRemote)) {
|
|
38
|
+
const { loadSettings } = await import('./settings');
|
|
39
|
+
const settings = loadSettings();
|
|
40
|
+
console.log(`[auth] Login success (${isRemote ? 'remote' : 'local'})`);
|
|
41
|
+
return { id: 'local', name: settings.displayName || 'Forge', email: settings.displayEmail || 'local@forge' };
|
|
42
|
+
}
|
|
43
|
+
console.warn(`[auth] Login failed (${isRemote ? 'remote' : 'local'})`);
|
|
44
|
+
return null;
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
],
|
|
48
|
+
pages: {
|
|
49
|
+
signIn: '/login',
|
|
50
|
+
},
|
|
51
|
+
callbacks: {
|
|
52
|
+
authorized({ auth }) {
|
|
53
|
+
return !!auth;
|
|
54
|
+
},
|
|
55
|
+
redirect({ url, baseUrl }) {
|
|
56
|
+
if (url.startsWith(baseUrl)) return url;
|
|
57
|
+
if (url.startsWith('/')) return `${baseUrl}${url}`;
|
|
58
|
+
if (url.includes('.trycloudflare.com')) return url;
|
|
59
|
+
return baseUrl;
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|