@zhang6hk/dsh-codex 0.0.0-stage → 0.1.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 (37) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +18 -0
  3. package/README.md +75 -2
  4. package/README.zh.md +78 -0
  5. package/cordis.patch.yml +7 -0
  6. package/lib/args.js +118 -0
  7. package/lib/codex-path.js +31 -0
  8. package/lib/commands.js +242 -0
  9. package/lib/companion.js +177 -0
  10. package/lib/index.js +105 -0
  11. package/lib/render.js +114 -0
  12. package/lib/tool.js +103 -0
  13. package/locale/en.json +6 -0
  14. package/locale/zh.json +6 -0
  15. package/package.json +49 -4
  16. package/vendor/codex/.claude-plugin/plugin.json +8 -0
  17. package/vendor/codex/prompts/adversarial-review.md +84 -0
  18. package/vendor/codex/prompts/stop-review-gate.md +36 -0
  19. package/vendor/codex/schemas/review-output.schema.json +87 -0
  20. package/vendor/codex/scripts/app-server-broker.mjs +252 -0
  21. package/vendor/codex/scripts/codex-companion.mjs +1073 -0
  22. package/vendor/codex/scripts/lib/app-server-protocol.d.ts +75 -0
  23. package/vendor/codex/scripts/lib/app-server.mjs +354 -0
  24. package/vendor/codex/scripts/lib/args.mjs +128 -0
  25. package/vendor/codex/scripts/lib/broker-endpoint.mjs +41 -0
  26. package/vendor/codex/scripts/lib/broker-lifecycle.mjs +209 -0
  27. package/vendor/codex/scripts/lib/claude-session-transfer.mjs +44 -0
  28. package/vendor/codex/scripts/lib/codex.mjs +1219 -0
  29. package/vendor/codex/scripts/lib/fs.mjs +40 -0
  30. package/vendor/codex/scripts/lib/git.mjs +347 -0
  31. package/vendor/codex/scripts/lib/job-control.mjs +308 -0
  32. package/vendor/codex/scripts/lib/process.mjs +135 -0
  33. package/vendor/codex/scripts/lib/prompts.mjs +13 -0
  34. package/vendor/codex/scripts/lib/render.mjs +465 -0
  35. package/vendor/codex/scripts/lib/state.mjs +191 -0
  36. package/vendor/codex/scripts/lib/tracked-jobs.mjs +204 -0
  37. package/vendor/codex/scripts/lib/workspace.mjs +9 -0
