@proletariat/cli 0.3.58 → 0.3.60

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 (212) hide show
  1. package/dist/commands/{spec → dashboard}/index.d.ts +4 -4
  2. package/dist/commands/dashboard/index.js +117 -0
  3. package/dist/commands/dashboard/index.js.map +1 -0
  4. package/dist/commands/execution/config.js +5 -4
  5. package/dist/commands/execution/config.js.map +1 -1
  6. package/dist/commands/execution/stop.js +4 -2
  7. package/dist/commands/execution/stop.js.map +1 -1
  8. package/dist/commands/execution/view.js +3 -0
  9. package/dist/commands/execution/view.js.map +1 -1
  10. package/dist/commands/init.d.ts +1 -0
  11. package/dist/commands/init.js +40 -3
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/mcp-server.js +1 -2
  14. package/dist/commands/mcp-server.js.map +1 -1
  15. package/dist/commands/{spec/link/index.d.ts → session/exec.d.ts} +5 -3
  16. package/dist/commands/session/exec.js +205 -0
  17. package/dist/commands/session/exec.js.map +1 -0
  18. package/dist/commands/session/index.js +12 -0
  19. package/dist/commands/session/index.js.map +1 -1
  20. package/dist/commands/{spec/delete.d.ts → session/inspect.d.ts} +3 -3
  21. package/dist/commands/session/inspect.js +316 -0
  22. package/dist/commands/session/inspect.js.map +1 -0
  23. package/dist/commands/session/peek.d.ts +15 -0
  24. package/dist/commands/session/peek.js +141 -8
  25. package/dist/commands/session/peek.js.map +1 -1
  26. package/dist/commands/session/poke.d.ts +4 -1
  27. package/dist/commands/session/poke.js +175 -20
  28. package/dist/commands/session/poke.js.map +1 -1
  29. package/dist/commands/{spec/link/depends.d.ts → session/restart.d.ts} +6 -4
  30. package/dist/commands/session/restart.js +320 -0
  31. package/dist/commands/session/restart.js.map +1 -0
  32. package/dist/commands/ticket/list.d.ts +10 -0
  33. package/dist/commands/ticket/list.js +126 -1
  34. package/dist/commands/ticket/list.js.map +1 -1
  35. package/dist/commands/tools/add.d.ts +20 -0
  36. package/dist/commands/tools/add.js +129 -0
  37. package/dist/commands/tools/add.js.map +1 -0
  38. package/dist/commands/tools/check.d.ts +10 -0
  39. package/dist/commands/tools/check.js +75 -0
  40. package/dist/commands/tools/check.js.map +1 -0
  41. package/dist/commands/tools/detect.d.ts +11 -0
  42. package/dist/commands/tools/detect.js +107 -0
  43. package/dist/commands/tools/detect.js.map +1 -0
  44. package/dist/commands/tools/index.d.ts +11 -0
  45. package/dist/commands/tools/index.js +87 -0
  46. package/dist/commands/tools/index.js.map +1 -0
  47. package/dist/commands/tools/remove.d.ts +13 -0
  48. package/dist/commands/tools/remove.js +55 -0
  49. package/dist/commands/tools/remove.js.map +1 -0
  50. package/dist/commands/{spec/view.d.ts → trello/configure.d.ts} +6 -6
  51. package/dist/commands/trello/configure.js +259 -0
  52. package/dist/commands/trello/configure.js.map +1 -0
  53. package/dist/commands/{spec/plan.d.ts → trello/import.d.ts} +3 -5
  54. package/dist/commands/trello/import.js +241 -0
  55. package/dist/commands/trello/import.js.map +1 -0
  56. package/dist/commands/{spec/ticket.d.ts → trello/sync.d.ts} +5 -6
  57. package/dist/commands/trello/sync.js +190 -0
  58. package/dist/commands/trello/sync.js.map +1 -0
  59. package/dist/commands/work/start.d.ts +1 -0
  60. package/dist/commands/work/start.js +17 -39
  61. package/dist/commands/work/start.js.map +1 -1
  62. package/dist/lib/dashboard/data.d.ts +64 -0
  63. package/dist/lib/dashboard/data.js +259 -0
  64. package/dist/lib/dashboard/data.js.map +1 -0
  65. package/dist/lib/dashboard/html.d.ts +7 -0
  66. package/dist/lib/dashboard/html.js +682 -0
  67. package/dist/lib/dashboard/html.js.map +1 -0
  68. package/dist/lib/dashboard/server.d.ts +20 -0
  69. package/dist/lib/dashboard/server.js +114 -0
  70. package/dist/lib/dashboard/server.js.map +1 -0
  71. package/dist/lib/events/events.d.ts +26 -2
  72. package/dist/lib/events/events.js +3 -2
  73. package/dist/lib/events/events.js.map +1 -1
  74. package/dist/lib/execution/config.d.ts +8 -0
  75. package/dist/lib/execution/config.js +83 -4
  76. package/dist/lib/execution/config.js.map +1 -1
  77. package/dist/lib/execution/runners.d.ts +36 -3
  78. package/dist/lib/execution/runners.js +216 -35
  79. package/dist/lib/execution/runners.js.map +1 -1
  80. package/dist/lib/execution/spawner.d.ts +2 -0
  81. package/dist/lib/execution/spawner.js +22 -31
  82. package/dist/lib/execution/spawner.js.map +1 -1
  83. package/dist/lib/execution/types.d.ts +26 -5
  84. package/dist/lib/execution/types.js +24 -0
  85. package/dist/lib/execution/types.js.map +1 -1
  86. package/dist/lib/external-issues/adapters.d.ts +17 -0
  87. package/dist/lib/external-issues/adapters.js +88 -0
  88. package/dist/lib/external-issues/adapters.js.map +1 -1
  89. package/dist/lib/external-issues/index.d.ts +1 -0
  90. package/dist/lib/external-issues/index.js +2 -0
  91. package/dist/lib/external-issues/index.js.map +1 -1
  92. package/dist/lib/external-issues/mapping-store.js +1 -1
  93. package/dist/lib/external-issues/outbound-sync.d.ts +79 -0
  94. package/dist/lib/external-issues/outbound-sync.js +296 -0
  95. package/dist/lib/external-issues/outbound-sync.js.map +1 -0
  96. package/dist/lib/external-issues/trello.d.ts +80 -0
  97. package/dist/lib/external-issues/trello.js +266 -0
  98. package/dist/lib/external-issues/trello.js.map +1 -0
  99. package/dist/lib/external-issues/types.d.ts +3 -3
  100. package/dist/lib/external-issues/types.js +1 -1
  101. package/dist/lib/external-issues/types.js.map +1 -1
  102. package/dist/lib/linear/client.d.ts +4 -3
  103. package/dist/lib/linear/client.js +185 -122
  104. package/dist/lib/linear/client.js.map +1 -1
  105. package/dist/lib/mcp/tools/cli-passthrough.js +77 -0
  106. package/dist/lib/mcp/tools/cli-passthrough.js.map +1 -1
  107. package/dist/lib/mcp/tools/index.d.ts +0 -1
  108. package/dist/lib/mcp/tools/index.js +0 -1
  109. package/dist/lib/mcp/tools/index.js.map +1 -1
  110. package/dist/lib/onboarding/detect-tools.d.ts +15 -0
  111. package/dist/lib/onboarding/detect-tools.js +44 -0
  112. package/dist/lib/onboarding/detect-tools.js.map +1 -0
  113. package/dist/lib/onboarding/index.d.ts +2 -0
  114. package/dist/lib/onboarding/index.js +3 -0
  115. package/dist/lib/onboarding/index.js.map +1 -0
  116. package/dist/lib/onboarding/wizard.d.ts +25 -0
  117. package/dist/lib/onboarding/wizard.js +156 -0
  118. package/dist/lib/onboarding/wizard.js.map +1 -0
  119. package/dist/lib/pmo/schema.d.ts +2 -1
  120. package/dist/lib/pmo/schema.js +3 -1
  121. package/dist/lib/pmo/schema.js.map +1 -1
  122. package/dist/lib/pmo/storage/tickets.d.ts +8 -0
  123. package/dist/lib/pmo/storage/tickets.js +56 -2
  124. package/dist/lib/pmo/storage/tickets.js.map +1 -1
  125. package/dist/lib/pmo/sync-manager.js +3 -0
  126. package/dist/lib/pmo/sync-manager.js.map +1 -1
  127. package/dist/lib/runners/claude-code-runner.js +6 -0
  128. package/dist/lib/runners/claude-code-runner.js.map +1 -1
  129. package/dist/lib/tool-registry/detect.d.ts +20 -0
  130. package/dist/lib/tool-registry/detect.js +95 -0
  131. package/dist/lib/tool-registry/detect.js.map +1 -0
  132. package/dist/lib/tool-registry/index.d.ts +10 -0
  133. package/dist/lib/tool-registry/index.js +13 -0
  134. package/dist/lib/tool-registry/index.js.map +1 -0
  135. package/dist/lib/tool-registry/policy.d.ts +32 -0
  136. package/dist/lib/tool-registry/policy.js +97 -0
  137. package/dist/lib/tool-registry/policy.js.map +1 -0
  138. package/dist/lib/tool-registry/registry.d.ts +42 -0
  139. package/dist/lib/tool-registry/registry.js +120 -0
  140. package/dist/lib/tool-registry/registry.js.map +1 -0
  141. package/dist/lib/tool-registry/spawn.d.ts +50 -0
  142. package/dist/lib/tool-registry/spawn.js +103 -0
  143. package/dist/lib/tool-registry/spawn.js.map +1 -0
  144. package/dist/lib/tool-registry/types.d.ts +56 -0
  145. package/dist/lib/tool-registry/types.js +109 -0
  146. package/dist/lib/tool-registry/types.js.map +1 -0
  147. package/dist/lib/trello/client.d.ts +23 -0
  148. package/dist/lib/trello/client.js +114 -0
  149. package/dist/lib/trello/client.js.map +1 -0
  150. package/dist/lib/trello/config.d.ts +55 -0
  151. package/dist/lib/trello/config.js +127 -0
  152. package/dist/lib/trello/config.js.map +1 -0
  153. package/dist/lib/trello/index.d.ts +5 -0
  154. package/dist/lib/trello/index.js +5 -0
  155. package/dist/lib/trello/index.js.map +1 -0
  156. package/dist/lib/trello/mapper.d.ts +13 -0
  157. package/dist/lib/trello/mapper.js +71 -0
  158. package/dist/lib/trello/mapper.js.map +1 -0
  159. package/dist/lib/trello/sync.d.ts +13 -0
  160. package/dist/lib/trello/sync.js +38 -0
  161. package/dist/lib/trello/sync.js.map +1 -0
  162. package/dist/lib/trello/types.d.ts +53 -0
  163. package/dist/lib/trello/types.js +2 -0
  164. package/dist/lib/trello/types.js.map +1 -0
  165. package/dist/lib/work-source/client.js +17 -0
  166. package/dist/lib/work-source/client.js.map +1 -1
  167. package/dist/lib/work-source/config.d.ts +1 -1
  168. package/dist/lib/work-source/config.js +23 -1
  169. package/dist/lib/work-source/config.js.map +1 -1
  170. package/dist/lib/work-source/index.d.ts +1 -0
  171. package/dist/lib/work-source/index.js +1 -0
  172. package/dist/lib/work-source/index.js.map +1 -1
  173. package/dist/lib/work-source/provider-sources.d.ts +113 -0
  174. package/dist/lib/work-source/provider-sources.js +227 -0
  175. package/dist/lib/work-source/provider-sources.js.map +1 -0
  176. package/oclif.manifest.json +4368 -4364
  177. package/package.json +6 -2
  178. package/dist/commands/spec/create.d.ts +0 -20
  179. package/dist/commands/spec/create.js +0 -171
  180. package/dist/commands/spec/create.js.map +0 -1
  181. package/dist/commands/spec/delete.js +0 -112
  182. package/dist/commands/spec/delete.js.map +0 -1
  183. package/dist/commands/spec/edit.d.ts +0 -23
  184. package/dist/commands/spec/edit.js +0 -262
  185. package/dist/commands/spec/edit.js.map +0 -1
  186. package/dist/commands/spec/index.js +0 -88
  187. package/dist/commands/spec/index.js.map +0 -1
  188. package/dist/commands/spec/link/depends.js +0 -87
  189. package/dist/commands/spec/link/depends.js.map +0 -1
  190. package/dist/commands/spec/link/index.js +0 -93
  191. package/dist/commands/spec/link/index.js.map +0 -1
  192. package/dist/commands/spec/link/remove.d.ts +0 -18
  193. package/dist/commands/spec/link/remove.js +0 -91
  194. package/dist/commands/spec/link/remove.js.map +0 -1
  195. package/dist/commands/spec/list.d.ts +0 -14
  196. package/dist/commands/spec/list.js +0 -101
  197. package/dist/commands/spec/list.js.map +0 -1
  198. package/dist/commands/spec/plan.js +0 -102
  199. package/dist/commands/spec/plan.js.map +0 -1
  200. package/dist/commands/spec/ticket.js +0 -144
  201. package/dist/commands/spec/ticket.js.map +0 -1
  202. package/dist/commands/spec/view.js +0 -202
  203. package/dist/commands/spec/view.js.map +0 -1
  204. package/dist/lib/mcp/tools/spec.d.ts +0 -6
  205. package/dist/lib/mcp/tools/spec.js +0 -197
  206. package/dist/lib/mcp/tools/spec.js.map +0 -1
  207. package/dist/lib/pmo/spec-parser.d.ts +0 -25
  208. package/dist/lib/pmo/spec-parser.js +0 -206
  209. package/dist/lib/pmo/spec-parser.js.map +0 -1
  210. package/dist/lib/pmo/spec-types.d.ts +0 -43
  211. package/dist/lib/pmo/spec-types.js +0 -8
  212. package/dist/lib/pmo/spec-types.js.map +0 -1
