@proletariat/cli 0.3.48 → 0.3.49

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.
Files changed (38) hide show
  1. package/dist/commands/claude/index.js +21 -21
  2. package/dist/commands/claude/open.js +1 -1
  3. package/dist/commands/config/index.js +2 -2
  4. package/dist/commands/execution/config.d.ts +2 -2
  5. package/dist/commands/execution/config.js +18 -18
  6. package/dist/commands/execution/list.js +2 -2
  7. package/dist/commands/execution/view.js +2 -2
  8. package/dist/commands/orchestrator/start.d.ts +1 -1
  9. package/dist/commands/orchestrator/start.js +19 -19
  10. package/dist/commands/qa/index.js +12 -12
  11. package/dist/commands/staff/add.js +1 -1
  12. package/dist/commands/work/linear.js +28 -5
  13. package/dist/commands/work/revise.js +8 -8
  14. package/dist/commands/work/spawn.js +1 -1
  15. package/dist/commands/work/start.js +12 -12
  16. package/dist/commands/work/watch.js +3 -3
  17. package/dist/lib/agents/index.js +2 -2
  18. package/dist/lib/database/drizzle-schema.d.ts +7 -7
  19. package/dist/lib/database/drizzle-schema.js +1 -1
  20. package/dist/lib/execution/config.d.ts +6 -6
  21. package/dist/lib/execution/config.js +17 -10
  22. package/dist/lib/execution/devcontainer.d.ts +3 -3
  23. package/dist/lib/execution/devcontainer.js +3 -3
  24. package/dist/lib/execution/runners.d.ts +2 -2
  25. package/dist/lib/execution/runners.js +23 -24
  26. package/dist/lib/execution/spawner.js +3 -3
  27. package/dist/lib/execution/storage.d.ts +2 -2
  28. package/dist/lib/execution/storage.js +3 -3
  29. package/dist/lib/execution/types.d.ts +2 -2
  30. package/dist/lib/execution/types.js +1 -1
  31. package/dist/lib/external-issues/linear.d.ts +6 -0
  32. package/dist/lib/external-issues/linear.js +63 -0
  33. package/dist/lib/pmo/schema.d.ts +1 -1
  34. package/dist/lib/pmo/schema.js +1 -1
  35. package/dist/lib/pmo/storage/base.js +31 -0
  36. package/dist/lib/repos/index.js +1 -1
  37. package/oclif.manifest.json +4318 -4313
  38. package/package.json +1 -1
