@proletariat/cli 0.3.57 → 0.3.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/{spec/view.d.ts → dashboard/index.d.ts} +4 -6
- package/dist/commands/dashboard/index.js +117 -0
- package/dist/commands/dashboard/index.js.map +1 -0
- package/dist/commands/execution/config.js +5 -4
- package/dist/commands/execution/config.js.map +1 -1
- package/dist/commands/execution/stop.js +4 -2
- package/dist/commands/execution/stop.js.map +1 -1
- package/dist/commands/execution/view.js +3 -0
- package/dist/commands/execution/view.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +40 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp-server.js +1 -2
- package/dist/commands/mcp-server.js.map +1 -1
- package/dist/commands/media/add.d.ts +19 -0
- package/dist/commands/media/add.js +94 -0
- package/dist/commands/media/add.js.map +1 -0
- package/dist/commands/{spec → media}/index.d.ts +1 -1
- package/dist/commands/media/index.js +85 -0
- package/dist/commands/media/index.js.map +1 -0
- package/dist/commands/{spec/link/remove.d.ts → media/list.d.ts} +3 -6
- package/dist/commands/media/list.js +89 -0
- package/dist/commands/media/list.js.map +1 -0
- package/dist/commands/media/preprocess.d.ts +19 -0
- package/dist/commands/media/preprocess.js +91 -0
- package/dist/commands/media/preprocess.js.map +1 -0
- package/dist/commands/{spec/delete.d.ts → media/remove.d.ts} +2 -2
- package/dist/commands/media/remove.js +101 -0
- package/dist/commands/media/remove.js.map +1 -0
- package/dist/commands/{spec/link/index.d.ts → media/show.d.ts} +3 -3
- package/dist/commands/media/show.js +122 -0
- package/dist/commands/media/show.js.map +1 -0
- package/dist/commands/orchestrator/start.js +5 -0
- package/dist/commands/orchestrator/start.js.map +1 -1
- package/dist/commands/session/exec.d.ts +19 -0
- package/dist/commands/session/exec.js +205 -0
- package/dist/commands/session/exec.js.map +1 -0
- package/dist/commands/session/index.js +12 -0
- package/dist/commands/session/index.js.map +1 -1
- package/dist/commands/{spec/link/depends.d.ts → session/inspect.d.ts} +4 -4
- package/dist/commands/session/inspect.js +316 -0
- package/dist/commands/session/inspect.js.map +1 -0
- package/dist/commands/session/peek.d.ts +15 -0
- package/dist/commands/session/peek.js +141 -8
- package/dist/commands/session/peek.js.map +1 -1
- package/dist/commands/session/poke.d.ts +4 -1
- package/dist/commands/session/poke.js +175 -20
- package/dist/commands/session/poke.js.map +1 -1
- package/dist/commands/session/restart.d.ts +20 -0
- package/dist/commands/session/restart.js +320 -0
- package/dist/commands/session/restart.js.map +1 -0
- package/dist/commands/tools/add.d.ts +20 -0
- package/dist/commands/tools/add.js +129 -0
- package/dist/commands/tools/add.js.map +1 -0
- package/dist/commands/tools/check.d.ts +10 -0
- package/dist/commands/tools/check.js +75 -0
- package/dist/commands/tools/check.js.map +1 -0
- package/dist/commands/tools/detect.d.ts +11 -0
- package/dist/commands/tools/detect.js +107 -0
- package/dist/commands/tools/detect.js.map +1 -0
- package/dist/commands/tools/index.d.ts +11 -0
- package/dist/commands/tools/index.js +87 -0
- package/dist/commands/tools/index.js.map +1 -0
- package/dist/commands/tools/remove.d.ts +13 -0
- package/dist/commands/tools/remove.js +55 -0
- package/dist/commands/tools/remove.js.map +1 -0
- package/dist/commands/trello/configure.d.ts +16 -0
- package/dist/commands/trello/configure.js +259 -0
- package/dist/commands/trello/configure.js.map +1 -0
- package/dist/commands/{spec/plan.d.ts → trello/import.d.ts} +3 -5
- package/dist/commands/trello/import.js +241 -0
- package/dist/commands/trello/import.js.map +1 -0
- package/dist/commands/{spec/ticket.d.ts → trello/sync.d.ts} +5 -6
- package/dist/commands/trello/sync.js +190 -0
- package/dist/commands/trello/sync.js.map +1 -0
- package/dist/commands/work/start.d.ts +2 -0
- package/dist/commands/work/start.js +27 -41
- package/dist/commands/work/start.js.map +1 -1
- package/dist/lib/dashboard/data.d.ts +64 -0
- package/dist/lib/dashboard/data.js +259 -0
- package/dist/lib/dashboard/data.js.map +1 -0
- package/dist/lib/dashboard/html.d.ts +7 -0
- package/dist/lib/dashboard/html.js +682 -0
- package/dist/lib/dashboard/html.js.map +1 -0
- package/dist/lib/dashboard/server.d.ts +20 -0
- package/dist/lib/dashboard/server.js +114 -0
- package/dist/lib/dashboard/server.js.map +1 -0
- package/dist/lib/database/index.d.ts +49 -1
- package/dist/lib/database/index.js +127 -0
- package/dist/lib/database/index.js.map +1 -1
- package/dist/lib/execution/config.d.ts +8 -0
- package/dist/lib/execution/config.js +83 -4
- package/dist/lib/execution/config.js.map +1 -1
- package/dist/lib/execution/runners.d.ts +60 -4
- package/dist/lib/execution/runners.js +398 -79
- package/dist/lib/execution/runners.js.map +1 -1
- package/dist/lib/execution/spawner.d.ts +4 -2
- package/dist/lib/execution/spawner.js +54 -47
- package/dist/lib/execution/spawner.js.map +1 -1
- package/dist/lib/execution/types.d.ts +27 -5
- package/dist/lib/execution/types.js +24 -0
- package/dist/lib/execution/types.js.map +1 -1
- package/dist/lib/external-issues/adapters.d.ts +17 -0
- package/dist/lib/external-issues/adapters.js +88 -0
- package/dist/lib/external-issues/adapters.js.map +1 -1
- package/dist/lib/external-issues/mapping-store.js +1 -1
- package/dist/lib/external-issues/shortcut.js +2 -1
- package/dist/lib/external-issues/shortcut.js.map +1 -1
- package/dist/lib/external-issues/trello.d.ts +80 -0
- package/dist/lib/external-issues/trello.js +266 -0
- package/dist/lib/external-issues/trello.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/linear/client.d.ts +4 -3
- package/dist/lib/linear/client.js +185 -122
- package/dist/lib/linear/client.js.map +1 -1
- package/dist/lib/mcp/tools/cli-passthrough.js +77 -0
- package/dist/lib/mcp/tools/cli-passthrough.js.map +1 -1
- package/dist/lib/mcp/tools/index.d.ts +0 -1
- package/dist/lib/mcp/tools/index.js +0 -1
- package/dist/lib/mcp/tools/index.js.map +1 -1
- package/dist/lib/media/index.d.ts +91 -0
- package/dist/lib/media/index.js +475 -0
- package/dist/lib/media/index.js.map +1 -0
- package/dist/lib/onboarding/detect-tools.d.ts +15 -0
- package/dist/lib/onboarding/detect-tools.js +44 -0
- package/dist/lib/onboarding/detect-tools.js.map +1 -0
- package/dist/lib/onboarding/index.d.ts +2 -0
- package/dist/lib/onboarding/index.js +3 -0
- package/dist/lib/onboarding/index.js.map +1 -0
- package/dist/lib/onboarding/wizard.d.ts +25 -0
- package/dist/lib/onboarding/wizard.js +156 -0
- package/dist/lib/onboarding/wizard.js.map +1 -0
- package/dist/lib/pmo/schema.d.ts +2 -1
- package/dist/lib/pmo/schema.js +3 -1
- package/dist/lib/pmo/schema.js.map +1 -1
- package/dist/lib/runners/claude-code-runner.js +6 -0
- package/dist/lib/runners/claude-code-runner.js.map +1 -1
- package/dist/lib/tool-registry/detect.d.ts +20 -0
- package/dist/lib/tool-registry/detect.js +95 -0
- package/dist/lib/tool-registry/detect.js.map +1 -0
- package/dist/lib/tool-registry/index.d.ts +10 -0
- package/dist/lib/tool-registry/index.js +13 -0
- package/dist/lib/tool-registry/index.js.map +1 -0
- package/dist/lib/tool-registry/policy.d.ts +32 -0
- package/dist/lib/tool-registry/policy.js +97 -0
- package/dist/lib/tool-registry/policy.js.map +1 -0
- package/dist/lib/tool-registry/registry.d.ts +42 -0
- package/dist/lib/tool-registry/registry.js +120 -0
- package/dist/lib/tool-registry/registry.js.map +1 -0
- package/dist/lib/tool-registry/spawn.d.ts +50 -0
- package/dist/lib/tool-registry/spawn.js +103 -0
- package/dist/lib/tool-registry/spawn.js.map +1 -0
- package/dist/lib/tool-registry/types.d.ts +56 -0
- package/dist/lib/tool-registry/types.js +109 -0
- package/dist/lib/tool-registry/types.js.map +1 -0
- package/dist/lib/trello/client.d.ts +23 -0
- package/dist/lib/trello/client.js +114 -0
- package/dist/lib/trello/client.js.map +1 -0
- package/dist/lib/trello/config.d.ts +55 -0
- package/dist/lib/trello/config.js +127 -0
- package/dist/lib/trello/config.js.map +1 -0
- package/dist/lib/trello/index.d.ts +5 -0
- package/dist/lib/trello/index.js +5 -0
- package/dist/lib/trello/index.js.map +1 -0
- package/dist/lib/trello/mapper.d.ts +13 -0
- package/dist/lib/trello/mapper.js +71 -0
- package/dist/lib/trello/mapper.js.map +1 -0
- package/dist/lib/trello/sync.d.ts +13 -0
- package/dist/lib/trello/sync.js +38 -0
- package/dist/lib/trello/sync.js.map +1 -0
- package/dist/lib/trello/types.d.ts +53 -0
- package/dist/lib/trello/types.js +2 -0
- package/dist/lib/trello/types.js.map +1 -0
- package/dist/lib/work-source/client.js +17 -0
- package/dist/lib/work-source/client.js.map +1 -1
- package/dist/lib/work-source/config.d.ts +6 -1
- package/dist/lib/work-source/config.js +30 -1
- package/dist/lib/work-source/config.js.map +1 -1
- package/dist/lib/work-source/index.d.ts +1 -1
- package/dist/lib/work-source/index.js +1 -1
- package/dist/lib/work-source/index.js.map +1 -1
- package/oclif.manifest.json +6524 -6171
- package/package.json +6 -2
- package/dist/commands/spec/create.d.ts +0 -20
- package/dist/commands/spec/create.js +0 -171
- package/dist/commands/spec/create.js.map +0 -1
- package/dist/commands/spec/delete.js +0 -112
- package/dist/commands/spec/delete.js.map +0 -1
- package/dist/commands/spec/edit.d.ts +0 -23
- package/dist/commands/spec/edit.js +0 -262
- package/dist/commands/spec/edit.js.map +0 -1
- package/dist/commands/spec/index.js +0 -88
- package/dist/commands/spec/index.js.map +0 -1
- package/dist/commands/spec/link/depends.js +0 -87
- package/dist/commands/spec/link/depends.js.map +0 -1
- package/dist/commands/spec/link/index.js +0 -93
- package/dist/commands/spec/link/index.js.map +0 -1
- package/dist/commands/spec/link/remove.js +0 -91
- package/dist/commands/spec/link/remove.js.map +0 -1
- package/dist/commands/spec/list.d.ts +0 -14
- package/dist/commands/spec/list.js +0 -101
- package/dist/commands/spec/list.js.map +0 -1
- package/dist/commands/spec/plan.js +0 -102
- package/dist/commands/spec/plan.js.map +0 -1
- package/dist/commands/spec/ticket.js +0 -144
- package/dist/commands/spec/ticket.js.map +0 -1
- package/dist/commands/spec/view.js +0 -202
- package/dist/commands/spec/view.js.map +0 -1
- package/dist/lib/mcp/tools/spec.d.ts +0 -6
- package/dist/lib/mcp/tools/spec.js +0 -197
- package/dist/lib/mcp/tools/spec.js.map +0 -1
- package/dist/lib/pmo/spec-parser.d.ts +0 -25
- package/dist/lib/pmo/spec-parser.js +0 -206
- package/dist/lib/pmo/spec-parser.js.map +0 -1
- package/dist/lib/pmo/spec-types.d.ts +0 -43
- package/dist/lib/pmo/spec-types.js +0 -8
- 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 (
|
|
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,14 +381,84 @@ 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
|
-
|
|
384
|
+
const env = normalizeEnvironment(environment);
|
|
385
|
+
if (env === 'host' || env === 'sandbox') {
|
|
384
386
|
return checkExecutorOnHost(executor);
|
|
385
387
|
}
|
|
386
|
-
if (
|
|
388
|
+
if (env === 'devcontainer' && options?.containerId) {
|
|
387
389
|
return checkExecutorInContainer(executor, options.containerId);
|
|
388
390
|
}
|
|
389
391
|
return { ok: true };
|
|
390
392
|
}
|
|
393
|
+
const INTEGRATION_COMMANDS = [
|
|
394
|
+
{
|
|
395
|
+
provider: 'asana',
|
|
396
|
+
displayName: 'Asana',
|
|
397
|
+
commands: [
|
|
398
|
+
'prlt asana connect — authenticate with Asana',
|
|
399
|
+
'prlt asana sync --ticket TKT-XXX --create-missing --project <gid> — sync a PMO ticket to Asana',
|
|
400
|
+
'prlt asana import — import Asana tasks into PMO',
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
provider: 'linear',
|
|
405
|
+
displayName: 'Linear',
|
|
406
|
+
commands: [
|
|
407
|
+
'prlt linear connect — authenticate with Linear',
|
|
408
|
+
'prlt linear sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Linear',
|
|
409
|
+
'prlt linear import — import Linear issues into PMO',
|
|
410
|
+
],
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
provider: 'jira',
|
|
414
|
+
displayName: 'Jira',
|
|
415
|
+
commands: [
|
|
416
|
+
'prlt jira connect — authenticate with Jira',
|
|
417
|
+
'prlt jira sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Jira',
|
|
418
|
+
'prlt jira import — import Jira issues into PMO',
|
|
419
|
+
],
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
provider: 'shortcut',
|
|
423
|
+
displayName: 'Shortcut',
|
|
424
|
+
commands: [
|
|
425
|
+
'prlt shortcut connect — authenticate with Shortcut',
|
|
426
|
+
'prlt shortcut sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Shortcut',
|
|
427
|
+
'prlt shortcut import — import Shortcut stories into PMO',
|
|
428
|
+
],
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
provider: 'monday',
|
|
432
|
+
displayName: 'Monday.com',
|
|
433
|
+
commands: [
|
|
434
|
+
'prlt monday connect — authenticate with Monday.com',
|
|
435
|
+
'prlt monday sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Monday.com',
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
];
|
|
439
|
+
/**
|
|
440
|
+
* Build the integration commands section for agent prompts.
|
|
441
|
+
* Only includes integrations that are actually connected/configured.
|
|
442
|
+
* Returns empty string if no integrations are connected.
|
|
443
|
+
*/
|
|
444
|
+
function buildIntegrationCommandsSection(connectedIntegrations) {
|
|
445
|
+
if (!connectedIntegrations || connectedIntegrations.length === 0)
|
|
446
|
+
return '';
|
|
447
|
+
const connected = INTEGRATION_COMMANDS.filter(ic => connectedIntegrations.includes(ic.provider));
|
|
448
|
+
if (connected.length === 0)
|
|
449
|
+
return '';
|
|
450
|
+
let section = `## Integration Commands\n\n`;
|
|
451
|
+
section += `The following external integrations are connected. Use these prlt commands to interact with them.\n\n`;
|
|
452
|
+
for (const integration of connected) {
|
|
453
|
+
section += `### ${integration.displayName}\n`;
|
|
454
|
+
for (const cmd of integration.commands) {
|
|
455
|
+
section += `- \`${cmd.split(' — ')[0]}\` — ${cmd.split(' — ')[1] || ''}\n`;
|
|
456
|
+
}
|
|
457
|
+
section += '\n';
|
|
458
|
+
}
|
|
459
|
+
section += `**ANTI-PATTERN:** Never use curl, raw API calls, or shell scripts to interact with external services (Asana, Linear, Jira, Shortcut, Monday.com, etc.). Always use the corresponding \`prlt\` commands.\n\n`;
|
|
460
|
+
return section;
|
|
461
|
+
}
|
|
391
462
|
const ORCHESTRATOR_COMMAND_REGISTRY = [
|
|
392
463
|
{
|
|
393
464
|
title: 'Agent Lifecycle',
|
|
@@ -486,6 +557,18 @@ function buildOrchestratorAntiPatterns() {
|
|
|
486
557
|
*/
|
|
487
558
|
function buildOrchestratorBody(hqName, context) {
|
|
488
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`;
|
|
489
572
|
// Runtime declaration
|
|
490
573
|
prompt += `## prlt Is Your Orchestration Runtime\n\n`;
|
|
491
574
|
prompt += `prlt is your orchestration runtime. NEVER use raw docker exec, tmux send-keys, or direct container access. `;
|
|
@@ -494,10 +577,12 @@ function buildOrchestratorBody(hqName, context) {
|
|
|
494
577
|
prompt += `health monitoring, and creates orphaned processes.\n\n`;
|
|
495
578
|
// Role
|
|
496
579
|
prompt += `## Your Role\n`;
|
|
497
|
-
prompt += `-
|
|
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`;
|
|
498
582
|
prompt += `- Delegate implementation to agents via \`prlt work start\`\n`;
|
|
499
|
-
prompt += `- Monitor agent progress and review completed work\n`;
|
|
500
|
-
prompt += `-
|
|
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`;
|
|
501
586
|
prompt += `- Never write code or make changes to source files yourself\n\n`;
|
|
502
587
|
// Command reference (dynamically generated)
|
|
503
588
|
prompt += `## Command Reference\n\n`;
|
|
@@ -511,11 +596,20 @@ function buildOrchestratorBody(hqName, context) {
|
|
|
511
596
|
prompt += `- Fix: \`--action review-fix\`\n\n`;
|
|
512
597
|
// Anti-patterns (dynamically generated)
|
|
513
598
|
prompt += buildOrchestratorAntiPatterns();
|
|
599
|
+
// Integration commands (only for connected integrations)
|
|
600
|
+
prompt += buildIntegrationCommandsSection(context.connectedIntegrations);
|
|
514
601
|
// Workflow
|
|
515
602
|
prompt += `## Workflow\n`;
|
|
516
603
|
prompt += `- Squash merge only: \`gh pr merge --squash\`\n`;
|
|
517
604
|
prompt += `- After merging: subsequent PRs from parallel agents will need rebase\n`;
|
|
518
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
|
+
}
|
|
519
613
|
// Load .orchestrator-context.md from HQ root if it exists
|
|
520
614
|
if (context.hqPath) {
|
|
521
615
|
const contextFilePath = path.join(context.hqPath, '.orchestrator-context.md');
|
|
@@ -540,9 +634,12 @@ function buildOrchestratorBody(hqName, context) {
|
|
|
540
634
|
*/
|
|
541
635
|
export function buildOrchestratorSystemPrompt(context) {
|
|
542
636
|
const hqName = context.hqName || 'workspace';
|
|
543
|
-
let prompt =
|
|
544
|
-
prompt += `
|
|
545
|
-
prompt +=
|
|
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`;
|
|
546
643
|
prompt += buildOrchestratorBody(hqName, context);
|
|
547
644
|
return prompt;
|
|
548
645
|
}
|
|
@@ -552,7 +649,10 @@ function buildOrchestratorPrompt(context) {
|
|
|
552
649
|
// a system prompt (role/tools) + a shorter user message.
|
|
553
650
|
const hqName = context.hqName || 'workspace';
|
|
554
651
|
let prompt = `# Orchestrator: ${hqName}\n\n`;
|
|
555
|
-
prompt += `You are the orchestrator for the **${hqName}**
|
|
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`;
|
|
556
656
|
prompt += buildOrchestratorBody(hqName, context);
|
|
557
657
|
// Include user's custom prompt or action content
|
|
558
658
|
if (context.actionPrompt) {
|
|
@@ -591,9 +691,6 @@ function buildPrompt(context) {
|
|
|
591
691
|
if (context.epicTitle) {
|
|
592
692
|
prompt += `**Epic:** ${context.epicTitle}\n`;
|
|
593
693
|
}
|
|
594
|
-
if (context.specId) {
|
|
595
|
-
prompt += `**Spec:** ${context.specId}${context.specTitle ? ` - ${context.specTitle}` : ''}\n`;
|
|
596
|
-
}
|
|
597
694
|
if (context.ticketDescription) {
|
|
598
695
|
prompt += `\n## Description\n\n${context.ticketDescription}\n`;
|
|
599
696
|
}
|
|
@@ -606,10 +703,22 @@ function buildPrompt(context) {
|
|
|
606
703
|
}
|
|
607
704
|
// Note: Branch setup (fetch + checkout/create) is now handled programmatically
|
|
608
705
|
// in work/start.ts before the agent spawns, so no prompt instructions needed
|
|
706
|
+
// Integration commands (only for connected integrations)
|
|
707
|
+
const integrationSection = buildIntegrationCommandsSection(context.connectedIntegrations);
|
|
708
|
+
if (integrationSection) {
|
|
709
|
+
prompt += `\n${integrationSection}`;
|
|
710
|
+
}
|
|
609
711
|
// Additional instructions from --message flag (appended to any action)
|
|
610
712
|
if (context.customMessage) {
|
|
611
713
|
prompt += `\n## Additional Instructions\n\n${context.customMessage}\n`;
|
|
612
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
|
+
}
|
|
613
722
|
// END HOOK - Action-specific completion instructions
|
|
614
723
|
prompt += `\n---\n\n## When Complete\n\n`;
|
|
615
724
|
// For revisions, use the revision-specific end prompt
|
|
@@ -711,16 +820,27 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
711
820
|
fs.writeFileSync(systemPromptPath, systemPrompt, { mode: 0o644 });
|
|
712
821
|
// Override user message: just action instructions or a default startup message
|
|
713
822
|
const userMessage = context.actionPrompt
|
|
714
|
-
|| '
|
|
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.';
|
|
715
828
|
fs.writeFileSync(promptPath, userMessage, { mode: 0o644 });
|
|
716
829
|
}
|
|
717
830
|
else {
|
|
718
831
|
// Write full prompt (includes role context for non-Claude executors)
|
|
719
832
|
fs.writeFileSync(promptPath, prompt, { mode: 0o644 });
|
|
720
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
|
+
}
|
|
721
840
|
// Build the executor command using getExecutorCommand() output
|
|
722
841
|
// For Claude Code, we also support outputMode and additional flags
|
|
723
|
-
// For
|
|
842
|
+
// For Codex, we use the codex adapter for deterministic command building (TKT-080)
|
|
843
|
+
// For other executors, we use the command as-is from getExecutorCommand()
|
|
724
844
|
let executorInvocation;
|
|
725
845
|
if (isClaudeExecutor(executor)) {
|
|
726
846
|
// Build flags based on config - Claude-specific flags
|
|
@@ -731,13 +851,28 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
731
851
|
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
732
852
|
// Orchestrator sessions inject their role via --system-prompt
|
|
733
853
|
const systemPromptFlag = systemPromptPath ? '--system-prompt "$(cat "$SYSTEM_PROMPT_PATH")" ' : '';
|
|
734
|
-
|
|
854
|
+
// TKT-053: Disable plan mode for background agents — prevents silent stalls
|
|
855
|
+
// when there's no user to approve the plan mode transition
|
|
856
|
+
const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
|
|
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")"`;
|
|
860
|
+
}
|
|
861
|
+
else if (executor === 'codex') {
|
|
862
|
+
// TKT-080: Use Codex adapter for deterministic command building.
|
|
863
|
+
// Uses PLACEHOLDER pattern for reliable prompt replacement (same as devcontainer runner).
|
|
864
|
+
const codexPermission = config.permissionMode;
|
|
865
|
+
const codexContext = resolveCodexExecutionContext(displayMode, config.outputMode);
|
|
866
|
+
const codexResult = getCodexCommand('PLACEHOLDER', codexPermission, codexContext);
|
|
867
|
+
const argsStr = codexResult.args.map(a => a === 'PLACEHOLDER' ? '"$(cat "$PROMPT_PATH")"' : a).join(' ');
|
|
868
|
+
executorInvocation = `${codexResult.cmd} ${argsStr}`;
|
|
735
869
|
}
|
|
736
870
|
else {
|
|
737
|
-
// Non-Claude executors: build command from getExecutorCommand() args
|
|
738
|
-
//
|
|
739
|
-
const
|
|
740
|
-
|
|
871
|
+
// Non-Claude, non-Codex executors: build command from getExecutorCommand() args
|
|
872
|
+
// Use PLACEHOLDER for reliable prompt replacement instead of fragile string comparison
|
|
873
|
+
const { cmd: execCmd, args: execArgs } = getExecutorCommand(executor, 'PLACEHOLDER', skipPermissions);
|
|
874
|
+
const argsWithFile = execArgs.map(a => a === 'PLACEHOLDER' ? '"$(cat "$PROMPT_PATH")"' : `"${a}"`);
|
|
875
|
+
executorInvocation = `${execCmd} ${argsWithFile.join(' ')}`;
|
|
741
876
|
}
|
|
742
877
|
// Build script that runs executor and keeps shell open after completion
|
|
743
878
|
const setTitleCmds = getSetTitleCommands(windowTitle);
|
|
@@ -755,16 +890,25 @@ echo ""
|
|
|
755
890
|
echo "✅ Agent work complete. Press Enter to close or run more commands."
|
|
756
891
|
exec $SHELL
|
|
757
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
|
+
}
|
|
758
901
|
const scriptContent = `#!/bin/bash
|
|
759
902
|
# Auto-generated script for ticket ${context.ticketId}
|
|
760
903
|
SCRIPT_PATH="${scriptPath}"
|
|
761
904
|
PROMPT_PATH="${promptPath}"${systemPromptVar}
|
|
762
905
|
${setTitleCmds}
|
|
763
906
|
echo "🚀 Starting: ${sessionName}"
|
|
907
|
+
${context.executionEnvironment === 'sandbox' ? 'echo "🔒 Running in srt sandbox (filesystem + network isolation)"' : ''}
|
|
764
908
|
echo ""
|
|
765
909
|
cd "${context.worktreePath}"
|
|
766
910
|
# Run executor in subshell with CLAUDECODE unset (prevents nested session error)
|
|
767
|
-
(unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT; ${
|
|
911
|
+
(unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT; ${finalInvocation})
|
|
768
912
|
|
|
769
913
|
# Clean up script and prompt files
|
|
770
914
|
rm -f "$SCRIPT_PATH" "$PROMPT_PATH"${systemPromptPath ? ' "$SYSTEM_PROMPT_PATH"' : ''}
|
|
@@ -1049,30 +1193,71 @@ export function getGitHubToken() {
|
|
|
1049
1193
|
export function isGitHubTokenAvailable() {
|
|
1050
1194
|
return getGitHubToken() !== null;
|
|
1051
1195
|
}
|
|
1052
|
-
// =============================================================================
|
|
1053
|
-
// Docker Status Check
|
|
1054
|
-
// =============================================================================
|
|
1055
1196
|
/**
|
|
1056
|
-
* Check
|
|
1057
|
-
*
|
|
1058
|
-
* Uses
|
|
1197
|
+
* Check Docker daemon health with fast detection (TKT-081).
|
|
1198
|
+
*
|
|
1199
|
+
* Uses `docker ps` with a 5-second timeout to quickly detect:
|
|
1200
|
+
* - Docker not installed
|
|
1201
|
+
* - Docker installed but daemon unresponsive (stuck on license, initializing, 500 errors)
|
|
1202
|
+
* - Docker ready
|
|
1203
|
+
*
|
|
1204
|
+
* Total worst-case time: ~5 seconds (single attempt with timeout).
|
|
1059
1205
|
*/
|
|
1060
|
-
export function
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1206
|
+
export function checkDockerDaemon() {
|
|
1207
|
+
// First: is docker even installed?
|
|
1208
|
+
try {
|
|
1209
|
+
execSync('which docker', { stdio: 'pipe', timeout: 3000 });
|
|
1210
|
+
}
|
|
1211
|
+
catch {
|
|
1212
|
+
return {
|
|
1213
|
+
available: false,
|
|
1214
|
+
reason: 'not-installed',
|
|
1215
|
+
message: 'Docker is not installed.',
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
// Second: is the daemon responsive? Use `docker ps` — it's lightweight and
|
|
1219
|
+
// fails fast when the daemon returns 500s or hangs on GUI prompts.
|
|
1220
|
+
const timeout = 5000; // 5 seconds — enough for a healthy daemon, fast fail otherwise
|
|
1221
|
+
try {
|
|
1222
|
+
execSync('docker ps -q --no-trunc', { stdio: 'pipe', timeout });
|
|
1223
|
+
return {
|
|
1224
|
+
available: true,
|
|
1225
|
+
reason: 'ready',
|
|
1226
|
+
message: 'Docker daemon is ready.',
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
catch (error) {
|
|
1230
|
+
// Parse the error to give actionable feedback
|
|
1231
|
+
const stderr = error?.stderr?.toString() || '';
|
|
1232
|
+
const isTimeout = error?.killed === true;
|
|
1233
|
+
let message;
|
|
1234
|
+
if (isTimeout) {
|
|
1235
|
+
message = 'Docker daemon is not responding (timed out after 5s). Docker Desktop may be initializing or stuck — check for license/login prompts.';
|
|
1067
1236
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1237
|
+
else if (stderr.includes('500') || stderr.includes('Internal Server Error')) {
|
|
1238
|
+
message = 'Docker daemon is returning errors (500). Docker Desktop needs attention — check for license/login prompts.';
|
|
1239
|
+
}
|
|
1240
|
+
else if (stderr.includes('connect') || stderr.includes('Cannot connect') || stderr.includes('Is the docker daemon running')) {
|
|
1241
|
+
message = 'Docker daemon is not running. Start Docker Desktop and try again.';
|
|
1073
1242
|
}
|
|
1243
|
+
else {
|
|
1244
|
+
message = `Docker daemon is not ready: ${stderr.trim() || 'unknown error'}. Check Docker Desktop status.`;
|
|
1245
|
+
}
|
|
1246
|
+
return {
|
|
1247
|
+
available: false,
|
|
1248
|
+
reason: 'daemon-not-ready',
|
|
1249
|
+
message,
|
|
1250
|
+
};
|
|
1074
1251
|
}
|
|
1075
|
-
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Check if Docker daemon is running.
|
|
1255
|
+
* Returns true if Docker is available and responsive.
|
|
1256
|
+
*
|
|
1257
|
+
* For detailed diagnostics, use checkDockerDaemon() instead.
|
|
1258
|
+
*/
|
|
1259
|
+
export function isDockerRunning() {
|
|
1260
|
+
return checkDockerDaemon().available;
|
|
1076
1261
|
}
|
|
1077
1262
|
/**
|
|
1078
1263
|
* Check if the devcontainer CLI is installed.
|
|
@@ -1129,7 +1314,7 @@ function getImageName(agentName) {
|
|
|
1129
1314
|
*/
|
|
1130
1315
|
export function containerExists(containerName) {
|
|
1131
1316
|
try {
|
|
1132
|
-
execSync(`docker container inspect ${containerName}`, { stdio: 'pipe' });
|
|
1317
|
+
execSync(`docker container inspect ${containerName}`, { stdio: 'pipe', timeout: 5000 });
|
|
1133
1318
|
return true;
|
|
1134
1319
|
}
|
|
1135
1320
|
catch {
|
|
@@ -1141,7 +1326,7 @@ export function containerExists(containerName) {
|
|
|
1141
1326
|
*/
|
|
1142
1327
|
export function isContainerRunning(containerName) {
|
|
1143
1328
|
try {
|
|
1144
|
-
const status = execSync(`docker container inspect -f '{{.State.Running}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
1329
|
+
const status = execSync(`docker container inspect -f '{{.State.Running}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }).trim();
|
|
1145
1330
|
return status === 'true';
|
|
1146
1331
|
}
|
|
1147
1332
|
catch {
|
|
@@ -1153,7 +1338,7 @@ export function isContainerRunning(containerName) {
|
|
|
1153
1338
|
*/
|
|
1154
1339
|
export function getContainerId(containerName) {
|
|
1155
1340
|
try {
|
|
1156
|
-
const containerId = execSync(`docker container inspect -f '{{.Id}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
1341
|
+
const containerId = execSync(`docker container inspect -f '{{.Id}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }).trim();
|
|
1157
1342
|
return containerId ? containerId.substring(0, 12) : null;
|
|
1158
1343
|
}
|
|
1159
1344
|
catch {
|
|
@@ -1189,7 +1374,7 @@ function buildDockerImage(agentDir, imageName, buildArgs = {}) {
|
|
|
1189
1374
|
*/
|
|
1190
1375
|
function imageExists(imageName) {
|
|
1191
1376
|
try {
|
|
1192
|
-
execSync(`docker image inspect ${imageName}`, { stdio: 'pipe' });
|
|
1377
|
+
execSync(`docker image inspect ${imageName}`, { stdio: 'pipe', timeout: 5000 });
|
|
1193
1378
|
return true;
|
|
1194
1379
|
}
|
|
1195
1380
|
catch {
|
|
@@ -1420,7 +1605,7 @@ function ensureDockerContainer(context, config, executor = 'claude-code') {
|
|
|
1420
1605
|
// Container exists but is stopped - remove and recreate for fresh mounts
|
|
1421
1606
|
console.debug(`[runners:docker] Removing stopped container ${containerName} to create fresh one`);
|
|
1422
1607
|
try {
|
|
1423
|
-
execSync(`docker rm -f ${containerName}`, { stdio: 'pipe' });
|
|
1608
|
+
execSync(`docker rm -f ${containerName}`, { stdio: 'pipe', timeout: 10000 });
|
|
1424
1609
|
}
|
|
1425
1610
|
catch {
|
|
1426
1611
|
// Ignore removal errors
|
|
@@ -1564,7 +1749,7 @@ function writePromptFile(context) {
|
|
|
1564
1749
|
* Uses docker exec for direct container access.
|
|
1565
1750
|
* Uses a prompt file to avoid shell escaping issues.
|
|
1566
1751
|
*/
|
|
1567
|
-
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) {
|
|
1568
1753
|
// Calculate the relative path from agentDir to worktreePath for cd
|
|
1569
1754
|
const relativePath = path.relative(context.agentDir, context.worktreePath);
|
|
1570
1755
|
const cdCmd = relativePath ? `cd /workspace/${relativePath} && ` : '';
|
|
@@ -1582,7 +1767,11 @@ export function buildDevcontainerCommand(context, executor, promptFile, containe
|
|
|
1582
1767
|
const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
|
|
1583
1768
|
// --effort high: skips the effort level prompt for automated agents (TKT-1134)
|
|
1584
1769
|
const effortFlag = '--effort high ';
|
|
1585
|
-
|
|
1770
|
+
// TKT-053: Disable plan mode for background agents — prevents silent stalls
|
|
1771
|
+
const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
|
|
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})"`;
|
|
1586
1775
|
}
|
|
1587
1776
|
else if (executor === 'codex') {
|
|
1588
1777
|
// Use Codex adapter for mode validation and deterministic command building.
|
|
@@ -1634,11 +1823,12 @@ export async function runDevcontainer(context, executor, config, displayMode = '
|
|
|
1634
1823
|
};
|
|
1635
1824
|
}
|
|
1636
1825
|
try {
|
|
1637
|
-
// Check if Docker is running
|
|
1638
|
-
|
|
1826
|
+
// Check if Docker is running (TKT-081: fast detection with diagnostic info)
|
|
1827
|
+
const dockerStatus = checkDockerDaemon();
|
|
1828
|
+
if (!dockerStatus.available) {
|
|
1639
1829
|
return {
|
|
1640
1830
|
success: false,
|
|
1641
|
-
error:
|
|
1831
|
+
error: `Docker daemon is not available. ${dockerStatus.message}`,
|
|
1642
1832
|
};
|
|
1643
1833
|
}
|
|
1644
1834
|
// Ensure GitHub token is available for git push operations
|
|
@@ -1672,6 +1862,16 @@ export async function runDevcontainer(context, executor, config, displayMode = '
|
|
|
1672
1862
|
}
|
|
1673
1863
|
// Write prompt to file in worktree (accessible by container)
|
|
1674
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
|
+
}
|
|
1675
1875
|
// Inject fresh GitHub token into container (containers may be reused with stale/empty tokens)
|
|
1676
1876
|
// This ensures git push works even if the container was created before token was available
|
|
1677
1877
|
const githubToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
@@ -1688,7 +1888,7 @@ export async function runDevcontainer(context, executor, config, displayMode = '
|
|
|
1688
1888
|
}
|
|
1689
1889
|
// Build the docker exec command (just runs claude directly)
|
|
1690
1890
|
// tmux session setup is handled by runDevcontainerInTmux, not buildDevcontainerCommand
|
|
1691
|
-
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);
|
|
1692
1892
|
// Execute based on display mode
|
|
1693
1893
|
// When sessionManager is 'tmux', always use tmux inside container for session persistence
|
|
1694
1894
|
// (allows reattach via `prlt session attach` even for background mode)
|
|
@@ -2333,13 +2533,12 @@ export async function runDocker(context, executor, config) {
|
|
|
2333
2533
|
const prompt = buildPrompt(context);
|
|
2334
2534
|
const containerName = `work-${context.ticketId}-${Date.now()}`;
|
|
2335
2535
|
try {
|
|
2336
|
-
// Check if docker is available
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
if (!isDockerRunning()) {
|
|
2536
|
+
// Check if docker is available and daemon is responsive (TKT-081)
|
|
2537
|
+
const dockerStatus = checkDockerDaemon();
|
|
2538
|
+
if (!dockerStatus.available) {
|
|
2340
2539
|
return {
|
|
2341
2540
|
success: false,
|
|
2342
|
-
error:
|
|
2541
|
+
error: `Docker daemon is not available. ${dockerStatus.message}`,
|
|
2343
2542
|
};
|
|
2344
2543
|
}
|
|
2345
2544
|
// Build docker run command
|
|
@@ -2371,7 +2570,8 @@ export async function runDocker(context, executor, config) {
|
|
|
2371
2570
|
// Non-Claude executors use their native command format from getExecutorCommand()
|
|
2372
2571
|
dockerCmd += ` ${config.docker.image}`;
|
|
2373
2572
|
if (isClaudeExecutor(executor)) {
|
|
2374
|
-
|
|
2573
|
+
// TKT-053: Disable plan mode — Docker runner is always detached (no user to approve)
|
|
2574
|
+
dockerCmd += ` ${cmd} --print --disallowedTools EnterPlanMode '${escapedPrompt}'`;
|
|
2375
2575
|
}
|
|
2376
2576
|
else {
|
|
2377
2577
|
const argsStr = args.map(a => a === escapedPrompt ? `'${escapedPrompt}'` : a).join(' ');
|
|
@@ -2420,11 +2620,12 @@ export async function runOrchestratorInDocker(context, executor, config, options
|
|
|
2420
2620
|
const containerName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}-${(orchestratorName).replace(/[^a-zA-Z0-9._-]/g, '-')}`;
|
|
2421
2621
|
const imageName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}:latest`;
|
|
2422
2622
|
try {
|
|
2423
|
-
// Check Docker is running
|
|
2424
|
-
|
|
2623
|
+
// Check Docker is running (TKT-081: fast detection with diagnostic info)
|
|
2624
|
+
const dockerStatus = checkDockerDaemon();
|
|
2625
|
+
if (!dockerStatus.available) {
|
|
2425
2626
|
return {
|
|
2426
2627
|
success: false,
|
|
2427
|
-
error:
|
|
2628
|
+
error: `Docker daemon is not available. ${dockerStatus.message}`,
|
|
2428
2629
|
};
|
|
2429
2630
|
}
|
|
2430
2631
|
// Check if container already exists and is running
|
|
@@ -2584,8 +2785,10 @@ export async function runOrchestratorInDocker(context, executor, config, options
|
|
|
2584
2785
|
const skipPermissions = config.permissionMode === 'danger';
|
|
2585
2786
|
const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
|
|
2586
2787
|
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
2788
|
+
// TKT-053: Disable plan mode for background agents — prevents silent stalls
|
|
2789
|
+
const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
|
|
2587
2790
|
const executorCmd = executor === 'claude-code'
|
|
2588
|
-
? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`
|
|
2791
|
+
? `claude ${permissionsFlag}${effortFlag}${disallowPlanFlag}"$(cat ${promptPath})"`
|
|
2589
2792
|
: `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`;
|
|
2590
2793
|
// Build tmux session name (reuses the same name as host tmux for consistency)
|
|
2591
2794
|
const tmuxSessionName = options?.sessionName || containerName;
|
|
@@ -2602,7 +2805,7 @@ export async function runOrchestratorInDocker(context, executor, config, options
|
|
|
2602
2805
|
const scriptContent = `#!/bin/bash
|
|
2603
2806
|
cd /hq
|
|
2604
2807
|
unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT
|
|
2605
|
-
${executor === 'claude-code' ? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"` : `claude "$(cat ${promptPath})"`}
|
|
2808
|
+
${executor === 'claude-code' ? `claude ${permissionsFlag}${effortFlag}${disallowPlanFlag}"$(cat ${promptPath})"` : `claude "$(cat ${promptPath})"`}
|
|
2606
2809
|
echo ""
|
|
2607
2810
|
echo "Orchestrator complete. Press Enter to close."
|
|
2608
2811
|
exec bash
|
|
@@ -2712,19 +2915,125 @@ exec $SHELL
|
|
|
2712
2915
|
}
|
|
2713
2916
|
}
|
|
2714
2917
|
// =============================================================================
|
|
2715
|
-
//
|
|
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)
|
|
2716
3018
|
// =============================================================================
|
|
2717
|
-
|
|
2718
|
-
|
|
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;
|
|
2719
3028
|
if (!targetHost) {
|
|
2720
3029
|
return {
|
|
2721
3030
|
success: false,
|
|
2722
|
-
error: 'No
|
|
3031
|
+
error: 'No cloud host specified. Use --host or configure execution.cloud.default_host',
|
|
2723
3032
|
};
|
|
2724
3033
|
}
|
|
2725
3034
|
const prompt = buildPrompt(context);
|
|
2726
|
-
const user =
|
|
2727
|
-
const keyPath =
|
|
3035
|
+
const user = cloudConfig.user;
|
|
3036
|
+
const keyPath = cloudConfig.keyPath;
|
|
2728
3037
|
const remoteWorkspace = `/workspace/${context.agentName}`;
|
|
2729
3038
|
try {
|
|
2730
3039
|
// Build SSH options
|
|
@@ -2733,7 +3042,7 @@ export async function runVm(context, executor, config, host) {
|
|
|
2733
3042
|
sshOpts = `-i "${keyPath}"`;
|
|
2734
3043
|
}
|
|
2735
3044
|
// Sync worktree to remote
|
|
2736
|
-
if (
|
|
3045
|
+
if (cloudConfig.syncMethod === 'rsync') {
|
|
2737
3046
|
let rsyncCmd = `rsync -avz`;
|
|
2738
3047
|
if (keyPath) {
|
|
2739
3048
|
rsyncCmd += ` -e "ssh -i ${keyPath}"`;
|
|
@@ -2747,7 +3056,7 @@ export async function runVm(context, executor, config, host) {
|
|
|
2747
3056
|
const gitPullCmd = `cd ${remoteWorkspace} && git fetch && git checkout ${context.branch}`;
|
|
2748
3057
|
execSync(`ssh ${sshOpts} ${user}@${targetHost} "${gitPullCmd}"`, { stdio: 'pipe' });
|
|
2749
3058
|
}
|
|
2750
|
-
// Validate Codex mode:
|
|
3059
|
+
// Validate Codex mode: Cloud runner is always non-tty (SSH + nohup)
|
|
2751
3060
|
if (executor === 'codex') {
|
|
2752
3061
|
const codexPermission = config.permissionMode;
|
|
2753
3062
|
const modeError = validateCodexMode(codexPermission, 'non-tty');
|
|
@@ -2761,7 +3070,8 @@ export async function runVm(context, executor, config, host) {
|
|
|
2761
3070
|
// Build the remote command based on executor type
|
|
2762
3071
|
let remoteCmd;
|
|
2763
3072
|
if (isClaudeExecutor(executor)) {
|
|
2764
|
-
|
|
3073
|
+
// TKT-053: Disable plan mode — VM runner is always nohup (no user to approve)
|
|
3074
|
+
remoteCmd = `cd ${remoteWorkspace} && ${executorCmd} --print --disallowedTools EnterPlanMode '${escapedPrompt}'`;
|
|
2765
3075
|
}
|
|
2766
3076
|
else {
|
|
2767
3077
|
const argsStr = executorArgs.map(a => a === escapedPrompt ? `'${escapedPrompt}'` : a).join(' ');
|
|
@@ -2778,29 +3088,38 @@ export async function runVm(context, executor, config, host) {
|
|
|
2778
3088
|
catch (error) {
|
|
2779
3089
|
return {
|
|
2780
3090
|
success: false,
|
|
2781
|
-
error: error instanceof Error ? error.message : 'Failed to execute on
|
|
3091
|
+
error: error instanceof Error ? error.message : 'Failed to execute on cloud',
|
|
2782
3092
|
};
|
|
2783
3093
|
}
|
|
2784
3094
|
}
|
|
3095
|
+
/** @deprecated Use runCloud instead */
|
|
3096
|
+
export const runVm = runCloud;
|
|
2785
3097
|
// =============================================================================
|
|
2786
3098
|
// Runner Dispatcher
|
|
2787
3099
|
// =============================================================================
|
|
2788
3100
|
export async function runExecution(environment, context, executor, config = DEFAULT_EXECUTION_CONFIG, options) {
|
|
2789
|
-
// Ensure
|
|
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)
|
|
2790
3108
|
// Docker uses claude-credentials volume, devcontainer runs inside container
|
|
2791
|
-
if (
|
|
3109
|
+
if (normalizedEnv === 'host' || normalizedEnv === 'sandbox') {
|
|
2792
3110
|
await ensureTmuxServerHasKeychainAccess();
|
|
2793
3111
|
}
|
|
2794
|
-
switch (
|
|
3112
|
+
switch (normalizedEnv) {
|
|
2795
3113
|
case 'devcontainer':
|
|
2796
3114
|
return runDevcontainer(context, executor, config, options?.displayMode, options?.sessionManager);
|
|
2797
3115
|
case 'host':
|
|
2798
|
-
// Host uses tmux for session persistence (same as devcontainer)
|
|
2799
3116
|
return runHost(context, executor, config, options?.displayMode);
|
|
3117
|
+
case 'sandbox':
|
|
3118
|
+
return runSandbox(context, executor, config, options?.displayMode);
|
|
2800
3119
|
case 'docker':
|
|
2801
3120
|
return runDocker(context, executor, config);
|
|
2802
|
-
case '
|
|
2803
|
-
return
|
|
3121
|
+
case 'cloud':
|
|
3122
|
+
return runCloud(context, executor, config, options?.host);
|
|
2804
3123
|
default:
|
|
2805
3124
|
return { success: false, error: `Unknown execution environment: ${environment}` };
|
|
2806
3125
|
}
|