cc-viewer 1.9.9 → 1.9.10
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/README.md +1 -1
- package/cli.js +4 -3
- package/dist/assets/App-Ci9GYs24.js +2 -0
- package/dist/assets/{MdxEditorPanel-BJVD_bcU.js → MdxEditorPanel-8fwlNHMz.js} +1 -1
- package/dist/assets/Mobile-DngSOqYr.js +1 -0
- package/dist/assets/ProxyStatsModal-CWSIELLj.js +1 -0
- package/dist/assets/{_baseUniq-CwQykUuc.js → _baseUniq-6ZLcdMmL.js} +1 -1
- package/dist/assets/{arc-EdUdBxtG.js → arc-Dirf6EZP.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-DtBIdElY.js → architectureDiagram-Q4EWVU46-BfIhmu_I.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-guNZIKkZ.js → blockDiagram-DXYQGD6D-D0y4ALi1.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-xV5vB-Z9.js → c4Diagram-AHTNJAMY-Dlfp4SUK.js} +1 -1
- package/dist/assets/{channel-DYEktNap.js → channel-CaNcU8SI.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-ZTkItpvj.js → chunk-4BX2VUAB-ki0vHNS-.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-dYIewyyR.js → chunk-4TB4RGXK-D8yO8Iuv.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-CSsPNgPX.js → chunk-55IACEB6-BvIgaNPO.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-CnMiuqzQ.js → chunk-EDXVE4YY-DF2Of_VO.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CVpATPZ3.js → chunk-FMBD7UC4-BIw7SXs-.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-Dpq4s8dW.js → chunk-OYMX7WX6-AB76s0A0.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Bnojh6X6.js → chunk-QZHKN3VN-CuN2LCFc.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-BYqK4Dsa.js → chunk-YZCP3GAM-D4DLBzBN.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-DLJzWgYm.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-DLJzWgYm.js +1 -0
- package/dist/assets/clone-H_VGsHn9.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-OJ7mmWyV.js → cose-bilkent-S5V4N54A-Bn485PX9.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-B1Kwd_Rk.js → dagre-KV5264BT-e4QVCf17.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-D8qrw8sp.js → diagram-5BDNPKRD-_SBFGgnf.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-ip-N4ho8.js → diagram-G4DWMVQ6-Bz1ovxLs.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-2PZaskUF.js → diagram-MMDJMWI5-B-oojW8K.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-CeAOQUZP.js → diagram-TYMM5635-CTAyNaOj.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-BCBaEYEb.js → erDiagram-SMLLAGMA-BfvKAqq2.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CsGyIqcX.js → flowDiagram-DWJPFMVM-BpTEZEAe.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-COsj3FZ-.js → ganttDiagram-T4ZO3ILL-DUD_vkyE.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-DvQVQGJG.js → gitGraphDiagram-UUTBAWPF-DnlXcTud.js} +1 -1
- package/dist/assets/{graph-BSB9fDSP.js → graph-D4xsUKSn.js} +1 -1
- package/dist/assets/{index-DKufsAzx.js → index-84-BjpGl.js} +1 -1
- package/dist/assets/index-BDKwOnE3.js +2 -0
- package/dist/assets/{index-Bo-12bug.js → index-BniBLRtZ.js} +1 -1
- package/dist/assets/{index-BRw4Iz2O.js → index-C2Zo3KOg.js} +1 -1
- package/dist/assets/{index-BuVComcY.js → index-CWZoKT7e.js} +1 -1
- package/dist/assets/{index-eM2ih5n7.js → index-D-4EsFub.js} +1 -1
- package/dist/assets/{index-CNNAYjkF.js → index-D3KzRmkP.js} +1 -1
- package/dist/assets/{index-DXBfwPYD.js → index-NGgdum2i.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-eNucS4iW.js → infoDiagram-42DDH7IO-BbdvmCDt.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-Dxxj-gUX.js → ishikawaDiagram-UXIWVN3A-oZBOPoTO.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-yJJzVEBb.js → journeyDiagram-VCZTEJTY-DWoH0hs9.js} +1 -1
- package/dist/assets/{jszip.min-ByI0ds5u.js → jszip.min-I5y9RZhq.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-2rfTi2zw.js → kanban-definition-6JOO6SKY-BLHtjKsS.js} +1 -1
- package/dist/assets/{layout-DTmHj0Wm.js → layout-C116SNcQ.js} +1 -1
- package/dist/assets/{linear-BOA3a9FE.js → linear-Br08WCkF.js} +1 -1
- package/dist/assets/{mermaid.core-5H-NR79H.js → mermaid.core-D-4dTrvI.js} +2 -2
- package/dist/assets/{min-CpjFTEec.js → min-khs74HbH.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-B8KCjFgB.js → mindmap-definition-QFDTVHPH-C3_yItFF.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-BARy96g5.js → pieDiagram-DEJITSTG-ByCwM2_P.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-DYoTapuI.js → quadrantDiagram-34T5L4WZ-CjV65X1r.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-EeANLLK2.js → requirementDiagram-MS252O5E-DFJ_dbtV.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-DmQ-YVPj.js → sankeyDiagram-XADWPNL6-BBvXoBfj.js} +1 -1
- package/dist/assets/{seqResourceLoaders-C0cU2e1o.css → seqResourceLoaders-C34z9uOa.css} +2 -2
- package/dist/assets/seqResourceLoaders-JJzgZZzU.js +2 -0
- package/dist/assets/{sequenceDiagram-FGHM5R23-SZtW0rYC.js → sequenceDiagram-FGHM5R23-BJM04Y3N.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-CEygsIld.js → stateDiagram-FHFEXIEX-DI2q_Kkn.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BU1XPhBe.js → stateDiagram-v2-QKLJ7IA2-CrGC6kEY.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-DhPBaQBt.js → timeline-definition-GMOUNBTQ-JcfRUsjD.js} +1 -1
- package/dist/assets/vendor-antd-DxEVqVhD.js +17 -0
- package/dist/assets/vendor-codemirror-CgNlkZva.js +1 -0
- package/dist/assets/{vendor-mdxeditor-Cz09a1_I.js → vendor-mdxeditor-DoreQf4A.js} +2 -2
- package/dist/assets/{vendor-qrcode-qbbntD6-.js → vendor-qrcode-q1CKtOyX.js} +1 -1
- package/dist/assets/{vendor-virtuoso-Sk8fQ4Hx.js → vendor-virtuoso-BXGe9q7E.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-NniTHxDw.js → vennDiagram-DHZGUBPP-mAHR1Ugj.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-D9SNOrDw.js → wardley-RL74JXVD-mlTq_P-4.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-DvHXKNza.js → wardleyDiagram-NUSXRM2D-BDGfajRb.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-CaiwPTQB.js → xychartDiagram-5P7HB3ND-Cf2pR1mV.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/plugins/README.md +9 -1
- package/server/interceptor.js +1 -1
- package/server/lib/builtin-model-prompts.js +1 -1
- package/server/lib/chat-queue.js +1 -1
- package/server/lib/credential-access.js +1 -1
- package/server/lib/credential-migrate.js +1 -1
- package/server/lib/error-report.js +26 -0
- package/server/lib/json-store.js +1 -1
- package/server/lib/launch-config.js +1 -1
- package/server/lib/log-watcher.js +1 -1
- package/server/lib/plugin-host.mjs +198 -0
- package/server/lib/plugin-loader.js +346 -90
- package/server/lib/plugin-manager.js +46 -2
- package/server/lib/plugin-manifest.js +329 -0
- package/server/lib/plugin-panels.js +183 -0
- package/server/lib/plugin-proxy.js +408 -0
- package/server/lib/plugin-runtime.js +775 -0
- package/server/lib/plugin-service-manager.js +463 -0
- package/server/lib/proxy/proxy-retry.js +1 -1
- package/server/lib/proxy/proxy-stats.js +1 -1
- package/server/lib/resume-watchdog.js +1 -1
- package/server/lib/same-origin.js +13 -3
- package/server/lib/sdk-manager.js +16 -9
- package/server/lib/sdk-session-index.js +1 -1
- package/server/lib/stats-worker.js +1 -1
- package/server/lib/system-prompt-files.js +1 -1
- package/server/lib/system-prompt-live.js +1 -1
- package/server/lib/system-prompt-snapshots.js +1 -1
- package/server/lib/v2/adapter.js +1 -1
- package/server/lib/v2/convert.js +1 -1
- package/server/lib/v2/jsonl-read.js +1 -1
- package/server/lib/v2/live-feed.js +1 -1
- package/server/lib/v2/meta-rows.js +1 -1
- package/server/lib/v2/resume-list.js +1 -1
- package/server/lib/v2/session-list.js +1 -1
- package/server/lib/v2/session-owner.js +1 -1
- package/server/lib/v2/v2-writer.js +1 -1
- package/server/lib/wire-compress.js +1 -1
- package/server/lib/ws-mux.js +1 -1
- package/server/proxy.js +1 -1
- package/server/pty-manager.js +1 -1
- package/server/routes/ask-perm.js +1 -1
- package/server/routes/events.js +1 -1
- package/server/routes/expert.js +1 -1
- package/server/routes/files-content.js +1 -1
- package/server/routes/files-fs.js +4 -2
- package/server/routes/misc.js +11 -0
- package/server/routes/plugin-services.js +104 -0
- package/server/routes/plugins.js +145 -17
- package/server/routes/preferences.js +1 -1
- package/server/routes/resume.js +1 -1
- package/server/routes/sdk-context.js +1 -1
- package/server/routes/sdk-mcp-status.js +1 -1
- package/server/routes/workspaces.js +1 -1
- package/server/server.js +328 -171
- package/dist/assets/App-CLteNB83.js +0 -2
- package/dist/assets/Mobile-CJCQf3MQ.js +0 -1
- package/dist/assets/ProxyStatsModal-B5ccoOPy.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-ClGF33bF.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-ClGF33bF.js +0 -1
- package/dist/assets/clone-CFj55h49.js +0 -1
- package/dist/assets/index-CqX5TkEL.js +0 -2
- package/dist/assets/seqResourceLoaders-7lsgbvGa.js +0 -2
- package/dist/assets/vendor-antd-CS_S2mBN.js +0 -17
- package/dist/assets/vendor-codemirror-B5l-VbBO.js +0 -1
|
@@ -19,7 +19,7 @@ import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { randomBytes } from 'node:crypto';
|
|
21
21
|
import { LOG_DIR } from '../../findcc.js';
|
|
22
|
-
import { reportSwallowed } from '
|
|
22
|
+
import { reportSwallowed } from '../lib/error-report.js';
|
|
23
23
|
import {
|
|
24
24
|
buildSystemPromptFileArgs, hasArg, argValue, isNonEmptyFile,
|
|
25
25
|
SYSTEM_PROMPT_FILE, APPEND_SYSTEM_PROMPT_FILE,
|
|
@@ -5,7 +5,7 @@ import { reconstructEntries } from '@ccv/core/delta-reconstructor';
|
|
|
5
5
|
import { enrichEntry } from './enrich-plan-input.js';
|
|
6
6
|
import { enrichEntry as enrichWorkflowEntry } from './enrich-workflow.js';
|
|
7
7
|
import { sseWrite } from './wire-compress.js';
|
|
8
|
-
import { reportSwallowed } from '
|
|
8
|
+
import { reportSwallowed } from '../lib/error-report.js';
|
|
9
9
|
|
|
10
10
|
// 1.7.0: the v1 log-file tail (fs.watch + byte cursors + rotation follow)
|
|
11
11
|
// retired with the v1 write path — the live channel is server/lib/v2/
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// plugin-host.mjs — SDK for service-plugin child processes. Loaded by the plugin's
|
|
2
|
+
// service entry (service.mjs) via the CCV_PLUGIN_HOST file:// URL; never imported by
|
|
3
|
+
// the main cc-viewer process.
|
|
4
|
+
//
|
|
5
|
+
// Wraps the wire contract between parent and child so third-party services only write
|
|
6
|
+
// request handlers:
|
|
7
|
+
// - HTTP server on 127.0.0.1:0 (random loopback port, no port races)
|
|
8
|
+
// - x-ccv-plugin-secret check on EVERY route (the parent proxy is the only legitimate
|
|
9
|
+
// caller; the child must not trust anything else on loopback)
|
|
10
|
+
// - GET /__ccv/health with a forced nonce (plugins cannot override identity fields)
|
|
11
|
+
// - atomic lock-file write (temp + rename) so the parent can discover pid/port
|
|
12
|
+
// - SIGTERM/SIGINT graceful shutdown, parent-process watchdog (PPID change → exit)
|
|
13
|
+
// - re-exports ws (WebSocketServer/WebSocket): bare 'ws' resolves against THIS file's
|
|
14
|
+
// location (cc-viewer's own dependency), so plugins must not `import 'ws'` themselves.
|
|
15
|
+
import { createServer } from 'node:http';
|
|
16
|
+
import { readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
19
|
+
|
|
20
|
+
export { WebSocket, WebSocketServer } from 'ws';
|
|
21
|
+
|
|
22
|
+
function readConfig() {
|
|
23
|
+
const configPath = process.env.CCV_PLUGIN_CONFIG;
|
|
24
|
+
if (!configPath) {
|
|
25
|
+
throw new Error('CCV_PLUGIN_CONFIG is not set — service entries must be spawned by cc-viewer');
|
|
26
|
+
}
|
|
27
|
+
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const config = readConfig();
|
|
31
|
+
export const plugin = {
|
|
32
|
+
name: config.name,
|
|
33
|
+
version: config.version ?? null,
|
|
34
|
+
dir: config.pluginDir,
|
|
35
|
+
dataDir: config.dataDir,
|
|
36
|
+
prefix: config.prefix,
|
|
37
|
+
};
|
|
38
|
+
export const parent = {
|
|
39
|
+
port: config.parentPort ?? null,
|
|
40
|
+
basePath: config.parentBasePath || '',
|
|
41
|
+
url: config.parentPort ? `http://127.0.0.1:${config.parentPort}${config.parentBasePath || ''}` : null,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Call the parent's HTTP API. Loopback parent APIs are token-free; the internal header
|
|
46
|
+
* marks plugin traffic for the host interceptor. Never append the ACCESS_TOKEN here.
|
|
47
|
+
*/
|
|
48
|
+
export async function callParent(path, init = {}) {
|
|
49
|
+
if (!parent.url) throw new Error('parent port is not available');
|
|
50
|
+
const headers = { 'x-cc-viewer-internal': '1', ...(init.headers || {}) };
|
|
51
|
+
return fetch(`${parent.url}${path}`, { ...init, headers });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function secretMatches(headerValue, secret) {
|
|
55
|
+
if (!headerValue || !secret) return false;
|
|
56
|
+
const a = Buffer.from(String(headerValue));
|
|
57
|
+
const b = Buffer.from(String(secret));
|
|
58
|
+
// timingSafeEqual throws on length mismatch - compare lengths first
|
|
59
|
+
if (a.length !== b.length) return false;
|
|
60
|
+
return timingSafeEqual(a, b);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeLockAtomic(lockPath, body) {
|
|
64
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
65
|
+
const tmpPath = `${lockPath}.tmp-${process.pid}`;
|
|
66
|
+
writeFileSync(tmpPath, JSON.stringify(body), { mode: 0o600 });
|
|
67
|
+
renameSync(tmpPath, lockPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Start the plugin service.
|
|
72
|
+
* @param {object} opts
|
|
73
|
+
* @param {(req, res) => void | Promise<void>} opts.onRequest standard node http handler
|
|
74
|
+
* @param {(req, socket, head) => void} [opts.onUpgrade] optional ws upgrade handler
|
|
75
|
+
* @param {() => object} [opts.health] extra health fields (identity is forced)
|
|
76
|
+
* @returns {Promise<{ server, port, close():Promise<void> }>}
|
|
77
|
+
*/
|
|
78
|
+
export async function startPluginService(opts = {}) {
|
|
79
|
+
if (typeof opts.onRequest !== 'function') {
|
|
80
|
+
throw new Error('startPluginService requires onRequest(req, res)');
|
|
81
|
+
}
|
|
82
|
+
const secret = config.secret;
|
|
83
|
+
const nonce = config.nonce;
|
|
84
|
+
const lockPath = process.env.CCV_PLUGIN_LOCK || config.lockPath;
|
|
85
|
+
|
|
86
|
+
const server = createServer(async (req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
if (!secretMatches(req.headers['x-ccv-plugin-secret'], secret)) {
|
|
89
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
90
|
+
res.end(JSON.stringify({ error: 'invalid plugin secret' }));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
94
|
+
if (url.pathname === '/__ccv/health') {
|
|
95
|
+
const extra = typeof opts.health === 'function' ? (opts.health() || {}) : {};
|
|
96
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
97
|
+
// Identity fields are written last so a plugin's custom health cannot override them
|
|
98
|
+
res.end(JSON.stringify({
|
|
99
|
+
...extra,
|
|
100
|
+
ok: true,
|
|
101
|
+
name: config.name,
|
|
102
|
+
pid: process.pid,
|
|
103
|
+
version: config.version ?? null,
|
|
104
|
+
nonce,
|
|
105
|
+
}));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
await opts.onRequest(req, res);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (!res.headersSent) {
|
|
111
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
112
|
+
res.end(JSON.stringify({ error: String(err && err.message || err) }));
|
|
113
|
+
} else {
|
|
114
|
+
res.destroy();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
server.on('upgrade', (req, socket, head) => {
|
|
120
|
+
try {
|
|
121
|
+
if (!secretMatches(req.headers['x-ccv-plugin-secret'], secret)) {
|
|
122
|
+
socket.destroy();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (typeof opts.onUpgrade === 'function') {
|
|
126
|
+
opts.onUpgrade(req, socket, head);
|
|
127
|
+
} else {
|
|
128
|
+
socket.destroy();
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
try { socket.destroy(); } catch { /* noop */ }
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
136
|
+
server.once('error', rejectListen);
|
|
137
|
+
server.listen(0, '127.0.0.1', () => {
|
|
138
|
+
server.off('error', rejectListen);
|
|
139
|
+
resolveListen();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
const port = server.address().port;
|
|
143
|
+
|
|
144
|
+
const lockBody = {
|
|
145
|
+
pid: process.pid,
|
|
146
|
+
port,
|
|
147
|
+
parentPid: config.parentPid ?? process.ppid,
|
|
148
|
+
name: config.name,
|
|
149
|
+
generation: config.generation ?? null,
|
|
150
|
+
nonce,
|
|
151
|
+
bootNonce: config.bootNonce ?? null,
|
|
152
|
+
startedAt: new Date().toISOString(),
|
|
153
|
+
};
|
|
154
|
+
writeLockAtomic(lockPath, lockBody);
|
|
155
|
+
|
|
156
|
+
let closed = false;
|
|
157
|
+
const close = () => new Promise((resolveClose) => {
|
|
158
|
+
if (closed) { resolveClose(); return; }
|
|
159
|
+
closed = true;
|
|
160
|
+
clearInterval(watchdog);
|
|
161
|
+
try { unlinkSync(lockPath); } catch { /* noop */ }
|
|
162
|
+
const forceTimer = setTimeout(() => process.exit(0), 2000);
|
|
163
|
+
forceTimer.unref?.();
|
|
164
|
+
server.close(() => {
|
|
165
|
+
clearTimeout(forceTimer);
|
|
166
|
+
resolveClose();
|
|
167
|
+
});
|
|
168
|
+
try { server.closeAllConnections?.(); } catch { /* noop */ }
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
process.on('SIGTERM', () => { close().then(() => process.exit(0)); });
|
|
172
|
+
process.on('SIGINT', () => { close().then(() => process.exit(0)); });
|
|
173
|
+
|
|
174
|
+
// Parent watchdog: a PPID change (parent died/adopted as orphan) -> exit and release port + lock
|
|
175
|
+
const initialPpid = process.ppid;
|
|
176
|
+
const watchdog = setInterval(() => {
|
|
177
|
+
let parentAlive = true;
|
|
178
|
+
try {
|
|
179
|
+
process.kill(initialPpid, 0);
|
|
180
|
+
} catch {
|
|
181
|
+
parentAlive = false;
|
|
182
|
+
}
|
|
183
|
+
if (!parentAlive || process.ppid !== initialPpid) {
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
}, 2000);
|
|
187
|
+
watchdog.unref?.();
|
|
188
|
+
|
|
189
|
+
const logPrefix = `[plugin:${config.name}]`;
|
|
190
|
+
return {
|
|
191
|
+
server,
|
|
192
|
+
port,
|
|
193
|
+
close,
|
|
194
|
+
log: (...args) => console.log(logPrefix, ...args),
|
|
195
|
+
warn: (...args) => console.warn(logPrefix, ...args),
|
|
196
|
+
error: (...args) => console.error(logPrefix, ...args),
|
|
197
|
+
};
|
|
198
|
+
}
|