@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.
Files changed (233) hide show
  1. package/README.md +5 -4
  2. package/dist/commands/agent/cleanup.d.ts +1 -0
  3. package/dist/commands/agent/cleanup.js +11 -1
  4. package/dist/commands/agent/cleanup.js.map +1 -1
  5. package/dist/commands/agent/rebuild.js +1 -1
  6. package/dist/commands/agent/rebuild.js.map +1 -1
  7. package/dist/commands/asana/import.d.ts +14 -0
  8. package/dist/commands/asana/import.js +240 -0
  9. package/dist/commands/asana/import.js.map +1 -0
  10. package/dist/commands/claude-yolo.d.ts +20 -0
  11. package/dist/commands/claude-yolo.js +86 -0
  12. package/dist/commands/claude-yolo.js.map +1 -0
  13. package/dist/commands/codex/index.d.ts +23 -0
  14. package/dist/commands/codex/index.js +168 -0
  15. package/dist/commands/codex/index.js.map +1 -0
  16. package/dist/commands/codex-yolo.d.ts +20 -0
  17. package/dist/commands/codex-yolo.js +86 -0
  18. package/dist/commands/codex-yolo.js.map +1 -0
  19. package/dist/commands/config/index.js +2 -2
  20. package/dist/commands/config/index.js.map +1 -1
  21. package/dist/commands/docker/clean.js +1 -1
  22. package/dist/commands/docker/clean.js.map +1 -1
  23. package/dist/commands/docker/list.js +1 -1
  24. package/dist/commands/docker/list.js.map +1 -1
  25. package/dist/commands/docker/logs.js +1 -1
  26. package/dist/commands/docker/logs.js.map +1 -1
  27. package/dist/commands/docker/restart.js +1 -1
  28. package/dist/commands/docker/restart.js.map +1 -1
  29. package/dist/commands/docker/shell.js +1 -1
  30. package/dist/commands/docker/shell.js.map +1 -1
  31. package/dist/commands/docker/start.js +1 -1
  32. package/dist/commands/docker/start.js.map +1 -1
  33. package/dist/commands/docker/stop.js +1 -1
  34. package/dist/commands/docker/stop.js.map +1 -1
  35. package/dist/commands/docker/sync.js +1 -1
  36. package/dist/commands/docker/sync.js.map +1 -1
  37. package/dist/commands/execution/config.js +2 -2
  38. package/dist/commands/execution/config.js.map +1 -1
  39. package/dist/commands/execution/list.js +1 -1
  40. package/dist/commands/execution/list.js.map +1 -1
  41. package/dist/commands/execution/logs.js +10 -8
  42. package/dist/commands/execution/logs.js.map +1 -1
  43. package/dist/commands/execution/stop.js +1 -1
  44. package/dist/commands/execution/stop.js.map +1 -1
  45. package/dist/commands/execution/view.js +1 -1
  46. package/dist/commands/execution/view.js.map +1 -1
  47. package/dist/commands/init.d.ts +0 -16
  48. package/dist/commands/init.js +50 -160
  49. package/dist/commands/init.js.map +1 -1
  50. package/dist/commands/logs.d.ts +20 -0
  51. package/dist/commands/logs.js +84 -0
  52. package/dist/commands/logs.js.map +1 -0
  53. package/dist/commands/mcp-server.js +1 -1
  54. package/dist/commands/mcp-server.js.map +1 -1
  55. package/dist/commands/new.d.ts +27 -0
  56. package/dist/commands/new.js +184 -0
  57. package/dist/commands/new.js.map +1 -0
  58. package/dist/commands/orchestrator/attach.d.ts +1 -0
  59. package/dist/commands/orchestrator/attach.js +159 -26
  60. package/dist/commands/orchestrator/attach.js.map +1 -1
  61. package/dist/commands/orchestrator/start.d.ts +20 -0
  62. package/dist/commands/orchestrator/start.js +122 -14
  63. package/dist/commands/orchestrator/start.js.map +1 -1
  64. package/dist/commands/orchestrator/status.d.ts +4 -0
  65. package/dist/commands/orchestrator/status.js +70 -29
  66. package/dist/commands/orchestrator/status.js.map +1 -1
  67. package/dist/commands/orchestrator/stop.js +57 -23
  68. package/dist/commands/orchestrator/stop.js.map +1 -1
  69. package/dist/commands/peek.d.ts +20 -0
  70. package/dist/commands/peek.js +84 -0
  71. package/dist/commands/peek.js.map +1 -0
  72. package/dist/commands/poke.d.ts +20 -0
  73. package/dist/commands/poke.js +89 -0
  74. package/dist/commands/poke.js.map +1 -0
  75. package/dist/commands/pr/link.js +1 -1
  76. package/dist/commands/pr/link.js.map +1 -1
  77. package/dist/commands/pr/status.js +1 -1
  78. package/dist/commands/pr/status.js.map +1 -1
  79. package/dist/commands/ps.d.ts +18 -0
  80. package/dist/commands/ps.js +123 -0
  81. package/dist/commands/ps.js.map +1 -0
  82. package/dist/commands/qa/index.js +1 -1
  83. package/dist/commands/qa/index.js.map +1 -1
  84. package/dist/commands/repo/add.js +1 -1
  85. package/dist/commands/repo/add.js.map +1 -1
  86. package/dist/commands/repo/list.js +1 -1
  87. package/dist/commands/repo/list.js.map +1 -1
  88. package/dist/commands/repo/remove.js +1 -1
  89. package/dist/commands/repo/remove.js.map +1 -1
  90. package/dist/commands/repo/view.js +1 -1
  91. package/dist/commands/repo/view.js.map +1 -1
  92. package/dist/commands/run/index.d.ts +25 -0
  93. package/dist/commands/run/index.js +212 -0
  94. package/dist/commands/run/index.js.map +1 -0
  95. package/dist/commands/session/index.js +4 -0
  96. package/dist/commands/session/index.js.map +1 -1
  97. package/dist/commands/session/prune.d.ts +18 -0
  98. package/dist/commands/session/prune.js +327 -0
  99. package/dist/commands/session/prune.js.map +1 -0
  100. package/dist/commands/shortcut/connect.d.ts +15 -0
  101. package/dist/commands/shortcut/connect.js +210 -0
  102. package/dist/commands/shortcut/connect.js.map +1 -0
  103. package/dist/commands/stop.d.ts +19 -0
  104. package/dist/commands/stop.js +83 -0
  105. package/dist/commands/stop.js.map +1 -0
  106. package/dist/commands/work/asana.d.ts +23 -0
  107. package/dist/commands/work/asana.js +213 -0
  108. package/dist/commands/work/asana.js.map +1 -0
  109. package/dist/commands/work/complete.js +1 -1
  110. package/dist/commands/work/complete.js.map +1 -1
  111. package/dist/commands/work/ready.js +1 -1
  112. package/dist/commands/work/ready.js.map +1 -1
  113. package/dist/commands/work/review.js +1 -1
  114. package/dist/commands/work/review.js.map +1 -1
  115. package/dist/commands/work/revise.js +1 -1
  116. package/dist/commands/work/revise.js.map +1 -1
  117. package/dist/commands/work/shortcut.d.ts +23 -0
  118. package/dist/commands/work/shortcut.js +212 -0
  119. package/dist/commands/work/shortcut.js.map +1 -0
  120. package/dist/commands/work/spawn.d.ts +3 -0
  121. package/dist/commands/work/spawn.js +175 -2
  122. package/dist/commands/work/spawn.js.map +1 -1
  123. package/dist/commands/work/start.js +22 -30
  124. package/dist/commands/work/start.js.map +1 -1
  125. package/dist/commands/work/watch.js +1 -1
  126. package/dist/commands/work/watch.js.map +1 -1
  127. package/dist/commands/workspace/add.js +1 -1
  128. package/dist/commands/workspace/add.js.map +1 -1
  129. package/dist/commands/workspace/list.js +1 -1
  130. package/dist/commands/workspace/list.js.map +1 -1
  131. package/dist/hooks/init.d.ts +1 -1
  132. package/dist/hooks/init.js +6 -6
  133. package/dist/hooks/init.js.map +1 -1
  134. package/dist/lib/agent-naming.d.ts +15 -0
  135. package/dist/lib/agent-naming.js +32 -0
  136. package/dist/lib/agent-naming.js.map +1 -0
  137. package/dist/lib/agents/commands.d.ts +2 -0
  138. package/dist/lib/agents/commands.js +51 -44
  139. package/dist/lib/agents/commands.js.map +1 -1
  140. package/dist/lib/agents/index.js +5 -0
  141. package/dist/lib/agents/index.js.map +1 -1
  142. package/dist/lib/asana/client.d.ts +8 -0
  143. package/dist/lib/asana/client.js +26 -7
  144. package/dist/lib/asana/client.js.map +1 -1
  145. package/dist/lib/asana/types.d.ts +20 -0
  146. package/dist/lib/branch/index.d.ts +36 -0
  147. package/dist/lib/branch/index.js +129 -0
  148. package/dist/lib/branch/index.js.map +1 -1
  149. package/dist/lib/database/index.js +1 -1
  150. package/dist/lib/database/index.js.map +1 -1
  151. package/dist/lib/events/emitting-runner.d.ts +30 -0
  152. package/dist/lib/events/emitting-runner.js +95 -0
  153. package/dist/lib/events/emitting-runner.js.map +1 -0
  154. package/dist/lib/events/event-bus.d.ts +54 -0
  155. package/dist/lib/events/event-bus.js +107 -0
  156. package/dist/lib/events/event-bus.js.map +1 -0
  157. package/dist/lib/events/events.d.ts +67 -0
  158. package/dist/lib/events/events.js +8 -0
  159. package/dist/lib/events/events.js.map +1 -0
  160. package/dist/lib/events/index.d.ts +7 -0
  161. package/dist/lib/events/index.js +6 -0
  162. package/dist/lib/events/index.js.map +1 -0
  163. package/dist/lib/execution/devcontainer.d.ts +27 -0
  164. package/dist/lib/execution/devcontainer.js +80 -0
  165. package/dist/lib/execution/devcontainer.js.map +1 -1
  166. package/dist/lib/execution/runners.d.ts +28 -0
  167. package/dist/lib/execution/runners.js +459 -31
  168. package/dist/lib/execution/runners.js.map +1 -1
  169. package/dist/lib/execution/spawner.js +35 -17
  170. package/dist/lib/execution/spawner.js.map +1 -1
  171. package/dist/lib/execution/types.d.ts +1 -0
  172. package/dist/lib/execution/types.js.map +1 -1
  173. package/dist/lib/external-issues/adapters.d.ts +34 -0
  174. package/dist/lib/external-issues/adapters.js +191 -0
  175. package/dist/lib/external-issues/adapters.js.map +1 -1
  176. package/dist/lib/external-issues/asana.d.ts +75 -0
  177. package/dist/lib/external-issues/asana.js +243 -0
  178. package/dist/lib/external-issues/asana.js.map +1 -0
  179. package/dist/lib/external-issues/index.d.ts +4 -2
  180. package/dist/lib/external-issues/index.js +6 -2
  181. package/dist/lib/external-issues/index.js.map +1 -1
  182. package/dist/lib/external-issues/linear.js.map +1 -1
  183. package/dist/lib/external-issues/mapping-store.js +1 -1
  184. package/dist/lib/external-issues/shortcut.d.ts +86 -0
  185. package/dist/lib/external-issues/shortcut.js +274 -0
  186. package/dist/lib/external-issues/shortcut.js.map +1 -0
  187. package/dist/lib/external-issues/types.d.ts +3 -3
  188. package/dist/lib/external-issues/types.js +1 -1
  189. package/dist/lib/external-issues/types.js.map +1 -1
  190. package/dist/lib/mcp/tools/cli-passthrough.js +1 -1
  191. package/dist/lib/mcp/tools/cli-passthrough.js.map +1 -1
  192. package/dist/lib/mcp/tools/work.js +1 -1
  193. package/dist/lib/mcp/tools/work.js.map +1 -1
  194. package/dist/lib/pmo/index.d.ts +2 -2
  195. package/dist/lib/pmo/index.js +3 -3
  196. package/dist/lib/pmo/index.js.map +1 -1
  197. package/dist/lib/pmo/sync-manager.js +1 -1
  198. package/dist/lib/pmo/sync-manager.js.map +1 -1
  199. package/dist/lib/pmo/watcher.js +1 -1
  200. package/dist/lib/pmo/watcher.js.map +1 -1
  201. package/dist/lib/repos/index.js +1 -1
  202. package/dist/lib/repos/index.js.map +1 -1
  203. package/dist/lib/runners/agent-runner.d.ts +92 -0
  204. package/dist/lib/runners/agent-runner.js +9 -0
  205. package/dist/lib/runners/agent-runner.js.map +1 -0
  206. package/dist/lib/runners/claude-code-runner.d.ts +53 -0
  207. package/dist/lib/runners/claude-code-runner.js +132 -0
  208. package/dist/lib/runners/claude-code-runner.js.map +1 -0
  209. package/dist/lib/runners/index.d.ts +34 -0
  210. package/dist/lib/runners/index.js +97 -0
  211. package/dist/lib/runners/index.js.map +1 -0
  212. package/dist/lib/session-store.d.ts +70 -0
  213. package/dist/lib/session-store.js +167 -0
  214. package/dist/lib/session-store.js.map +1 -0
  215. package/dist/lib/shortcut/config.d.ts +44 -0
  216. package/dist/lib/shortcut/config.js +98 -0
  217. package/dist/lib/shortcut/config.js.map +1 -0
  218. package/dist/lib/shortcut/index.d.ts +7 -0
  219. package/dist/lib/shortcut/index.js +7 -0
  220. package/dist/lib/shortcut/index.js.map +1 -0
  221. package/dist/lib/work-source/client.d.ts +10 -0
  222. package/dist/lib/work-source/client.js +74 -0
  223. package/dist/lib/work-source/client.js.map +1 -0
  224. package/dist/lib/work-source/config.d.ts +1 -1
  225. package/dist/lib/work-source/config.js +17 -1
  226. package/dist/lib/work-source/config.js.map +1 -1
  227. package/dist/lib/work-source/index.d.ts +1 -0
  228. package/dist/lib/work-source/index.js +1 -0
  229. package/dist/lib/work-source/index.js.map +1 -1
  230. package/dist/lib/workspace.js +2 -2
  231. package/dist/lib/workspace.js.map +1 -1
  232. package/oclif.manifest.json +4291 -3248
  233. 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 a role-specific prompt for orchestrator sessions.
