@pixelbyte-software/pixcode 1.50.4 → 1.50.6

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/index.html CHANGED
@@ -35,7 +35,7 @@
35
35
 
36
36
  <!-- Prevent zoom on iOS -->
37
37
  <meta name="format-detection" content="telephone=no" />
38
- <script type="module" crossorigin src="/assets/index-DYQjBZrd.js"></script>
38
+ <script type="module" crossorigin src="/assets/index-DVEXTVKy.js"></script>
39
39
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DB6V5Fl1.js">
40
40
  <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-CIYNS698.js">
41
41
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-C7tpxJl7.js">
@@ -69,6 +69,7 @@ import providerRoutes from './modules/providers/provider.routes.js';
69
69
  import { createHermesTaskRouter, adapterRegistry, ClaudeCodeA2AAdapter, CodexA2AAdapter, CursorA2AAdapter, GeminiA2AAdapter, OpenCodeA2AAdapter, QwenA2AAdapter, createPreviewProxyRouter, createOrchestrationTaskRouter, createHermesRouter, createWorkflowRouter, } from './modules/orchestration/index.js';
70
70
  import networkRoutes from './routes/network.js';
71
71
  import telegramRoutes from './routes/telegram.js';
72
+ import { restoreRequestedTunnel } from './services/external-access.js';
72
73
  import { restoreBotFromConfig } from './services/telegram/bot.js';
73
74
  import { ensurePortOpen } from './utils/port-access.js';
74
75
  import { applyAllStoredCredentialsToEnv, } from './services/provider-credentials.js';
@@ -232,6 +233,7 @@ const app = express();
232
233
  const server = http.createServer(app);
233
234
  const ptySessionsMap = new Map();
234
235
  const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
236
+ const COMPLETED_PTY_SESSION_TTL = 5 * 60 * 1000;
235
237
  const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
236
238
  const SHELL_CLI_PROVIDERS = new Set(['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode']);
237
239
  import { stripAnsiSequences, normalizeDetectedUrl, extractUrlsFromText, shouldAutoOpenUrlFromOutput } from './utils/url-detection.js';
@@ -287,22 +289,24 @@ function detectProviderTerminalState(provider, output) {
287
289
  terminalStateReason: 'process_exit',
288
290
  };
289
291
  }
