@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.
Files changed (93) hide show
  1. package/README.md +27 -2
  2. package/RELEASE_NOTES.md +21 -14
  3. package/app/api/agents/route.ts +17 -0
  4. package/app/api/delivery/[id]/route.ts +62 -0
  5. package/app/api/delivery/route.ts +40 -0
  6. package/app/api/mobile-chat/route.ts +13 -7
  7. package/app/api/monitor/route.ts +10 -6
  8. package/app/api/pipelines/[id]/route.ts +16 -3
  9. package/app/api/tasks/route.ts +2 -1
  10. package/app/api/workspace/[id]/agents/route.ts +35 -0
  11. package/app/api/workspace/[id]/memory/route.ts +23 -0
  12. package/app/api/workspace/[id]/smith/route.ts +22 -0
  13. package/app/api/workspace/[id]/stream/route.ts +28 -0
  14. package/app/api/workspace/route.ts +100 -0
  15. package/app/global-error.tsx +10 -4
  16. package/app/icon.ico +0 -0
  17. package/app/layout.tsx +2 -2
  18. package/app/login/LoginForm.tsx +96 -0
  19. package/app/login/page.tsx +7 -98
  20. package/app/page.tsx +2 -2
  21. package/bin/forge-server.mjs +13 -1
  22. package/check-forge-status.sh +9 -0
  23. package/components/ConversationEditor.tsx +411 -0
  24. package/components/ConversationGraphView.tsx +347 -0
  25. package/components/ConversationTerminalView.tsx +303 -0
  26. package/components/Dashboard.tsx +36 -39
  27. package/components/DashboardWrapper.tsx +9 -0
  28. package/components/DeliveryFlowEditor.tsx +491 -0
  29. package/components/DeliveryList.tsx +230 -0
  30. package/components/DeliveryWorkspace.tsx +589 -0
  31. package/components/DocTerminal.tsx +10 -2
  32. package/components/DocsViewer.tsx +10 -2
  33. package/components/HelpTerminal.tsx +11 -6
  34. package/components/InlinePipelineView.tsx +111 -0
  35. package/components/MobileView.tsx +20 -0
  36. package/components/MonitorPanel.tsx +9 -4
  37. package/components/NewTaskModal.tsx +32 -0
  38. package/components/PipelineEditor.tsx +49 -6
  39. package/components/PipelineView.tsx +482 -64
  40. package/components/ProjectDetail.tsx +314 -56
  41. package/components/ProjectManager.tsx +49 -4
  42. package/components/SessionView.tsx +27 -13
  43. package/components/SettingsModal.tsx +790 -124
  44. package/components/SkillsPanel.tsx +31 -8
  45. package/components/TaskBoard.tsx +3 -0
  46. package/components/WebTerminal.tsx +257 -43
  47. package/components/WorkspaceTree.tsx +221 -0
  48. package/components/WorkspaceView.tsx +2245 -0
  49. package/install.sh +2 -2
  50. package/lib/agents/claude-adapter.ts +104 -0
  51. package/lib/agents/generic-adapter.ts +64 -0
  52. package/lib/agents/index.ts +242 -0
  53. package/lib/agents/types.ts +70 -0
  54. package/lib/artifacts.ts +106 -0
  55. package/lib/delivery.ts +787 -0
  56. package/lib/forge-skills/forge-inbox.md +37 -0
  57. package/lib/forge-skills/forge-send.md +40 -0
  58. package/lib/forge-skills/forge-status.md +32 -0
  59. package/lib/forge-skills/forge-workspace-sync.md +37 -0
  60. package/lib/help-docs/00-overview.md +7 -1
  61. package/lib/help-docs/01-settings.md +159 -2
  62. package/lib/help-docs/05-pipelines.md +89 -0
  63. package/lib/help-docs/07-projects.md +35 -1
  64. package/lib/help-docs/11-workspace.md +254 -0
  65. package/lib/help-docs/CLAUDE.md +7 -2
  66. package/lib/init.ts +60 -10
  67. package/lib/pipeline.ts +537 -1
  68. package/lib/settings.ts +115 -22
  69. package/lib/skills.ts +249 -372
  70. package/lib/task-manager.ts +113 -33
  71. package/lib/telegram-bot.ts +33 -1
  72. package/lib/workspace/__tests__/state-machine.test.ts +388 -0
  73. package/lib/workspace/__tests__/workspace.test.ts +311 -0
  74. package/lib/workspace/agent-bus.ts +416 -0
  75. package/lib/workspace/agent-worker.ts +667 -0
  76. package/lib/workspace/backends/api-backend.ts +262 -0
  77. package/lib/workspace/backends/cli-backend.ts +479 -0
  78. package/lib/workspace/index.ts +82 -0
  79. package/lib/workspace/manager.ts +136 -0
  80. package/lib/workspace/orchestrator.ts +1914 -0
  81. package/lib/workspace/persistence.ts +310 -0
  82. package/lib/workspace/presets.ts +170 -0
  83. package/lib/workspace/skill-installer.ts +188 -0
  84. package/lib/workspace/smith-memory.ts +498 -0
  85. package/lib/workspace/types.ts +231 -0
  86. package/lib/workspace/watch-manager.ts +288 -0
  87. package/lib/workspace-standalone.ts +814 -0
  88. package/middleware.ts +1 -0
  89. package/next-env.d.ts +1 -1
  90. package/package.json +4 -1
  91. package/src/config/index.ts +12 -1
  92. package/src/core/db/database.ts +1 -0
  93. package/start.sh +7 -0