@@ -76,7 +76,7 @@ export default class WorkRevise extends PMOCommand {
76
76
  // Early Docker check
77
77
  if (!flags['run-on-host'] && !isDockerRunning()) {
78
78
  return handleError('DOCKER_NOT_RUNNING', 'Docker is not running.\n\n' +
79
- 'Docker is required for devcontainer execution (recommended for agent sandboxing).\n' +
79
+ 'Docker is required for devcontainer execution (recommended for agent isolation).\n' +
80
80
  'Please start Docker Desktop and try again.\n\n' +
81
81
  'Alternatively, use --run-on-host to run directly on your machine (bypasses sandbox).');
82
82
  }
@@ -220,7 +220,7 @@ export default class WorkRevise extends PMOCommand {
220
220
  const hasDevcontainer = hasDevcontainerConfig(agentDir);
221
221
  let environment = 'host';
222
222
  let displayMode = 'terminal';
223
- let sandboxed = false;
223
+ let permissionMode = 'danger';
224
224
  const reviseJsonModeConfig = jsonMode ? { flags: flags, commandName: 'work revise' } : null;
225
225
  if (hasDevcontainer && !flags['run-on-host']) {
226
226
  environment = 'devcontainer';
@@ -245,19 +245,19 @@ export default class WorkRevise extends PMOCommand {
245
245
  const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
246
246
  const executorName = getExecutorDisplayName(executor);
247
247
  // Permission mode
248
- const { permissionMode } = await this.prompt([
248
+ const { permissionMode: selectedPermMode } = await this.prompt([
249
249
  {
250
250
  type: 'list',
251
251
  name: 'permissionMode',
252
252
  message: `Permission mode for ${executorName}:`,
253
253
  choices: [
254
- { name: 'danger - Skip permission checks (faster for revisions)', value: 'danger', command: `prlt work revise ${ticketId} --json` },
255
- { name: 'safe - Requires approval for dangerous operations', value: 'safe', command: `prlt work revise ${ticketId} --json` },
254
+ { name: '⚠️ danger - Skip permission checks (faster for revisions)', value: 'danger', command: `prlt work revise ${ticketId} --json` },
255
+ { name: '🔒 safe - Requires approval for dangerous operations', value: 'safe', command: `prlt work revise ${ticketId} --json` },
256
256
  ],
257
257
  default: 'danger',
258
258
  },
259
259
  ], reviseJsonModeConfig);
260
- sandboxed = permissionMode === 'safe';
260
+ permissionMode = selectedPermMode;
261
261
  // Show execution info
262
262
  this.log('');
263
263
  this.log(styles.header(`Revising: ${ticket.id}: ${ticket.title}`));
@@ -292,7 +292,7 @@ export default class WorkRevise extends PMOCommand {
292
292
  executor,
293
293
  environment,
294
294
  displayMode,
295
- sandboxed,
295
+ permissionMode,
296
296
  branch,
297
297
  });
298
298
  this.log(styles.muted(` Work ID: ${execution.id}`));
@@ -327,7 +327,7 @@ export default class WorkRevise extends PMOCommand {
327
327
  executionConfig.shell = shell;
328
328
  }
329
329
  executionConfig.outputMode = 'interactive';
330
- executionConfig.sandboxed = sandboxed;
330
+ executionConfig.permissionMode = permissionMode;
331
331
  // Run execution
332
332
  this.log(styles.muted('Starting agent to address feedback...'));
333
333
  const sessionManager = (flags.session || 'tmux');
@@ -1198,7 +1198,7 @@ export default class WorkSpawn extends PMOCommand {
1198
1198
  }
1199
1199
  // Prompt for environment (devcontainer vs host) if devcontainer available and not already set
1200
1200
  if (hasDevcontainer && !batchRunOnHost && !batchDisplay) {
1201
- const devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
1201
+ const devcontainerLabel = '🐳 devcontainer (isolated, recommended)';
1202
1202
  const envChoices = [
1203
1203
  { name: devcontainerLabel, value: 'devcontainer' },
1204
1204
  { name: '💻 host (runs directly on your machine)', value: 'host' },
@@ -805,10 +805,10 @@ export default class WorkStart extends PMOCommand {
805
805
  // Determine execution environment and display mode
806
806
  let environment = 'host';
807
807
  let displayMode = 'terminal';
808
- let sandboxed = false; // Whether --dangerously-skip-permissions is NOT used
808
+ let permissionMode = 'danger';
809
809
  if (hasDevcontainer && !flags.display && !flags['run-on-host']) {
810
810
  // Agent has devcontainer - prompt for environment choice
811
- const devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
811
+ const devcontainerLabel = '🐳 devcontainer (isolated, recommended)';
812
812
  const envChoices = [
813
813
  { name: devcontainerLabel, value: 'devcontainer' },
814
814
  { name: '💻 host (runs directly on your machine)', value: 'host' },
@@ -1211,11 +1211,11 @@ export default class WorkStart extends PMOCommand {
1211
1211
  const actionModifiesCode = context.modifiesCode !== false;
1212
1212
  const defaultPermissionMode = actionModifiesCode ? 'danger' : 'safe';
1213
1213
  if (flags['permission-mode']) {
1214
- sandboxed = flags['permission-mode'] === 'safe';
1214
+ permissionMode = (flags['permission-mode'] || 'danger');
1215
1215
  }
1216
1216
  else if (!actionModifiesCode) {
1217
1217
  // Non-code-modifying actions automatically use safe mode
1218
- sandboxed = true;
1218
+ permissionMode = 'safe';
1219
1219
  }
1220
1220
  else {
1221
1221
  const containerNote = environment === 'devcontainer'
@@ -1241,7 +1241,7 @@ export default class WorkStart extends PMOCommand {
1241
1241
  when: (ctx) => !ctx.flags['permission-mode'],
1242
1242
  });
1243
1243
  const resolvedPermission = await permissionResolver.resolve();
1244
- sandboxed = resolvedPermission['permission-mode'] === 'safe';
1244
+ permissionMode = (resolvedPermission['permission-mode'] || 'danger');
1245
1245
  }
1246
1246
  // Prompt for PR creation when work is complete
1247
1247
  // Resolution order: explicit flags > workspace config default > interactive prompt
@@ -1312,7 +1312,7 @@ export default class WorkStart extends PMOCommand {
1312
1312
  this.log(styles.muted(` Environment: ${envIcon} ${environment}`));
1313
1313
  this.log(styles.muted(` Display: ${displayMode}`));
1314
1314
  // Permissions info
1315
- if (sandboxed) {
1315
+ if (permissionMode === 'safe') {
1316
1316
  this.log(styles.success(` Permissions: 🔒 safe`));
1317
1317
  }
1318
1318
  else {
@@ -1529,7 +1529,7 @@ export default class WorkStart extends PMOCommand {
1529
1529
  executor,
1530
1530
  environment,
1531
1531
  displayMode,
1532
- sandboxed,
1532
+ permissionMode,
1533
1533
  branch,
1534
1534
  });
1535
1535
  if (!jsonMode) {
@@ -1569,8 +1569,8 @@ export default class WorkStart extends PMOCommand {
1569
1569
  }
1570
1570
  // Set output mode from user selection
1571
1571
  executionConfig.outputMode = outputMode;
1572
- // Set sandboxed mode (determines whether --dangerously-skip-permissions is used)
1573
- executionConfig.sandboxed = sandboxed;
1572
+ // Set permission mode (determines whether --dangerously-skip-permissions is used)
1573
+ executionConfig.permissionMode = permissionMode;
1574
1574
  // Handle --focus flag: when set, bring terminal to foreground instead of opening in background
1575
1575
  if (flags.focus) {
1576
1576
  executionConfig.terminal.openInBackground = false;
@@ -1992,7 +1992,7 @@ export default class WorkStart extends PMOCommand {
1992
1992
  const environment = useDevcontainer ? 'devcontainer' : 'host';
1993
1993
  const displayMode = 'terminal';
1994
1994
  const actionModifiesCode = context.modifiesCode !== false;
1995
- const sandboxed = flags['permission-mode'] === 'safe' || (!flags['permission-mode'] && !actionModifiesCode);
1995
+ const permissionMode = flags['permission-mode'] || (!actionModifiesCode ? 'safe' : 'danger');
1996
1996
  const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
1997
1997
  const outputMode = 'interactive';
1998
1998
  // Handle git branch - only if action modifies code
@@ -2033,14 +2033,14 @@ export default class WorkStart extends PMOCommand {
2033
2033
  executor,
2034
2034
  environment,
2035
2035
  displayMode,
2036
- sandboxed,
2036
+ permissionMode,
2037
2037
  branch,
2038
2038
  });
2039
2039
  // Note: Ticket status update moved to after successful spawn
2040
2040
  // Load execution config
2041
2041
  const executionConfig = loadExecutionConfig(db);
2042
2042
  executionConfig.outputMode = outputMode;
2043
- executionConfig.sandboxed = sandboxed;
2043
+ executionConfig.permissionMode = permissionMode;
2044
2044
  // Run execution
2045
2045
  this.log(styles.muted(` Starting ${ticket.id} → ${agentName}...`));
2046
2046
  const batchSessionManager = (flags.session || 'tmux');
@@ -161,7 +161,7 @@ export default class WorkWatch extends PMOCommand {
161
161
  if (!flags.mode) {
162
162
  if (hasDevcontainer) {
163
163
  const envChoices = [
164
- { name: '🐳 devcontainer (sandboxed, recommended)', value: 'devcontainer' },
164
+ { name: '🐳 devcontainer (isolated, recommended)', value: 'devcontainer' },
165
165
  { name: '💻 host (runs directly on your machine)', value: 'host' },
166
166
  ];
167
167
  if (jsonMode) {
@@ -244,13 +244,13 @@ export default class WorkWatch extends PMOCommand {
244
244
  const promptResult = await promptExecutionSettings(db, {
245
245
  displayMode: this.displayMode,
246
246
  environment: this.environment,
247
- skipPermissions: flags['skip-permissions'] ? true : undefined,
247
+ permissionMode: flags['skip-permissions'] ? 'danger' : undefined,
248
248
  createPR: flags['create-pr'] ? true : undefined,
249
249
  log: (msg) => this.log(styles.header(msg)),
250
250
  jsonMode: jsonMode ? { flags, commandName: 'work watch' } : undefined,
251
251
  });
252
252
  this.executionConfig = promptResult.executionConfig;
253
- this.skipPermissions = promptResult.skipPermissions;
253
+ this.skipPermissions = promptResult.permissionMode === 'danger';
254
254
  this.createPR = promptResult.createPR;
255
255
  this.log('');
256
256
  this.log(styles.header(`👁️ Watching column "${this.columnName}" for new tickets`));
@@ -223,7 +223,7 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
223
223
  }
224
224
  }
225
225
  }
226
- // Create devcontainer config for sandboxed execution
226
+ // Create devcontainer config for isolated execution
227
227
  // Note: Agent metadata is stored in SQLite (agents table), not in config files
228
228
  // Always create devcontainer config (even if no repos were created) so agent rebuild works
229
229
  if (!options?.skipDevcontainer) {
@@ -368,7 +368,7 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
368
368
  }
369
369
  }
370
370
  }
371
- // Create devcontainer config for sandboxed execution
371
+ // Create devcontainer config for isolated execution
372
372
  // Note: Agent metadata is stored in SQLite (agents table), not in config files
373
373
  if (!options?.skipDevcontainer) {
374
374
  console.log(styles.muted(` Creating devcontainer config...`));
@@ -3548,19 +3548,19 @@ export declare const pmoAgentWork: import("drizzle-orm/sqlite-core").SQLiteTable
3548
3548
  identity: undefined;
3549
3549
  generated: undefined;
3550
3550
  }, object>;
3551
- sandboxed: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3552
- name: "sandboxed";
3551
+ permissionMode: import("drizzle-orm/sqlite-core").SQLiteColumn<{
3552
+ name: "permission_mode";
3553
3553
  tableName: "agent_work";
3554
- dataType: "boolean";
3555
- columnType: "SQLiteBoolean";
3556
- data: boolean;
3557
- driverParam: number;
3554
+ dataType: "string";
3555
+ columnType: "SQLiteText";
3556
+ data: string;
3557
+ driverParam: string;
3558
3558
  notNull: true;
3559
3559
  hasDefault: true;
3560
3560
  isPrimaryKey: false;
3561
3561
  isAutoincrement: false;
3562
3562
  hasRuntimeDefault: false;
3563
- enumValues: undefined;
3563
+ enumValues: [string, ...string[]];
3564
3564
  baseColumn: never;
3565
3565
  identity: undefined;
3566
3566
  generated: undefined;
@@ -428,7 +428,7 @@ export const pmoAgentWork = sqliteTable('agent_work', {
428
428
  executor: text('executor').notNull(),
429
429
  environment: text('environment').notNull().default('host'),
430
430
  displayMode: text('display_mode').notNull().default('terminal'),
431
- sandboxed: integer('sandboxed', { mode: 'boolean' }).notNull().default(false),
431
+ permissionMode: text('permission_mode').notNull().default('safe'),
432
432
  status: text('status').notNull().default('starting'),
433
433
  branch: text('branch'),
434
434
  pid: text('pid'),
@@ -5,7 +5,7 @@
5
5
  * Uses the workspace_settings table (not pmo_settings - execution is workspace-level).
6
6
  */
7
7
  import Database from 'better-sqlite3';
8
- import { ExecutionConfig, TerminalApp, Shell, DisplayMode, OutputMode, ExecutionEnvironment, AuthMethod } from './types.js';
8
+ import { ExecutionConfig, TerminalApp, Shell, DisplayMode, OutputMode, ExecutionEnvironment, AuthMethod, PermissionMode } from './types.js';
9
9
  import { type JsonFlags } from '../prompt-json.js';
10
10
  declare const CONFIG_KEYS: {
11
11
  terminalApp: string;
@@ -15,7 +15,7 @@ declare const CONFIG_KEYS: {
15
15
  defaultExecutor: string;
16
16
  autoExecute: string;
17
17
  outputMode: string;
18
- sandboxed: string;
18
+ permissionMode: string;
19
19
  tmuxSession: string;
20
20
  tmuxLayout: string;
21
21
  tmuxControlMode: string;
@@ -130,8 +130,8 @@ export interface ExecutionPromptOptions {
130
130
  environment: ExecutionEnvironment;
131
131
  /** Skip output mode prompt and use this value instead */
132
132
  outputMode?: OutputMode;
133
- /** Skip permission prompt and use this value instead */
134
- skipPermissions?: boolean;
133
+ /** Permission mode override (skips prompt) */
134
+ permissionMode?: PermissionMode;
135
135
  /** Skip PR prompt and use this value instead */
136
136
  createPR?: boolean;
137
137
  /** Force re-prompt for terminal preferences */
@@ -147,8 +147,8 @@ export interface ExecutionPromptOptions {
147
147
  export interface ExecutionPromptResult {
148
148
  /** Execution config with terminal/shell/output settings */
149
149
  executionConfig: ExecutionConfig;
150
- /** Whether to skip permission checks */
151
- skipPermissions: boolean;
150
+ /** Permission mode for agent execution */
151
+ permissionMode: PermissionMode;
152
152
  /** Whether to create PR when work is ready */
153
153
  createPR: boolean;
154
154
  }
@@ -19,7 +19,7 @@ const CONFIG_KEYS = {
19
19
  defaultExecutor: 'execution.default_executor',
20
20
  autoExecute: 'execution.auto_execute',
21
21
  outputMode: 'execution.output_mode',
22
- sandboxed: 'execution.sandboxed',
22
+ permissionMode: 'execution.permission_mode',
23
23
  tmuxSession: 'execution.tmux.session',
24
24
  tmuxLayout: 'execution.tmux.layout',
25
25
  tmuxControlMode: 'execution.tmux.control_mode',
@@ -95,10 +95,17 @@ export function loadExecutionConfig(db) {
95
95
  if (outputMode) {
96
96
  config.outputMode = outputMode;
97
97
  }
98
- // Load sandboxed preference
99
- const sandboxed = getSetting(db, CONFIG_KEYS.sandboxed);
100
- if (sandboxed !== null) {
101
- config.sandboxed = sandboxed === 'true';
98
+ // Load permission mode preference
99
+ const permissionMode = getSetting(db, CONFIG_KEYS.permissionMode);
100
+ if (permissionMode) {
101
+ config.permissionMode = permissionMode;
102
+ }
103
+ else {
104
+ // Backward compat: read legacy 'execution.sandboxed' setting
105
+ const legacySandboxed = getSetting(db, 'execution.sandboxed');
106
+ if (legacySandboxed !== null) {
107
+ config.permissionMode = legacySandboxed === 'true' ? 'safe' : 'danger';
108
+ }
102
109
  }
103
110
  // Load auth method preference
104
111
  const authMethod = getSetting(db, CONFIG_KEYS.authMethod);
@@ -491,8 +498,8 @@ export async function promptExecutionSettings(db, options) {
491
498
  }
492
499
  executionConfig.outputMode = outputMode;
493
500
  // Prompt for permissions mode (unless flag override is provided)
494
- let skipPermissions = options.skipPermissions ?? false;
495
- if (options.skipPermissions === undefined) {
501
+ let resolvedPermissionMode = options.permissionMode ?? 'safe';
502
+ if (options.permissionMode === undefined) {
496
503
  const containerNote = (environment === 'devcontainer' || environment === 'docker')
497
504
  ? ' (container provides additional isolation)'
498
505
  : '';
@@ -504,7 +511,7 @@ export async function promptExecutionSettings(db, options) {
504
511
  if (isJsonMode && jsonMode) {
505
512
  outputPromptAsJson(buildPromptConfig('list', 'permissionMode', `Permission mode for Claude Code${containerNote}:`, permissionChoices, 'safe'), createMetadata(jsonMode.commandName, jsonMode.flags));
506
513
  }
507
- const { permissionMode } = await inquirer.prompt([
514
+ const { permissionMode: selectedMode } = await inquirer.prompt([
508
515
  {
509
516
  type: 'list',
510
517
  name: 'permissionMode',
@@ -513,7 +520,7 @@ export async function promptExecutionSettings(db, options) {
513
520
  default: 'safe',
514
521
  },
515
522
  ]);
516
- skipPermissions = permissionMode === 'danger';
523
+ resolvedPermissionMode = selectedMode;
517
524
  }
518
525
  // Prompt for PR creation when work is complete (unless flag override is provided)
519
526
  let createPR = options.createPR ?? false;
@@ -542,7 +549,7 @@ export async function promptExecutionSettings(db, options) {
542
549
  }
543
550
  return {
544
551
  executionConfig,
545
- skipPermissions,
552
+ permissionMode: resolvedPermissionMode,
546
553
  createPR,
547
554
  };
548
555
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Devcontainer Template Generator
3
3
  *
4
- * Generates .devcontainer/ configuration for agent sandboxed execution.
5
- * Uses a custom Dockerfile with network firewall for security sandboxing.
4
+ * Generates .devcontainer/ configuration for agent isolated execution.
5
+ * Uses a custom Dockerfile with network firewall for security isolation.
6
6
  */
7
7
  import { ExecutionConfig, ExecutorType } from './types.js';
8
8
  export type MountMode = 'worktree' | 'clone';
@@ -48,7 +48,7 @@ export interface DevcontainerJson {
48
48
  /**
49
49
  * Generate default devcontainer.json content
50
50
  *
51
- * Uses a custom Dockerfile with firewall for network sandboxing.
51
+ * Uses a custom Dockerfile with firewall for network isolation.
52
52
  * Mounts the entire agent workspace directory so all contents (repos, prompt files, etc.)
53
53
  * are accessible inside the container at /workspace.
54
54
  */
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Devcontainer Template Generator
3
3
  *
4
- * Generates .devcontainer/ configuration for agent sandboxed execution.
5
- * Uses a custom Dockerfile with network firewall for security sandboxing.
4
+ * Generates .devcontainer/ configuration for agent isolated execution.
5
+ * Uses a custom Dockerfile with network firewall for security isolation.
6
6
  */
7
7
  import * as fs from 'node:fs';
8
8
  import * as path from 'node:path';
@@ -11,7 +11,7 @@ import { parseChannel } from '../workspace-config.js';
11
11
  /**
12
12
  * Generate default devcontainer.json content
13
13
  *
14
- * Uses a custom Dockerfile with firewall for network sandboxing.
14
+ * Uses a custom Dockerfile with firewall for network isolation.
15
15
  * Mounts the entire agent workspace directory so all contents (repos, prompt files, etc.)
16
16
  * are accessible inside the container at /workspace.
17
17
  */
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Implementations for each execution environment (devcontainer, host, docker, vm).
5
5
  */
6
- import { ExecutionEnvironment, DisplayMode, OutputMode, SessionManager, ExecutorType, ExecutionContext, ExecutionConfig } from './types.js';
6
+ import { ExecutionEnvironment, DisplayMode, OutputMode, PermissionMode, SessionManager, ExecutorType, ExecutionContext, ExecutionConfig } from './types.js';
7
7
  /**
8
8
  * Build a unified name for tmux sessions, window names, and tab titles.
9
9
  * Format: "{ticketId}-{action}-{agentName}"
@@ -162,7 +162,7 @@ export declare function getContainerId(containerName: string): string | null;
162
162
  * Uses docker exec for direct container access.
163
163
  * Uses a prompt file to avoid shell escaping issues.
164
164
  */
165
- export declare function buildDevcontainerCommand(context: ExecutionContext, executor: ExecutorType, promptFile: string, containerId?: string, outputMode?: OutputMode, sandboxed?: boolean, displayMode?: DisplayMode): string;
165
+ export declare function buildDevcontainerCommand(context: ExecutionContext, executor: ExecutorType, promptFile: string, containerId?: string, outputMode?: OutputMode, permissionMode?: PermissionMode, displayMode?: DisplayMode): string;
166
166
  /**
167
167
  * Run command inside a Docker container.
168
168
  * Uses raw Docker commands for filesystem isolation - no devcontainer CLI required.
@@ -386,11 +386,11 @@ export async function runHost(context, executor, config, displayMode = 'terminal
386
386
  const sessionName = buildTmuxWindowName(context);
387
387
  const windowTitle = buildWindowTitle(context);
388
388
  const prompt = buildPrompt(context);
389
- // Terminal - use sandboxed setting
390
- const skipPermissions = !config.sandboxed;
389
+ // Terminal - use permission mode setting
390
+ const skipPermissions = config.permissionMode === 'danger';
391
391
  // Validate Codex mode combination before proceeding
392
392
  if (executor === 'codex') {
393
- const codexPermission = config.sandboxed ? 'safe' : 'danger';
393
+ const codexPermission = config.permissionMode;
394
394
  const codexContext = resolveCodexExecutionContext(displayMode, config.outputMode);
395
395
  const modeError = validateCodexMode(codexPermission, codexContext);
396
396
  if (modeError) {
@@ -934,7 +934,7 @@ function createDockerContainer(context, containerName, imageName, config, execut
934
934
  `--memory=${config.devcontainer.memory}`,
935
935
  `--cpus=${config.devcontainer.cpus}`,
936
936
  ];
937
- // Security flags - these provide the sandboxing
937
+ // Security flags - these provide the isolation
938
938
  const securityFlags = [
939
939
  '--cap-add=NET_ADMIN', // For firewall setup
940
940
  '--cap-add=NET_RAW', // For firewall setup
@@ -966,10 +966,10 @@ function createDockerContainer(context, containerName, imageName, config, execut
966
966
  * Run the post-start setup commands in a container.
967
967
  * This includes firewall initialization, prlt setup, and Claude settings.
968
968
  * @param containerId - Docker container ID
969
- * @param sandboxed - Whether running in safe mode (true) or danger mode (false)
969
+ * @param permissionMode - Permission mode: 'safe' requires approval, 'danger' skips checks
970
970
  * @param executor - Which executor is being used (determines Claude-specific setup)
971
971
  */
972
- function runContainerSetup(containerId, sandboxed = true, executor = 'claude-code') {
972
+ function runContainerSetup(containerId, permissionMode = 'safe', executor = 'claude-code') {
973
973
  try {
974
974
  // Run firewall init (requires sudo since we're running as node user)
975
975
  execSync(`docker exec ${containerId} sudo /usr/local/bin/init-firewall.sh`, { stdio: 'pipe' });
@@ -1010,9 +1010,9 @@ function runContainerSetup(containerId, sandboxed = true, executor = 'claude-cod
1010
1010
  console.debug('[runners:docker] Failed to parse host .claude.json, using empty settings');
1011
1011
  }
1012
1012
  }
1013
- // Only set bypassPermissionsModeAccepted when user chose danger mode (!sandboxed)
1013
+ // Only set bypassPermissionsModeAccepted when user chose danger mode
1014
1014
  // This doesn't modify the host file - only the container copy
1015
- if (!sandboxed) {
1015
+ if (permissionMode === 'danger') {
1016
1016
  settings.bypassPermissionsModeAccepted = true;
1017
1017
  }
1018
1018
  // Skip first-run onboarding (theme picker, tips, etc.) for automated agents
@@ -1047,7 +1047,7 @@ function runContainerSetup(containerId, sandboxed = true, executor = 'claude-cod
1047
1047
  const settingsJson = JSON.stringify(settings);
1048
1048
  // Write to container at /home/node/.claude.json using stdin piping
1049
1049
  execSync(`docker exec -i ${containerId} bash -c 'cat > /home/node/.claude.json'`, { input: settingsJson, stdio: ['pipe', 'pipe', 'pipe'] });
1050
- console.debug(`[runners:docker] Copied .claude.json settings to container (bypassPermissionsModeAccepted=${!sandboxed})`);
1050
+ console.debug(`[runners:docker] Copied .claude.json settings to container (bypassPermissionsModeAccepted=${permissionMode === 'danger'})`);
1051
1051
  // Write ~/.claude/settings.json to skip the dangerous mode permission prompt (TKT-1134)
1052
1052
  // This prevents Claude Code from prompting about permission mode on first run
1053
1053
  const claudeSettings = JSON.stringify({ skipDangerousModePermissionPrompt: true });
@@ -1153,10 +1153,10 @@ function ensureDockerContainer(context, config, executor = 'claude-code') {
1153
1153
  return null;
1154
1154
  }
1155
1155
  // Run post-start setup (firewall, prlt, Claude settings)
1156
- // Pass sandboxed config to determine whether to set bypassPermissionsModeAccepted
1156
+ // Pass permission mode to determine whether to set bypassPermissionsModeAccepted
1157
1157
  // Pass executor to skip Claude-specific setup for non-Claude executors
1158
- console.debug(`[runners:docker] Running container setup (sandboxed=${config.sandboxed}, executor=${executor})`);
1159
- if (!runContainerSetup(containerId, config.sandboxed, executor)) {
1158
+ console.debug(`[runners:docker] Running container setup (permissionMode=${config.permissionMode}, executor=${executor})`);
1159
+ if (!runContainerSetup(containerId, config.permissionMode, executor)) {
1160
1160
  console.debug(`[runners:docker] Setup failed, but continuing...`);
1161
1161
  // Don't fail completely - setup might partially work
1162
1162
  }
@@ -1240,23 +1240,22 @@ function writePromptFile(context) {
1240
1240
  * Uses docker exec for direct container access.
1241
1241
  * Uses a prompt file to avoid shell escaping issues.
1242
1242
  */
1243
- export function buildDevcontainerCommand(context, executor, promptFile, containerId, outputMode = 'interactive', sandboxed = true, displayMode = 'terminal') {
1243
+ export function buildDevcontainerCommand(context, executor, promptFile, containerId, outputMode = 'interactive', permissionMode = 'safe', displayMode = 'terminal') {
1244
1244
  // Calculate the relative path from agentDir to worktreePath for cd
1245
1245
  const relativePath = path.relative(context.agentDir, context.worktreePath);
1246
1246
  const cdCmd = relativePath ? `cd /workspace/${relativePath} && ` : '';
1247
1247
  // Build executor command using the centralized getExecutorCommand()
1248
1248
  // This ensures all runners use consistent executor invocation
1249
1249
  let executorCmd;
1250
+ const skipPermissions = permissionMode === 'danger';
1250
1251
  if (isClaudeExecutor(executor)) {
1251
- // Claude-specific flags based on output mode and sandboxed setting
1252
+ // Claude-specific flags based on output mode and permission mode
1252
1253
  // - interactive: No -p flag, shows streaming UI (watch Claude work in real-time)
1253
1254
  // - print: Uses -p flag, outputs final result only (better for logs/automation)
1254
1255
  const printFlag = outputMode === 'print' ? '-p ' : '';
1255
- // sandboxed=true means safe mode (no --dangerously-skip-permissions)
1256
- // sandboxed=false means danger mode (use --dangerously-skip-permissions)
1257
1256
  // --permission-mode bypassPermissions: skips the "trust this folder" dialog
1258
1257
  const bypassTrustFlag = '--permission-mode bypassPermissions ';
1259
- const permissionsFlag = !sandboxed ? '--dangerously-skip-permissions ' : '';
1258
+ const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
1260
1259
  // --effort high: skips the effort level prompt for automated agents (TKT-1134)
1261
1260
  const effortFlag = '--effort high ';
1262
1261
  executorCmd = `claude ${bypassTrustFlag}${permissionsFlag}${effortFlag}${printFlag}"$(cat ${promptFile})"`;
@@ -1264,7 +1263,7 @@ export function buildDevcontainerCommand(context, executor, promptFile, containe
1264
1263
  else if (executor === 'codex') {
1265
1264
  // Use Codex adapter for mode validation and deterministic command building.
1266
1265
  // Validates that the permission/display combination is supported before building.
1267
- const codexPermission = sandboxed ? 'safe' : 'danger';
1266
+ const codexPermission = permissionMode;
1268
1267
  const codexContext = resolveCodexExecutionContext(displayMode, outputMode);
1269
1268
  const modeError = validateCodexMode(codexPermission, codexContext);
1270
1269
  if (modeError) {
@@ -1276,7 +1275,7 @@ export function buildDevcontainerCommand(context, executor, promptFile, containe
1276
1275
  }
1277
1276
  else {
1278
1277
  // Non-Claude, non-Codex executors: use getExecutorCommand() to get correct command and args
1279
- const { cmd, args } = getExecutorCommand(executor, `PLACEHOLDER`, !sandboxed);
1278
+ const { cmd, args } = getExecutorCommand(executor, `PLACEHOLDER`, skipPermissions);
1280
1279
  // Replace the placeholder prompt with a file read for shell safety
1281
1280
  const argsStr = args.map(a => a === 'PLACEHOLDER' ? `"$(cat ${promptFile})"` : a).join(' ');
1282
1281
  executorCmd = `${cmd} ${argsStr}`;
@@ -1365,7 +1364,7 @@ export async function runDevcontainer(context, executor, config, displayMode = '
1365
1364
  }
1366
1365
  // Build the docker exec command (just runs claude directly)
1367
1366
  // tmux session setup is handled by runDevcontainerInTmux, not buildDevcontainerCommand
1368
- const devcontainerCmd = buildDevcontainerCommand(context, executor, promptFile, containerId, config.outputMode, config.sandboxed, displayMode);
1367
+ const devcontainerCmd = buildDevcontainerCommand(context, executor, promptFile, containerId, config.outputMode, config.permissionMode, displayMode);
1369
1368
  // Execute based on display mode
1370
1369
  // When sessionManager is 'tmux', always use tmux inside container for session persistence
1371
1370
  // (allows reattach via `prlt session attach` even for background mode)
@@ -2017,7 +2016,7 @@ export async function runDocker(context, executor, config) {
2017
2016
  }
2018
2017
  // Validate Codex mode: Docker runner is always non-tty (detached with -d)
2019
2018
  if (executor === 'codex') {
2020
- const codexPermission = config.sandboxed ? 'safe' : 'danger';
2019
+ const codexPermission = config.permissionMode;
2021
2020
  const modeError = validateCodexMode(codexPermission, 'non-tty');
2022
2021
  if (modeError) {
2023
2022
  return { success: false, error: modeError.message };
@@ -2025,7 +2024,7 @@ export async function runDocker(context, executor, config) {
2025
2024
  }
2026
2025
  // Build executor command using getExecutorCommand() for correct invocation
2027
2026
  const escapedPrompt = prompt.replace(/'/g, "'\\''");
2028
- const { cmd, args } = getExecutorCommand(executor, escapedPrompt, !config.sandboxed);
2027
+ const { cmd, args } = getExecutorCommand(executor, escapedPrompt, config.permissionMode === 'danger');
2029
2028
  // For Claude Code in Docker, use --print for non-interactive output
2030
2029
  // Non-Claude executors use their native command format from getExecutorCommand()
2031
2030
  dockerCmd += ` ${config.docker.image}`;
@@ -2087,7 +2086,7 @@ export async function runVm(context, executor, config, host) {
2087
2086
  }
2088
2087
  // Validate Codex mode: VM runner is always non-tty (SSH + nohup)
2089
2088
  if (executor === 'codex') {
2090
- const codexPermission = config.sandboxed ? 'safe' : 'danger';
2089
+ const codexPermission = config.permissionMode;
2091
2090
  const modeError = validateCodexMode(codexPermission, 'non-tty');
2092
2091
  if (modeError) {
2093
2092
  return { success: false, error: modeError.message };
@@ -2095,7 +2094,7 @@ export async function runVm(context, executor, config, host) {
2095
2094
  }
2096
2095
  // Execute on remote using executor-appropriate command
2097
2096
  const escapedPrompt = prompt.replace(/'/g, "'\\''");
2098
- const { cmd: executorCmd, args: executorArgs } = getExecutorCommand(executor, escapedPrompt, !config.sandboxed);
2097
+ const { cmd: executorCmd, args: executorArgs } = getExecutorCommand(executor, escapedPrompt, config.permissionMode === 'danger');
2099
2098
  // Build the remote command based on executor type
2100
2099
  let remoteCmd;
2101
2100
  if (isClaudeExecutor(executor)) {
@@ -282,7 +282,7 @@ export async function spawnAgentForTicket(ticket, agentName, storage, executionS
282
282
  environment = 'host';
283
283
  }
284
284
  const displayMode = options.displayMode || 'terminal';
285
- const sandboxed = !(options.skipPermissions ?? false);
285
+ const permissionMode = (options.skipPermissions ?? false) ? 'danger' : 'safe';
286
286
  // Executor preflight check (TKT-1082): verify binary is available before proceeding
287
287
  // For host environment, check immediately. For devcontainer, check happens after container start.
288
288
  if (environment === 'host') {
@@ -457,12 +457,12 @@ export async function spawnAgentForTicket(ticket, agentName, storage, executionS
457
457
  executor,
458
458
  environment,
459
459
  displayMode,
460
- sandboxed,
460
+ permissionMode,
461
461
  branch,
462
462
  });
463
463
  // Load execution config (use passed config or load from db)
464
464
  const executionConfig = options.executionConfig || loadExecutionConfig(db);
465
- executionConfig.sandboxed = sandboxed;
465
+ executionConfig.permissionMode = permissionMode;
466
466
  // Run execution
467
467
  // Default to tmux for session persistence (enables peek/poke/attach)
468
468
  const sessionManager = options.sessionManager || 'tmux';
@@ -4,7 +4,7 @@
4
4
  * Database operations for agent_work table.
5
5
  */
6
6
  import Database from 'better-sqlite3';
7
- import { AgentWork, ExecutionStatus, ExecutorType, ExecutionEnvironment, DisplayMode } from './types.js';
7
+ import { AgentWork, ExecutionStatus, ExecutorType, ExecutionEnvironment, DisplayMode, PermissionMode } from './types.js';
8
8
  export declare class ExecutionStorage {
9
9
  private db;
10
10
  constructor(db: Database.Database);
@@ -18,7 +18,7 @@ export declare class ExecutionStorage {
18
18
  executor: ExecutorType;
19
19
  environment: ExecutionEnvironment;
20
20
  displayMode: DisplayMode;
21
- sandboxed: boolean;
21
+ permissionMode: PermissionMode;
22
22
  branch?: string;
23
23
  pid?: string;
24
24
  containerId?: string;