cc-viewer 1.9.8 → 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.
Files changed (141) hide show
  1. package/README.md +1 -1
  2. package/cli.js +5 -3
  3. package/dist/assets/App-Ci9GYs24.js +2 -0
  4. package/dist/assets/App-D06hBGII.css +1 -0
  5. package/dist/assets/{MdxEditorPanel-BnbZpgDp.js → MdxEditorPanel-8fwlNHMz.js} +1 -1
  6. package/dist/assets/Mobile-DngSOqYr.js +1 -0
  7. package/dist/assets/ProxyStatsModal-CWSIELLj.js +1 -0
  8. package/dist/assets/{_baseUniq-CwQykUuc.js → _baseUniq-6ZLcdMmL.js} +1 -1
  9. package/dist/assets/{arc-EdUdBxtG.js → arc-Dirf6EZP.js} +1 -1
  10. package/dist/assets/{architectureDiagram-Q4EWVU46-DtBIdElY.js → architectureDiagram-Q4EWVU46-BfIhmu_I.js} +1 -1
  11. package/dist/assets/{blockDiagram-DXYQGD6D-guNZIKkZ.js → blockDiagram-DXYQGD6D-D0y4ALi1.js} +1 -1
  12. package/dist/assets/{c4Diagram-AHTNJAMY-xV5vB-Z9.js → c4Diagram-AHTNJAMY-Dlfp4SUK.js} +1 -1
  13. package/dist/assets/{channel-DYEktNap.js → channel-CaNcU8SI.js} +1 -1
  14. package/dist/assets/{chunk-4BX2VUAB-ZTkItpvj.js → chunk-4BX2VUAB-ki0vHNS-.js} +1 -1
  15. package/dist/assets/{chunk-4TB4RGXK-dYIewyyR.js → chunk-4TB4RGXK-D8yO8Iuv.js} +1 -1
  16. package/dist/assets/{chunk-55IACEB6-CSsPNgPX.js → chunk-55IACEB6-BvIgaNPO.js} +1 -1
  17. package/dist/assets/{chunk-EDXVE4YY-CnMiuqzQ.js → chunk-EDXVE4YY-DF2Of_VO.js} +1 -1
  18. package/dist/assets/{chunk-FMBD7UC4-CVpATPZ3.js → chunk-FMBD7UC4-BIw7SXs-.js} +1 -1
  19. package/dist/assets/{chunk-OYMX7WX6-Dpq4s8dW.js → chunk-OYMX7WX6-AB76s0A0.js} +1 -1
  20. package/dist/assets/{chunk-QZHKN3VN-Bnojh6X6.js → chunk-QZHKN3VN-CuN2LCFc.js} +1 -1
  21. package/dist/assets/{chunk-YZCP3GAM-BYqK4Dsa.js → chunk-YZCP3GAM-D4DLBzBN.js} +1 -1
  22. package/dist/assets/classDiagram-6PBFFD2Q-DLJzWgYm.js +1 -0
  23. package/dist/assets/classDiagram-v2-HSJHXN6E-DLJzWgYm.js +1 -0
  24. package/dist/assets/clone-H_VGsHn9.js +1 -0
  25. package/dist/assets/{cose-bilkent-S5V4N54A-OJ7mmWyV.js → cose-bilkent-S5V4N54A-Bn485PX9.js} +1 -1
  26. package/dist/assets/{dagre-KV5264BT-B1Kwd_Rk.js → dagre-KV5264BT-e4QVCf17.js} +1 -1
  27. package/dist/assets/{diagram-5BDNPKRD-D8qrw8sp.js → diagram-5BDNPKRD-_SBFGgnf.js} +1 -1
  28. package/dist/assets/{diagram-G4DWMVQ6-ip-N4ho8.js → diagram-G4DWMVQ6-Bz1ovxLs.js} +1 -1
  29. package/dist/assets/{diagram-MMDJMWI5-2PZaskUF.js → diagram-MMDJMWI5-B-oojW8K.js} +1 -1
  30. package/dist/assets/{diagram-TYMM5635-CeAOQUZP.js → diagram-TYMM5635-CTAyNaOj.js} +1 -1
  31. package/dist/assets/{erDiagram-SMLLAGMA-BCBaEYEb.js → erDiagram-SMLLAGMA-BfvKAqq2.js} +1 -1
  32. package/dist/assets/{flowDiagram-DWJPFMVM-CsGyIqcX.js → flowDiagram-DWJPFMVM-BpTEZEAe.js} +1 -1
  33. package/dist/assets/{ganttDiagram-T4ZO3ILL-COsj3FZ-.js → ganttDiagram-T4ZO3ILL-DUD_vkyE.js} +1 -1
  34. package/dist/assets/{gitGraphDiagram-UUTBAWPF-DvQVQGJG.js → gitGraphDiagram-UUTBAWPF-DnlXcTud.js} +1 -1
  35. package/dist/assets/{graph-BSB9fDSP.js → graph-D4xsUKSn.js} +1 -1
  36. package/dist/assets/{index-DKufsAzx.js → index-84-BjpGl.js} +1 -1
  37. package/dist/assets/index-BDKwOnE3.js +2 -0
  38. package/dist/assets/{index-Bo-12bug.js → index-BniBLRtZ.js} +1 -1
  39. package/dist/assets/{index-BRw4Iz2O.js → index-C2Zo3KOg.js} +1 -1
  40. package/dist/assets/{index-BuVComcY.js → index-CWZoKT7e.js} +1 -1
  41. package/dist/assets/{index-eM2ih5n7.js → index-D-4EsFub.js} +1 -1
  42. package/dist/assets/{index-CNNAYjkF.js → index-D3KzRmkP.js} +1 -1
  43. package/dist/assets/{index-DXBfwPYD.js → index-NGgdum2i.js} +1 -1
  44. package/dist/assets/{infoDiagram-42DDH7IO-eNucS4iW.js → infoDiagram-42DDH7IO-BbdvmCDt.js} +1 -1
  45. package/dist/assets/{ishikawaDiagram-UXIWVN3A-Dxxj-gUX.js → ishikawaDiagram-UXIWVN3A-oZBOPoTO.js} +1 -1
  46. package/dist/assets/{journeyDiagram-VCZTEJTY-yJJzVEBb.js → journeyDiagram-VCZTEJTY-DWoH0hs9.js} +1 -1
  47. package/dist/assets/{jszip.min-ByI0ds5u.js → jszip.min-I5y9RZhq.js} +1 -1
  48. package/dist/assets/{kanban-definition-6JOO6SKY-2rfTi2zw.js → kanban-definition-6JOO6SKY-BLHtjKsS.js} +1 -1
  49. package/dist/assets/{layout-DTmHj0Wm.js → layout-C116SNcQ.js} +1 -1
  50. package/dist/assets/{linear-BOA3a9FE.js → linear-Br08WCkF.js} +1 -1
  51. package/dist/assets/{mermaid.core-5H-NR79H.js → mermaid.core-D-4dTrvI.js} +2 -2
  52. package/dist/assets/{min-CpjFTEec.js → min-khs74HbH.js} +1 -1
  53. package/dist/assets/{mindmap-definition-QFDTVHPH-B8KCjFgB.js → mindmap-definition-QFDTVHPH-C3_yItFF.js} +1 -1
  54. package/dist/assets/{pieDiagram-DEJITSTG-BARy96g5.js → pieDiagram-DEJITSTG-ByCwM2_P.js} +1 -1
  55. package/dist/assets/{quadrantDiagram-34T5L4WZ-DYoTapuI.js → quadrantDiagram-34T5L4WZ-CjV65X1r.js} +1 -1
  56. package/dist/assets/{requirementDiagram-MS252O5E-EeANLLK2.js → requirementDiagram-MS252O5E-DFJ_dbtV.js} +1 -1
  57. package/dist/assets/{sankeyDiagram-XADWPNL6-DmQ-YVPj.js → sankeyDiagram-XADWPNL6-BBvXoBfj.js} +1 -1
  58. package/dist/assets/seqResourceLoaders-C34z9uOa.css +41 -0
  59. package/dist/assets/seqResourceLoaders-JJzgZZzU.js +2 -0
  60. package/dist/assets/{sequenceDiagram-FGHM5R23-SZtW0rYC.js → sequenceDiagram-FGHM5R23-BJM04Y3N.js} +1 -1
  61. package/dist/assets/{stateDiagram-FHFEXIEX-CEygsIld.js → stateDiagram-FHFEXIEX-DI2q_Kkn.js} +1 -1
  62. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BU1XPhBe.js → stateDiagram-v2-QKLJ7IA2-CrGC6kEY.js} +1 -1
  63. package/dist/assets/{timeline-definition-GMOUNBTQ-DhPBaQBt.js → timeline-definition-GMOUNBTQ-JcfRUsjD.js} +1 -1
  64. package/dist/assets/vendor-antd-DxEVqVhD.js +17 -0
  65. package/dist/assets/vendor-codemirror-CgNlkZva.js +1 -0
  66. package/dist/assets/{vendor-mdxeditor-Cz09a1_I.js → vendor-mdxeditor-DoreQf4A.js} +2 -2
  67. package/dist/assets/{vendor-qrcode-qbbntD6-.js → vendor-qrcode-q1CKtOyX.js} +1 -1
  68. package/dist/assets/{vendor-virtuoso-Sk8fQ4Hx.js → vendor-virtuoso-BXGe9q7E.js} +1 -1
  69. package/dist/assets/{vennDiagram-DHZGUBPP-NniTHxDw.js → vennDiagram-DHZGUBPP-mAHR1Ugj.js} +1 -1
  70. package/dist/assets/{wardley-RL74JXVD-D9SNOrDw.js → wardley-RL74JXVD-mlTq_P-4.js} +1 -1
  71. package/dist/assets/{wardleyDiagram-NUSXRM2D-DvHXKNza.js → wardleyDiagram-NUSXRM2D-BDGfajRb.js} +1 -1
  72. package/dist/assets/{xychartDiagram-5P7HB3ND-CaiwPTQB.js → xychartDiagram-5P7HB3ND-Cf2pR1mV.js} +1 -1
  73. package/dist/index.html +4 -4
  74. package/package.json +1 -1
  75. package/plugins/README.md +9 -1
  76. package/server/interceptor.js +1 -1
  77. package/server/lib/builtin-model-prompts.js +1 -1
  78. package/server/lib/chat-queue.js +1 -1
  79. package/server/lib/credential-access.js +1 -1
  80. package/server/lib/credential-migrate.js +1 -1
  81. package/server/lib/error-report.js +26 -0
  82. package/server/lib/json-store.js +1 -1
  83. package/server/lib/launch-config.js +1 -1
  84. package/server/lib/log-watcher.js +1 -1
  85. package/server/lib/plugin-host.mjs +198 -0
  86. package/server/lib/plugin-loader.js +346 -90
  87. package/server/lib/plugin-manager.js +46 -2
  88. package/server/lib/plugin-manifest.js +329 -0
  89. package/server/lib/plugin-panels.js +183 -0
  90. package/server/lib/plugin-proxy.js +408 -0
  91. package/server/lib/plugin-runtime.js +775 -0
  92. package/server/lib/plugin-service-manager.js +463 -0
  93. package/server/lib/proxy/proxy-retry.js +1 -1
  94. package/server/lib/proxy/proxy-stats.js +1 -1
  95. package/server/lib/resume-watchdog.js +1 -1
  96. package/server/lib/same-origin.js +13 -3
  97. package/server/lib/sdk-manager.js +117 -23
  98. package/server/lib/sdk-session-index.js +1 -1
  99. package/server/lib/stats-worker.js +1 -1
  100. package/server/lib/system-prompt-files.js +1 -1
  101. package/server/lib/system-prompt-live.js +1 -1
  102. package/server/lib/system-prompt-snapshots.js +1 -1
  103. package/server/lib/v2/adapter.js +1 -1
  104. package/server/lib/v2/convert.js +1 -1
  105. package/server/lib/v2/jsonl-read.js +1 -1
  106. package/server/lib/v2/live-feed.js +1 -1
  107. package/server/lib/v2/meta-rows.js +1 -1
  108. package/server/lib/v2/resume-list.js +1 -1
  109. package/server/lib/v2/session-list.js +1 -1
  110. package/server/lib/v2/session-owner.js +1 -1
  111. package/server/lib/v2/v2-writer.js +1 -1
  112. package/server/lib/wire-compress.js +1 -1
  113. package/server/lib/ws-mux.js +1 -1
  114. package/server/proxy.js +1 -1
  115. package/server/pty-manager.js +1 -1
  116. package/server/routes/ask-perm.js +1 -1
  117. package/server/routes/events.js +1 -1
  118. package/server/routes/expert.js +1 -1
  119. package/server/routes/files-content.js +1 -1
  120. package/server/routes/files-fs.js +4 -2
  121. package/server/routes/misc.js +11 -0
  122. package/server/routes/plugin-services.js +104 -0
  123. package/server/routes/plugins.js +145 -17
  124. package/server/routes/preferences.js +1 -1
  125. package/server/routes/resume.js +1 -1
  126. package/server/routes/sdk-context.js +1 -1
  127. package/server/routes/sdk-mcp-status.js +1 -1
  128. package/server/routes/workspaces.js +1 -1
  129. package/server/server.js +370 -173
  130. package/dist/assets/App-D2zrtS4P.js +0 -2
  131. package/dist/assets/App-DuDeDOuX.css +0 -1
  132. package/dist/assets/Mobile-CU-g7cTn.js +0 -1
  133. package/dist/assets/ProxyStatsModal-CavWQfBA.js +0 -1
  134. package/dist/assets/classDiagram-6PBFFD2Q-ClGF33bF.js +0 -1
  135. package/dist/assets/classDiagram-v2-HSJHXN6E-ClGF33bF.js +0 -1
  136. package/dist/assets/clone-CFj55h49.js +0 -1
  137. package/dist/assets/index-D2maXxkC.js +0 -2
  138. package/dist/assets/seqResourceLoaders-DZF6ofB7.css +0 -41
  139. package/dist/assets/seqResourceLoaders-jOSz1x7F.js +0 -2
  140. package/dist/assets/vendor-antd-CS_S2mBN.js +0 -17
  141. package/dist/assets/vendor-codemirror-B5l-VbBO.js +0 -1
