@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.
- package/dist/commands/claude/index.js +21 -21
- package/dist/commands/claude/open.js +1 -1
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/execution/config.d.ts +2 -2
- package/dist/commands/execution/config.js +18 -18
- package/dist/commands/execution/list.js +2 -2
- package/dist/commands/execution/view.js +2 -2
- package/dist/commands/orchestrator/start.d.ts +1 -1
- package/dist/commands/orchestrator/start.js +19 -19
- package/dist/commands/qa/index.js +12 -12
- package/dist/commands/staff/add.js +1 -1
- package/dist/commands/work/linear.js +28 -5
- package/dist/commands/work/revise.js +8 -8
- package/dist/commands/work/spawn.js +1 -1
- package/dist/commands/work/start.js +12 -12
- package/dist/commands/work/watch.js +3 -3
- package/dist/lib/agents/index.js +2 -2
- package/dist/lib/database/drizzle-schema.d.ts +7 -7
- package/dist/lib/database/drizzle-schema.js +1 -1
- package/dist/lib/execution/config.d.ts +6 -6
- package/dist/lib/execution/config.js +17 -10
- package/dist/lib/execution/devcontainer.d.ts +3 -3
- package/dist/lib/execution/devcontainer.js +3 -3
- package/dist/lib/execution/runners.d.ts +2 -2
- package/dist/lib/execution/runners.js +23 -24
- package/dist/lib/execution/spawner.js +3 -3
- package/dist/lib/execution/storage.d.ts +2 -2
- package/dist/lib/execution/storage.js +3 -3
- package/dist/lib/execution/types.d.ts +2 -2
- package/dist/lib/execution/types.js +1 -1
- package/dist/lib/external-issues/linear.d.ts +6 -0
- package/dist/lib/external-issues/linear.js +63 -0
- package/dist/lib/pmo/schema.d.ts +1 -1
- package/dist/lib/pmo/schema.js +1 -1
- package/dist/lib/pmo/storage/base.js +31 -0
- package/dist/lib/repos/index.js +1 -1
- package/oclif.manifest.json +4318 -4313
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
1214
|
+
permissionMode = (flags['permission-mode'] || 'danger');
|
|
1215
1215
|
}
|
|
1216
1216
|
else if (!actionModifiesCode) {
|
|
1217
1217
|
// Non-code-modifying actions automatically use safe mode
|
|
1218
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
1573
|
-
executionConfig.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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`));
|
package/dist/lib/agents/index.js
CHANGED
|
@@ -223,7 +223,7 @@ export async function createAgentWorktrees(workspacePath, agents, hqPath, option
|
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
|
-
// Create devcontainer config for
|
|
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
|
|
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
|
-
|
|
3552
|
-
name: "
|
|
3551
|
+
permissionMode: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
3552
|
+
name: "permission_mode";
|
|
3553
3553
|
tableName: "agent_work";
|
|
3554
|
-
dataType: "
|
|
3555
|
-
columnType: "
|
|
3556
|
-
data:
|
|
3557
|
-
driverParam:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
134
|
-
|
|
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
|
-
/**
|
|
151
|
-
|
|
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
|
-
|
|
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
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
101
|
-
config.
|
|
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
|
|
495
|
-
if (options.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5
|
-
* Uses a custom Dockerfile with network firewall for security
|
|
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
|
|
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
|
|
5
|
-
* Uses a custom Dockerfile with network firewall for security
|
|
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
|
|
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,
|
|
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
|
|
390
|
-
const skipPermissions =
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
1013
|
+
// Only set bypassPermissionsModeAccepted when user chose danger mode
|
|
1014
1014
|
// This doesn't modify the host file - only the container copy
|
|
1015
|
-
if (
|
|
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=${
|
|
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
|
|
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 (
|
|
1159
|
-
if (!runContainerSetup(containerId, config.
|
|
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',
|
|
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
|
|
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 =
|
|
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 =
|
|
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`,
|
|
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.
|
|
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.
|
|
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,
|
|
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.
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
21
|
+
permissionMode: PermissionMode;
|
|
22
22
|
branch?: string;
|
|
23
23
|
pid?: string;
|
|
24
24
|
containerId?: string;
|