termdock 1.4.107 → 1.4.109

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 (79) hide show
  1. package/.desktop-dist/main.js +345 -1
  2. package/dist/client/assets/{ModelPreview-CoR41z2e.js → ModelPreview-Cxk4IZLv.js} +1 -1
  3. package/dist/client/assets/{abnfDiagram-VRR7QNED-CXFrMEcU.js → abnfDiagram-VRR7QNED-Bz2HWRRX.js} +1 -1
  4. package/dist/client/assets/{arc-DXT1yvki.js → arc-D3vf35f_.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-B4Tl5gm4.js → architectureDiagram-ZJ3FMSHR-LI-OXsDp.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-677ZJIJ3-D8OY0bG2.js → blockDiagram-677ZJIJ3-Dm1JtLIo.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-LMCZKHZV-BHIhcBy3.js → c4Diagram-LMCZKHZV-DSkxlBnU.js} +1 -1
  8. package/dist/client/assets/channel-qZtjoGxW.js +1 -0
  9. package/dist/client/assets/{chunk-2Q5K7J3B-NFF-sZCT.js → chunk-2Q5K7J3B-Wd2AO6yQ.js} +1 -1
  10. package/dist/client/assets/{chunk-32BRIVSS-Cysd2WNQ.js → chunk-32BRIVSS-CIBkFIAg.js} +1 -1
  11. package/dist/client/assets/{chunk-5VM5RSS4-BRHAAr29.js → chunk-5VM5RSS4-9veDV0NJ.js} +1 -1
  12. package/dist/client/assets/{chunk-EX3LRPZG-BovDZYVQ.js → chunk-EX3LRPZG-DVi5mqrV.js} +1 -1
  13. package/dist/client/assets/{chunk-JWPE2WC7-ByWUvKGO.js → chunk-JWPE2WC7-DZc_3_5i.js} +1 -1
  14. package/dist/client/assets/{chunk-MOJQB5TN-GWk63RII.js → chunk-MOJQB5TN-Czq1YGEA.js} +1 -1
  15. package/dist/client/assets/{chunk-RYQCIY6F-GvzHMCqG.js → chunk-RYQCIY6F-BN3UqH_7.js} +1 -1
  16. package/dist/client/assets/{chunk-V7JOEXUC-BoiSCBad.js → chunk-V7JOEXUC-BgDCEp_z.js} +1 -1
  17. package/dist/client/assets/{chunk-VR4S4FIN-ByS_rjkq.js → chunk-VR4S4FIN-BnyRVZSn.js} +1 -1
  18. package/dist/client/assets/{chunk-XXDRQBXY-D9fRuB0M.js → chunk-XXDRQBXY-KOa0rQIz.js} +1 -1
  19. package/dist/client/assets/classDiagram-OUVF2IWQ-CnWXW1tk.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-EOCWNBFH-CnWXW1tk.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-JH36ORCC-BVOwkeRc.js → cose-bilkent-JH36ORCC-B-P2BstB.js} +1 -1
  22. package/dist/client/assets/{cynefin-VYW2F7L2-8rNTW_mJ.js → cynefin-VYW2F7L2-DOSg0Q6x.js} +1 -1
  23. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-DHASHiYD.js → cynefinDiagram-TSTJHNR4-Bm8Ct13U.js} +1 -1
  24. package/dist/client/assets/{dagre-VKFMJZFB-wWqVXFNE.js → dagre-VKFMJZFB-Cnw99n0y.js} +1 -1
  25. package/dist/client/assets/{diagram-FQU43EPY-Bp2E7omd.js → diagram-FQU43EPY-B742HZhK.js} +1 -1
  26. package/dist/client/assets/{diagram-G47NLZAW-DajrBIg7.js → diagram-G47NLZAW-BGXKr2u9.js} +1 -1
  27. package/dist/client/assets/{diagram-NH7WQ7WH-ByGTRgNg.js → diagram-NH7WQ7WH-DIyEYN64.js} +1 -1
  28. package/dist/client/assets/{diagram-OA4YK3LP-bsRCtWPe.js → diagram-OA4YK3LP-BdRMWU80.js} +1 -1
  29. package/dist/client/assets/{diagram-WEI45ONY-9f1_0Bjd.js → diagram-WEI45ONY-Bu-180k8.js} +1 -1
  30. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-DyvJDQQ5.js → ebnfDiagram-CCIWWBDH-Djcej7Pq.js} +1 -1
  31. package/dist/client/assets/{erDiagram-Q63AITRT-DwLHRiMz.js → erDiagram-Q63AITRT-D70JxkGr.js} +1 -1
  32. package/dist/client/assets/{flowDiagram-23GEKE2U--9lOgA3z.js → flowDiagram-23GEKE2U-DovWUM1i.js} +1 -1
  33. package/dist/client/assets/{ganttDiagram-NO4QXBWP-j9C0N_DS.js → ganttDiagram-NO4QXBWP-Izsfqq5i.js} +1 -1
  34. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-lHdVffsh.js → gitGraphDiagram-IHSO6WYX-BTZo0N08.js} +1 -1
  35. package/dist/client/assets/index-BE4Js_ss.js +456 -0
  36. package/dist/client/assets/index-CRIZk117.css +1 -0
  37. package/dist/client/assets/{infoDiagram-FWYZ7A6U-D8kAsmdn.js → infoDiagram-FWYZ7A6U-Ina5uVvZ.js} +1 -1
  38. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CTY9BLyE.js → ishikawaDiagram-FXEZZL3T-iUdzuACl.js} +1 -1
  39. package/dist/client/assets/{journeyDiagram-5HDEW3XC-B3QO8wF2.js → journeyDiagram-5HDEW3XC-lh15NShH.js} +1 -1
  40. package/dist/client/assets/{kanban-definition-HUTT4EX6-CesXysl2.js → kanban-definition-HUTT4EX6-CKZooxMa.js} +1 -1
  41. package/dist/client/assets/{linear-wxKR9xNu.js → linear-BrmrQdSX.js} +1 -1
  42. package/dist/client/assets/{mermaid.core-DqsMKQ0n.js → mermaid.core-fbY4nFyi.js} +4 -4
  43. package/dist/client/assets/{mindmap-definition-LN4V7U3C-CCfpEZEP.js → mindmap-definition-LN4V7U3C-BHUhjMUf.js} +1 -1
  44. package/dist/client/assets/{pegDiagram-2B236MQR-DEje4xdU.js → pegDiagram-2B236MQR-8qcHqxsE.js} +1 -1
  45. package/dist/client/assets/{pieDiagram-ENE6RG2P-B5guqQ7K.js → pieDiagram-ENE6RG2P-CpIkkExH.js} +1 -1
  46. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-D58kwRrP.js → quadrantDiagram-ABIIQ3AL-hWzvYsWG.js} +1 -1
  47. package/dist/client/assets/{railroadDiagram-RFXS5EU6-D9VFfFu0.js → railroadDiagram-RFXS5EU6-2nhrE9bK.js} +1 -1
  48. package/dist/client/assets/{requirementDiagram-TGXJPOKE-B0q4l0cr.js → requirementDiagram-TGXJPOKE-B05mEN7w.js} +1 -1
  49. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-DTsXXi0M.js → sankeyDiagram-HTMAVEWB-CXHkBpDN.js} +1 -1
  50. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-3Xpi1nmi.js → sequenceDiagram-DBY2YBRQ-C4wG1r_I.js} +1 -1
  51. package/dist/client/assets/{sizeCapture-X5ZJPWSS-C0jHd0eC.js → sizeCapture-X5ZJPWSS-D_3i42tQ.js} +1 -1
  52. package/dist/client/assets/{stateDiagram-2N3HPSRC-DlDm_dYg.js → stateDiagram-2N3HPSRC-DhEgczRA.js} +1 -1
  53. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-D9gqRttI.js +1 -0
  54. package/dist/client/assets/{swimlanes-5IMT3BWC-C128cuo6.js → swimlanes-5IMT3BWC-DTrpFviv.js} +2 -2
  55. package/dist/client/assets/swimlanesDiagram-G3AALYLV-CNOSFfLB.js +8 -0
  56. package/dist/client/assets/{timeline-definition-FHXFAJF6-BHsLELm-.js → timeline-definition-FHXFAJF6-DLpl_8Za.js} +1 -1
  57. package/dist/client/assets/{vennDiagram-L72KCM5P-DkdQFPIy.js → vennDiagram-L72KCM5P-CgnCFHj7.js} +1 -1
  58. package/dist/client/assets/{wardleyDiagram-EHGQE667-DWaahbD5.js → wardleyDiagram-EHGQE667-BwNfD-cz.js} +1 -1
  59. package/dist/client/assets/{xychartDiagram-FW5EYKEG-CSwOYmgm.js → xychartDiagram-FW5EYKEG--sZgo_Fw.js} +1 -1
  60. package/dist/client/index.html +2 -2
  61. package/dist/client/sw.js +1 -1
  62. package/dist/server/agent/automationStore.js +12 -7
  63. package/dist/server/agent/sessionSearchStore.js +135 -5
  64. package/dist/server/entry.js +24 -1
  65. package/dist/server/routes/filesystem.js +31 -6
  66. package/dist/server/routes/terminal.js +6 -2
  67. package/dist/server/utils/heicPreview.js +50 -0
  68. package/dist/server/utils/runtimeMonitor.js +287 -0
  69. package/dist/server/utils/settings.js +9 -0
  70. package/dist/server/workers/heicPreviewWorker.js +14 -0
  71. package/package.json +3 -1
  72. package/runtime-manifest.json +3 -3
  73. package/dist/client/assets/channel-CXw70OW3.js +0 -1
  74. package/dist/client/assets/classDiagram-OUVF2IWQ-CNF7sVVd.js +0 -1
  75. package/dist/client/assets/classDiagram-v2-EOCWNBFH-CNF7sVVd.js +0 -1
  76. package/dist/client/assets/index-B1FKcn-W.js +0 -451
  77. package/dist/client/assets/index-BloZ2WQg.css +0 -1
  78. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BVOBevpU.js +0 -1
  79. package/dist/client/assets/swimlanesDiagram-G3AALYLV-Bf87D8dr.js +0 -8