@@ -1,17 +1,84 @@
1
1
  // Plugin management routes (moved verbatim from server.js handleRequest).
2
+ // Write operations go through the plugin runtime's single-flight queue so service
3
+ // plugins are deactivated/stopped/re-spawned — never a bare loadPlugins() that would
4
+ // leave child processes behind.
2
5
  import { existsSync, unlinkSync } from 'node:fs';
3
6
  import { join } from 'node:path';
4
- import { loadPlugins, getPluginsInfo, getPluginsDir } from '../lib/plugin-loader.js';
5
- import { uploadPlugins, installPluginFromUrl } from '../lib/plugin-manager.js';
7
+ import { getPluginsInfo, getPluginsDir, getPluginEntries } from '../lib/plugin-loader.js';
8
+ import { uploadPlugins, installPluginFromUrl, deletePluginDir } from '../lib/plugin-manager.js';
9
+ import {
10
+ reloadRuntime,
11
+ unloadPlugin,
12
+ restartPlugin,
13
+ getRuntimePluginStatus,
14
+ isRuntimeStopping,
15
+ } from '../lib/plugin-runtime.js';
16
+ import { buildPanelDescriptor } from '../lib/plugin-panels.js';
6
17
  import { SERVER_LIB } from '../_paths.js';
18
+ import { reportSwallowed } from '../lib/error-report.js';
19
+ import { isAdminReq } from '../lib/is-admin.js';
20
+ import { isSameOriginBrowserRequest } from '../lib/same-origin.js';
21
+
22
+ // Write endpoints execute arbitrary plugin code (reload/restart), install remote files,
23
+ // or delete on-disk plugins. They require an admin AND a same-origin browser context:
24
+ // the global ACAO:* prelude would otherwise let a cross-site page drive a localhost
25
+ // server that trusts its socket peer. CLI/native clients (no Origin) stay allowed.
26
+ function rejectWrite(req, res, isLocal, parsedUrl) {
27
+ // Refuse during the shutdown teardown window: a reload/restart accepted here could
28
+ // spawn a child after onServerStopping already swept the runtime, leaking a lock file.
29
+ if (isRuntimeStopping()) {
30
+ res.writeHead(503, { 'Content-Type': 'application/json' });
31
+ res.end(JSON.stringify({ error: 'server is stopping' }));
32
+ return true;
33
+ }
34
+ if (isAdminReq(req, isLocal) && isSameOriginBrowserRequest(req, parsedUrl)) return false;
35
+ res.writeHead(403, { 'Content-Type': 'application/json' });
36
+ res.end(JSON.stringify({ error: 'Forbidden: admin and same-origin required' }));
37
+ return true;
38
+ }
39
+
40
+ function decorateWithRuntime(plugins) {
41
+ return plugins.map(p => {
42
+ // Only service plugins have a service status; lifecycle-only dir plugins also get a
43
+ // runtime entry, but exposing it as serviceStatus would imply a service exists.
44
+ if (p.type !== 'service') return p;
45
+ const status = getRuntimePluginStatus(p.name);
46
+ return status ? { ...p, serviceStatus: status } : p;
47
+ });
48
+ }
7
49
 
