@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.
- package/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/RELEASE_NOTES.md +170 -13
- 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 +23 -4
- package/check-forge-status.sh +9 -0
- package/cli/mw.ts +2 -2
- 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 +12 -4
- package/components/DocsViewer.tsx +10 -2
- package/components/HelpTerminal.tsx +13 -8
- 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 +34 -8
- package/components/TaskBoard.tsx +3 -0
- package/components/WebTerminal.tsx +259 -45
- package/components/WorkspaceTree.tsx +221 -0
- package/components/WorkspaceView.tsx +2224 -0
- package/docs/LOCAL-DEPLOY.md +15 -15
- 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/cloudflared.ts +1 -1
- 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 +8 -2
- package/lib/help-docs/01-settings.md +159 -2
- package/lib/help-docs/05-pipelines.md +95 -6
- 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 +62 -12
- 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/telegram-standalone.ts +1 -1
- package/lib/terminal-server.ts +2 -2
- package/lib/terminal-standalone.ts +1 -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/next-env.d.ts +1 -1
- package/package.json +5 -2
- package/src/config/index.ts +13 -2
- package/src/core/db/database.ts +1 -0
- 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
|
package/lib/help-docs/CLAUDE.md
CHANGED
|
@@ -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
|
|
49
|
-
function
|
|
48
|
+
/** Auto-detect agent binaries */
|
|
49
|
+
function autoDetectAgents() {
|
|
50
50
|
try {
|
|
51
51
|
const settings = loadSettings();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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 ||
|
|
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) ||
|
|
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
|
+
}
|