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.
- package/.desktop-dist/main.js +345 -1
- package/dist/client/assets/{ModelPreview-CoR41z2e.js → ModelPreview-Cxk4IZLv.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-CXFrMEcU.js → abnfDiagram-VRR7QNED-Bz2HWRRX.js} +1 -1
- package/dist/client/assets/{arc-DXT1yvki.js → arc-D3vf35f_.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-B4Tl5gm4.js → architectureDiagram-ZJ3FMSHR-LI-OXsDp.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-D8OY0bG2.js → blockDiagram-677ZJIJ3-Dm1JtLIo.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-BHIhcBy3.js → c4Diagram-LMCZKHZV-DSkxlBnU.js} +1 -1
- package/dist/client/assets/channel-qZtjoGxW.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-NFF-sZCT.js → chunk-2Q5K7J3B-Wd2AO6yQ.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-Cysd2WNQ.js → chunk-32BRIVSS-CIBkFIAg.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-BRHAAr29.js → chunk-5VM5RSS4-9veDV0NJ.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BovDZYVQ.js → chunk-EX3LRPZG-DVi5mqrV.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-ByWUvKGO.js → chunk-JWPE2WC7-DZc_3_5i.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-GWk63RII.js → chunk-MOJQB5TN-Czq1YGEA.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-GvzHMCqG.js → chunk-RYQCIY6F-BN3UqH_7.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BoiSCBad.js → chunk-V7JOEXUC-BgDCEp_z.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-ByS_rjkq.js → chunk-VR4S4FIN-BnyRVZSn.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-D9fRuB0M.js → chunk-XXDRQBXY-KOa0rQIz.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CnWXW1tk.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CnWXW1tk.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-BVOwkeRc.js → cose-bilkent-JH36ORCC-B-P2BstB.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-8rNTW_mJ.js → cynefin-VYW2F7L2-DOSg0Q6x.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-DHASHiYD.js → cynefinDiagram-TSTJHNR4-Bm8Ct13U.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-wWqVXFNE.js → dagre-VKFMJZFB-Cnw99n0y.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-Bp2E7omd.js → diagram-FQU43EPY-B742HZhK.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-DajrBIg7.js → diagram-G47NLZAW-BGXKr2u9.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-ByGTRgNg.js → diagram-NH7WQ7WH-DIyEYN64.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-bsRCtWPe.js → diagram-OA4YK3LP-BdRMWU80.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-9f1_0Bjd.js → diagram-WEI45ONY-Bu-180k8.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-DyvJDQQ5.js → ebnfDiagram-CCIWWBDH-Djcej7Pq.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-DwLHRiMz.js → erDiagram-Q63AITRT-D70JxkGr.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U--9lOgA3z.js → flowDiagram-23GEKE2U-DovWUM1i.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-j9C0N_DS.js → ganttDiagram-NO4QXBWP-Izsfqq5i.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-lHdVffsh.js → gitGraphDiagram-IHSO6WYX-BTZo0N08.js} +1 -1
- package/dist/client/assets/index-BE4Js_ss.js +456 -0
- package/dist/client/assets/index-CRIZk117.css +1 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-D8kAsmdn.js → infoDiagram-FWYZ7A6U-Ina5uVvZ.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CTY9BLyE.js → ishikawaDiagram-FXEZZL3T-iUdzuACl.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-B3QO8wF2.js → journeyDiagram-5HDEW3XC-lh15NShH.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-CesXysl2.js → kanban-definition-HUTT4EX6-CKZooxMa.js} +1 -1
- package/dist/client/assets/{linear-wxKR9xNu.js → linear-BrmrQdSX.js} +1 -1
- package/dist/client/assets/{mermaid.core-DqsMKQ0n.js → mermaid.core-fbY4nFyi.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-CCfpEZEP.js → mindmap-definition-LN4V7U3C-BHUhjMUf.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-DEje4xdU.js → pegDiagram-2B236MQR-8qcHqxsE.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-B5guqQ7K.js → pieDiagram-ENE6RG2P-CpIkkExH.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-D58kwRrP.js → quadrantDiagram-ABIIQ3AL-hWzvYsWG.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-D9VFfFu0.js → railroadDiagram-RFXS5EU6-2nhrE9bK.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-B0q4l0cr.js → requirementDiagram-TGXJPOKE-B05mEN7w.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-DTsXXi0M.js → sankeyDiagram-HTMAVEWB-CXHkBpDN.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-3Xpi1nmi.js → sequenceDiagram-DBY2YBRQ-C4wG1r_I.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-C0jHd0eC.js → sizeCapture-X5ZJPWSS-D_3i42tQ.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-DlDm_dYg.js → stateDiagram-2N3HPSRC-DhEgczRA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-D9gqRttI.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-C128cuo6.js → swimlanes-5IMT3BWC-DTrpFviv.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CNOSFfLB.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-BHsLELm-.js → timeline-definition-FHXFAJF6-DLpl_8Za.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-DkdQFPIy.js → vennDiagram-L72KCM5P-CgnCFHj7.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-DWaahbD5.js → wardleyDiagram-EHGQE667-BwNfD-cz.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-CSwOYmgm.js → xychartDiagram-FW5EYKEG--sZgo_Fw.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/automationStore.js +12 -7
- package/dist/server/agent/sessionSearchStore.js +135 -5
- package/dist/server/entry.js +24 -1
- package/dist/server/routes/filesystem.js +31 -6
- package/dist/server/routes/terminal.js +6 -2
- package/dist/server/utils/heicPreview.js +50 -0
- package/dist/server/utils/runtimeMonitor.js +287 -0
- package/dist/server/utils/settings.js +9 -0
- package/dist/server/workers/heicPreviewWorker.js +14 -0
- package/package.json +3 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/channel-CXw70OW3.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CNF7sVVd.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CNF7sVVd.js +0 -1
- package/dist/client/assets/index-B1FKcn-W.js +0 -451
- package/dist/client/assets/index-BloZ2WQg.css +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BVOBevpU.js +0 -1
- 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.
|
|
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",
|
package/runtime-manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.109",
|
|
5
5
|
"runtimeProtocolVersion": 1,
|
|
6
6
|
"minimumDesktopVersion": "1.4.46",
|
|
7
7
|
"nodeMajor": 22,
|
|
8
|
-
"dependencyHash": "sha256-
|
|
9
|
-
"serverBundleHash": "sha256-
|
|
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};
|