@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,204 @@
1
+ # Workspace (Forge Smiths)
2
+
3
+ ## Overview
4
+
5
+ Workspace is a multi-agent orchestration system. You define a team of **Smiths** (agents) with roles, dependencies, and steps. Smiths run as long-lived daemons, communicate via a message bus, and can be controlled manually or automatically.
6
+
7
+ ## Concepts
8
+
9
+ | Term | Description |
10
+ |------|-------------|
11
+ | **Smith** | A long-running agent instance in the workspace |
12
+ | **Agent Profile** | A reusable configuration (CLI type + env vars + model) that can be assigned to any smith |
13
+ | **Message Bus** | Inter-agent communication system with two message categories |
14
+ | **Input Node** | User-provided requirements node (append-only history) |
15
+ | **Daemon** | Background execution loop that keeps smiths alive and consuming messages |
16
+
17
+ ## Three-Layer State Model
18
+
19
+ Each smith has three independent status layers displayed on the node:
20
+
21
+ | Layer | Values | Description |
22
+ |-------|--------|-------------|
23
+ | **Smith Status** | `down` / `active` | Whether the daemon process is running |
24
+ | **Mode** | `auto` / `manual` | `auto` = daemon-driven, `manual` = user in terminal (purple) |
25
+ | **Task Status** | `idle` / `running` / `done` / `failed` | Current work status |
26
+
27
+ - **Mode controls message consumption**: `manual` pauses inbox processing (same as `running`)
28
+ - **Smith Status controls the daemon loop**: `down` stops the message loop entirely
29
+ - **Task Status tracks work**: unaffected by mode changes
30
+
31
+ ## Dependencies (DAG)
32
+
33
+ Dependencies must form a **directed acyclic graph** (DAG). Circular dependencies are rejected when adding or editing agents.
34
+
35
+ ```
36
+ Input → PM → Engineer → QA → Reviewer
37
+ ```
38
+
39
+ - Upstream agents complete first, then broadcast to downstream
40
+ - Each agent knows its upstream (dependsOn) and the system prevents cycles
41
+
42
+ ## Message System
43
+
44
+ ### Two Message Categories
45
+
46
+ | Category | Direction | Behavior | Use Case |
47
+ |----------|-----------|----------|----------|
48
+ | **Notification** | Follows DAG (upstream → downstream) | Auto-broadcast on completion, downstream discards reverse notifications | "I'm done, here's what I did" |
49
+ | **Ticket** | Any direction (ignores DAG) | 1-to-1, independent lifecycle, retry limits | Bug reports, fix requests |
50
+
51
+ ### Notification Messages (Default)
52
+
53
+ When a smith completes:
54
+ - If it was processing an **upstream** message → broadcast to all downstream agents
55
+ - If it was processing a **downstream** message → no broadcast, just mark the message done (sender checks outbox status)
56
+ - Each message carries a `causedBy` field tracing which inbox message triggered it
57
+
58
+ ### Ticket Messages
59
+
60
+ - Created via `create_ticket` API or forge skills
61
+ - Have their own lifecycle: `open → in_progress → fixed → verified → closed`
62
+ - Retry limit (default 3), exceeding marks ticket as failed
63
+ - Not affected by DAG direction — any agent can ticket any agent
64
+
65
+ ### CausedBy Chain
66
+
67
+ Every outgoing message carries `causedBy` linking to the inbox message that triggered it:
68
+
69
+ ```json
70
+ {
71
+ "from": "qa-123",
72
+ "to": "reviewer-456",
73
+ "action": "upstream_complete",
74
+ "causedBy": {
75
+ "messageId": "msg-789",
76
+ "from": "engineer-111",
77
+ "to": "qa-123"
78
+ }
79
+ }
80
+ ```
81
+
82
+ This enables:
83
+ - **Loop prevention**: Notifications from downstream are silently discarded
84
+ - **Outbox tracking**: Sender can verify their message was processed
85
+ - **Audit trail**: Every message traces back to its trigger
86
+
87
+ ### Message Receive Rules
88
+
89
+ When a message arrives at an agent's inbox:
90
+
91
+ 1. **Tickets** → always accepted (with retry limit check)
92
+ 2. **Notification with causedBy matching own outbox** → accepted (response to my request)
93
+ 3. **Notification from downstream agent** → silently discarded (prevents reverse flow)
94
+ 4. **Notification from upstream or no causedBy** → accepted (normal DAG flow)
95
+
96
+ ### Message Status Flow
97
+
98
+ `pending` → `running` → `done` / `failed`
99
+
100
+ - Only one message processed at a time per agent
101
+ - `currentMessageId` persisted in agent state for crash recovery
102
+
103
+ ## Manual Mode
104
+
105
+ Click the **⌨️** button on any smith to open a terminal:
106
+ - Mode switches to `manual` (purple indicator on node)
107
+ - Inbox message processing pauses (messages stay pending)
108
+ - A tmux session opens with CLI + profile env + forge env vars (`FORGE_AGENT_ID`, `FORGE_WORKSPACE_ID`, `FORGE_PORT`)
109
+ - Forge Skills auto-installed for inter-agent communication
110
+ - Close terminal → mode returns to `auto`, pending messages resume processing
111
+
112
+ ### Forge Skills in Terminal
113
+
114
+ | Skill | Description |
115
+ |-------|-------------|
116
+ | `/forge-send` | Send a message to another smith (blocked if replying to current sender — use for NEW issues only) |
117
+ | `/forge-inbox` | Check incoming messages |
118
+ | `/forge-status` | Check all smiths' status |
119
+ | `/forge-workspace-sync` | Sync progress back to workspace |
120
+
121
+ **Note**: When processing a message from another agent, do NOT use `/forge-send` to reply — the system auto-delivers results via `markMessageDone`. Only use `/forge-send` for new issues to other agents.
122
+
123
+ ## Inbox Management
124
+
125
+ Each smith has an inbox panel with two tabs:
126
+
127
+ ### Inbox Tab
128
+ - Messages received from other smiths
129
+ - Status badges: pending (yellow), running (blue), done (green), failed (red)
130
+ - Ticket messages have purple border + TICKET badge + lifecycle status
131
+ - CausedBy trace shows which agent triggered the message
132
+
133
+ ### Outbox Tab
134
+ - Messages sent by this smith
135
+ - Track delivery status and responses
136
+
137
+ ### Batch Operations
138
+ - **Select all completed** → batch delete done/failed messages
139
+ - **Abort all pending (N)** → cancel all pending messages at once
140
+ - Checkbox selection on individual done/failed messages
141
+
142
+ ## Controls
143
+
144
+ | Action | Description |
145
+ |--------|-------------|
146
+ | **Start** | Launch daemon, begin executing all agents |
147
+ | **Stop** | Stop daemon, kill all workers |
148
+ | **Pause** | Pause a specific smith (stops consuming messages) |
149
+ | **Resume** | Resume a paused smith |
150
+ | **Retry** | Retry a failed smith |
151
+ | **Reset** | Reset smith to idle, clear history |
152
+ | **Open Terminal** | Switch to manual mode, open floating terminal |
153
+ | **Close Terminal** | Return to auto mode, resume message processing |
154
+
155
+ ## Workspace API
156
+
157
+ ```bash
158
+ # List workspaces
159
+ curl http://localhost:8403/api/workspace
160
+
161
+ # Get workspace state
162
+ curl http://localhost:8403/api/workspace/<id>
163
+
164
+ # Agent operations
165
+ curl -X POST http://localhost:8403/api/workspace/<id>/agents \
166
+ -H 'Content-Type: application/json' \
167
+ -d '{"action":"start"}' # start daemon
168
+ -d '{"action":"stop"}' # stop daemon
169
+ -d '{"action":"run", "agentId":"engineer-123"}' # run one smith
170
+ -d '{"action":"open_terminal", "agentId":"engineer-123"}' # manual mode
171
+ -d '{"action":"close_terminal", "agentId":"engineer-123"}' # back to auto
172
+ -d '{"action":"reset", "agentId":"engineer-123"}' # reset to idle
173
+
174
+ # Smith API (via workspace daemon)
175
+ curl -X POST http://localhost:8403/api/workspace/<id>/smith \
176
+ -H 'Content-Type: application/json' \
177
+ -d '{"action":"send","agentId":"$ID","to":"QA","msgAction":"review","content":"Please check"}'
178
+ -d '{"action":"inbox","agentId":"$ID"}'
179
+ -d '{"action":"status","agentId":"$ID"}'
180
+ -d '{"action":"create_ticket","agentId":"$FROM","targetId":"$TO","content":"Bug found"}'
181
+ -d '{"action":"update_ticket","messageId":"$ID","ticketStatus":"fixed"}'
182
+
183
+ # Stream real-time events (SSE)
184
+ curl http://localhost:8403/api/workspace/<id>/stream
185
+ ```
186
+
187
+ ## Persistence
188
+
189
+ - Workspace state: `~/.forge/workspaces/<id>/state.json`
190
+ - Auto-saved every 10 seconds
191
+ - Atomic writes (temp file → rename) for crash safety
192
+ - Synchronous save on daemon shutdown
193
+ - `currentMessageId` persisted per agent for crash recovery
194
+
195
+ ## Tips
196
+
197
+ 1. **Dependencies must be a DAG** — no circular dependencies allowed
198
+ 2. **Start with Input nodes** — define requirements before adding agent smiths
199
+ 3. **Use profiles** for agents that need custom API endpoints or models
200
+ 4. **Notifications flow downstream** — upstream agents won't receive downstream broadcasts
201
+ 5. **Use tickets for bugs** — tickets ignore DAG direction, have retry limits
202
+ 6. **Open Terminal** for manual intervention — mode switches to manual, inbox pauses
203
+ 7. **Check Inbox** when a smith is stuck — it may have unprocessed messages
204
+ 8. **Batch delete** completed messages to keep inbox clean
@@ -25,6 +25,7 @@ Your job is to answer user questions about Forge features, configuration, and tr
25
25
  | `08-rules.md` | CLAUDE.md templates and rule injection |
