0.10.2-3 Hop it works

This commit is contained in:
2026-03-15 19:28:22 +00:00
parent cc20dc8120
commit 3c694d06df
16 changed files with 465 additions and 134 deletions
+88 -2
View File
@@ -3,6 +3,7 @@ const wsService = require('./websocketService');
const MAX_RECENT_ACTIVITIES = 120;
const MAX_ACTIVITY_OUTPUT_CHARS = 12000;
const MAX_ACTIVITY_TEXT_CHARS = 2000;
const OUTPUT_BROADCAST_THROTTLE_MS = 180;
function nowIso() {
return new Date().toISOString();
@@ -28,12 +29,52 @@ function normalizeText(value, { trim = true, maxChars = MAX_ACTIVITY_TEXT_CHARS
return null;
}
if (text.length > maxChars) {
const suffix = trim ? ' ...[gekürzt]' : '\n...[gekürzt]';
text = `${text.slice(0, Math.max(0, maxChars - suffix.length))}${suffix}`;
if (trim) {
const suffix = ' ...[gekürzt]';
text = `${text.slice(0, Math.max(0, maxChars - suffix.length))}${suffix}`;
} else {
const prefix = '...[gekürzt]\n';
text = `${prefix}${text.slice(-Math.max(0, maxChars - prefix.length))}`;
}
}
return text;
}
function normalizeOutputChunk(value) {
if (value === null || value === undefined) {
return '';
}
const normalized = String(value).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
if (!normalized) {
return '';
}
return normalized.endsWith('\n') ? normalized : `${normalized}\n`;
}
function appendOutputTail(currentValue, chunk, maxChars = MAX_ACTIVITY_OUTPUT_CHARS) {
const normalizedChunk = normalizeOutputChunk(chunk);
const currentText = currentValue == null ? '' : String(currentValue);
if (!normalizedChunk) {
return {
value: currentText || null,
truncated: false
};
}
const combined = `${currentText}${normalizedChunk}`;
if (combined.length <= maxChars) {
return {
value: combined,
truncated: false
};
}
return {
value: combined.slice(-maxChars),
truncated: true
};
}
function sanitizeActivity(input = {}) {
const source = input && typeof input === 'object' ? input : {};
const normalizedOutcome = normalizeText(source.outcome, { trim: true, maxChars: 40 });
@@ -61,6 +102,7 @@ function sanitizeActivity(input = {}) {
output: normalizeText(source.output, { trim: false, maxChars: MAX_ACTIVITY_OUTPUT_CHARS }),
stdout: normalizeText(source.stdout, { trim: false, maxChars: MAX_ACTIVITY_OUTPUT_CHARS }),
stderr: normalizeText(source.stderr, { trim: false, maxChars: MAX_ACTIVITY_OUTPUT_CHARS }),
outputTruncated: Boolean(source.outputTruncated),
stdoutTruncated: Boolean(source.stdoutTruncated),
stderrTruncated: Boolean(source.stderrTruncated),
startedAt: source.startedAt || nowIso(),
@@ -77,6 +119,7 @@ class RuntimeActivityService {
this.active = new Map();
this.recent = [];
this.controls = new Map();
this.outputBroadcastTimer = null;
}
buildSnapshot() {
@@ -92,9 +135,23 @@ class RuntimeActivityService {
}
broadcastSnapshot() {
if (this.outputBroadcastTimer) {
clearTimeout(this.outputBroadcastTimer);
this.outputBroadcastTimer = null;
}
wsService.broadcast('RUNTIME_ACTIVITY_CHANGED', this.buildSnapshot());
}
scheduleOutputBroadcast() {
if (this.outputBroadcastTimer) {
return;
}
this.outputBroadcastTimer = setTimeout(() => {
this.outputBroadcastTimer = null;
wsService.broadcast('RUNTIME_ACTIVITY_CHANGED', this.buildSnapshot());
}, OUTPUT_BROADCAST_THROTTLE_MS);
}
startActivity(type, payload = {}) {
const id = this.nextId;
this.nextId += 1;
@@ -134,6 +191,35 @@ class RuntimeActivityService {
return next;
}
appendActivityOutput(activityId, patch = {}) {
const id = normalizeNumber(activityId);
if (!id || !this.active.has(id)) {
return null;
}
const current = this.active.get(id);
const nextOutput = appendOutputTail(current.output, patch?.output, MAX_ACTIVITY_OUTPUT_CHARS);
const nextStdout = appendOutputTail(current.stdout, patch?.stdout, MAX_ACTIVITY_OUTPUT_CHARS);
const nextStderr = appendOutputTail(current.stderr, patch?.stderr, MAX_ACTIVITY_OUTPUT_CHARS);
const next = sanitizeActivity({
...current,
...patch,
id: current.id,
type: current.type,
status: current.status,
startedAt: current.startedAt,
output: nextOutput.value,
stdout: nextStdout.value,
stderr: nextStderr.value,
outputTruncated: Boolean(current.outputTruncated || patch?.outputTruncated || nextOutput.truncated),
stdoutTruncated: Boolean(current.stdoutTruncated || patch?.stdoutTruncated || nextStdout.truncated),
stderrTruncated: Boolean(current.stderrTruncated || patch?.stderrTruncated || nextStderr.truncated)
});
this.active.set(id, next);
this.scheduleOutputBroadcast();
return next;
}
completeActivity(activityId, payload = {}) {
const id = normalizeNumber(activityId);
if (!id || !this.active.has(id)) {