@aion0/forge 0.4.15 → 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.
Files changed (100) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +2 -2
  3. package/RELEASE_NOTES.md +170 -13
  4. package/app/api/agents/route.ts +17 -0
  5. package/app/api/delivery/[id]/route.ts +62 -0
  6. package/app/api/delivery/route.ts +40 -0
  7. package/app/api/mobile-chat/route.ts +13 -7
  8. package/app/api/monitor/route.ts +10 -6
  9. package/app/api/pipelines/[id]/route.ts +16 -3
  10. package/app/api/tasks/route.ts +2 -1
  11. package/app/api/workspace/[id]/agents/route.ts +35 -0
  12. package/app/api/workspace/[id]/memory/route.ts +23 -0
  13. package/app/api/workspace/[id]/smith/route.ts +22 -0
  14. package/app/api/workspace/[id]/stream/route.ts +28 -0
  15. package/app/api/workspace/route.ts +100 -0
  16. package/app/global-error.tsx +10 -4
  17. package/app/icon.ico +0 -0
  18. package/app/layout.tsx +2 -2
  19. package/app/login/LoginForm.tsx +96 -0
  20. package/app/login/page.tsx +7 -98
  21. package/app/page.tsx +2 -2
  22. package/bin/forge-server.mjs +23 -4
  23. package/check-forge-status.sh +9 -0
  24. package/cli/mw.ts +2 -2
  25. package/components/ConversationEditor.tsx +411 -0
  26. package/components/ConversationGraphView.tsx +347 -0
  27. package/components/ConversationTerminalView.tsx +303 -0
  28. package/components/Dashboard.tsx +36 -39
  29. package/components/DashboardWrapper.tsx +9 -0
  30. package/components/DeliveryFlowEditor.tsx +491 -0
  31. package/components/DeliveryList.tsx +230 -0
  32. package/components/DeliveryWorkspace.tsx +589 -0
  33. package/components/DocTerminal.tsx +12 -4
  34. package/components/DocsViewer.tsx +10 -2
  35. package/components/HelpTerminal.tsx +13 -8
  36. package/components/InlinePipelineView.tsx +111 -0
  37. package/components/MobileView.tsx +20 -0
  38. package/components/MonitorPanel.tsx +9 -4
  39. package/components/NewTaskModal.tsx +32 -0
  40. package/components/PipelineEditor.tsx +49 -6
  41. package/components/PipelineView.tsx +482 -64
  42. package/components/ProjectDetail.tsx +314 -56
  43. package/components/ProjectManager.tsx +49 -4
  44. package/components/SessionView.tsx +27 -13
  45. package/components/SettingsModal.tsx +790 -124
  46. package/components/SkillsPanel.tsx +34 -8
  47. package/components/TaskBoard.tsx +3 -0
  48. package/components/WebTerminal.tsx +259 -45
  49. package/components/WorkspaceTree.tsx +221 -0
  50. package/components/WorkspaceView.tsx +2224 -0
  51. package/docs/LOCAL-DEPLOY.md +15 -15
  52. package/install.sh +2 -2
  53. package/lib/agents/claude-adapter.ts +104 -0
  54. package/lib/agents/generic-adapter.ts +64 -0
  55. package/lib/agents/index.ts +242 -0
  56. package/lib/agents/types.ts +70 -0
  57. package/lib/artifacts.ts +106 -0
  58. package/lib/cloudflared.ts +1 -1
  59. package/lib/delivery.ts +787 -0
  60. package/lib/forge-skills/forge-inbox.md +37 -0
  61. package/lib/forge-skills/forge-send.md +40 -0
  62. package/lib/forge-skills/forge-status.md +32 -0
  63. package/lib/forge-skills/forge-workspace-sync.md +37 -0
  64. package/lib/help-docs/00-overview.md +8 -2
  65. package/lib/help-docs/01-settings.md +159 -2
  66. package/lib/help-docs/05-pipelines.md +95 -6
  67. package/lib/help-docs/07-projects.md +35 -1
  68. package/lib/help-docs/11-workspace.md +204 -0
  69. package/lib/help-docs/CLAUDE.md +5 -2
  70. package/lib/init.ts +62 -12
  71. package/lib/pipeline.ts +537 -1
  72. package/lib/settings.ts +115 -22
  73. package/lib/skills.ts +249 -372
  74. package/lib/task-manager.ts +113 -33
  75. package/lib/telegram-bot.ts +33 -1
  76. package/lib/telegram-standalone.ts +1 -1
  77. package/lib/terminal-server.ts +2 -2
  78. package/lib/terminal-standalone.ts +1 -1
  79. package/lib/workspace/__tests__/state-machine.test.ts +388 -0
  80. package/lib/workspace/__tests__/workspace.test.ts +311 -0
  81. package/lib/workspace/agent-bus.ts +416 -0
  82. package/lib/workspace/agent-worker.ts +667 -0
  83. package/lib/workspace/backends/api-backend.ts +262 -0
  84. package/lib/workspace/backends/cli-backend.ts +479 -0
  85. package/lib/workspace/index.ts +82 -0
  86. package/lib/workspace/manager.ts +136 -0
  87. package/lib/workspace/orchestrator.ts +1804 -0
  88. package/lib/workspace/persistence.ts +310 -0
  89. package/lib/workspace/presets.ts +170 -0
  90. package/lib/workspace/skill-installer.ts +188 -0
  91. package/lib/workspace/smith-memory.ts +498 -0
  92. package/lib/workspace/types.ts +231 -0
  93. package/lib/workspace/watch-manager.ts +288 -0
  94. package/lib/workspace-standalone.ts +790 -0
  95. package/middleware.ts +1 -0
  96. package/next-env.d.ts +1 -1
  97. package/package.json +5 -2
  98. package/src/config/index.ts +13 -2
  99. package/src/core/db/database.ts +1 -0
  100. package/start.sh +10 -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
+ }