@pixelbyte-software/pixcode 1.50.7 → 1.50.8
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/dist/assets/{index-CR4j4iu_.js → index-gecaamTl.js} +69 -69
- package/dist/index.html +1 -1
- package/dist-server/server/index.js +91 -14
- package/dist-server/server/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/hermes/pixcode-mcp-server.mjs +35 -14
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +22 -1
- package/scripts/smoke/hermes-settings-commands.mjs +45 -0
- package/server/index.js +102 -13
|
@@ -9,6 +9,7 @@ const appRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..',
|
|
|
9
9
|
const mcpServerPath = path.join(appRoot, 'scripts', 'hermes', 'pixcode-mcp-server.mjs');
|
|
10
10
|
const READBACK_IDLE_STABLE_MS = 2500;
|
|
11
11
|
const DEFAULT_STARTUP_WAIT_MS = 100000;
|
|
12
|
+
const CODEX_PROMPT_INPUT_PENDING_REASON = 'codex_prompt_input_pending';
|
|
12
13
|
|
|
13
14
|
const tools = [
|
|
14
15
|
{
|
|
@@ -140,7 +141,7 @@ const tools = [
|
|
|
140
141
|
},
|
|
141
142
|
startIfNeeded: {
|
|
142
143
|
type: 'boolean',
|
|
143
|
-
description: 'When
|
|
144
|
+
description: 'When false, only probe an already-running gateway. Defaults to true so Pixcode keeps Hermes REST ready.',
|
|
144
145
|
},
|
|
145
146
|
},
|
|
146
147
|
additionalProperties: false,
|
|
@@ -218,13 +219,29 @@ function getLastMatchIndex(text, pattern) {
|
|
|
218
219
|
return lastIndex;
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
function
|
|
222
|
+
function normalizePromptInput(value) {
|
|
223
|
+
return String(value || '').replace(/(?:\r\n|\r|\n)+$/u, '').trim();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function hasCodexPromptInputPending(output, expectedInput) {
|
|
227
|
+
const expected = normalizePromptInput(expectedInput);
|
|
228
|
+
if (!expected) return false;
|
|
229
|
+
const match = String(output || '').match(/(?:^|\n)[^\S\r\n]*[›❯][ \t]+([^\r\n]+)[\r\n]*$/u);
|
|
230
|
+
if (!match) return false;
|
|
231
|
+
return normalizePromptInput(match[1]) === expected;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function inferTerminalState(provider, terminalOutput, expectedInput = null) {
|
|
222
235
|
if (!terminalOutput) return 'unknown';
|
|
236
|
+
const output = String(terminalOutput.output || '');
|
|
237
|
+
if (provider === 'codex' && hasCodexPromptInputPending(output, expectedInput)) {
|
|
238
|
+
terminalOutput.terminalStateReason = terminalOutput.terminalStateReason || CODEX_PROMPT_INPUT_PENDING_REASON;
|
|
239
|
+
return 'busy';
|
|
240
|
+
}
|
|
223
241
|
if (typeof terminalOutput.terminalState === 'string') return terminalOutput.terminalState;
|
|
224
242
|
if (typeof terminalOutput.isBusy === 'boolean') return terminalOutput.isBusy ? 'busy' : 'idle';
|
|
225
243
|
if (terminalOutput.active === false) return terminalOutput.output ? 'idle' : 'unknown';
|
|
226
244
|
|
|
227
|
-
const output = String(terminalOutput.output || '');
|
|
228
245
|
if (!output.trim()) return 'unknown';
|
|
229
246
|
if (/Process exited with code/iu.test(output)) return 'idle';
|
|
230
247
|
|
|
@@ -240,6 +257,10 @@ function inferTerminalState(provider, terminalOutput) {
|
|
|
240
257
|
getLastMatchIndex(output, /(?:^|\n)\s*›(?:\s|$)/gu),
|
|
241
258
|
getLastMatchIndex(output, /(?:^|\n)\s*❯(?:\s|$)/gu),
|
|
242
259
|
);
|
|
260
|
+
if (hasCodexPromptInputPending(output, expectedInput)) {
|
|
261
|
+
terminalOutput.terminalStateReason = terminalOutput.terminalStateReason || CODEX_PROMPT_INPUT_PENDING_REASON;
|
|
262
|
+
return 'busy';
|
|
263
|
+
}
|
|
243
264
|
if (lastPrompt >= 0) return lastStrongBusy > lastPrompt ? 'busy' : 'idle';
|
|
244
265
|
if (lastBusy >= 0) return 'busy';
|
|
245
266
|
return 'unknown';
|
|
@@ -249,13 +270,13 @@ function inferTerminalState(provider, terminalOutput) {
|
|
|
249
270
|
return 'unknown';
|
|
250
271
|
}
|
|
251
272
|
|
|
252
|
-
function isTerminalReadbackFinal(provider, terminalOutput) {
|
|
253
|
-
const terminalState = inferTerminalState(provider, terminalOutput);
|
|
273
|
+
function isTerminalReadbackFinal(provider, terminalOutput, expectedInput = null) {
|
|
274
|
+
const terminalState = inferTerminalState(provider, terminalOutput, expectedInput);
|
|
254
275
|
return terminalState === 'idle' || terminalState === 'completed' || terminalState === 'exited' || terminalState === 'failed';
|
|
255
276
|
}
|
|
256
277
|
|
|
257
|
-
function isTerminalReadbackHardFinal(provider, terminalOutput) {
|
|
258
|
-
const terminalState = inferTerminalState(provider, terminalOutput);
|
|
278
|
+
function isTerminalReadbackHardFinal(provider, terminalOutput, expectedInput = null) {
|
|
279
|
+
const terminalState = inferTerminalState(provider, terminalOutput, expectedInput);
|
|
259
280
|
return terminalState === 'completed' || terminalState === 'exited' || terminalState === 'failed' || Boolean(terminalOutput?.terminalFailed);
|
|
260
281
|
}
|
|
261
282
|
|
|
@@ -269,7 +290,7 @@ function getReadbackFingerprint(terminalOutput) {
|
|
|
269
290
|
].join('\n---pixcode-readback---\n');
|
|
270
291
|
}
|
|
271
292
|
|
|
272
|
-
async function waitForProviderTerminalOutput(provider, projectPath, waitMs, launchId = null) {
|
|
293
|
+
async function waitForProviderTerminalOutput(provider, projectPath, waitMs, launchId = null, expectedInput = null) {
|
|
273
294
|
const startedAt = Date.now();
|
|
274
295
|
let latestOutput = null;
|
|
275
296
|
let stableFingerprint = null;
|
|
@@ -285,8 +306,8 @@ async function waitForProviderTerminalOutput(provider, projectPath, waitMs, laun
|
|
|
285
306
|
error: error instanceof Error ? error.message : String(error),
|
|
286
307
|
}));
|
|
287
308
|
|
|
288
|
-
if (latestOutput?.output && isTerminalReadbackFinal(provider, latestOutput)) {
|
|
289
|
-
if (isTerminalReadbackHardFinal(provider, latestOutput)) {
|
|
309
|
+
if (latestOutput?.output && isTerminalReadbackFinal(provider, latestOutput, expectedInput)) {
|
|
310
|
+
if (isTerminalReadbackHardFinal(provider, latestOutput, expectedInput)) {
|
|
290
311
|
stableFinal = true;
|
|
291
312
|
break;
|
|
292
313
|
}
|
|
@@ -307,7 +328,7 @@ async function waitForProviderTerminalOutput(provider, projectPath, waitMs, laun
|
|
|
307
328
|
} while (Date.now() - startedAt < waitMs);
|
|
308
329
|
|
|
309
330
|
if (latestOutput && !latestOutput.terminalState) {
|
|
310
|
-
latestOutput.terminalState = inferTerminalState(provider, latestOutput);
|
|
331
|
+
latestOutput.terminalState = inferTerminalState(provider, latestOutput, expectedInput);
|
|
311
332
|
}
|
|
312
333
|
if (latestOutput && typeof latestOutput.isBusy !== 'boolean') {
|
|
313
334
|
latestOutput.isBusy = latestOutput.terminalState === 'busy';
|
|
@@ -436,10 +457,10 @@ async function callTool(name, args = {}) {
|
|
|
436
457
|
const requestedWaitMs = Number(args.waitForCompletionMs ?? args.waitForOutputMs ?? defaultWaitMs);
|
|
437
458
|
const waitForOutputMs = Math.min(600000, Math.max(0, requestedWaitMs));
|
|
438
459
|
if (waitForOutputMs > 0) {
|
|
439
|
-
terminalOutput = await waitForProviderTerminalOutput(provider, projectPath, waitForOutputMs, launchId);
|
|
460
|
+
terminalOutput = await waitForProviderTerminalOutput(provider, projectPath, waitForOutputMs, launchId, startupInput);
|
|
440
461
|
}
|
|
441
462
|
const terminalOutputFinal = terminalOutput
|
|
442
|
-
? Boolean(terminalOutput.terminalOutputFinal ?? isTerminalReadbackFinal(provider, terminalOutput))
|
|
463
|
+
? Boolean(terminalOutput.terminalOutputFinal ?? isTerminalReadbackFinal(provider, terminalOutput, startupInput))
|
|
443
464
|
: false;
|
|
444
465
|
return textResult(JSON.stringify({
|
|
445
466
|
launched: true,
|
|
@@ -495,7 +516,7 @@ async function callTool(name, args = {}) {
|
|
|
495
516
|
body: JSON.stringify({
|
|
496
517
|
projectPath: args.projectPath || null,
|
|
497
518
|
input: args.input || null,
|
|
498
|
-
startIfNeeded: args.startIfNeeded
|
|
519
|
+
startIfNeeded: args.startIfNeeded !== false,
|
|
499
520
|
}),
|
|
500
521
|
});
|
|
501
522
|
return textResult(JSON.stringify(body, null, 2));
|
|
@@ -127,7 +127,28 @@ const server = createServer(async (req, res) => {
|
|
|
127
127
|
provider: 'codex',
|
|
128
128
|
projectPath: '/root/pixcode',
|
|
129
129
|
terminalState: 'idle',
|
|
130
|
-
output: 'OpenAI Codex\n› /init\n
|
|
130
|
+
output: 'OpenAI Codex\n› /init\n',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
active: true,
|
|
134
|
+
provider: 'codex',
|
|
135
|
+
projectPath: '/root/pixcode',
|
|
136
|
+
terminalState: 'idle',
|
|
137
|
+
output: 'OpenAI Codex\n› /init\n',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
active: true,
|
|
141
|
+
provider: 'codex',
|
|
142
|
+
projectPath: '/root/pixcode',
|
|
143
|
+
terminalState: 'idle',
|
|
144
|
+
output: 'OpenAI Codex\n› /init\n',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
active: true,
|
|
148
|
+
provider: 'codex',
|
|
149
|
+
projectPath: '/root/pixcode',
|
|
150
|
+
terminalState: 'idle',
|
|
151
|
+
output: 'OpenAI Codex\n› /init\n',
|
|
131
152
|
},
|
|
132
153
|
{
|
|
133
154
|
active: true,
|
|
@@ -34,6 +34,16 @@ assert.match(
|
|
|
34
34
|
/pixcode:hermes-terminal[\s\S]+command[\s\S]+title/,
|
|
35
35
|
'Hermes settings should dispatch command and title to the workbench terminal.',
|
|
36
36
|
);
|
|
37
|
+
assert.match(
|
|
38
|
+
settingsTab,
|
|
39
|
+
/ensureGatewayReady/,
|
|
40
|
+
'Hermes settings should automatically start/probe the REST API gateway when Hermes is installed.',
|
|
41
|
+
);
|
|
42
|
+
assert.match(
|
|
43
|
+
settingsTab,
|
|
44
|
+
/startIfNeeded:\s*true/,
|
|
45
|
+
'Hermes settings should start the REST API gateway through Pixcode when checking gateway readiness.',
|
|
46
|
+
);
|
|
37
47
|
assert.match(
|
|
38
48
|
settingsModal,
|
|
39
49
|
/<HermesSettingsTab onClose=\{onClose\} \/>/,
|
|
@@ -74,6 +84,11 @@ assert.match(
|
|
|
74
84
|
/pixcode_read_cli_terminal/,
|
|
75
85
|
'Pixcode MCP should expose a terminal transcript reader so Hermes can report provider CLI output.',
|
|
76
86
|
);
|
|
87
|
+
assert.match(
|
|
88
|
+
pixcodeMcpServer,
|
|
89
|
+
/startIfNeeded:\s*args\.startIfNeeded !== false/,
|
|
90
|
+
'Pixcode MCP gateway probes should start the managed REST gateway by default unless Hermes explicitly opts out.',
|
|
91
|
+
);
|
|
77
92
|
assert.match(
|
|
78
93
|
pixcodeMcpServer,
|
|
79
94
|
/Use this instead of Hermes shell\/proc\/skill execution/,
|
|
@@ -94,6 +109,11 @@ assert.match(
|
|
|
94
109
|
/lastStrongBusy[\s\S]+lastPrompt[\s\S]+\?\s*'busy'\s*:\s*'idle'/,
|
|
95
110
|
'Codex readback should ignore weak spinner remnants once the prompt has returned.',
|
|
96
111
|
);
|
|
112
|
+
assert.match(
|
|
113
|
+
pixcodeMcpServer,
|
|
114
|
+
/codex_prompt_input_pending/,
|
|
115
|
+
'Codex readback should not treat a prompt line with typed-but-unsubmitted input as final output.',
|
|
116
|
+
);
|
|
97
117
|
assert.match(
|
|
98
118
|
serverIndex,
|
|
99
119
|
/lastStrongBusy[\s\S]+lastPrompt[\s\S]+\?\s*'busy'\s*:\s*'idle'/,
|
|
@@ -109,6 +129,11 @@ assert.match(
|
|
|
109
129
|
/requestedLaunchId[\s\S]+session\.hermesLaunchId === requestedLaunchId/,
|
|
110
130
|
'Provider output API should filter by Hermes terminal launch id when supplied.',
|
|
111
131
|
);
|
|
132
|
+
assert.match(
|
|
133
|
+
serverIndex,
|
|
134
|
+
/existingSession\.hermesLaunchId = hermesLaunchId \|\| existingSession\.hermesLaunchId/,
|
|
135
|
+
'Reused visible provider PTYs should be rebound to the latest Hermes launch id so MCP readback follows the current request.',
|
|
136
|
+
);
|
|
112
137
|
assert.match(
|
|
113
138
|
serverIndex,
|
|
114
139
|
/lifecycleState/,
|
|
@@ -244,6 +269,26 @@ assert.match(
|
|
|
244
269
|
/writeTerminalStartupInput/,
|
|
245
270
|
'Shell backend should submit Hermes startup input directly into reused visible PTYs.',
|
|
246
271
|
);
|
|
272
|
+
assert.match(
|
|
273
|
+
serverIndex,
|
|
274
|
+
/queueTerminalStartupInput/,
|
|
275
|
+
'Shell backend should queue Hermes startup input until the visible provider terminal is ready instead of writing blindly after a fixed delay.',
|
|
276
|
+
);
|
|
277
|
+
assert.match(
|
|
278
|
+
serverIndex,
|
|
279
|
+
/STARTUP_INPUT_READY_TIMEOUT_MS/,
|
|
280
|
+
'Queued provider startup input should have a bounded readiness timeout.',
|
|
281
|
+
);
|
|
282
|
+
assert.match(
|
|
283
|
+
serverIndex,
|
|
284
|
+
/resolveProviderTerminalState[\s\S]+terminalState === 'busy'/,
|
|
285
|
+
'Queued startup input should inspect the provider terminal state and avoid sending while the CLI is busy.',
|
|
286
|
+
);
|
|
287
|
+
assert.match(
|
|
288
|
+
serverIndex,
|
|
289
|
+
/\\x15/,
|
|
290
|
+
'Provider startup input should clear any half-typed prompt line before typing the requested work.',
|
|
291
|
+
);
|
|
247
292
|
assert.match(
|
|
248
293
|
serverIndex,
|
|
249
294
|
/startupInputDelivery === 'terminal'[\s\S]+writeTerminalStartupInput/,
|
package/server/index.js
CHANGED
|
@@ -153,6 +153,8 @@ let projectsWatchers = [];
|
|
|
153
153
|
let projectsWatcherDebounceTimer = null;
|
|
154
154
|
const connectedClients = new Set();
|
|
155
155
|
let isGetProjectsRunning = false; // Flag to prevent reentrant calls
|
|
156
|
+
const STARTUP_INPUT_READY_TIMEOUT_MS = 10 * 60 * 1000;
|
|
157
|
+
const STARTUP_INPUT_POLL_MS = 750;
|
|
156
158
|
|
|
157
159
|
// Broadcast progress to all connected WebSocket clients
|
|
158
160
|
function broadcastProgress(progress) {
|
|
@@ -298,6 +300,10 @@ function terminatePtySession(sessionKey, session, reason) {
|
|
|
298
300
|
if (session.timeoutId) {
|
|
299
301
|
clearTimeout(session.timeoutId);
|
|
300
302
|
}
|
|
303
|
+
if (session.startupInputTimerId) {
|
|
304
|
+
clearTimeout(session.startupInputTimerId);
|
|
305
|
+
session.startupInputTimerId = null;
|
|
306
|
+
}
|
|
301
307
|
|
|
302
308
|
try {
|
|
303
309
|
if (session.pty && session.pty.kill) {
|
|
@@ -436,23 +442,97 @@ function appendPtySessionBuffer(session, data) {
|
|
|
436
442
|
}
|
|
437
443
|
|
|
438
444
|
function normalizeTerminalStartupInput(input) {
|
|
439
|
-
return
|
|
445
|
+
return `\x15${String(input || '').replace(/(?:\r\n|\r|\n)+$/u, '')}\r`;
|
|
440
446
|
}
|
|
441
447
|
|
|
442
|
-
function
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
448
|
+
function readSessionOutputForState(session, maxChars = 12000) {
|
|
449
|
+
return stripAnsiSequences((session?.buffer || []).join('').slice(-maxChars));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function shouldWaitForProviderIdle(provider) {
|
|
453
|
+
return provider === 'codex';
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function isTerminalReadyForStartupInput(session) {
|
|
457
|
+
if (!session?.pty || session.lifecycleState !== 'running') {
|
|
458
|
+
return { ready: false, retry: false, terminalState: 'exited' };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const output = readSessionOutputForState(session);
|
|
462
|
+
const state = resolveProviderTerminalState(session, session.provider, output);
|
|
463
|
+
if (state.terminalState === 'busy') {
|
|
464
|
+
return { ready: false, retry: true, terminalState: state.terminalState };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (state.terminalState === 'idle') {
|
|
468
|
+
return { ready: true, retry: false, terminalState: state.terminalState };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (shouldWaitForProviderIdle(session.provider)) {
|
|
472
|
+
return { ready: false, retry: true, terminalState: state.terminalState };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return { ready: true, retry: false, terminalState: state.terminalState };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function processTerminalStartupInputQueue(session) {
|
|
479
|
+
if (!session?.pendingStartupInputs?.length) {
|
|
480
|
+
session.startupInputTimerId = null;
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const item = session.pendingStartupInputs[0];
|
|
485
|
+
const readiness = isTerminalReadyForStartupInput(session);
|
|
486
|
+
if (!readiness.ready) {
|
|
487
|
+
if (!readiness.retry || Date.now() - item.queuedAt > STARTUP_INPUT_READY_TIMEOUT_MS) {
|
|
488
|
+
session.pendingStartupInputs.shift();
|
|
489
|
+
session.startupInputTimerId = null;
|
|
490
|
+
const message = `\r\n\x1b[33m[Pixcode] Startup input was not sent because ${session.provider} is still ${readiness.terminalState || 'unavailable'}.\x1b[0m\r\n`;
|
|
491
|
+
try {
|
|
492
|
+
session.ws?.send?.(JSON.stringify({ type: 'output', data: message }));
|
|
493
|
+
} catch { /* websocket gone */ }
|
|
494
|
+
if (session.pendingStartupInputs.length > 0) {
|
|
495
|
+
session.startupInputTimerId = setTimeout(() => processTerminalStartupInputQueue(session), STARTUP_INPUT_POLL_MS);
|
|
451
496
|
}
|
|
452
|
-
|
|
453
|
-
console.warn('Failed to submit startup input to visible PTY:', error?.message || error);
|
|
497
|
+
return;
|
|
454
498
|
}
|
|
455
|
-
|
|
499
|
+
|
|
500
|
+
session.startupInputTimerId = setTimeout(() => processTerminalStartupInputQueue(session), STARTUP_INPUT_POLL_MS);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
session.pendingStartupInputs.shift();
|
|
505
|
+
session.startupInputTimerId = null;
|
|
506
|
+
try {
|
|
507
|
+
session.pty.write(normalizeTerminalStartupInput(item.startupInput));
|
|
508
|
+
session.updatedAt = Date.now();
|
|
509
|
+
console.log(`⌨️ Submitted startup input to visible PTY (${item.reason})`);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
console.warn('Failed to submit startup input to visible PTY:', error?.message || error);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (session.pendingStartupInputs.length > 0) {
|
|
515
|
+
session.startupInputTimerId = setTimeout(() => processTerminalStartupInputQueue(session), STARTUP_INPUT_POLL_MS);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function queueTerminalStartupInput(session, startupInput, reason, delayMs = 500) {
|
|
520
|
+
if (!session?.pty || !startupInput) return;
|
|
521
|
+
if (!Array.isArray(session.pendingStartupInputs)) {
|
|
522
|
+
session.pendingStartupInputs = [];
|
|
523
|
+
}
|
|
524
|
+
session.pendingStartupInputs.push({
|
|
525
|
+
startupInput,
|
|
526
|
+
reason,
|
|
527
|
+
queuedAt: Date.now(),
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
if (session.startupInputTimerId) return;
|
|
531
|
+
session.startupInputTimerId = setTimeout(() => processTerminalStartupInputQueue(session), delayMs);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function writeTerminalStartupInput(session, startupInput, reason, delayMs = 500) {
|
|
535
|
+
queueTerminalStartupInput(session, startupInput, reason, delayMs);
|
|
456
536
|
}
|
|
457
537
|
|
|
458
538
|
function normalizeShellPermissionMode(value) {
|
|
@@ -2497,6 +2577,8 @@ function handleShellConnection(ws, request) {
|
|
|
2497
2577
|
}
|
|
2498
2578
|
|
|
2499
2579
|
existingSession.ws = ws;
|
|
2580
|
+
existingSession.hermesLaunchId = hermesLaunchId || existingSession.hermesLaunchId;
|
|
2581
|
+
existingSession.updatedAt = Date.now();
|
|
2500
2582
|
if (terminalStartupInput && !isPlainShell) {
|
|
2501
2583
|
writeTerminalStartupInput(existingSession, terminalStartupInput, 'reused provider session', 350);
|
|
2502
2584
|
}
|
|
@@ -2708,6 +2790,8 @@ function handleShellConnection(ws, request) {
|
|
|
2708
2790
|
exitSignal: null,
|
|
2709
2791
|
completedAt: null,
|
|
2710
2792
|
keepAliveUntilExit: false,
|
|
2793
|
+
pendingStartupInputs: [],
|
|
2794
|
+
startupInputTimerId: null,
|
|
2711
2795
|
updatedAt: Date.now(),
|
|
2712
2796
|
});
|
|
2713
2797
|
const createdSession = ptySessionsMap.get(ptySessionKey);
|
|
@@ -2792,6 +2876,11 @@ function handleShellConnection(ws, request) {
|
|
|
2792
2876
|
session.exitSignal = exitCode.signal || null;
|
|
2793
2877
|
session.completedAt = new Date().toISOString();
|
|
2794
2878
|
session.updatedAt = Date.now();
|
|
2879
|
+
if (session.startupInputTimerId) {
|
|
2880
|
+
clearTimeout(session.startupInputTimerId);
|
|
2881
|
+
session.startupInputTimerId = null;
|
|
2882
|
+
}
|
|
2883
|
+
session.pendingStartupInputs = [];
|
|
2795
2884
|
session.pty = null;
|
|
2796
2885
|
appendPtySessionBuffer(session, exitMessage);
|
|
2797
2886
|
}
|