@@ -0,0 +1,221 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+
5
+ // ─── Types ───────────────────────────────────────────────
6
+
7
+ interface Project {
8
+ name: string;
9
+ path: string;
10
+ }
11
+
12
+ interface AgentInfo {
13
+ id: string;
14
+ label: string;
15
+ icon: string;
16
+ type?: 'agent' | 'input';
17
+ status: string;
18
+ currentStep?: string;
19
+ }
20
+
21
+ interface WorkspaceSummary {
22
+ id: string;
23
+ projectName: string;
24
+ projectPath: string;
25
+ agents: AgentInfo[];
26
+ }
27
+
28
+ // ─── Status indicators ───────────────────────────────────
29
+
30
+ const STATUS_DOT: Record<string, { char: string; color: string }> = {
31
+ idle: { char: '○', color: 'text-gray-500' },
32
+ running: { char: '◐', color: 'text-green-400' },
33
+ paused: { char: '⏸', color: 'text-yellow-400' },
34
+ waiting_approval: { char: '⏳', color: 'text-yellow-400' },
35
+ done: { char: '●', color: 'text-blue-400' },
36
+ failed: { char: '✕', color: 'text-red-400' },
37
+ interrupted: { char: '◌', color: 'text-gray-400' },
38
+ };
39
+
40
+ // ─── Component ───────────────────────────────────────────
41
+
42
+ export default function WorkspaceTree({
43
+ activeProjectPath,
44
+ onSelectProject,
45
+ onSelectAgent,
46
+ onCreateWorkspace,
47
+ }: {
48
+ activeProjectPath: string | null;
49
+ onSelectProject: (project: Project) => void;
50
+ onSelectAgent: (agentId: string) => void;
51
+ onCreateWorkspace: () => void;
52
+ }) {
53
+ const [projects, setProjects] = useState<Project[]>([]);
54
+ const [workspaces, setWorkspaces] = useState<Map<string, WorkspaceSummary>>(new Map());
55
+ const [expanded, setExpanded] = useState<Set<string>>(new Set());
56
+
57
+ // Fetch projects
58
+ useEffect(() => {
59
+ fetch('/api/projects').then(r => r.json())
60
+ .then((data: any[]) => {
61
+ const projs = data.map(p => ({ name: p.name, path: p.path }));
62
+ setProjects(projs);
63
+ // Auto-expand active project
64
+ if (activeProjectPath) {
65
+ setExpanded(prev => new Set([...prev, activeProjectPath]));
66
+ }
67
+ })
68
+ .catch(() => {});
69
+ }, [activeProjectPath]);
70
+
71
+ // Fetch workspace summaries for expanded projects
72
+ useEffect(() => {
73
+ for (const path of expanded) {
74
+ if (workspaces.has(path)) continue;
75
+ fetch(`/api/workspace?projectPath=${encodeURIComponent(path)}`)
76
+ .then(r => r.json())
77
+ .then(ws => {
78
+ if (!ws?.id) return;
79
+ // Fetch agent states
80
+ fetch(`/api/workspace/${ws.id}/agents`).then(r => r.json())
81
+ .then(data => {
82
+ const agents: AgentInfo[] = (data.agents || []).map((a: any) => {
83
+ const state = data.states?.[a.id] || {};
84
+ return {
85
+ id: a.id,
86
+ label: a.label,
87
+ icon: a.icon,
88
+ type: a.type,
89
+ status: state.status || 'idle',
90
+ currentStep: state.currentStep !== undefined
91
+ ? a.steps?.[state.currentStep]?.label
92
+ : undefined,
93
+ };
94
+ });
95
+ setWorkspaces(prev => new Map([...prev, [path, {
96
+ id: ws.id,
97
+ projectName: ws.projectName,
98
+ projectPath: ws.projectPath,
99
+ agents,
100
+ }]]));
101
+ })
102
+ .catch(() => {});
103
+ })
104
+ .catch(() => {
105
+ // No workspace for this project
106
+ setWorkspaces(prev => new Map([...prev, [path, {
107
+ id: '',
108
+ projectName: '',
109
+ projectPath: path,
110
+ agents: [],
111
+ }]]));
112
+ });
113
+ }
114
+ }, [expanded]); // eslint-disable-line react-hooks/exhaustive-deps
115
+
116
+ const toggleExpand = (path: string) => {
117
+ setExpanded(prev => {
118
+ const next = new Set(prev);
119
+ if (next.has(path)) next.delete(path); else next.add(path);
120
+ return next;
121
+ });
122
+ };
123
+
124
+ return (
125
+ <div className="flex flex-col h-full overflow-hidden">
126
+ {/* Header */}
127
+ <div className="flex items-center gap-2 px-3 py-2 border-b border-[var(--border)] shrink-0">
128
+ <span className="text-xs font-bold text-[var(--text-primary)]">Workspace</span>
129
+ <button onClick={onCreateWorkspace}
130
+ className="text-[8px] px-1.5 py-0.5 rounded border border-[var(--border)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] ml-auto">
131
+ +
132
+ </button>
133
+ </div>
134
+
135
+ {/* Tree */}
136
+ <div className="flex-1 overflow-auto py-1">
137
+ {projects.length === 0 && (
138
+ <div className="text-[10px] text-[var(--text-secondary)] text-center mt-4">No projects</div>
139
+ )}
140
+ {projects.map(project => {
141
+ const isExpanded = expanded.has(project.path);
142
+ const isActive = project.path === activeProjectPath;
143
+ const ws = workspaces.get(project.path);
144
+
145
+ return (
146
+ <div key={project.path}>
147
+ {/* Project row */}
148
+ <div
149
+ className={`flex items-center gap-1.5 px-2 py-1 cursor-pointer text-[11px] hover:bg-[var(--bg-secondary)] ${
150
+ isActive ? 'bg-[var(--bg-secondary)] text-[var(--text-primary)]' : 'text-[var(--text-secondary)]'
151
+ }`}
152
+ onClick={() => {
153
+ toggleExpand(project.path);
154
+ onSelectProject(project);
155
+ }}
156
+ >
157
+ <span className="text-[8px] w-3 text-center shrink-0">{isExpanded ? '▾' : '▸'}</span>
158
+ <span className="text-[10px]">📂</span>
159
+ <span className="truncate flex-1">{project.name}</span>
160
+ {ws && ws.agents.length > 0 && (
161
+ <span className="text-[8px] text-[var(--text-secondary)]">{ws.agents.length}</span>
162
+ )}
163
+ </div>
164
+
165
+ {/* Agents */}
166
+ {isExpanded && ws && ws.agents.length > 0 && (
167
+ <div className="ml-4">
168
+ {ws.agents.map(agent => {
169
+ const dot = STATUS_DOT[agent.status] || STATUS_DOT.idle;
170
+ return (
171
+ <div
172
+ key={agent.id}
173
+ className="flex items-center gap-1.5 px-2 py-0.5 cursor-pointer text-[10px] hover:bg-[var(--bg-secondary)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
174
+ onClick={(e) => {
175
+ e.stopPropagation();
176
+ onSelectProject(project);
177
+ setTimeout(() => onSelectAgent(agent.id), 100);
178
+ }}
179
+ >
180
+ <span className="text-[9px]">{agent.icon}</span>
181
+ <span className="truncate flex-1">{agent.label}</span>
182
+ <span className={`text-[8px] ${dot.color}`} title={agent.status}>
183
+ {dot.char}
184
+ </span>
185
+ {agent.status === 'running' && agent.currentStep && (
186
+ <span className="text-[7px] text-green-400/60 truncate max-w-16">{agent.currentStep}</span>
187
+ )}
188
+ </div>
189
+ );
190
+ })}
191
+ </div>
192
+ )}
193
+
194
+ {/* No workspace yet */}
195
+ {isExpanded && ws && ws.agents.length === 0 && ws.id === '' && (
196
+ <div className="ml-6 py-1">
197
+ <button
198
+ onClick={(e) => {
199
+ e.stopPropagation();
200
+ onSelectProject(project);
201
+ }}
202
+ className="text-[9px] text-[var(--text-secondary)] hover:text-[var(--accent)]"
203
+ >
204
+ + Create workspace
205
+ </button>
206
+ </div>
207
+ )}
208
+
209
+ {/* Empty workspace */}
210
+ {isExpanded && ws && ws.agents.length === 0 && ws.id !== '' && (
211
+ <div className="ml-6 py-1 text-[9px] text-[var(--text-secondary)]">
212
+ No agents yet
213
+ </div>
214
+ )}
215
+ </div>
216
+ );
217
+ })}
218
+ </div>
219
+ </div>
220
+ );
221
+ }