@aion0/forge 0.5.26 → 0.5.27
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 +11 -28
- package/app/api/terminal-bell/route.ts +6 -2
- package/components/WebTerminal.tsx +36 -2
- package/lib/terminal-standalone.ts +19 -2
- package/next-env.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request/Response Document System — structured YAML documents for
|
|
3
|
+
* multi-agent delivery workflows.
|
|
4
|
+
*
|
|
5
|
+
* Storage layout:
|
|
6
|
+
* <project>/.forge/requests/<id>/
|
|
7
|
+
* ├── request.yml — created by Architect
|
|
8
|
+
* └── response.yml — updated by Engineer, Reviewer, QA
|
|
9
|
+
*
|
|
10
|
+
* Inspired by Accord protocol: YAML frontmatter + structured content,
|
|
11
|
+
* status lifecycle (open → in_progress → review → qa → done).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import YAML from 'yaml';
|
|
17
|
+
|
|
18
|
+
// ─── Types ──────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export type RequestStatus = 'open' | 'in_progress' | 'review' | 'qa' | 'done' | 'rejected';
|
|
21
|
+
export type RequestPriority = 'high' | 'medium' | 'low';
|
|
22
|
+
export type RequestType = 'feature' | 'bugfix' | 'refactor' | 'task';
|
|
23
|
+
|
|
24
|
+
export interface RequestModule {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
acceptance_criteria: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RequestDocument {
|
|
31
|
+
id: string;
|
|
32
|
+
batch: string; // groups requests into a delivery
|
|
33
|
+
title: string;
|
|
34
|
+
description: string;
|
|
35
|
+
type: RequestType;
|
|
36
|
+
modules: RequestModule[];
|
|
37
|
+
priority: RequestPriority;
|
|
38
|
+
status: RequestStatus;
|
|
39
|
+
assigned_to: string; // agent label
|
|
40
|
+
created_by: string; // agent label
|
|
41
|
+
created_at: string; // ISO timestamp
|
|
42
|
+
updated_at: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface EngineerResponse {
|
|
46
|
+
completed_at?: string;
|
|
47
|
+
files_changed: string[];
|
|
48
|
+
notes: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ReviewResponse {
|
|
52
|
+
completed_at?: string;
|
|
53
|
+
result: 'approved' | 'changes_requested' | 'rejected';
|
|
54
|
+
findings: Array<{ severity: string; description: string }>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface QaResponse {
|
|
58
|
+
completed_at?: string;
|
|
59
|
+
result: 'passed' | 'failed';
|
|
60
|
+
test_files: string[];
|
|
61
|
+
findings: Array<{ severity: string; description: string }>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ResponseDocument {
|
|
65
|
+
request_id: string;
|
|
66
|
+
status: RequestStatus;
|
|
67
|
+
engineer?: EngineerResponse;
|
|
68
|
+
review?: ReviewResponse;
|
|
69
|
+
qa?: QaResponse;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Paths ──────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function requestsRoot(projectPath: string): string {
|
|
75
|
+
return join(projectPath, '.forge', 'requests');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function requestDir(projectPath: string, requestId: string): string {
|
|
79
|
+
return join(requestsRoot(projectPath), requestId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function ensureDir(dir: string): void {
|
|
83
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── CRUD ───────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate a request ID: REQ-YYYYMMDD-NNN
|
|
90
|
+
*/
|
|
91
|
+
export function generateRequestId(projectPath: string): string {
|
|
92
|
+
const now = new Date();
|
|
93
|
+
const date = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
|
94
|
+
const root = requestsRoot(projectPath);
|
|
95
|
+
if (!existsSync(root)) return `REQ-${date}-001`;
|
|
96
|
+
const existing = readdirSync(root).filter(f => f.startsWith(`REQ-${date}-`));
|
|
97
|
+
const num = existing.length + 1;
|
|
98
|
+
return `REQ-${date}-${String(num).padStart(3, '0')}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a new request document.
|
|
103
|
+
* Returns the relative path to request.yml (for use as ref in bus messages).
|
|
104
|
+
*/
|
|
105
|
+
export function createRequest(projectPath: string, doc: Omit<RequestDocument, 'id' | 'created_at' | 'updated_at'> & { id?: string }): string {
|
|
106
|
+
const id = doc.id || generateRequestId(projectPath);
|
|
107
|
+
const dir = requestDir(projectPath, id);
|
|
108
|
+
ensureDir(dir);
|
|
109
|
+
|
|
110
|
+
const now = new Date().toISOString();
|
|
111
|
+
const full: RequestDocument = {
|
|
112
|
+
...doc,
|
|
113
|
+
id,
|
|
114
|
+
status: doc.status || 'open',
|
|
115
|
+
created_at: now,
|
|
116
|
+
updated_at: now,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const filePath = join(dir, 'request.yml');
|
|
120
|
+
writeFileSync(filePath, YAML.stringify(full), 'utf-8');
|
|
121
|
+
console.log(`[requests] Created ${id}: ${doc.title}`);
|
|
122
|
+
|
|
123
|
+
// Return relative path for bus ref
|
|
124
|
+
return `.forge/requests/${id}/request.yml`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get a request and its optional response.
|
|
129
|
+
*/
|
|
130
|
+
export function getRequest(projectPath: string, requestId: string): { request: RequestDocument; response?: ResponseDocument } | null {
|
|
131
|
+
const dir = requestDir(projectPath, requestId);
|
|
132
|
+
const reqFile = join(dir, 'request.yml');
|
|
133
|
+
if (!existsSync(reqFile)) return null;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const request: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
|
|
137
|
+
let response: ResponseDocument | undefined;
|
|
138
|
+
const resFile = join(dir, 'response.yml');
|
|
139
|
+
if (existsSync(resFile)) {
|
|
140
|
+
response = YAML.parse(readFileSync(resFile, 'utf-8'));
|
|
141
|
+
}
|
|
142
|
+
return { request, response };
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
console.error(`[requests] Failed to read ${requestId}: ${err.message}`);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* List all requests, optionally filtered by batch or status.
|
|
151
|
+
*/
|
|
152
|
+
export function listRequests(projectPath: string, opts?: { batch?: string; status?: RequestStatus }): RequestDocument[] {
|
|
153
|
+
const root = requestsRoot(projectPath);
|
|
154
|
+
if (!existsSync(root)) return [];
|
|
155
|
+
|
|
156
|
+
const results: RequestDocument[] = [];
|
|
157
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
158
|
+
if (!entry.isDirectory()) continue;
|
|
159
|
+
const reqFile = join(root, entry.name, 'request.yml');
|
|
160
|
+
if (!existsSync(reqFile)) continue;
|
|
161
|
+
try {
|
|
162
|
+
const doc: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
|
|
163
|
+
if (opts?.batch && doc.batch !== opts.batch) continue;
|
|
164
|
+
if (opts?.status && doc.status !== opts.status) continue;
|
|
165
|
+
results.push(doc);
|
|
166
|
+
} catch {}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return results.sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Update a response document section (engineer, review, or qa).
|
|
174
|
+
* Automatically advances request status:
|
|
175
|
+
* engineer → review, review(approved) → qa, qa(passed) → done
|
|
176
|
+
* Returns the relative path to response.yml.
|
|
177
|
+
*/
|
|
178
|
+
export function updateResponse(
|
|
179
|
+
projectPath: string,
|
|
180
|
+
requestId: string,
|
|
181
|
+
section: 'engineer' | 'review' | 'qa',
|
|
182
|
+
data: Record<string, any>,
|
|
183
|
+
): string {
|
|
184
|
+
const dir = requestDir(projectPath, requestId);
|
|
185
|
+
const reqFile = join(dir, 'request.yml');
|
|
186
|
+
if (!existsSync(reqFile)) throw new Error(`Request ${requestId} not found`);
|
|
187
|
+
|
|
188
|
+
ensureDir(dir);
|
|
189
|
+
const resFile = join(dir, 'response.yml');
|
|
190
|
+
|
|
191
|
+
// Load or create response
|
|
192
|
+
let response: ResponseDocument;
|
|
193
|
+
if (existsSync(resFile)) {
|
|
194
|
+
response = YAML.parse(readFileSync(resFile, 'utf-8'));
|
|
195
|
+
} else {
|
|
196
|
+
response = { request_id: requestId, status: 'in_progress' };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Update section with timestamp
|
|
200
|
+
const now = new Date().toISOString();
|
|
201
|
+
(response as any)[section] = { ...data, completed_at: now };
|
|
202
|
+
|
|
203
|
+
// Auto-advance status
|
|
204
|
+
const request: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
|
|
205
|
+
let newStatus: RequestStatus = request.status;
|
|
206
|
+
|
|
207
|
+
if (section === 'engineer') {
|
|
208
|
+
newStatus = 'review';
|
|
209
|
+
} else if (section === 'review') {
|
|
210
|
+
newStatus = data.result === 'rejected' ? 'rejected' : data.result === 'changes_requested' ? 'in_progress' : 'qa';
|
|
211
|
+
} else if (section === 'qa') {
|
|
212
|
+
newStatus = data.result === 'passed' ? 'done' : 'in_progress'; // failed → back to engineer
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
response.status = newStatus;
|
|
216
|
+
request.status = newStatus;
|
|
217
|
+
request.updated_at = now;
|
|
218
|
+
|
|
219
|
+
// Write both files
|
|
220
|
+
writeFileSync(resFile, YAML.stringify(response), 'utf-8');
|
|
221
|
+
writeFileSync(reqFile, YAML.stringify(request), 'utf-8');
|
|
222
|
+
|
|
223
|
+
console.log(`[requests] ${requestId}: ${section} updated → status=${newStatus}`);
|
|
224
|
+
return `.forge/requests/${requestId}/response.yml`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Claim a request — set assigned_to and status to in_progress.
|
|
229
|
+
* Returns true if claimed successfully, false if already claimed by someone else.
|
|
230
|
+
*/
|
|
231
|
+
export function claimRequest(projectPath: string, requestId: string, agentLabel: string): { ok: boolean; claimedBy?: string } {
|
|
232
|
+
const dir = requestDir(projectPath, requestId);
|
|
233
|
+
const reqFile = join(dir, 'request.yml');
|
|
234
|
+
if (!existsSync(reqFile)) throw new Error(`Request ${requestId} not found`);
|
|
235
|
+
|
|
236
|
+
const doc: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
|
|
237
|
+
|
|
238
|
+
// Already claimed by someone else
|
|
239
|
+
if (doc.assigned_to && doc.assigned_to !== agentLabel) {
|
|
240
|
+
return { ok: false, claimedBy: doc.assigned_to };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Already claimed by this agent (idempotent)
|
|
244
|
+
if (doc.assigned_to === agentLabel) {
|
|
245
|
+
return { ok: true };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Only open requests can be claimed
|
|
249
|
+
if (doc.status !== 'open') {
|
|
250
|
+
return { ok: false, claimedBy: doc.assigned_to || `(status: ${doc.status})` };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
doc.assigned_to = agentLabel;
|
|
254
|
+
doc.status = 'in_progress';
|
|
255
|
+
doc.updated_at = new Date().toISOString();
|
|
256
|
+
writeFileSync(reqFile, YAML.stringify(doc), 'utf-8');
|
|
257
|
+
console.log(`[requests] ${requestId}: claimed by ${agentLabel}`);
|
|
258
|
+
return { ok: true };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Manually update request status.
|
|
263
|
+
*/
|
|
264
|
+
export function updateRequestStatus(projectPath: string, requestId: string, status: RequestStatus): void {
|
|
265
|
+
const dir = requestDir(projectPath, requestId);
|
|
266
|
+
const reqFile = join(dir, 'request.yml');
|
|
267
|
+
if (!existsSync(reqFile)) throw new Error(`Request ${requestId} not found`);
|
|
268
|
+
|
|
269
|
+
const doc: RequestDocument = YAML.parse(readFileSync(reqFile, 'utf-8'));
|
|
270
|
+
doc.status = status;
|
|
271
|
+
doc.updated_at = new Date().toISOString();
|
|
272
|
+
writeFileSync(reqFile, YAML.stringify(doc), 'utf-8');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get batch completion status.
|
|
277
|
+
*/
|
|
278
|
+
export function getBatchStatus(projectPath: string, batch: string): { total: number; done: number; allDone: boolean; requests: Array<{ id: string; title: string; status: RequestStatus }> } {
|
|
279
|
+
const all = listRequests(projectPath, { batch });
|
|
280
|
+
const done = all.filter(r => r.status === 'done' || r.status === 'rejected').length;
|
|
281
|
+
return {
|
|
282
|
+
total: all.length,
|
|
283
|
+
done,
|
|
284
|
+
allDone: all.length > 0 && done === all.length,
|
|
285
|
+
requests: all.map(r => ({ id: r.id, title: r.title, status: r.status })),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session File Monitor — detects agent running/idle state by watching
|
|
3
|
+
* Claude Code's .jsonl session files.
|
|
4
|
+
*
|
|
5
|
+
* How it works:
|
|
6
|
+
* - Each agent has a known session file path (boundSessionId/fixedSessionId/--session-id)
|
|
7
|
+
* - Monitor checks file mtime every 3s
|
|
8
|
+
* - mtime changing → agent is running (LLM streaming, tool use, etc.)
|
|
9
|
+
* - mtime stable for IDLE_THRESHOLD → check last lines for 'result' entry → done
|
|
10
|
+
* - No session file → idle (not started)
|
|
11
|
+
*
|
|
12
|
+
* Works for both terminal and headless modes — both write the same .jsonl format.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { statSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import { resolve } from 'node:path';
|
|
19
|
+
import { EventEmitter } from 'node:events';
|
|
20
|
+
|
|
21
|
+
export type SessionMonitorState = 'idle' | 'running' | 'done';
|
|
22
|
+
|
|
23
|
+
export interface SessionMonitorEvent {
|
|
24
|
+
agentId: string;
|
|
25
|
+
state: SessionMonitorState;
|
|
26
|
+
sessionFile: string;
|
|
27
|
+
detail?: string; // e.g., result summary
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const POLL_INTERVAL = 3000; // check every 3s
|
|
31
|
+
const IDLE_THRESHOLD = 29 * 60 * 1000; // 29min of no file change → check for result entry
|
|
32
|
+
const STABLE_THRESHOLD = 30 * 60 * 1000; // 30min of no change → force done (fallback if hook missed)
|
|
33
|
+
|
|
34
|
+
export class SessionFileMonitor extends EventEmitter {
|
|
35
|
+
private timers = new Map<string, NodeJS.Timeout>();
|
|
36
|
+
private lastMtime = new Map<string, number>();
|
|
37
|
+
private lastSize = new Map<string, number>();
|
|
38
|
+
private lastStableTime = new Map<string, number>();
|
|
39
|
+
private currentState = new Map<string, SessionMonitorState>();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Start monitoring a session file for an agent.
|
|
43
|
+
* @param agentId - Agent identifier
|
|
44
|
+
* @param sessionFilePath - Full path to the .jsonl session file
|
|
45
|
+
*/
|
|
46
|
+
startMonitoring(agentId: string, sessionFilePath: string): void {
|
|
47
|
+
this.stopMonitoring(agentId);
|
|
48
|
+
this.currentState.set(agentId, 'idle');
|
|
49
|
+
this.lastStableTime.set(agentId, Date.now());
|
|
50
|
+
this.warmupCount.set(agentId, 0); // reset warmup for fresh start
|
|
51
|
+
|
|
52
|
+
const timer = setInterval(() => {
|
|
53
|
+
this.checkFile(agentId, sessionFilePath);
|
|
54
|
+
}, POLL_INTERVAL);
|
|
55
|
+
timer.unref();
|
|
56
|
+
this.timers.set(agentId, timer);
|
|
57
|
+
|
|
58
|
+
console.log(`[session-monitor] Started monitoring ${agentId}: ${sessionFilePath}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Stop monitoring an agent's session file.
|
|
63
|
+
*/
|
|
64
|
+
stopMonitoring(agentId: string): void {
|
|
65
|
+
const timer = this.timers.get(agentId);
|
|
66
|
+
if (timer) clearInterval(timer);
|
|
67
|
+
this.timers.delete(agentId);
|
|
68
|
+
this.lastMtime.delete(agentId);
|
|
69
|
+
this.lastSize.delete(agentId);
|
|
70
|
+
this.lastStableTime.delete(agentId);
|
|
71
|
+
this.currentState.delete(agentId);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Stop all monitors.
|
|
76
|
+
*/
|
|
77
|
+
stopAll(): void {
|
|
78
|
+
for (const [id] of this.timers) {
|
|
79
|
+
this.stopMonitoring(id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get current state for an agent.
|
|
85
|
+
*/
|
|
86
|
+
getState(agentId: string): SessionMonitorState {
|
|
87
|
+
return this.currentState.get(agentId) || 'idle';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Reset monitor state to idle and pause detection briefly.
|
|
92
|
+
* Call when orchestrator manually changes taskStatus (button/hook).
|
|
93
|
+
* Suppresses detection for 10s to avoid immediately flipping back.
|
|
94
|
+
*/
|
|
95
|
+
private suppressUntil = new Map<string, number>();
|
|
96
|
+
|
|
97
|
+
resetState(agentId: string): void {
|
|
98
|
+
this.currentState.set(agentId, 'idle');
|
|
99
|
+
this.lastStableTime.set(agentId, Date.now());
|
|
100
|
+
// Don't reset warmupCount here — warmup only on startMonitoring (fresh start/restart)
|
|
101
|
+
// 10s suppress is enough to prevent immediate flip-back after hook/button
|
|
102
|
+
this.suppressUntil.set(agentId, Date.now() + 10_000);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve session file path for a project + session ID.
|
|
107
|
+
*/
|
|
108
|
+
static resolveSessionPath(projectPath: string, workDir: string | undefined, sessionId: string): string {
|
|
109
|
+
const fullPath = workDir && workDir !== './' && workDir !== '.'
|
|
110
|
+
? join(projectPath, workDir) : projectPath;
|
|
111
|
+
// Claude Code encodes paths by replacing all non-alphanumeric chars with '-'
|
|
112
|
+
const encoded = resolve(fullPath).replace(/[^a-zA-Z0-9]/g, '-');
|
|
113
|
+
return join(homedir(), '.claude', 'projects', encoded, `${sessionId}.jsonl`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private warmupCount = new Map<string, number>();
|
|
117
|
+
private checkFile(agentId: string, filePath: string): void {
|
|
118
|
+
try {
|
|
119
|
+
const stat = statSync(filePath);
|
|
120
|
+
const mtime = stat.mtimeMs;
|
|
121
|
+
const size = stat.size;
|
|
122
|
+
|
|
123
|
+
// Warmup: skip first 6 polls (~18s) to avoid false running during startup
|
|
124
|
+
// On poll 7 (first real check), just set new baseline — don't trigger running
|
|
125
|
+
const count = (this.warmupCount.get(agentId) || 0) + 1;
|
|
126
|
+
this.warmupCount.set(agentId, count);
|
|
127
|
+
if (count <= 7) {
|
|
128
|
+
this.lastMtime.set(agentId, mtime);
|
|
129
|
+
this.lastSize.set(agentId, size);
|
|
130
|
+
this.lastStableTime.set(agentId, Date.now());
|
|
131
|
+
if (count === 1) console.log(`[session-monitor] ${agentId}: warmup started`);
|
|
132
|
+
if (count === 7) console.log(`[session-monitor] ${agentId}: warmup done, monitoring active`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const prevMtime = this.lastMtime.get(agentId) || 0;
|
|
137
|
+
const prevSize = this.lastSize.get(agentId) || 0;
|
|
138
|
+
const prevState = this.currentState.get(agentId) || 'idle';
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
|
|
141
|
+
this.lastMtime.set(agentId, mtime);
|
|
142
|
+
this.lastSize.set(agentId, size);
|
|
143
|
+
|
|
144
|
+
// File changed (mtime or size different) → running
|
|
145
|
+
if (mtime !== prevMtime || size !== prevSize) {
|
|
146
|
+
this.lastStableTime.set(agentId, now);
|
|
147
|
+
if (prevState !== 'running') {
|
|
148
|
+
this.setState(agentId, 'running', filePath);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// File unchanged — how long has it been stable?
|
|
154
|
+
const stableFor = now - (this.lastStableTime.get(agentId) || now);
|
|
155
|
+
|
|
156
|
+
if (prevState === 'running') {
|
|
157
|
+
if (stableFor >= IDLE_THRESHOLD) {
|
|
158
|
+
// Check if session file has a 'result' entry at the end
|
|
159
|
+
const resultInfo = this.checkForResult(filePath, size);
|
|
160
|
+
if (resultInfo) {
|
|
161
|
+
this.setState(agentId, 'done', filePath, resultInfo);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (stableFor >= STABLE_THRESHOLD) {
|
|
166
|
+
// Force done after 60min even without result entry (ultimate fallback)
|
|
167
|
+
this.setState(agentId, 'done', filePath, 'stable timeout');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
if ((this.warmupCount.get(`err-${agentId}`) || 0) === 0) {
|
|
173
|
+
this.warmupCount.set(`err-${agentId}`, 1);
|
|
174
|
+
console.log(`[session-monitor] ${agentId}: checkFile error — ${err.message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check the last few lines of the session file for a 'result' type entry.
|
|
181
|
+
* Claude Code writes this when a turn completes.
|
|
182
|
+
*/
|
|
183
|
+
private checkForResult(filePath: string, fileSize?: number): string | null {
|
|
184
|
+
try {
|
|
185
|
+
// Read last 4KB of the file — fileSize passed from caller to avoid second statSync
|
|
186
|
+
const { openSync, readSync, closeSync } = require('node:fs') as typeof import('node:fs');
|
|
187
|
+
const size = fileSize ?? statSync(filePath).size;
|
|
188
|
+
const readSize = Math.min(4096, size);
|
|
189
|
+
const fd = openSync(filePath, 'r');
|
|
190
|
+
const buf = Buffer.alloc(readSize);
|
|
191
|
+
try {
|
|
192
|
+
readSync(fd, buf, 0, readSize, Math.max(0, size - readSize));
|
|
193
|
+
} finally {
|
|
194
|
+
closeSync(fd);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const tail = buf.toString('utf-8');
|
|
198
|
+
const lines = tail.split('\n').filter(l => l.trim());
|
|
199
|
+
|
|
200
|
+
// Scan last lines for result entry
|
|
201
|
+
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 10); i--) {
|
|
202
|
+
try {
|
|
203
|
+
const entry = JSON.parse(lines[i]);
|
|
204
|
+
// Claude Code writes result entries with these fields
|
|
205
|
+
if (entry.type === 'result' || entry.result || entry.duration_ms !== undefined) {
|
|
206
|
+
const summary = entry.result?.slice?.(0, 200) || entry.summary?.slice?.(0, 200) || '';
|
|
207
|
+
return summary || 'completed';
|
|
208
|
+
}
|
|
209
|
+
// Also check for assistant message without tool_use (model stopped)
|
|
210
|
+
if (entry.type === 'assistant' && entry.message?.content) {
|
|
211
|
+
const content = entry.message.content;
|
|
212
|
+
const hasToolUse = Array.isArray(content)
|
|
213
|
+
? content.some((b: any) => b.type === 'tool_use')
|
|
214
|
+
: false;
|
|
215
|
+
if (!hasToolUse) {
|
|
216
|
+
return 'model stopped (no tool_use)';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch {} // skip non-JSON lines
|
|
220
|
+
}
|
|
221
|
+
} catch {}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private setState(agentId: string, state: SessionMonitorState, filePath: string, detail?: string): void {
|
|
226
|
+
const prev = this.currentState.get(agentId);
|
|
227
|
+
if (prev === state) return;
|
|
228
|
+
|
|
229
|
+
// Suppress state changes if recently reset by orchestrator
|
|
230
|
+
const suppressed = this.suppressUntil.get(agentId);
|
|
231
|
+
if (suppressed && Date.now() < suppressed) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.currentState.set(agentId, state);
|
|
236
|
+
const event: SessionMonitorEvent = { agentId, state, sessionFile: filePath, detail };
|
|
237
|
+
this.emit('stateChange', event);
|
|
238
|
+
console.log(`[session-monitor] ${agentId}: ${prev} → ${state}${detail ? ` (${detail})` : ''}`);
|
|
239
|
+
}
|
|
240
|
+
}
|