@capagents/forgeqe 0.0.0-stage → 1.0.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/HOW_TO_USE.md +1639 -0
- package/README.md +338 -2
- package/bin/forgeqe.d.ts +3 -0
- package/bin/forgeqe.js +188 -0
- package/docs/ARCHITECTURE.md +408 -0
- package/docs/security-data-flow.html +1870 -0
- package/docs/user-guide.html +2094 -0
- package/evals/domain/access-role-change.yaml +37 -0
- package/evals/domain/banking-payment-transfer.yaml +41 -0
- package/evals/domain/gdpr-account-erasure.yaml +30 -0
- package/evals/domain/insurance-claim-fnol.yaml +30 -0
- package/evals/domain/insurance-policy-renewal.yaml +37 -0
- package/evals/domain/lending-loan-application.yaml +36 -0
- package/forgeqe-architecture.html +718 -0
- package/forgeqe.example.yaml +203 -0
- package/knowledge/README.md +18 -0
- package/knowledge/business-rules.md +10 -0
- package/knowledge/compliance.md +16 -0
- package/knowledge/environments.md +5 -0
- package/knowledge/glossary.md +5 -0
- package/knowledge/product.md +16 -0
- package/knowledge/roles.md +9 -0
- package/knowledge/test-data.md +7 -0
- package/llms.example.json +51 -0
- package/package.json +63 -4
- package/prompts/README.md +34 -0
- package/prompts/api-scenarios.md +21 -0
- package/prompts/api-tests.md +39 -0
- package/prompts/brain.md +166 -0
- package/prompts/browser-capture.md +7 -0
- package/prompts/browser-case-report.md +7 -0
- package/prompts/browser-checks.md +14 -0
- package/prompts/browser-efficiency.md +7 -0
- package/prompts/browser-error-recovery.md +15 -0
- package/prompts/browser-extra.md +5 -0
- package/prompts/browser-goal-verification.md +13 -0
- package/prompts/browser-skill-select.md +6 -0
- package/prompts/domain-rule.md +13 -0
- package/prompts/eval-judge.md +19 -0
- package/prompts/explore-charters.md +27 -0
- package/prompts/find-tests.md +28 -0
- package/prompts/heal.md +40 -0
- package/prompts/pr-review.md +29 -0
- package/prompts/skill-rule.md +6 -0
- package/prompts/test-agent.md +34 -0
- package/prompts/test-reviewer.md +30 -0
- package/prompts/test-rules.md +28 -0
- package/py/forgeqe_engine/__init__.py +3 -0
- package/py/forgeqe_engine/__main__.py +3 -0
- package/py/forgeqe_engine/brand.py +453 -0
- package/py/forgeqe_engine/brand_assets.py +6 -0
- package/py/forgeqe_engine/browser.py +196 -0
- package/py/forgeqe_engine/capture.py +538 -0
- package/py/forgeqe_engine/checks.py +98 -0
- package/py/forgeqe_engine/cli.py +188 -0
- package/py/forgeqe_engine/diagnostics.py +609 -0
- package/py/forgeqe_engine/llm.py +195 -0
- package/py/forgeqe_engine/logins.py +251 -0
- package/py/forgeqe_engine/loops.py +224 -0
- package/py/forgeqe_engine/memory.py +1004 -0
- package/py/forgeqe_engine/network.py +244 -0
- package/py/forgeqe_engine/prompts.py +20 -0
- package/py/forgeqe_engine/replay.py +240 -0
- package/py/forgeqe_engine/report.py +173 -0
- package/py/forgeqe_engine/service.py +1335 -0
- package/py/forgeqe_engine/settings.py +93 -0
- package/py/forgeqe_engine/skills.py +88 -0
- package/py/forgeqe_engine/summary.py +239 -0
- package/py/forgeqe_engine/urls.py +81 -0
- package/py/forgeqe_engine/workflow_yaml.py +689 -0
- package/skills/dotnet-nunit-playwright/SKILL.md +34 -0
- package/skills/dynamics-365/SKILL.md +21 -0
- package/skills/playwright-typescript/SKILL.md +35 -0
- package/skills/writing-skills/SKILL.md +30 -0
- package/src/api/plan.ts +246 -0
- package/src/api/run.ts +204 -0
- package/src/api/scenario.ts +502 -0
- package/src/api/schema.ts +170 -0
- package/src/api/spec.ts +624 -0
- package/src/api/store.ts +232 -0
- package/src/api/traffic.ts +281 -0
- package/src/api/types.ts +163 -0
- package/src/api/write.ts +272 -0
- package/src/auth/signin.ts +494 -0
- package/src/auth/store.ts +170 -0
- package/src/brain/index.ts +189 -0
- package/src/brain/prompt.ts +19 -0
- package/src/capabilities/docs.ts +101 -0
- package/src/capabilities/guide.ts +455 -0
- package/src/capabilities/registry.ts +343 -0
- package/src/capabilities/render.ts +106 -0
- package/src/capabilities/status.ts +137 -0
- package/src/card.ts +11 -0
- package/src/cases/ado.ts +325 -0
- package/src/cases/api.ts +117 -0
- package/src/cases/bugs.ts +223 -0
- package/src/cases/confluence.ts +111 -0
- package/src/cases/context.ts +223 -0
- package/src/cases/dashboards.ts +137 -0
- package/src/cases/failure.ts +97 -0
- package/src/cases/files.ts +295 -0
- package/src/cases/goal.ts +53 -0
- package/src/cases/http.ts +111 -0
- package/src/cases/index.ts +131 -0
- package/src/cases/jira.ts +122 -0
- package/src/cases/qtest.ts +108 -0
- package/src/cases/quality.ts +190 -0
- package/src/cases/rag.ts +100 -0
- package/src/cases/readiness.ts +284 -0
- package/src/cases/save.ts +149 -0
- package/src/cases/sprints.ts +148 -0
- package/src/cases/testrail.ts +176 -0
- package/src/cases/types.ts +107 -0
- package/src/cases/wiki.ts +105 -0
- package/src/cases/xray.ts +206 -0
- package/src/cases/zephyr.ts +153 -0
- package/src/childEnv.ts +86 -0
- package/src/cli/args.ts +114 -0
- package/src/cli/help.ts +167 -0
- package/src/cli/index.ts +1142 -0
- package/src/cli/terminal.ts +187 -0
- package/src/code/areas.ts +337 -0
- package/src/code/attention.ts +214 -0
- package/src/code/find.ts +540 -0
- package/src/code/handoff.ts +541 -0
- package/src/code/history.ts +170 -0
- package/src/code/impact.ts +231 -0
- package/src/code/index.ts +34 -0
- package/src/code/livebrowser.ts +67 -0
- package/src/code/loop.ts +603 -0
- package/src/code/notes.ts +75 -0
- package/src/code/profile.ts +226 -0
- package/src/code/prompt.ts +303 -0
- package/src/code/prreview.ts +649 -0
- package/src/code/prtests.ts +103 -0
- package/src/code/replay.ts +772 -0
- package/src/code/review.ts +202 -0
- package/src/code/runtime.ts +757 -0
- package/src/code/slices.ts +159 -0
- package/src/code/testrun.ts +187 -0
- package/src/code/traceability.ts +100 -0
- package/src/code/worktree.ts +153 -0
- package/src/commands.ts +101 -0
- package/src/config.ts +970 -0
- package/src/detail.ts +65 -0
- package/src/domain.ts +19 -0
- package/src/engine/bridge.ts +359 -0
- package/src/engine/mock.ts +102 -0
- package/src/engine/setup.ts +298 -0
- package/src/entra.ts +154 -0
- package/src/env.ts +24 -0
- package/src/evals/domain.ts +338 -0
- package/src/explore/charters.ts +151 -0
- package/src/explore/run.ts +280 -0
- package/src/explore/story.ts +106 -0
- package/src/guard.ts +51 -0
- package/src/heal/loop.ts +357 -0
- package/src/heal/prompt.ts +143 -0
- package/src/heal/report.ts +95 -0
- package/src/index.ts +12 -0
- package/src/init.ts +102 -0
- package/src/knowledge.ts +114 -0
- package/src/legacyEnv.ts +9 -0
- package/src/llm.ts +61 -0
- package/src/locale.ts +25 -0
- package/src/logins.ts +148 -0
- package/src/managed.ts +101 -0
- package/src/mcp/apitools.ts +335 -0
- package/src/mcp/captools.ts +25 -0
- package/src/mcp/casetools.ts +223 -0
- package/src/mcp/commands.ts +421 -0
- package/src/mcp/exploretools.ts +129 -0
- package/src/mcp/external.ts +172 -0
- package/src/mcp/git.ts +78 -0
- package/src/mcp/notifytools.ts +49 -0
- package/src/mcp/prtools.ts +261 -0
- package/src/mcp/qualitytools.ts +154 -0
- package/src/mcp/repotests.ts +286 -0
- package/src/mcp/server.ts +234 -0
- package/src/mcp/tools.ts +810 -0
- package/src/memory.ts +250 -0
- package/src/money.ts +39 -0
- package/src/monitor/alerts.ts +94 -0
- package/src/monitor/background.ts +68 -0
- package/src/monitor/run.ts +263 -0
- package/src/monitor/state.ts +105 -0
- package/src/notify/events.ts +144 -0
- package/src/notify/notice.ts +177 -0
- package/src/prices.ts +175 -0
- package/src/print.ts +54 -0
- package/src/privacy.ts +35 -0
- package/src/prompts.ts +165 -0
- package/src/quality/budgets.ts +249 -0
- package/src/quality/history.ts +149 -0
- package/src/quality/visual.ts +201 -0
- package/src/report/data.ts +475 -0
- package/src/report/html.ts +106 -0
- package/src/report/template.html +84 -0
- package/src/run.ts +243 -0
- package/src/sessionLog.ts +216 -0
- package/src/sessionLogins.ts +94 -0
- package/src/skills/registry.ts +217 -0
- package/src/skills/scaffold.ts +65 -0
- package/src/skills/select.ts +37 -0
- package/src/skills/view.ts +44 -0
- package/src/suite/report.ts +135 -0
- package/src/suite/run.ts +303 -0
- package/src/summary.ts +188 -0
- package/src/tools.ts +130 -0
- package/src/tui/App.tsx +983 -0
- package/src/tui/clipboard.ts +36 -0
- package/src/tui/components.tsx +263 -0
- package/src/tui/dialogs.ts +65 -0
- package/src/tui/main.tsx +20 -0
- package/src/tui/text.ts +28 -0
- package/src/tui/theme.ts +104 -0
- package/src/tui/transcript.tsx +344 -0
- package/src/types.ts +136 -0
- package/src/update.ts +311 -0
- package/src/usage.ts +158 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto"
|
|
2
|
+
|
|
3
|
+
import { logEvent } from "../sessionLog.ts"
|
|
4
|
+
import type { McpTool, ToolContext, ToolEvent } from "./tools.ts"
|
|
5
|
+
|
|
6
|
+
type Json = Record<string, any>
|
|
7
|
+
type Message = { jsonrpc: "2.0"; id?: string | number | null; method?: string; params?: Json; result?: unknown; error?: unknown }
|
|
8
|
+
type Send = (message: Json) => void
|
|
9
|
+
|
|
10
|
+
const PROTOCOLS = ["2025-06-18", "2025-03-26", "2024-11-05"]
|
|
11
|
+
const PROGRESS_EVERY_MS = 10_000
|
|
12
|
+
|
|
13
|
+
/** The MCP server name ForgeQE's tools get in the agent runtime, so they appear as forgeqe_* tools. */
|
|
14
|
+
export const MCP_NAME = "forgeqe"
|
|
15
|
+
/** Tool calls may take as long as a whole suite. */
|
|
16
|
+
export const TOOL_TIMEOUT_MS = 6 * 60 * 60 * 1000
|
|
17
|
+
|
|
18
|
+
export type McpServerOptions = {
|
|
19
|
+
tools: McpTool[]
|
|
20
|
+
/** Read on every call, so the shell's current profile and headed setting apply. */
|
|
21
|
+
context: () => ToolContext
|
|
22
|
+
instructions?: () => string
|
|
23
|
+
version?: string
|
|
24
|
+
progressMs?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** JSON-RPC dispatch for MCP: initialize, ping, tools/list, tools/call, and cancellation. */
|
|
28
|
+
export function createMcpServer(opts: McpServerOptions) {
|
|
29
|
+
const running = new Map<string | number, AbortController>()
|
|
30
|
+
|
|
31
|
+
async function callTool(params: Json, id: string | number, send: Send, outer?: AbortSignal): Promise<Json> {
|
|
32
|
+
const tool = opts.tools.find((t) => t.name === params.name)
|
|
33
|
+
if (!tool) return { content: [{ type: "text", text: `Unknown tool ${params.name}` }], isError: true }
|
|
34
|
+
const base = opts.context()
|
|
35
|
+
const controller = new AbortController()
|
|
36
|
+
const abort = () => controller.abort()
|
|
37
|
+
base.signal?.addEventListener("abort", abort, { once: true })
|
|
38
|
+
outer?.addEventListener("abort", abort, { once: true })
|
|
39
|
+
running.set(id, controller)
|
|
40
|
+
const token = params._meta?.progressToken
|
|
41
|
+
let tick = 0
|
|
42
|
+
const timer = token !== undefined ? setInterval(() => send({ jsonrpc: "2.0", method: "notifications/progress", params: { progressToken: token, progress: ++tick, message: `${tool.name} running` } }), opts.progressMs || PROGRESS_EVERY_MS) : null
|
|
43
|
+
const started = Date.now()
|
|
44
|
+
logEvent("tool.call", { tool: tool.name, arguments: params.arguments || {} })
|
|
45
|
+
const finish = (result: Json) => {
|
|
46
|
+
logEvent("tool.result", { tool: tool.name, ms: Date.now() - started, error: Boolean(result.isError), text: result.content?.[0]?.text })
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const emit = base.emit
|
|
51
|
+
? (e: ToolEvent) => {
|
|
52
|
+
// Browser run events are logged by the engine bridge as engine.event.
|
|
53
|
+
if (e.type !== "run-event") logEvent("tool.event", { tool: tool.name, ...e })
|
|
54
|
+
base.emit?.(e)
|
|
55
|
+
}
|
|
56
|
+
: undefined
|
|
57
|
+
const out = await tool.run(params.arguments || {}, { ...base, emit, signal: controller.signal })
|
|
58
|
+
if (controller.signal.aborted) return finish({ content: [{ type: "text", text: "Stopped by the user." }], isError: true })
|
|
59
|
+
return finish({ content: [{ type: "text", text: typeof out === "string" ? out : JSON.stringify(out, null, 2) }] })
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return finish({ content: [{ type: "text", text: controller.signal.aborted ? "Stopped by the user." : (err as Error).message || String(err) }], isError: true })
|
|
62
|
+
} finally {
|
|
63
|
+
if (timer) clearInterval(timer)
|
|
64
|
+
running.delete(id)
|
|
65
|
+
base.signal?.removeEventListener("abort", abort)
|
|
66
|
+
outer?.removeEventListener("abort", abort)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The response for a request, or null for notifications and responses. */
|
|
71
|
+
async function handle(msg: Message, send: Send, signal?: AbortSignal): Promise<Json | null> {
|
|
72
|
+
if (!msg.method) return null
|
|
73
|
+
if (msg.id === undefined || msg.id === null) {
|
|
74
|
+
if (msg.method === "notifications/cancelled") running.get(msg.params?.requestId)?.abort()
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
const ok = (result: unknown) => ({ jsonrpc: "2.0", id: msg.id, result })
|
|
78
|
+
switch (msg.method) {
|
|
79
|
+
case "initialize": {
|
|
80
|
+
const asked = String(msg.params?.protocolVersion || "")
|
|
81
|
+
return ok({
|
|
82
|
+
protocolVersion: PROTOCOLS.includes(asked) ? asked : PROTOCOLS[0],
|
|
83
|
+
capabilities: { tools: { listChanged: false } },
|
|
84
|
+
serverInfo: { name: "forgeqe", version: opts.version || "0" },
|
|
85
|
+
...(opts.instructions ? { instructions: opts.instructions() } : {}),
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
case "ping":
|
|
89
|
+
return ok({})
|
|
90
|
+
case "tools/list":
|
|
91
|
+
return ok({ tools: opts.tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })) })
|
|
92
|
+
case "tools/call":
|
|
93
|
+
return ok(await callTool(msg.params || {}, msg.id, send, signal))
|
|
94
|
+
default:
|
|
95
|
+
return { jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { handle, abortAll: () => running.forEach((c) => c.abort()) }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type McpServer = ReturnType<typeof createMcpServer>
|
|
103
|
+
|
|
104
|
+
/** The bearer token check, in constant time. */
|
|
105
|
+
export function tokenMatches(header: string | null, token: string): boolean {
|
|
106
|
+
const digest = (s: string) => createHash("sha256").update(s).digest()
|
|
107
|
+
return timingSafeEqual(digest(header || ""), digest(`Bearer ${token}`))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A browser page may not call the tools (DNS rebinding): requests that carry an Origin must come from this machine.
|
|
112
|
+
* MCP clients that are not browsers send no Origin.
|
|
113
|
+
*/
|
|
114
|
+
export function originAllowed(origin: string | null): boolean {
|
|
115
|
+
if (!origin) return true
|
|
116
|
+
try {
|
|
117
|
+
return ["127.0.0.1", "localhost", "[::1]"].includes(new URL(origin).hostname)
|
|
118
|
+
} catch {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Streamable HTTP on 127.0.0.1 with a bearer token. Tool calls answer as an event stream so progress notifications
|
|
125
|
+
* and keep-alives flow while a browser run takes minutes; everything else answers as JSON.
|
|
126
|
+
*/
|
|
127
|
+
export function serveHttp(server: McpServer, token: string): { url: string; stop(): void } {
|
|
128
|
+
const http = Bun.serve({
|
|
129
|
+
hostname: "127.0.0.1",
|
|
130
|
+
port: 0,
|
|
131
|
+
idleTimeout: 0,
|
|
132
|
+
async fetch(req) {
|
|
133
|
+
if (new URL(req.url).pathname !== "/mcp") return new Response("Not found", { status: 404 })
|
|
134
|
+
if (!originAllowed(req.headers.get("origin"))) return new Response("Forbidden", { status: 403 })
|
|
135
|
+
if (!tokenMatches(req.headers.get("authorization"), token)) return new Response("Unauthorized", { status: 401 })
|
|
136
|
+
if (req.method === "DELETE") return new Response(null, { status: 200 })
|
|
137
|
+
if (req.method !== "POST") return new Response(null, { status: 405, headers: { Allow: "POST, DELETE" } })
|
|
138
|
+
let body: Message | Message[]
|
|
139
|
+
try {
|
|
140
|
+
body = await req.json()
|
|
141
|
+
} catch {
|
|
142
|
+
return Response.json({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }, { status: 400 })
|
|
143
|
+
}
|
|
144
|
+
const batch = Array.isArray(body) ? body : [body]
|
|
145
|
+
const requests = batch.filter((m) => m.method && m.id !== undefined && m.id !== null)
|
|
146
|
+
if (!requests.length) {
|
|
147
|
+
for (const m of batch) await server.handle(m, () => {})
|
|
148
|
+
return new Response(null, { status: 202 })
|
|
149
|
+
}
|
|
150
|
+
if (!requests.some((m) => m.method === "tools/call")) {
|
|
151
|
+
const out = (await Promise.all(batch.map((m) => server.handle(m, () => {})))).filter(Boolean)
|
|
152
|
+
return Response.json(Array.isArray(body) ? out : out[0])
|
|
153
|
+
}
|
|
154
|
+
const enc = new TextEncoder()
|
|
155
|
+
const stream = new ReadableStream({
|
|
156
|
+
async start(controller) {
|
|
157
|
+
let open = true
|
|
158
|
+
const write = (s: string) => {
|
|
159
|
+
if (!open) return
|
|
160
|
+
try {
|
|
161
|
+
controller.enqueue(enc.encode(s))
|
|
162
|
+
} catch {
|
|
163
|
+
open = false
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const send: Send = (m) => write(`event: message\ndata: ${JSON.stringify(m)}\n\n`)
|
|
167
|
+
const keepAlive = setInterval(() => write(": keep-alive\n\n"), 15_000)
|
|
168
|
+
try {
|
|
169
|
+
await Promise.all(
|
|
170
|
+
batch.map(async (m) => {
|
|
171
|
+
const res = await server.handle(m, send, req.signal)
|
|
172
|
+
if (res) send(res)
|
|
173
|
+
}),
|
|
174
|
+
)
|
|
175
|
+
} finally {
|
|
176
|
+
clearInterval(keepAlive)
|
|
177
|
+
open = false
|
|
178
|
+
try {
|
|
179
|
+
controller.close()
|
|
180
|
+
} catch {}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" } })
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
return { url: `http://127.0.0.1:${http.port}/mcp`, stop: () => http.stop(true) }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* ForgeQE's tools on 127.0.0.1 behind a random bearer token, for one agent runtime session: `entry` goes under `mcp` in
|
|
192
|
+
* its config. Tool calls may take as long as a whole suite; progress notifications keep the connection busy meanwhile.
|
|
193
|
+
*/
|
|
194
|
+
export function localToolServer(opts: McpServerOptions & { timeoutMs: number }): { server: McpServer; entry: Record<string, unknown>; stop(): void } {
|
|
195
|
+
const server = createMcpServer(opts)
|
|
196
|
+
const token = randomBytes(24).toString("hex")
|
|
197
|
+
const http = serveHttp(server, token)
|
|
198
|
+
return {
|
|
199
|
+
server,
|
|
200
|
+
entry: { type: "remote", url: http.url, headers: { Authorization: `Bearer ${token}` }, oauth: false, enabled: true, timeout: opts.timeoutMs },
|
|
201
|
+
stop: () => http.stop(),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Newline-delimited JSON-RPC on stdin/stdout, for MCP clients that start `forgeqe mcp` themselves. */
|
|
206
|
+
export async function serveStdio(server: McpServer, input: ReadableStream<Uint8Array> = Bun.stdin.stream()): Promise<void> {
|
|
207
|
+
const send: Send = (m) => process.stdout.write(`${JSON.stringify(m)}\n`)
|
|
208
|
+
const dec = new TextDecoder()
|
|
209
|
+
let buf = ""
|
|
210
|
+
const pending = new Set<Promise<void>>()
|
|
211
|
+
for await (const chunk of input) {
|
|
212
|
+
buf += dec.decode(chunk, { stream: true })
|
|
213
|
+
let nl: number
|
|
214
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
215
|
+
const line = buf.slice(0, nl).trim()
|
|
216
|
+
buf = buf.slice(nl + 1)
|
|
217
|
+
if (!line) continue
|
|
218
|
+
let msg: Message
|
|
219
|
+
try {
|
|
220
|
+
msg = JSON.parse(line)
|
|
221
|
+
} catch {
|
|
222
|
+
send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } })
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
const p = server.handle(msg, send).then((res) => {
|
|
226
|
+
if (res) send(res)
|
|
227
|
+
})
|
|
228
|
+
pending.add(p)
|
|
229
|
+
void p.finally(() => pending.delete(p))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
server.abortAll()
|
|
233
|
+
await Promise.allSettled([...pending])
|
|
234
|
+
}
|