26
26
  | `09-issue-autofix.md` | GitHub issue auto-fix pipeline |
27
27
  | `10-troubleshooting.md` | Common issues and solutions |
28
+ | `11-workspace.md` | Workspace (Forge Smiths) — multi-agent orchestration, daemon, message bus, profiles |
28
29
 
29
30
  ## Matching questions to docs
30
31
 
@@ -33,9 +34,11 @@ Your job is to answer user questions about Forge features, configuration, and tr
33
34
  - Telegram/notification → `02-telegram.md`
34
35
  - Tunnel/remote/cloudflare → `03-tunnel.md`
35
36
  - Task/background/queue → `04-tasks.md`
36
- - Settings/config → `01-settings.md`
37
+ - Settings/config/agent/profile/provider → `01-settings.md`
37
38
  - Install/start/update → `00-overview.md`
38
39
  - Error/bug/crash → `10-troubleshooting.md`
39
40
  - Skill/marketplace → `06-skills.md`
40
- - Project/favorite → `07-projects.md`
41
+ - Project/favorite/terminal → `07-projects.md`
41
42
  - Rules/CLAUDE.md/template → `08-rules.md`
43
+ - Workspace/smith/daemon/multi-agent/bus/message → `11-workspace.md`
44
+ - Agent profile/env/model/cliType → `01-settings.md` + `11-workspace.md`
package/lib/init.ts CHANGED
@@ -45,18 +45,25 @@ function migrateSecrets() {
45
45
  }