392
- * Unlike ticket workers, orchestrators are long-running managers that delegate work.
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 += `- View and manage work using \`prlt\` CLI commands or MCP tools\n`;
400
- prompt += `- Delegate tasks to agents do NOT implement work yourself\n`;
401
- prompt += `- Monitor agent progress, review completed work, and manage the board\n`;
402
- prompt += `- Spawn agents for Ready tickets, review PRs, merge completed work\n\n`;
403
- prompt += `## Available Tools\n`;
404
- prompt += `- Board: \`prlt board\`, \`prlt ticket list\`, \`prlt ticket show <id>\`, \`prlt work status\`\n`;
405
- prompt += `- Agents: \`prlt work start <ticket-id>\`, \`prlt session list\`, \`prlt session peek <session>\`, \`prlt session poke <session> "message"\`\n`;
406
- prompt += `- PRs: \`gh pr list\`, \`gh pr view\`, \`gh pr merge\`\n`;
407
- prompt += `- MCP: All prlt MCP tools are available\n\n`;
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
- // Write prompt to separate file to avoid any shell escaping issues
570
- fs.writeFileSync(promptPath, prompt, { mode: 0o644 });
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
- executorInvocation = `${cmd} ${permissionsFlag}${effortFlag}${printFlag}"$(cat "$PROMPT_PATH")"`;
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
- // Use -CC for iTerm when control mode is enabled
644
- const fgTmuxAttach = buildTmuxAttachCommand(useControlMode);
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
- # Keep shell open after completion
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
- echo ""
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
- // Use -CC for iTerm when control mode is enabled
1949
- const fgTmuxAttach = buildTmuxAttachCommand(useControlMode, true);
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) {