@@ -0,0 +1,287 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { monitorEventLoopDelay } from 'node:perf_hooks';
5
+ const SAMPLE_INTERVAL_MS = 10_000;
6
+ const HISTORY_INTERVAL_MS = 60_000;
7
+ const MAX_HISTORY_SAMPLES = 24 * 60;
8
+ function round(value, digits = 2) {
9
+ if (!Number.isFinite(value))
10
+ return 0;
11
+ const scale = 10 ** digits;
12
+ return Math.round(value * scale) / scale;
13
+ }
14
+ function percentile(values, quantile) {
15
+ if (values.length === 0)
16
+ return 0;
17
+ const ordered = [...values].sort((a, b) => a - b);
18
+ return ordered[Math.min(ordered.length - 1, Math.floor(ordered.length * quantile))] ?? 0;
19
+ }
20
+ function readProcessIo() {
21
+ if (process.platform !== 'linux')
22
+ return { readBytes: 0, writeBytes: 0 };
23
+ try {
24
+ const values = new Map(fs.readFileSync('/proc/self/io', 'utf8').trim().split('\n').map((line) => {
25
+ const separator = line.indexOf(':');
26
+ return [line.slice(0, separator), Number(line.slice(separator + 1).trim())];
27
+ }));
28
+ return { readBytes: values.get('read_bytes') ?? 0, writeBytes: values.get('write_bytes') ?? 0 };
29
+ }
30
+ catch {
31
+ return { readBytes: 0, writeBytes: 0 };
32
+ }
33
+ }
34
+ async function collectStorageSnapshot(root, maxEntries = 20_000) {
35
+ const totals = new Map();
36
+ const queue = [root];
37
+ let totalBytes = 0;
38
+ let fileCount = 0;
39
+ let visited = 0;
40
+ while (queue.length > 0 && visited < maxEntries) {
41
+ const directory = queue.shift();
42
+ let entries;
43
+ try {
44
+ entries = await fs.promises.readdir(directory, { withFileTypes: true });
45
+ }
46
+ catch {
47
+ continue;
48
+ }
49
+ for (const entry of entries) {
50
+ if (visited >= maxEntries)
51
+ break;
52
+ visited += 1;
53
+ const entryPath = path.join(directory, entry.name);
54
+ if (entry.isDirectory()) {
55
+ queue.push(entryPath);
56
+ continue;
57
+ }
58
+ if (!entry.isFile())
59
+ continue;
60
+ try {
61
+ const stat = await fs.promises.stat(entryPath);
62
+ totalBytes += stat.size;
63
+ fileCount += 1;
64
+ const topLevelName = path.relative(root, entryPath).split(path.sep)[0] || entry.name;
65
+ totals.set(topLevelName, (totals.get(topLevelName) ?? 0) + stat.size);
66
+ }
67
+ catch { /* The file may disappear while diagnostics walk the directory. */ }
68
+ }
69
+ }
70
+ let freeBytes = null;
71
+ let totalFilesystemBytes = null;
72
+ try {
73
+ const stat = await fs.promises.statfs(root);
74
+ freeBytes = stat.bavail * stat.bsize;
75
+ totalFilesystemBytes = stat.blocks * stat.bsize;
76
+ }
77
+ catch { /* Not available on every supported filesystem. */ }
78
+ return {
79
+ root, totalBytes, fileCount, truncated: queue.length > 0 || visited >= maxEntries,
80
+ freeBytes, totalFilesystemBytes,
81
+ topLevel: [...totals.entries()].map(([name, bytes]) => ({ name, bytes })).sort((a, b) => b.bytes - a.bytes).slice(0, 20),
82
+ };
83
+ }
84
+ export function evaluateRuntimeHealth(sample, storage) {
85
+ const findings = [];
86
+ const add = (severity, code, message, value, threshold) => {
87
+ findings.push({ severity, code, message, value: round(value), threshold });
88
+ };
89
+ if (sample.rssBytes >= 1024 ** 3)
90
+ add('critical', 'PROCESS_RSS_HIGH', 'Termdock 内存占用超过 1 GiB', sample.rssBytes, 1024 ** 3);
91
+ else if (sample.rssBytes >= 512 * 1024 ** 2)
92
+ add('warning', 'PROCESS_RSS_HIGH', 'Termdock 内存占用超过 512 MiB', sample.rssBytes, 512 * 1024 ** 2);
93
+ if (sample.eventLoopDelayP99Ms >= 500)
94
+ add('critical', 'EVENT_LOOP_STALLED', 'Node.js 事件循环 P99 延迟过高', sample.eventLoopDelayP99Ms, 500);
95
+ else if (sample.eventLoopDelayP99Ms >= 100)
96
+ add('warning', 'EVENT_LOOP_SLOW', 'Node.js 事件循环 P99 延迟偏高', sample.eventLoopDelayP99Ms, 100);
97
+ if (sample.diskWriteBytesPerSecond >= 25 * 1024 ** 2)
98
+ add('critical', 'DISK_WRITE_RATE_HIGH', 'Termdock 持续磁盘写入速率过高', sample.diskWriteBytesPerSecond, 25 * 1024 ** 2);
99
+ else if (sample.diskWriteBytesPerSecond >= 5 * 1024 ** 2)
100
+ add('warning', 'DISK_WRITE_RATE_HIGH', 'Termdock 磁盘写入速率偏高', sample.diskWriteBytesPerSecond, 5 * 1024 ** 2);
101
+ if (sample.activeConnections >= 1000)
102
+ add('critical', 'CONNECTION_COUNT_HIGH', 'Termdock 活跃连接数过高', sample.activeConnections, 1000);
103
+ else if (sample.activeConnections >= 200)
104
+ add('warning', 'CONNECTION_COUNT_HIGH', 'Termdock 活跃连接数偏高', sample.activeConnections, 200);
105
+ if (sample.requestsPerSecond >= 0.25 && sample.requestErrorRatio >= 0.25)
106
+ add('critical', 'REQUEST_ERROR_RATIO_HIGH', '最近采样窗口请求错误率过高', sample.requestErrorRatio, 0.25);
107
+ else if (sample.requestsPerSecond >= 0.25 && sample.requestErrorRatio >= 0.1)
108
+ add('warning', 'REQUEST_ERROR_RATIO_HIGH', '最近采样窗口请求错误率偏高', sample.requestErrorRatio, 0.1);
109
+ if (sample.requestLatencyP95Ms >= 5_000)
110
+ add('critical', 'REQUEST_LATENCY_HIGH', '请求 P95 延迟超过 5 秒', sample.requestLatencyP95Ms, 5_000);
111
+ else if (sample.requestLatencyP95Ms >= 1_000)
112
+ add('warning', 'REQUEST_LATENCY_HIGH', '请求 P95 延迟超过 1 秒', sample.requestLatencyP95Ms, 1_000);
113
+ if (storage.totalBytes >= 2 * 1024 ** 3)
114
+ add('critical', 'STATE_DIRECTORY_LARGE', 'Termdock 状态目录超过 2 GiB', storage.totalBytes, 2 * 1024 ** 3);
115
+ else if (storage.totalBytes >= 512 * 1024 ** 2)
116
+ add('warning', 'STATE_DIRECTORY_LARGE', 'Termdock 状态目录超过 512 MiB', storage.totalBytes, 512 * 1024 ** 2);
117
+ if (storage.freeBytes !== null && storage.totalFilesystemBytes) {
118
+ const freeRatio = storage.freeBytes / storage.totalFilesystemBytes;
119
+ if (storage.freeBytes <= 1024 ** 3 || freeRatio <= 0.05)
120
+ add('critical', 'DISK_SPACE_LOW', 'Termdock 所在磁盘剩余空间严重不足', freeRatio, 0.05);
121
+ else if (storage.freeBytes <= 5 * 1024 ** 3 || freeRatio <= 0.1)
122
+ add('warning', 'DISK_SPACE_LOW', 'Termdock 所在磁盘剩余空间偏低', freeRatio, 0.1);
123
+ }
124
+ return findings;
125
+ }
126
+ export class RuntimeMonitor {
127
+ stateDirectory;
128
+ historyPath;
129
+ sampleIntervalMs;
130
+ historyIntervalMs;
131
+ maxHistorySamples;
132
+ sockets = new Map();
133
+ eventLoopHistogram;
134
+ history = [];
135
+ current = null;
136
+ sampleTimer = null;
137
+ lastHistoryAt = 0;
138
+ previousAt = Date.now();
139
+ previousCpu = process.cpuUsage();
140
+ previousIo = readProcessIo();
141
+ closedSocketBytes = { read: 0, written: 0 };
142
+ previousSocketBytes = { read: 0, written: 0 };
143
+ requestDurations = [];
144
+ requestCount = 0;
145
+ requestErrors = 0;
146
+ constructor(options) {
147
+ this.stateDirectory = options.stateDirectory;
148
+ this.historyPath = options.historyPath ?? null;
149
+ this.sampleIntervalMs = options.sampleIntervalMs ?? SAMPLE_INTERVAL_MS;
150
+ this.historyIntervalMs = options.historyIntervalMs ?? HISTORY_INTERVAL_MS;
151
+ this.maxHistorySamples = options.maxHistorySamples ?? MAX_HISTORY_SAMPLES;
152
+ this.eventLoopHistogram = monitorEventLoopDelay({ resolution: 20 });
153
+ this.loadHistory();
154
+ }
155
+ start() {
156
+ if (this.sampleTimer)
157
+ return;
158
+ this.eventLoopHistogram.enable();
159
+ this.sample();
160
+ this.sampleTimer = setInterval(() => this.sample(), this.sampleIntervalMs);
161
+ this.sampleTimer.unref?.();
162
+ }
163
+ stop() {
164
+ if (this.sampleTimer)
165
+ clearInterval(this.sampleTimer);
166
+ this.sampleTimer = null;
167
+ this.eventLoopHistogram.disable();
168
+ }
169
+ trackSocket(socket) {
170
+ if (this.sockets.has(socket))
171
+ return;
172
+ this.sockets.set(socket, { read: socket.bytesRead, written: socket.bytesWritten });
173
+ socket.once('close', () => {
174
+ const baseline = this.sockets.get(socket);
175
+ if (!baseline)
176
+ return;
177
+ this.closedSocketBytes.read += Math.max(0, socket.bytesRead - baseline.read);
178
+ this.closedSocketBytes.written += Math.max(0, socket.bytesWritten - baseline.written);
179
+ this.sockets.delete(socket);
180
+ });
181
+ }
182
+ recordRequest(statusCode, durationMs) {
183
+ this.requestCount += 1;
184
+ if (statusCode >= 400)
185
+ this.requestErrors += 1;
186
+ if (Number.isFinite(durationMs))
187
+ this.requestDurations.push(durationMs);
188
+ if (this.requestDurations.length > 10_000)
189
+ this.requestDurations.splice(0, this.requestDurations.length - 10_000);
190
+ }
191
+ sample(now = Date.now()) {
192
+ const elapsedSeconds = Math.max(0.001, (now - this.previousAt) / 1000);
193
+ const cpu = process.cpuUsage(this.previousCpu);
194
+ const cpuProcessPercent = (cpu.user + cpu.system) / 1_000_000 / elapsedSeconds * 100;
195
+ const io = readProcessIo();
196
+ const socketBytes = this.getSocketBytes();
197
+ const memory = process.memoryUsage();
198
+ const requestCount = this.requestCount;
199
+ const result = {
200
+ timestamp: now,
201
+ uptimeSeconds: round(process.uptime()),
202
+ cpuProcessPercent: round(cpuProcessPercent),
203
+ cpuHostCapacityPercent: round(cpuProcessPercent / Math.max(1, os.availableParallelism())),
204
+ rssBytes: memory.rss,
205
+ heapUsedBytes: memory.heapUsed,
206
+ heapTotalBytes: memory.heapTotal,
207
+ externalBytes: memory.external,
208
+ eventLoopDelayP99Ms: round(this.eventLoopHistogram.percentile(99) / 1_000_000),
209
+ diskReadBytesPerSecond: round(Math.max(0, io.readBytes - this.previousIo.readBytes) / elapsedSeconds),
210
+ diskWriteBytesPerSecond: round(Math.max(0, io.writeBytes - this.previousIo.writeBytes) / elapsedSeconds),
211
+ networkReadBytesPerSecond: round(Math.max(0, socketBytes.read - this.previousSocketBytes.read) / elapsedSeconds),
212
+ networkWriteBytesPerSecond: round(Math.max(0, socketBytes.written - this.previousSocketBytes.written) / elapsedSeconds),
213
+ activeConnections: this.sockets.size,
214
+ requestsPerSecond: round(requestCount / elapsedSeconds),
215
+ requestErrorRatio: round(requestCount > 0 ? this.requestErrors / requestCount : 0, 4),
216
+ requestLatencyP95Ms: round(percentile(this.requestDurations, 0.95)),
217
+ };
218
+ this.current = result;
219
+ this.previousAt = now;
220
+ this.previousCpu = process.cpuUsage();
221
+ this.previousIo = io;
222
+ this.previousSocketBytes = socketBytes;
223
+ this.requestCount = 0;
224
+ this.requestErrors = 0;
225
+ this.requestDurations = [];
226
+ this.eventLoopHistogram.reset();
227
+ if (now - this.lastHistoryAt >= this.historyIntervalMs) {
228
+ this.history.push(result);
229
+ this.history = this.history.slice(-this.maxHistorySamples);
230
+ this.lastHistoryAt = now;
231
+ this.appendHistory(result);
232
+ }
233
+ return result;
234
+ }
235
+ async diagnostics() {
236
+ const current = this.current ?? this.sample();
237
+ const storage = await collectStorageSnapshot(this.stateDirectory);
238
+ const findings = evaluateRuntimeHealth(current, storage);
239
+ return {
240
+ status: findings.some((item) => item.severity === 'critical') ? 'critical' : findings.length > 0 ? 'warning' : 'ok',
241
+ generatedAt: Date.now(), current, history: [...this.history], storage, findings,
242
+ scopes: { cpu: 'termdock-process', memory: 'termdock-process', diskIo: 'termdock-process', networkIo: 'termdock-http-sockets', storage: 'termdock-state-directory' },
243
+ };
244
+ }
245
+ getSocketBytes() {
246
+ let read = this.closedSocketBytes.read;
247
+ let written = this.closedSocketBytes.written;
248
+ for (const [socket, baseline] of this.sockets) {
249
+ read += Math.max(0, socket.bytesRead - baseline.read);
250
+ written += Math.max(0, socket.bytesWritten - baseline.written);
251
+ }
252
+ return { read, written };
253
+ }
254
+ loadHistory() {
255
+ if (!this.historyPath)
256
+ return;
257
+ try {
258
+ this.history = fs.readFileSync(this.historyPath, 'utf8').trim().split('\n').flatMap((line) => {
259
+ try {
260
+ const item = JSON.parse(line);
261
+ return Number.isFinite(item.timestamp) ? [item] : [];
262
+ }
263
+ catch {
264
+ return [];
265
+ }
266
+ }).slice(-this.maxHistorySamples);
267
+ this.lastHistoryAt = this.history.at(-1)?.timestamp ?? 0;
268
+ }
269
+ catch { /* First run or a missing/corrupt history file. */ }
270
+ }
271
+ appendHistory(sample) {
272
+ if (!this.historyPath)
273
+ return;
274
+ void fs.promises.mkdir(path.dirname(this.historyPath), { recursive: true })
275
+ .then(() => fs.promises.appendFile(this.historyPath, `${JSON.stringify(sample)}\n`, { mode: 0o600 }))
276
+ .then(async () => {
277
+ const stat = await fs.promises.stat(this.historyPath);
278
+ if (stat.size <= 2 * 1024 * 1024)
279
+ return;
280
+ const retained = this.history.map((item) => JSON.stringify(item)).join('\n') + '\n';
281
+ const temporary = `${this.historyPath}.${process.pid}.tmp`;
282
+ await fs.promises.writeFile(temporary, retained, { mode: 0o600 });
283
+ await fs.promises.rename(temporary, this.historyPath);
284
+ })
285
+ .catch(() => undefined);
286
+ }
287
+ }
@@ -105,6 +105,7 @@ function normalizeSettings(value) {
105
105
  ? raw.autoRenamePromptPayloadChars
106
106
  : 12_000,
107
107
  newSessionAgentSlug: normalizeNewSessionAgentSlug(raw.newSessionAgentSlug),
108
+ runningSessionButtonEnabled: raw.runningSessionButtonEnabled === true,
108
109
  updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : Date.now(),
109
110
  };
