@proletariat/cli 0.3.56 → 0.3.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -1
- package/bin/validate-better-sqlite3.cjs +44 -5
- package/dist/commands/dashboard.d.ts +38 -0
- package/dist/commands/dashboard.js +352 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/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/media/index.d.ts +14 -0
- package/dist/commands/media/index.js +85 -0
- package/dist/commands/media/index.js.map +1 -0
- package/dist/commands/media/list.d.ts +15 -0
- 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/media/remove.d.ts +18 -0
- package/dist/commands/media/remove.js +101 -0
- package/dist/commands/media/remove.js.map +1 -0
- package/dist/commands/media/show.d.ts +17 -0
- package/dist/commands/media/show.js +122 -0
- package/dist/commands/media/show.js.map +1 -0
- package/dist/commands/orchestrator/start.js +6 -0
- package/dist/commands/orchestrator/start.js.map +1 -1
- package/dist/commands/repo/fix-remotes.d.ts +14 -0
- package/dist/commands/repo/fix-remotes.js +154 -0
- package/dist/commands/repo/fix-remotes.js.map +1 -0
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +42 -17
- package/dist/commands/work/start.js.map +1 -1
- package/dist/lib/agents/commands.js +5 -7
- package/dist/lib/agents/commands.js.map +1 -1
- package/dist/lib/agents/index.js +6 -7
- package/dist/lib/agents/index.js.map +1 -1
- 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/database/native-validation.d.ts +2 -0
- package/dist/lib/database/native-validation.js +30 -1
- package/dist/lib/database/native-validation.js.map +1 -1
- package/dist/lib/execution/runners.d.ts +24 -1
- package/dist/lib/execution/runners.js +309 -98
- package/dist/lib/execution/runners.js.map +1 -1
- package/dist/lib/execution/spawner.d.ts +2 -2
- package/dist/lib/execution/spawner.js +42 -23
- package/dist/lib/execution/spawner.js.map +1 -1
- package/dist/lib/execution/types.d.ts +2 -0
- package/dist/lib/execution/types.js.map +1 -1
- package/dist/lib/external-issues/shortcut.js +2 -1
- package/dist/lib/external-issues/shortcut.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/repos/git.d.ts +44 -0
- package/dist/lib/repos/git.js +127 -0
- package/dist/lib/repos/git.js.map +1 -1
- package/dist/lib/repos/index.js +35 -2
- package/dist/lib/repos/index.js.map +1 -1
- package/dist/lib/work-source/config.d.ts +5 -0
- package/dist/lib/work-source/config.js +19 -0
- 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 +3612 -3130
- package/package.json +1 -1
|
@@ -8,6 +8,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
|
+
import { fileURLToPath } from 'node:url';
|
|
11
12
|
import { DEFAULT_EXECUTION_CONFIG, } from './types.js';
|
|
12
13
|
import { getSetTitleCommands } from '../terminal.js';
|
|
13
14
|
import { readDevcontainerJson, generateOrchestratorDockerfile } from './devcontainer.js';
|
|
@@ -387,35 +388,201 @@ export function runExecutorPreflight(environment, executor, options) {
|
|
|
387
388
|
}
|
|
388
389
|
return { ok: true };
|
|
389
390
|
}
|
|
391
|
+
const INTEGRATION_COMMANDS = [
|
|
392
|
+
{
|
|
393
|
+
provider: 'asana',
|
|
394
|
+
displayName: 'Asana',
|
|
395
|
+
commands: [
|
|
396
|
+
'prlt asana connect — authenticate with Asana',
|
|
397
|
+
'prlt asana sync --ticket TKT-XXX --create-missing --project <gid> — sync a PMO ticket to Asana',
|
|
398
|
+
'prlt asana import — import Asana tasks into PMO',
|
|
399
|
+
],
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
provider: 'linear',
|
|
403
|
+
displayName: 'Linear',
|
|
404
|
+
commands: [
|
|
405
|
+
'prlt linear connect — authenticate with Linear',
|
|
406
|
+
'prlt linear sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Linear',
|
|
407
|
+
'prlt linear import — import Linear issues into PMO',
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
provider: 'jira',
|
|
412
|
+
displayName: 'Jira',
|
|
413
|
+
commands: [
|
|
414
|
+
'prlt jira connect — authenticate with Jira',
|
|
415
|
+
'prlt jira sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Jira',
|
|
416
|
+
'prlt jira import — import Jira issues into PMO',
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
provider: 'shortcut',
|
|
421
|
+
displayName: 'Shortcut',
|
|
422
|
+
commands: [
|
|
423
|
+
'prlt shortcut connect — authenticate with Shortcut',
|
|
424
|
+
'prlt shortcut sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Shortcut',
|
|
425
|
+
'prlt shortcut import — import Shortcut stories into PMO',
|
|
426
|
+
],
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
provider: 'monday',
|
|
430
|
+
displayName: 'Monday.com',
|
|
431
|
+
commands: [
|
|
432
|
+
'prlt monday connect — authenticate with Monday.com',
|
|
433
|
+
'prlt monday sync --ticket TKT-XXX --create-missing — sync a PMO ticket to Monday.com',
|
|
434
|
+
],
|
|
435
|
+
},
|
|
436
|
+
];
|
|
390
437
|
/**
|
|
391
|
-
* Build the
|
|
392
|
-
*
|
|
393
|
-
*
|
|
438
|
+
* Build the integration commands section for agent prompts.
|
|
439
|
+
* Only includes integrations that are actually connected/configured.
|
|
440
|
+
* Returns empty string if no integrations are connected.
|
|
394
441
|
*/
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
442
|
+
function buildIntegrationCommandsSection(connectedIntegrations) {
|
|
443
|
+
if (!connectedIntegrations || connectedIntegrations.length === 0)
|
|
444
|
+
return '';
|
|
445
|
+
const connected = INTEGRATION_COMMANDS.filter(ic => connectedIntegrations.includes(ic.provider));
|
|
446
|
+
if (connected.length === 0)
|
|
447
|
+
return '';
|
|
448
|
+
let section = `## Integration Commands\n\n`;
|
|
449
|
+
section += `The following external integrations are connected. Use these prlt commands to interact with them.\n\n`;
|
|
450
|
+
for (const integration of connected) {
|
|
451
|
+
section += `### ${integration.displayName}\n`;
|
|
452
|
+
for (const cmd of integration.commands) {
|
|
453
|
+
section += `- \`${cmd.split(' — ')[0]}\` — ${cmd.split(' — ')[1] || ''}\n`;
|
|
454
|
+
}
|
|
455
|
+
section += '\n';
|
|
456
|
+
}
|
|
457
|
+
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`;
|
|
458
|
+
return section;
|
|
459
|
+
}
|
|
460
|
+
const ORCHESTRATOR_COMMAND_REGISTRY = [
|
|
461
|
+
{
|
|
462
|
+
title: 'Agent Lifecycle',
|
|
463
|
+
commands: [
|
|
464
|
+
{ cmd: 'prlt work start <ticket> --ephemeral --skip-permissions --create-pr --display background --action implement --run-on-host --yes', desc: 'Spawn an agent for a ticket', checkPath: 'work/start' },
|
|
465
|
+
{ cmd: 'prlt session list', desc: 'List running sessions', checkPath: 'session/list' },
|
|
466
|
+
{ cmd: 'prlt session inspect <agent>', desc: 'Inspect session details', checkPath: 'session/inspect' },
|
|
467
|
+
{ cmd: 'prlt session poke <agent> \'message\'', desc: 'Send message to agent', checkPath: 'session/poke' },
|
|
468
|
+
{ cmd: 'prlt session peek <agent> --lines 200', desc: 'Read agent output', checkPath: 'session/peek' },
|
|
469
|
+
{ cmd: 'prlt session health', desc: 'Check health of all sessions', checkPath: 'session/health' },
|
|
470
|
+
{ cmd: 'prlt session restart <agent>', desc: 'Restart a stuck agent', checkPath: 'session/restart' },
|
|
471
|
+
{ cmd: 'prlt session exec <agent> -- git status', desc: 'Run command in agent context', checkPath: 'session/exec' },
|
|
472
|
+
{ cmd: 'prlt session prune', desc: 'Clean up dead sessions', checkPath: 'session/prune' },
|
|
473
|
+
],
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
title: 'Board Management',
|
|
477
|
+
commands: [
|
|
478
|
+
{ cmd: 'prlt board view', desc: 'View the board', checkPath: 'board/view' },
|
|
479
|
+
{ cmd: 'prlt ticket list', desc: 'List tickets', checkPath: 'ticket/list' },
|
|
480
|
+
{ cmd: 'prlt ticket show <id>', desc: 'Show ticket details', checkPath: 'ticket/show' },
|
|
481
|
+
{ cmd: 'prlt ticket create --title \'x\' --description \'y\'', desc: 'Create a ticket', checkPath: 'ticket/create' },
|
|
482
|
+
{ cmd: 'prlt ticket edit <id> --title \'...\' --add-ac \'...\'', desc: 'Edit ticket fields', checkPath: 'ticket/edit' },
|
|
483
|
+
],
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
title: 'PR Workflow',
|
|
487
|
+
commands: [
|
|
488
|
+
{ cmd: 'gh pr list', desc: 'List open PRs' },
|
|
489
|
+
{ cmd: 'gh pr view <num>', desc: 'View PR details' },
|
|
490
|
+
{ cmd: 'gh pr checks <num>', desc: 'Check CI status' },
|
|
491
|
+
{ cmd: 'gh pr merge <num> --squash', desc: 'Merge PR (squash only)' },
|
|
492
|
+
],
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
const ORCHESTRATOR_ANTI_PATTERNS = [
|
|
496
|
+
{ bad: 'docker exec <container> ...', good: 'prlt session exec', checkPath: 'session/exec' },
|
|
497
|
+
{ bad: 'tmux send-keys ...', good: 'prlt session poke', checkPath: 'session/poke' },
|
|
498
|
+
{ bad: 'tmux capture-pane ...', good: 'prlt session peek', checkPath: 'session/peek' },
|
|
499
|
+
{ bad: 'Direct git operations on agent worktrees', good: 'prlt session exec', checkPath: 'session/exec' },
|
|
500
|
+
];
|
|
501
|
+
/**
|
|
502
|
+
* Resolve the commands directory for dynamic command availability checks.
|
|
503
|
+
* Looks for compiled command files under dist/commands/.
|
|
504
|
+
*/
|
|
505
|
+
let _commandsDir = null;
|
|
506
|
+
function getCommandsDir() {
|
|
507
|
+
if (_commandsDir === null) {
|
|
508
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
509
|
+
// From dist/lib/execution/runners.js → dist/commands/
|
|
510
|
+
_commandsDir = path.resolve(path.dirname(currentFile), '..', '..', 'commands');
|
|
511
|
+
}
|
|
512
|
+
return _commandsDir;
|
|
513
|
+
}
|
|
514
|
+
function isCommandAvailable(checkPath) {
|
|
515
|
+
const dir = getCommandsDir();
|
|
516
|
+
// Check for compiled .js file or directory (which would contain index.js)
|
|
517
|
+
return fs.existsSync(path.join(dir, `${checkPath}.js`)) || fs.existsSync(path.join(dir, checkPath));
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Build the dynamic command reference section for the orchestrator prompt.
|
|
521
|
+
* Only includes commands that are actually available in this build.
|
|
522
|
+
*/
|
|
523
|
+
function buildOrchestratorCommandReference() {
|
|
524
|
+
let ref = '';
|
|
525
|
+
for (const category of ORCHESTRATOR_COMMAND_REGISTRY) {
|
|
526
|
+
const available = category.commands.filter(c => !c.checkPath || isCommandAvailable(c.checkPath));
|
|
527
|
+
if (available.length === 0)
|
|
528
|
+
continue;
|
|
529
|
+
ref += `### ${category.title}\n`;
|
|
530
|
+
for (const cmd of available) {
|
|
531
|
+
ref += `- \`${cmd.cmd}\` — ${cmd.desc}\n`;
|
|
532
|
+
}
|
|
533
|
+
ref += '\n';
|
|
534
|
+
}
|
|
535
|
+
return ref;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Build the anti-patterns section for the orchestrator prompt.
|
|
539
|
+
* Only includes anti-patterns where the prlt replacement is available.
|
|
540
|
+
*/
|
|
541
|
+
function buildOrchestratorAntiPatterns() {
|
|
542
|
+
const available = ORCHESTRATOR_ANTI_PATTERNS.filter(ap => !ap.checkPath || isCommandAvailable(ap.checkPath));
|
|
543
|
+
if (available.length === 0)
|
|
544
|
+
return '';
|
|
545
|
+
let section = `## Anti-Patterns — NEVER DO\n\n`;
|
|
546
|
+
for (const ap of available) {
|
|
547
|
+
section += `- \`${ap.bad}\` → use \`${ap.good}\` instead\n`;
|
|
548
|
+
}
|
|
549
|
+
section += `\n`;
|
|
550
|
+
return section;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Build the shared orchestrator prompt body (role, runtime, commands, anti-patterns).
|
|
554
|
+
* Used by both buildOrchestratorSystemPrompt and buildOrchestratorPrompt.
|
|
555
|
+
*/
|
|
556
|
+
function buildOrchestratorBody(hqName, context) {
|
|
557
|
+
let prompt = '';
|
|
558
|
+
// Runtime declaration
|
|
559
|
+
prompt += `## prlt Is Your Orchestration Runtime\n\n`;
|
|
560
|
+
prompt += `prlt is your orchestration runtime. NEVER use raw docker exec, tmux send-keys, or direct container access. `;
|
|
561
|
+
prompt += `All orchestration goes through prlt. Every agent interaction, session management, and board operation `;
|
|
562
|
+
prompt += `has a dedicated prlt command. Using raw infrastructure commands bypasses session tracking, breaks `;
|
|
563
|
+
prompt += `health monitoring, and creates orphaned processes.\n\n`;
|
|
564
|
+
// Role
|
|
401
565
|
prompt += `## Your Role\n`;
|
|
402
566
|
prompt += `- Plan and prioritize work across the board\n`;
|
|
403
567
|
prompt += `- Delegate implementation to agents via \`prlt work start\`\n`;
|
|
404
568
|
prompt += `- Monitor agent progress and review completed work\n`;
|
|
405
569
|
prompt += `- Merge completed PRs via \`gh pr merge --squash\`\n`;
|
|
406
570
|
prompt += `- Never write code or make changes to source files yourself\n\n`;
|
|
407
|
-
|
|
408
|
-
prompt +=
|
|
409
|
-
prompt +=
|
|
410
|
-
|
|
411
|
-
prompt += `- **PRs/CI**: \`gh pr list\`, \`gh pr view <num>\`, \`gh pr checks <num>\`\n`;
|
|
412
|
-
prompt += `- All prlt MCP tools are also available\n\n`;
|
|
571
|
+
// Command reference (dynamically generated)
|
|
572
|
+
prompt += `## Command Reference\n\n`;
|
|
573
|
+
prompt += buildOrchestratorCommandReference();
|
|
574
|
+
// Spawning agents (detailed example)
|
|
413
575
|
prompt += `## Spawning Agents\n`;
|
|
414
576
|
prompt += `\`\`\`\n`;
|
|
415
577
|
prompt += `script -q /dev/null prlt work start TKT-XXXX --ephemeral --skip-permissions --create-pr --display background --action implement --run-on-host --yes\n`;
|
|
416
578
|
prompt += `\`\`\`\n`;
|
|
417
579
|
prompt += `- Review: \`--action review-comment\`\n`;
|
|
418
580
|
prompt += `- Fix: \`--action review-fix\`\n\n`;
|
|
581
|
+
// Anti-patterns (dynamically generated)
|
|
582
|
+
prompt += buildOrchestratorAntiPatterns();
|
|
583
|
+
// Integration commands (only for connected integrations)
|
|
584
|
+
prompt += buildIntegrationCommandsSection(context.connectedIntegrations);
|
|
585
|
+
// Workflow
|
|
419
586
|
prompt += `## Workflow\n`;
|
|
420
587
|
prompt += `- Squash merge only: \`gh pr merge --squash\`\n`;
|
|
421
588
|
prompt += `- After merging: subsequent PRs from parallel agents will need rebase\n`;
|
|
@@ -437,6 +604,19 @@ export function buildOrchestratorSystemPrompt(context) {
|
|
|
437
604
|
}
|
|
438
605
|
return prompt;
|
|
439
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Build the system prompt for orchestrator sessions.
|
|
609
|
+
* This is injected via Claude Code's --system-prompt flag so the orchestrator
|
|
610
|
+
* knows its role immediately without relying on CLAUDE.md.
|
|
611
|
+
*/
|
|
612
|
+
export function buildOrchestratorSystemPrompt(context) {
|
|
613
|
+
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`;
|
|
617
|
+
prompt += buildOrchestratorBody(hqName, context);
|
|
618
|
+
return prompt;
|
|
619
|
+
}
|
|
440
620
|
function buildOrchestratorPrompt(context) {
|
|
441
621
|
// Full prompt including role context — used for non-Claude executors that
|
|
442
622
|
// don't support --system-prompt. For Claude Code, runHost() splits this into
|
|
@@ -444,43 +624,7 @@ function buildOrchestratorPrompt(context) {
|
|
|
444
624
|
const hqName = context.hqName || 'workspace';
|
|
445
625
|
let prompt = `# Orchestrator: ${hqName}\n\n`;
|
|
446
626
|
prompt += `You are the orchestrator for the **${hqName}** workspace using the prlt ecosystem.\n\n`;
|
|
447
|
-
prompt +=
|
|
448
|
-
prompt += `- Plan and prioritize work across the board\n`;
|
|
449
|
-
prompt += `- Delegate implementation to agents via \`prlt work start\`\n`;
|
|
450
|
-
prompt += `- Monitor agent progress and review completed work\n`;
|
|
451
|
-
prompt += `- Merge completed PRs via \`gh pr merge --squash\`\n`;
|
|
452
|
-
prompt += `- Never write code or make changes to source files yourself\n\n`;
|
|
453
|
-
prompt += `## Discovering State\n`;
|
|
454
|
-
prompt += `Always discover current state dynamically — do NOT rely on static context files:\n`;
|
|
455
|
-
prompt += `- **Board**: \`prlt board view\`, \`prlt ticket list\`, \`prlt ticket show <id>\`\n`;
|
|
456
|
-
prompt += `- **Agents**: \`prlt session list\`, \`prlt session peek <session>\`, \`prlt work status\`\n`;
|
|
457
|
-
prompt += `- **PRs/CI**: \`gh pr list\`, \`gh pr view <num>\`, \`gh pr checks <num>\`\n`;
|
|
458
|
-
prompt += `- All prlt MCP tools are also available\n\n`;
|
|
459
|
-
prompt += `## Spawning Agents\n`;
|
|
460
|
-
prompt += `\`\`\`\n`;
|
|
461
|
-
prompt += `script -q /dev/null prlt work start TKT-XXXX --ephemeral --skip-permissions --create-pr --display background --action implement --run-on-host --yes\n`;
|
|
462
|
-
prompt += `\`\`\`\n`;
|
|
463
|
-
prompt += `- Review: \`--action review-comment\`\n`;
|
|
464
|
-
prompt += `- Fix: \`--action review-fix\`\n\n`;
|
|
465
|
-
prompt += `## Workflow\n`;
|
|
466
|
-
prompt += `- Squash merge only: \`gh pr merge --squash\`\n`;
|
|
467
|
-
prompt += `- After merging: subsequent PRs from parallel agents will need rebase\n`;
|
|
468
|
-
prompt += `- Kill stale sessions after their PRs are merged\n\n`;
|
|
469
|
-
// Load .orchestrator-context.md from HQ root if it exists
|
|
470
|
-
if (context.hqPath) {
|
|
471
|
-
const contextFilePath = path.join(context.hqPath, '.orchestrator-context.md');
|
|
472
|
-
if (fs.existsSync(contextFilePath)) {
|
|
473
|
-
try {
|
|
474
|
-
const contextContent = fs.readFileSync(contextFilePath, 'utf-8').trim();
|
|
475
|
-
if (contextContent) {
|
|
476
|
-
prompt += `## Workspace Context\n\n${contextContent}\n\n`;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
catch {
|
|
480
|
-
// Ignore read errors
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
627
|
+
prompt += buildOrchestratorBody(hqName, context);
|
|
484
628
|
// Include user's custom prompt or action content
|
|
485
629
|
if (context.actionPrompt) {
|
|
486
630
|
prompt += `## Instructions\n\n${context.actionPrompt}\n`;
|
|
@@ -533,6 +677,11 @@ function buildPrompt(context) {
|
|
|
533
677
|
}
|
|
534
678
|
// Note: Branch setup (fetch + checkout/create) is now handled programmatically
|
|
535
679
|
// in work/start.ts before the agent spawns, so no prompt instructions needed
|
|
680
|
+
// Integration commands (only for connected integrations)
|
|
681
|
+
const integrationSection = buildIntegrationCommandsSection(context.connectedIntegrations);
|
|
682
|
+
if (integrationSection) {
|
|
683
|
+
prompt += `\n${integrationSection}`;
|
|
684
|
+
}
|
|
536
685
|
// Additional instructions from --message flag (appended to any action)
|
|
537
686
|
if (context.customMessage) {
|
|
538
687
|
prompt += `\n## Additional Instructions\n\n${context.customMessage}\n`;
|
|
@@ -647,7 +796,8 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
647
796
|
}
|
|
648
797
|
// Build the executor command using getExecutorCommand() output
|
|
649
798
|
// For Claude Code, we also support outputMode and additional flags
|
|
650
|
-
// For
|
|
799
|
+
// For Codex, we use the codex adapter for deterministic command building (TKT-080)
|
|
800
|
+
// For other executors, we use the command as-is from getExecutorCommand()
|
|
651
801
|
let executorInvocation;
|
|
652
802
|
if (isClaudeExecutor(executor)) {
|
|
653
803
|
// Build flags based on config - Claude-specific flags
|
|
@@ -658,13 +808,26 @@ export async function runHost(context, executor, config, displayMode = 'terminal
|
|
|
658
808
|
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
659
809
|
// Orchestrator sessions inject their role via --system-prompt
|
|
660
810
|
const systemPromptFlag = systemPromptPath ? '--system-prompt "$(cat "$SYSTEM_PROMPT_PATH")" ' : '';
|
|
661
|
-
|
|
811
|
+
// TKT-053: Disable plan mode for background agents — prevents silent stalls
|
|
812
|
+
// when there's no user to approve the plan mode transition
|
|
813
|
+
const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
|
|
814
|
+
executorInvocation = `${cmd} ${permissionsFlag}${effortFlag}${printFlag}${disallowPlanFlag}${systemPromptFlag}"$(cat "$PROMPT_PATH")"`;
|
|
815
|
+
}
|
|
816
|
+
else if (executor === 'codex') {
|
|
817
|
+
// TKT-080: Use Codex adapter for deterministic command building.
|
|
818
|
+
// Uses PLACEHOLDER pattern for reliable prompt replacement (same as devcontainer runner).
|
|
819
|
+
const codexPermission = config.permissionMode;
|
|
820
|
+
const codexContext = resolveCodexExecutionContext(displayMode, config.outputMode);
|
|
821
|
+
const codexResult = getCodexCommand('PLACEHOLDER', codexPermission, codexContext);
|
|
822
|
+
const argsStr = codexResult.args.map(a => a === 'PLACEHOLDER' ? '"$(cat "$PROMPT_PATH")"' : a).join(' ');
|
|
823
|
+
executorInvocation = `${codexResult.cmd} ${argsStr}`;
|
|
662
824
|
}
|
|
663
825
|
else {
|
|
664
|
-
// Non-Claude executors: build command from getExecutorCommand() args
|
|
665
|
-
//
|
|
666
|
-
const
|
|
667
|
-
|
|
826
|
+
// Non-Claude, non-Codex executors: build command from getExecutorCommand() args
|
|
827
|
+
// Use PLACEHOLDER for reliable prompt replacement instead of fragile string comparison
|
|
828
|
+
const { cmd: execCmd, args: execArgs } = getExecutorCommand(executor, 'PLACEHOLDER', skipPermissions);
|
|
829
|
+
const argsWithFile = execArgs.map(a => a === 'PLACEHOLDER' ? '"$(cat "$PROMPT_PATH")"' : `"${a}"`);
|
|
830
|
+
executorInvocation = `${execCmd} ${argsWithFile.join(' ')}`;
|
|
668
831
|
}
|
|
669
832
|
// Build script that runs executor and keeps shell open after completion
|
|
670
833
|
const setTitleCmds = getSetTitleCommands(windowTitle);
|
|
@@ -976,30 +1139,71 @@ export function getGitHubToken() {
|
|
|
976
1139
|
export function isGitHubTokenAvailable() {
|
|
977
1140
|
return getGitHubToken() !== null;
|
|
978
1141
|
}
|
|
979
|
-
// =============================================================================
|
|
980
|
-
// Docker Status Check
|
|
981
|
-
// =============================================================================
|
|
982
1142
|
/**
|
|
983
|
-
* Check
|
|
984
|
-
*
|
|
985
|
-
* Uses
|
|
1143
|
+
* Check Docker daemon health with fast detection (TKT-081).
|
|
1144
|
+
*
|
|
1145
|
+
* Uses `docker ps` with a 5-second timeout to quickly detect:
|
|
1146
|
+
* - Docker not installed
|
|
1147
|
+
* - Docker installed but daemon unresponsive (stuck on license, initializing, 500 errors)
|
|
1148
|
+
* - Docker ready
|
|
1149
|
+
*
|
|
1150
|
+
* Total worst-case time: ~5 seconds (single attempt with timeout).
|
|
986
1151
|
*/
|
|
987
|
-
export function
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1152
|
+
export function checkDockerDaemon() {
|
|
1153
|
+
// First: is docker even installed?
|
|
1154
|
+
try {
|
|
1155
|
+
execSync('which docker', { stdio: 'pipe', timeout: 3000 });
|
|
1156
|
+
}
|
|
1157
|
+
catch {
|
|
1158
|
+
return {
|
|
1159
|
+
available: false,
|
|
1160
|
+
reason: 'not-installed',
|
|
1161
|
+
message: 'Docker is not installed.',
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
// Second: is the daemon responsive? Use `docker ps` — it's lightweight and
|
|
1165
|
+
// fails fast when the daemon returns 500s or hangs on GUI prompts.
|
|
1166
|
+
const timeout = 5000; // 5 seconds — enough for a healthy daemon, fast fail otherwise
|
|
1167
|
+
try {
|
|
1168
|
+
execSync('docker ps -q --no-trunc', { stdio: 'pipe', timeout });
|
|
1169
|
+
return {
|
|
1170
|
+
available: true,
|
|
1171
|
+
reason: 'ready',
|
|
1172
|
+
message: 'Docker daemon is ready.',
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
catch (error) {
|
|
1176
|
+
// Parse the error to give actionable feedback
|
|
1177
|
+
const stderr = error?.stderr?.toString() || '';
|
|
1178
|
+
const isTimeout = error?.killed === true;
|
|
1179
|
+
let message;
|
|
1180
|
+
if (isTimeout) {
|
|
1181
|
+
message = 'Docker daemon is not responding (timed out after 5s). Docker Desktop may be initializing or stuck — check for license/login prompts.';
|
|
994
1182
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
return false;
|
|
998
|
-
}
|
|
999
|
-
// Brief pause before retry
|
|
1183
|
+
else if (stderr.includes('500') || stderr.includes('Internal Server Error')) {
|
|
1184
|
+
message = 'Docker daemon is returning errors (500). Docker Desktop needs attention — check for license/login prompts.';
|
|
1000
1185
|
}
|
|
1186
|
+
else if (stderr.includes('connect') || stderr.includes('Cannot connect') || stderr.includes('Is the docker daemon running')) {
|
|
1187
|
+
message = 'Docker daemon is not running. Start Docker Desktop and try again.';
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
message = `Docker daemon is not ready: ${stderr.trim() || 'unknown error'}. Check Docker Desktop status.`;
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
available: false,
|
|
1194
|
+
reason: 'daemon-not-ready',
|
|
1195
|
+
message,
|
|
1196
|
+
};
|
|
1001
1197
|
}
|
|
1002
|
-
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Check if Docker daemon is running.
|
|
1201
|
+
* Returns true if Docker is available and responsive.
|
|
1202
|
+
*
|
|
1203
|
+
* For detailed diagnostics, use checkDockerDaemon() instead.
|
|
1204
|
+
*/
|
|
1205
|
+
export function isDockerRunning() {
|
|
1206
|
+
return checkDockerDaemon().available;
|
|
1003
1207
|
}
|
|
1004
1208
|
/**
|
|
1005
1209
|
* Check if the devcontainer CLI is installed.
|
|
@@ -1056,7 +1260,7 @@ function getImageName(agentName) {
|
|
|
1056
1260
|
*/
|
|
1057
1261
|
export function containerExists(containerName) {
|
|
1058
1262
|
try {
|
|
1059
|
-
execSync(`docker container inspect ${containerName}`, { stdio: 'pipe' });
|
|
1263
|
+
execSync(`docker container inspect ${containerName}`, { stdio: 'pipe', timeout: 5000 });
|
|
1060
1264
|
return true;
|
|
1061
1265
|
}
|
|
1062
1266
|
catch {
|
|
@@ -1068,7 +1272,7 @@ export function containerExists(containerName) {
|
|
|
1068
1272
|
*/
|
|
1069
1273
|
export function isContainerRunning(containerName) {
|
|
1070
1274
|
try {
|
|
1071
|
-
const status = execSync(`docker container inspect -f '{{.State.Running}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
1275
|
+
const status = execSync(`docker container inspect -f '{{.State.Running}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }).trim();
|
|
1072
1276
|
return status === 'true';
|
|
1073
1277
|
}
|
|
1074
1278
|
catch {
|
|
@@ -1080,7 +1284,7 @@ export function isContainerRunning(containerName) {
|
|
|
1080
1284
|
*/
|
|
1081
1285
|
export function getContainerId(containerName) {
|
|
1082
1286
|
try {
|
|
1083
|
-
const containerId = execSync(`docker container inspect -f '{{.Id}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
1287
|
+
const containerId = execSync(`docker container inspect -f '{{.Id}}' ${containerName}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }).trim();
|
|
1084
1288
|
return containerId ? containerId.substring(0, 12) : null;
|
|
1085
1289
|
}
|
|
1086
1290
|
catch {
|
|
@@ -1116,7 +1320,7 @@ function buildDockerImage(agentDir, imageName, buildArgs = {}) {
|
|
|
1116
1320
|
*/
|
|
1117
1321
|
function imageExists(imageName) {
|
|
1118
1322
|
try {
|
|
1119
|
-
execSync(`docker image inspect ${imageName}`, { stdio: 'pipe' });
|
|
1323
|
+
execSync(`docker image inspect ${imageName}`, { stdio: 'pipe', timeout: 5000 });
|
|
1120
1324
|
return true;
|
|
1121
1325
|
}
|
|
1122
1326
|
catch {
|
|
@@ -1347,7 +1551,7 @@ function ensureDockerContainer(context, config, executor = 'claude-code') {
|
|
|
1347
1551
|
// Container exists but is stopped - remove and recreate for fresh mounts
|
|
1348
1552
|
console.debug(`[runners:docker] Removing stopped container ${containerName} to create fresh one`);
|
|
1349
1553
|
try {
|
|
1350
|
-
execSync(`docker rm -f ${containerName}`, { stdio: 'pipe' });
|
|
1554
|
+
execSync(`docker rm -f ${containerName}`, { stdio: 'pipe', timeout: 10000 });
|
|
1351
1555
|
}
|
|
1352
1556
|
catch {
|
|
1353
1557
|
// Ignore removal errors
|
|
@@ -1509,7 +1713,9 @@ export function buildDevcontainerCommand(context, executor, promptFile, containe
|
|
|
1509
1713
|
const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
|
|
1510
1714
|
// --effort high: skips the effort level prompt for automated agents (TKT-1134)
|
|
1511
1715
|
const effortFlag = '--effort high ';
|
|
1512
|
-
|
|
1716
|
+
// TKT-053: Disable plan mode for background agents — prevents silent stalls
|
|
1717
|
+
const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
|
|
1718
|
+
executorCmd = `claude ${bypassTrustFlag}${permissionsFlag}${effortFlag}${printFlag}${disallowPlanFlag}"$(cat ${promptFile})"`;
|
|
1513
1719
|
}
|
|
1514
1720
|
else if (executor === 'codex') {
|
|
1515
1721
|
// Use Codex adapter for mode validation and deterministic command building.
|
|
@@ -1561,11 +1767,12 @@ export async function runDevcontainer(context, executor, config, displayMode = '
|
|
|
1561
1767
|
};
|
|
1562
1768
|
}
|
|
1563
1769
|
try {
|
|
1564
|
-
// Check if Docker is running
|
|
1565
|
-
|
|
1770
|
+
// Check if Docker is running (TKT-081: fast detection with diagnostic info)
|
|
1771
|
+
const dockerStatus = checkDockerDaemon();
|
|
1772
|
+
if (!dockerStatus.available) {
|
|
1566
1773
|
return {
|
|
1567
1774
|
success: false,
|
|
1568
|
-
error:
|
|
1775
|
+
error: `Docker daemon is not available. ${dockerStatus.message}`,
|
|
1569
1776
|
};
|
|
1570
1777
|
}
|
|
1571
1778
|
// Ensure GitHub token is available for git push operations
|
|
@@ -2260,13 +2467,12 @@ export async function runDocker(context, executor, config) {
|
|
|
2260
2467
|
const prompt = buildPrompt(context);
|
|
2261
2468
|
const containerName = `work-${context.ticketId}-${Date.now()}`;
|
|
2262
2469
|
try {
|
|
2263
|
-
// Check if docker is available
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
if (!isDockerRunning()) {
|
|
2470
|
+
// Check if docker is available and daemon is responsive (TKT-081)
|
|
2471
|
+
const dockerStatus = checkDockerDaemon();
|
|
2472
|
+
if (!dockerStatus.available) {
|
|
2267
2473
|
return {
|
|
2268
2474
|
success: false,
|
|
2269
|
-
error:
|
|
2475
|
+
error: `Docker daemon is not available. ${dockerStatus.message}`,
|
|
2270
2476
|
};
|
|
2271
2477
|
}
|
|
2272
2478
|
// Build docker run command
|
|
@@ -2298,7 +2504,8 @@ export async function runDocker(context, executor, config) {
|
|
|
2298
2504
|
// Non-Claude executors use their native command format from getExecutorCommand()
|
|
2299
2505
|
dockerCmd += ` ${config.docker.image}`;
|
|
2300
2506
|
if (isClaudeExecutor(executor)) {
|
|
2301
|
-
|
|
2507
|
+
// TKT-053: Disable plan mode — Docker runner is always detached (no user to approve)
|
|
2508
|
+
dockerCmd += ` ${cmd} --print --disallowedTools EnterPlanMode '${escapedPrompt}'`;
|
|
2302
2509
|
}
|
|
2303
2510
|
else {
|
|
2304
2511
|
const argsStr = args.map(a => a === escapedPrompt ? `'${escapedPrompt}'` : a).join(' ');
|
|
@@ -2347,11 +2554,12 @@ export async function runOrchestratorInDocker(context, executor, config, options
|
|
|
2347
2554
|
const containerName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}-${(orchestratorName).replace(/[^a-zA-Z0-9._-]/g, '-')}`;
|
|
2348
2555
|
const imageName = `prlt-orchestrator-${(hqName).replace(/[^a-zA-Z0-9._-]/g, '-')}:latest`;
|
|
2349
2556
|
try {
|
|
2350
|
-
// Check Docker is running
|
|
2351
|
-
|
|
2557
|
+
// Check Docker is running (TKT-081: fast detection with diagnostic info)
|
|
2558
|
+
const dockerStatus = checkDockerDaemon();
|
|
2559
|
+
if (!dockerStatus.available) {
|
|
2352
2560
|
return {
|
|
2353
2561
|
success: false,
|
|
2354
|
-
error:
|
|
2562
|
+
error: `Docker daemon is not available. ${dockerStatus.message}`,
|
|
2355
2563
|
};
|
|
2356
2564
|
}
|
|
2357
2565
|
// Check if container already exists and is running
|
|
@@ -2511,8 +2719,10 @@ export async function runOrchestratorInDocker(context, executor, config, options
|
|
|
2511
2719
|
const skipPermissions = config.permissionMode === 'danger';
|
|
2512
2720
|
const permissionsFlag = skipPermissions ? '--dangerously-skip-permissions ' : '';
|
|
2513
2721
|
const effortFlag = skipPermissions ? '--effort high ' : '';
|
|
2722
|
+
// TKT-053: Disable plan mode for background agents — prevents silent stalls
|
|
2723
|
+
const disallowPlanFlag = displayMode === 'background' ? '--disallowedTools EnterPlanMode ' : '';
|
|
2514
2724
|
const executorCmd = executor === 'claude-code'
|
|
2515
|
-
? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`
|
|
2725
|
+
? `claude ${permissionsFlag}${effortFlag}${disallowPlanFlag}"$(cat ${promptPath})"`
|
|
2516
2726
|
: `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"`;
|
|
2517
2727
|
// Build tmux session name (reuses the same name as host tmux for consistency)
|
|
2518
2728
|
const tmuxSessionName = options?.sessionName || containerName;
|
|
@@ -2529,7 +2739,7 @@ export async function runOrchestratorInDocker(context, executor, config, options
|
|
|
2529
2739
|
const scriptContent = `#!/bin/bash
|
|
2530
2740
|
cd /hq
|
|
2531
2741
|
unset CLAUDECODE CLAUDE_CODE_ENTRYPOINT
|
|
2532
|
-
${executor === 'claude-code' ? `claude ${permissionsFlag}${effortFlag}"$(cat ${promptPath})"` : `claude "$(cat ${promptPath})"`}
|
|
2742
|
+
${executor === 'claude-code' ? `claude ${permissionsFlag}${effortFlag}${disallowPlanFlag}"$(cat ${promptPath})"` : `claude "$(cat ${promptPath})"`}
|
|
2533
2743
|
echo ""
|
|
2534
2744
|
echo "Orchestrator complete. Press Enter to close."
|
|
2535
2745
|
exec bash
|
|
@@ -2688,7 +2898,8 @@ export async function runVm(context, executor, config, host) {
|
|
|
2688
2898
|
// Build the remote command based on executor type
|
|
2689
2899
|
let remoteCmd;
|
|
2690
2900
|
if (isClaudeExecutor(executor)) {
|
|
2691
|
-
|
|
2901
|
+
// TKT-053: Disable plan mode — VM runner is always nohup (no user to approve)
|
|
2902
|
+
remoteCmd = `cd ${remoteWorkspace} && ${executorCmd} --print --disallowedTools EnterPlanMode '${escapedPrompt}'`;
|
|
2692
2903
|
}
|
|
2693
2904
|
else {
|
|
2694
2905
|
const argsStr = executorArgs.map(a => a === escapedPrompt ? `'${escapedPrompt}'` : a).join(' ');
|