@metabob/minibob 0.1.2
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/ARCHITECTURE.md +255 -0
- package/CHANGELOG.md +112 -0
- package/README.md +380 -0
- package/bin/minibob.js +36 -0
- package/dist/acp-gossip.d.ts +72 -0
- package/dist/acp-gossip.d.ts.map +1 -0
- package/dist/acp-gossip.js +156 -0
- package/dist/acp-gossip.js.map +1 -0
- package/dist/acp.d.ts +62 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +292 -0
- package/dist/acp.js.map +1 -0
- package/dist/activity.d.ts +157 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +518 -0
- package/dist/activity.js.map +1 -0
- package/dist/agent-runtime.d.ts +104 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/boredom.d.ts +125 -0
- package/dist/boredom.d.ts.map +1 -0
- package/dist/boredom.js +244 -0
- package/dist/boredom.js.map +1 -0
- package/dist/cli/acp-server.d.ts +23 -0
- package/dist/cli/acp-server.d.ts.map +1 -0
- package/dist/cli/burrow.d.ts +26 -0
- package/dist/cli/burrow.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts +22 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/goal.d.ts +22 -0
- package/dist/cli/goal.d.ts.map +1 -0
- package/dist/cli/index.d.ts +47 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/instance-registry.d.ts +78 -0
- package/dist/cli/instance-registry.d.ts.map +1 -0
- package/dist/cli/observe.d.ts +35 -0
- package/dist/cli/observe.d.ts.map +1 -0
- package/dist/cli/vessel.d.ts +14 -0
- package/dist/cli/vessel.d.ts.map +1 -0
- package/dist/composition-observer.d.ts +96 -0
- package/dist/composition-observer.d.ts.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +128 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/Dockerfile +35 -0
- package/dist/environment.d.ts +72 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +142 -0
- package/dist/environment.js.map +1 -0
- package/dist/goal-processor.d.ts +165 -0
- package/dist/goal-processor.d.ts.map +1 -0
- package/dist/helm/minibob-cluster/Chart.yaml +13 -0
- package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
- package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
- package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
- package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
- package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
- package/dist/helm/minibob-cluster/values-local.yaml +41 -0
- package/dist/helm/minibob-cluster/values-production.yaml +57 -0
- package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
- package/dist/helm/minibob-cluster/values.yaml +127 -0
- package/dist/improviser.d.ts +74 -0
- package/dist/improviser.d.ts.map +1 -0
- package/dist/impulse-filter.d.ts +74 -0
- package/dist/impulse-filter.d.ts.map +1 -0
- package/dist/impulse.d.ts +92 -0
- package/dist/impulse.d.ts.map +1 -0
- package/dist/impulse.js +234 -0
- package/dist/impulse.js.map +1 -0
- package/dist/lib.d.ts +29 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +18561 -0
- package/dist/lib.js.map +98 -0
- package/dist/lifecycle-hooks.d.ts +99 -0
- package/dist/lifecycle-hooks.d.ts.map +1 -0
- package/dist/lifecycle-hooks.js +135 -0
- package/dist/lifecycle-hooks.js.map +1 -0
- package/dist/llm.d.ts +31 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +349 -0
- package/dist/llm.js.map +1 -0
- package/dist/mcp-activity-bridge.d.ts +66 -0
- package/dist/mcp-activity-bridge.d.ts.map +1 -0
- package/dist/mcp-activity-bridge.js +126 -0
- package/dist/mcp-activity-bridge.js.map +1 -0
- package/dist/mcp.d.ts +216 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +292 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory-agent.d.ts +92 -0
- package/dist/memory-agent.d.ts.map +1 -0
- package/dist/memory-agent.js +277 -0
- package/dist/memory-agent.js.map +1 -0
- package/dist/runtime-mapping.d.ts +97 -0
- package/dist/runtime-mapping.d.ts.map +1 -0
- package/dist/search-first-executor.d.ts +113 -0
- package/dist/search-first-executor.d.ts.map +1 -0
- package/dist/session.d.ts +48 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/template-extractor.d.ts +9 -0
- package/dist/template-extractor.d.ts.map +1 -0
- package/dist/template-generator.d.ts +12 -0
- package/dist/template-generator.d.ts.map +1 -0
- package/dist/tools.d.ts +58 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +771 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +503 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/understanding/analyzer.d.ts +55 -0
- package/dist/understanding/analyzer.d.ts.map +1 -0
- package/dist/understanding/explorer.d.ts +73 -0
- package/dist/understanding/explorer.d.ts.map +1 -0
- package/dist/understanding/index.d.ts +7 -0
- package/dist/understanding/index.d.ts.map +1 -0
- package/dist/understanding/types.d.ts +136 -0
- package/dist/understanding/types.d.ts.map +1 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +106 -0
- package/dist/validation.js.map +1 -0
- package/dist/vessel-bootstrap.d.ts +190 -0
- package/dist/vessel-bootstrap.d.ts.map +1 -0
- package/dist/vessel-registry.d.ts +229 -0
- package/dist/vessel-registry.d.ts.map +1 -0
- package/index.ts +1329 -0
- package/package.json +54 -0
- package/src/acp-gossip.ts +193 -0
- package/src/acp.ts +362 -0
- package/src/activity.ts +1464 -0
- package/src/agent-runtime.ts +365 -0
- package/src/boredom.ts +423 -0
- package/src/cli/acp-server.ts +377 -0
- package/src/cli/burrow.ts +896 -0
- package/src/cli/doctor.ts +526 -0
- package/src/cli/goal.ts +224 -0
- package/src/cli/index.ts +147 -0
- package/src/cli/instance-registry.ts +271 -0
- package/src/cli/observe.ts +682 -0
- package/src/cli/vessel.ts +287 -0
- package/src/components/SystemOverview.tsx +331 -0
- package/src/composition-observer.ts +449 -0
- package/src/config.ts +172 -0
- package/src/environment.ts +167 -0
- package/src/goal-processor.ts +654 -0
- package/src/improviser.ts +591 -0
- package/src/impulse-filter.ts +273 -0
- package/src/impulse.ts +311 -0
- package/src/lib.ts +147 -0
- package/src/lifecycle-hooks.ts +181 -0
- package/src/llm.ts +434 -0
- package/src/mcp-activity-bridge.ts +158 -0
- package/src/mcp.ts +747 -0
- package/src/memory-agent.ts +316 -0
- package/src/runtime-mapping.ts +527 -0
- package/src/search-first-executor.ts +666 -0
- package/src/session.ts +141 -0
- package/src/template-extractor.ts +256 -0
- package/src/template-generator.ts +130 -0
- package/src/tools.ts +924 -0
- package/src/types.ts +497 -0
- package/src/understanding/analyzer.ts +354 -0
- package/src/understanding/explorer.ts +488 -0
- package/src/understanding/index.ts +27 -0
- package/src/understanding/types.ts +153 -0
- package/src/validation.ts +125 -0
- package/src/vessel-bootstrap.ts +440 -0
- package/src/vessel-registry.ts +621 -0
- package/templates/core/edit-file.json +85 -0
- package/templates/understanding/diagnose-problem.json +32 -0
- package/templates/understanding/explore-codebase-v2.json +57 -0
- package/templates/understanding/explore-codebase.json +37 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP Server Command
|
|
3
|
+
*
|
|
4
|
+
* Launch standalone ACP server for external agent integration.
|
|
5
|
+
*
|
|
6
|
+
* Modes:
|
|
7
|
+
* - stdio: JSON-RPC over stdin/stdout (for editor extensions, CLI piping)
|
|
8
|
+
* - http: HTTP server on specified port (default)
|
|
9
|
+
* - websocket: WebSocket server for streaming
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { parseArgs, formatHelp, printInfo, exitWithError } from './index'
|
|
13
|
+
import { loadConfig } from '../config'
|
|
14
|
+
import { ACPSession, handleACPRequest, type ACPServerConfig } from '../acp'
|
|
15
|
+
import { createToolHandlers, getAllToolDefinitions, type ToolHandler } from '../tools'
|
|
16
|
+
import type { ACPMessage } from '../types'
|
|
17
|
+
|
|
18
|
+
export interface ACPServerOptions {
|
|
19
|
+
mode?: 'stdio' | 'http' | 'websocket'
|
|
20
|
+
port?: number
|
|
21
|
+
noTools?: boolean
|
|
22
|
+
tools?: string[]
|
|
23
|
+
denyTools?: string[]
|
|
24
|
+
system?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run ACP server command
|
|
29
|
+
*/
|
|
30
|
+
export async function launchACPServer(args: string[]): Promise<void> {
|
|
31
|
+
const { flags, values } = parseArgs(args)
|
|
32
|
+
|
|
33
|
+
// Show help
|
|
34
|
+
if (flags.help || flags.h) {
|
|
35
|
+
console.log(formatHelp('acp', 'Launch standalone ACP server', [
|
|
36
|
+
{ flag: '--mode <mode>', description: 'stdio, http, websocket', default: 'http' },
|
|
37
|
+
{ flag: '--port <port>', description: 'Port for http/websocket mode', default: '8080' },
|
|
38
|
+
{ flag: '--no-tools', description: 'Disable tool access (prompt only)' },
|
|
39
|
+
{ flag: '--tools <list>', description: 'Comma-separated whitelist of allowed tools' },
|
|
40
|
+
{ flag: '--deny-tools <list>', description: 'Comma-separated blacklist of denied tools' },
|
|
41
|
+
{ flag: '--system <file>', description: 'Custom system prompt from file' },
|
|
42
|
+
{ flag: '--help, -h', description: 'Show this help message' },
|
|
43
|
+
]))
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const options: ACPServerOptions = {
|
|
48
|
+
mode: (values.mode as 'stdio' | 'http' | 'websocket') || 'http',
|
|
49
|
+
port: values.port ? parseInt(values.port) : 8080,
|
|
50
|
+
noTools: flags['no-tools'],
|
|
51
|
+
tools: values.tools?.split(',').map((t) => t.trim()),
|
|
52
|
+
denyTools: values['deny-tools']?.split(',').map((t) => t.trim()),
|
|
53
|
+
system: values.system,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Load configuration
|
|
57
|
+
const config = await loadConfig()
|
|
58
|
+
|
|
59
|
+
// Load custom system prompt if specified
|
|
60
|
+
let systemPrompt: string | undefined
|
|
61
|
+
if (options.system) {
|
|
62
|
+
try {
|
|
63
|
+
systemPrompt = await Bun.file(options.system).text()
|
|
64
|
+
} catch {
|
|
65
|
+
exitWithError(`Could not read system prompt file: ${options.system}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const acpConfig: ACPServerConfig = {
|
|
70
|
+
provider: config.provider,
|
|
71
|
+
apiKey: config.apiKey,
|
|
72
|
+
model: config.model,
|
|
73
|
+
workingDirectory: config.workingDirectory,
|
|
74
|
+
systemPrompt,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Filter tools based on options
|
|
78
|
+
const toolFilter = createToolFilter(options)
|
|
79
|
+
|
|
80
|
+
switch (options.mode) {
|
|
81
|
+
case 'stdio':
|
|
82
|
+
await runStdioServer(acpConfig, toolFilter)
|
|
83
|
+
break
|
|
84
|
+
case 'websocket':
|
|
85
|
+
await runWebSocketServer(acpConfig, options.port!, toolFilter)
|
|
86
|
+
break
|
|
87
|
+
case 'http':
|
|
88
|
+
default:
|
|
89
|
+
await runHttpServer(acpConfig, options.port!, toolFilter)
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create tool filter based on options
|
|
96
|
+
*/
|
|
97
|
+
function createToolFilter(options: ACPServerOptions): (name: string) => boolean {
|
|
98
|
+
if (options.noTools) {
|
|
99
|
+
return () => false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (name: string) => {
|
|
103
|
+
// Check whitelist
|
|
104
|
+
if (options.tools && options.tools.length > 0) {
|
|
105
|
+
return options.tools.includes(name)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check blacklist
|
|
109
|
+
if (options.denyTools && options.denyTools.length > 0) {
|
|
110
|
+
return !options.denyTools.includes(name)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Run stdio mode server
|
|
119
|
+
*/
|
|
120
|
+
async function runStdioServer(
|
|
121
|
+
config: ACPServerConfig,
|
|
122
|
+
toolFilter: (name: string) => boolean
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
printInfo('ACP server running in stdio mode')
|
|
125
|
+
printInfo('Send JSON messages to stdin, receive responses on stdout')
|
|
126
|
+
|
|
127
|
+
const sessions = new Map<string, ACPSession>()
|
|
128
|
+
|
|
129
|
+
// Read lines from stdin
|
|
130
|
+
const reader = Bun.stdin.stream().getReader()
|
|
131
|
+
const decoder = new TextDecoder()
|
|
132
|
+
let buffer = ''
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
while (true) {
|
|
136
|
+
const { done, value } = await reader.read()
|
|
137
|
+
if (done) break
|
|
138
|
+
|
|
139
|
+
buffer += decoder.decode(value, { stream: true })
|
|
140
|
+
|
|
141
|
+
// Process complete lines
|
|
142
|
+
const lines = buffer.split('\n')
|
|
143
|
+
buffer = lines.pop() || ''
|
|
144
|
+
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
if (!line.trim()) continue
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const message = JSON.parse(line) as ACPMessage & { sessionId?: string }
|
|
150
|
+
|
|
151
|
+
// Get or create session
|
|
152
|
+
const sessionId = message.sessionId || `stdio_${Date.now()}`
|
|
153
|
+
let session = sessions.get(sessionId)
|
|
154
|
+
if (!session) {
|
|
155
|
+
session = new ACPSession(config, sessionId)
|
|
156
|
+
sessions.set(sessionId, session)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Handle message
|
|
160
|
+
const responses = await session.handleMessage(message)
|
|
161
|
+
|
|
162
|
+
// Write responses to stdout
|
|
163
|
+
for (const response of responses) {
|
|
164
|
+
const responseWithSession = { ...response, sessionId }
|
|
165
|
+
console.log(JSON.stringify(responseWithSession))
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log(
|
|
169
|
+
JSON.stringify({
|
|
170
|
+
type: 'error',
|
|
171
|
+
error: error instanceof Error ? error.message : String(error),
|
|
172
|
+
})
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
reader.releaseLock()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Run HTTP mode server
|
|
184
|
+
*/
|
|
185
|
+
async function runHttpServer(
|
|
186
|
+
config: ACPServerConfig,
|
|
187
|
+
port: number,
|
|
188
|
+
toolFilter: (name: string) => boolean
|
|
189
|
+
): Promise<void> {
|
|
190
|
+
const server = Bun.serve({
|
|
191
|
+
port,
|
|
192
|
+
hostname: '0.0.0.0',
|
|
193
|
+
|
|
194
|
+
fetch(request) {
|
|
195
|
+
const url = new URL(request.url)
|
|
196
|
+
|
|
197
|
+
// Health check
|
|
198
|
+
if (url.pathname === '/health') {
|
|
199
|
+
return new Response(JSON.stringify({ status: 'ok', mode: 'acp-server' }), {
|
|
200
|
+
headers: { 'Content-Type': 'application/json' },
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ACP endpoint
|
|
205
|
+
if (url.pathname === '/acp' && request.method === 'POST') {
|
|
206
|
+
return handleACPRequest(config, request)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// List available tools
|
|
210
|
+
if (url.pathname === '/tools' && request.method === 'GET') {
|
|
211
|
+
const allTools = getAllToolDefinitions()
|
|
212
|
+
const filteredTools = allTools.filter((t) => toolFilter(t.name))
|
|
213
|
+
return new Response(JSON.stringify({ tools: filteredTools }), {
|
|
214
|
+
headers: { 'Content-Type': 'application/json' },
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return new Response(
|
|
219
|
+
JSON.stringify({
|
|
220
|
+
error: 'Not found',
|
|
221
|
+
endpoints: ['GET /health', 'POST /acp', 'GET /tools'],
|
|
222
|
+
}),
|
|
223
|
+
{
|
|
224
|
+
status: 404,
|
|
225
|
+
headers: { 'Content-Type': 'application/json' },
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
console.log(`\nACP server running at http://0.0.0.0:${port}`)
|
|
232
|
+
console.log('\nEndpoints:')
|
|
233
|
+
console.log(' GET /health - Health check')
|
|
234
|
+
console.log(' POST /acp - ACP protocol handler')
|
|
235
|
+
console.log(' GET /tools - List available tools')
|
|
236
|
+
console.log('\nExample request:')
|
|
237
|
+
console.log(` curl -X POST http://localhost:${port}/acp \\`)
|
|
238
|
+
console.log(` -H "Content-Type: application/json" \\`)
|
|
239
|
+
console.log(` -d '{"type":"prompt","messages":[{"role":"user","content":"Hello!"}]}'`)
|
|
240
|
+
console.log('\nPress Ctrl+C to stop')
|
|
241
|
+
|
|
242
|
+
// Handle graceful shutdown
|
|
243
|
+
process.on('SIGINT', () => {
|
|
244
|
+
console.log('\n\nShutting down...')
|
|
245
|
+
server.stop()
|
|
246
|
+
process.exit(0)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
process.on('SIGTERM', () => {
|
|
250
|
+
console.log('\n\nShutting down...')
|
|
251
|
+
server.stop()
|
|
252
|
+
process.exit(0)
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Run WebSocket mode server
|
|
258
|
+
*/
|
|
259
|
+
async function runWebSocketServer(
|
|
260
|
+
config: ACPServerConfig,
|
|
261
|
+
port: number,
|
|
262
|
+
toolFilter: (name: string) => boolean
|
|
263
|
+
): Promise<void> {
|
|
264
|
+
const sessions = new Map<WebSocket, ACPSession>()
|
|
265
|
+
|
|
266
|
+
const server = Bun.serve({
|
|
267
|
+
port,
|
|
268
|
+
hostname: '0.0.0.0',
|
|
269
|
+
|
|
270
|
+
fetch(request, server) {
|
|
271
|
+
const url = new URL(request.url)
|
|
272
|
+
|
|
273
|
+
// Health check
|
|
274
|
+
if (url.pathname === '/health') {
|
|
275
|
+
return new Response(JSON.stringify({ status: 'ok', mode: 'acp-websocket' }), {
|
|
276
|
+
headers: { 'Content-Type': 'application/json' },
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// WebSocket upgrade
|
|
281
|
+
if (url.pathname === '/acp/stream') {
|
|
282
|
+
const upgraded = server.upgrade(request)
|
|
283
|
+
if (!upgraded) {
|
|
284
|
+
return new Response('WebSocket upgrade failed', { status: 400 })
|
|
285
|
+
}
|
|
286
|
+
return undefined
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return new Response(
|
|
290
|
+
JSON.stringify({
|
|
291
|
+
error: 'Not found',
|
|
292
|
+
endpoints: ['GET /health', 'WS /acp/stream'],
|
|
293
|
+
}),
|
|
294
|
+
{
|
|
295
|
+
status: 404,
|
|
296
|
+
headers: { 'Content-Type': 'application/json' },
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
websocket: {
|
|
302
|
+
open(ws) {
|
|
303
|
+
const sessionId = `ws_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
304
|
+
const session = new ACPSession(config, sessionId)
|
|
305
|
+
sessions.set(ws, session)
|
|
306
|
+
|
|
307
|
+
// Send hello
|
|
308
|
+
ws.send(
|
|
309
|
+
JSON.stringify({
|
|
310
|
+
type: 'hello',
|
|
311
|
+
sessionId,
|
|
312
|
+
version: '1.0',
|
|
313
|
+
capabilities: ['prompt', 'tool_call'],
|
|
314
|
+
})
|
|
315
|
+
)
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
async message(ws, message) {
|
|
319
|
+
const session = sessions.get(ws)
|
|
320
|
+
if (!session) {
|
|
321
|
+
ws.send(JSON.stringify({ type: 'error', error: 'Session not found' }))
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const data = JSON.parse(
|
|
327
|
+
typeof message === 'string' ? message : message.toString()
|
|
328
|
+
) as ACPMessage
|
|
329
|
+
|
|
330
|
+
const responses = await session.handleMessage(data)
|
|
331
|
+
for (const response of responses) {
|
|
332
|
+
ws.send(JSON.stringify(response))
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
335
|
+
ws.send(
|
|
336
|
+
JSON.stringify({
|
|
337
|
+
type: 'error',
|
|
338
|
+
error: error instanceof Error ? error.message : String(error),
|
|
339
|
+
})
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
close(ws) {
|
|
345
|
+
sessions.delete(ws)
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
console.log(`\nACP WebSocket server running at ws://0.0.0.0:${port}/acp/stream`)
|
|
351
|
+
console.log('\nEndpoints:')
|
|
352
|
+
console.log(' GET /health - Health check')
|
|
353
|
+
console.log(' WS /acp/stream - WebSocket ACP endpoint')
|
|
354
|
+
console.log('\nPress Ctrl+C to stop')
|
|
355
|
+
|
|
356
|
+
// Handle graceful shutdown
|
|
357
|
+
process.on('SIGINT', () => {
|
|
358
|
+
console.log('\n\nShutting down...')
|
|
359
|
+
server.stop()
|
|
360
|
+
process.exit(0)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
process.on('SIGTERM', () => {
|
|
364
|
+
console.log('\n\nShutting down...')
|
|
365
|
+
server.stop()
|
|
366
|
+
process.exit(0)
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// CLI entry point
|
|
371
|
+
if (import.meta.main) {
|
|
372
|
+
const args = process.argv.slice(2)
|
|
373
|
+
launchACPServer(args).catch((error) => {
|
|
374
|
+
console.error('ACP server failed:', error)
|
|
375
|
+
process.exit(1)
|
|
376
|
+
})
|
|
377
|
+
}
|