@aion0/forge 0.4.16 → 0.5.1
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 +27 -2
- package/RELEASE_NOTES.md +21 -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 +2245 -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 +254 -0
- package/lib/help-docs/CLAUDE.md +7 -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 +1914 -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 +814 -0
- package/middleware.ts +1 -0
- package/next-env.d.ts +1 -1
- 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
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, lazy, Suspense } from 'react';
|
|
4
|
+
import type { PhaseOutput } from './DeliveryFlowEditor';
|
|
5
|
+
|
|
6
|
+
const DeliveryFlowEditor = lazy(() => import('./DeliveryFlowEditor'));
|
|
7
|
+
|
|
8
|
+
interface DeliveryItem {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
status: string;
|
|
12
|
+
input: { project: string; prUrl?: string; description?: string };
|
|
13
|
+
currentPhaseIndex: number;
|
|
14
|
+
phases: { name: string; status: string; _label?: string; _icon?: string }[];
|
|
15
|
+
createdAt: string;
|
|
16
|
+
completedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface RolePreset {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
role: string;
|
|
24
|
+
inputArtifactTypes: string[];
|
|
25
|
+
outputArtifactName: string;
|
|
26
|
+
outputArtifactType: string;
|
|
27
|
+
waitForHuman?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const PHASE_ICONS: Record<string, string> = {
|
|
31
|
+
analyze: '📋', implement: '🔨', test: '🧪', review: '🔍',
|
|
32
|
+
pm: '📋', engineer: '🔨', qa: '🧪', reviewer: '🔍',
|
|
33
|
+
devops: '🚀', security: '🔒', docs: '📝',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default function DeliveryList({ projects, onOpen }: {
|
|
37
|
+
projects: { name: string; path: string }[];
|
|
38
|
+
onOpen: (id: string) => void;
|
|
39
|
+
}) {
|
|
40
|
+
const [deliveries, setDeliveries] = useState<DeliveryItem[]>([]);
|
|
41
|
+
const [presets, setPresets] = useState<RolePreset[]>([]);
|
|
42
|
+
const [agents, setAgents] = useState<{ id: string; name: string; detected?: boolean }[]>([]);
|
|
43
|
+
const [showCreate, setShowCreate] = useState(false);
|
|
44
|
+
const [creating, setCreating] = useState(false);
|
|
45
|
+
const [form, setForm] = useState({ project: '', title: '', description: '', prUrl: '' });
|
|
46
|
+
const [flowPhases, setFlowPhases] = useState<PhaseOutput[]>([]);
|
|
47
|
+
|
|
48
|
+
const fetchList = useCallback(async () => {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch('/api/delivery');
|
|
51
|
+
if (res.ok) setDeliveries(await res.json());
|
|
52
|
+
} catch {}
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
fetchList();
|
|
57
|
+
const timer = setInterval(fetchList, 5000);
|
|
58
|
+
return () => clearInterval(timer);
|
|
59
|
+
}, [fetchList]);
|
|
60
|
+
|
|
61
|
+
// Load presets + agents when create form opens
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!showCreate) return;
|
|
64
|
+
fetch('/api/delivery?type=presets').then(r => r.json()).then(setPresets).catch(() => {});
|
|
65
|
+
fetch('/api/agents').then(r => r.json()).then(d => setAgents(d.agents || [])).catch(() => {});
|
|
66
|
+
}, [showCreate]);
|
|
67
|
+
|
|
68
|
+
const handleCreate = async () => {
|
|
69
|
+
if (!form.project || !form.description || flowPhases.length === 0) return;
|
|
70
|
+
setCreating(true);
|
|
71
|
+
try {
|
|
72
|
+
const proj = projects.find(p => p.name === form.project);
|
|
73
|
+
if (!proj) { alert('Project not found'); setCreating(false); return; }
|
|
74
|
+
const res = await fetch('/api/delivery', {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/json' },
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
title: form.title || form.description.slice(0, 50),
|
|
79
|
+
project: proj.name,
|
|
80
|
+
projectPath: proj.path,
|
|
81
|
+
description: form.description,
|
|
82
|
+
prUrl: form.prUrl || undefined,
|
|
83
|
+
phases: flowPhases.map(p => ({
|
|
84
|
+
name: p.name,
|
|
85
|
+
label: p.label,
|
|
86
|
+
icon: p.icon,
|
|
87
|
+
role: p.role,
|
|
88
|
+
agentId: p.agentId,
|
|
89
|
+
inputArtifactTypes: p.inputArtifactTypes,
|
|
90
|
+
outputArtifactName: p.outputArtifactName,
|
|
91
|
+
outputArtifactType: p.outputArtifactType,
|
|
92
|
+
waitForHuman: p.waitForHuman,
|
|
93
|
+
requires: p.requires,
|
|
94
|
+
produces: p.produces,
|
|
95
|
+
})),
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
const data = await res.json();
|
|
99
|
+
if (data.id) onOpen(data.id);
|
|
100
|
+
} catch { alert('Failed to create delivery'); }
|
|
101
|
+
setCreating(false);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleDelete = async (id: string) => {
|
|
105
|
+
if (!confirm('Delete this delivery?')) return;
|
|
106
|
+
await fetch(`/api/delivery/${id}`, { method: 'DELETE' });
|
|
107
|
+
fetchList();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className="flex-1 flex flex-col" style={{ background: '#0a0a1a' }}>
|
|
112
|
+
{/* Header */}
|
|
113
|
+
<div className="px-4 py-3 border-b border-[#2a2a3a] flex items-center gap-3">
|
|
114
|
+
<span className="text-sm font-bold text-white">Delivery</span>
|
|
115
|
+
<span className="text-[9px] text-gray-500">Multi-agent software delivery</span>
|
|
116
|
+
<button
|
|
117
|
+
onClick={() => setShowCreate(v => !v)}
|
|
118
|
+
className="text-[10px] px-3 py-1 bg-[var(--accent)] text-white rounded hover:opacity-90 ml-auto"
|
|
119
|
+
>+ New Delivery</button>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Create form */}
|
|
123
|
+
{showCreate && (
|
|
124
|
+
<div className="border-b border-[#2a2a3a] bg-[#0d1117]">
|
|
125
|
+
<div className="px-4 py-3 space-y-3">
|
|
126
|
+
{/* Basic info */}
|
|
127
|
+
<div className="grid grid-cols-2 gap-2">
|
|
128
|
+
<select
|
|
129
|
+
value={form.project}
|
|
130
|
+
onChange={e => setForm(f => ({ ...f, project: e.target.value }))}
|
|
131
|
+
className="text-xs bg-[#161b22] border border-[#30363d] rounded px-2 py-1.5 text-gray-300"
|
|
132
|
+
>
|
|
133
|
+
<option value="">Select project...</option>
|
|
134
|
+
{projects.map(p => <option key={p.name} value={p.name}>{p.name}</option>)}
|
|
135
|
+
</select>
|
|
136
|
+
<input
|
|
137
|
+
value={form.title}
|
|
138
|
+
onChange={e => setForm(f => ({ ...f, title: e.target.value }))}
|
|
139
|
+
placeholder="Title (optional)"
|
|
140
|
+
className="text-xs bg-[#161b22] border border-[#30363d] rounded px-2 py-1.5 text-gray-300 focus:outline-none"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
<textarea
|
|
144
|
+
value={form.description}
|
|
145
|
+
onChange={e => setForm(f => ({ ...f, description: e.target.value }))}
|
|
146
|
+
placeholder="Task description — what needs to be built or fixed..."
|
|
147
|
+
className="w-full text-xs bg-[#161b22] border border-[#30363d] rounded px-2 py-1.5 text-gray-300 resize-none focus:outline-none"
|
|
148
|
+
rows={2}
|
|
149
|
+
/>
|
|
150
|
+
<input
|
|
151
|
+
value={form.prUrl}
|
|
152
|
+
onChange={e => setForm(f => ({ ...f, prUrl: e.target.value }))}
|
|
153
|
+
placeholder="PR URL (optional)"
|
|
154
|
+
className="w-full text-xs bg-[#161b22] border border-[#30363d] rounded px-2 py-1.5 text-gray-300 focus:outline-none"
|
|
155
|
+
/>
|
|
156
|
+
|
|
157
|
+
{/* Flow editor — drag & connect agent roles */}
|
|
158
|
+
{presets.length > 0 && (
|
|
159
|
+
<Suspense fallback={<div className="h-[350px] flex items-center justify-center text-[9px] text-gray-500">Loading editor...</div>}>
|
|
160
|
+
<DeliveryFlowEditor
|
|
161
|
+
presets={presets}
|
|
162
|
+
agents={agents}
|
|
163
|
+
onChange={setFlowPhases}
|
|
164
|
+
/>
|
|
165
|
+
</Suspense>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
{/* Actions */}
|
|
169
|
+
<div className="flex gap-2">
|
|
170
|
+
<button onClick={handleCreate} disabled={creating || !form.project || !form.description || flowPhases.length === 0}
|
|
171
|
+
className="text-[10px] px-3 py-1.5 bg-green-600 text-white rounded hover:opacity-90 disabled:opacity-50">
|
|
172
|
+
{creating ? 'Creating...' : `Start Delivery (${flowPhases.length} agents)`}
|
|
173
|
+
</button>
|
|
174
|
+
<button onClick={() => setShowCreate(false)} className="text-[10px] text-gray-400 hover:text-white">Cancel</button>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* List */}
|
|
181
|
+
<div className="flex-1 overflow-y-auto">
|
|
182
|
+
{deliveries.length === 0 && !showCreate ? (
|
|
183
|
+
<div className="flex flex-col items-center justify-center h-full text-center gap-2">
|
|
184
|
+
<span className="text-3xl">🚀</span>
|
|
185
|
+
<div className="text-sm text-gray-400">No deliveries yet</div>
|
|
186
|
+
<div className="text-[10px] text-gray-600">Create one to start a multi-agent delivery pipeline</div>
|
|
187
|
+
</div>
|
|
188
|
+
) : (
|
|
189
|
+
deliveries.map(d => (
|
|
190
|
+
<button
|
|
191
|
+
key={d.id}
|
|
192
|
+
onClick={() => onOpen(d.id)}
|
|
193
|
+
className="w-full text-left px-4 py-3 border-b border-[#2a2a3a] hover:bg-[#161b22] transition-colors"
|
|
194
|
+
>
|
|
195
|
+
<div className="flex items-center gap-2">
|
|
196
|
+
<span className={`text-[9px] px-1.5 py-0.5 rounded ${
|
|
197
|
+
d.status === 'running' ? 'bg-yellow-500/20 text-yellow-400' :
|
|
198
|
+
d.status === 'done' ? 'bg-green-500/20 text-green-400' :
|
|
199
|
+
d.status === 'failed' ? 'bg-red-500/20 text-red-400' :
|
|
200
|
+
'bg-gray-500/20 text-gray-400'
|
|
201
|
+
}`}>{d.status}</span>
|
|
202
|
+
<span className="text-[11px] font-semibold text-white">{d.title}</span>
|
|
203
|
+
<span className="text-[9px] text-gray-500">{d.input.project}</span>
|
|
204
|
+
<div className="flex gap-0.5 ml-auto">
|
|
205
|
+
{d.phases.map(p => (
|
|
206
|
+
<span key={p.name} className={`text-[8px] ${
|
|
207
|
+
p.status === 'done' ? 'opacity-100' :
|
|
208
|
+
p.status === 'running' || p.status === 'waiting_human' ? 'opacity-100 animate-pulse' :
|
|
209
|
+
'opacity-30'
|
|
210
|
+
}`}>{p._icon || PHASE_ICONS[p.name] || '⚙'}</span>
|
|
211
|
+
))}
|
|
212
|
+
</div>
|
|
213
|
+
<span className="text-[8px] text-gray-600 ml-2">
|
|
214
|
+
{new Date(d.createdAt).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
|
|
215
|
+
</span>
|
|
216
|
+
<button
|
|
217
|
+
onClick={(e) => { e.stopPropagation(); handleDelete(d.id); }}
|
|
218
|
+
className="text-[8px] text-gray-600 hover:text-red-400 ml-1"
|
|
219
|
+
>×</button>
|
|
220
|
+
</div>
|
|
221
|
+
{d.input.description && (
|
|
222
|
+
<div className="text-[9px] text-gray-500 mt-1 truncate">{d.input.description}</div>
|
|
223
|
+
)}
|
|
224
|
+
</button>
|
|
225
|
+
))
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
}
|