110
111
  }
@@ -399,3 +400,11 @@ export function setNewSessionAgentSlugSetting(slug) {
399
400
  settings.newSessionAgentSlug = normalizeNewSessionAgentSlug(slug);
400
401
  });
401
402
  }
403
+ export function getRunningSessionButtonEnabledSetting() {
404
+ return loadSettings().runningSessionButtonEnabled;
405
+ }
406
+ export function setRunningSessionButtonEnabledSetting(enabled) {
407
+ return updateSettings((settings) => {
408
+ settings.runningSessionButtonEnabled = enabled;
409
+ });
410
+ }
@@ -0,0 +1,14 @@
1
+ import fs from 'node:fs/promises';
2
+ import { parentPort, workerData } from 'node:worker_threads';
3
+ import convert from 'heic-convert';
4
+ async function run() {
5
+ if (!parentPort)
6
+ throw new Error('HEIC preview worker requires a parent port');
7
+ const { filePath } = workerData;
8
+ const input = await fs.readFile(filePath);
9
+ const output = await convert({ buffer: input, format: 'JPEG', quality: 0.9 });
10
+ parentPort.postMessage({ output });
11
+ }
12
+ run().catch((error) => {
13
+ parentPort?.postMessage({ error: error instanceof Error ? error.message : 'Failed to convert HEIC preview' });
14
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termdock",
3
- "version": "1.4.107",
3
+ "version": "1.4.109",
4
4
  "description": "A complete web-based terminal application with modern UI",
5
5
  "type": "module",
6
6
  "main": ".desktop-dist/main.js",
@@ -71,6 +71,7 @@
71
71
  "dompurify": "^3.4.11",
72
72
  "dotenv": "^17.4.2",
73
73
  "express": "^5.2.1",
74
+ "heic-convert": "^2.1.0",
74
75
  "katex": "^0.17.0",
75
76
  "lucide-react": "^1.17.0",
76
77
  "mermaid": "^11.16.0",
@@ -98,6 +99,7 @@
98
99
  "@testing-library/user-event": "^14.5.0",
99
100
  "@types/busboy": "^1.5.4",
100
101
  "@types/express": "^4.17.21",
102
+ "@types/heic-convert": "^2.1.1",
101
103
  "@types/node": "^20.11.0",
102
104
  "@types/react": "^18.2.0",
103
105
  "@types/react-dom": "^18.2.0",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "termdock",
4
- "version": "1.4.107",
4
+ "version": "1.4.109",
5
5
  "runtimeProtocolVersion": 1,
6
6
  "minimumDesktopVersion": "1.4.46",
7
7
  "nodeMajor": 22,
8
- "dependencyHash": "sha256-emuXnf9w0kJF3ksMJVvdI0u8oLHKwZJ2/OzHBAXulZs=",
9
- "serverBundleHash": "sha256-V2lLoyINaqWm8KFZx6orN6c98sLzClQWUnjwPiv9bi0=",
8
+ "dependencyHash": "sha256-3I9X7zF4J4BUYOvxi3lIuWSKfDy6S6GD61w8Ay95Ypw=",
9
+ "serverBundleHash": "sha256-FzVprEN6mYO+IXk9xqujHpNv1s544akAZOVv28fGvww=",
10
10
  "entrypoint": "dist/server/cli.js",
11
11
  "clientEntrypoint": "dist/client/index.html"
12
12
  }
@@ -1 +0,0 @@
1
- import{U as a,C as n}from"./mermaid.core-DqsMKQ0n.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-BoiSCBad.js";import{b as i}from"./mermaid.core-DqsMKQ0n.js";import"./chunk-5VM5RSS4-BRHAAr29.js";import"./chunk-XXDRQBXY-D9fRuB0M.js";import"./chunk-VR4S4FIN-ByS_rjkq.js";import"./chunk-32BRIVSS-Cysd2WNQ.js";import"./purify.es-CC4Brkr_.js";import"./index-B1FKcn-W.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
@@ -1 +0,0 @@
1
- import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-BoiSCBad.js";import{b as i}from"./mermaid.core-DqsMKQ0n.js";import"./chunk-5VM5RSS4-BRHAAr29.js";import"./chunk-XXDRQBXY-D9fRuB0M.js";import"./chunk-VR4S4FIN-ByS_rjkq.js";import"./chunk-32BRIVSS-Cysd2WNQ.js";import"./purify.es-CC4Brkr_.js";import"./index-B1FKcn-W.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};