290
- const lastBusy = Math.max(getLastRegexMatchIndex(cleanOutput, /(?:^|\n)\s*[•*]\s*(?:Working|Running|Thinking)\b/giu), getLastRegexMatchIndex(cleanOutput, /\bWorking\s*\([^)]*esc to interrupt[^)]*\)/giu), getLastRegexMatchIndex(cleanOutput, /\bmsg=interrupt\b/giu));
292
+ const lastWeakBusy = getLastRegexMatchIndex(cleanOutput, /(?:^|\n)\s*[•*]\s*(?:Working|Running|Thinking)\b/giu);
293
+ const lastStrongBusy = Math.max(getLastRegexMatchIndex(cleanOutput, /\bWorking\s*\([^)]*esc to interrupt[^)]*\)/giu), getLastRegexMatchIndex(cleanOutput, /\bmsg=interrupt\b/giu));
294
+ const lastBusy = Math.max(lastWeakBusy, lastStrongBusy);
291
295
  if (provider === 'codex') {
292
296
  const lastPrompt = Math.max(getLastRegexMatchIndex(cleanOutput, /(?:^|\n)\s*›(?:\s|$)/gu), getLastRegexMatchIndex(cleanOutput, /(?:^|\n)\s*❯(?:\s|$)/gu));
293
- if (lastBusy >= 0) {
294
- const isBusy = lastPrompt <= lastBusy;
297
+ if (lastPrompt >= 0) {
298
+ const isBusy = lastStrongBusy > lastPrompt;
295
299
  return {
296
300
  terminalState: isBusy ? 'busy' : 'idle',
297
301
  isBusy,
298
- terminalStateReason: isBusy ? 'codex_busy_marker_after_prompt' : 'codex_prompt_after_busy_marker',
302
+ terminalStateReason: isBusy ? 'codex_strong_busy_marker_after_prompt' : 'codex_prompt_after_busy_marker',
299
303
  };
300
304
  }
301
- if (lastPrompt >= 0 && /(?:Initialized|Baseline check passed|I did not modify files|Use \/skills)/iu.test(cleanOutput)) {
305
+ if (lastBusy >= 0) {
302
306
  return {
303
- terminalState: 'idle',
304
- isBusy: false,
305
- terminalStateReason: 'codex_idle_prompt',
307
+ terminalState: 'busy',
308
+ isBusy: true,
309
+ terminalStateReason: 'codex_busy_marker_without_prompt',
306
310
  };
307
311
  }
308
312
  }
@@ -319,6 +323,42 @@ function detectProviderTerminalState(provider, output) {
319
323
  terminalStateReason: 'no_known_marker',
320
324
  };
321
325
  }
326
+ function resolveProviderTerminalState(session, provider, output) {
327
+ if (session?.lifecycleState === 'completed' || session?.lifecycleState === 'failed' || session?.lifecycleState === 'exited') {
328
+ const exitCode = typeof session.exitCode === 'number' ? session.exitCode : null;
329
+ const terminalFailed = exitCode !== null ? exitCode !== 0 : Boolean(session.exitSignal);
330
+ return {
331
+ terminalState: terminalFailed ? 'failed' : 'completed',
332
+ lifecycleState: session.lifecycleState,
333
+ isBusy: false,
334
+ terminalFailed,
335
+ exitCode,
336
+ exitSignal: session.exitSignal || null,
337
+ completedAt: session.completedAt || null,
338
+ terminalStateReason: terminalFailed ? 'pty_failed' : 'pty_completed',
339
+ };
340
+ }
341
+ const detected = detectProviderTerminalState(provider, output);
342
+ return {
343
+ ...detected,
344
+ lifecycleState: session?.lifecycleState || 'running',
345
+ terminalFailed: false,
346
+ exitCode: null,
347
+ exitSignal: null,
348
+ completedAt: null,
349
+ };
350
+ }
351
+ function appendPtySessionBuffer(session, data) {
352
+ if (!session)
353
+ return;
354
+ if (session.buffer.length < 5000) {
355
+ session.buffer.push(data);
356
+ }
357
+ else {
358
+ session.buffer.shift();
359
+ session.buffer.push(data);
360
+ }
361
+ }
322
362
  function normalizeShellPermissionMode(value) {
323
363
  return typeof value === 'string' ? value.trim() : '';
324
364
  }
@@ -524,7 +564,7 @@ app.get('/api/shell/sessions/provider-output', authenticateToken, (req, res) =>
524
564
  }
525
565
  const rawOutput = matchedSession.buffer.join('').slice(-maxChars);
526
566
  const output = stripAnsiSequences(rawOutput);
527
- const terminalState = detectProviderTerminalState(provider, output);
567
+ const terminalState = resolveProviderTerminalState(matchedSession, provider, output);
528
568
  res.json({
529
569
  active: true,
530
570
  provider,
@@ -2202,24 +2242,29 @@ function handleShellConnection(ws, request) {
2202
2242
  }
2203
2243
  const existingSession = (isLoginCommand || forceNewSession) ? null : ptySessionsMap.get(ptySessionKey);
2204
2244
  if (existingSession) {
2205
- console.log('♻️ Reconnecting to existing PTY session:', ptySessionKey);
2206
- shellProcess = existingSession.pty;
2207
- clearTimeout(existingSession.timeoutId);
2208
- ws.send(JSON.stringify({
2209
- type: 'output',
2210
- data: `\x1b[36m[Reconnected to existing session]\x1b[0m\r\n`
2211
- }));
2212
- if (existingSession.buffer && existingSession.buffer.length > 0) {
2213
- console.log(`📜 Sending ${existingSession.buffer.length} buffered messages`);
2214
- existingSession.buffer.forEach(bufferedData => {
2215
- ws.send(JSON.stringify({
2216
- type: 'output',
2217
- data: bufferedData
2218
- }));
2219
- });
2245
+ if (!existingSession.pty || existingSession.lifecycleState === 'completed' || existingSession.lifecycleState === 'failed') {
2246
+ ptySessionsMap.delete(ptySessionKey);
2247
+ }
2248
+ else {
2249
+ console.log('♻️ Reconnecting to existing PTY session:', ptySessionKey);
2250
+ shellProcess = existingSession.pty;
2251
+ clearTimeout(existingSession.timeoutId);
2252
+ ws.send(JSON.stringify({
2253
+ type: 'output',
2254
+ data: `\x1b[36m[Reconnected to existing session]\x1b[0m\r\n`
2255
+ }));
2256
+ if (existingSession.buffer && existingSession.buffer.length > 0) {
2257
+ console.log(`📜 Sending ${existingSession.buffer.length} buffered messages`);
2258
+ existingSession.buffer.forEach(bufferedData => {
2259
+ ws.send(JSON.stringify({
2260
+ type: 'output',
2261
+ data: bufferedData
2262
+ }));
2263
+ });
2264
+ }
2265
+ existingSession.ws = ws;
2266
+ return;
2220
2267
  }
2221
- existingSession.ws = ws;
2222
- return;
2223
2268
  }
2224
2269
  console.log('[INFO] Starting shell in:', projectPath);
2225
2270
  console.log('📋 Session info:', hasSession ? `Resume session ${sessionId}` : (isPlainShell ? 'Plain shell mode' : 'New session'));
@@ -2425,6 +2470,10 @@ function handleShellConnection(ws, request) {
2425
2470
  hermesLaunchId,
2426
2471
  provider,
2427
2472
  isPlainShell,
2473
+ lifecycleState: 'running',
2474
+ exitCode: null,
2475
+ exitSignal: null,
2476
+ completedAt: null,
2428
2477
  keepAliveUntilExit: false,
2429
2478
  updatedAt: Date.now(),
2430
2479
  });
@@ -2434,13 +2483,7 @@ function handleShellConnection(ws, request) {
2434
2483
  if (!session)
2435
2484
  return;
2436
2485
  session.updatedAt = Date.now();
2437
- if (session.buffer.length < 5000) {
2438
- session.buffer.push(data);
2439
- }
2440
- else {
2441
- session.buffer.shift();
2442
- session.buffer.push(data);
2443
- }
2486
+ appendPtySessionBuffer(session, data);
2444
2487
  if (session.ws && session.ws.readyState === WebSocket.OPEN) {
2445
2488
  let outputData = data;
2446
2489
  const cleanChunk = stripAnsiSequences(data);
@@ -2481,16 +2524,41 @@ function handleShellConnection(ws, request) {
2481
2524
  shellProcess.onExit((exitCode) => {
2482
2525
  console.log('🔚 Shell process exited with code:', exitCode.exitCode, 'signal:', exitCode.signal);
2483
2526
  const session = ptySessionsMap.get(ptySessionKey);
2527
+ if (session?.pty && session.pty !== shellProcess) {
2528
+ console.log('↩️ Ignoring stale PTY exit for replacement session:', ptySessionKey);
2529
+ return;
2530
+ }
2531
+ const exitMessage = `\r\n\x1b[33mProcess exited with code ${exitCode.exitCode}${exitCode.signal ? ` (${exitCode.signal})` : ''}\x1b[0m\r\n`;
2532
+ if (session) {
2533
+ session.lifecycleState = exitCode.exitCode === 0 && !exitCode.signal ? 'completed' : 'failed';
2534
+ session.exitCode = typeof exitCode.exitCode === 'number' ? exitCode.exitCode : null;
2535
+ session.exitSignal = exitCode.signal || null;
2536
+ session.completedAt = new Date().toISOString();
2537
+ session.updatedAt = Date.now();
2538
+ session.pty = null;
2539
+ appendPtySessionBuffer(session, exitMessage);
2540
+ }
2484
2541
  if (session && session.ws && session.ws.readyState === WebSocket.OPEN) {
2485
2542
  session.ws.send(JSON.stringify({
2486
2543
  type: 'output',
2487
- data: `\r\n\x1b[33mProcess exited with code ${exitCode.exitCode}${exitCode.signal ? ` (${exitCode.signal})` : ''}\x1b[0m\r\n`
2544
+ data: exitMessage
2488
2545
  }));
2489
2546
  }
2490
2547
  if (session && session.timeoutId) {
2491
2548
  clearTimeout(session.timeoutId);
2492
2549
  }
2493
- ptySessionsMap.delete(ptySessionKey);
2550
+ if (session) {
2551
+ session.ws = null;
2552
+ session.timeoutId = setTimeout(() => {
2553
+ const current = ptySessionsMap.get(ptySessionKey);
2554
+ if (current && current.lifecycleState !== 'running') {
2555
+ ptySessionsMap.delete(ptySessionKey);
2556
+ }
2557
+ }, COMPLETED_PTY_SESSION_TTL);
2558
+ }
2559
+ else {
2560
+ ptySessionsMap.delete(ptySessionKey);
2561
+ }
2494
2562
  shellProcess = null;
2495
2563
  });
2496
2564
  }
@@ -3195,6 +3263,9 @@ async function startServer() {
3195
3263
  catch (err) {
3196
3264
  console.log(`${c.dim('[INFO]')} Port-access helper failed: ${err?.message || err}`);
3197
3265
  }
3266
+ restoreRequestedTunnel({ port: Number(SERVER_PORT) }).catch((err) => {
3267
+ console.warn('[external-access] tunnel restore failed:', err?.message || err);
3268
+ });
3198
3269
  console.log(`${c.tip('[TIP]')} Run "pixcode status" for full configuration details`);
3199
3270
  console.log('');
3200
3271
  // Start watching the projects folder for changes