8
50
  function pluginsList(req, res) {
9
- const plugins = getPluginsInfo();
51
+ const plugins = decorateWithRuntime(getPluginsInfo());
10
52
  res.writeHead(200, { 'Content-Type': 'application/json' });
11
53
  res.end(JSON.stringify({ plugins, pluginsDir: getPluginsDir() }));
12
54
  }
13
55
 
14
- async function pluginsDelete(req, res, parsedUrl) {
56
+ async function pluginsDelete(req, res, parsedUrl, isLocal) {
57
+ if (rejectWrite(req, res, isLocal, parsedUrl)) return;
58
+ const dir = parsedUrl.searchParams.get('dir');
59
+ if (dir) {
60
+ try {
61
+ const unloaded = await unloadPlugin(dir, 'delete');
62
+ if (!unloaded.ok) {
63
+ res.writeHead(503, { 'Content-Type': 'application/json' });
64
+ res.end(JSON.stringify({ error: unloaded.error || 'plugin runtime is stopping' }));
65
+ return;
66
+ }
67
+ deletePluginDir(getPluginsDir(), dir);
68
+ const result = await reloadRuntime();
69
+ if (!result.ok) {
70
+ res.writeHead(503, { 'Content-Type': 'application/json' });
71
+ res.end(JSON.stringify({ error: result.error || 'plugin runtime is stopping' }));
72
+ return;
73
+ }
74
+ res.writeHead(200, { 'Content-Type': 'application/json' });
75
+ res.end(JSON.stringify({ ok: true, plugins: decorateWithRuntime(getPluginsInfo()), pluginsDir: getPluginsDir(), generation: result.generation }));
76
+ } catch (err) {
77
+ res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' });
78
+ res.end(JSON.stringify({ error: err.message }));
79
+ }
80
+ return;
81
+ }
15
82
  const file = parsedUrl.searchParams.get('file');
16
83
  if (!file || file.includes('..') || file.includes('/') || file.includes('\\')) {
17
84
  res.writeHead(400, { 'Content-Type': 'application/json' });
@@ -26,39 +93,93 @@ async function pluginsDelete(req, res, parsedUrl) {
26
93
  return;
27
94
  }
28
95
  unlinkSync(filePath);
29
- await loadPlugins();
30
- const plugins = getPluginsInfo();
96
+ const result = await reloadRuntime();
97
+ if (!result.ok) {
98
+ res.writeHead(503, { 'Content-Type': 'application/json' });
99
+ res.end(JSON.stringify({ error: result.error || 'plugin runtime is stopping' }));
100
+ return;
101
+ }
31
102
  res.writeHead(200, { 'Content-Type': 'application/json' });
32
- res.end(JSON.stringify({ ok: true, plugins, pluginsDir: getPluginsDir() }));
103
+ res.end(JSON.stringify({ ok: true, plugins: decorateWithRuntime(getPluginsInfo()), pluginsDir: getPluginsDir(), generation: result.generation }));
33
104
  } catch (err) {
34
105
  res.writeHead(500, { 'Content-Type': 'application/json' });
35
106
  res.end(JSON.stringify({ error: err.message }));
36
107
  }
37
108
  }
38
109
 
39
- async function pluginsReload(req, res) {
110
+ async function pluginsReload(req, res, parsedUrl, isLocal) {
111
+ if (rejectWrite(req, res, isLocal, parsedUrl)) return;
40
112
  try {
41
- await loadPlugins();
42
- const plugins = getPluginsInfo();
113
+ // ?reason=disable: the UI disable toggle goes through preferences + reload, so deactivate must receive 'disable'
114
+ const reason = parsedUrl && parsedUrl.searchParams.get('reason');
115
+ const result = await reloadRuntime(reason === 'disable' ? { reason: 'disable' } : {});
116
+ if (!result.ok) {
117
+ res.writeHead(503, { 'Content-Type': 'application/json' });
118
+ res.end(JSON.stringify({ error: result.error || 'plugin runtime is stopping' }));
119
+ return;
120
+ }
43
121
  res.writeHead(200, { 'Content-Type': 'application/json' });
44
- res.end(JSON.stringify({ ok: true, plugins, pluginsDir: getPluginsDir() }));
122
+ res.end(JSON.stringify({ ok: true, plugins: decorateWithRuntime(getPluginsInfo()), pluginsDir: getPluginsDir(), generation: result.generation }));
123
+ } catch (err) {
124
+ reportSwallowed('plugins.reload', err);
125
+ res.writeHead(500, { 'Content-Type': 'application/json' });
126
+ res.end(JSON.stringify({ error: err.message }));
127
+ }
128
+ }
129
+
130
+ async function pluginsRestart(req, res, parsedUrl, isLocal) {
131
+ if (rejectWrite(req, res, isLocal, parsedUrl)) return;
132
+ const name = parsedUrl.searchParams.get('name');
133
+ if (!name) {
134
+ res.writeHead(400, { 'Content-Type': 'application/json' });
135
+ res.end(JSON.stringify({ error: 'name is required' }));
136
+ return;
137
+ }
138
+ try {
139
+ const result = await restartPlugin(name);
140
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json' });
141
+ res.end(JSON.stringify(result));
142
+ } catch (err) {
143
+ reportSwallowed('plugins.restart', err);
144
+ res.writeHead(500, { 'Content-Type': 'application/json' });
145
+ res.end(JSON.stringify({ error: err.message }));
146
+ }
147
+ }
148
+
149
+ function pluginPanelsList(req, res) {
150
+ try {
151
+ const panels = [];
152
+ for (const entry of getPluginEntries()) {
153
+ if (entry.shape !== 'dir' || !entry.enabled || entry.loadError || !entry.panel) continue;
154
+ const status = getRuntimePluginStatus(entry.name);
155
+ panels.push(buildPanelDescriptor(entry, { serviceStatus: status }));
156
+ }
157
+ panels.sort((a, b) => a.name.localeCompare(b.name));
158
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
159
+ res.end(JSON.stringify({ ok: true, panels }));
45
160
  } catch (err) {
161
+ reportSwallowed('plugins.panels', err);
46
162
  res.writeHead(500, { 'Content-Type': 'application/json' });
47
163
  res.end(JSON.stringify({ error: err.message }));
48
164
  }
49
165
  }
50
166
 
51
167
  function pluginsUpload(req, res, parsedUrl, isLocal, deps) {
168
+ if (rejectWrite(req, res, isLocal, parsedUrl)) return;
52
169
  let body = '';
53
170
  req.on('data', chunk => { body += chunk; if (body.length > deps.MAX_POST_BODY) req.destroy(); });
54
171
  req.on('end', async () => {
55
172
  try {
56
173
  const { files: fileList } = JSON.parse(body);
57
174
  uploadPlugins(getPluginsDir(), fileList);
58
- await loadPlugins();
59
- const plugins = getPluginsInfo();
175
+ const result = await reloadRuntime();
176
+ if (!result.ok) {
177
+ res.writeHead(503, { 'Content-Type': 'application/json' });
178
+ res.end(JSON.stringify({ error: result.error || 'plugin runtime is stopping' }));
179
+ return;
180
+ }
60
181
  res.writeHead(200, { 'Content-Type': 'application/json' });
61
- res.end(JSON.stringify({ ok: true, plugins, pluginsDir: getPluginsDir() }));
182
+ res.end(JSON.stringify({ ok: true, plugins: decorateWithRuntime(getPluginsInfo()), pluginsDir: getPluginsDir(), generation: result.generation }));
62
183
  } catch (err) {
63
184
  const status = err.statusCode || 500;
64
185
  res.writeHead(status, { 'Content-Type': 'application/json' });
@@ -68,6 +189,7 @@ function pluginsUpload(req, res, parsedUrl, isLocal, deps) {
68
189
  }
69
190
 
70
191
  function pluginsInstallFromUrl(req, res, parsedUrl, isLocal, deps) {
192
+ if (rejectWrite(req, res, isLocal, parsedUrl)) return;
71
193
  let body = '';
72
194
  req.on('data', chunk => { body += chunk; if (body.length > deps.MAX_POST_BODY) req.destroy(); });
73
195
  req.on('end', async () => {
@@ -75,10 +197,14 @@ function pluginsInstallFromUrl(req, res, parsedUrl, isLocal, deps) {
75
197
  const { url: fileUrl } = JSON.parse(body);
76
198
  const extractScript = join(SERVER_LIB, 'extract-plugin-name.mjs');
77
199
  await installPluginFromUrl(getPluginsDir(), fileUrl, extractScript);
78
- await loadPlugins();
79
- const plugins = getPluginsInfo();
200
+ const result = await reloadRuntime();
201
+ if (!result.ok) {
202
+ res.writeHead(503, { 'Content-Type': 'application/json' });
203
+ res.end(JSON.stringify({ error: result.error || 'plugin runtime is stopping' }));
204
+ return;
205
+ }
80
206
  res.writeHead(200, { 'Content-Type': 'application/json' });
81
- res.end(JSON.stringify({ ok: true, plugins, pluginsDir: getPluginsDir() }));
207
+ res.end(JSON.stringify({ ok: true, plugins: decorateWithRuntime(getPluginsInfo()), pluginsDir: getPluginsDir(), generation: result.generation }));
82
208
  } catch (err) {
83
209
  const status = err.statusCode || 500;
84
210
  res.writeHead(status, { 'Content-Type': 'application/json' });
@@ -91,6 +217,8 @@ export const pluginsRoutes = [
91
217
  { method: 'GET', match: 'exact', path: '/api/plugins', handler: pluginsList },
92
218
  { method: 'DELETE', match: 'exact', path: '/api/plugins', handler: pluginsDelete },
93
219
  { method: 'POST', match: 'exact', path: '/api/plugins/reload', handler: pluginsReload },
220
+ { method: 'POST', match: 'exact', path: '/api/plugins/restart', handler: pluginsRestart },
221
+ { method: 'GET', match: 'exact', path: '/api/plugin-panels', handler: pluginPanelsList },
94
222
  { method: 'POST', match: 'exact', path: '/api/plugins/upload', handler: pluginsUpload },
95
223
  { method: 'POST', match: 'exact', path: '/api/plugins/install-from-url', handler: pluginsInstallFromUrl },
96
224
  ];
@@ -7,7 +7,7 @@ import { PROFILE_PATH, _defaultConfig, getActiveProfileId, getStoredRoles, isOff
7
7
  import { migrateProxyProfileList, isValidRoleValue, PROXY_ROLE_KEYS } from '../lib/interceptor-core.js';
8
8
  import { DEFAULT_RETRY_CONFIG, validateRetryConfig, resolveRetryConfig } from '../lib/proxy/proxy-retry.js';
9
9
  import { discoverCcSwitchProviders, mergeImportedProfiles } from '../lib/ccswitch-import.js';
10
- import { reportSwallowed } from '@ccv/core/error-report';
10
+ import { reportSwallowed } from '../lib/error-report.js';
11
11
  import { setLang } from '../i18n.js';
12
12
  import { reconcileVoicePackPrefs as vpReconcile } from '../lib/voice-pack-manager.js';
13
13
  import { readClaudeProjectModel } from '../lib/context-watcher.js';
@@ -53,7 +53,7 @@ import { bumpWorkspacesVersion } from '../lib/file-access-policy.js';
53
53
  import { isAdminReq } from '../lib/is-admin.js';
54
54
  import { isSameOriginBrowserRequest } from '../lib/same-origin.js';
55
55
  import { sseWrite } from '../lib/wire-compress.js';
56
- import { reportSwallowed } from '@ccv/core/error-report';
56
+ import { reportSwallowed } from '../lib/error-report.js';
57
57
  import { findSdkSessionByInstance, listSdkSessionInstances } from '../lib/sdk-session-index.js';
58
58
 
59
59
  function sendJson(res, code, obj) {
@@ -2,7 +2,7 @@
2
2
  // the automatic turn-end/compact refreshes flow over the WS broadcast instead. A
3
3
  // `session-inactive` outcome is a NORMAL state (no turn run yet / query dead), so it
4
4
  // returns 200 with { ok:false, reason } rather than an HTTP error.
5
- import { reportSwallowed } from '@ccv/core/error-report';
5
+ import { reportSwallowed } from '../lib/error-report.js';
6
6
 
7
7
  async function sdkContextUsage(req, res) {
8
8
  try {
@@ -2,7 +2,7 @@
2
2
  // pull; there is no turn-driven refresh (MCP status is not turn-correlated). A
3
3
  // `session-inactive` outcome is a NORMAL state (no turn run yet / query dead), so it
4
4
  // returns 200 with { ok:false, reason } rather than an HTTP error.
5
- import { reportSwallowed } from '@ccv/core/error-report';
5
+ import { reportSwallowed } from '../lib/error-report.js';
6
6
 
7
7
  async function sdkMcpStatus(req, res) {
8
8
  try {
@@ -4,7 +4,7 @@ import { basename } from 'node:path';
4
4
  import { initForWorkspace, resetWorkspace, getLiveLogSource, markContinuedLaunch, markForkSession, markResumeSession, isContinuedLaunch, clearScopeState } from '../interceptor.js';
5
5
  import { LOG_DIR } from '../../findcc.js';
6
6
  import { migrationStatus } from '../lib/v2/migrate-prompt.js';
7
- import { reportSwallowed } from '@ccv/core/error-report';
7
+ import { reportSwallowed } from '../lib/error-report.js';
8
8
 
9
9
  // P2: claude continuation flags — the workspace launcher injects `-c` itself
10
10
  // (WorkspaceList's logCount heuristic), so argv scanning in cli.js never sees