chore(app): add terminal input/output debug logging
This commit is contained in:
@@ -29,6 +29,10 @@ import {
|
|||||||
TerminalStreamController,
|
TerminalStreamController,
|
||||||
type TerminalStreamControllerStatus,
|
type TerminalStreamControllerStatus,
|
||||||
} from "@/terminal/runtime/terminal-stream-controller";
|
} from "@/terminal/runtime/terminal-stream-controller";
|
||||||
|
import {
|
||||||
|
summarizeTerminalText,
|
||||||
|
terminalDebugLog,
|
||||||
|
} from "@/terminal/runtime/terminal-debug";
|
||||||
import TerminalEmulator from "./terminal-emulator";
|
import TerminalEmulator from "./terminal-emulator";
|
||||||
|
|
||||||
interface TerminalPaneProps {
|
interface TerminalPaneProps {
|
||||||
@@ -459,10 +463,32 @@ export function TerminalPane({ serverId, cwd }: TerminalPaneProps) {
|
|||||||
}
|
}
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (!client || activeStreamId === null) {
|
if (!client || activeStreamId === null) {
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "terminal-pane",
|
||||||
|
event: "input:key:drop-no-stream",
|
||||||
|
details: {
|
||||||
|
key: input.key,
|
||||||
|
ctrl: input.ctrl,
|
||||||
|
shift: input.shift,
|
||||||
|
alt: input.alt,
|
||||||
|
activeStreamId,
|
||||||
|
},
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedKey = normalizeTerminalTransportKey(input.key);
|
const normalizedKey = normalizeTerminalTransportKey(input.key);
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "terminal-pane",
|
||||||
|
event: "input:key:send",
|
||||||
|
details: {
|
||||||
|
key: normalizedKey,
|
||||||
|
ctrl: input.ctrl,
|
||||||
|
shift: input.shift,
|
||||||
|
alt: input.alt,
|
||||||
|
activeStreamId,
|
||||||
|
},
|
||||||
|
});
|
||||||
client.sendTerminalStreamKey(activeStreamId, {
|
client.sendTerminalStreamKey(activeStreamId, {
|
||||||
key: normalizedKey,
|
key: normalizedKey,
|
||||||
ctrl: input.ctrl,
|
ctrl: input.ctrl,
|
||||||
@@ -480,6 +506,15 @@ export function TerminalPane({ serverId, cwd }: TerminalPaneProps) {
|
|||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "terminal-pane",
|
||||||
|
event: "input:data:received",
|
||||||
|
details: {
|
||||||
|
length: data.length,
|
||||||
|
preview: summarizeTerminalText({ text: data, maxChars: 80 }),
|
||||||
|
activeStreamId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (hasPendingTerminalModifiers(modifiers)) {
|
if (hasPendingTerminalModifiers(modifiers)) {
|
||||||
const pendingResolution = resolvePendingModifierDataInput({
|
const pendingResolution = resolvePendingModifierDataInput({
|
||||||
@@ -507,8 +542,25 @@ export function TerminalPane({ serverId, cwd }: TerminalPaneProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!client || activeStreamId === null) {
|
if (!client || activeStreamId === null) {
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "terminal-pane",
|
||||||
|
event: "input:data:drop-no-stream",
|
||||||
|
details: {
|
||||||
|
length: data.length,
|
||||||
|
preview: summarizeTerminalText({ text: data, maxChars: 80 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "terminal-pane",
|
||||||
|
event: "input:data:send",
|
||||||
|
details: {
|
||||||
|
length: data.length,
|
||||||
|
preview: summarizeTerminalText({ text: data, maxChars: 80 }),
|
||||||
|
activeStreamId,
|
||||||
|
},
|
||||||
|
});
|
||||||
client.sendTerminalStreamInput(activeStreamId, data);
|
client.sendTerminalStreamInput(activeStreamId, data);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -539,6 +591,15 @@ export function TerminalPane({ serverId, cwd }: TerminalPaneProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastReportedSizeRef.current = { rows: normalizedRows, cols: normalizedCols };
|
lastReportedSizeRef.current = { rows: normalizedRows, cols: normalizedCols };
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "terminal-pane",
|
||||||
|
event: "display:resize:send",
|
||||||
|
details: {
|
||||||
|
terminalId: selectedTerminalId,
|
||||||
|
rows: normalizedRows,
|
||||||
|
cols: normalizedCols,
|
||||||
|
},
|
||||||
|
});
|
||||||
client.sendTerminalInput(selectedTerminalId, {
|
client.sendTerminalInput(selectedTerminalId, {
|
||||||
type: "resize",
|
type: "resize",
|
||||||
rows: normalizedRows,
|
rows: normalizedRows,
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
type TerminalDebugGlobal = {
|
||||||
|
__PASEO_TERMINAL_DEBUG?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TerminalDebugLogInput = {
|
||||||
|
scope: string;
|
||||||
|
event: string;
|
||||||
|
details?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveGlobalDebugFlag(): boolean | null {
|
||||||
|
if (typeof globalThis === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = (globalThis as TerminalDebugGlobal).__PASEO_TERMINAL_DEBUG;
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTerminalDebugEnabled(): boolean {
|
||||||
|
const globalFlag = resolveGlobalDebugFlag();
|
||||||
|
if (globalFlag !== null) {
|
||||||
|
return globalFlag;
|
||||||
|
}
|
||||||
|
return process.env.NODE_ENV === "development";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function terminalDebugLog(input: TerminalDebugLogInput): void {
|
||||||
|
if (!isTerminalDebugEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = input.details
|
||||||
|
? { ...input.details, ts: Date.now() }
|
||||||
|
: { ts: Date.now() };
|
||||||
|
console.log(`[terminal][${input.scope}] ${input.event}`, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeControlBytes(input: { text: string }): string {
|
||||||
|
let output = "";
|
||||||
|
for (const char of input.text) {
|
||||||
|
const code = char.charCodeAt(0);
|
||||||
|
if (code === 10) {
|
||||||
|
output += "\\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code === 13) {
|
||||||
|
output += "\\r";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code === 9) {
|
||||||
|
output += "\\t";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code < 32 || code === 127) {
|
||||||
|
output += `\\x${code.toString(16).padStart(2, "0")}`;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
output += char;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function summarizeTerminalText(input: {
|
||||||
|
text: string;
|
||||||
|
maxChars?: number;
|
||||||
|
}): string {
|
||||||
|
const maxChars = input.maxChars ?? 80;
|
||||||
|
const escaped = escapeControlBytes({ text: input.text });
|
||||||
|
if (escaped.length <= maxChars) {
|
||||||
|
return escaped;
|
||||||
|
}
|
||||||
|
return `${escaped.slice(0, maxChars)}…`;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
normalizeTerminalTransportKey,
|
normalizeTerminalTransportKey,
|
||||||
shouldInterceptDomTerminalKey,
|
shouldInterceptDomTerminalKey,
|
||||||
} from "@/utils/terminal-keys";
|
} from "@/utils/terminal-keys";
|
||||||
|
import { summarizeTerminalText, terminalDebugLog } from "./terminal-debug";
|
||||||
|
|
||||||
export type TerminalEmulatorRuntimeTheme = {
|
export type TerminalEmulatorRuntimeTheme = {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
@@ -82,6 +83,13 @@ export class TerminalEmulatorRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mount(input: TerminalEmulatorRuntimeMountInput): void {
|
mount(input: TerminalEmulatorRuntimeMountInput): void {
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "mount:start",
|
||||||
|
details: {
|
||||||
|
initialOutputLength: input.initialOutputText.length,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.unmount();
|
this.unmount();
|
||||||
|
|
||||||
input.host.innerHTML = "";
|
input.host.innerHTML = "";
|
||||||
@@ -138,6 +146,15 @@ export class TerminalEmulatorRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.lastSize = { rows: nextRows, cols: nextCols };
|
this.lastSize = { rows: nextRows, cols: nextCols };
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "resize:emit",
|
||||||
|
details: {
|
||||||
|
rows: nextRows,
|
||||||
|
cols: nextCols,
|
||||||
|
force,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.callbacks.onResize?.({
|
this.callbacks.onResize?.({
|
||||||
rows: nextRows,
|
rows: nextRows,
|
||||||
cols: nextCols,
|
cols: nextCols,
|
||||||
@@ -147,6 +164,14 @@ export class TerminalEmulatorRuntime {
|
|||||||
fitAndEmitResize(true);
|
fitAndEmitResize(true);
|
||||||
|
|
||||||
const inputDisposable = terminal.onData((data) => {
|
const inputDisposable = terminal.onData((data) => {
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "input:onData",
|
||||||
|
details: {
|
||||||
|
length: data.length,
|
||||||
|
preview: summarizeTerminalText({ text: data, maxChars: 64 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
this.callbacks.onInput?.(data);
|
this.callbacks.onInput?.(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -182,6 +207,17 @@ export class TerminalEmulatorRuntime {
|
|||||||
key: normalizeTerminalTransportKey(normalizedKey),
|
key: normalizeTerminalTransportKey(normalizedKey),
|
||||||
...modifiers,
|
...modifiers,
|
||||||
});
|
});
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "key:intercepted",
|
||||||
|
details: {
|
||||||
|
key: normalizedKey,
|
||||||
|
ctrl: modifiers.ctrl,
|
||||||
|
shift: modifiers.shift,
|
||||||
|
alt: modifiers.alt,
|
||||||
|
meta: modifiers.meta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (this.pendingModifiers.ctrl || this.pendingModifiers.shift || this.pendingModifiers.alt) {
|
if (this.pendingModifiers.ctrl || this.pendingModifiers.shift || this.pendingModifiers.alt) {
|
||||||
this.callbacks.onPendingModifiersConsumed?.();
|
this.callbacks.onPendingModifiersConsumed?.();
|
||||||
@@ -238,6 +274,17 @@ export class TerminalEmulatorRuntime {
|
|||||||
|
|
||||||
if (input.initialOutputText.length > 0) {
|
if (input.initialOutputText.length > 0) {
|
||||||
terminal.write(input.initialOutputText);
|
terminal.write(input.initialOutputText);
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "output:initial-write",
|
||||||
|
details: {
|
||||||
|
length: input.initialOutputText.length,
|
||||||
|
preview: summarizeTerminalText({
|
||||||
|
text: input.initialOutputText,
|
||||||
|
maxChars: 96,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const disposables: TerminalEmulatorRuntimeDisposables = {
|
const disposables: TerminalEmulatorRuntimeDisposables = {
|
||||||
@@ -296,11 +343,24 @@ export class TerminalEmulatorRuntime {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.pendingWriteText += input.text;
|
this.pendingWriteText += input.text;
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "output:enqueue",
|
||||||
|
details: {
|
||||||
|
chunkLength: input.text.length,
|
||||||
|
queueLength: this.pendingWriteText.length,
|
||||||
|
preview: summarizeTerminalText({ text: input.text, maxChars: 64 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
this.scheduleWriteFlush();
|
this.scheduleWriteFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
this.pendingWriteText = "";
|
this.pendingWriteText = "";
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "output:clear",
|
||||||
|
});
|
||||||
this.terminal?.reset();
|
this.terminal?.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +369,13 @@ export class TerminalEmulatorRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unmount(): void {
|
unmount(): void {
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "mount:unmount",
|
||||||
|
details: {
|
||||||
|
pendingWriteLength: this.pendingWriteText.length,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.pendingWriteText = "";
|
this.pendingWriteText = "";
|
||||||
this.isWriteFlushQueued = false;
|
this.isWriteFlushQueued = false;
|
||||||
|
|
||||||
@@ -340,6 +407,14 @@ export class TerminalEmulatorRuntime {
|
|||||||
}
|
}
|
||||||
const text = this.pendingWriteText;
|
const text = this.pendingWriteText;
|
||||||
this.pendingWriteText = "";
|
this.pendingWriteText = "";
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "emulator-runtime",
|
||||||
|
event: "output:flush",
|
||||||
|
details: {
|
||||||
|
length: text.length,
|
||||||
|
preview: summarizeTerminalText({ text, maxChars: 96 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
this.terminal.write(text);
|
this.terminal.write(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
readTerminalOutputBuffer,
|
readTerminalOutputBuffer,
|
||||||
type TerminalOutputBuffer,
|
type TerminalOutputBuffer,
|
||||||
} from "@/utils/terminal-output-buffer";
|
} from "@/utils/terminal-output-buffer";
|
||||||
|
import { summarizeTerminalText, terminalDebugLog } from "./terminal-debug";
|
||||||
|
|
||||||
export type TerminalOutputChunk = {
|
export type TerminalOutputChunk = {
|
||||||
sequence: number;
|
sequence: number;
|
||||||
@@ -50,6 +51,14 @@ export class TerminalOutputPump {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "output-pump",
|
||||||
|
event: "selected-terminal:set",
|
||||||
|
details: {
|
||||||
|
previousTerminalId: this.selectedTerminalId,
|
||||||
|
nextTerminalId: input.terminalId,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.clearSelectedChunkFlushTimer();
|
this.clearSelectedChunkFlushTimer();
|
||||||
this.selectedChunkAccumulator = "";
|
this.selectedChunkAccumulator = "";
|
||||||
this.selectedTerminalId = input.terminalId;
|
this.selectedTerminalId = input.terminalId;
|
||||||
@@ -77,6 +86,16 @@ export class TerminalOutputPump {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.selectedChunkAccumulator += input.text;
|
this.selectedChunkAccumulator += input.text;
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "output-pump",
|
||||||
|
event: "selected-terminal:accumulate",
|
||||||
|
details: {
|
||||||
|
terminalId: input.terminalId,
|
||||||
|
appendedLength: input.text.length,
|
||||||
|
accumulatorLength: this.selectedChunkAccumulator.length,
|
||||||
|
preview: summarizeTerminalText({ text: input.text, maxChars: 80 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
this.scheduleSelectedChunkFlush();
|
this.scheduleSelectedChunkFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +153,15 @@ export class TerminalOutputPump {
|
|||||||
|
|
||||||
const text = this.selectedChunkAccumulator;
|
const text = this.selectedChunkAccumulator;
|
||||||
this.selectedChunkAccumulator = "";
|
this.selectedChunkAccumulator = "";
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "output-pump",
|
||||||
|
event: "selected-terminal:flush",
|
||||||
|
details: {
|
||||||
|
terminalId: this.selectedTerminalId,
|
||||||
|
textLength: text.length,
|
||||||
|
preview: summarizeTerminalText({ text, maxChars: 96 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
this.emitSelectedChunk({ text });
|
this.emitSelectedChunk({ text });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
waitForDuration,
|
waitForDuration,
|
||||||
withPromiseTimeout,
|
withPromiseTimeout,
|
||||||
} from "@/utils/terminal-attach";
|
} from "@/utils/terminal-attach";
|
||||||
|
import { summarizeTerminalText, terminalDebugLog } from "./terminal-debug";
|
||||||
|
|
||||||
export type TerminalStreamControllerAttachPayload = {
|
export type TerminalStreamControllerAttachPayload = {
|
||||||
streamId: number | null;
|
streamId: number | null;
|
||||||
@@ -100,6 +101,14 @@ export class TerminalStreamController {
|
|||||||
if (this.isDisposed) {
|
if (this.isDisposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "terminal:set",
|
||||||
|
details: {
|
||||||
|
previousTerminalId: this.selectedTerminalId,
|
||||||
|
nextTerminalId: input.terminalId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const nextTerminalId = input.terminalId;
|
const nextTerminalId = input.terminalId;
|
||||||
const previousTerminalId = this.selectedTerminalId;
|
const previousTerminalId = this.selectedTerminalId;
|
||||||
@@ -144,6 +153,14 @@ export class TerminalStreamController {
|
|||||||
if (this.isDisposed) {
|
if (this.isDisposed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "stream:exit",
|
||||||
|
details: {
|
||||||
|
terminalId: input.terminalId,
|
||||||
|
streamId: input.streamId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const activeStream = this.activeStream;
|
const activeStream = this.activeStream;
|
||||||
if (!activeStream) {
|
if (!activeStream) {
|
||||||
@@ -217,6 +234,16 @@ export class TerminalStreamController {
|
|||||||
if (!this.isAttachGenerationCurrent({ generation: input.generation, terminalId: input.terminalId })) {
|
if (!this.isAttachGenerationCurrent({ generation: input.generation, terminalId: input.terminalId })) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "attach:attempt",
|
||||||
|
details: {
|
||||||
|
terminalId: input.terminalId,
|
||||||
|
generation: input.generation,
|
||||||
|
attempt,
|
||||||
|
maxAttachAttempts,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const preferredSize = this.options.getPreferredSize();
|
const preferredSize = this.options.getPreferredSize();
|
||||||
@@ -244,6 +271,15 @@ export class TerminalStreamController {
|
|||||||
|
|
||||||
if (attachPayload.error || typeof attachPayload.streamId !== "number") {
|
if (attachPayload.error || typeof attachPayload.streamId !== "number") {
|
||||||
lastErrorMessage = attachPayload.error ?? "Unable to attach terminal stream";
|
lastErrorMessage = attachPayload.error ?? "Unable to attach terminal stream";
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "attach:response-error",
|
||||||
|
details: {
|
||||||
|
terminalId: input.terminalId,
|
||||||
|
attempt,
|
||||||
|
error: lastErrorMessage,
|
||||||
|
},
|
||||||
|
});
|
||||||
const hasRemainingAttempts = attempt < maxAttachAttempts - 1;
|
const hasRemainingAttempts = attempt < maxAttachAttempts - 1;
|
||||||
if (hasRemainingAttempts && isRetryableError({ message: lastErrorMessage })) {
|
if (hasRemainingAttempts && isRetryableError({ message: lastErrorMessage })) {
|
||||||
await waitForDelay({ durationMs: getRetryDelayMs({ attempt }) });
|
await waitForDelay({ durationMs: getRetryDelayMs({ attempt }) });
|
||||||
@@ -287,6 +323,16 @@ export class TerminalStreamController {
|
|||||||
decoder,
|
decoder,
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
};
|
};
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "attach:success",
|
||||||
|
details: {
|
||||||
|
terminalId: input.terminalId,
|
||||||
|
streamId,
|
||||||
|
currentOffset: attachPayload.currentOffset,
|
||||||
|
reset: attachPayload.reset,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.updateStatus({
|
this.updateStatus({
|
||||||
terminalId: input.terminalId,
|
terminalId: input.terminalId,
|
||||||
streamId,
|
streamId,
|
||||||
@@ -297,6 +343,15 @@ export class TerminalStreamController {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastErrorMessage =
|
lastErrorMessage =
|
||||||
error instanceof Error ? error.message : "Unable to attach terminal stream";
|
error instanceof Error ? error.message : "Unable to attach terminal stream";
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "attach:exception",
|
||||||
|
details: {
|
||||||
|
terminalId: input.terminalId,
|
||||||
|
attempt,
|
||||||
|
error: lastErrorMessage,
|
||||||
|
},
|
||||||
|
});
|
||||||
const hasRemainingAttempts = attempt < maxAttachAttempts - 1;
|
const hasRemainingAttempts = attempt < maxAttachAttempts - 1;
|
||||||
if (hasRemainingAttempts && isRetryableError({ message: lastErrorMessage })) {
|
if (hasRemainingAttempts && isRetryableError({ message: lastErrorMessage })) {
|
||||||
await waitForDelay({ durationMs: getRetryDelayMs({ attempt }) });
|
await waitForDelay({ durationMs: getRetryDelayMs({ attempt }) });
|
||||||
@@ -345,6 +400,18 @@ export class TerminalStreamController {
|
|||||||
if (text.length === 0) {
|
if (text.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "stream:chunk",
|
||||||
|
details: {
|
||||||
|
terminalId: input.terminalId,
|
||||||
|
streamId: input.streamId,
|
||||||
|
endOffset: input.chunk.endOffset,
|
||||||
|
byteLength: input.chunk.data.byteLength,
|
||||||
|
textLength: text.length,
|
||||||
|
preview: summarizeTerminalText({ text, maxChars: 96 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.options.onChunk({
|
this.options.onChunk({
|
||||||
terminalId: input.terminalId,
|
terminalId: input.terminalId,
|
||||||
@@ -357,6 +424,15 @@ export class TerminalStreamController {
|
|||||||
if (!activeStream) {
|
if (!activeStream) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "stream:detach",
|
||||||
|
details: {
|
||||||
|
terminalId: activeStream.terminalId,
|
||||||
|
streamId: activeStream.streamId,
|
||||||
|
shouldDetach: input.shouldDetach,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.activeStream = null;
|
this.activeStream = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -403,7 +479,16 @@ export class TerminalStreamController {
|
|||||||
|
|
||||||
private updateStatus(status: TerminalStreamControllerStatus): void {
|
private updateStatus(status: TerminalStreamControllerStatus): void {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
terminalDebugLog({
|
||||||
|
scope: "stream-controller",
|
||||||
|
event: "status:update",
|
||||||
|
details: {
|
||||||
|
terminalId: status.terminalId,
|
||||||
|
streamId: status.streamId,
|
||||||
|
isAttaching: status.isAttaching,
|
||||||
|
error: status.error,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.options.onStatusChange?.(status);
|
this.options.onStatusChange?.(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user