@@ -2,17 +2,18 @@
2
2
  /**
3
3
  * Execution Runners
4
4
  *
5
- * Implementations for each execution environment (devcontainer, host, docker, vm).
5
+ * Implementations for each execution environment (host, sandbox, devcontainer, docker, cloud).
6
6
  */
7
7
  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
11
  import { fileURLToPath } from 'node:url';
12
- import { DEFAULT_EXECUTION_CONFIG, } from './types.js';
12
+ import { DEFAULT_EXECUTION_CONFIG, normalizeEnvironment, } from './types.js';
13
13
  import { getSetTitleCommands } from '../terminal.js';
14
14
  import { readDevcontainerJson, generateOrchestratorDockerfile } from './devcontainer.js';
15
15
  import { getCodexCommand, resolveCodexExecutionContext, validateCodexMode } from './codex-adapter.js';
16
+ import { resolveToolsForSpawn } from '../tool-registry/index.js';
16
17
  // =============================================================================
17
18
  // Terminal Title Helpers
18
19
  // =============================================================================
@@ -380,10 +381,11 @@ export function checkExecutorInContainer(executor, containerId) {
380
381
  * Run executor preflight checks for the target environment.
381
382
  */
382
383
  export function runExecutorPreflight(environment, executor, options) {
383
- if (environment === 'host') {
384
+ const env = normalizeEnvironment(environment);
385
+ if (env === 'host' || env === 'sandbox') {
384
386
  return checkExecutorOnHost(executor);
385
387
  }
386
- if (environment === 'devcontainer' && options?.containerId) {
388
+ if (env === 'devcontainer' && options?.containerId) {
387
389
  return checkExecutorInContainer(executor, options.containerId);
388
390
  }
389
391
  return { ok: true };
@@ -555,6 +557,18 @@ function buildOrchestratorAntiPatterns() {
555
557
  */
556
558
  function buildOrchestratorBody(hqName, context) {
557
559
  let prompt = '';
560
+ // Dynamic workspace context
561
+ const prltVersion = getHostPrltVersion();
562
+ prompt += `## Environment\n`;
563
+ if (prltVersion) {
564
+ prompt += `- **prlt version**: ${prltVersion}\n`;
565
+ }
566
+ prompt += `- **Available executors**: claude-code, codex\n`;
567
+ prompt += `- **Agent worktrees**: \`agents/temp/<agent-name>/<repo>\` — each agent gets an isolated git worktree\n`;
568
+ if (context.hqPath) {
569
+ prompt += `- **HQ path**: \`${context.hqPath}\`\n`;
570
+ }
571
+ prompt += `\n`;
558
572
  // Runtime declaration
559
573
  prompt += `## prlt Is Your Orchestration Runtime\n\n`;
560
574
  prompt += `prlt is your orchestration runtime. NEVER use raw docker exec, tmux send-keys, or direct container access. `;
@@ -563,10 +577,12 @@ function buildOrchestratorBody(hqName, context) {
563
577
  prompt += `health monitoring, and creates orphaned processes.\n\n`;
564
578
  // Role
565
579
  prompt += `## Your Role\n`;
566
- prompt += `- Plan and prioritize work across the board\n`;
580
+ prompt += `- Assess the current state of the board, running agents, and open PRs\n`;
581
+ prompt += `- Plan and prioritize work — decide what to tackle next and in what order\n`;
567
582
  prompt += `- Delegate implementation to agents via \`prlt work start\`\n`;
568
- prompt += `- Monitor agent progress and review completed work\n`;
569
- prompt += `- Merge completed PRs via \`gh pr merge --squash\`\n`;
583
+ prompt += `- Monitor agent progress via sessions and review completed work\n`;
584
+ prompt += `- Review and merge completed PRs via \`gh pr merge --squash\`\n`;
585
+ prompt += `- Coordinate parallel agents — handle rebases after merges\n`;
570
586
  prompt += `- Never write code or make changes to source files yourself\n\n`;
571
587
  // Command reference (dynamically generated)
572
588
  prompt += `## Command Reference\n\n`;
@@ -587,6 +603,13 @@ function buildOrchestratorBody(hqName, context) {
587
603
  prompt += `- Squash merge only: \`gh pr merge --squash\`\n`;
588
604
  prompt += `- After merging: subsequent PRs from parallel agents will need rebase\n`;
589
605
  prompt += `- Kill stale sessions after their PRs are merged\n\n`;
606
+ // Tool registry (TKT-083): inject available tools into orchestrator prompt
607
+ if (context.hqPath) {
608
+ const toolsResult = resolveToolsForSpawn(context.hqPath, context.toolPolicy, path.join(context.hqPath, '.proletariat', 'scripts'));
609
+ if (toolsResult.promptSection) {
610
+ prompt += toolsResult.promptSection;
611
+ }
612
+ }
590
613
  // Load .orchestrator-context.md from HQ root if it exists
591
614
  if (context.hqPath) {
592
615
  const contextFilePath = path.join(context.hqPath, '.orchestrator-context.md');
@@ -611,9 +634,12 @@ function buildOrchestratorBody(hqName, context) {
611
634
  */
612
635
  export function buildOrchestratorSystemPrompt(context) {
613
636
  const hqName = context.hqName || 'workspace';
614
- let prompt = `You are an orchestrator for the **${hqName}** project. `;
615
- prompt += `Do not implement any work yourself. `;
616
- prompt += `Your job is to review, plan, investigate, delegate (via \`prlt work start\`), and review completed work.\n\n`;
637
+ let prompt = `# Orchestrator: ${hqName}\n\n`;
638
+ prompt += `You are the orchestrator for the **${hqName}** headquarters — a technical project manager driving software delivery through delegated AI agents.\n\n`;
639
+ prompt += `**prlt** is an AI agent orchestration CLI. It manages software development by coordinating autonomous coding agents that work in isolated git worktrees. `;
640
+ prompt += `Your workspace (HQ) contains a PMO board for tracking tickets, agent worktrees under \`agents/temp/\`, and repo connections. `;
641
+ prompt += `Agents are spawned to implement, review, and fix code — you never write code yourself. `;
642
+ prompt += `Your job is to assess the state of the project, plan and prioritize work, delegate to agents, monitor their progress, review results, and merge completed PRs.\n\n`;
617
643
  prompt += buildOrchestratorBody(hqName, context);
618
644
  return prompt;
619
645
  }
@@ -623,7 +649,10 @@ function buildOrchestratorPrompt(context) {
623
649
  // a system prompt (role/tools) + a shorter user message.
624
650
  const hqName = context.hqName || 'workspace';
625
651
  let prompt = `# Orchestrator: ${hqName}\n\n`;
626
- prompt += `You are the orchestrator for the **${hqName}** workspace using the prlt ecosystem.\n\n`;
652
+ prompt += `You are the orchestrator for the **${hqName}** headquarters a technical project manager driving software delivery through delegated AI agents.\n\n`;
653
+ prompt += `**prlt** is an AI agent orchestration CLI. It manages software development by coordinating autonomous coding agents that work in isolated git worktrees. `;
654
+ prompt += `Your workspace (HQ) contains a PMO board for tracking tickets, agent worktrees under \`agents/temp/\`, and repo connections. `;
655
+ prompt += `Agents are spawned to implement, review, and fix code — you never write code yourself.\n\n`;
627
656
  prompt += buildOrchestratorBody(hqName, context);
628
657
  // Include user's custom prompt or action content
629
658
  if (context.actionPrompt) {
@@ -662,9 +691,6 @@ function buildPrompt(context) {
662
691
  if (context.epicTitle) {
663
692
  prompt += `**Epic:** ${context.epicTitle}\n`;
664
693
  }
665
- if (context.specId) {
666
- prompt += `**Spec:** ${context.specId}${context.specTitle ? ` - ${context.specTitle}` : ''}\n`;
667
- }
668
694
  if (context.ticketDescription) {
669
695
  prompt += `\n## Description\n\n${context.ticketDescription}\n`;
670
696
  }
@@ -686,6 +712,13 @@ function buildPrompt(context) {
686
712
  if (context.customMessage) {
687
713
  prompt += `\n## Additional Instructions\n\n${context.customMessage}\n`;
688
714
  }
715
+ // Tool registry (TKT-083): inject available tools into agent prompt
716
+ if (context.hqPath) {
717
+ const toolsResult = resolveToolsForSpawn(context.hqPath, context.toolPolicy, path.join(context.hqPath, '.proletariat', 'scripts'));
718
+ if (toolsResult.promptSection) {
719
+ prompt += `\n${toolsResult.promptSection}`;
720
+ }
721
+ }
689
722
  // END HOOK - Action-specific completion instructions
690
723
  prompt += `\n---\n\n## When Complete\n\n`;
691
724
  // For revisions, use the revision-specific end prompt
@@ -787,13 +820,23 @@ export async function runHost(context, executor, config, displayMode = 'terminal
787
820
  fs.writeFileSync(systemPromptPath, systemPrompt, { mode: 0o644 });
788
821
  // Override user message: just action instructions or a default startup message
789
822
  const userMessage = context.actionPrompt
790
- || 'You are now running as the orchestrator. Check the board status and report what you see.';
823
+ || 'Assess the current state of the project:\n'
824
+ + '1. Check the board: `prlt board view` — what tickets are in progress, blocked, or ready?\n'
825
+ + '2. List running agents: `prlt session list` — who is working on what? Any stale sessions?\n'
826
+ + '3. Check open PRs: `gh pr list` — any PRs ready for review or merge?\n'
827
+ + '4. Summarize what needs attention and recommend next actions.';
791
828
  fs.writeFileSync(promptPath, userMessage, { mode: 0o644 });
792
829
  }
793
830
  else {
794
831
  // Write full prompt (includes role context for non-Claude executors)
795
832
  fs.writeFileSync(promptPath, prompt, { mode: 0o644 });
796
833
  }
834
+ // Tool registry (TKT-083): generate MCP config for Claude Code
835
+ let mcpConfigPath = null;
836
+ if (context.hqPath && isClaudeExecutor(executor)) {
837
+ const toolsResult = resolveToolsForSpawn(context.hqPath, context.toolPolicy, baseDir);
838
+ mcpConfigPath = toolsResult.mcpConfigPath;
839
+ }
797
840
  // Build the executor command using getExecutorCommand() output
798
841
  // For Claude Code, we also support outputMode and additional flags
799
842
  // For Codex, we use the codex adapter for deterministic command building (TKT-080)
@@ -811,7 +854,9 @@ export async function runHost(context, executor, config, displayMode = 'terminal
811
854
  // TKT-053: Disable plan mode for background agents — prevents silent stalls
812
855
  // when there's no user to approve the plan mode transition
813
856
  const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
814
- executorInvocation = `${cmd} ${permissionsFlag}${effortFlag}${printFlag}${disallowPlanFlag}${systemPromptFlag}"$(cat "$PROMPT_PATH")"`;
857
+ // Tool registry (TKT-083): pass MCP config to Claude Code via --mcp-config flag
858
+ const mcpConfigFlag = mcpConfigPath ? `--mcp-config "${mcpConfigPath}" ` : '';
859
+ executorInvocation = `${cmd} ${permissionsFlag}${effortFlag}${printFlag}${disallowPlanFlag}${systemPromptFlag}${mcpConfigFlag}"$(cat "$PROMPT_PATH")"`;
815
860
  }
816
861
  else if (executor === 'codex') {
817
862
  // TKT-080: Use Codex adapter for deterministic command building.
@@ -845,16 +890,25 @@ echo ""
845
890
  echo "✅ Agent work complete. Press Enter to close or run more commands."
846
891
  exec $SHELL
847
892
  `;
893
+ // Wrap with srt sandbox if running in sandbox environment
894
+ let finalInvocation = executorInvocation;
895
+ if (context.executionEnvironment === 'sandbox') {
896
+ // Build the srt wrapper command
897
+ // The inner command is the executor invocation that reads from PROMPT_PATH
898
+ const srtCmd = buildSrtCommand(`bash -c '${executorInvocation.replace(/'/g, "'\\''")}'`, context, config);
899
+ finalInvocation = srtCmd;
900
+ }
848
901
  const scriptContent = `#!/bin/bash
849
902
  # Auto-generated script for ticket ${context.ticketId}
850
903
  SCRIPT_PATH="${scriptPath}"
851
904
  PROMPT_PATH="${promptPath}"${systemPromptVar}
852
905
  ${setTitleCmds}
853
906
  echo "🚀 Starting: ${sessionName}"
907
+ ${context.executionEnvironment === 'sandbox' ? 'echo "🔒 Running in srt sandbox (filesystem + network isolation)"' : ''}
854
908
  echo ""
855
909
  cd "${context.worktreePath}"
856
910
  # Run executor in subshell with CLAUDECODE unset (prevents nested session error)
857
- (unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT; ${executorInvocation})
911
+ (unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT; ${finalInvocation})
858
912
 
859
913
  # Clean up script and prompt files
860
914
  rm -f "$SCRIPT_PATH" "$PROMPT_PATH"${systemPromptPath ? ' "$SYSTEM_PROMPT_PATH"' : ''}
@@ -1695,7 +1749,7 @@ function writePromptFile(context) {
1695
1749
  * Uses docker exec for direct container access.
1696
1750
  * Uses a prompt file to avoid shell escaping issues.
1697
1751
  */
1698
- export function buildDevcontainerCommand(context, executor, promptFile, containerId, outputMode = 'interactive', permissionMode = 'safe', displayMode = 'terminal') {
1752
+ export function buildDevcontainerCommand(context, executor, promptFile, containerId, outputMode = 'interactive', permissionMode = 'safe', displayMode = 'terminal', mcpConfigFile) {
1699
1753
  // Calculate the relative path from agentDir to worktreePath for cd
1700
1754
  const relativePath = path.relative(context.agentDir, context.worktreePath);
1701
1755
  const cdCmd = relativePath ? `cd /workspace/${relativePath} && ` : '';
@@ -1715,7 +1769,9 @@ export function buildDevcontainerCommand(context, executor, promptFile, containe
1715
1769
  const effortFlag = '--effort high ';
1716
1770
  // TKT-053: Disable plan mode for background agents — prevents silent stalls
1717
1771
  const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
1718
- executorCmd = `claude ${bypassTrustFlag}${permissionsFlag}${effortFlag}${printFlag}${disallowPlanFlag}"$(cat ${promptFile})"`;
1772
+ // Tool registry (TKT-083): pass MCP config to Claude Code via --mcp-config flag
1773
+ const mcpConfigFlag = mcpConfigFile ? `--mcp-config ${mcpConfigFile} ` : '';
1774
+ executorCmd = `claude ${bypassTrustFlag}${permissionsFlag}${effortFlag}${printFlag}${disallowPlanFlag}${mcpConfigFlag}"$(cat ${promptFile})"`;
1719
1775
  }
1720
1776
  else if (executor === 'codex') {
1721
1777
  // Use Codex adapter for mode validation and deterministic command building.
@@ -1806,6 +1862,16 @@ export async function runDevcontainer(context, executor, config, displayMode = '
1806
1862
  }
1807
1863
  // Write prompt to file in worktree (accessible by container)
1808
1864
  const { hostPath: promptHostPath, containerPath: promptFile } = writePromptFile(context);
1865
+ // Tool registry (TKT-083): generate MCP config file for container
1866
+ let mcpConfigContainerPath;
1867
+ if (context.hqPath && isClaudeExecutor(executor)) {
1868
+ const toolsResult = resolveToolsForSpawn(context.hqPath, context.toolPolicy, context.worktreePath);
1869
+ if (toolsResult.mcpConfigPath) {
1870
+ // Map host path to container path
1871
+ const relativeMcp = path.relative(context.agentDir, toolsResult.mcpConfigPath);
1872
+ mcpConfigContainerPath = `/workspace/${relativeMcp}`;
1873
+ }
1874
+ }
1809
1875
  // Inject fresh GitHub token into container (containers may be reused with stale/empty tokens)
1810
1876
  // This ensures git push works even if the container was created before token was available
1811
1877
  const githubToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
@@ -1822,7 +1888,7 @@ export async function runDevcontainer(context, executor, config, displayMode = '
1822
1888
  }
1823
1889
  // Build the docker exec command (just runs claude directly)
1824
1890
  // tmux session setup is handled by runDevcontainerInTmux, not buildDevcontainerCommand
1825
- const devcontainerCmd = buildDevcontainerCommand(context, executor, promptFile, containerId, config.outputMode, config.permissionMode, displayMode);
1891
+ const devcontainerCmd = buildDevcontainerCommand(context, executor, promptFile, containerId, config.outputMode, config.permissionMode, displayMode, mcpConfigContainerPath);
1826
1892
  // Execute based on display mode
1827
1893
  // When sessionManager is 'tmux', always use tmux inside container for session persistence
1828
1894
  // (allows reattach via `prlt session attach` even for background mode)
@@ -2849,19 +2915,125 @@ exec $SHELL
2849
2915
  }
2850
2916
  }
2851
2917
  // =============================================================================
2852
- // VM Runner
2918
+ // Sandbox Utilities
2919
+ // =============================================================================
2920
+ /**
2921
+ * Check if srt (sandbox-runtime) is installed on the host.
2922
+ */
2923
+ export function isSrtInstalled() {
2924
+ try {
2925
+ execSync('which srt', { stdio: 'pipe' });
2926
+ return true;
2927
+ }
2928
+ catch {
2929
+ return false;
2930
+ }
2931
+ }
2932
+ /**
2933
+ * Build the srt command with filesystem and network restrictions.
2934
+ *
2935
+ * Filesystem policy (read-restriction philosophy from claude-code-sandbox):
2936
+ * - Read/write: agent worktree directory
2937
+ * - Read-only: repo source (if different from worktree)
2938
+ * - Read-only: additional configured read paths
2939
+ * - Deny: home directory, system paths, other repos
2940
+ *
2941
+ * Network policy:
2942
+ * - Allow: configured domains (GitHub, Anthropic API, npm registries, etc.)
2943
+ * - Deny: everything else
2944
+ */
2945
+ export function buildSrtCommand(innerCommand, context, config) {
2946
+ const args = ['srt'];
2947
+ // Filesystem: always allow read/write to agent worktree
2948
+ args.push(`--fs-write=${context.worktreePath}`);
2949
+ // Allow read/write to the agent directory (parent of worktree, contains .devcontainer etc.)
2950
+ if (context.agentDir && context.agentDir !== context.worktreePath) {
2951
+ args.push(`--fs-write=${context.agentDir}`);
2952
+ }
2953
+ // Allow read/write to HQ scripts directory (for temp script files)
2954
+ if (context.hqPath) {
2955
+ const scriptsDir = path.join(context.hqPath, '.proletariat', 'scripts');
2956
+ args.push(`--fs-write=${scriptsDir}`);
2957
+ }
2958
+ // Allow read access to additional configured paths
2959
+ for (const readPath of config.sandbox.allowReadPaths) {
2960
+ args.push(`--fs-read=${readPath}`);
2961
+ }
2962
+ // Allow write access to additional configured paths
2963
+ for (const writePath of config.sandbox.allowWritePaths) {
2964
+ args.push(`--fs-write=${writePath}`);
2965
+ }
2966
+ // Allow read to temp directory (needed for script execution)
2967
+ args.push(`--fs-write=${os.tmpdir()}`);
2968
+ // Network: merge sandbox domains with firewall allowlist
2969
+ const allDomains = new Set([
2970
+ ...config.sandbox.networkDomains,
2971
+ ...config.firewall.allowlistDomains,
2972
+ ]);
2973
+ for (const domain of allDomains) {
2974
+ args.push(`--net-allow=${domain}`);
2975
+ }
2976
+ // The inner command to execute inside the sandbox
2977
+ args.push('--');
2978
+ args.push(innerCommand);
2979
+ return args.join(' ');
2980
+ }
2981
+ // =============================================================================
2982
+ // Sandbox Runner - srt-based sandbox on host
2983
+ // =============================================================================
2984
+ /**
2985
+ * Run command in an srt sandbox on the host machine.
2986
+ * Uses the same tmux session approach as the host runner, but wraps the
2987
+ * executor command with srt for filesystem and network isolation.
2988
+ *
2989
+ * Falls back to host runner with warning if srt is not installed.
2990
+ */
2991
+ export async function runSandbox(context, executor, config, displayMode = 'terminal') {
2992
+ // Check if srt is installed
2993
+ if (!isSrtInstalled()) {
2994
+ if (config.sandbox.fallbackToHost) {
2995
+ // Log warning via stderr (will be visible in terminal)
2996
+ process.stderr.write('\x1b[33m⚠️ srt (sandbox-runtime) not installed. Falling back to host execution.\n' +
2997
+ ' Install srt for filesystem + network isolation: https://github.com/anthropic-experimental/sandbox-runtime\x1b[0m\n');
2998
+ // Fall back to host runner
2999
+ return runHost(context, executor, config, displayMode);
3000
+ }
3001
+ return {
3002
+ success: false,
3003
+ error: 'srt (sandbox-runtime) is not installed.\n\n' +
3004
+ 'Install it from: https://github.com/anthropic-experimental/sandbox-runtime\n' +
3005
+ 'Or set sandbox.fallbackToHost to true in execution config to fall back to host.',
3006
+ };
3007
+ }
3008
+ // Delegate to host runner — the sandbox wrapping happens at the script level
3009
+ // We set a flag on context so the host runner knows to wrap with srt
3010
+ const sandboxContext = {
3011
+ ...context,
3012
+ executionEnvironment: 'sandbox',
3013
+ };
3014
+ return runHost(sandboxContext, executor, config, displayMode);
3015
+ }
3016
+ // =============================================================================
3017
+ // Cloud Runner (was VM Runner)
2853
3018
  // =============================================================================
2854
- export async function runVm(context, executor, config, host) {
2855
- const targetHost = host || config.vm.defaultHost;
3019
+ /**
3020
+ * Run command on a remote machine (cloud) via SSH.
3021
+ * Formerly 'runVm' — renamed to reflect the simplified environment hierarchy.
3022
+ * Uses cloud config with fallback to legacy vm config for backwards compatibility.
3023
+ */
3024
+ export async function runCloud(context, executor, config, host) {
3025
+ // Use cloud config, fall back to vm config for backwards compatibility
3026
+ const cloudConfig = config.cloud?.defaultHost ? config.cloud : config.vm;
3027
+ const targetHost = host || cloudConfig.defaultHost;
2856
3028
  if (!targetHost) {
2857
3029
  return {
2858
3030
  success: false,
2859
- error: 'No VM host specified. Use --host or configure execution.vm.default_host',
3031
+ error: 'No cloud host specified. Use --host or configure execution.cloud.default_host',
2860
3032
  };
2861
3033
  }
2862
3034
  const prompt = buildPrompt(context);
2863
- const user = config.vm.user;
2864
- const keyPath = config.vm.keyPath;
3035
+ const user = cloudConfig.user;
3036
+ const keyPath = cloudConfig.keyPath;
2865
3037
  const remoteWorkspace = `/workspace/${context.agentName}`;
2866
3038
  try {
2867
3039
  // Build SSH options
@@ -2870,7 +3042,7 @@ export async function runVm(context, executor, config, host) {
2870
3042
  sshOpts = `-i "${keyPath}"`;
2871
3043
  }
2872
3044
  // Sync worktree to remote
2873
- if (config.vm.syncMethod === 'rsync') {
3045
+ if (cloudConfig.syncMethod === 'rsync') {
2874
3046
  let rsyncCmd = `rsync -avz`;
2875
3047
  if (keyPath) {
2876
3048
  rsyncCmd += ` -e "ssh -i ${keyPath}"`;
@@ -2884,7 +3056,7 @@ export async function runVm(context, executor, config, host) {
2884
3056
  const gitPullCmd = `cd ${remoteWorkspace} && git fetch && git checkout ${context.branch}`;
2885
3057
  execSync(`ssh ${sshOpts} ${user}@${targetHost} "${gitPullCmd}"`, { stdio: 'pipe' });
2886
3058
  }
2887
- // Validate Codex mode: VM runner is always non-tty (SSH + nohup)
3059
+ // Validate Codex mode: Cloud runner is always non-tty (SSH + nohup)
2888
3060
  if (executor === 'codex') {
2889
3061
  const codexPermission = config.permissionMode;
2890
3062
  const modeError = validateCodexMode(codexPermission, 'non-tty');
@@ -2916,29 +3088,38 @@ export async function runVm(context, executor, config, host) {
2916
3088
  catch (error) {
2917
3089
  return {
2918
3090
  success: false,
2919
- error: error instanceof Error ? error.message : 'Failed to execute on VM',
3091
+ error: error instanceof Error ? error.message : 'Failed to execute on cloud',
2920
3092
  };
2921
3093
  }
2922
3094
  }
3095
+ /** @deprecated Use runCloud instead */
3096
+ export const runVm = runCloud;
2923
3097
  // =============================================================================
2924
3098
  // Runner Dispatcher
2925
3099
  // =============================================================================
2926
3100
  export async function runExecution(environment, context, executor, config = DEFAULT_EXECUTION_CONFIG, options) {
2927
- // Ensure tmux server has keychain access for OAuth (host only)
3101
+ // Ensure context knows its execution environment
3102
+ if (!context.executionEnvironment) {
3103
+ context.executionEnvironment = environment;
3104
+ }
3105
+ // Normalize environment (maps 'vm' -> 'cloud')
3106
+ const normalizedEnv = normalizeEnvironment(environment);
3107
+ // Ensure tmux server has keychain access for OAuth (host/sandbox only)
2928
3108
  // Docker uses claude-credentials volume, devcontainer runs inside container
2929
- if (environment === 'host') {
3109
+ if (normalizedEnv === 'host' || normalizedEnv === 'sandbox') {
2930
3110
  await ensureTmuxServerHasKeychainAccess();
2931
3111
  }
2932
- switch (environment) {
3112
+ switch (normalizedEnv) {
2933
3113
  case 'devcontainer':
2934
3114
  return runDevcontainer(context, executor, config, options?.displayMode, options?.sessionManager);
2935
3115
  case 'host':
2936
- // Host uses tmux for session persistence (same as devcontainer)
2937
3116
  return runHost(context, executor, config, options?.displayMode);
3117
+ case 'sandbox':
3118
+ return runSandbox(context, executor, config, options?.displayMode);
2938
3119
  case 'docker':
2939
3120
  return runDocker(context, executor, config);
2940
- case 'vm':
2941
- return runVm(context, executor, config, options?.host);
3121
+ case 'cloud':
3122
+ return runCloud(context, executor, config, options?.host);
2942
3123
  default:
2943
3124
  return { success: false, error: `Unknown execution environment: ${environment}` };
2944
3125
  }