fix: Project action terminal lifecycle (#3287)

* fix(terminal): make command sessions own action lifecycle

* fix(ui): reconcile project action terminal state

* feat(ui): show running project actions in terminal tabs

* feat(ui): run project actions from linked worktrees

* fix(ui): guard project action reconciliation

* fix(ui): scope project action preview fallback

* fix(ui): default project actions to worktrees

* fix(ui): reveal project action terminals

* fix(ui): retain terminal output after snapshot replay

* fix(ui): restore running action terminals on revisit
This commit is contained in:
Matt Visnovsky
2026-09-05 12:04:36 +03:00
committed by GitHub
parent 58dfc789a2
commit 4e0eed717d
53 changed files with 5194 additions and 608 deletions
@@ -1,5 +1,6 @@
const TERMINAL_SHELL_IDS = ['bash', 'zsh', 'sh', 'fish', 'pwsh', 'powershell', 'cmd', 'dash', 'ksh', 'nu'];
const TERMINAL_SHELL_ID_SET = new Set(TERMINAL_SHELL_IDS);
const isString = (value) => String(value) === value;
export const normalizeTerminalShell = (value) => {
if (typeof value !== 'string') return null;
@@ -24,6 +25,22 @@ export const getTerminalShellLoginArgs = (executable, platform = process.platfor
return null;
};
export const buildTerminalShellLaunch = (executable, { mode = 'interactive', command = null, loginShell = false, platform = process.platform } = {}) => {
const loginArgs = loginShell ? getTerminalShellLoginArgs(executable, platform) : [];
if (!loginArgs) throw new Error(`Terminal shell "${shellIdFromPath(executable) ?? executable}" does not support login mode`);
if (mode === 'interactive') return { executable, args: loginArgs };
const trimmedCommand = isString(command) ? command.trim() : '';
if (!trimmedCommand) throw new Error('Terminal command is required');
const id = shellIdFromPath(executable);
if (id === 'nu') return { executable, args: [...loginArgs, '-c', trimmedCommand] };
if (id === 'pwsh' || id === 'powershell') return { executable, args: [...loginArgs, '-Command', trimmedCommand] };
if (id === 'cmd') return { executable, args: ['/d', '/s', '/c', trimmedCommand] };
return { executable, args: [...loginArgs, '-i', '-c', trimmedCommand] };
};
export const createTerminalShellResolver = ({ fs, path, searchPathFor, isExecutable, buildAugmentedPath = () => env.PATH || '', platform = process.platform, env = process.env }) => {
const resolveExecutable = (candidate) => {
if (!candidate) return null;