@proletariat/cli 0.3.55 → 0.3.56
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 +5 -4
- 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/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 +122 -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/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 +22 -30
- 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 +51 -44
- package/dist/lib/agents/commands.js.map +1 -1
- package/dist/lib/agents/index.js +5 -0
- 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/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 +459 -31
- package/dist/lib/execution/runners.js.map +1 -1
- package/dist/lib/execution/spawner.js +35 -17
- package/dist/lib/execution/spawner.js.map +1 -1
- package/dist/lib/execution/types.d.ts +1 -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/index.js +1 -1
- 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 +4291 -3248
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import * as path from 'node:path';
|
|
|
10
10
|
import * as os from 'node:os';
|
|
11
11
|
import { DEFAULT_EXECUTION_CONFIG, } from './types.js';
|
|
12
12
|
import { getSetTitleCommands } from '../terminal.js';
|
|
13
|
-
import { readDevcontainerJson } from './devcontainer.js';
|
|
13
|
+
import { readDevcontainerJson, generateOrchestratorDockerfile } from './devcontainer.js';
|
|
14
14
|
import { getCodexCommand, resolveCodexExecutionContext, validateCodexMode } from './codex-adapter.js';
|
|
15
15
|
// =============================================================================
|
|
16
16
|
// Terminal Title Helpers
|
|
@@ -388,23 +388,84 @@ export function runExecutorPreflight(environment, executor, options) {
|
|
|
388
388
|
return { ok: true };
|
|
389
389
|
}
|
|
390
390
|
/**
|
|
391
|
-
* Build
|
|
392
|
-
*
|
|
391
|
+
* Build the system prompt for orchestrator sessions.
|
|
392
|
+
* This is injected via Claude Code's --system-prompt flag so the orchestrator
|
|
393
|
+
* knows its role immediately without relying on CLAUDE.md.
|
|
393
394
|
*/
|
|
395
|
+
export function buildOrchestratorSystemPrompt(context) {
|
|
396
|
+
const hqName = context.hqName || 'workspace';
|
|
397
|
+
let prompt = `You are an orchestrator for the **${hqName}** project. `;
|
|
398
|
+
prompt += `Use \`prlt\` to view what's running — board, sessions, tickets, PRs. `;
|
|
399
|
+
prompt += `Do not implement any work yourself. `;
|
|
400
|
+
prompt += `Your job is to review, plan, investigate, delegate (via \`prlt work start\`), and review completed work.\n\n`;
|
|
401
|
+
prompt += `## Your Role\n`;
|
|
402
|
+
prompt += `- Plan and prioritize work across the board\n`;
|
|
403
|
+
prompt += `- Delegate implementation to agents via \`prlt work start\`\n`;
|
|
404
|
+
prompt += `- Monitor agent progress and review completed work\n`;
|
|
405
|
+
prompt += `- Merge completed PRs via \`gh pr merge --squash\`\n`;
|
|
406
|
+
prompt += `- Never write code or make changes to source files yourself\n\n`;
|
|
407
|
+
prompt += `## Discovering State\n`;
|
|
408
|
+
prompt += `Always discover current state dynamically — do NOT rely on static context files:\n`;
|
|
409
|
+
prompt += `- **Board**: \`prlt board view\`, \`prlt ticket list\`, \`prlt ticket show <id>\`\n`;
|
|
410
|
+
prompt += `- **Agents**: \`prlt session list\`, \`prlt session peek <session>\`, \`prlt work status\`\n`;
|
|
411
|
+
prompt += `- **PRs/CI**: \`gh pr list\`, \`gh pr view <num>\`, \`gh pr checks <num>\`\n`;
|
|
412
|
+
prompt += `- All prlt MCP tools are also available\n\n`;
|
|
413
|
+
prompt += `## Spawning Agents\n`;
|
|
414
|
+
prompt += `\`\`\`\n`;
|
|
415
|
+
prompt += `script -q /dev/null prlt work start TKT-XXXX --ephemeral --skip-permissions --create-pr --display background --action implement --run-on-host --yes\n`;
|
|
416
|
+
prompt += `\`\`\`\n`;
|
|
417
|
+
prompt += `- Review: \`--action review-comment\`\n`;
|
|
418
|
+
prompt += `- Fix: \`--action review-fix\`\n\n`;
|
|
419
|
+
prompt += `## Workflow\n`;
|
|
420
|
+
prompt += `- Squash merge only: \`gh pr merge --squash\`\n`;
|
|
421
|
+
prompt += `- After merging: subsequent PRs from parallel agents will need rebase\n`;
|
|
422
|
+
prompt += `- Kill stale sessions after their PRs are merged\n\n`;
|
|
423
|
+
// Load .orchestrator-context.md from HQ root if it exists
|
|
424
|
+
if (context.hqPath) {
|
|
425
|
+
const contextFilePath = path.join(context.hqPath, '.orchestrator-context.md');
|
|
426
|
+
if (fs.existsSync(contextFilePath)) {
|
|
427
|
+
try {
|
|
428
|
+
const contextContent = fs.readFileSync(contextFilePath, 'utf-8').trim();
|
|
429
|
+
if (contextContent) {
|
|
430
|
+
prompt += `## Workspace Context\n\n${contextContent}\n\n`;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// Ignore read errors
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return prompt;
|
|
439
|
+
}
|
|
394
440
|
function buildOrchestratorPrompt(context) {
|
|
441
|
+
// Full prompt including role context — used for non-Claude executors that
|
|
442
|
+
// don't support --system-prompt. For Claude Code, runHost() splits this into
|
|
443
|
+
// a system prompt (role/tools) + a shorter user message.
|
|
395
444
|
const hqName = context.hqName || 'workspace';
|
|
396
445
|
let prompt = `# Orchestrator: ${hqName}\n\n`;
|
|
397
446
|
prompt += `You are the orchestrator for the **${hqName}** workspace using the prlt ecosystem.\n\n`;
|
|
398
447
|
prompt += `## Your Role\n`;
|
|
399
|
-
prompt += `-
|
|
400
|
-
prompt += `- Delegate
|
|
401
|
-
prompt += `- Monitor agent progress
|
|
402
|
-
prompt += `-
|
|
403
|
-
prompt +=
|
|
404
|
-
prompt +=
|
|
405
|
-
prompt +=
|
|
406
|
-
prompt += `-
|
|
407
|
-
prompt += `-
|
|
448
|
+
prompt += `- Plan and prioritize work across the board\n`;
|
|
449
|
+
prompt += `- Delegate implementation to agents via \`prlt work start\`\n`;
|
|
450
|
+
prompt += `- Monitor agent progress and review completed work\n`;
|
|
451
|
+
prompt += `- Merge completed PRs via \`gh pr merge --squash\`\n`;
|
|
452
|
+
prompt += `- Never write code or make changes to source files yourself\n\n`;
|
|
453
|
+
prompt += `## Discovering State\n`;
|
|
454
|
+
prompt += `Always discover current state dynamically — do NOT rely on static context files:\n`;
|
|
455
|
+
prompt += `- **Board**: \`prlt board view\`, \`prlt ticket list\`, \`prlt ticket show <id>\`\n`;
|
|
456
|
+
prompt += `- **Agents**: \`prlt session list\`, \`prlt session peek <session>\`, \`prlt work status\`\n`;
|
|
457
|
+
prompt += `- **PRs/CI**: \`gh pr list\`, \`gh pr view <num>\`, \`gh pr checks <num>\`\n`;
|
|
458
|
+
prompt += `- All prlt MCP tools are also available\n\n`;
|
|
459
|
+
prompt += `## Spawning Agents\n`;
|
|
460
|
+
prompt += `\`\`\`\n`;
|
|
461
|
+
prompt += `script -q /dev/null prlt work start TKT-XXXX --ephemeral --skip-permissions --create-pr --display background --action implement --run-on-host --yes\n`;
|
|
462
|
+
prompt += `\`\`\`\n`;
|
|
463
|
+
prompt += `- Review: \`--action review-comment\`\n`;
|
|
464
|
+
prompt += `- Fix: \`--action review-fix\`\n\n`;
|
|
465
|
+
prompt += `## Workflow\n`;
|
|
466
|
+
prompt += `- Squash merge only: \`gh pr merge --squash\`\n`;
|
|
467
|
+
prompt += `- After merging: subsequent PRs from parallel agents will need rebase\n`;
|
|
468
|
+
prompt += `- Kill stale sessions after their PRs are merged\n\n`;
|
|
408
469
|
// Load .orchestrator-context.md from HQ root if it exists
|
|
409
470
|
if (context.hqPath) {
|
|
410
471
|
const contextFilePath = path.join(context.hqPath, '.orchestrator-context.md');
|
|
@@ -566,8 +627,24 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
566
627
|
const timestamp = Date.now();
|
|
567
628
|
const scriptPath = path.join(baseDir, `exec-${context.ticketId}-${timestamp}.sh`);
|
|
568
629
|
const promptPath = path.join(baseDir, `prompt-${context.ticketId}-${timestamp}.txt`);
|
|
569
|
-
//
|
|
570
|
-
|
|
630
|
+
// For orchestrator sessions with Claude Code, split the prompt:
|
|
631
|
+
// - System prompt (role/tools/context) → injected via --system-prompt flag
|
|
632
|
+
// - User message (action instructions or default) → passed as the initial message
|
|
633
|
+
// Non-Claude executors get the full combined prompt as the user message.
|
|
634
|
+
let systemPromptPath = null;
|
|
635
|
+
if (context.isOrchestrator && isClaudeExecutor(executor)) {
|
|
636
|
+
const systemPrompt = buildOrchestratorSystemPrompt(context);
|
|
637
|
+
systemPromptPath = path.join(baseDir, `system-prompt-${context.ticketId}-${timestamp}.txt`);
|
|
638
|
+
fs.writeFileSync(systemPromptPath, systemPrompt, { mode: 0o644 });
|
|
639
|
+
// Override user message: just action instructions or a default startup message
|
|
640
|
+
const userMessage = context.actionPrompt
|
|
641
|
+
|| 'You are now running as the orchestrator. Check the board status and report what you see.';
|
|
642
|
+
fs.writeFileSync(promptPath, userMessage, { mode: 0o644 });
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
// Write full prompt (includes role context for non-Claude executors)
|
|
646
|
+
fs.writeFileSync(promptPath, prompt, { mode: 0o644 });
|
|
647
|
+
}
|
|
571
648
|
// Build the executor command using getExecutorCommand() output
|
|
572
649
|
// For Claude Code, we also support outputMode and additional flags
|
|
573
650
|
// For non-Claude executors, we use the command as-is from getExecutorCommand()
|
|
@@ -579,7 +656,9 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
579
656
|
const printFlag = config.outputMode === 'print' ? '-p ' : '';
|
|
580
657
|
// --effort high: skips the effort level prompt for automated agents (TKT-1134)
|
|
581
658
|
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
582
|
-
|
|
659
|
+
// Orchestrator sessions inject their role via --system-prompt
|
|
660
|
+
const systemPromptFlag = systemPromptPath ? '--system-prompt "$(cat "$SYSTEM_PROMPT_PATH")" ' : '';
|
|
661
|
+
executorInvocation = `${cmd} ${permissionsFlag}${effortFlag}${printFlag}${systemPromptFlag}"$(cat "$PROMPT_PATH")"`;
|
|
583
662
|
}
|
|
584
663
|
else {
|
|
585
664
|
// Non-Claude executors: build command from getExecutorCommand() args
|
|
@@ -589,10 +668,24 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
589
668
|
}
|
|
590
669
|
// Build script that runs executor and keeps shell open after completion
|
|
591
670
|
const setTitleCmds = getSetTitleCommands(windowTitle);
|
|
671
|
+
const systemPromptVar = systemPromptPath ? `\nSYSTEM_PROMPT_PATH="${systemPromptPath}"` : '';
|
|
672
|
+
// Ephemeral agents auto-close after completion instead of dropping to interactive shell
|
|
673
|
+
const postExecBlock = context.isEphemeral
|
|
674
|
+
? `
|
|
675
|
+
echo ""
|
|
676
|
+
echo "✅ Ephemeral agent work complete. Session will auto-close in 5s..."
|
|
677
|
+
sleep 5
|
|
678
|
+
exit 0
|
|
679
|
+
`
|
|
680
|
+
: `
|
|
681
|
+
echo ""
|
|
682
|
+
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
683
|
+
exec $SHELL
|
|
684
|
+
`;
|
|
592
685
|
const scriptContent = `#!/bin/bash
|
|
593
686
|
# Auto-generated script for ticket ${context.ticketId}
|
|
594
687
|
SCRIPT_PATH="${scriptPath}"
|
|
595
|
-
PROMPT_PATH="${promptPath}"
|
|
688
|
+
PROMPT_PATH="${promptPath}"${systemPromptVar}
|
|
596
689
|
${setTitleCmds}
|
|
597
690
|
echo "🚀 Starting: ${sessionName}"
|
|
598
691
|
echo ""
|
|
@@ -601,12 +694,8 @@ cd "${context.worktreePath}"
|
|
|
601
694
|
(unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT; ${executorInvocation})
|
|
602
695
|
|
|
603
696
|
# 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
|
-
`;
|
|
697
|
+
rm -f "$SCRIPT_PATH" "$PROMPT_PATH"${systemPromptPath ? ' "$SYSTEM_PROMPT_PATH"' : ''}
|
|
698
|
+
${postExecBlock}`;
|
|
610
699
|
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
611
700
|
try {
|
|
612
701
|
// Check if tmux is available
|
|
@@ -640,8 +729,10 @@ exec $SHELL
|
|
|
640
729
|
if (displayMode === 'foreground') {
|
|
641
730
|
try {
|
|
642
731
|
// Clear screen and attach - this blocks until user detaches or claude exits
|
|
643
|
-
//
|
|
644
|
-
|
|
732
|
+
// Never use -CC in foreground mode: control mode sends raw tmux protocol
|
|
733
|
+
// sequences (%begin, %output, %end) that render as garbled text unless
|
|
734
|
+
// iTerm's native CC handler is active (only happens in new tabs opened via AppleScript)
|
|
735
|
+
const fgTmuxAttach = buildTmuxAttachCommand(false);
|
|
645
736
|
execSync(`clear && ${fgTmuxAttach} -t "${sessionName}"`, { stdio: 'inherit' });
|
|
646
737
|
return {
|
|
647
738
|
success: true,
|
|
@@ -1617,6 +1708,14 @@ async function runDevcontainerInTerminal(context, devcontainerCmd, config) {
|
|
|
1617
1708
|
// Write script - run the command directly
|
|
1618
1709
|
// No auth check needed - if auth is required, Claude will show "Invalid API key"
|
|
1619
1710
|
// and user can run /login from there
|
|
1711
|
+
// Ephemeral agents auto-close after completion
|
|
1712
|
+
const postExecBlock = context.isEphemeral
|
|
1713
|
+
? `echo ""
|
|
1714
|
+
echo "✅ Ephemeral agent work complete. Session will auto-close in 5s..."
|
|
1715
|
+
sleep 5
|
|
1716
|
+
exit 0`
|
|
1717
|
+
: `# Keep shell open after completion
|
|
1718
|
+
exec $SHELL`;
|
|
1620
1719
|
const scriptContent = `#!/bin/bash
|
|
1621
1720
|
# Auto-generated script for ticket ${context.ticketId}
|
|
1622
1721
|
${setTitleCmds}
|
|
@@ -1629,8 +1728,7 @@ ${devcontainerCmd}
|
|
|
1629
1728
|
# Clean up script file
|
|
1630
1729
|
rm -f "${scriptPath}"
|
|
1631
1730
|
|
|
1632
|
-
|
|
1633
|
-
exec $SHELL
|
|
1731
|
+
${postExecBlock}
|
|
1634
1732
|
`;
|
|
1635
1733
|
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
1636
1734
|
// Check if we should open in background (don't steal focus)
|
|
@@ -1855,6 +1953,15 @@ async function runDevcontainerInTmux(context, devcontainerCmd, config, displayMo
|
|
|
1855
1953
|
// Unset CI to prevent Claude from detecting CI environment which suppresses TUI output
|
|
1856
1954
|
// Unset CLAUDECODE to allow Claude Code to run (prevents nested session error)
|
|
1857
1955
|
// Note: We keep DEVCONTAINER set so prlt workspace detection works correctly
|
|
1956
|
+
// Ephemeral agents auto-close after completion
|
|
1957
|
+
const containerPostExec = context.isEphemeral
|
|
1958
|
+
? `echo ""
|
|
1959
|
+
echo "✅ Ephemeral agent work complete. Session will auto-close in 5s..."
|
|
1960
|
+
sleep 5
|
|
1961
|
+
exit 0`
|
|
1962
|
+
: `echo ""
|
|
1963
|
+
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
1964
|
+
exec bash`;
|
|
1858
1965
|
const tmuxScript = `#!/bin/bash
|
|
1859
1966
|
export TERM=xterm-256color
|
|
1860
1967
|
export COLORTERM=truecolor
|
|
@@ -1863,9 +1970,7 @@ unset CLAUDECODE
|
|
|
1863
1970
|
echo "🚀 Starting: ${sessionName}"
|
|
1864
1971
|
echo ""
|
|
1865
1972
|
${claudeCmd}
|
|
1866
|
-
|
|
1867
|
-
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
1868
|
-
exec bash
|
|
1973
|
+
${containerPostExec}
|
|
1869
1974
|
`;
|
|
1870
1975
|
const scriptPath = `/tmp/prlt-${sessionName}.sh`;
|
|
1871
1976
|
// Write script and start tmux session inside container
|
|
@@ -1945,8 +2050,10 @@ exec bash
|
|
|
1945
2050
|
if (displayMode === 'foreground') {
|
|
1946
2051
|
try {
|
|
1947
2052
|
// Clear screen and attach - this blocks until user detaches or claude exits
|
|
1948
|
-
//
|
|
1949
|
-
|
|
2053
|
+
// Never use -CC in foreground mode: control mode sends raw tmux protocol
|
|
2054
|
+
// sequences (%begin, %output, %end) that render as garbled text unless
|
|
2055
|
+
// iTerm's native CC handler is active (only happens in new tabs opened via AppleScript)
|
|
2056
|
+
const fgTmuxAttach = buildTmuxAttachCommand(false, true);
|
|
1950
2057
|
execSync(`clear && docker exec -it ${actualContainerId} ${fgTmuxAttach} -t "${sessionName}"`, { stdio: 'inherit' });
|
|
1951
2058
|
return {
|
|
1952
2059
|
success: true,
|
|
@@ -2211,6 +2318,327 @@ export async function runDocker(context, executor, config) {
|
|
|
2211
2318
|
}
|
|
2212
2319
|
}
|
|
2213
2320
|
// =============================================================================
|
|
2321
|
+
// Orchestrator Docker Runner (Sibling Container Pattern)
|
|
2322
|
+
// =============================================================================
|
|
2323
|
+
/**
|
|
2324
|
+
* Run orchestrator in a Docker container using the sibling container pattern.
|
|
2325
|
+
*
|
|
2326
|
+
* Architecture:
|
|
2327
|
+
* ```
|
|
2328
|
+
* Host Docker daemon
|
|
2329
|
+
* ├── orchestrator container (has /var/run/docker.sock mounted)
|
|
2330
|
+
* ├── agent-1 container (spawned by orchestrator, sibling)
|
|
2331
|
+
* ├── agent-2 container (spawned by orchestrator, sibling)
|
|
2332
|
+
* ```
|
|
2333
|
+
*
|
|
2334
|
+
* The orchestrator container needs:
|
|
2335
|
+
* - HQ directory mounted (proletariat-hq)
|
|
2336
|
+
* - Docker socket mounted (/var/run/docker.sock) — so it can spawn agent containers as siblings
|
|
2337
|
+
* - prlt CLI installed in the container
|
|
2338
|
+
* - OAuth credentials for Claude Code (via Docker volume)
|
|
2339
|
+
* - tmux for session persistence inside the container
|
|
2340
|
+
*/
|
|
2341
|
+
export async function runOrchestratorInDocker(context, executor, config, options) {
|
|
2342
|
+
const displayMode = options?.displayMode || 'background';
|
|
2343
|
+
const hqPath = context.hqPath || context.worktreePath;
|
|
2344
|
+
const hqName = context.hqName || 'default';
|
|
2345
|
+
const orchestratorName = context.agentName || 'main';
|
|
2346
|
+
// Container name matches tmux session name for consistency
|
|
2347
|
+
const containerName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}-${(orchestratorName).replace(/[^a-zA-Z0-9._-]/g, '-')}`;
|
|
2348
|
+
const imageName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}:latest`;
|
|
2349
|
+
try {
|
|
2350
|
+
// Check Docker is running
|
|
2351
|
+
if (!isDockerRunning()) {
|
|
2352
|
+
return {
|
|
2353
|
+
success: false,
|
|
2354
|
+
error: 'Docker is not running. Please start Docker Desktop and try again.',
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
// Check if container already exists and is running
|
|
2358
|
+
if (containerExists(containerName)) {
|
|
2359
|
+
if (isContainerRunning(containerName)) {
|
|
2360
|
+
return {
|
|
2361
|
+
success: false,
|
|
2362
|
+
error: `Orchestrator container "${containerName}" is already running. Use "prlt orchestrator attach" to reattach.`,
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
// Remove stopped container
|
|
2366
|
+
try {
|
|
2367
|
+
execSync(`docker rm -f ${containerName}`, { stdio: 'pipe' });
|
|
2368
|
+
}
|
|
2369
|
+
catch {
|
|
2370
|
+
// Ignore removal errors
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
// Generate Dockerfile
|
|
2374
|
+
const orchestratorDockerOptions = {
|
|
2375
|
+
orchestratorName,
|
|
2376
|
+
hqPath,
|
|
2377
|
+
executor,
|
|
2378
|
+
};
|
|
2379
|
+
const dockerfileContent = generateOrchestratorDockerfile(orchestratorDockerOptions);
|
|
2380
|
+
// Write Dockerfile to temp directory
|
|
2381
|
+
const buildDir = path.join(hqPath, '.proletariat', 'orchestrator-docker');
|
|
2382
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
2383
|
+
const dockerfilePath = path.join(buildDir, 'Dockerfile');
|
|
2384
|
+
fs.writeFileSync(dockerfilePath, dockerfileContent);
|
|
2385
|
+
// Build the image
|
|
2386
|
+
const hostPrltVersion = getHostPrltVersion();
|
|
2387
|
+
const buildArgs = {
|
|
2388
|
+
PRLT_VERSION: hostPrltVersion || 'latest',
|
|
2389
|
+
};
|
|
2390
|
+
const buildArgFlags = Object.entries(buildArgs)
|
|
2391
|
+
.map(([key, value]) => `--build-arg ${key}="${value}"`)
|
|
2392
|
+
.join(' ');
|
|
2393
|
+
console.debug(`[runners:orchestrator-docker] Building image: ${imageName}`);
|
|
2394
|
+
try {
|
|
2395
|
+
execSync(`docker build -t ${imageName} -f "${dockerfilePath}" ${buildArgFlags} "${buildDir}"`, { stdio: 'pipe' });
|
|
2396
|
+
}
|
|
2397
|
+
catch (buildError) {
|
|
2398
|
+
return {
|
|
2399
|
+
success: false,
|
|
2400
|
+
error: `Failed to build orchestrator Docker image: ${buildError instanceof Error ? buildError.message : buildError}`,
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
// Build mount flags for docker run
|
|
2404
|
+
const mounts = [
|
|
2405
|
+
// Mount HQ directory
|
|
2406
|
+
`-v "${hqPath}:/hq:cached"`,
|
|
2407
|
+
// Docker socket for sibling container pattern
|
|
2408
|
+
`-v /var/run/docker.sock:/var/run/docker.sock`,
|
|
2409
|
+
// Claude credentials volume (shared with agent containers)
|
|
2410
|
+
...(executor === 'claude-code' ? ['-v "claude-credentials:/home/node/.claude"'] : []),
|
|
2411
|
+
// Persistent bash history
|
|
2412
|
+
'-v "claude-bash-history:/commandhistory"',
|
|
2413
|
+
];
|
|
2414
|
+
// Build environment variables
|
|
2415
|
+
const envVars = [
|
|
2416
|
+
`-e PRLT_HQ_PATH=/hq`,
|
|
2417
|
+
`-e PRLT_AGENT_NAME="orchestrator-${orchestratorName}"`,
|
|
2418
|
+
`-e PRLT_HOST_PATH="${hqPath}"`,
|
|
2419
|
+
// Pass through GitHub tokens for agent spawning
|
|
2420
|
+
...(process.env.GITHUB_TOKEN ? [`-e GITHUB_TOKEN="${process.env.GITHUB_TOKEN}"`] : []),
|
|
2421
|
+
...(process.env.GH_TOKEN ? [`-e GH_TOKEN="${process.env.GH_TOKEN}"`] : []),
|
|
2422
|
+
// Pass ANTHROPIC_API_KEY if available (for cases where OAuth is not set up)
|
|
2423
|
+
...(process.env.ANTHROPIC_API_KEY ? [`-e ANTHROPIC_API_KEY="${process.env.ANTHROPIC_API_KEY}"`] : []),
|
|
2424
|
+
];
|
|
2425
|
+
// Create and start container
|
|
2426
|
+
const createCmd = [
|
|
2427
|
+
'docker run -d',
|
|
2428
|
+
`--name ${containerName}`,
|
|
2429
|
+
'--user node',
|
|
2430
|
+
'-w /hq',
|
|
2431
|
+
...mounts,
|
|
2432
|
+
...envVars,
|
|
2433
|
+
`--memory=${config.devcontainer.memory}`,
|
|
2434
|
+
`--cpus=${config.devcontainer.cpus}`,
|
|
2435
|
+
imageName,
|
|
2436
|
+
'sleep infinity', // Keep container running
|
|
2437
|
+
].join(' ');
|
|
2438
|
+
console.debug(`[runners:orchestrator-docker] Creating container: ${createCmd}`);
|
|
2439
|
+
execSync(createCmd, { stdio: 'pipe' });
|
|
2440
|
+
const containerId = getContainerId(containerName);
|
|
2441
|
+
if (!containerId) {
|
|
2442
|
+
return {
|
|
2443
|
+
success: false,
|
|
2444
|
+
error: 'Failed to get container ID after creation',
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
// Fix Docker socket permissions inside the container
|
|
2448
|
+
// The socket is owned by root on the host; we need the node user to access it
|
|
2449
|
+
try {
|
|
2450
|
+
execSync(`docker exec --user root ${containerId} chmod 666 /var/run/docker.sock`, { stdio: 'pipe' });
|
|
2451
|
+
}
|
|
2452
|
+
catch {
|
|
2453
|
+
console.debug('[runners:orchestrator-docker] Failed to fix Docker socket permissions (may already be accessible)');
|
|
2454
|
+
}
|
|
2455
|
+
// Copy Claude Code settings to container (for bypassing prompts)
|
|
2456
|
+
if (executor === 'claude-code') {
|
|
2457
|
+
try {
|
|
2458
|
+
const hostClaudeJson = path.join(os.homedir(), '.claude.json');
|
|
2459
|
+
let settings = {};
|
|
2460
|
+
if (fs.existsSync(hostClaudeJson)) {
|
|
2461
|
+
try {
|
|
2462
|
+
settings = JSON.parse(fs.readFileSync(hostClaudeJson, 'utf-8'));
|
|
2463
|
+
}
|
|
2464
|
+
catch {
|
|
2465
|
+
// Use empty settings
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
if (config.permissionMode === 'danger') {
|
|
2469
|
+
settings.bypassPermissionsModeAccepted = true;
|
|
2470
|
+
}
|
|
2471
|
+
settings.numStartups = settings.numStartups || 1;
|
|
2472
|
+
settings.hasCompletedOnboarding = true;
|
|
2473
|
+
settings.theme = settings.theme || 'dark';
|
|
2474
|
+
if (!settings.tipsHistory || typeof settings.tipsHistory !== 'object') {
|
|
2475
|
+
settings.tipsHistory = {};
|
|
2476
|
+
}
|
|
2477
|
+
const tips = settings.tipsHistory;
|
|
2478
|
+
tips['new-user-warmup'] = tips['new-user-warmup'] || 1;
|
|
2479
|
+
settings.effortCalloutDismissed = true;
|
|
2480
|
+
if (!settings.projects || typeof settings.projects !== 'object') {
|
|
2481
|
+
settings.projects = {};
|
|
2482
|
+
}
|
|
2483
|
+
const projects = settings.projects;
|
|
2484
|
+
for (const projectPath of ['/hq', '/']) {
|
|
2485
|
+
if (!projects[projectPath])
|
|
2486
|
+
projects[projectPath] = {};
|
|
2487
|
+
projects[projectPath].hasTrustDialogAccepted = true;
|
|
2488
|
+
projects[projectPath].hasCompletedProjectOnboarding = true;
|
|
2489
|
+
}
|
|
2490
|
+
execSync(`docker exec -i ${containerId} bash -c 'cat > /home/node/.claude.json'`, { input: JSON.stringify(settings), stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2491
|
+
const claudeSettings = JSON.stringify({ skipDangerousModePermissionPrompt: true });
|
|
2492
|
+
execSync(`docker exec -i ${containerId} bash -c 'mkdir -p /home/node/.claude && cat > /home/node/.claude/settings.json'`, { input: claudeSettings, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2493
|
+
}
|
|
2494
|
+
catch (error) {
|
|
2495
|
+
console.debug('[runners:orchestrator-docker] Failed to copy Claude settings:', error);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
// Build the prompt and write to temp file inside container
|
|
2499
|
+
const prompt = buildPrompt(context);
|
|
2500
|
+
const promptPath = `/tmp/orchestrator-prompt-${Date.now()}.txt`;
|
|
2501
|
+
try {
|
|
2502
|
+
execSync(`docker exec -i ${containerId} bash -c 'cat > ${promptPath}'`, { input: prompt, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2503
|
+
}
|
|
2504
|
+
catch {
|
|
2505
|
+
return {
|
|
2506
|
+
success: false,
|
|
2507
|
+
error: 'Failed to write prompt to container',
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
// Build executor command
|
|
2511
|
+
const skipPermissions = config.permissionMode === 'danger';
|
|
2512
|
+
const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
|
|
2513
|
+
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
2514
|
+
const executorCmd = executor === 'claude-code'
|
|
2515
|
+
? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`
|
|
2516
|
+
: `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`;
|
|
2517
|
+
// Build tmux session name (reuses the same name as host tmux for consistency)
|
|
2518
|
+
const tmuxSessionName = options?.sessionName || containerName;
|
|
2519
|
+
// Create tmux session inside container with the executor command
|
|
2520
|
+
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'`;
|
|
2521
|
+
try {
|
|
2522
|
+
execSync(`docker exec ${containerId} bash -c '${tmuxCmd.replace(/'/g, "'\\''")}'`, { stdio: 'pipe' });
|
|
2523
|
+
}
|
|
2524
|
+
catch (tmuxError) {
|
|
2525
|
+
// Fallback: try simpler command without subshell
|
|
2526
|
+
console.debug('[runners:orchestrator-docker] tmux creation failed, trying simpler approach:', tmuxError);
|
|
2527
|
+
try {
|
|
2528
|
+
// Write a script inside the container
|
|
2529
|
+
const scriptContent = `#!/bin/bash
|
|
2530
|
+
cd /hq
|
|
2531
|
+
unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT
|
|
2532
|
+
${executor === 'claude-code' ? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"` : `claude "$(cat ${promptPath})"`}
|
|
2533
|
+
echo ""
|
|
2534
|
+
echo "Orchestrator complete. Press Enter to close."
|
|
2535
|
+
exec bash
|
|
2536
|
+
`;
|
|
2537
|
+
execSync(`docker exec -i ${containerId} bash -c 'cat > /tmp/orchestrator-start.sh && chmod +x /tmp/orchestrator-start.sh'`, { input: scriptContent, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
2538
|
+
execSync(`docker exec ${containerId} tmux new-session -d -s "${tmuxSessionName}" /tmp/orchestrator-start.sh`, { stdio: 'pipe' });
|
|
2539
|
+
}
|
|
2540
|
+
catch (fallbackError) {
|
|
2541
|
+
return {
|
|
2542
|
+
success: false,
|
|
2543
|
+
error: `Failed to create tmux session in container: ${fallbackError instanceof Error ? fallbackError.message : fallbackError}`,
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
// Handle display mode
|
|
2548
|
+
if (displayMode === 'foreground') {
|
|
2549
|
+
// Attach to tmux inside the container in current terminal
|
|
2550
|
+
try {
|
|
2551
|
+
const child = spawn('docker', ['exec', '-it', containerId, 'tmux', 'attach', '-t', tmuxSessionName], {
|
|
2552
|
+
stdio: 'inherit',
|
|
2553
|
+
});
|
|
2554
|
+
await new Promise((resolve) => {
|
|
2555
|
+
child.on('close', () => resolve());
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
catch {
|
|
2559
|
+
// User detached - that's fine
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
else if (displayMode === 'terminal' && process.platform === 'darwin') {
|
|
2563
|
+
// Open a new terminal tab that attaches to the container's tmux
|
|
2564
|
+
const baseDir = path.join(hqPath, '.proletariat', 'scripts');
|
|
2565
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
2566
|
+
const scriptPath = path.join(baseDir, `orch-docker-attach-${Date.now()}.sh`);
|
|
2567
|
+
const scriptContent = `#!/bin/bash
|
|
2568
|
+
echo -ne "\\033]0;Orchestrator (Docker)\\007"
|
|
2569
|
+
echo -ne "\\033]1;Orchestrator (Docker)\\007"
|
|
2570
|
+
docker exec -it ${containerId} tmux attach -t "${tmuxSessionName}"
|
|
2571
|
+
rm -f "${scriptPath}"
|
|
2572
|
+
exec $SHELL
|
|
2573
|
+
`;
|
|
2574
|
+
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
2575
|
+
const terminalApp = config.terminal.app;
|
|
2576
|
+
try {
|
|
2577
|
+
switch (terminalApp) {
|
|
2578
|
+
case 'iTerm':
|
|
2579
|
+
execSync(`osascript -e '
|
|
2580
|
+
tell application "iTerm"
|
|
2581
|
+
activate
|
|
2582
|
+
tell current window
|
|
2583
|
+
set newTab to (create tab with default profile)
|
|
2584
|
+
tell current session of newTab
|
|
2585
|
+
set name to "Orchestrator (Docker)"
|
|
2586
|
+
write text "${scriptPath}"
|
|
2587
|
+
end tell
|
|
2588
|
+
end tell
|
|
2589
|
+
end tell
|
|
2590
|
+
'`);
|
|
2591
|
+
break;
|
|
2592
|
+
case 'Ghostty':
|
|
2593
|
+
execSync(`osascript -e '
|
|
2594
|
+
tell application "Ghostty"
|
|
2595
|
+
activate
|
|
2596
|
+
end tell
|
|
2597
|
+
tell application "System Events"
|
|
2598
|
+
tell process "Ghostty"
|
|
2599
|
+
keystroke "t" using command down
|
|
2600
|
+
delay 0.3
|
|
2601
|
+
keystroke "${scriptPath}"
|
|
2602
|
+
keystroke return
|
|
2603
|
+
end tell
|
|
2604
|
+
end tell
|
|
2605
|
+
'`);
|
|
2606
|
+
break;
|
|
2607
|
+
default:
|
|
2608
|
+
execSync(`osascript -e '
|
|
2609
|
+
tell application "Terminal"
|
|
2610
|
+
activate
|
|
2611
|
+
tell application "System Events"
|
|
2612
|
+
tell process "Terminal"
|
|
2613
|
+
keystroke "t" using command down
|
|
2614
|
+
end tell
|
|
2615
|
+
end tell
|
|
2616
|
+
delay 0.3
|
|
2617
|
+
do script "${scriptPath}" in front window
|
|
2618
|
+
end tell
|
|
2619
|
+
'`);
|
|
2620
|
+
break;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
catch {
|
|
2624
|
+
console.debug('[runners:orchestrator-docker] Failed to open terminal tab, running in background');
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
// 'background' display mode: container is already running, nothing more to do
|
|
2628
|
+
return {
|
|
2629
|
+
success: true,
|
|
2630
|
+
containerId,
|
|
2631
|
+
sessionId: tmuxSessionName,
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
catch (error) {
|
|
2635
|
+
return {
|
|
2636
|
+
success: false,
|
|
2637
|
+
error: error instanceof Error ? error.message : 'Failed to start orchestrator in Docker',
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
// =============================================================================
|
|
2214
2642
|
// VM Runner
|
|
2215
2643
|
// =============================================================================
|
|
2216
2644
|
export async function runVm(context, executor, config, host) {
|