@aion0/forge 0.4.16 → 0.5.0
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/README.md +1 -1
- package/RELEASE_NOTES.md +170 -14
- package/app/api/agents/route.ts +17 -0
- package/app/api/delivery/[id]/route.ts +62 -0
- package/app/api/delivery/route.ts +40 -0
- package/app/api/mobile-chat/route.ts +13 -7
- package/app/api/monitor/route.ts +10 -6
- package/app/api/pipelines/[id]/route.ts +16 -3
- package/app/api/tasks/route.ts +2 -1
- package/app/api/workspace/[id]/agents/route.ts +35 -0
- package/app/api/workspace/[id]/memory/route.ts +23 -0
- package/app/api/workspace/[id]/smith/route.ts +22 -0
- package/app/api/workspace/[id]/stream/route.ts +28 -0
- package/app/api/workspace/route.ts +100 -0
- package/app/global-error.tsx +10 -4
- package/app/icon.ico +0 -0
- package/app/layout.tsx +2 -2
- package/app/login/LoginForm.tsx +96 -0
- package/app/login/page.tsx +7 -98
- package/app/page.tsx +2 -2
- package/bin/forge-server.mjs +13 -1
- package/check-forge-status.sh +9 -0
- package/components/ConversationEditor.tsx +411 -0
- package/components/ConversationGraphView.tsx +347 -0
- package/components/ConversationTerminalView.tsx +303 -0
- package/components/Dashboard.tsx +36 -39
- package/components/DashboardWrapper.tsx +9 -0
- package/components/DeliveryFlowEditor.tsx +491 -0
- package/components/DeliveryList.tsx +230 -0
- package/components/DeliveryWorkspace.tsx +589 -0
- package/components/DocTerminal.tsx +10 -2
- package/components/DocsViewer.tsx +10 -2
- package/components/HelpTerminal.tsx +11 -6
- package/components/InlinePipelineView.tsx +111 -0
- package/components/MobileView.tsx +20 -0
- package/components/MonitorPanel.tsx +9 -4
- package/components/NewTaskModal.tsx +32 -0
- package/components/PipelineEditor.tsx +49 -6
- package/components/PipelineView.tsx +482 -64
- package/components/ProjectDetail.tsx +314 -56
- package/components/ProjectManager.tsx +49 -4
- package/components/SessionView.tsx +27 -13
- package/components/SettingsModal.tsx +790 -124
- package/components/SkillsPanel.tsx +31 -8
- package/components/TaskBoard.tsx +3 -0
- package/components/WebTerminal.tsx +257 -43
- package/components/WorkspaceTree.tsx +221 -0
- package/components/WorkspaceView.tsx +2224 -0
- package/install.sh +2 -2
- package/lib/agents/claude-adapter.ts +104 -0
- package/lib/agents/generic-adapter.ts +64 -0
- package/lib/agents/index.ts +242 -0
- package/lib/agents/types.ts +70 -0
- package/lib/artifacts.ts +106 -0
- package/lib/delivery.ts +787 -0
- package/lib/forge-skills/forge-inbox.md +37 -0
- package/lib/forge-skills/forge-send.md +40 -0
- package/lib/forge-skills/forge-status.md +32 -0
- package/lib/forge-skills/forge-workspace-sync.md +37 -0
- package/lib/help-docs/00-overview.md +7 -1
- package/lib/help-docs/01-settings.md +159 -2
- package/lib/help-docs/05-pipelines.md +89 -0
- package/lib/help-docs/07-projects.md +35 -1
- package/lib/help-docs/11-workspace.md +204 -0
- package/lib/help-docs/CLAUDE.md +5 -2
- package/lib/init.ts +60 -10
- package/lib/pipeline.ts +537 -1
- package/lib/settings.ts +115 -22
- package/lib/skills.ts +249 -372
- package/lib/task-manager.ts +113 -33
- package/lib/telegram-bot.ts +33 -1
- package/lib/workspace/__tests__/state-machine.test.ts +388 -0
- package/lib/workspace/__tests__/workspace.test.ts +311 -0
- package/lib/workspace/agent-bus.ts +416 -0
- package/lib/workspace/agent-worker.ts +667 -0
- package/lib/workspace/backends/api-backend.ts +262 -0
- package/lib/workspace/backends/cli-backend.ts +479 -0
- package/lib/workspace/index.ts +82 -0
- package/lib/workspace/manager.ts +136 -0
- package/lib/workspace/orchestrator.ts +1804 -0
- package/lib/workspace/persistence.ts +310 -0
- package/lib/workspace/presets.ts +170 -0
- package/lib/workspace/skill-installer.ts +188 -0
- package/lib/workspace/smith-memory.ts +498 -0
- package/lib/workspace/types.ts +231 -0
- package/lib/workspace/watch-manager.ts +288 -0
- package/lib/workspace-standalone.ts +790 -0
- package/middleware.ts +1 -0
- package/package.json +4 -1
- package/src/config/index.ts +12 -1
- package/src/core/db/database.ts +1 -0
- package/start.sh +7 -0
package/README.md
CHANGED
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,26 +1,182 @@
|
|
|
1
|
-
# Forge v0.
|
|
1
|
+
# Forge v0.5.0
|
|
2
2
|
|
|
3
|
-
Released: 2026-03-
|
|
3
|
+
Released: 2026-03-28
|
|
4
4
|
|
|
5
|
-
## Changes since v0.4.
|
|
5
|
+
## Changes since v0.4.16
|
|
6
6
|
|
|
7
7
|
### Features
|
|
8
|
-
- feat:
|
|
9
|
-
- feat:
|
|
10
|
-
-
|
|
11
|
-
- feat:
|
|
8
|
+
- feat: watch actions (log/analyze/approve) + config UI
|
|
9
|
+
- feat: agent watch — autonomous periodic monitoring
|
|
10
|
+
- feat: Workspace — multi-agent terminal view per project
|
|
11
|
+
- feat: requires-driven scheduling for delivery engine
|
|
12
|
+
- feat: standardized envelope format + request/response audit trail
|
|
13
|
+
- feat: visible data contracts on flow editor — artifact names on edges and nodes
|
|
14
|
+
- feat: ReactFlow-based delivery role editor — drag & connect agent topology
|
|
15
|
+
- feat: customizable delivery roles — users compose agent phases from presets
|
|
16
|
+
- feat: show all 4 agent panels in 2x2 grid with SVG flow arrows
|
|
17
|
+
- feat: Delivery Workspace — multi-agent orchestrated software delivery
|
|
18
|
+
- feat: conversation terminal view with inline input and data flow
|
|
19
|
+
- feat: Conversation Mode — multi-agent dialogue with graph view and live logs
|
|
20
|
+
- feat: Pipeline editor node edit modal has Agent + Mode selectors
|
|
21
|
+
- feat: Pipeline UI shows agent per node
|
|
22
|
+
- feat: per-doc-root agent config (Settings + Docs toolbar)
|
|
23
|
+
- feat: Telegram default agent + Docs agent config
|
|
24
|
+
- feat: TTY support for agents that need terminal (e.g. Codex)
|
|
25
|
+
- feat: per-agent skip permissions flag with presets
|
|
26
|
+
- feat: Telegram agent support — @agent syntax + /agents command
|
|
27
|
+
- feat: agent selection for Pipeline, Mobile, Help
|
|
28
|
+
- feat: per-scene model config for each agent
|
|
29
|
+
- feat: model config moved from global to per-agent
|
|
30
|
+
- feat: task system uses agent adapter + agent selector in NewTaskModal
|
|
31
|
+
- feat: terminal tab agent selection
|
|
32
|
+
- feat: Agents management UI in Settings
|
|
33
|
+
- feat: multi-agent foundation — registry, adapters, API
|
|
12
34
|
|
|
13
35
|
### Bug Fixes
|
|
14
|
-
- fix:
|
|
15
|
-
- fix:
|
|
16
|
-
- fix:
|
|
17
|
-
- fix:
|
|
36
|
+
- fix: tolerate Next.js 16 _global-error prerender bug in build
|
|
37
|
+
- fix: Log panel reads from persistent logs.jsonl + clear logs button
|
|
38
|
+
- fix: restart watch loop when agent config is updated
|
|
39
|
+
- fix: watch directory picker uses correct tree type and flattens nested dirs
|
|
40
|
+
- fix: send block only when message is still running, not after done
|
|
41
|
+
- fix: block forge-send reply to message sender + skill prompt hint
|
|
42
|
+
- fix: hasRunning check uses worker's current message, not all bus log
|
|
43
|
+
- fix: no extra reply message when processing downstream request
|
|
44
|
+
- fix: smith send API returns messageId for outbox tracking
|
|
45
|
+
- fix: inject FORGE_AGENT_ID/WORKSPACE_ID/PORT into manual terminal env
|
|
46
|
+
- fix: forge skills use $FORGE_AGENT_ID instead of hardcoded 'unknown'
|
|
47
|
+
- fix: deduplicate bus_message SSE events by message ID
|
|
48
|
+
- fix: emit done before markMessageDone so causedBy can read messageId
|
|
49
|
+
- fix: remove notifyDownstreamForRevalidation + prevent multiple running messages
|
|
50
|
+
- fix: abort_message no longer errors on already-aborted messages
|
|
51
|
+
- fix: getAllAgentStates returns worker state with entry mode override
|
|
52
|
+
- fix: manual stays in mode field, displayed as purple 'manual' on node
|
|
53
|
+
- fix: show 'manual' task status when agent is in manual mode
|
|
54
|
+
- fix: close terminal uses close_terminal action instead of reset
|
|
55
|
+
- fix: restartAgentDaemon recreates worker after resetAgent kills it
|
|
56
|
+
- fix: parseBusMarkers re-scanning entire history causes message loops
|
|
57
|
+
- fix: restartAgentDaemon aligned with simplified setManualMode
|
|
58
|
+
- fix: manual mode shows down because worker async cleanup overrides state
|
|
59
|
+
- fix: buffered wake prevents lost messages in daemon loop
|
|
60
|
+
- fix: simplify retry — reset original message to pending, no emit
|
|
61
|
+
- fix: retry creates new message, preserves original for history
|
|
62
|
+
- fix: message retry causing duplicate execution
|
|
63
|
+
- fix: PTY spawn in ESM — use createRequire for node-pty
|
|
64
|
+
- fix: TTY detection for codex profiles + clear agent cache on terminal open
|
|
65
|
+
- fix: project terminal dialog loads sessions from claude-sessions API
|
|
66
|
+
- fix: bypass GitHub CDN cache on skills sync
|
|
67
|
+
- fix: merge tags from info.json during v2 registry sync
|
|
68
|
+
- fix: enable allowProposedApi for search decorations
|
|
69
|
+
- fix: profile env/model propagation across all terminal launch paths
|
|
70
|
+
- fix: saveAgentConfig preserves profile fields (base/env/model/type)
|
|
71
|
+
- fix: sessions API uses orch.projectPath, ESM imports, non-claude compat
|
|
72
|
+
- fix: stricter workDir validation — block .. and sibling dir escape
|
|
73
|
+
- fix: workDir with leading / treated as relative to project, not absolute
|
|
74
|
+
- fix: workDir normalize strips ./ prefix, default to smith label
|
|
75
|
+
- fix: only use claude -c (resume) if existing session exists
|
|
76
|
+
- fix: handle unknown agentId in smith send API
|
|
77
|
+
- fix: whitelist /api/workspace in middleware for forge skill auto-discover
|
|
78
|
+
- fix: install skills as directories with SKILL.md (Claude Code format)
|
|
79
|
+
- fix: use imported resolve instead of require('node:path') in ESM context
|
|
80
|
+
- fix: orchestrator actively manages smith lifecycle in start/stop daemon
|
|
81
|
+
- fix: startDaemon error handling + stopDaemon cleanup
|
|
82
|
+
- fix: close terminal should enter listening, not execute steps
|
|
83
|
+
- fix: workspace terminal uses correct message types from terminal server
|
|
84
|
+
- fix: workspace terminal input + keep alive when switching tabs
|
|
85
|
+
- fix: rewrite WorkspaceView — each agent is a real interactive terminal
|
|
86
|
+
- fix: move ssr:false dynamic import to client wrapper component
|
|
87
|
+
- fix: disable SSR for Dashboard to eliminate hydration mismatch
|
|
88
|
+
- fix: default phases missing _outputArtifactName/_label/_icon metadata
|
|
89
|
+
- fix: data flow arrows based on requires/produces, not sequential order
|
|
90
|
+
- fix: suppress hydration warning from locale/extension mismatch
|
|
91
|
+
- fix: pipeline node shows 'default' instead of 'claude' when no agent set
|
|
92
|
+
- fix: PTY onExit/onData registered once, fixes stuck tasks after cancel
|
|
93
|
+
- fix: auto-kill PTY agents after 15s idle
|
|
94
|
+
- fix: strip ANSI/terminal control codes from PTY agent output
|
|
95
|
+
- fix: retryTask preserves original agent selection
|
|
96
|
+
- fix: use pipe stdin for task spawn, close immediately after
|
|
97
|
+
- fix: non-claude agents no longer fallback to claude or show claude model
|
|
98
|
+
- fix: settings agent config debounced save + unsaved warning on close
|
|
99
|
+
- fix: generic agents use taskFlags from settings, log raw text output
|
|
100
|
+
- fix: only pass model flag to agents that support it, show agent in task
|
|
101
|
+
- fix: settings agent colors match terminal — use API detected status
|
|
102
|
+
- fix: show all configured agents, not just detected ones
|
|
103
|
+
|
|
104
|
+
### Performance
|
|
105
|
+
- perf: watch heartbeat only logs to console, not to files/history
|
|
106
|
+
- perf: watch uses timestamp comparison instead of full snapshot
|
|
107
|
+
|
|
108
|
+
### Refactoring
|
|
109
|
+
- refactor: remove Delivery tab, keep only Workspace
|
|
18
110
|
|
|
19
111
|
### Documentation
|
|
20
|
-
- docs: update
|
|
112
|
+
- docs: update workspace help with message system design
|
|
113
|
+
- docs: add workspace help, update settings/projects/overview docs
|
|
21
114
|
|
|
22
115
|
### Other
|
|
23
|
-
-
|
|
116
|
+
- watch: push heartbeat + alert logs to agent history for Log panel
|
|
117
|
+
- watch: log heartbeat on each check cycle
|
|
118
|
+
- ui: structured watch target builder with directory picker
|
|
119
|
+
- debug: log deleteMessage results for diagnosing message reappearance
|
|
120
|
+
- persist currentMessageId as task trigger identifier
|
|
121
|
+
- inbox: abort all pending button for batch operations
|
|
122
|
+
- inbox/outbox: batch select + delete for completed messages
|
|
123
|
+
- Phase 3: ticket UI, retry limits, ticket API actions
|
|
124
|
+
- Phase 2: causedBy chain + ticket messages + receive rules
|
|
125
|
+
- Phase 1: anti-loop — DAG cycle detection, directional broadcast, disable SEND markers
|
|
126
|
+
- ui: show mode (auto/manual) as separate line on agent node
|
|
127
|
+
- simplify: setManualMode only changes mode, message loop skips manual
|
|
128
|
+
- debug: log when agent message loop skips due to not-listening state
|
|
129
|
+
- skills: auto-loop sync with progress indicator
|
|
130
|
+
- skills: incremental sync — registry fast, info.json in batches
|
|
131
|
+
- terminal: add Ctrl/Cmd+F search in terminal buffer
|
|
132
|
+
- terminal: add WebGL rendering + Unicode 11 support
|
|
133
|
+
- unified terminal launch: resolveTerminalLaunch for both VibeCoding + Workspace
|
|
134
|
+
- settings: agent has profile selector dropdown
|
|
135
|
+
- settings: profiles are global/shared, not per-agent
|
|
136
|
+
- settings: add cliType selector to agent config panel
|
|
137
|
+
- settings: profiles nested inside each agent, not standalone section
|
|
138
|
+
- open_terminal: return cliType + cliCmd from agent registry
|
|
139
|
+
- settings: env var templates per CLI type for profiles
|
|
140
|
+
- agent config: add cliType field (claude-code/codex/aider/generic)
|
|
141
|
+
- vibecoding: profile selector + session picker + env injection for terminal
|
|
142
|
+
- terminal profile: env var injection via export, not settings.json
|
|
143
|
+
- terminal: session picker with recent sessions list
|
|
144
|
+
- terminal: styled launch dialog — New Session / Resume Latest
|
|
145
|
+
- terminal: simple prompt dialog for new/resume before opening
|
|
146
|
+
- terminal: prompt user to choose new session or resume
|
|
147
|
+
- cleanup: simplify resume flag check in FloatingTerminal
|
|
148
|
+
- UI: show resolved workDir path hint below input
|
|
149
|
+
- forge skills: explicit 2-step commands, no env var checks
|
|
150
|
+
- forge skills: inline auto-discover, no separate setup step
|
|
151
|
+
- workDir validation: unique per smith, must be within project, no nesting
|
|
152
|
+
- profile settings: write to smith workDir, not project root
|
|
153
|
+
- profile terminal: apply env/model to .claude/settings.json on open_terminal
|
|
154
|
+
- forge skills: install/update on every forge startup
|
|
155
|
+
- forge skills: auto-discover workspace context with fallback defaults
|
|
156
|
+
- skills: install once in startDaemon, remove per-smith install
|
|
157
|
+
- skills: install to ~/.claude/skills/ globally + fix project deny rules
|
|
158
|
+
- skill installer: auto-fix .claude/settings.json curl permissions
|
|
159
|
+
- forge skills: env var injection + auto-install on smith startup
|
|
160
|
+
- agent profiles: editable profile rows with expand/collapse
|
|
161
|
+
- agent profiles: env vars support for custom CLI configs (e.g., FortiAI)
|
|
162
|
+
- agent profiles UI: settings management + workspace profile selector
|
|
163
|
+
- agent profiles + provider config data layer
|
|
164
|
+
- smith message-driven architecture: independent message loop, inbox management, status simplification
|
|
165
|
+
- multiple agent implementation
|
|
166
|
+
- fix issue
|
|
167
|
+
- fix issue for workspace
|
|
168
|
+
- optmized projects
|
|
169
|
+
- refactoring workspace
|
|
170
|
+
- implement workspace and fix issue
|
|
171
|
+
- implement multiple agents
|
|
172
|
+
- ui: show agent badge on ReactFlow node blocks in pipeline editor
|
|
173
|
+
- simplify: single docs agent instead of per-root
|
|
174
|
+
- ui: remove Docs page agent selector, keep Settings-only config
|
|
175
|
+
- ui: remove leftover model/permissions migration notes from Settings
|
|
176
|
+
- ui: consistent agent colors across terminal and settings
|
|
177
|
+
- ui: agent buttons green=installed, gray=not installed
|
|
178
|
+
- ui: agent buttons ≤3 inline, >3 overflow with dropdown
|
|
179
|
+
- ui: agent buttons inline with project name in new tab modal
|
|
24
180
|
|
|
25
181
|
|
|
26
|
-
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.
|
|
182
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.4.16...v0.5.0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { listAgents, getDefaultAgentId, resolveTerminalLaunch } from '@/lib/agents';
|
|
3
|
+
|
|
4
|
+
export async function GET(req: Request) {
|
|
5
|
+
const url = new URL(req.url);
|
|
6
|
+
const resolve = url.searchParams.get('resolve');
|
|
7
|
+
|
|
8
|
+
// GET /api/agents?resolve=claude → resolve terminal launch info for an agent
|
|
9
|
+
if (resolve) {
|
|
10
|
+
const info = resolveTerminalLaunch(resolve);
|
|
11
|
+
return NextResponse.json(info);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const agents = listAgents();
|
|
15
|
+
const defaultAgent = getDefaultAgentId();
|
|
16
|
+
return NextResponse.json({ agents, defaultAgent });
|
|
17
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
getDelivery, cancelDelivery, deleteDelivery,
|
|
4
|
+
approveDeliveryPhase, rejectDeliveryPhase,
|
|
5
|
+
sendToAgent, retryPhase,
|
|
6
|
+
} from '@/lib/delivery';
|
|
7
|
+
import { listArtifacts, getArtifact } from '@/lib/artifacts';
|
|
8
|
+
import type { PhaseName } from '@/lib/delivery';
|
|
9
|
+
|
|
10
|
+
// GET /api/delivery/:id — get delivery state + artifacts
|
|
11
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
12
|
+
const { id } = await params;
|
|
13
|
+
const delivery = getDelivery(id);
|
|
14
|
+
if (!delivery) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
15
|
+
|
|
16
|
+
const artifacts = listArtifacts(id);
|
|
17
|
+
return NextResponse.json({ ...delivery, artifacts });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// POST /api/delivery/:id — actions
|
|
21
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
22
|
+
const { id } = await params;
|
|
23
|
+
const body = await req.json();
|
|
24
|
+
const { action } = body;
|
|
25
|
+
|
|
26
|
+
if (action === 'cancel') {
|
|
27
|
+
return NextResponse.json({ ok: cancelDelivery(id) });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (action === 'delete') {
|
|
31
|
+
return NextResponse.json({ ok: deleteDelivery(id) });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (action === 'approve') {
|
|
35
|
+
return NextResponse.json({ ok: approveDeliveryPhase(id, body.feedback) });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (action === 'reject') {
|
|
39
|
+
if (!body.feedback) return NextResponse.json({ error: 'feedback required' }, { status: 400 });
|
|
40
|
+
return NextResponse.json({ ok: rejectDeliveryPhase(id, body.feedback) });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (action === 'send') {
|
|
44
|
+
const { phase, message } = body;
|
|
45
|
+
if (!phase || !message) return NextResponse.json({ error: 'phase and message required' }, { status: 400 });
|
|
46
|
+
return NextResponse.json({ ok: sendToAgent(id, phase as PhaseName, message) });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (action === 'retry') {
|
|
50
|
+
const { phase } = body;
|
|
51
|
+
if (!phase) return NextResponse.json({ error: 'phase required' }, { status: 400 });
|
|
52
|
+
return NextResponse.json({ ok: retryPhase(id, phase as PhaseName) });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// DELETE /api/delivery/:id
|
|
59
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
60
|
+
const { id } = await params;
|
|
61
|
+
return NextResponse.json({ ok: deleteDelivery(id) });
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { createDelivery, listDeliveries, ROLE_PRESETS } from '@/lib/delivery';
|
|
3
|
+
|
|
4
|
+
// GET /api/delivery — list deliveries, or get role presets
|
|
5
|
+
export async function GET(req: Request) {
|
|
6
|
+
const { searchParams } = new URL(req.url);
|
|
7
|
+
|
|
8
|
+
if (searchParams.get('type') === 'presets') {
|
|
9
|
+
return NextResponse.json(ROLE_PRESETS);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const deliveries = listDeliveries();
|
|
13
|
+
return NextResponse.json(deliveries);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// POST /api/delivery — create new delivery
|
|
17
|
+
export async function POST(req: Request) {
|
|
18
|
+
const body = await req.json();
|
|
19
|
+
const { title, project, projectPath, prUrl, description, agentId, phases } = body;
|
|
20
|
+
|
|
21
|
+
if (!project || !projectPath) {
|
|
22
|
+
return NextResponse.json({ error: 'project and projectPath required' }, { status: 400 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const delivery = createDelivery({
|
|
27
|
+
title: title || description?.slice(0, 50) || 'Delivery',
|
|
28
|
+
project,
|
|
29
|
+
projectPath,
|
|
30
|
+
prUrl,
|
|
31
|
+
description,
|
|
32
|
+
agentId,
|
|
33
|
+
customPhases: phases, // user-defined phases
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return NextResponse.json(delivery);
|
|
37
|
+
} catch (e: any) {
|
|
38
|
+
return NextResponse.json({ error: e.message }, { status: 500 });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -7,26 +7,32 @@ export const runtime = 'nodejs';
|
|
|
7
7
|
|
|
8
8
|
// POST /api/mobile-chat — send a message to claude and stream response
|
|
9
9
|
export async function POST(req: Request) {
|
|
10
|
-
const { message, projectPath, resume } = await req.json() as {
|
|
10
|
+
const { message, projectPath, resume, agent: agentId } = await req.json() as {
|
|
11
11
|
message: string;
|
|
12
12
|
projectPath: string;
|
|
13
13
|
resume?: boolean;
|
|
14
|
+
agent?: string;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
if (!message || !projectPath) {
|
|
17
18
|
return NextResponse.json({ error: 'message and projectPath required' }, { status: 400 });
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
const
|
|
21
|
+
const { getAgent } = require('@/lib/agents');
|
|
22
|
+
const adapter = getAgent(agentId);
|
|
22
23
|
const projectName = projectPath.split('/').pop() || projectPath;
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
|
|
25
|
+
const spawnOpts = adapter.buildTaskSpawn({
|
|
26
|
+
projectPath,
|
|
27
|
+
prompt: message,
|
|
28
|
+
skipPermissions: true,
|
|
29
|
+
outputFormat: adapter.config.capabilities?.supportsStreamJson ? 'json' : undefined,
|
|
30
|
+
conversationId: resume ? 'last' : undefined,
|
|
31
|
+
});
|
|
26
32
|
|
|
27
|
-
const child = spawn(
|
|
33
|
+
const child = spawn(spawnOpts.cmd, spawnOpts.args, {
|
|
28
34
|
cwd: projectPath,
|
|
29
|
-
env: { ...process.env },
|
|
35
|
+
env: { ...process.env, ...(spawnOpts.env || {}) },
|
|
30
36
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
37
|
});
|
|
32
38
|
|
package/app/api/monitor/route.ts
CHANGED
|
@@ -7,11 +7,13 @@ function run(cmd: string): string {
|
|
|
7
7
|
} catch { return ''; }
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
function countProcess(pattern: string): { count: number; pid: string } {
|
|
10
|
+
function countProcess(pattern: string): { count: number; pid: string; startedAt: string } {
|
|
11
11
|
const out = run(`ps aux | grep '${pattern}' | grep -v grep | head -1`);
|
|
12
12
|
const pid = out ? out.split(/\s+/)[1] || '' : '';
|
|
13
13
|
const count = out ? run(`ps aux | grep '${pattern}' | grep -v grep | wc -l`).trim() : '0';
|
|
14
|
-
|
|
14
|
+
// Get start time from ps
|
|
15
|
+
const startedAt = pid ? run(`ps -o lstart= -p ${pid} 2>/dev/null`).trim() : '';
|
|
16
|
+
return { count: parseInt(count), pid, startedAt };
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export async function GET() {
|
|
@@ -19,6 +21,7 @@ export async function GET() {
|
|
|
19
21
|
const nextjs = countProcess('next-server');
|
|
20
22
|
const terminal = countProcess('terminal-standalone');
|
|
21
23
|
const telegram = countProcess('telegram-standalone');
|
|
24
|
+
const workspace = countProcess('workspace-standalone');
|
|
22
25
|
const tunnel = countProcess('cloudflared tunnel');
|
|
23
26
|
|
|
24
27
|
// Tunnel URL
|
|
@@ -47,10 +50,11 @@ export async function GET() {
|
|
|
47
50
|
|
|
48
51
|
return NextResponse.json({
|
|
49
52
|
processes: {
|
|
50
|
-
nextjs: { running: nextjs.count > 0, pid: nextjs.pid },
|
|
51
|
-
terminal: { running: terminal.count > 0, pid: terminal.pid },
|
|
52
|
-
telegram: { running: telegram.count > 0, pid: telegram.pid },
|
|
53
|
-
|
|
53
|
+
nextjs: { running: nextjs.count > 0, pid: nextjs.pid, startedAt: nextjs.startedAt },
|
|
54
|
+
terminal: { running: terminal.count > 0, pid: terminal.pid, startedAt: terminal.startedAt },
|
|
55
|
+
telegram: { running: telegram.count > 0, pid: telegram.pid, startedAt: telegram.startedAt },
|
|
56
|
+
workspace: { running: workspace.count > 0, pid: workspace.pid, startedAt: workspace.startedAt },
|
|
57
|
+
tunnel: { running: tunnel.count > 0, pid: tunnel.pid, url: tunnelUrl, startedAt: tunnel.startedAt },
|
|
54
58
|
},
|
|
55
59
|
sessions,
|
|
56
60
|
uptime: uptime.replace(/.*up\s+/, '').replace(/,\s+\d+ user.*/, '').trim(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import { getPipeline, cancelPipeline, deletePipeline } from '@/lib/pipeline';
|
|
2
|
+
import { getPipeline, cancelPipeline, deletePipeline, injectConversationMessage } from '@/lib/pipeline';
|
|
3
3
|
|
|
4
4
|
// GET /api/pipelines/:id
|
|
5
5
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
@@ -9,10 +9,11 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
9
9
|
return NextResponse.json(pipeline);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
// POST /api/pipelines/:id — actions (cancel)
|
|
12
|
+
// POST /api/pipelines/:id — actions (cancel, delete, inject)
|
|
13
13
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
14
14
|
const { id } = await params;
|
|
15
|
-
const
|
|
15
|
+
const body = await req.json();
|
|
16
|
+
const { action } = body;
|
|
16
17
|
|
|
17
18
|
if (action === 'cancel') {
|
|
18
19
|
const ok = cancelPipeline(id);
|
|
@@ -24,5 +25,17 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
24
25
|
return NextResponse.json({ ok });
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
// Inject a message into a running conversation
|
|
29
|
+
if (action === 'inject') {
|
|
30
|
+
const { agentId, message } = body;
|
|
31
|
+
if (!agentId || !message) return NextResponse.json({ error: 'agentId and message required' }, { status: 400 });
|
|
32
|
+
try {
|
|
33
|
+
const ok = injectConversationMessage(id, agentId, message);
|
|
34
|
+
return NextResponse.json({ ok });
|
|
35
|
+
} catch (e: any) {
|
|
36
|
+
return NextResponse.json({ error: e.message }, { status: 400 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
|
|
28
41
|
}
|
package/app/api/tasks/route.ts
CHANGED
|
@@ -14,7 +14,7 @@ export async function GET(req: Request) {
|
|
|
14
14
|
|
|
15
15
|
// Create a new task
|
|
16
16
|
export async function POST(req: Request) {
|
|
17
|
-
const { projectName, prompt, priority, newSession, conversationId, scheduledAt, mode, watchConfig } = await req.json();
|
|
17
|
+
const { projectName, prompt, priority, newSession, conversationId, scheduledAt, mode, watchConfig, agent } = await req.json();
|
|
18
18
|
|
|
19
19
|
if (!projectName || !prompt) {
|
|
20
20
|
return NextResponse.json({ error: 'projectName and prompt are required' }, { status: 400 });
|
|
@@ -37,6 +37,7 @@ export async function POST(req: Request) {
|
|
|
37
37
|
scheduledAt: scheduledAt || undefined,
|
|
38
38
|
mode: mode || 'prompt',
|
|
39
39
|
watchConfig: watchConfig || undefined,
|
|
40
|
+
agent: agent || undefined,
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
return NextResponse.json(task);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
const WORKSPACE_PORT = Number(process.env.WORKSPACE_PORT) || 8405;
|
|
4
|
+
const DAEMON_URL = `http://localhost:${WORKSPACE_PORT}`;
|
|
5
|
+
|
|
6
|
+
// Proxy to workspace daemon — Agent operations
|
|
7
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params;
|
|
9
|
+
const body = await req.json();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch(`${DAEMON_URL}/workspace/${id}/agents`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
body: JSON.stringify(body),
|
|
16
|
+
});
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
return NextResponse.json(data, { status: res.status });
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
return NextResponse.json({ error: 'Workspace daemon not available' }, { status: 503 });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Proxy to workspace daemon — Get agent states
|
|
25
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
26
|
+
const { id } = await params;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(`${DAEMON_URL}/workspace/${id}/agents`);
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
return NextResponse.json(data, { status: res.status });
|
|
32
|
+
} catch (err: any) {
|
|
33
|
+
return NextResponse.json({ error: 'Workspace daemon not available' }, { status: 503 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
const WORKSPACE_PORT = Number(process.env.WORKSPACE_PORT) || 8405;
|
|
4
|
+
const DAEMON_URL = `http://localhost:${WORKSPACE_PORT}`;
|
|
5
|
+
|
|
6
|
+
// Proxy to workspace daemon — Memory query
|
|
7
|
+
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id: workspaceId } = await params;
|
|
9
|
+
const url = new URL(req.url);
|
|
10
|
+
const agentId = url.searchParams.get('agentId');
|
|
11
|
+
|
|
12
|
+
if (!agentId) {
|
|
13
|
+
return NextResponse.json({ error: 'agentId required' }, { status: 400 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(`${DAEMON_URL}/workspace/${workspaceId}/memory?agentId=${encodeURIComponent(agentId)}`);
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
return NextResponse.json(data, { status: res.status });
|
|
20
|
+
} catch (err: any) {
|
|
21
|
+
return NextResponse.json({ error: 'Workspace daemon not available' }, { status: 503 });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
const WORKSPACE_PORT = Number(process.env.WORKSPACE_PORT) || 8405;
|
|
4
|
+
const DAEMON_URL = `http://localhost:${WORKSPACE_PORT}`;
|
|
5
|
+
|
|
6
|
+
// Proxy to workspace daemon — Smith API (called by forge skills in terminal)
|
|
7
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params;
|
|
9
|
+
const body = await req.json();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch(`${DAEMON_URL}/workspace/${id}/smith`, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
body: JSON.stringify(body),
|
|
16
|
+
});
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
return NextResponse.json(data, { status: res.status });
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
return NextResponse.json({ error: 'Workspace daemon not available' }, { status: 503 });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const WORKSPACE_PORT = Number(process.env.WORKSPACE_PORT) || 8405;
|
|
2
|
+
const DAEMON_URL = `http://localhost:${WORKSPACE_PORT}`;
|
|
3
|
+
|
|
4
|
+
// SSE relay — proxy daemon's SSE stream to browser
|
|
5
|
+
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
|
+
const { id } = await params;
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const daemonRes = await fetch(`${DAEMON_URL}/workspace/${id}/stream`, {
|
|
10
|
+
signal: req.signal,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (!daemonRes.ok || !daemonRes.body) {
|
|
14
|
+
return new Response(daemonRes.statusText || 'Daemon error', { status: daemonRes.status });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Pipe daemon SSE stream directly to browser
|
|
18
|
+
return new Response(daemonRes.body, {
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'text/event-stream',
|
|
21
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
22
|
+
'Connection': 'keep-alive',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
} catch (err: any) {
|
|
26
|
+
return new Response('Workspace daemon not available', { status: 503 });
|
|
27
|
+
}
|
|
28
|
+
}
|