package/lib/tool.js ADDED
@@ -0,0 +1,103 @@
1
+ import { renderRun } from './render.js'
2
+
3
+ const DESCRIPTION = [
4
+ '把一段需要自己跑代码、改文件或复现问题的任务交给本机 Codex CLI。',
5
+ 'Codex 在独立线程里工作,能用同一线程续跑;返回它的最终结论与改动摘要。',
6
+ '适合:复现并定位问题、按描述实施修改、把一次探索沉淀成结论。',
7
+ '不适合:只读的代码审查(用 /codex-review)、纯知识问答。',
8
+ ].join('')
9
+
10
+ /**
11
+ * @param {object} deps
12
+ * @param {(argv: string[], options: {cwd: string, sessionId?: string, signal?: AbortSignal, timeoutMs?: number}) => Promise<import('./companion.js').CompanionResult>} deps.run
13
+ * @param {(invocation: {agent?: unknown}) => string} deps.resolveCwd
14
+ * @param {Record<string, any>} deps.settings
15
+ * @returns {object} A registry-ready ToolDefinition.
16
+ */
17
+ export function createCodexTaskTool(deps) {
18
+ const { run, resolveCwd, settings } = deps
19
+
20
+ return {
21
+ name: 'codex_task',
22
+ description: DESCRIPTION,
23
+ parameters: {
24
+ type: 'object',
25
+ properties: {
26
+ prompt: {
27
+ type: 'string',
28
+ description: '交给 Codex 的任务描述,写清目标与验收标准。',
29
+ },
30
+ resume: {
31
+ type: 'boolean',
32
+ description: '续跑本会话里最近的 Codex 线程,而不是新开一个。',
33
+ },
34
+ },
35
+ required: ['prompt'],
36
+ additionalProperties: false,
37
+ },
38
+ output: {
39
+ schema: {
40
+ type: 'object',
41
+ properties: {
42
+ status: { type: 'number' },
43
+ threadId: { type: 'string' },
44
+ output: { type: 'string' },
45
+ },
46
+ required: ['status', 'threadId', 'output'],
47
+ additionalProperties: false,
48
+ },
49
+ render(_args, value) {
50
+ return [{ type: 'text', text: String(value?.output ?? '') }]
51
+ },
52
+ },
53
+ timeoutMs: settings.timeoutMs,
54
+ isConcurrencySafe: () => false,
55
+ async execute(args, exec) {
56
+ const prompt = typeof args?.prompt === 'string' ? args.prompt.trim() : ''
57
+ if (prompt.length === 0) {
58
+ throw new Error('codex_task 需要非空的 prompt。')
59
+ }
60
+
61
+ const argv = ['task', '--json', args?.resume === true ? '--resume' : '--fresh']
62
+ if (typeof settings.model === 'string' && settings.model.length > 0) {
63
+ argv.push('--model', settings.model)
64
+ }
65
+ if (typeof settings.effort === 'string' && settings.effort.length > 0) {
66
+ argv.push('--effort', settings.effort)
67
+ }
68
+ argv.push(prompt)
69
+
70
+ const result = await run(argv, {
71
+ cwd: resolveCwd(exec),
72
+ sessionId: exec?.agent?.session?.id === undefined ? undefined : String(exec.agent.session.id),
73
+ signal: exec?.signal,
74
+ timeoutMs: settings.timeoutMs,
75
+ })
76
+
77
+ if (result.code !== 0) {
78
+ const failure = renderRun(result, 'Codex 任务')
79
+ throw new Error(failure.text)
80
+ }
81
+
82
+ const payload = parseJson(result.stdout)
83
+ const output = payload?.rawOutput ?? payload?.output ?? result.stdout.trim()
84
+ return {
85
+ status: typeof payload?.status === 'number' ? payload.status : 0,
86
+ threadId: typeof payload?.threadId === 'string' ? payload.threadId : '',
87
+ output: typeof output === 'string' && output.length > 0 ? output : 'Codex 已完成任务。',
88
+ }
89
+ },
90
+ }
91
+ }
92
+
93
+ /**
94
+ * @param {string} text
95
+ * @returns {Record<string, any> | null}
96
+ */
97
+ function parseJson(text) {
98
+ try {
99
+ return JSON.parse(text)
100
+ } catch {
101
+ return null
102
+ }
103
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Codex Bridge",
4
+ "description": "Run Codex code reviews and delegate investigation or fix tasks from DeepSeek Harness."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Codex 协作",
4
+ "description": "在 DSH 里调用本机 Codex CLI 做代码审查,或把调查与修复任务交给它。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@zhang6hk/dsh-codex",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Run Codex CLI code reviews and hand investigation or fix tasks to Codex from DeepSeek Harness.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js"
9
+ },
10
+ "files": [
11
+ "lib",
12
+ "vendor",
13
+ "locale",
14
+ "cordis.patch.yml",
15
+ "LICENSE",
16
+ "NOTICE",
17
+ "README.md",
18
+ "README.zh.md"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "scripts": {
24
+ "test": "node --test test/"
25
+ },
26
+ "keywords": [
27
+ "dsh",
28
+ "dsh-plugin",
29
+ "deepseek-harness",
30
+ "codex",
31
+ "code-review",
32
+ "codex-cli",
33
+ "review",
34
+ "agent"
35
+ ],
36
+ "license": "Apache-2.0",
37
+ "author": "zhang6hk",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/zhang6hk/dsh-codex.git"
41
+ },
42
+ "homepage": "https://github.com/zhang6hk/dsh-codex#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/zhang6hk/dsh-codex/issues"
45
+ },
46
+ "dsh": {
47
+ "bundle": {
48
+ "patch": "./cordis.patch.yml"
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "codex",
3
+ "version": "1.0.6",
4
+ "description": "Use Codex from Claude Code to review code or delegate tasks.",
5
+ "author": {
6
+ "name": "OpenAI"
7
+ }
8
+ }
@@ -0,0 +1,84 @@
1
+ <role>
2
+ You are Codex performing an adversarial software review.
3
+ Your job is to break confidence in the change, not to validate it.
4
+ </role>
5
+
6
+ <task>
7
+ Review the provided repository context as if you are trying to find the strongest reasons this change should not ship yet.
8
+ Target: {{TARGET_LABEL}}
9
+ User focus: {{USER_FOCUS}}
10
+ </task>
11
+
12
+ <operating_stance>
13
+ Default to skepticism.
14
+ Assume the change can fail in subtle, high-cost, or user-visible ways until the evidence says otherwise.
15
+ Do not give credit for good intent, partial fixes, or likely follow-up work.
16
+ If something only works on the happy path, treat that as a real weakness.
17
+ </operating_stance>
18
+
19
+ <attack_surface>
20
+ Prioritize the kinds of failures that are expensive, dangerous, or hard to detect:
21
+ - auth, permissions, tenant isolation, and trust boundaries
22
+ - data loss, corruption, duplication, and irreversible state changes
23
+ - rollback safety, retries, partial failure, and idempotency gaps
24
+ - race conditions, ordering assumptions, stale state, and re-entrancy
25
+ - empty-state, null, timeout, and degraded dependency behavior
26
+ - version skew, schema drift, migration hazards, and compatibility regressions
27
+ - observability gaps that would hide failure or make recovery harder
28
+ </attack_surface>
29
+
30
+ <review_method>
31
+ Actively try to disprove the change.
32
+ Look for violated invariants, missing guards, unhandled failure paths, and assumptions that stop being true under stress.
33
+ Trace how bad inputs, retries, concurrent actions, or partially completed operations move through the code.
34
+ If the user supplied a focus area, weight it heavily, but still report any other material issue you can defend.
35
+ {{REVIEW_COLLECTION_GUIDANCE}}
36
+ </review_method>
37
+
38
+ <finding_bar>
39
+ Report only material findings.
40
+ Do not include style feedback, naming feedback, low-value cleanup, or speculative concerns without evidence.
41
+ A finding should answer:
42
+ 1. What can go wrong?
43
+ 2. Why is this code path vulnerable?
44
+ 3. What is the likely impact?
45
+ 4. What concrete change would reduce the risk?
46
+ </finding_bar>
47
+
48
+ <structured_output_contract>
49
+ Return only valid JSON matching the provided schema.
50
+ Keep the output compact and specific.
51
+ Use `needs-attention` if there is any material risk worth blocking on.
52
+ Use `approve` only if you cannot support any substantive adversarial finding from the provided context.
53
+ Every finding must include:
54
+ - the affected file
55
+ - `line_start` and `line_end`
56
+ - a confidence score from 0 to 1
57
+ - a concrete recommendation
58
+ Write the summary like a terse ship/no-ship assessment, not a neutral recap.
59
+ </structured_output_contract>
60
+
61
+ <grounding_rules>
62
+ Be aggressive, but stay grounded.
63
+ Every finding must be defensible from the provided repository context or tool outputs.
64
+ Do not invent files, lines, code paths, incidents, attack chains, or runtime behavior you cannot support.
65
+ If a conclusion depends on an inference, state that explicitly in the finding body and keep the confidence honest.
66
+ </grounding_rules>
67
+
68
+ <calibration_rules>
69
+ Prefer one strong finding over several weak ones.
70
+ Do not dilute serious issues with filler.
71
+ If the change looks safe, say so directly and return no findings.
72
+ </calibration_rules>
73
+
74
+ <final_check>
75
+ Before finalizing, check that each finding is:
76
+ - adversarial rather than stylistic
77
+ - tied to a concrete code location
78
+ - plausible under a real failure scenario
79
+ - actionable for an engineer fixing the issue
80
+ </final_check>
81
+
82
+ <repository_context>
83
+ {{REVIEW_INPUT}}
84
+ </repository_context>
@@ -0,0 +1,36 @@
1
+ <task>
2
+ Run a stop-gate review of the previous Claude turn.
3
+ Only review the work from the previous Claude turn.
4
+ Only review it if Claude actually did code changes in that turn.
5
+ Pure status, setup, or reporting output does not count as reviewable work.
6
+ For example, the output of /codex:setup or /codex:status does not count.
7
+ Only direct edits made in that specific turn count.
8
+ If the previous Claude turn was only a status update, a summary, a setup/login check, a review result, or output from a command that did not itself make direct edits in that turn, return ALLOW immediately and do no further work.
9
+ Challenge whether that specific work and its design choices should ship.
10
+
11
+ {{CLAUDE_RESPONSE_BLOCK}}
12
+ </task>
13
+
14
+ <compact_output_contract>
15
+ Return a compact final answer.
16
+ Your first line must be exactly one of:
17
+ - ALLOW: <short reason>
18
+ - BLOCK: <short reason>
19
+ Do not put anything before that first line.
20
+ </compact_output_contract>
21
+
22
+ <default_follow_through_policy>
23
+ Use ALLOW if the previous turn did not make code changes or if you do not see a blocking issue.
24
+ Use ALLOW immediately, without extra investigation, if the previous turn was not an edit-producing turn.
25
+ Use BLOCK only if the previous turn made code changes and you found something that still needs to be fixed before stopping.
26
+ </default_follow_through_policy>
27
+
28
+ <grounding_rules>
29
+ Ground every blocking claim in the repository context or tool outputs you inspected during this run.
30
+ Do not treat the previous Claude response as proof that code changes happened; verify that from the repository state before you block.
31
+ Do not block based on older edits from earlier turns when the immediately previous turn did not itself make direct edits.
32
+ </grounding_rules>
33
+
34
+ <dig_deeper_nudge>
35
+ If the previous turn did make code changes, check for second-order failures, empty-state behavior, retries, stale state, rollback risk, and design tradeoffs before you finalize.
36
+ </dig_deeper_nudge>
@@ -0,0 +1,87 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "required": [
6
+ "verdict",
7
+ "summary",
8
+ "findings",
9
+ "next_steps"
10
+ ],
11
+ "properties": {
12
+ "verdict": {
13
+ "type": "string",
14
+ "enum": [
15
+ "approve",
16
+ "needs-attention"
17
+ ]
18
+ },
19
+ "summary": {
20
+ "type": "string",
21
+ "minLength": 1
22
+ },
23
+ "findings": {
24
+ "type": "array",
25
+ "items": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": [
29
+ "severity",
30
+ "title",
31
+ "body",
32
+ "file",
33
+ "line_start",
34
+ "line_end",
35
+ "confidence",
36
+ "recommendation"
37
+ ],
38
+ "properties": {
39
+ "severity": {
40
+ "type": "string",
41
+ "enum": [
42
+ "critical",
43
+ "high",
44
+ "medium",
45
+ "low"
46
+ ]
47
+ },
48
+ "title": {
49
+ "type": "string",
50
+ "minLength": 1
51
+ },
52
+ "body": {
53
+ "type": "string",
54
+ "minLength": 1
55
+ },
56
+ "file": {
57
+ "type": "string",
58
+ "minLength": 1
59
+ },
60
+ "line_start": {
61
+ "type": "integer",
62
+ "minimum": 1
63
+ },
64
+ "line_end": {
65
+ "type": "integer",
66
+ "minimum": 1
67
+ },
68
+ "confidence": {
69
+ "type": "number",
70
+ "minimum": 0,
71
+ "maximum": 1
72
+ },
73
+ "recommendation": {
74
+ "type": "string"
75
+ }
76
+ }
77
+ }
78
+ },
79
+ "next_steps": {
80
+ "type": "array",
81
+ "items": {
82
+ "type": "string",
83
+ "minLength": 1
84
+ }
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import net from "node:net";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+
8
+ import { parseArgs } from "./lib/args.mjs";
9
+ import { BROKER_BUSY_RPC_CODE, CodexAppServerClient } from "./lib/app-server.mjs";
10
+ import { parseBrokerEndpoint } from "./lib/broker-endpoint.mjs";
11
+
12
+ const STREAMING_METHODS = new Set(["turn/start", "review/start", "thread/compact/start"]);
13
+
14
+ function buildStreamThreadIds(method, params, result) {
15
+ const threadIds = new Set();
16
+ if (params?.threadId) {
17
+ threadIds.add(params.threadId);
18
+ }
19
+ if (method === "review/start" && result?.reviewThreadId) {
20
+ threadIds.add(result.reviewThreadId);
21
+ }
22
+ return threadIds;
23
+ }
24
+
25
+ function buildJsonRpcError(code, message, data) {
26
+ return data === undefined ? { code, message } : { code, message, data };
27
+ }
28
+
29
+ function send(socket, message) {
30
+ if (socket.destroyed) {
31
+ return;
32
+ }
33
+ socket.write(`${JSON.stringify(message)}\n`);
34
+ }
35
+
36
+ function isInterruptRequest(message) {
37
+ return message?.method === "turn/interrupt";
38
+ }
39
+
40
+ function writePidFile(pidFile) {
41
+ if (!pidFile) {
42
+ return;
43
+ }
44
+ fs.mkdirSync(path.dirname(pidFile), { recursive: true });
45
+ fs.writeFileSync(pidFile, `${process.pid}\n`, "utf8");
46
+ }
47
+
48
+ async function main() {
49
+ const [subcommand, ...argv] = process.argv.slice(2);
50
+ if (subcommand !== "serve") {
51
+ throw new Error("Usage: node scripts/app-server-broker.mjs serve --endpoint <value> [--cwd <path>] [--pid-file <path>]");
52
+ }
53
+
54
+ const { options } = parseArgs(argv, {
55
+ valueOptions: ["cwd", "pid-file", "endpoint"]
56
+ });
57
+
58
+ if (!options.endpoint) {
59
+ throw new Error("Missing required --endpoint.");
60
+ }
61
+
62
+ const cwd = options.cwd ? path.resolve(process.cwd(), options.cwd) : process.cwd();
63
+ const endpoint = String(options.endpoint);
64
+ const listenTarget = parseBrokerEndpoint(endpoint);
65
+ const pidFile = options["pid-file"] ? path.resolve(options["pid-file"]) : null;
66
+ writePidFile(pidFile);
67
+
68
+ const appClient = await CodexAppServerClient.connect(cwd, { disableBroker: true });
69
+ let activeRequestSocket = null;
70
+ let activeStreamSocket = null;
71
+ let activeStreamThreadIds = null;
72
+ const sockets = new Set();
73
+
74
+ function clearSocketOwnership(socket) {
75
+ if (activeRequestSocket === socket) {
76
+ activeRequestSocket = null;
77
+ }
78
+ if (activeStreamSocket === socket) {
79
+ activeStreamSocket = null;
80
+ activeStreamThreadIds = null;
81
+ }
82
+ }
83
+
84
+ function routeNotification(message) {
85
+ const target = activeRequestSocket ?? activeStreamSocket;
86
+ if (!target) {
87
+ return;
88
+ }
89
+ send(target, message);
90
+ if (message.method === "turn/completed" && activeStreamSocket === target) {
91
+ const threadId = message.params?.threadId ?? null;
92
+ if (!threadId || !activeStreamThreadIds || activeStreamThreadIds.has(threadId)) {
93
+ activeStreamSocket = null;
94
+ activeStreamThreadIds = null;
95
+ if (activeRequestSocket === target) {
96
+ activeRequestSocket = null;
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ async function shutdown(server) {
103
+ for (const socket of sockets) {
104
+ socket.end();
105
+ }
106
+ await appClient.close().catch(() => {});
107
+ await new Promise((resolve) => server.close(resolve));
108
+ if (listenTarget.kind === "unix" && fs.existsSync(listenTarget.path)) {
109
+ fs.unlinkSync(listenTarget.path);
110
+ }
111
+ if (pidFile && fs.existsSync(pidFile)) {
112
+ fs.unlinkSync(pidFile);
113
+ }
114
+ }
115
+
116
+ appClient.setNotificationHandler(routeNotification);
117
+
118
+ const server = net.createServer((socket) => {
119
+ sockets.add(socket);
120
+ socket.setEncoding("utf8");
121
+ let buffer = "";
122
+
123
+ socket.on("data", async (chunk) => {
124
+ buffer += chunk;
125
+ let newlineIndex = buffer.indexOf("\n");
126
+ while (newlineIndex !== -1) {
127
+ const line = buffer.slice(0, newlineIndex);
128
+ buffer = buffer.slice(newlineIndex + 1);
129
+ newlineIndex = buffer.indexOf("\n");
130
+
131
+ if (!line.trim()) {
132
+ continue;
133
+ }
134
+
135
+ let message;
136
+ try {
137
+ message = JSON.parse(line);
138
+ } catch (error) {
139
+ send(socket, {
140
+ id: null,
141
+ error: buildJsonRpcError(-32700, `Invalid JSON: ${error.message}`)
142
+ });
143
+ continue;
144
+ }
145
+
146
+ if (message.id !== undefined && message.method === "initialize") {
147
+ send(socket, {
148
+ id: message.id,
149
+ result: {
150
+ userAgent: "codex-companion-broker"
151
+ }
152
+ });
153
+ continue;
154
+ }
155
+
156
+ if (message.method === "initialized" && message.id === undefined) {
157
+ continue;
158
+ }
159
+
160
+ if (message.id !== undefined && message.method === "broker/shutdown") {
161
+ send(socket, { id: message.id, result: {} });
162
+ await shutdown(server);
163
+ process.exit(0);
164
+ }
165
+
166
+ if (message.id === undefined) {
167
+ continue;
168
+ }
169
+
170
+ const allowInterruptDuringActiveStream =
171
+ isInterruptRequest(message) && activeStreamSocket && activeStreamSocket !== socket && !activeRequestSocket;
172
+
173
+ if (
174
+ ((activeRequestSocket && activeRequestSocket !== socket) || (activeStreamSocket && activeStreamSocket !== socket)) &&
175
+ !allowInterruptDuringActiveStream
176
+ ) {
177
+ send(socket, {
178
+ id: message.id,
179
+ error: buildJsonRpcError(BROKER_BUSY_RPC_CODE, "Shared Codex broker is busy.")
180
+ });
181
+ continue;
182
+ }
183
+
184
+ if (allowInterruptDuringActiveStream) {
185
+ try {
186
+ const result = await appClient.request(message.method, message.params ?? {});
187
+ send(socket, { id: message.id, result });
188
+ } catch (error) {
189
+ send(socket, {
190
+ id: message.id,
191
+ error: buildJsonRpcError(error.rpcCode ?? -32000, error.message)
192
+ });
193
+ }
194
+ continue;
195
+ }
196
+
197
+ const isStreaming = STREAMING_METHODS.has(message.method);
198
+ activeRequestSocket = socket;
199
+
200
+ try {
201
+ const result = await appClient.request(message.method, message.params ?? {});
202
+ send(socket, { id: message.id, result });
203
+ if (isStreaming) {
204
+ activeStreamSocket = socket;
205
+ activeStreamThreadIds = buildStreamThreadIds(message.method, message.params ?? {}, result);
206
+ }
207
+ if (activeRequestSocket === socket) {
208
+ activeRequestSocket = null;
209
+ }
210
+ } catch (error) {
211
+ send(socket, {
212
+ id: message.id,
213
+ error: buildJsonRpcError(error.rpcCode ?? -32000, error.message)
214
+ });
215
+ if (activeRequestSocket === socket) {
216
+ activeRequestSocket = null;
217
+ }
218
+ if (activeStreamSocket === socket && !isStreaming) {
219
+ activeStreamSocket = null;
220
+ }
221
+ }
222
+ }
223
+ });
224
+
225
+ socket.on("close", () => {
226
+ sockets.delete(socket);
227
+ clearSocketOwnership(socket);
228
+ });
229
+
230
+ socket.on("error", () => {
231
+ sockets.delete(socket);
232
+ clearSocketOwnership(socket);
233
+ });
234
+ });
235
+
236
+ process.on("SIGTERM", async () => {
237
+ await shutdown(server);
238
+ process.exit(0);
239
+ });
240
+
241
+ process.on("SIGINT", async () => {
242
+ await shutdown(server);
243
+ process.exit(0);
244
+ });
245
+
246
+ server.listen(listenTarget.path);
247
+ }
248
+
249
+ main().catch((error) => {
250
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
251
+ process.exit(1);
252
+ });