@proletariat/cli 0.3.55 → 0.3.57
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/README.md +74 -5
- package/bin/validate-better-sqlite3.cjs +44 -5
- package/dist/commands/agent/cleanup.d.ts +1 -0
- package/dist/commands/agent/cleanup.js +11 -1
- package/dist/commands/agent/cleanup.js.map +1 -1
- package/dist/commands/agent/rebuild.js +1 -1
- package/dist/commands/agent/rebuild.js.map +1 -1
- package/dist/commands/asana/import.d.ts +14 -0
- package/dist/commands/asana/import.js +240 -0
- package/dist/commands/asana/import.js.map +1 -0
- package/dist/commands/claude-yolo.d.ts +20 -0
- package/dist/commands/claude-yolo.js +86 -0
- package/dist/commands/claude-yolo.js.map +1 -0
- package/dist/commands/codex/index.d.ts +23 -0
- package/dist/commands/codex/index.js +168 -0
- package/dist/commands/codex/index.js.map +1 -0
- package/dist/commands/codex-yolo.d.ts +20 -0
- package/dist/commands/codex-yolo.js +86 -0
- package/dist/commands/codex-yolo.js.map +1 -0
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/index.js.map +1 -1
- package/dist/commands/dashboard.d.ts +38 -0
- package/dist/commands/dashboard.js +352 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/docker/clean.js +1 -1
- package/dist/commands/docker/clean.js.map +1 -1
- package/dist/commands/docker/list.js +1 -1
- package/dist/commands/docker/list.js.map +1 -1
- package/dist/commands/docker/logs.js +1 -1
- package/dist/commands/docker/logs.js.map +1 -1
- package/dist/commands/docker/restart.js +1 -1
- package/dist/commands/docker/restart.js.map +1 -1
- package/dist/commands/docker/shell.js +1 -1
- package/dist/commands/docker/shell.js.map +1 -1
- package/dist/commands/docker/start.js +1 -1
- package/dist/commands/docker/start.js.map +1 -1
- package/dist/commands/docker/stop.js +1 -1
- package/dist/commands/docker/stop.js.map +1 -1
- package/dist/commands/docker/sync.js +1 -1
- package/dist/commands/docker/sync.js.map +1 -1
- package/dist/commands/execution/config.js +2 -2
- package/dist/commands/execution/config.js.map +1 -1
- package/dist/commands/execution/list.js +1 -1
- package/dist/commands/execution/list.js.map +1 -1
- package/dist/commands/execution/logs.js +10 -8
- package/dist/commands/execution/logs.js.map +1 -1
- package/dist/commands/execution/stop.js +1 -1
- package/dist/commands/execution/stop.js.map +1 -1
- package/dist/commands/execution/view.js +1 -1
- package/dist/commands/execution/view.js.map +1 -1
- package/dist/commands/init.d.ts +0 -16
- package/dist/commands/init.js +50 -160
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/logs.d.ts +20 -0
- package/dist/commands/logs.js +84 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/mcp-server.js +1 -1
- package/dist/commands/mcp-server.js.map +1 -1
- package/dist/commands/new.d.ts +27 -0
- package/dist/commands/new.js +184 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/orchestrator/attach.d.ts +1 -0
- package/dist/commands/orchestrator/attach.js +159 -26
- package/dist/commands/orchestrator/attach.js.map +1 -1
- package/dist/commands/orchestrator/start.d.ts +20 -0
- package/dist/commands/orchestrator/start.js +123 -14
- package/dist/commands/orchestrator/start.js.map +1 -1
- package/dist/commands/orchestrator/status.d.ts +4 -0
- package/dist/commands/orchestrator/status.js +70 -29
- package/dist/commands/orchestrator/status.js.map +1 -1
- package/dist/commands/orchestrator/stop.js +57 -23
- package/dist/commands/orchestrator/stop.js.map +1 -1
- package/dist/commands/peek.d.ts +20 -0
- package/dist/commands/peek.js +84 -0
- package/dist/commands/peek.js.map +1 -0
- package/dist/commands/poke.d.ts +20 -0
- package/dist/commands/poke.js +89 -0
- package/dist/commands/poke.js.map +1 -0
- package/dist/commands/pr/link.js +1 -1
- package/dist/commands/pr/link.js.map +1 -1
- package/dist/commands/pr/status.js +1 -1
- package/dist/commands/pr/status.js.map +1 -1
- package/dist/commands/ps.d.ts +18 -0
- package/dist/commands/ps.js +123 -0
- package/dist/commands/ps.js.map +1 -0
- package/dist/commands/qa/index.js +1 -1
- package/dist/commands/qa/index.js.map +1 -1
- package/dist/commands/repo/add.js +1 -1
- package/dist/commands/repo/add.js.map +1 -1
- package/dist/commands/repo/fix-remotes.d.ts +14 -0
- package/dist/commands/repo/fix-remotes.js +154 -0
- package/dist/commands/repo/fix-remotes.js.map +1 -0
- package/dist/commands/repo/list.js +1 -1
- package/dist/commands/repo/list.js.map +1 -1
- package/dist/commands/repo/remove.js +1 -1
- package/dist/commands/repo/remove.js.map +1 -1
- package/dist/commands/repo/view.js +1 -1
- package/dist/commands/repo/view.js.map +1 -1
- package/dist/commands/run/index.d.ts +25 -0
- package/dist/commands/run/index.js +212 -0
- package/dist/commands/run/index.js.map +1 -0
- package/dist/commands/session/index.js +4 -0
- package/dist/commands/session/index.js.map +1 -1
- package/dist/commands/session/prune.d.ts +18 -0
- package/dist/commands/session/prune.js +327 -0
- package/dist/commands/session/prune.js.map +1 -0
- package/dist/commands/shortcut/connect.d.ts +15 -0
- package/dist/commands/shortcut/connect.js +210 -0
- package/dist/commands/shortcut/connect.js.map +1 -0
- package/dist/commands/stop.d.ts +19 -0
- package/dist/commands/stop.js +83 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/work/asana.d.ts +23 -0
- package/dist/commands/work/asana.js +213 -0
- package/dist/commands/work/asana.js.map +1 -0
- package/dist/commands/work/complete.js +1 -1
- package/dist/commands/work/complete.js.map +1 -1
- package/dist/commands/work/ready.js +1 -1
- package/dist/commands/work/ready.js.map +1 -1
- package/dist/commands/work/review.js +1 -1
- package/dist/commands/work/review.js.map +1 -1
- package/dist/commands/work/revise.js +1 -1
- package/dist/commands/work/revise.js.map +1 -1
- package/dist/commands/work/shortcut.d.ts +23 -0
- package/dist/commands/work/shortcut.js +212 -0
- package/dist/commands/work/shortcut.js.map +1 -0
- package/dist/commands/work/spawn.d.ts +3 -0
- package/dist/commands/work/spawn.js +175 -2
- package/dist/commands/work/spawn.js.map +1 -1
- package/dist/commands/work/start.js +54 -45
- package/dist/commands/work/start.js.map +1 -1
- package/dist/commands/work/watch.js +1 -1
- package/dist/commands/work/watch.js.map +1 -1
- package/dist/commands/workspace/add.js +1 -1
- package/dist/commands/workspace/add.js.map +1 -1
- package/dist/commands/workspace/list.js +1 -1
- package/dist/commands/workspace/list.js.map +1 -1
- package/dist/hooks/init.d.ts +1 -1
- package/dist/hooks/init.js +6 -6
- package/dist/hooks/init.js.map +1 -1
- package/dist/lib/agent-naming.d.ts +15 -0
- package/dist/lib/agent-naming.js +32 -0
- package/dist/lib/agent-naming.js.map +1 -0
- package/dist/lib/agents/commands.d.ts +2 -0
- package/dist/lib/agents/commands.js +56 -51
- package/dist/lib/agents/commands.js.map +1 -1
- package/dist/lib/agents/index.js +11 -7
- package/dist/lib/agents/index.js.map +1 -1
- package/dist/lib/asana/client.d.ts +8 -0
- package/dist/lib/asana/client.js +26 -7
- package/dist/lib/asana/client.js.map +1 -1
- package/dist/lib/asana/types.d.ts +20 -0
- package/dist/lib/branch/index.d.ts +36 -0
- package/dist/lib/branch/index.js +129 -0
- package/dist/lib/branch/index.js.map +1 -1
- package/dist/lib/database/index.js +1 -1
- package/dist/lib/database/index.js.map +1 -1
- package/dist/lib/database/native-validation.d.ts +2 -0
- package/dist/lib/database/native-validation.js +30 -1
- package/dist/lib/database/native-validation.js.map +1 -1
- package/dist/lib/events/emitting-runner.d.ts +30 -0
- package/dist/lib/events/emitting-runner.js +95 -0
- package/dist/lib/events/emitting-runner.js.map +1 -0
- package/dist/lib/events/event-bus.d.ts +54 -0
- package/dist/lib/events/event-bus.js +107 -0
- package/dist/lib/events/event-bus.js.map +1 -0
- package/dist/lib/events/events.d.ts +67 -0
- package/dist/lib/events/events.js +8 -0
- package/dist/lib/events/events.js.map +1 -0
- package/dist/lib/events/index.d.ts +7 -0
- package/dist/lib/events/index.js +6 -0
- package/dist/lib/events/index.js.map +1 -0
- package/dist/lib/execution/devcontainer.d.ts +27 -0
- package/dist/lib/execution/devcontainer.js +80 -0
- package/dist/lib/execution/devcontainer.js.map +1 -1
- package/dist/lib/execution/runners.d.ts +28 -0
- package/dist/lib/execution/runners.js +536 -35
- package/dist/lib/execution/runners.js.map +1 -1
- package/dist/lib/execution/spawner.js +38 -17
- package/dist/lib/execution/spawner.js.map +1 -1
- package/dist/lib/execution/types.d.ts +2 -0
- package/dist/lib/execution/types.js.map +1 -1
- package/dist/lib/external-issues/adapters.d.ts +34 -0
- package/dist/lib/external-issues/adapters.js +191 -0
- package/dist/lib/external-issues/adapters.js.map +1 -1
- package/dist/lib/external-issues/asana.d.ts +75 -0
- package/dist/lib/external-issues/asana.js +243 -0
- package/dist/lib/external-issues/asana.js.map +1 -0
- package/dist/lib/external-issues/index.d.ts +4 -2
- package/dist/lib/external-issues/index.js +6 -2
- package/dist/lib/external-issues/index.js.map +1 -1
- package/dist/lib/external-issues/linear.js.map +1 -1
- package/dist/lib/external-issues/mapping-store.js +1 -1
- package/dist/lib/external-issues/shortcut.d.ts +86 -0
- package/dist/lib/external-issues/shortcut.js +274 -0
- package/dist/lib/external-issues/shortcut.js.map +1 -0
- package/dist/lib/external-issues/types.d.ts +3 -3
- package/dist/lib/external-issues/types.js +1 -1
- package/dist/lib/external-issues/types.js.map +1 -1
- package/dist/lib/mcp/tools/cli-passthrough.js +1 -1
- package/dist/lib/mcp/tools/cli-passthrough.js.map +1 -1
- package/dist/lib/mcp/tools/work.js +1 -1
- package/dist/lib/mcp/tools/work.js.map +1 -1
- package/dist/lib/pmo/index.d.ts +2 -2
- package/dist/lib/pmo/index.js +3 -3
- package/dist/lib/pmo/index.js.map +1 -1
- package/dist/lib/pmo/sync-manager.js +1 -1
- package/dist/lib/pmo/sync-manager.js.map +1 -1
- package/dist/lib/pmo/watcher.js +1 -1
- package/dist/lib/pmo/watcher.js.map +1 -1
- package/dist/lib/repos/git.d.ts +44 -0
- package/dist/lib/repos/git.js +127 -0
- package/dist/lib/repos/git.js.map +1 -1
- package/dist/lib/repos/index.js +36 -3
- package/dist/lib/repos/index.js.map +1 -1
- package/dist/lib/runners/agent-runner.d.ts +92 -0
- package/dist/lib/runners/agent-runner.js +9 -0
- package/dist/lib/runners/agent-runner.js.map +1 -0
- package/dist/lib/runners/claude-code-runner.d.ts +53 -0
- package/dist/lib/runners/claude-code-runner.js +132 -0
- package/dist/lib/runners/claude-code-runner.js.map +1 -0
- package/dist/lib/runners/index.d.ts +34 -0
- package/dist/lib/runners/index.js +97 -0
- package/dist/lib/runners/index.js.map +1 -0
- package/dist/lib/session-store.d.ts +70 -0
- package/dist/lib/session-store.js +167 -0
- package/dist/lib/session-store.js.map +1 -0
- package/dist/lib/shortcut/config.d.ts +44 -0
- package/dist/lib/shortcut/config.js +98 -0
- package/dist/lib/shortcut/config.js.map +1 -0
- package/dist/lib/shortcut/index.d.ts +7 -0
- package/dist/lib/shortcut/index.js +7 -0
- package/dist/lib/shortcut/index.js.map +1 -0
- package/dist/lib/work-source/client.d.ts +10 -0
- package/dist/lib/work-source/client.js +74 -0
- package/dist/lib/work-source/client.js.map +1 -0
- package/dist/lib/work-source/config.d.ts +1 -1
- package/dist/lib/work-source/config.js +17 -1
- package/dist/lib/work-source/config.js.map +1 -1
- package/dist/lib/work-source/index.d.ts +1 -0
- package/dist/lib/work-source/index.js +1 -0
- package/dist/lib/work-source/index.js.map +1 -1
- package/dist/lib/workspace.js +2 -2
- package/dist/lib/workspace.js.map +1 -1
- package/oclif.manifest.json +8135 -6979
- package/package.json +1 -1
|
@@ -8,9 +8,10 @@ import { spawn, execSync } from 'node:child_process';
|
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
import * as os from 'node:os';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
11
12
|
import { DEFAULT_EXECUTION_CONFIG, } from './types.js';
|
|
12
13
|
import { getSetTitleCommands } from '../terminal.js';
|
|
13
|
-
import { readDevcontainerJson } from './devcontainer.js';
|
|
14
|
+
import { readDevcontainerJson, generateOrchestratorDockerfile } from './devcontainer.js';
|
|
14
15
|
import { getCodexCommand, resolveCodexExecutionContext, validateCodexMode } from './codex-adapter.js';
|
|
15
16
|
// =============================================================================
|
|
16
17
|
// Terminal Title Helpers
|
|
@@ -387,24 +388,134 @@ export function runExecutorPreflight(environment, executor, options) {
|
|
|
387
388
|
}
|
|
388
389
|
return { ok: true };
|
|
389
390
|
}
|
|
391
|
+
const ORCHESTRATOR_COMMAND_REGISTRY = [
|
|
392
|
+
{
|
|
393
|
+
title: 'Agent Lifecycle',
|
|
394
|
+
commands: [
|
|
395
|
+
{ cmd: 'prlt work start <ticket> --ephemeral --skip-permissions --create-pr --display background --action implement --run-on-host --yes', desc: 'Spawn an agent for a ticket', checkPath: 'work/start' },
|
|
396
|
+
{ cmd: 'prlt session list', desc: 'List running sessions', checkPath: 'session/list' },
|
|
397
|
+
{ cmd: 'prlt session inspect <agent>', desc: 'Inspect session details', checkPath: 'session/inspect' },
|
|
398
|
+
{ cmd: 'prlt session poke <agent> \'message\'', desc: 'Send message to agent', checkPath: 'session/poke' },
|
|
399
|
+
{ cmd: 'prlt session peek <agent> --lines 200', desc: 'Read agent output', checkPath: 'session/peek' },
|
|
400
|
+
{ cmd: 'prlt session health', desc: 'Check health of all sessions', checkPath: 'session/health' },
|
|
401
|
+
{ cmd: 'prlt session restart <agent>', desc: 'Restart a stuck agent', checkPath: 'session/restart' },
|
|
402
|
+
{ cmd: 'prlt session exec <agent> -- git status', desc: 'Run command in agent context', checkPath: 'session/exec' },
|
|
403
|
+
{ cmd: 'prlt session prune', desc: 'Clean up dead sessions', checkPath: 'session/prune' },
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
title: 'Board Management',
|
|
408
|
+
commands: [
|
|
409
|
+
{ cmd: 'prlt board view', desc: 'View the board', checkPath: 'board/view' },
|
|
410
|
+
{ cmd: 'prlt ticket list', desc: 'List tickets', checkPath: 'ticket/list' },
|
|
411
|
+
{ cmd: 'prlt ticket show <id>', desc: 'Show ticket details', checkPath: 'ticket/show' },
|
|
412
|
+
{ cmd: 'prlt ticket create --title \'x\' --description \'y\'', desc: 'Create a ticket', checkPath: 'ticket/create' },
|
|
413
|
+
{ cmd: 'prlt ticket edit <id> --title \'...\' --add-ac \'...\'', desc: 'Edit ticket fields', checkPath: 'ticket/edit' },
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
title: 'PR Workflow',
|
|
418
|
+
commands: [
|
|
419
|
+
{ cmd: 'gh pr list', desc: 'List open PRs' },
|
|
420
|
+
{ cmd: 'gh pr view <num>', desc: 'View PR details' },
|
|
421
|
+
{ cmd: 'gh pr checks <num>', desc: 'Check CI status' },
|
|
422
|
+
{ cmd: 'gh pr merge <num> --squash', desc: 'Merge PR (squash only)' },
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
];
|
|
426
|
+
const ORCHESTRATOR_ANTI_PATTERNS = [
|
|
427
|
+
{ bad: 'docker exec <container> ...', good: 'prlt session exec', checkPath: 'session/exec' },
|
|
428
|
+
{ bad: 'tmux send-keys ...', good: 'prlt session poke', checkPath: 'session/poke' },
|
|
429
|
+
{ bad: 'tmux capture-pane ...', good: 'prlt session peek', checkPath: 'session/peek' },
|
|
430
|
+
{ bad: 'Direct git operations on agent worktrees', good: 'prlt session exec', checkPath: 'session/exec' },
|
|
431
|
+
];
|
|
390
432
|
/**
|
|
391
|
-
*
|
|
392
|
-
*
|
|
433
|
+
* Resolve the commands directory for dynamic command availability checks.
|
|
434
|
+
* Looks for compiled command files under dist/commands/.
|
|
393
435
|
*/
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
436
|
+
let _commandsDir = null;
|
|
437
|
+
function getCommandsDir() {
|
|
438
|
+
if (_commandsDir === null) {
|
|
439
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
440
|
+
// From dist/lib/execution/runners.js → dist/commands/
|
|
441
|
+
_commandsDir = path.resolve(path.dirname(currentFile), '..', '..', 'commands');
|
|
442
|
+
}
|
|
443
|
+
return _commandsDir;
|
|
444
|
+
}
|
|
445
|
+
function isCommandAvailable(checkPath) {
|
|
446
|
+
const dir = getCommandsDir();
|
|
447
|
+
// Check for compiled .js file or directory (which would contain index.js)
|
|
448
|
+
return fs.existsSync(path.join(dir, `${checkPath}.js`)) || fs.existsSync(path.join(dir, checkPath));
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Build the dynamic command reference section for the orchestrator prompt.
|
|
452
|
+
* Only includes commands that are actually available in this build.
|
|
453
|
+
*/
|
|
454
|
+
function buildOrchestratorCommandReference() {
|
|
455
|
+
let ref = '';
|
|
456
|
+
for (const category of ORCHESTRATOR_COMMAND_REGISTRY) {
|
|
457
|
+
const available = category.commands.filter(c => !c.checkPath || isCommandAvailable(c.checkPath));
|
|
458
|
+
if (available.length === 0)
|
|
459
|
+
continue;
|
|
460
|
+
ref += `### ${category.title}\n`;
|
|
461
|
+
for (const cmd of available) {
|
|
462
|
+
ref += `- \`${cmd.cmd}\` — ${cmd.desc}\n`;
|
|
463
|
+
}
|
|
464
|
+
ref += '\n';
|
|
465
|
+
}
|
|
466
|
+
return ref;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Build the anti-patterns section for the orchestrator prompt.
|
|
470
|
+
* Only includes anti-patterns where the prlt replacement is available.
|
|
471
|
+
*/
|
|
472
|
+
function buildOrchestratorAntiPatterns() {
|
|
473
|
+
const available = ORCHESTRATOR_ANTI_PATTERNS.filter(ap => !ap.checkPath || isCommandAvailable(ap.checkPath));
|
|
474
|
+
if (available.length === 0)
|
|
475
|
+
return '';
|
|
476
|
+
let section = `## Anti-Patterns — NEVER DO\n\n`;
|
|
477
|
+
for (const ap of available) {
|
|
478
|
+
section += `- \`${ap.bad}\` → use \`${ap.good}\` instead\n`;
|
|
479
|
+
}
|
|
480
|
+
section += `\n`;
|
|
481
|
+
return section;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Build the shared orchestrator prompt body (role, runtime, commands, anti-patterns).
|
|
485
|
+
* Used by both buildOrchestratorSystemPrompt and buildOrchestratorPrompt.
|
|
486
|
+
*/
|
|
487
|
+
function buildOrchestratorBody(hqName, context) {
|
|
488
|
+
let prompt = '';
|
|
489
|
+
// Runtime declaration
|
|
490
|
+
prompt += `## prlt Is Your Orchestration Runtime\n\n`;
|
|
491
|
+
prompt += `prlt is your orchestration runtime. NEVER use raw docker exec, tmux send-keys, or direct container access. `;
|
|
492
|
+
prompt += `All orchestration goes through prlt. Every agent interaction, session management, and board operation `;
|
|
493
|
+
prompt += `has a dedicated prlt command. Using raw infrastructure commands bypasses session tracking, breaks `;
|
|
494
|
+
prompt += `health monitoring, and creates orphaned processes.\n\n`;
|
|
495
|
+
// Role
|
|
398
496
|
prompt += `## Your Role\n`;
|
|
399
|
-
prompt += `-
|
|
400
|
-
prompt += `- Delegate
|
|
401
|
-
prompt += `- Monitor agent progress
|
|
402
|
-
prompt += `-
|
|
403
|
-
prompt +=
|
|
404
|
-
|
|
405
|
-
prompt +=
|
|
406
|
-
prompt +=
|
|
407
|
-
|
|
497
|
+
prompt += `- Plan and prioritize work across the board\n`;
|
|
498
|
+
prompt += `- Delegate implementation to agents via \`prlt work start\`\n`;
|
|
499
|
+
prompt += `- Monitor agent progress and review completed work\n`;
|
|
500
|
+
prompt += `- Merge completed PRs via \`gh pr merge --squash\`\n`;
|
|
501
|
+
prompt += `- Never write code or make changes to source files yourself\n\n`;
|
|
502
|
+
// Command reference (dynamically generated)
|
|
503
|
+
prompt += `## Command Reference\n\n`;
|
|
504
|
+
prompt += buildOrchestratorCommandReference();
|
|
505
|
+
// Spawning agents (detailed example)
|
|
506
|
+
prompt += `## Spawning Agents\n`;
|
|
507
|
+
prompt += `\`\`\`\n`;
|
|
508
|
+
prompt += `script -q /dev/null prlt work start TKT-XXXX --ephemeral --skip-permissions --create-pr --display background --action implement --run-on-host --yes\n`;
|
|
509
|
+
prompt += `\`\`\`\n`;
|
|
510
|
+
prompt += `- Review: \`--action review-comment\`\n`;
|
|
511
|
+
prompt += `- Fix: \`--action review-fix\`\n\n`;
|
|
512
|
+
// Anti-patterns (dynamically generated)
|
|
513
|
+
prompt += buildOrchestratorAntiPatterns();
|
|
514
|
+
// Workflow
|
|
515
|
+
prompt += `## Workflow\n`;
|
|
516
|
+
prompt += `- Squash merge only: \`gh pr merge --squash\`\n`;
|
|
517
|
+
prompt += `- After merging: subsequent PRs from parallel agents will need rebase\n`;
|
|
518
|
+
prompt += `- Kill stale sessions after their PRs are merged\n\n`;
|
|
408
519
|
// Load .orchestrator-context.md from HQ root if it exists
|
|
409
520
|
if (context.hqPath) {
|
|
410
521
|
const contextFilePath = path.join(context.hqPath, '.orchestrator-context.md');
|
|
@@ -420,6 +531,29 @@ function buildOrchestratorPrompt(context) {
|
|
|
420
531
|
}
|
|
421
532
|
}
|
|
422
533
|
}
|
|
534
|
+
return prompt;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Build the system prompt for orchestrator sessions.
|
|
538
|
+
* This is injected via Claude Code's --system-prompt flag so the orchestrator
|
|
539
|
+
* knows its role immediately without relying on CLAUDE.md.
|
|
540
|
+
*/
|
|
541
|
+
export function buildOrchestratorSystemPrompt(context) {
|
|
542
|
+
const hqName = context.hqName || 'workspace';
|
|
543
|
+
let prompt = `You are an orchestrator for the **${hqName}** project. `;
|
|
544
|
+
prompt += `Do not implement any work yourself. `;
|
|
545
|
+
prompt += `Your job is to review, plan, investigate, delegate (via \`prlt work start\`), and review completed work.\n\n`;
|
|
546
|
+
prompt += buildOrchestratorBody(hqName, context);
|
|
547
|
+
return prompt;
|
|
548
|
+
}
|
|
549
|
+
function buildOrchestratorPrompt(context) {
|
|
550
|
+
// Full prompt including role context — used for non-Claude executors that
|
|
551
|
+
// don't support --system-prompt. For Claude Code, runHost() splits this into
|
|
552
|
+
// a system prompt (role/tools) + a shorter user message.
|
|
553
|
+
const hqName = context.hqName || 'workspace';
|
|
554
|
+
let prompt = `# Orchestrator: ${hqName}\n\n`;
|
|
555
|
+
prompt += `You are the orchestrator for the **${hqName}** workspace using the prlt ecosystem.\n\n`;
|
|
556
|
+
prompt += buildOrchestratorBody(hqName, context);
|
|
423
557
|
// Include user's custom prompt or action content
|
|
424
558
|
if (context.actionPrompt) {
|
|
425
559
|
prompt += `## Instructions\n\n${context.actionPrompt}\n`;
|
|
@@ -566,8 +700,24 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
566
700
|
const timestamp = Date.now();
|
|
567
701
|
const scriptPath = path.join(baseDir, `exec-${context.ticketId}-${timestamp}.sh`);
|
|
568
702
|
const promptPath = path.join(baseDir, `prompt-${context.ticketId}-${timestamp}.txt`);
|
|
569
|
-
//
|
|
570
|
-
|
|
703
|
+
// For orchestrator sessions with Claude Code, split the prompt:
|
|
704
|
+
// - System prompt (role/tools/context) → injected via --system-prompt flag
|
|
705
|
+
// - User message (action instructions or default) → passed as the initial message
|
|
706
|
+
// Non-Claude executors get the full combined prompt as the user message.
|
|
707
|
+
let systemPromptPath = null;
|
|
708
|
+
if (context.isOrchestrator && isClaudeExecutor(executor)) {
|
|
709
|
+
const systemPrompt = buildOrchestratorSystemPrompt(context);
|
|
710
|
+
systemPromptPath = path.join(baseDir, `system-prompt-${context.ticketId}-${timestamp}.txt`);
|
|
711
|
+
fs.writeFileSync(systemPromptPath, systemPrompt, { mode: 0o644 });
|
|
712
|
+
// Override user message: just action instructions or a default startup message
|
|
713
|
+
const userMessage = context.actionPrompt
|
|
714
|
+
|| 'You are now running as the orchestrator. Check the board status and report what you see.';
|
|
715
|
+
fs.writeFileSync(promptPath, userMessage, { mode: 0o644 });
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
// Write full prompt (includes role context for non-Claude executors)
|
|
719
|
+
fs.writeFileSync(promptPath, prompt, { mode: 0o644 });
|
|
720
|
+
}
|
|
571
721
|
// Build the executor command using getExecutorCommand() output
|
|
572
722
|
// For Claude Code, we also support outputMode and additional flags
|
|
573
723
|
// For non-Claude executors, we use the command as-is from getExecutorCommand()
|
|
@@ -579,7 +729,9 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
579
729
|
const printFlag = config.outputMode === 'print' ? '-p ' : '';
|
|
580
730
|
// --effort high: skips the effort level prompt for automated agents (TKT-1134)
|
|
581
731
|
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
582
|
-
|
|
732
|
+
// Orchestrator sessions inject their role via --system-prompt
|
|
733
|
+
const systemPromptFlag = systemPromptPath ? '--system-prompt "$(cat "$SYSTEM_PROMPT_PATH")" ' : '';
|
|
734
|
+
executorInvocation = `${cmd} ${permissionsFlag}${effortFlag}${printFlag}${systemPromptFlag}"$(cat "$PROMPT_PATH")"`;
|
|
583
735
|
}
|
|
584
736
|
else {
|
|
585
737
|
// Non-Claude executors: build command from getExecutorCommand() args
|
|
@@ -589,10 +741,24 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
589
741
|
}
|
|
590
742
|
// Build script that runs executor and keeps shell open after completion
|
|
591
743
|
const setTitleCmds = getSetTitleCommands(windowTitle);
|
|
744
|
+
const systemPromptVar = systemPromptPath ? `\nSYSTEM_PROMPT_PATH="${systemPromptPath}"` : '';
|
|
745
|
+
// Ephemeral agents auto-close after completion instead of dropping to interactive shell
|
|
746
|
+
const postExecBlock = context.isEphemeral
|
|
747
|
+
? `
|
|
748
|
+
echo ""
|
|
749
|
+
echo "✅ Ephemeral agent work complete. Session will auto-close in 5s..."
|
|
750
|
+
sleep 5
|
|
751
|
+
exit 0
|
|
752
|
+
`
|
|
753
|
+
: `
|
|
754
|
+
echo ""
|
|
755
|
+
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
756
|
+
exec $SHELL
|
|
757
|
+
`;
|
|
592
758
|
const scriptContent = `#!/bin/bash
|
|
593
759
|
# Auto-generated script for ticket ${context.ticketId}
|
|
594
760
|
SCRIPT_PATH="${scriptPath}"
|
|
595
|
-
PROMPT_PATH="${promptPath}"
|
|
761
|
+
PROMPT_PATH="${promptPath}"${systemPromptVar}
|
|
596
762
|
${setTitleCmds}
|
|
597
763
|
echo "🚀 Starting: ${sessionName}"
|
|
598
764
|
echo ""
|
|
@@ -601,12 +767,8 @@ cd "${context.worktreePath}"
|
|
|
601
767
|
(unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT; ${executorInvocation})
|
|
602
768
|
|
|
603
769
|
# Clean up script and prompt files
|
|
604
|
-
rm -f "$SCRIPT_PATH" "$PROMPT_PATH"
|
|
605
|
-
|
|
606
|
-
echo ""
|
|
607
|
-
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
608
|
-
exec $SHELL
|
|
609
|
-
`;
|
|
770
|
+
rm -f "$SCRIPT_PATH" "$PROMPT_PATH"${systemPromptPath ? ' "$SYSTEM_PROMPT_PATH"' : ''}
|
|
771
|
+
${postExecBlock}`;
|
|
610
772
|
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
611
773
|
try {
|
|
612
774
|
// Check if tmux is available
|
|
@@ -640,8 +802,10 @@ exec $SHELL
|
|
|
640
802
|
if (displayMode === 'foreground') {
|
|
641
803
|
try {
|
|
642
804
|
// Clear screen and attach - this blocks until user detaches or claude exits
|
|
643
|
-
//
|
|
644
|
-
|
|
805
|
+
// Never use -CC in foreground mode: control mode sends raw tmux protocol
|
|
806
|
+
// sequences (%begin, %output, %end) that render as garbled text unless
|
|
807
|
+
// iTerm's native CC handler is active (only happens in new tabs opened via AppleScript)
|
|
808
|
+
const fgTmuxAttach = buildTmuxAttachCommand(false);
|
|
645
809
|
execSync(`clear && ${fgTmuxAttach} -t "${sessionName}"`, { stdio: 'inherit' });
|
|
646
810
|
return {
|
|
647
811
|
success: true,
|
|
@@ -1617,6 +1781,14 @@ async function runDevcontainerInTerminal(context, devcontainerCmd, config) {
|
|
|
1617
1781
|
// Write script - run the command directly
|
|
1618
1782
|
// No auth check needed - if auth is required, Claude will show "Invalid API key"
|
|
1619
1783
|
// and user can run /login from there
|
|
1784
|
+
// Ephemeral agents auto-close after completion
|
|
1785
|
+
const postExecBlock = context.isEphemeral
|
|
1786
|
+
? `echo ""
|
|
1787
|
+
echo "✅ Ephemeral agent work complete. Session will auto-close in 5s..."
|
|
1788
|
+
sleep 5
|
|
1789
|
+
exit 0`
|
|
1790
|
+
: `# Keep shell open after completion
|
|
1791
|
+
exec $SHELL`;
|
|
1620
1792
|
const scriptContent = `#!/bin/bash
|
|
1621
1793
|
# Auto-generated script for ticket ${context.ticketId}
|
|
1622
1794
|
${setTitleCmds}
|
|
@@ -1629,8 +1801,7 @@ ${devcontainerCmd}
|
|
|
1629
1801
|
# Clean up script file
|
|
1630
1802
|
rm -f "${scriptPath}"
|
|
1631
1803
|
|
|
1632
|
-
|
|
1633
|
-
exec $SHELL
|
|
1804
|
+
${postExecBlock}
|
|
1634
1805
|
`;
|
|
1635
1806
|
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
1636
1807
|
// Check if we should open in background (don't steal focus)
|
|
@@ -1855,6 +2026,15 @@ async function runDevcontainerInTmux(context, devcontainerCmd, config, displayMo
|
|
|
1855
2026
|
// Unset CI to prevent Claude from detecting CI environment which suppresses TUI output
|
|
1856
2027
|
// Unset CLAUDECODE to allow Claude Code to run (prevents nested session error)
|
|
1857
2028
|
// Note: We keep DEVCONTAINER set so prlt workspace detection works correctly
|
|
2029
|
+
// Ephemeral agents auto-close after completion
|
|
2030
|
+
const containerPostExec = context.isEphemeral
|
|
2031
|
+
? `echo ""
|
|
2032
|
+
echo "✅ Ephemeral agent work complete. Session will auto-close in 5s..."
|
|
2033
|
+
sleep 5
|
|
2034
|
+
exit 0`
|
|
2035
|
+
: `echo ""
|
|
2036
|
+
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
2037
|
+
exec bash`;
|
|
1858
2038
|
const tmuxScript = `#!/bin/bash
|
|
1859
2039
|
export TERM=xterm-256color
|
|
1860
2040
|
export COLORTERM=truecolor
|
|
@@ -1863,9 +2043,7 @@ unset CLAUDECODE
|
|
|
1863
2043
|
echo "🚀 Starting: ${sessionName}"
|
|
1864
2044
|
echo ""
|
|
1865
2045
|
${claudeCmd}
|
|
1866
|
-
|
|
1867
|
-
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
1868
|
-
exec bash
|
|
2046
|
+
${containerPostExec}
|
|
1869
2047
|
`;
|
|
1870
2048
|
const scriptPath = `/tmp/prlt-${sessionName}.sh`;
|
|
1871
2049
|
// Write script and start tmux session inside container
|
|
@@ -1945,8 +2123,10 @@ exec bash
|
|
|
1945
2123
|
if (displayMode === 'foreground') {
|
|
1946
2124
|
try {
|
|
1947
2125
|
// Clear screen and attach - this blocks until user detaches or claude exits
|
|
1948
|
-
//
|
|
1949
|
-
|
|
2126
|
+
// Never use -CC in foreground mode: control mode sends raw tmux protocol
|
|
2127
|
+
// sequences (%begin, %output, %end) that render as garbled text unless
|
|
2128
|
+
// iTerm's native CC handler is active (only happens in new tabs opened via AppleScript)
|
|
2129
|
+
const fgTmuxAttach = buildTmuxAttachCommand(false, true);
|
|
1950
2130
|
execSync(`clear && docker exec -it ${actualContainerId} ${fgTmuxAttach} -t "${sessionName}"`, { stdio: 'inherit' });
|
|
1951
2131
|
return {
|
|
1952
2132
|
success: true,
|
|
@@ -2211,6 +2391,327 @@ export async function runDocker(context, executor, config) {
|
|
|
2211
2391
|
}
|
|
2212
2392
|
}
|
|
2213
2393
|
// =============================================================================
|
|
2394
|
+
// Orchestrator Docker Runner (Sibling Container Pattern)
|
|
2395
|
+
// =============================================================================
|
|
2396
|
+
/**
|
|
2397
|
+
* Run orchestrator in a Docker container using the sibling container pattern.
|
|
2398
|
+
*
|
|
2399
|
+
* Architecture:
|
|
2400
|
+
* ```
|
|
2401
|
+
* Host Docker daemon
|
|
2402
|
+
* ├── orchestrator container (has /var/run/docker.sock mounted)
|
|
2403
|
+
* ├── agent-1 container (spawned by orchestrator, sibling)
|
|
2404
|
+
* ├── agent-2 container (spawned by orchestrator, sibling)
|
|
2405
|
+
* ```
|
|
2406
|
+
*
|
|
2407
|
+
* The orchestrator container needs:
|
|
2408
|
+
* - HQ directory mounted (proletariat-hq)
|
|
2409
|
+
* - Docker socket mounted (/var/run/docker.sock) — so it can spawn agent containers as siblings
|
|
2410
|
+
* - prlt CLI installed in the container
|
|
2411
|
+
* - OAuth credentials for Claude Code (via Docker volume)
|
|
2412
|
+
* - tmux for session persistence inside the container
|
|
2413
|
+
*/
|
|
2414
|
+
export async function runOrchestratorInDocker(context, executor, config, options) {
|
|
2415
|
+
const displayMode = options?.displayMode || 'background';
|
|
2416
|
+
const hqPath = context.hqPath || context.worktreePath;
|
|
2417
|
+
const hqName = context.hqName || 'default';
|
|
2418
|
+
const orchestratorName = context.agentName || 'main';
|
|
2419
|
+
// Container name matches tmux session name for consistency
|
|
2420
|
+
const containerName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}-${(orchestratorName).replace(/[^a-zA-Z0-9._-]/g, '-')}`;
|
|
2421
|
+
const imageName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}:latest`;
|
|
2422
|
+
try {
|
|
2423
|
+
// Check Docker is running
|
|
2424
|
+
if (!isDockerRunning()) {
|
|
2425
|
+
return {
|
|
2426
|
+
success: false,
|
|
2427
|
+
error: 'Docker is not running. Please start Docker Desktop and try again.',
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
// Check if container already exists and is running
|
|
2431
|
+
if (containerExists(containerName)) {
|
|
2432
|
+
if (isContainerRunning(containerName)) {
|
|
2433
|
+
return {
|
|
2434
|
+
success: false,
|
|
2435
|
+
error: `Orchestrator container "${containerName}" is already running. Use "prlt orchestrator attach" to reattach.`,
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
// Remove stopped container
|
|
2439
|
+
try {
|
|
2440
|
+
execSync(`docker rm -f ${containerName}`, { stdio: 'pipe' });
|
|
2441
|
+
}
|
|
2442
|
+
catch {
|
|
2443
|
+
// Ignore removal errors
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
// Generate Dockerfile
|
|
2447
|
+
const orchestratorDockerOptions = {
|
|
2448
|
+
orchestratorName,
|
|
2449
|
+
hqPath,
|
|
2450
|
+
executor,
|
|
2451
|
+
};
|
|
2452
|
+
const dockerfileContent = generateOrchestratorDockerfile(orchestratorDockerOptions);
|
|
2453
|
+
// Write Dockerfile to temp directory
|
|
2454
|
+
const buildDir = path.join(hqPath, '.proletariat', 'orchestrator-docker');
|
|
2455
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
2456
|
+
const dockerfilePath = path.join(buildDir, 'Dockerfile');
|
|
2457
|
+
fs.writeFileSync(dockerfilePath, dockerfileContent);
|
|
2458
|
+
// Build the image
|
|
2459
|
+
const hostPrltVersion = getHostPrltVersion();
|
|
2460
|
+
const buildArgs = {
|
|
2461
|
+
PRLT_VERSION: hostPrltVersion || 'latest',
|
|
2462
|
+
};
|
|
2463
|
+
const buildArgFlags = Object.entries(buildArgs)
|
|
2464
|
+
.map(([key, value]) => `--build-arg ${key}="${value}"`)
|
|
2465
|
+
.join(' ');
|
|
2466
|
+
console.debug(`[runners:orchestrator-docker] Building image: ${imageName}`);
|
|
2467
|
+
try {
|
|
2468
|
+
execSync(`docker build -t ${imageName} -f "${dockerfilePath}" ${buildArgFlags} "${buildDir}"`, { stdio: 'pipe' });
|
|
2469
|
+
}
|
|
2470
|
+
catch (buildError) {
|
|
2471
|
+
return {
|
|
2472
|
+
success: false,
|
|
2473
|
+
error: `Failed to build orchestrator Docker image: ${buildError instanceof Error ? buildError.message : buildError}`,
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
// Build mount flags for docker run
|
|
2477
|
+
const mounts = [
|
|
2478
|
+
// Mount HQ directory
|
|
2479
|
+
`-v "${hqPath}:/hq:cached"`,
|
|
2480
|
+
// Docker socket for sibling container pattern
|
|
2481
|
+
`-v /var/run/docker.sock:/var/run/docker.sock`,
|
|
2482
|
+
// Claude credentials volume (shared with agent containers)
|
|
2483
|
+
...(executor === 'claude-code' ? ['-v "claude-credentials:/home/node/.claude"'] : []),
|
|
2484
|
+
// Persistent bash history
|
|
2485
|
+
'-v "claude-bash-history:/commandhistory"',
|
|
2486
|
+
];
|
|
2487
|
+
// Build environment variables
|
|
2488
|
+
const envVars = [
|
|
2489
|
+
`-e PRLT_HQ_PATH=/hq`,
|
|
2490
|
+
`-e PRLT_AGENT_NAME="orchestrator-${orchestratorName}"`,
|
|
2491
|
+
`-e PRLT_HOST_PATH="${hqPath}"`,
|
|
2492
|
+
// Pass through GitHub tokens for agent spawning
|
|
2493
|
+
...(process.env.GITHUB_TOKEN ? [`-e GITHUB_TOKEN="${process.env.GITHUB_TOKEN}"`] : []),
|
|
2494
|
+
...(process.env.GH_TOKEN ? [`-e GH_TOKEN="${process.env.GH_TOKEN}"`] : []),
|
|
2495
|
+
// Pass ANTHROPIC_API_KEY if available (for cases where OAuth is not set up)
|
|
2496
|
+
...(process.env.ANTHROPIC_API_KEY ? [`-e ANTHROPIC_API_KEY="${process.env.ANTHROPIC_API_KEY}"`] : []),
|
|
2497
|
+
];
|
|
2498
|
+
// Create and start container
|
|
2499
|
+
const createCmd = [
|
|
2500
|
+
'docker run -d',
|
|
2501
|
+
`--name ${containerName}`,
|
|
2502
|
+
'--user node',
|
|
2503
|
+
'-w /hq',
|
|
2504
|
+
...mounts,
|
|
2505
|
+
...envVars,
|
|
2506
|
+
`--memory=${config.devcontainer.memory}`,
|
|
2507
|
+
`--cpus=${config.devcontainer.cpus}`,
|
|
2508
|
+
imageName,
|
|
2509
|
+
'sleep infinity', // Keep container running
|
|
2510
|
+
].join(' ');
|
|
2511
|
+
console.debug(`[runners:orchestrator-docker] Creating container: ${createCmd}`);
|
|
2512
|
+
execSync(createCmd, { stdio: 'pipe' });
|
|
2513
|
+
const containerId = getContainerId(containerName);
|
|
2514
|
+
if (!containerId) {
|
|
2515
|
+
return {
|
|
2516
|
+
success: false,
|
|
2517
|
+
error: 'Failed to get container ID after creation',
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
// Fix Docker socket permissions inside the container
|
|
2521
|
+
// The socket is owned by root on the host; we need the node user to access it
|
|
2522
|
+
try {
|
|
2523
|
+
execSync(`docker exec --user root ${containerId} chmod 666 /var/run/docker.sock`, { stdio: 'pipe' });
|
|
2524
|
+
}
|
|
2525
|
+
catch {
|
|
2526
|
+
console.debug('[runners:orchestrator-docker] Failed to fix Docker socket permissions (may already be accessible)');
|
|
2527
|
+
}
|
|
2528
|
+
// Copy Claude Code settings to container (for bypassing prompts)
|
|
2529
|
+
if (executor === 'claude-code') {
|
|
2530
|
+
try {
|
|
2531
|
+
const hostClaudeJson = path.join(os.homedir(), '.claude.json');
|
|
2532
|
+
let settings = {};
|
|
2533
|
+
if (fs.existsSync(hostClaudeJson)) {
|
|
2534
|
+
try {
|
|
2535
|
+
settings = JSON.parse(fs.readFileSync(hostClaudeJson, 'utf-8'));
|
|
2536
|
+
}
|
|
2537
|
+
catch {
|
|
2538
|
+
// Use empty settings
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (config.permissionMode === 'danger') {
|
|
2542
|
+
settings.bypassPermissionsModeAccepted = true;
|
|
2543
|
+
}
|
|
2544
|
+
settings.numStartups = settings.numStartups || 1;
|
|
2545
|
+
settings.hasCompletedOnboarding = true;
|
|
2546
|
+
settings.theme = settings.theme || 'dark';
|
|
2547
|
+
if (!settings.tipsHistory || typeof settings.tipsHistory !== 'object') {
|
|
2548
|
+
settings.tipsHistory = {};
|
|
2549
|
+
}
|
|
2550
|
+
const tips = settings.tipsHistory;
|
|
2551
|
+
tips['new-user-warmup'] = tips['new-user-warmup'] || 1;
|
|
2552
|
+
settings.effortCalloutDismissed = true;
|
|
2553
|
+
if (!settings.projects || typeof settings.projects !== 'object') {
|
|
2554
|
+
settings.projects = {};
|
|
2555
|
+
}
|
|
2556
|
+
const projects = settings.projects;
|
|
2557
|
+
for (const projectPath of ['/hq', '/']) {
|
|
2558
|
+
if (!projects[projectPath])
|
|
2559
|
+
projects[projectPath] = {};
|
|
2560
|
+
projects[projectPath].hasTrustDialogAccepted = true;
|
|
2561
|
+
projects[projectPath].hasCompletedProjectOnboarding = true;
|
|
2562
|
+
}
|
|
2563
|
+
execSync(`docker exec -i ${containerId} bash -c 'cat > /home/node/.claude.json'`, { input: JSON.stringify(settings), stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2564
|
+
const claudeSettings = JSON.stringify({ skipDangerousModePermissionPrompt: true });
|
|
2565
|
+
execSync(`docker exec -i ${containerId} bash -c 'mkdir -p /home/node/.claude && cat > /home/node/.claude/settings.json'`, { input: claudeSettings, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2566
|
+
}
|
|
2567
|
+
catch (error) {
|
|
2568
|
+
console.debug('[runners:orchestrator-docker] Failed to copy Claude settings:', error);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
// Build the prompt and write to temp file inside container
|
|
2572
|
+
const prompt = buildPrompt(context);
|
|
2573
|
+
const promptPath = `/tmp/orchestrator-prompt-${Date.now()}.txt`;
|
|
2574
|
+
try {
|
|
2575
|
+
execSync(`docker exec -i ${containerId} bash -c 'cat > ${promptPath}'`, { input: prompt, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2576
|
+
}
|
|
2577
|
+
catch {
|
|
2578
|
+
return {
|
|
2579
|
+
success: false,
|
|
2580
|
+
error: 'Failed to write prompt to container',
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
// Build executor command
|
|
2584
|
+
const skipPermissions = config.permissionMode === 'danger';
|
|
2585
|
+
const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
|
|
2586
|
+
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
2587
|
+
const executorCmd = executor === 'claude-code'
|
|
2588
|
+
? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`
|
|
2589
|
+
: `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`;
|
|
2590
|
+
// Build tmux session name (reuses the same name as host tmux for consistency)
|
|
2591
|
+
const tmuxSessionName = options?.sessionName || containerName;
|
|
2592
|
+
// Create tmux session inside container with the executor command
|
|
2593
|
+
const tmuxCmd = `tmux new-session -d -s "${tmuxSessionName}" -n "${tmuxSessionName}" bash -c '(unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT; cd /hq && ${executorCmd}); echo ""; echo "Orchestrator complete. Press Enter to close."; exec bash'`;
|
|
2594
|
+
try {
|
|
2595
|
+
execSync(`docker exec ${containerId} bash -c '${tmuxCmd.replace(/'/g, "'\\''")}'`, { stdio: 'pipe' });
|
|
2596
|
+
}
|
|
2597
|
+
catch (tmuxError) {
|
|
2598
|
+
// Fallback: try simpler command without subshell
|
|
2599
|
+
console.debug('[runners:orchestrator-docker] tmux creation failed, trying simpler approach:', tmuxError);
|
|
2600
|
+
try {
|
|
2601
|
+
// Write a script inside the container
|
|
2602
|
+
const scriptContent = `#!/bin/bash
|
|
2603
|
+
cd /hq
|
|
2604
|
+
unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT
|
|
2605
|
+
${executor === 'claude-code' ? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"` : `claude "$(cat ${promptPath})"`}
|
|
2606
|
+
echo ""
|
|
2607
|
+
echo "Orchestrator complete. Press Enter to close."
|
|
2608
|
+
exec bash
|
|
2609
|
+
`;
|
|
2610
|
+
execSync(`docker exec -i ${containerId} bash -c 'cat > /tmp/orchestrator-start.sh && chmod +x /tmp/orchestrator-start.sh'`, { input: scriptContent, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2611
|
+
execSync(`docker exec ${containerId} tmux new-session -d -s "${tmuxSessionName}" /tmp/orchestrator-start.sh`, { stdio: 'pipe' });
|
|
2612
|
+
}
|
|
2613
|
+
catch (fallbackError) {
|
|
2614
|
+
return {
|
|
2615
|
+
success: false,
|
|
2616
|
+
error: `Failed to create tmux session in container: ${fallbackError instanceof Error ? fallbackError.message : fallbackError}`,
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
// Handle display mode
|
|
2621
|
+
if (displayMode === 'foreground') {
|
|
2622
|
+
// Attach to tmux inside the container in current terminal
|
|
2623
|
+
try {
|
|
2624
|
+
const child = spawn('docker', ['exec', '-it', containerId, 'tmux', 'attach', '-t', tmuxSessionName], {
|
|
2625
|
+
stdio: 'inherit',
|
|
2626
|
+
});
|
|
2627
|
+
await new Promise((resolve) => {
|
|
2628
|
+
child.on('close', () => resolve());
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
2631
|
+
catch {
|
|
2632
|
+
// User detached - that's fine
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
else if (displayMode === 'terminal' && process.platform === 'darwin') {
|
|
2636
|
+
// Open a new terminal tab that attaches to the container's tmux
|
|
2637
|
+
const baseDir = path.join(hqPath, '.proletariat', 'scripts');
|
|
2638
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
2639
|
+
const scriptPath = path.join(baseDir, `orch-docker-attach-${Date.now()}.sh`);
|
|
2640
|
+
const scriptContent = `#!/bin/bash
|
|
2641
|
+
echo -ne "\\033]0;Orchestrator (Docker)\\007"
|
|
2642
|
+
echo -ne "\\033]1;Orchestrator (Docker)\\007"
|
|
2643
|
+
docker exec -it ${containerId} tmux attach -t "${tmuxSessionName}"
|
|
2644
|
+
rm -f "${scriptPath}"
|
|
2645
|
+
exec $SHELL
|
|
2646
|
+
`;
|
|
2647
|
+
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
2648
|
+
const terminalApp = config.terminal.app;
|
|
2649
|
+
try {
|
|
2650
|
+
switch (terminalApp) {
|
|
2651
|
+
case 'iTerm':
|
|
2652
|
+
execSync(`osascript -e '
|
|
2653
|
+
tell application "iTerm"
|
|
2654
|
+
activate
|
|
2655
|
+
tell current window
|
|
2656
|
+
set newTab to (create tab with default profile)
|
|
2657
|
+
tell current session of newTab
|
|
2658
|
+
set name to "Orchestrator (Docker)"
|
|
2659
|
+
write text "${scriptPath}"
|
|
2660
|
+
end tell
|
|
2661
|
+
end tell
|
|
2662
|
+
end tell
|
|
2663
|
+
'`);
|
|
2664
|
+
break;
|
|
2665
|
+
case 'Ghostty':
|
|
2666
|
+
execSync(`osascript -e '
|
|
2667
|
+
tell application "Ghostty"
|
|
2668
|
+
activate
|
|
2669
|
+
end tell
|
|
2670
|
+
tell application "System Events"
|
|
2671
|
+
tell process "Ghostty"
|
|
2672
|
+
keystroke "t" using command down
|
|
2673
|
+
delay 0.3
|
|
2674
|
+
keystroke "${scriptPath}"
|
|
2675
|
+
keystroke return
|
|
2676
|
+
end tell
|
|
2677
|
+
end tell
|
|
2678
|
+
'`);
|
|
2679
|
+
break;
|
|
2680
|
+
default:
|
|
2681
|
+
execSync(`osascript -e '
|
|
2682
|
+
tell application "Terminal"
|
|
2683
|
+
activate
|
|
2684
|
+
tell application "System Events"
|
|
2685
|
+
tell process "Terminal"
|
|
2686
|
+
keystroke "t" using command down
|
|
2687
|
+
end tell
|
|
2688
|
+
end tell
|
|
2689
|
+
delay 0.3
|
|
2690
|
+
do script "${scriptPath}" in front window
|
|
2691
|
+
end tell
|
|
2692
|
+
'`);
|
|
2693
|
+
break;
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
catch {
|
|
2697
|
+
console.debug('[runners:orchestrator-docker] Failed to open terminal tab, running in background');
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
// 'background' display mode: container is already running, nothing more to do
|
|
2701
|
+
return {
|
|
2702
|
+
success: true,
|
|
2703
|
+
containerId,
|
|
2704
|
+
sessionId: tmuxSessionName,
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
catch (error) {
|
|
2708
|
+
return {
|
|
2709
|
+
success: false,
|
|
2710
|
+
error: error instanceof Error ? error.message : 'Failed to start orchestrator in Docker',
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
// =============================================================================
|
|
2214
2715
|
// VM Runner
|
|
2215
2716
|
// =============================================================================
|
|
2216
2717
|
export async function runVm(context, executor, config, host) {
|