@volter/supercode-terminal 0.2.51 → 0.2.53

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/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execFile, spawn, spawnSync } from 'node:child_process';
2
2
  import { randomBytes, randomUUID } from 'node:crypto';
3
- import { appendFileSync, cpSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
5
  import { dirname, join } from 'node:path';
6
6
  import { connect as netConnect } from 'node:net';
@@ -514,11 +514,15 @@ for (const event of ['end', 'close', 'error']) process.stdin.on(event, sweep);
514
514
  * signal=…` when it failed), under SUPERCODE_HOME or ~/.config/supercode: the machine daemon tells a clean finish from
515
515
  * a failure by it, and a ConPTY pane that ended cleanly is otherwise gone without a trace.
516
516
  */
517
- function recordPaneExit(contextKey, exitCode, signal) {
517
+ function recordPaneExit(contextKey, exitCode, signal, screen = null) {
518
518
  if (!contextKey) return;
519
519
  const at = new Date().toISOString().slice(0, 19);
520
520
  const line = exitCode === 0 && !signal ? `${at} ${contextKey} closed\n` : `${at} ${contextKey} died status=${exitCode ?? ''} signal=${signal || ''}\n`;
521
521
  try { appendFileSync(join(process.env.SUPERCODE_HOME || join(homedir(), '.config', 'supercode'), 'pane-exits.log'), line); } catch { /* no log is no worse than before */ }
522
+ if ((exitCode !== 0 || signal) && typeof screen === 'string' && /^p_[A-Za-z0-9_-]+$/.test(contextKey)) {
523
+ try { const dir=join(process.env.SUPERCODE_HOME || join(homedir(), '.config', 'supercode'),'pane-exits');mkdirSync(dir,{recursive:true,mode:0o700});writeFileSync(join(dir,`${contextKey}.screen`),Buffer.from(screen).subarray(-32768),{mode:0o600}); }
524
+ catch { /* The receipt explicitly reports unavailable output. */ }
525
+ }
522
526
  }
523
527
 
524
528
  export class PtyTerminalHost {
@@ -597,7 +601,7 @@ export class PtyTerminalHost {
597
601
  // server) outlives it, as it outlives a tmux pane. What stayed in the pane's console ends with the console.
598
602
  // Only closing a pane, or its daemon dying, ends a whole tree.
599
603
  releasePane(child);
600
- recordPaneExit(entry.contextKey, exitCode, signal);
604
+ recordPaneExit(entry.contextKey, exitCode, signal, entry.serializer.serialize({scrollback:200}));
601
605
  this.#tellExit(entry);
602
606
  this.#writeWatchList();
603
607
  if (exitCode === 0) this.#forget(entry);
@@ -1739,7 +1743,7 @@ export function composerText(kind, screen) {
1739
1743
  const lines = String(screen ?? '').split('\n').map(styledCells);
1740
1744
  const plain = lines.map((cells) => cells.map((cell) => cell.char).join(''));
1741
1745
  const rule = /^\s*[─━]{4,}\s*$/u;
1742
- const footer = /^\s*(?:\? for shortcuts\b|[^\n]+ · (?:~(?:[\\/]|(?=\s|$))|[\\/]|[A-Z]:[\\/])[^\n]*$|.*← for agents\s*$|.*\bcontext(?:\s+\d+%)?\s+left\b)/iu;
1746
+ const footer = /^\s*(?:\? for shortcuts\b|⚠\s+\d+\s+warnings?\s+·\s+f2 to view\s*$|[^\n]+ · (?:~(?:[\\/]|(?=\s|$))|[\\/]|[A-Z]:[\\/])[^\n]*$|.*← for agents\s*$|.*\bcontext(?:\s+\d+%)?\s+left\b)/iu;
1743
1747
  const caret = kind === 'codex' ? /^\s*›(?:\s|$)/u : /^\s*[❯>](?:\s|$)/u;
1744
1748
  for (let row = plain.length - 1; row >= 0; row -= 1) {
1745
1749
  if (!caret.test(plain[row])) continue;
@@ -1748,8 +1752,9 @@ export function composerText(kind, screen) {
1748
1752
  // Claude encloses the editor in rules; Codex ends it at its shortcut/context footer.
1749
1753
  if (end === plain.length) return null;
1750
1754
  // Startup can draw a caret before loading finishes or a modal opens. A rule
1751
- // alone is not Codex's ready editor; require its footer at the live bottom.
1752
- if (kind === 'codex' && (plain.slice(end + 1).some((line) => line.trim()) ||
1755
+ // alone is not Codex's ready editor; require only recognized footer rows at
1756
+ // the live bottom (model/cwd and shortcuts can occupy separate rows).
1757
+ if (kind === 'codex' && (plain.slice(end + 1).some((line) => line.trim() && !footer.test(line)) ||
1753
1758
  /\bmodel:\s+loading\b/iu.test(plain.findLast((line) => /\bmodel:/iu.test(line)) ?? ''))) return null;
1754
1759
  if (kind === 'claude-code' && (row === 0 || !rule.test(plain[row - 1]))) return null;
1755
1760
  const first = plain[row].match(caret)[0].length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-terminal",
3
- "version": "0.2.51",
3
+ "version": "0.2.53",
4
4
  "type": "module",
5
5
  "description": "Optional leak-safe terminal and tmux host adapter for Volter Harness frontends",
6
6
  "exports": {
package/substrate/tmux.js CHANGED
@@ -343,7 +343,8 @@ async function declareSessionColorEnvironmentAsync(socket) {
343
343
  // Targets are not expanded as formats, so the shell that run-shell starts resolves
344
344
  // the pane and does nothing when it no longer belongs to a p_* session.
345
345
  const PANE_EXIT_LOG = '"${SUPERCODE_HOME:-$HOME/.config/supercode}/pane-exits.log"';
346
- const PANE_DIED_HOOK = "run-shell 'p=\"#{hook_pane}\"; t() { tmux -S \"#{socket_path}\" \"$@\"; }; s=$(t display -p -t \"$p\" \"##{session_name}\" 2>/dev/null) || exit 0; case \"$s\" in p_*) ;; *) exit 0 ;; esac; printf \"%s %s died status=%s signal=%s\\n\" \"$(date +%FT%T)\" \"$s\" \"$(t display -p -t \"$p\" \"##{pane_dead_status}\")\" \"$(t display -p -t \"$p\" \"##{pane_dead_signal}\")\" >> " + PANE_EXIT_LOG + "; t kill-pane -t \"$p\"'";
346
+ const PANE_EXIT_CAPTURE = 'case "$s" in *[!a-zA-Z0-9_-]*) exit 0 ;; esac; d="${SUPERCODE_HOME:-$HOME/.config/supercode}/pane-exits"; umask 077; mkdir -p "$d"; t capture-pane -p -e -S -200 -t "$p" 2>/dev/null | tail -c 32768 > "$d/$s.screen"';
347
+ const PANE_DIED_HOOK = "run-shell 'p=\"#{hook_pane}\"; t() { tmux -S \"#{socket_path}\" \"$@\"; }; s=$(t display -p -t \"$p\" \"##{session_name}\" 2>/dev/null) || exit 0; case \"$s\" in p_*) ;; *) exit 0 ;; esac; printf \"%s %s died status=%s signal=%s\\n\" \"$(date +%FT%T)\" \"$s\" \"$(t display -p -t \"$p\" \"##{pane_dead_status}\")\" \"$(t display -p -t \"$p\" \"##{pane_dead_signal}\")\" >> " + PANE_EXIT_LOG + "; " + PANE_EXIT_CAPTURE + "; t kill-pane -t \"$p\"'";
347
348
  const SESSION_CLOSED_HOOK = "if-shell -F '#{m:p_*,#{hook_session_name}}' { run-shell 'printf \"%s %s closed\\n\" \"$(date +%FT%T)\" \"#{hook_session_name}\" >> " + PANE_EXIT_LOG + "' }";
348
349
  function sessionServerOptionArgs() {
349
350
  return [