Merge branch 'master' into pr181

This commit is contained in:
Codeman maintainer
2026-08-05 00:04:43 +02:00
77 changed files with 8151 additions and 571 deletions
+61 -3
View File
@@ -43,6 +43,7 @@ import {
type CodexConfig,
type EffortLevel,
type GeminiConfig,
type AntigravityConfig,
type SessionRemote,
type SessionDocker,
type DockerCommandMode,
@@ -69,6 +70,8 @@ import {
resolveOpenCodeDir,
resolveCodexDir,
resolveGeminiDir,
resolveAntigravityDir,
resolveLocalShell,
} from './utils/index.js';
import type {
TerminalMultiplexer,
@@ -686,6 +689,34 @@ function buildGeminiCommand(config?: GeminiConfig): string {
return parts.join(' ');
}
/**
* Build the Antigravity CLI (agy) command with appropriate flags.
*
* Unlike gemini's yolo default, `--dangerously-skip-permissions` is only added
* when the config explicitly asks for it (the frontend sends it for parity with
* Codeman's Claude default; the multi-user clamp strips it for non-granted owners,
* and an ABSENT config stays at agy's own prompting default — safe like Codex).
*/
function buildAntigravityCommand(config?: AntigravityConfig): string {
const parts = ['agy'];
if (config?.dangerouslySkipPermissions) {
parts.push('--dangerously-skip-permissions');
}
if (config?.model) {
const safeModel = /^[a-zA-Z0-9._\-/]+$/.test(config.model) ? config.model : undefined;
if (safeModel) parts.push('--model', safeModel);
}
if (config?.resumeConversationId) {
const safeId = /^[a-zA-Z0-9._-]+$/.test(config.resumeConversationId) ? config.resumeConversationId : undefined;
if (safeId) parts.push('--conversation', safeId);
}
return parts.join(' ');
}
/**
* Build the spawn command for any session mode.
* Shared by createSession() and respawnPane() to avoid duplication.
@@ -713,6 +744,7 @@ export function buildSpawnCommand(options: {
openCodeConfig?: OpenCodeConfig;
codexConfig?: CodexConfig;
geminiConfig?: GeminiConfig;
antigravityConfig?: AntigravityConfig;
resumeSessionId?: string;
effort?: EffortLevel;
}): string {
@@ -743,7 +775,16 @@ export function buildSpawnCommand(options: {
if (options.mode === 'gemini') {
return buildGeminiCommand(options.geminiConfig);
}
return '$SHELL';
if (options.mode === 'antigravity') {
return buildAntigravityCommand(options.antigravityConfig);
}
// #208: NOT the literal '$SHELL'. This string is embedded in the `bash -c "…"`
// argument of the respawn-pane line, which execSync runs through `/bin/sh -c`,
// so a `$SHELL` here is expanded by the SERVER process's shell against the
// SERVER process's env — empty in containers and system systemd units, leaving
// the pane command ending in a dangling `&&` ("syntax error: unexpected end of
// file", pane dead on arrival). Resolve it in Node and quote the result.
return shellescape(resolveLocalShell());
}
/**
@@ -921,6 +962,8 @@ function appendResumeFlag(modeCommand: string, mode: SessionMode, resumeId: stri
return `${modeCommand} --resume ${resumeId}`;
case 'codex':
return `${modeCommand} resume ${resumeId}`;
case 'antigravity':
return `${modeCommand} --conversation ${resumeId}`;
default:
return modeCommand; // shell / opencode: no resume
}
@@ -1489,8 +1532,10 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
const exports = [
'export LANG=en_US.UTF-8',
'export LC_ALL=en_US.UTF-8',
mode === 'codex' || mode === 'gemini' ? 'export COLORTERM=truecolor' : 'unset COLORTERM',
...(mode === 'codex' || mode === 'gemini' ? ['unset NO_COLOR'] : []),
mode === 'codex' || mode === 'gemini' || mode === 'antigravity'
? 'export COLORTERM=truecolor'
: 'unset COLORTERM',
...(mode === 'codex' || mode === 'gemini' || mode === 'antigravity' ? ['unset NO_COLOR'] : []),
// Stamp each Codex pane with a unique originator so the response-viewer
// can locate THIS pane's rollout exactly — codex writes the value into
// session_meta.originator of every rollout it creates. Without it,
@@ -1573,6 +1618,10 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
const dir = resolveGeminiDir();
return { pathExport: dir ? `export PATH="${dir}:$PATH" && ` : '', dir };
}
if (mode === 'antigravity') {
const dir = resolveAntigravityDir();
return { pathExport: dir ? `export PATH="${dir}:$PATH" && ` : '', dir };
}
return { pathExport: '', dir: null };
}
@@ -1620,6 +1669,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
openCodeConfig,
codexConfig,
geminiConfig,
antigravityConfig,
resumeSessionId,
envOverrides,
effort,
@@ -1671,6 +1721,11 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
if (mode === 'gemini' && !cliDir) {
throw new Error('Gemini CLI not found. Install with: npm install -g @google/gemini-cli');
}
if (mode === 'antigravity' && !cliDir) {
throw new Error(
'Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash'
);
}
const envExportsStr = this.buildEnvExports(sessionId, muxName, mode).join(' && ');
@@ -1683,6 +1738,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
openCodeConfig,
codexConfig,
geminiConfig,
antigravityConfig,
resumeSessionId,
effort,
});
@@ -1905,6 +1961,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
openCodeConfig,
codexConfig,
geminiConfig,
antigravityConfig,
resumeSessionId,
envOverrides,
effort,
@@ -1942,6 +1999,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
openCodeConfig,
codexConfig,
geminiConfig,
antigravityConfig,
resumeSessionId,
effort,
});