@proletariat/cli 0.3.14 → 0.3.15
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 +15 -8
- package/dist/commands/agent/staff/add.d.ts +1 -0
- package/dist/commands/agent/staff/add.js +5 -0
- package/dist/commands/work/revise.js +8 -14
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +7 -0
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +19 -33
- package/dist/lib/agents/commands.d.ts +5 -1
- package/dist/lib/agents/commands.js +48 -21
- package/dist/lib/agents/index.d.ts +5 -1
- package/dist/lib/agents/index.js +165 -107
- package/dist/lib/database/index.d.ts +4 -2
- package/dist/lib/database/index.js +29 -14
- package/dist/lib/execution/context.d.ts +24 -0
- package/dist/lib/execution/context.js +55 -0
- package/dist/lib/execution/devcontainer.d.ts +3 -0
- package/dist/lib/execution/devcontainer.js +38 -22
- package/dist/lib/execution/runners.js +9 -67
- package/dist/lib/execution/spawner.js +5 -18
- package/dist/lib/execution/types.d.ts +1 -0
- package/oclif.manifest.json +1801 -1783
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ import { parseChannel } from '../workspace-config.js';
|
|
|
17
17
|
*/
|
|
18
18
|
export function generateDevcontainerJson(options, config) {
|
|
19
19
|
const cfg = config || DEFAULT_EXECUTION_CONFIG;
|
|
20
|
+
const mountMode = options.mountMode || 'worktree'; // Default to worktree mode
|
|
20
21
|
// Parse the channel to determine registry and version
|
|
21
22
|
const channel = parseChannel(options.prltChannel || 'npm');
|
|
22
23
|
const useMount = channel.registry === 'mount';
|
|
@@ -34,6 +35,34 @@ export function generateDevcontainerJson(options, config) {
|
|
|
34
35
|
if (channel.registry === 'gh') {
|
|
35
36
|
buildArgs.GITHUB_TOKEN = '${localEnv:GITHUB_TOKEN}';
|
|
36
37
|
}
|
|
38
|
+
// Build mounts array - parent repo mounts only needed for worktree mode
|
|
39
|
+
const mounts = [
|
|
40
|
+
'source=${localWorkspaceFolder},target=/workspace,type=bind',
|
|
41
|
+
'source=claude-bash-history,target=/commandhistory,type=volume',
|
|
42
|
+
'source=claude-credentials,target=/home/node/.claude,type=volume',
|
|
43
|
+
// NOTE: ~/.claude.json is COPIED (not mounted) to /workspace/.claude.json
|
|
44
|
+
// to avoid corruption from concurrent writes by multiple containers
|
|
45
|
+
// NOTE: SSH agent socket mounting doesn't work reliably on Docker Desktop for Mac
|
|
46
|
+
// So we use HTTPS + token approach instead. The token is fetched fresh at spawn time.
|
|
47
|
+
'source=${localEnv:PRLT_HQ_PATH}/.proletariat,target=/hq/.proletariat,type=bind',
|
|
48
|
+
// PMO path can be anywhere (e.g., /hq/pmo or /hq/repos/myrepo/pmo)
|
|
49
|
+
// Use PRLT_PMO_PATH env var to mount the actual location to /hq/pmo
|
|
50
|
+
'source=${localEnv:PRLT_PMO_PATH},target=/hq/pmo,type=bind',
|
|
51
|
+
];
|
|
52
|
+
// Only add parent repo mounts for worktree mode
|
|
53
|
+
// Worktree .git files reference paths like /Users/.../repos/{repoName}/.git/worktrees/name
|
|
54
|
+
// These mounts make those paths accessible inside the container at /hq/repos/{repoName}
|
|
55
|
+
// Clone mode doesn't need this because each clone has its own self-contained .git directory
|
|
56
|
+
if (mountMode === 'worktree' && options.repoWorktrees) {
|
|
57
|
+
for (const repoName of options.repoWorktrees) {
|
|
58
|
+
mounts.push(`source=\${localEnv:PRLT_HQ_PATH}/repos/${repoName},target=/hq/repos/${repoName},type=bind`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// If using "mount" channel, mount local prlt build from PRLT_REPO_PATH
|
|
62
|
+
// The setup-prlt.sh script will detect /opt/prlt and configure the wrapper
|
|
63
|
+
if (useMount) {
|
|
64
|
+
mounts.push('source=${localEnv:PRLT_REPO_PATH},target=/opt/prlt,type=bind,readonly');
|
|
65
|
+
}
|
|
37
66
|
const devcontainerJson = {
|
|
38
67
|
name: `Agent: ${options.agentName}`,
|
|
39
68
|
build: {
|
|
@@ -59,26 +88,7 @@ export function generateDevcontainerJson(options, config) {
|
|
|
59
88
|
`--cpus=${options.cpus || cfg.devcontainer.cpus}`,
|
|
60
89
|
],
|
|
61
90
|
remoteUser: 'node',
|
|
62
|
-
mounts
|
|
63
|
-
'source=${localWorkspaceFolder},target=/workspace,type=bind',
|
|
64
|
-
'source=claude-bash-history,target=/commandhistory,type=volume',
|
|
65
|
-
'source=claude-credentials,target=/home/node/.claude,type=volume',
|
|
66
|
-
// NOTE: ~/.claude.json is COPIED (not mounted) to /workspace/.claude.json
|
|
67
|
-
// to avoid corruption from concurrent writes by multiple containers
|
|
68
|
-
// NOTE: SSH agent socket mounting doesn't work reliably on Docker Desktop for Mac
|
|
69
|
-
// So we use HTTPS + token approach instead. The token is fetched fresh at spawn time.
|
|
70
|
-
'source=${localEnv:PRLT_HQ_PATH}/.proletariat,target=/hq/.proletariat,type=bind',
|
|
71
|
-
// PMO path can be anywhere (e.g., /hq/pmo or /hq/repos/myrepo/pmo)
|
|
72
|
-
// Use PRLT_PMO_PATH env var to mount the actual location to /hq/pmo
|
|
73
|
-
'source=${localEnv:PRLT_PMO_PATH},target=/hq/pmo,type=bind',
|
|
74
|
-
// Mount each repo's directory so git worktrees can resolve their parent
|
|
75
|
-
// Worktree .git files reference paths like /Users/.../repos/{repoName}/.git/worktrees/name
|
|
76
|
-
// These mounts make those paths accessible inside the container at /hq/repos/{repoName}
|
|
77
|
-
...(options.repoWorktrees || []).map(repoName => `source=\${localEnv:PRLT_HQ_PATH}/repos/${repoName},target=/hq/repos/${repoName},type=bind`),
|
|
78
|
-
// If using "mount" channel, mount local prlt build from PRLT_REPO_PATH
|
|
79
|
-
// The setup-prlt.sh script will detect /opt/prlt and configure the wrapper
|
|
80
|
-
...(useMount ? ['source=${localEnv:PRLT_REPO_PATH},target=/opt/prlt,type=bind,readonly'] : []),
|
|
81
|
-
],
|
|
91
|
+
mounts,
|
|
82
92
|
containerEnv: {
|
|
83
93
|
DEVCONTAINER: 'true',
|
|
84
94
|
ANTHROPIC_API_KEY: '${localEnv:ANTHROPIC_API_KEY}',
|
|
@@ -89,6 +99,8 @@ export function generateDevcontainerJson(options, config) {
|
|
|
89
99
|
// Agent identity - allows agent to know its name and host path
|
|
90
100
|
PRLT_AGENT_NAME: options.agentName,
|
|
91
101
|
PRLT_HOST_PATH: options.agentDir,
|
|
102
|
+
// Mount mode - allows scripts to know if git wrapper is needed
|
|
103
|
+
PRLT_MOUNT_MODE: mountMode,
|
|
92
104
|
// /hq/.proletariat/bin contains prlt wrapper with ESM loader for native modules
|
|
93
105
|
PATH: '/hq/.proletariat/bin:/home/node/.npm-global/bin:/usr/local/bin:/usr/bin:/bin',
|
|
94
106
|
},
|
|
@@ -386,8 +398,12 @@ GITWRAPPER
|
|
|
386
398
|
echo "Git wrapper installed for worktree path translation"
|
|
387
399
|
}
|
|
388
400
|
|
|
389
|
-
# Set up git wrapper for worktree path translation
|
|
390
|
-
|
|
401
|
+
# Set up git wrapper for worktree path translation (only needed for worktree mount mode)
|
|
402
|
+
if [ "\${PRLT_MOUNT_MODE:-clone}" = "worktree" ]; then
|
|
403
|
+
setup_git_wrapper
|
|
404
|
+
else
|
|
405
|
+
echo "Clone mode: git wrapper not needed (self-contained .git directories)"
|
|
406
|
+
fi
|
|
391
407
|
|
|
392
408
|
# Copy Claude credentials from workspace to home (each container gets its own copy)
|
|
393
409
|
if [ -f "/workspace/.claude.json" ]; then
|
|
@@ -742,10 +742,15 @@ function createDockerContainer(context, containerName, imageName, config) {
|
|
|
742
742
|
...(context.hqPath ? [`-v "${context.hqPath}/.proletariat:/hq/.proletariat"`] : []),
|
|
743
743
|
// PMO path
|
|
744
744
|
...(context.pmoPath ? [`-v "${context.pmoPath}:/hq/pmo"`] : []),
|
|
745
|
+
// Mount parent repos for git worktree resolution
|
|
746
|
+
// Worktree .git files reference paths like /Users/.../repos/{repoName}/.git/worktrees/name
|
|
747
|
+
// These mounts make those paths accessible inside the container at /hq/repos/{repoName}
|
|
748
|
+
...(context.repoWorktrees || []).map(repoName => `-v "${context.hqPath}/repos/${repoName}:/hq/repos/${repoName}"`),
|
|
745
749
|
// Claude credentials - shared named volume (login once, all containers share)
|
|
746
750
|
`-v "claude-credentials:/home/node/.claude"`,
|
|
747
751
|
];
|
|
748
752
|
// Build environment flags
|
|
753
|
+
const hasWorktrees = context.repoWorktrees && context.repoWorktrees.length > 0;
|
|
749
754
|
const envVars = [
|
|
750
755
|
`-e DEVCONTAINER=true`,
|
|
751
756
|
`-e PRLT_HQ_PATH=/hq`,
|
|
@@ -756,6 +761,8 @@ function createDockerContainer(context, containerName, imageName, config) {
|
|
|
756
761
|
...(process.env.GH_TOKEN ? [`-e GH_TOKEN="${process.env.GH_TOKEN}"`] : []),
|
|
757
762
|
// NOTE: Do NOT pass CLAUDE_CODE_OAUTH_TOKEN - it overrides credentials file
|
|
758
763
|
// and setup-token generates invalid tokens. Use "prlt agent auth" instead.
|
|
764
|
+
// Set mount mode to worktree if we have repo worktrees - triggers git wrapper setup
|
|
765
|
+
...(hasWorktrees ? [`-e PRLT_MOUNT_MODE=worktree`] : []),
|
|
759
766
|
];
|
|
760
767
|
// Resource limits
|
|
761
768
|
const resourceFlags = [
|
|
@@ -1421,12 +1428,11 @@ async function runDevcontainerInTmux(context, devcontainerCmd, config, displayMo
|
|
|
1421
1428
|
const claudeCmd = cmdMatch ? cmdMatch[1] : devcontainerCmd;
|
|
1422
1429
|
// Create a script inside the container that runs claude and keeps shell open
|
|
1423
1430
|
// TERM must be set for Claude's TUI to render properly
|
|
1424
|
-
// Unset
|
|
1425
|
-
//
|
|
1431
|
+
// Unset CI to prevent Claude from detecting CI environment which suppresses TUI output
|
|
1432
|
+
// Note: We keep DEVCONTAINER set so prlt workspace detection works correctly
|
|
1426
1433
|
const tmuxScript = `#!/bin/bash
|
|
1427
1434
|
export TERM=xterm-256color
|
|
1428
1435
|
export COLORTERM=truecolor
|
|
1429
|
-
unset DEVCONTAINER
|
|
1430
1436
|
unset CI
|
|
1431
1437
|
echo "🚀 Starting: ${sessionName}"
|
|
1432
1438
|
echo ""
|
|
@@ -1695,70 +1701,6 @@ exec $SHELL
|
|
|
1695
1701
|
};
|
|
1696
1702
|
}
|
|
1697
1703
|
}
|
|
1698
|
-
/**
|
|
1699
|
-
* Legacy: Run devcontainer in host-side tmux (kept for non-container modes)
|
|
1700
|
-
*/
|
|
1701
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1702
|
-
async function runDevcontainerInHostTmux(context, devcontainerCmd, config) {
|
|
1703
|
-
const sessionName = config.tmux.session;
|
|
1704
|
-
const windowName = buildTmuxWindowName(context);
|
|
1705
|
-
try {
|
|
1706
|
-
// Check if tmux is available on host
|
|
1707
|
-
execSync('which tmux', { stdio: 'pipe' });
|
|
1708
|
-
// Write command to temp script
|
|
1709
|
-
const baseDir = context.hqPath
|
|
1710
|
-
? path.join(context.hqPath, '.proletariat', 'scripts')
|
|
1711
|
-
: path.join(os.homedir(), '.proletariat', 'scripts');
|
|
1712
|
-
fs.mkdirSync(baseDir, { recursive: true });
|
|
1713
|
-
const scriptPath = path.join(baseDir, `exec-${context.ticketId}-${Date.now()}.sh`);
|
|
1714
|
-
const windowTitle = buildWindowTitle(context);
|
|
1715
|
-
const setTitleCmds = getSetTitleCommands(windowTitle);
|
|
1716
|
-
const scriptContent = `#!/bin/bash
|
|
1717
|
-
${setTitleCmds}
|
|
1718
|
-
echo "🚀 Starting ticket execution: ${context.ticketId}"
|
|
1719
|
-
${devcontainerCmd}
|
|
1720
|
-
rm -f "${scriptPath}"
|
|
1721
|
-
exec $SHELL
|
|
1722
|
-
`;
|
|
1723
|
-
fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
1724
|
-
// Check if session exists
|
|
1725
|
-
let sessionExists = false;
|
|
1726
|
-
try {
|
|
1727
|
-
execSync(`tmux has-session -t ${sessionName}`, { stdio: 'pipe' });
|
|
1728
|
-
sessionExists = true;
|
|
1729
|
-
}
|
|
1730
|
-
catch (err) {
|
|
1731
|
-
console.debug(`[runners:hostTmux] Session ${sessionName} does not exist:`, err);
|
|
1732
|
-
sessionExists = false;
|
|
1733
|
-
}
|
|
1734
|
-
const targetPane = `${sessionName}:${windowName}`;
|
|
1735
|
-
if (!sessionExists) {
|
|
1736
|
-
execSync(`tmux new-session -d -s ${sessionName} -n "${windowName}"`, { stdio: 'pipe' });
|
|
1737
|
-
}
|
|
1738
|
-
else if (config.tmux.layout === 'window') {
|
|
1739
|
-
// Create new window in existing session (starts with shell)
|
|
1740
|
-
execSync(`tmux new-window -t ${sessionName} -n "${windowName}"`, { stdio: 'pipe' });
|
|
1741
|
-
}
|
|
1742
|
-
else {
|
|
1743
|
-
// Split existing pane (starts with shell)
|
|
1744
|
-
execSync(`tmux split-window -t ${sessionName} -h`, { stdio: 'pipe' });
|
|
1745
|
-
}
|
|
1746
|
-
// Send the script command to the shell - execute directly (not source)
|
|
1747
|
-
// Using exec replaces the shell, ensuring proper TTY passthrough
|
|
1748
|
-
execSync(`tmux send-keys -t "${targetPane}" 'exec ${scriptPath}' Enter`, { stdio: 'pipe' });
|
|
1749
|
-
return {
|
|
1750
|
-
success: true,
|
|
1751
|
-
containerId: `devcontainer-${context.agentName}`,
|
|
1752
|
-
sessionId: `${sessionName}:${windowName}`,
|
|
1753
|
-
};
|
|
1754
|
-
}
|
|
1755
|
-
catch (error) {
|
|
1756
|
-
return {
|
|
1757
|
-
success: false,
|
|
1758
|
-
error: error instanceof Error ? error.message : 'Failed to start tmux session',
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
1704
|
// =============================================================================
|
|
1763
1705
|
// Docker Runner
|
|
1764
1706
|
// =============================================================================
|
|
@@ -13,6 +13,7 @@ import { findHQRoot } from '../repos/index.js';
|
|
|
13
13
|
import { hasDevcontainerConfig } from './devcontainer.js';
|
|
14
14
|
import { loadExecutionConfig, getOrPromptCoderName } from './config.js';
|
|
15
15
|
import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from './runners.js';
|
|
16
|
+
import { detectRepoWorktrees, resolveWorktreePath } from './context.js';
|
|
16
17
|
import { generateBranchName, DEFAULT_EXECUTION_CONFIG, } from './types.js';
|
|
17
18
|
// =============================================================================
|
|
18
19
|
// Git Utilities
|
|
@@ -165,24 +166,9 @@ export async function spawnAgentForTicket(ticket, agentName, storage, executionS
|
|
|
165
166
|
error: `Agent directory not found at ${agentDir}`,
|
|
166
167
|
};
|
|
167
168
|
}
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
const repoWorktrees = agentContents.filter(item => {
|
|
172
|
-
const itemPath = path.join(agentDir, item);
|
|
173
|
-
const gitPath = path.join(itemPath, '.git');
|
|
174
|
-
return fs.statSync(itemPath).isDirectory() && fs.existsSync(gitPath);
|
|
175
|
-
});
|
|
176
|
-
if (repoWorktrees.length === 1) {
|
|
177
|
-
worktreePath = path.join(agentDir, repoWorktrees[0]);
|
|
178
|
-
}
|
|
179
|
-
else if (repoWorktrees.length > 1) {
|
|
180
|
-
worktreePath = agentDir;
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
// No git worktrees found - use current directory
|
|
184
|
-
worktreePath = process.cwd();
|
|
185
|
-
}
|
|
169
|
+
// Detect repository worktrees within agent directory
|
|
170
|
+
const repoWorktrees = detectRepoWorktrees(agentDir);
|
|
171
|
+
const worktreePath = resolveWorktreePath(agentDir, repoWorktrees);
|
|
186
172
|
// Get coder name for branch naming (prompts on first use)
|
|
187
173
|
const coderName = await getOrPromptCoderName(db);
|
|
188
174
|
// Generate branch name
|
|
@@ -228,6 +214,7 @@ export async function spawnAgentForTicket(ticket, agentName, storage, executionS
|
|
|
228
214
|
branch,
|
|
229
215
|
hqPath,
|
|
230
216
|
pmoPath,
|
|
217
|
+
repoWorktrees,
|
|
231
218
|
createPR: options.createPR ?? false,
|
|
232
219
|
};
|
|
233
220
|
// Determine execution environment and display mode
|