46
46
  }
47
47
 
48
- /** Auto-detect claude binary path if not configured */
49
- function autoDetectClaude() {
48
+ /** Auto-detect agent binaries */
49
+ function autoDetectAgents() {
50
50
  try {
51
51
  const settings = loadSettings();
52
- if (settings.claudePath) return; // already configured
53
- const { execSync } = require('node:child_process');
54
- const path = execSync('which claude', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
55
- if (path) {
56
- settings.claudePath = path;
57
- saveSettings(settings);
58
- console.log(`[init] Auto-detected claude: ${path}`);
52
+ // Backward compat: detect claude if not configured
53
+ if (!settings.claudePath) {
54
+ const { execSync } = require('node:child_process');
55
+ try {
56
+ const path = execSync('which claude', { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
57
+ if (path) {
58
+ settings.claudePath = path;
59
+ saveSettings(settings);
60
+ console.log(`[init] Auto-detected claude: ${path}`);
61
+ }
62
+ } catch {}
59
63
  }
64
+ // Detect all agents
65
+ const { autoDetectAgents: detect } = require('./agents');
66
+ detect();
60
67
  } catch {}
61
68
  }
62
69
 
@@ -80,7 +87,14 @@ export function ensureInitialized() {
80
87
  } catch {}
81
88
 
82
89
  // Auto-detect claude path if not configured
83
- autoDetectClaude();
90
+ autoDetectAgents();
91
+
92
+ // Install/update forge skills to ~/.claude/skills/ on every startup
93
+ try {
94
+ const { installForgeSkills } = require('./workspace/skill-installer');
95
+ installForgeSkills('', '', '', Number(process.env.PORT) || 8403);
96
+ console.log('[init] Forge skills installed/updated');
97
+ } catch {}
84
98
 
85
99
  // Sync help docs + CLAUDE.md to data dir on startup
86
100
  try {
@@ -148,6 +162,7 @@ export function ensureInitialized() {
148
162
  startTelegramBot(); // registers task event listener only
149
163
  startTerminalProcess();
150
164
  startTelegramProcess(); // spawns telegram-standalone
165
+ startWorkspaceProcess(); // spawns workspace-standalone
151
166
 
152
167
  const settings = loadSettings();
153
168
  if (settings.tunnelAutoStart) {
@@ -182,7 +197,7 @@ function startTelegramProcess() {
182
197
  const script = join(process.cwd(), 'lib', 'telegram-standalone.ts');
183
198
  telegramChild = spawn('npx', ['tsx', script], {
184
199
  stdio: ['ignore', 'inherit', 'inherit'],
185
- env: { ...process.env, PORT: String(process.env.PORT || 3000) },
200
+ env: { ...process.env, PORT: String(process.env.PORT || 8403) },
186
201
  detached: false,
187
202
  });
188
203
  telegramChild.on('exit', () => { telegramChild = null; });
@@ -194,7 +209,7 @@ let terminalChild: ReturnType<typeof spawn> | null = null;
194
209
  function startTerminalProcess() {
195
210
  if (terminalChild) return;
196
211
 
197
- const termPort = Number(process.env.TERMINAL_PORT) || 3001;
212
+ const termPort = Number(process.env.TERMINAL_PORT) || 8404;
198
213
 
199
214
  const net = require('node:net');
200
215
  const tester = net.createServer();
@@ -214,3 +229,38 @@ function startTerminalProcess() {
214
229
  });
215
230
  tester.listen(termPort);
216
231
  }
232
+
233
+ let workspaceChild: ReturnType<typeof spawn> | null = null;
234
+
235
+ function startWorkspaceProcess() {
236
+ if (workspaceChild) return;
237
+
238
+ const wsPort = Number(process.env.WORKSPACE_PORT) || 8405;
239
+
240
+ const net = require('node:net');
241
+ const tester = net.createServer();
242
+ tester.once('error', () => {
243
+ // Port in use — kill stale process and retry after 2s
244
+ console.log(`[workspace] Port ${wsPort} in use, killing stale process...`);
245
+ try { require('node:child_process').execSync(`lsof -ti:${wsPort} | xargs kill -9 2>/dev/null`, { timeout: 3000 }); } catch {}
246
+ setTimeout(() => {
247
+ if (!workspaceChild) launchWorkspaceDaemon();
248
+ }, 2000);
249
+ });
250
+ tester.once('listening', () => {
251
+ tester.close();
252
+ launchWorkspaceDaemon();
253
+ });
254
+ tester.listen(wsPort);
255
+ }
256
+
257
+ function launchWorkspaceDaemon() {
258
+ const script = join(process.cwd(), 'lib', 'workspace-standalone.ts');
259
+ workspaceChild = spawn('npx', ['tsx', script], {
260
+ stdio: ['ignore', 'inherit', 'inherit'],
261
+ env: { ...process.env },
262
+ detached: false,
263
+ });
264
+ workspaceChild.on('exit', () => { workspaceChild = null; });
265
+ console.log('[workspace] Started daemon (pid:', workspaceChild.pid, ')');
266
+ }