@proletariat/cli 0.3.44 → 0.3.46
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/agent/list.js +2 -3
- package/dist/commands/agent/login.js +2 -2
- package/dist/commands/agent/rebuild.js +2 -3
- package/dist/commands/agent/shell.js +2 -2
- package/dist/commands/agent/status.js +3 -3
- package/dist/commands/agent/visit.js +2 -2
- package/dist/commands/config/index.js +39 -1
- package/dist/commands/linear/auth.d.ts +14 -0
- package/dist/commands/linear/auth.js +211 -0
- package/dist/commands/linear/import.d.ts +21 -0
- package/dist/commands/linear/import.js +260 -0
- package/dist/commands/linear/status.d.ts +11 -0
- package/dist/commands/linear/status.js +88 -0
- package/dist/commands/linear/sync.d.ts +15 -0
- package/dist/commands/linear/sync.js +233 -0
- package/dist/commands/orchestrator/attach.d.ts +9 -1
- package/dist/commands/orchestrator/attach.js +67 -13
- package/dist/commands/orchestrator/index.js +22 -7
- package/dist/commands/staff/list.js +2 -3
- package/dist/commands/ticket/link/duplicates.d.ts +15 -0
- package/dist/commands/ticket/link/duplicates.js +95 -0
- package/dist/commands/ticket/link/index.js +14 -0
- package/dist/commands/ticket/link/relates.d.ts +15 -0
- package/dist/commands/ticket/link/relates.js +95 -0
- package/dist/commands/work/revise.js +7 -6
- package/dist/commands/work/spawn.d.ts +5 -0
- package/dist/commands/work/spawn.js +195 -14
- package/dist/commands/work/start.js +79 -23
- package/dist/commands/work/watch.js +2 -2
- package/dist/lib/agents/commands.d.ts +11 -0
- package/dist/lib/agents/commands.js +40 -10
- package/dist/lib/execution/config.d.ts +15 -0
- package/dist/lib/execution/config.js +54 -0
- package/dist/lib/execution/devcontainer.d.ts +6 -3
- package/dist/lib/execution/devcontainer.js +39 -12
- package/dist/lib/execution/runners.d.ts +28 -32
- package/dist/lib/execution/runners.js +345 -271
- package/dist/lib/execution/spawner.js +65 -7
- package/dist/lib/execution/types.d.ts +4 -0
- package/dist/lib/execution/types.js +3 -0
- package/dist/lib/external-issues/adapters.d.ts +26 -0
- package/dist/lib/external-issues/adapters.js +251 -0
- package/dist/lib/external-issues/index.d.ts +10 -0
- package/dist/lib/external-issues/index.js +14 -0
- package/dist/lib/external-issues/mapper.d.ts +21 -0
- package/dist/lib/external-issues/mapper.js +86 -0
- package/dist/lib/external-issues/types.d.ts +144 -0
- package/dist/lib/external-issues/types.js +26 -0
- package/dist/lib/external-issues/validation.d.ts +34 -0
- package/dist/lib/external-issues/validation.js +219 -0
- package/dist/lib/linear/client.d.ts +55 -0
- package/dist/lib/linear/client.js +254 -0
- package/dist/lib/linear/config.d.ts +37 -0
- package/dist/lib/linear/config.js +100 -0
- package/dist/lib/linear/index.d.ts +11 -0
- package/dist/lib/linear/index.js +10 -0
- package/dist/lib/linear/mapper.d.ts +67 -0
- package/dist/lib/linear/mapper.js +219 -0
- package/dist/lib/linear/sync.d.ts +37 -0
- package/dist/lib/linear/sync.js +89 -0
- package/dist/lib/linear/types.d.ts +139 -0
- package/dist/lib/linear/types.js +34 -0
- package/dist/lib/mcp/helpers.d.ts +8 -0
- package/dist/lib/mcp/helpers.js +10 -0
- package/dist/lib/mcp/tools/board.js +63 -11
- package/dist/lib/pmo/schema.d.ts +2 -0
- package/dist/lib/pmo/schema.js +20 -0
- package/dist/lib/pmo/storage/base.js +92 -13
- package/dist/lib/pmo/storage/dependencies.js +15 -0
- package/dist/lib/prompt-json.d.ts +4 -0
- package/dist/lib/themes.js +32 -16
- package/oclif.manifest.json +2823 -2336
- package/package.json +2 -1
|
@@ -4,10 +4,12 @@ import Database from 'better-sqlite3';
|
|
|
4
4
|
import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getWorkspaceInfo, getTicketTmuxSession, killTmuxSession } from '../../lib/agents/commands.js';
|
|
7
|
-
import { isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
|
|
7
|
+
import { isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, getExecutorDisplayName } from '../../lib/execution/runners.js';
|
|
8
|
+
import { getCreatePrDefault, loadExecutionConfig } from '../../lib/execution/config.js';
|
|
8
9
|
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, outputConfirmationNeededAsJson, outputExecutionResultAsJson, } from '../../lib/prompt-json.js';
|
|
9
10
|
import { FlagResolver } from '../../lib/flags/index.js';
|
|
10
11
|
import { loadDietConfig, formatDietConfig, } from '../../lib/pmo/diet.js';
|
|
12
|
+
import { LinearClient, LinearMapper, isLinearConfigured, loadLinearConfig, } from '../../lib/linear/index.js';
|
|
11
13
|
export default class WorkSpawn extends PMOCommand {
|
|
12
14
|
static description = 'Spawn work for multiple tickets by column (batch mode)';
|
|
13
15
|
static strict = false; // Allow multiple ticket ID args without defining them
|
|
@@ -24,6 +26,10 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
24
26
|
'<%= config.bin %> <%= command.id %> --count 10 --diet --action groom # Diet-balanced',
|
|
25
27
|
'<%= config.bin %> <%= command.id %> --count 5 --category ship --action implement # Filtered by category',
|
|
26
28
|
'<%= config.bin %> <%= command.id %> --count 5 --priority P0 --action implement # Filtered by priority',
|
|
29
|
+
'<%= config.bin %> <%= command.id %> --from-linear # Pull Linear issues → PMO → spawn',
|
|
30
|
+
'<%= config.bin %> <%= command.id %> --from-linear ENG-123 ENG-124 # Pull specific Linear issues → spawn',
|
|
31
|
+
'<%= config.bin %> <%= command.id %> --from-linear --linear-team ENG # Pull from specific team',
|
|
32
|
+
'<%= config.bin %> <%= command.id %> --from-linear --linear-state "In Progress" # Filter by state',
|
|
27
33
|
'<%= config.bin %> <%= command.id %> --count 10 --diet --category ship,grow --action groom # Combined',
|
|
28
34
|
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002 --create-pr # Create PR when work is ready',
|
|
29
35
|
];
|
|
@@ -141,6 +147,28 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
141
147
|
status: Flags.string({
|
|
142
148
|
description: 'Filter tickets by status name (e.g., Backlog, Ready)',
|
|
143
149
|
}),
|
|
150
|
+
// Linear integration flags
|
|
151
|
+
'from-linear': Flags.boolean({
|
|
152
|
+
description: 'Pull issues from Linear, mirror into PMO, then spawn agents',
|
|
153
|
+
default: false,
|
|
154
|
+
}),
|
|
155
|
+
'linear-team': Flags.string({
|
|
156
|
+
description: 'Linear team key to pull issues from (e.g., ENG)',
|
|
157
|
+
dependsOn: ['from-linear'],
|
|
158
|
+
}),
|
|
159
|
+
'linear-state': Flags.string({
|
|
160
|
+
description: 'Filter Linear issues by state name (e.g., "In Progress")',
|
|
161
|
+
dependsOn: ['from-linear'],
|
|
162
|
+
}),
|
|
163
|
+
'linear-label': Flags.string({
|
|
164
|
+
description: 'Filter Linear issues by label name',
|
|
165
|
+
dependsOn: ['from-linear'],
|
|
166
|
+
}),
|
|
167
|
+
'linear-limit': Flags.integer({
|
|
168
|
+
description: 'Maximum number of Linear issues to pull',
|
|
169
|
+
default: 20,
|
|
170
|
+
dependsOn: ['from-linear'],
|
|
171
|
+
}),
|
|
144
172
|
};
|
|
145
173
|
async execute() {
|
|
146
174
|
const { flags, argv } = await this.parse(WorkSpawn);
|
|
@@ -154,12 +182,121 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
154
182
|
}
|
|
155
183
|
this.error(message);
|
|
156
184
|
};
|
|
185
|
+
// Check for conflicting PR flags (before deprecation warning to fail fast)
|
|
186
|
+
if (flags['create-pr'] && flags['no-pr']) {
|
|
187
|
+
this.error('--create-pr and --no-pr are mutually exclusive');
|
|
188
|
+
}
|
|
157
189
|
// Deprecation guidance for --no-pr
|
|
158
190
|
if (flags['no-pr']) {
|
|
159
191
|
this.warn('--no-pr is deprecated. Omit --create-pr instead (PR creation is off by default). --no-pr will continue to work.');
|
|
160
192
|
}
|
|
161
193
|
// Parse ticket IDs from args (everything after flags)
|
|
162
|
-
|
|
194
|
+
let ticketIdArgs = argv;
|
|
195
|
+
// =========================================================================
|
|
196
|
+
// --from-linear: Pull Linear issues → mirror into PMO → spawn from PMO
|
|
197
|
+
// =========================================================================
|
|
198
|
+
if (flags['from-linear']) {
|
|
199
|
+
const db = this.storage.getDatabase();
|
|
200
|
+
if (!isLinearConfigured(db)) {
|
|
201
|
+
return handleError('LINEAR_NOT_CONFIGURED', 'Linear is not configured. Run "prlt linear auth" first.');
|
|
202
|
+
}
|
|
203
|
+
const linearConfig = loadLinearConfig(db);
|
|
204
|
+
const linearClient = new LinearClient(linearConfig.apiKey);
|
|
205
|
+
const mapper = new LinearMapper(db);
|
|
206
|
+
// Determine project first (needed for ticket creation)
|
|
207
|
+
const linearProjectId = await this.requireProject({
|
|
208
|
+
jsonMode: jsonMode ? {
|
|
209
|
+
flags,
|
|
210
|
+
commandName: 'work spawn',
|
|
211
|
+
baseCommand: 'prlt work spawn --from-linear',
|
|
212
|
+
} : undefined,
|
|
213
|
+
});
|
|
214
|
+
// Get project workflow statuses for mapping
|
|
215
|
+
const workflow = await this.storage.getProjectWorkflow(linearProjectId);
|
|
216
|
+
if (!workflow) {
|
|
217
|
+
return handleError('NO_WORKFLOW', 'Project has no workflow configured.');
|
|
218
|
+
}
|
|
219
|
+
const statuses = await this.storage.listStatuses(workflow.id);
|
|
220
|
+
// Check if args are Linear identifiers (e.g., ENG-123)
|
|
221
|
+
const linearIdentifiers = ticketIdArgs.filter((id) => /^[A-Z]+-\d+$/i.test(id));
|
|
222
|
+
const pmoTicketIds = [];
|
|
223
|
+
if (linearIdentifiers.length > 0) {
|
|
224
|
+
// Fetch and import specific Linear issues
|
|
225
|
+
if (!jsonMode) {
|
|
226
|
+
this.log(styles.muted(`Pulling ${linearIdentifiers.length} issue(s) from Linear...`));
|
|
227
|
+
}
|
|
228
|
+
for (const identifier of linearIdentifiers) {
|
|
229
|
+
// eslint-disable-next-line no-await-in-loop
|
|
230
|
+
const issue = await linearClient.getIssueByIdentifier(identifier);
|
|
231
|
+
if (!issue) {
|
|
232
|
+
if (!jsonMode)
|
|
233
|
+
this.warn(`Linear issue not found: ${identifier}`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// eslint-disable-next-line no-await-in-loop
|
|
237
|
+
const { ticketId, created } = await mapper.importIssue(issue, linearProjectId, this.storage, statuses);
|
|
238
|
+
pmoTicketIds.push(ticketId);
|
|
239
|
+
if (!jsonMode) {
|
|
240
|
+
const action = created ? 'Imported' : 'Already imported';
|
|
241
|
+
this.log(styles.muted(` ${action}: ${identifier} → ${ticketId}`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Fetch issues using filters
|
|
247
|
+
const filter = {
|
|
248
|
+
teamKey: flags['linear-team'] ?? linearConfig.defaultTeamKey,
|
|
249
|
+
stateName: flags['linear-state'],
|
|
250
|
+
labelName: flags['linear-label'],
|
|
251
|
+
limit: flags['linear-limit'],
|
|
252
|
+
};
|
|
253
|
+
if (!jsonMode) {
|
|
254
|
+
this.log(styles.muted(`Pulling issues from Linear (team: ${filter.teamKey ?? 'all'})...`));
|
|
255
|
+
}
|
|
256
|
+
const issues = await linearClient.listIssues(filter);
|
|
257
|
+
if (issues.length === 0) {
|
|
258
|
+
if (jsonMode) {
|
|
259
|
+
outputSuccessAsJson({
|
|
260
|
+
imported: 0,
|
|
261
|
+
spawned: 0,
|
|
262
|
+
message: 'No matching Linear issues found.',
|
|
263
|
+
}, createMetadata('work spawn', flags));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.log(styles.muted('No matching Linear issues found.'));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (!jsonMode) {
|
|
270
|
+
this.log(styles.muted(`Found ${issues.length} issue(s). Importing into PMO...`));
|
|
271
|
+
}
|
|
272
|
+
const result = await mapper.importIssues(issues, linearProjectId, this.storage, statuses);
|
|
273
|
+
if (!jsonMode) {
|
|
274
|
+
if (result.imported > 0)
|
|
275
|
+
this.log(styles.success(` Imported: ${result.imported}`));
|
|
276
|
+
if (result.skipped > 0)
|
|
277
|
+
this.log(styles.muted(` Already imported: ${result.skipped}`));
|
|
278
|
+
if (result.errors.length > 0)
|
|
279
|
+
this.log(styles.error(` Errors: ${result.errors.length}`));
|
|
280
|
+
}
|
|
281
|
+
// Collect all PMO ticket IDs (both new and existing)
|
|
282
|
+
for (const issue of issues) {
|
|
283
|
+
const existing = mapper.getByLinearId(issue.id);
|
|
284
|
+
if (existing) {
|
|
285
|
+
pmoTicketIds.push(existing.pmoTicketId);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (pmoTicketIds.length === 0) {
|
|
290
|
+
return handleError('NO_LINEAR_ISSUES', 'No Linear issues were imported. Nothing to spawn.');
|
|
291
|
+
}
|
|
292
|
+
if (!jsonMode) {
|
|
293
|
+
this.log('');
|
|
294
|
+
this.log(styles.header(`Spawning agents for ${pmoTicketIds.length} imported ticket(s)...`));
|
|
295
|
+
this.log('');
|
|
296
|
+
}
|
|
297
|
+
// Replace ticket args with the imported PMO ticket IDs
|
|
298
|
+
ticketIdArgs = pmoTicketIds;
|
|
299
|
+
}
|
|
163
300
|
// Try to infer project from ticket IDs if provided
|
|
164
301
|
let projectId;
|
|
165
302
|
if (ticketIdArgs.length > 0) {
|
|
@@ -889,12 +1026,16 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
889
1026
|
return;
|
|
890
1027
|
}
|
|
891
1028
|
// Batch mode settings - prompt once for all tickets
|
|
1029
|
+
const executionConfig = loadExecutionConfig(db);
|
|
1030
|
+
const effectiveExecutor = flags.executor || executionConfig.defaultExecutor;
|
|
1031
|
+
const executorName = getExecutorDisplayName(effectiveExecutor);
|
|
892
1032
|
let batchDisplay = flags.display;
|
|
893
1033
|
let batchOutput = flags.output;
|
|
894
1034
|
// Track permission mode - default to 'safe', check flag to determine if prompting needed
|
|
895
1035
|
let batchPermissionMode = flags['skip-permissions'] ? 'danger' : 'safe';
|
|
896
1036
|
let batchCreatePr = flags['create-pr'];
|
|
897
1037
|
let batchNoPr = flags['no-pr'];
|
|
1038
|
+
let batchPrModeSource = flags['create-pr'] ? 'flag --create-pr' : flags['no-pr'] ? 'flag --no-pr' : 'default';
|
|
898
1039
|
let batchRunOnHost = flags['run-on-host'];
|
|
899
1040
|
let batchAction = flags.action;
|
|
900
1041
|
// Track custom message for custom action (needs to be outside the if block)
|
|
@@ -1045,10 +1186,12 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1045
1186
|
if (modifiesCode) {
|
|
1046
1187
|
batchCreatePr = true;
|
|
1047
1188
|
batchNoPr = false;
|
|
1189
|
+
batchPrModeSource = 'batch defaults';
|
|
1048
1190
|
}
|
|
1049
1191
|
else {
|
|
1050
1192
|
batchCreatePr = false;
|
|
1051
1193
|
batchNoPr = true;
|
|
1194
|
+
batchPrModeSource = 'action (non-code-modifying)';
|
|
1052
1195
|
}
|
|
1053
1196
|
this.log('');
|
|
1054
1197
|
}
|
|
@@ -1225,7 +1368,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1225
1368
|
permissionResolver.addPrompt({
|
|
1226
1369
|
flagName: 'permissionMode',
|
|
1227
1370
|
type: 'list',
|
|
1228
|
-
message:
|
|
1371
|
+
message: `Permission mode for ${executorName}:`,
|
|
1229
1372
|
default: 'danger',
|
|
1230
1373
|
choices: () => [
|
|
1231
1374
|
{ name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger' },
|
|
@@ -1236,14 +1379,28 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1236
1379
|
batchPermissionMode = permissionResult.permissionMode;
|
|
1237
1380
|
}
|
|
1238
1381
|
// Prompt for PR creation if not provided AND action modifies code
|
|
1239
|
-
//
|
|
1382
|
+
// Resolution order: explicit flags > workspace config default > interactive prompt
|
|
1240
1383
|
const actionModifiesCode = selectedActionDetails?.modifiesCode ?? true;
|
|
1241
1384
|
if (!batchCreatePr && !batchNoPr) {
|
|
1242
|
-
if (actionModifiesCode) {
|
|
1243
|
-
//
|
|
1244
|
-
|
|
1385
|
+
if (!actionModifiesCode) {
|
|
1386
|
+
// Non-code-modifying action - no PR needed
|
|
1387
|
+
batchCreatePr = false;
|
|
1388
|
+
batchNoPr = true;
|
|
1389
|
+
batchPrModeSource = 'action (non-code-modifying)';
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
// Check workspace config default
|
|
1393
|
+
const configPrDefault = getCreatePrDefault(db);
|
|
1394
|
+
if (configPrDefault !== null) {
|
|
1395
|
+
batchCreatePr = configPrDefault;
|
|
1396
|
+
batchNoPr = !configPrDefault;
|
|
1397
|
+
batchPrModeSource = 'workspace config (execution.create_pr_default)';
|
|
1398
|
+
}
|
|
1399
|
+
else if (jsonMode && flags.yes) {
|
|
1400
|
+
// In JSON mode with --yes, default to creating PRs for code-modifying actions
|
|
1245
1401
|
batchCreatePr = true;
|
|
1246
1402
|
batchNoPr = false;
|
|
1403
|
+
batchPrModeSource = 'default (--json --yes)';
|
|
1247
1404
|
}
|
|
1248
1405
|
else {
|
|
1249
1406
|
// Use FlagResolver for PR choice
|
|
@@ -1266,12 +1423,24 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1266
1423
|
const prResult = await prResolver.resolve();
|
|
1267
1424
|
batchCreatePr = prResult.prChoice === 'yes';
|
|
1268
1425
|
batchNoPr = prResult.prChoice === 'no';
|
|
1426
|
+
batchPrModeSource = 'interactive prompt';
|
|
1269
1427
|
}
|
|
1270
1428
|
}
|
|
1429
|
+
}
|
|
1430
|
+
// Show effective PR mode in batch summary
|
|
1431
|
+
if (!jsonMode) {
|
|
1432
|
+
const prModeLabel = batchCreatePr ? 'create-pr' : 'no-pr';
|
|
1433
|
+
if (batchCreatePr) {
|
|
1434
|
+
this.log(styles.success(` PR mode: ${prModeLabel} (${batchPrModeSource})`));
|
|
1435
|
+
}
|
|
1271
1436
|
else {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1437
|
+
this.log(styles.warning(` PR mode: ${prModeLabel} (${batchPrModeSource})`));
|
|
1438
|
+
}
|
|
1439
|
+
// Strong warning when no-pr is active for code-modifying actions
|
|
1440
|
+
if (!batchCreatePr && actionModifiesCode) {
|
|
1441
|
+
this.log('');
|
|
1442
|
+
this.log(styles.warning(` ⚠️ WARNING: PR creation is DISABLED. Branches will be pushed but NO pull requests will be created.`));
|
|
1443
|
+
this.log(styles.warning(` To create PRs later: prlt pr create <ticket-id>`));
|
|
1275
1444
|
}
|
|
1276
1445
|
}
|
|
1277
1446
|
this.log('');
|
|
@@ -1400,17 +1569,29 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
1400
1569
|
});
|
|
1401
1570
|
db.close();
|
|
1402
1571
|
// Output results
|
|
1572
|
+
const resolvedPRMode = batchCreatePr ? 'create-pr' : 'no-pr';
|
|
1403
1573
|
if (jsonMode) {
|
|
1404
|
-
// Output JSON execution results with resolved PR mode
|
|
1574
|
+
// Output JSON execution results with resolved PR mode and source
|
|
1405
1575
|
const spawnMetadata = createMetadata('work spawn', flags);
|
|
1406
|
-
spawnMetadata.resolvedPRMode =
|
|
1576
|
+
spawnMetadata.resolvedPRMode = resolvedPRMode;
|
|
1577
|
+
spawnMetadata.prModeSource = batchPrModeSource;
|
|
1578
|
+
if (!batchCreatePr && (selectedActionDetails?.modifiesCode ?? true)) {
|
|
1579
|
+
spawnMetadata.prWarning = `PR creation is DISABLED (${batchPrModeSource}). Branches will be pushed without PRs. To create later: prlt pr create <ticket-id>`;
|
|
1580
|
+
}
|
|
1407
1581
|
outputExecutionResultAsJson(executionResults, successCount, failCount, spawnMetadata);
|
|
1408
1582
|
}
|
|
1409
1583
|
else {
|
|
1410
|
-
const resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
|
|
1411
1584
|
this.log('');
|
|
1412
1585
|
this.log(styles.success(`✓ Spawn results: ${successCount} started, ${failCount} failed`));
|
|
1413
|
-
this.log(styles.muted(` PR mode: ${resolvedPRMode}`));
|
|
1586
|
+
this.log(styles.muted(` PR mode: ${resolvedPRMode} (${batchPrModeSource})`));
|
|
1587
|
+
// Post-run reminder when branches pushed without PRs
|
|
1588
|
+
if (!batchCreatePr && (selectedActionDetails?.modifiesCode ?? true)) {
|
|
1589
|
+
this.log('');
|
|
1590
|
+
this.log(styles.warning(`Note: No PRs will be auto-created. To create PRs later:`));
|
|
1591
|
+
for (const result of executionResults.filter(r => r.status === 'running')) {
|
|
1592
|
+
this.log(styles.warning(` prlt pr create ${result.ticketId}`));
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1414
1595
|
}
|
|
1415
1596
|
}
|
|
1416
1597
|
catch (error) {
|
|
@@ -9,11 +9,11 @@ import { shouldOutputJson, outputErrorAsJson, createMetadata, outputConfirmation
|
|
|
9
9
|
import { FlagResolver } from '../../lib/flags/index.js';
|
|
10
10
|
import { getWorkColumnSetting, findColumnByName } from '../../lib/pmo/utils.js';
|
|
11
11
|
import { styles } from '../../lib/styles.js';
|
|
12
|
-
import { getWorkspaceInfo, createEphemeralAgent, getTicketTmuxSession, killTmuxSession, findWorktreeForBranch, } from '../../lib/agents/commands.js';
|
|
12
|
+
import { getWorkspaceInfo, createEphemeralAgent, getTicketTmuxSession, killTmuxSession, findWorktreeForBranch, resolveAgentDir, } from '../../lib/agents/commands.js';
|
|
13
13
|
import { generateBranchName, DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
14
|
-
import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, dockerCredentialsExist, getDockerCredentialInfo } from '../../lib/execution/runners.js';
|
|
14
|
+
import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, dockerCredentialsExist, getDockerCredentialInfo, isClaudeExecutor, getExecutorDisplayName } from '../../lib/execution/runners.js';
|
|
15
15
|
import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.js';
|
|
16
|
-
import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, getOrPromptCoderName, getAuthMethod, saveAuthMethod } from '../../lib/execution/config.js';
|
|
16
|
+
import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, getOrPromptCoderName, getAuthMethod, saveAuthMethod, getCreatePrDefault } from '../../lib/execution/config.js';
|
|
17
17
|
import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
|
|
18
18
|
import { detectRepoWorktrees, resolveWorktreePath } from '../../lib/execution/context.js';
|
|
19
19
|
import { isGHInstalled, isGHAuthenticated } from '../../lib/pr/index.js';
|
|
@@ -130,7 +130,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
130
130
|
default: false,
|
|
131
131
|
}),
|
|
132
132
|
'permission-mode': Flags.string({
|
|
133
|
-
description: 'Permission mode for
|
|
133
|
+
description: 'Permission mode for selected executor (danger=skip checks, safe=require approval)',
|
|
134
134
|
options: ['danger', 'safe'],
|
|
135
135
|
}),
|
|
136
136
|
'skip-permissions': Flags.boolean({
|
|
@@ -277,7 +277,14 @@ export default class WorkStart extends PMOCommand {
|
|
|
277
277
|
if (allFlagsProvided && !flags.yes) {
|
|
278
278
|
// All flags provided but no --yes: return confirmation_needed with plan
|
|
279
279
|
const metadata = createMetadata('work start', flags);
|
|
280
|
-
|
|
280
|
+
// Resolve PR mode using same priority as execution: flags > workspace config > default
|
|
281
|
+
const earlyConfigPrDefault = getCreatePrDefault(db);
|
|
282
|
+
const earlyResolvedPr = flags['create-pr'] ? 'create-pr'
|
|
283
|
+
: flags['no-pr'] ? 'no-pr'
|
|
284
|
+
: earlyConfigPrDefault === true ? 'create-pr'
|
|
285
|
+
: earlyConfigPrDefault === false ? 'no-pr'
|
|
286
|
+
: 'no-pr';
|
|
287
|
+
metadata.resolvedPRMode = earlyResolvedPr;
|
|
281
288
|
// Build the confirm command with --yes
|
|
282
289
|
let confirmCmd = `prlt work start ${ticketId}`;
|
|
283
290
|
if (flags.action)
|
|
@@ -1024,7 +1031,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
1024
1031
|
// Track whether user explicitly chose to use API key instead of OAuth
|
|
1025
1032
|
let useApiKey = flags['use-api-key'] || false;
|
|
1026
1033
|
// Auth method resolution for devcontainer environment
|
|
1027
|
-
|
|
1034
|
+
// Only needed for Claude Code executor - other executors handle auth differently
|
|
1035
|
+
if (environment === 'devcontainer' && !useApiKey && isClaudeExecutor(executor)) {
|
|
1028
1036
|
// Check for saved auth method preference
|
|
1029
1037
|
const savedAuthMethod = getAuthMethod(db);
|
|
1030
1038
|
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
@@ -1212,10 +1220,11 @@ export default class WorkStart extends PMOCommand {
|
|
|
1212
1220
|
jsonMode,
|
|
1213
1221
|
flags: { 'permission-mode': flags['permission-mode'] },
|
|
1214
1222
|
});
|
|
1223
|
+
const executorName = getExecutorDisplayName(executor);
|
|
1215
1224
|
permissionResolver.addPrompt({
|
|
1216
1225
|
flagName: 'permission-mode',
|
|
1217
1226
|
type: 'list',
|
|
1218
|
-
message: `Permission mode for
|
|
1227
|
+
message: `Permission mode for ${executorName}${containerNote}:`,
|
|
1219
1228
|
default: 'danger',
|
|
1220
1229
|
choices: () => [
|
|
1221
1230
|
{ name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger' },
|
|
@@ -1227,24 +1236,34 @@ export default class WorkStart extends PMOCommand {
|
|
|
1227
1236
|
sandboxed = resolvedPermission['permission-mode'] === 'safe';
|
|
1228
1237
|
}
|
|
1229
1238
|
// Prompt for PR creation when work is complete
|
|
1230
|
-
//
|
|
1239
|
+
// Resolution order: explicit flags > workspace config default > interactive prompt
|
|
1231
1240
|
let createPR = false;
|
|
1241
|
+
let prModeSource = 'default'; // Track where PR mode was resolved from for display
|
|
1232
1242
|
const ghAvailable = isGHInstalled() && isGHAuthenticated();
|
|
1233
|
-
|
|
1243
|
+
const configPrDefault = getCreatePrDefault(db);
|
|
1234
1244
|
if (flags['create-pr']) {
|
|
1235
1245
|
createPR = true;
|
|
1246
|
+
prModeSource = 'flag --create-pr';
|
|
1236
1247
|
}
|
|
1237
1248
|
else if (flags['no-pr']) {
|
|
1238
1249
|
createPR = false;
|
|
1250
|
+
prModeSource = 'flag --no-pr';
|
|
1251
|
+
}
|
|
1252
|
+
else if (context.modifiesCode === false) {
|
|
1253
|
+
// Non-code-modifying actions (groom, review, resolve) default to no PR
|
|
1254
|
+
createPR = false;
|
|
1255
|
+
prModeSource = 'action (non-code-modifying)';
|
|
1256
|
+
}
|
|
1257
|
+
else if (configPrDefault !== null) {
|
|
1258
|
+
// Workspace config default is set - use it deterministically
|
|
1259
|
+
createPR = configPrDefault;
|
|
1260
|
+
prModeSource = 'workspace config (execution.create_pr_default)';
|
|
1239
1261
|
}
|
|
1240
1262
|
else if (ghAvailable) {
|
|
1241
|
-
if (
|
|
1242
|
-
// Non-code-modifying actions (groom, review, resolve) default to no PR
|
|
1243
|
-
createPR = false;
|
|
1244
|
-
}
|
|
1245
|
-
else if (jsonMode && flags.yes) {
|
|
1263
|
+
if (jsonMode && flags.yes) {
|
|
1246
1264
|
// In JSON mode with --yes, default to creating PR for code-modifying actions
|
|
1247
1265
|
createPR = true;
|
|
1266
|
+
prModeSource = 'default (--json --yes)';
|
|
1248
1267
|
}
|
|
1249
1268
|
else {
|
|
1250
1269
|
// Use FlagResolver for PR choice
|
|
@@ -1266,9 +1285,14 @@ export default class WorkStart extends PMOCommand {
|
|
|
1266
1285
|
});
|
|
1267
1286
|
const prResult = await prResolver.resolve();
|
|
1268
1287
|
createPR = prResult.prChoice === 'yes';
|
|
1288
|
+
prModeSource = 'interactive prompt';
|
|
1269
1289
|
}
|
|
1270
1290
|
}
|
|
1271
|
-
|
|
1291
|
+
else {
|
|
1292
|
+
prModeSource = 'default (gh CLI not available)';
|
|
1293
|
+
}
|
|
1294
|
+
// R1: Show clear PR mode in preflight summary
|
|
1295
|
+
// R2: Show strong warning when --no-pr is active
|
|
1272
1296
|
if (!jsonMode) {
|
|
1273
1297
|
this.log('');
|
|
1274
1298
|
this.log(styles.header(`🚀 Starting work: ${ticket.id}: ${ticket.title}`));
|
|
@@ -1286,12 +1310,30 @@ export default class WorkStart extends PMOCommand {
|
|
|
1286
1310
|
else {
|
|
1287
1311
|
this.log(styles.warning(` Permissions: ⚠️ danger (--dangerously-skip-permissions)`));
|
|
1288
1312
|
}
|
|
1289
|
-
this.log(styles.muted(` Output: ${outputMode === 'interactive' ?
|
|
1290
|
-
|
|
1313
|
+
this.log(styles.muted(` Output: ${outputMode === 'interactive' ? `streaming (watch ${getExecutorDisplayName(executor)} work)` : 'print (final result only)'}`));
|
|
1314
|
+
// PR mode with clear source indication
|
|
1315
|
+
if (createPR) {
|
|
1316
|
+
this.log(styles.success(` PR mode: create-pr (${prModeSource})`));
|
|
1317
|
+
}
|
|
1318
|
+
else {
|
|
1319
|
+
this.log(styles.warning(` PR mode: no-pr (${prModeSource})`));
|
|
1320
|
+
}
|
|
1321
|
+
// Strong warning when no-pr is active
|
|
1322
|
+
if (!createPR && context.modifiesCode !== false) {
|
|
1323
|
+
this.log('');
|
|
1324
|
+
this.log(styles.warning(` ⚠️ WARNING: PR creation is DISABLED. Branch will be pushed but NO pull request will be created.`));
|
|
1325
|
+
this.log(styles.warning(` To create a PR later: prlt pr create ${ticketId}`));
|
|
1326
|
+
}
|
|
1291
1327
|
this.log(styles.muted(` Worktree: ${worktreePath}`));
|
|
1292
1328
|
this.log(styles.muted(` Branch: ${branch}`));
|
|
1293
1329
|
this.log('');
|
|
1294
1330
|
}
|
|
1331
|
+
// R2: Include PR mode warning in JSON metadata
|
|
1332
|
+
if (jsonMode && !createPR && context.modifiesCode !== false) {
|
|
1333
|
+
// This will be included in the metadata of the JSON output at the end
|
|
1334
|
+
// We log a warning here for non-JSON consumers that may be watching stderr
|
|
1335
|
+
this.warn(`PR creation is DISABLED (${prModeSource}). Branch will be pushed without a PR. To create later: prlt pr create ${ticketId}`);
|
|
1336
|
+
}
|
|
1295
1337
|
// Add createPR to context
|
|
1296
1338
|
context.createPR = createPR;
|
|
1297
1339
|
// Handle git operations
|
|
@@ -1589,9 +1631,13 @@ export default class WorkStart extends PMOCommand {
|
|
|
1589
1631
|
});
|
|
1590
1632
|
// Output results
|
|
1591
1633
|
if (jsonMode) {
|
|
1592
|
-
// Output JSON execution result with resolved PR mode
|
|
1634
|
+
// Output JSON execution result with resolved PR mode and source
|
|
1593
1635
|
const metadata = createMetadata('work start', flags);
|
|
1594
1636
|
metadata.resolvedPRMode = createPR ? 'create-pr' : 'no-pr';
|
|
1637
|
+
metadata.prModeSource = prModeSource;
|
|
1638
|
+
if (!createPR && context.modifiesCode !== false) {
|
|
1639
|
+
metadata.prWarning = `PR creation is DISABLED (${prModeSource}). Branch will be pushed without a PR. To create later: prlt pr create ${ticketId}`;
|
|
1640
|
+
}
|
|
1595
1641
|
outputExecutionResultAsJson([{
|
|
1596
1642
|
workId: execution.id,
|
|
1597
1643
|
ticketId: ticket.id,
|
|
@@ -1609,6 +1655,12 @@ export default class WorkStart extends PMOCommand {
|
|
|
1609
1655
|
this.log(styles.muted(` prlt work status View work status`));
|
|
1610
1656
|
this.log(styles.muted(` prlt work ready ${ticketId} Mark ready for review`));
|
|
1611
1657
|
this.log(styles.muted(` prlt work stop ${execution.id} Stop work`));
|
|
1658
|
+
// R5: Post-run reminder when branch is pushed without PR
|
|
1659
|
+
if (!createPR && context.modifiesCode !== false) {
|
|
1660
|
+
this.log('');
|
|
1661
|
+
this.log(styles.warning(`Note: No PR will be auto-created. To create one later:`));
|
|
1662
|
+
this.log(styles.warning(` prlt pr create ${ticketId}`));
|
|
1663
|
+
}
|
|
1612
1664
|
}
|
|
1613
1665
|
}
|
|
1614
1666
|
else {
|
|
@@ -1617,6 +1669,7 @@ export default class WorkStart extends PMOCommand {
|
|
|
1617
1669
|
// Output JSON failure result with resolved PR mode
|
|
1618
1670
|
const failMetadata = createMetadata('work start', flags);
|
|
1619
1671
|
failMetadata.resolvedPRMode = createPR ? 'create-pr' : 'no-pr';
|
|
1672
|
+
failMetadata.prModeSource = prModeSource;
|
|
1620
1673
|
outputExecutionResultAsJson([{
|
|
1621
1674
|
workId: execution.id,
|
|
1622
1675
|
ticketId: ticket.id,
|
|
@@ -1694,13 +1747,15 @@ export default class WorkStart extends PMOCommand {
|
|
|
1694
1747
|
return;
|
|
1695
1748
|
}
|
|
1696
1749
|
// Prompt for permissions mode once for all tickets (TKT-513)
|
|
1750
|
+
const batchExecutor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
|
|
1751
|
+
const batchExecutorName = getExecutorDisplayName(batchExecutor);
|
|
1697
1752
|
let batchPermissionMode = flags['permission-mode'];
|
|
1698
1753
|
if (!batchPermissionMode) {
|
|
1699
1754
|
const { permissionMode } = await this.prompt([
|
|
1700
1755
|
{
|
|
1701
1756
|
type: 'list',
|
|
1702
1757
|
name: 'permissionMode',
|
|
1703
|
-
message:
|
|
1758
|
+
message: `Permission mode for ${batchExecutorName}:`,
|
|
1704
1759
|
choices: [
|
|
1705
1760
|
{ name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger', command: 'prlt work start --all --permission-mode danger --json' },
|
|
1706
1761
|
{ name: '🔒 safe - Requires approval for dangerous operations', value: 'safe', command: 'prlt work start --all --permission-mode safe --json' },
|
|
@@ -1712,12 +1767,13 @@ export default class WorkStart extends PMOCommand {
|
|
|
1712
1767
|
}
|
|
1713
1768
|
// Check Docker credentials if any agents use devcontainers
|
|
1714
1769
|
const anyUseDevcontainer = availableAgents.some(agent => {
|
|
1715
|
-
const agentDir =
|
|
1770
|
+
const agentDir = resolveAgentDir(workspaceInfo, agent.name);
|
|
1716
1771
|
return hasDevcontainerConfig(agentDir) && !flags['run-on-host'];
|
|
1717
1772
|
});
|
|
1718
1773
|
// Track whether user explicitly chose to use API key instead of OAuth
|
|
1719
1774
|
let batchUseApiKey = false;
|
|
1720
|
-
|
|
1775
|
+
// Credential check only applies to Claude Code executor
|
|
1776
|
+
if (anyUseDevcontainer && isClaudeExecutor(batchExecutor)) {
|
|
1721
1777
|
const hasCredentials = dockerCredentialsExist();
|
|
1722
1778
|
if (!hasCredentials) {
|
|
1723
1779
|
const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
|
|
@@ -1858,8 +1914,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
1858
1914
|
async spawnSingleTicket(ticket, agent, workspaceInfo, executionStorage, db, flags) {
|
|
1859
1915
|
const agentName = agent.name;
|
|
1860
1916
|
// Note: Ticket assignee update moved to after successful spawn
|
|
1861
|
-
// Find agent directory and worktree
|
|
1862
|
-
const agentDir =
|
|
1917
|
+
// Find agent directory and worktree (handles staff and temp agents)
|
|
1918
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
1863
1919
|
if (!fs.existsSync(agentDir)) {
|
|
1864
1920
|
throw new Error(`Agent directory not found: ${agentDir}`);
|
|
1865
1921
|
}
|
|
@@ -3,7 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
4
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
|
-
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
6
|
+
import { getWorkspaceInfo, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
7
7
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
8
8
|
import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
|
|
9
9
|
import { spawnForColumn, getAvailableAgents, isDockerRunning, isDevcontainerCliInstalled, } from '../../lib/execution/spawner.js';
|
|
@@ -152,7 +152,7 @@ export default class WorkWatch extends PMOCommand {
|
|
|
152
152
|
}
|
|
153
153
|
// Check if any agent has devcontainer
|
|
154
154
|
const hasDevcontainer = workspaceInfo.agents.some(agent => {
|
|
155
|
-
const agentDir =
|
|
155
|
+
const agentDir = resolveAgentDir(workspaceInfo, agent.name);
|
|
156
156
|
return hasDevcontainerConfig(agentDir);
|
|
157
157
|
});
|
|
158
158
|
// Prompt for environment and display mode if not provided
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { Agent, Repository, MountMode as DBMountMode } from '../database/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the directory for an agent, cascading through resolution strategies.
|
|
4
|
+
* Handles both staff (persistent) and temp (ephemeral) agents.
|
|
5
|
+
*
|
|
6
|
+
* Resolution order:
|
|
7
|
+
* 1. Check agent's worktree_path from DB (most reliable)
|
|
8
|
+
* 2. Check ephemeral directory if agent type is ephemeral
|
|
9
|
+
* 3. Check both directories on disk as fallback
|
|
10
|
+
* 4. Fall back to staff path (caller handles missing dir error)
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveAgentDir(workspaceInfo: WorkspaceInfo, agentName: string): string;
|
|
2
13
|
/**
|
|
3
14
|
* Format a list of agents for display in error messages.
|
|
4
15
|
* Truncates long lists to avoid overwhelming output.
|
|
@@ -10,6 +10,43 @@ import { isValidAgentName, getSuggestedAgentNames, generateEphemeralAgentName, g
|
|
|
10
10
|
import { createDevcontainerConfig } from '../execution/devcontainer.js';
|
|
11
11
|
import { getGitIdentity } from '../pr/index.js';
|
|
12
12
|
import { getPMOContext } from '../pmo/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the directory for an agent, cascading through resolution strategies.
|
|
15
|
+
* Handles both staff (persistent) and temp (ephemeral) agents.
|
|
16
|
+
*
|
|
17
|
+
* Resolution order:
|
|
18
|
+
* 1. Check agent's worktree_path from DB (most reliable)
|
|
19
|
+
* 2. Check ephemeral directory if agent type is ephemeral
|
|
20
|
+
* 3. Check both directories on disk as fallback
|
|
21
|
+
* 4. Fall back to staff path (caller handles missing dir error)
|
|
22
|
+
*/
|
|
23
|
+
export function resolveAgentDir(workspaceInfo, agentName) {
|
|
24
|
+
const agent = workspaceInfo.agents.find(a => a.name === agentName);
|
|
25
|
+
// 1. Check DB worktree_path (most reliable - set during agent creation)
|
|
26
|
+
if (agent?.worktree_path) {
|
|
27
|
+
const fullWorktreePath = path.join(workspaceInfo.path, agent.worktree_path);
|
|
28
|
+
// worktree_path may point to repo subdir (e.g. agents/staff/altman/proletariat)
|
|
29
|
+
// Go up to the agent dir level
|
|
30
|
+
const agentDir = path.dirname(fullWorktreePath);
|
|
31
|
+
if (fs.existsSync(agentDir))
|
|
32
|
+
return agentDir;
|
|
33
|
+
}
|
|
34
|
+
// 2. Check ephemeral directory if agent type is ephemeral
|
|
35
|
+
if (agent?.type === 'ephemeral') {
|
|
36
|
+
const ephemeralDir = path.join(workspaceInfo.path, 'agents', workspaceInfo.ephemeralAgentsDir, agentName);
|
|
37
|
+
if (fs.existsSync(ephemeralDir))
|
|
38
|
+
return ephemeralDir;
|
|
39
|
+
}
|
|
40
|
+
// 3. Check both directories on disk (handles DB inconsistencies)
|
|
41
|
+
const tempDir = path.join(workspaceInfo.path, 'agents', workspaceInfo.ephemeralAgentsDir, agentName);
|
|
42
|
+
if (fs.existsSync(tempDir))
|
|
43
|
+
return tempDir;
|
|
44
|
+
const staffDir = path.join(workspaceInfo.agentsPath, agentName);
|
|
45
|
+
if (fs.existsSync(staffDir))
|
|
46
|
+
return staffDir;
|
|
47
|
+
// 4. Fall back to staff path - caller will handle the missing dir error
|
|
48
|
+
return staffDir;
|
|
49
|
+
}
|
|
13
50
|
/**
|
|
14
51
|
* Format a list of agents for display in error messages.
|
|
15
52
|
* Truncates long lists to avoid overwhelming output.
|
|
@@ -169,15 +206,8 @@ export function getAgentStatus(workspaceInfo, agentName) {
|
|
|
169
206
|
const exists = !!agentRecord;
|
|
170
207
|
// Get worktrees from database to find actual agent location
|
|
171
208
|
const worktrees = getAgentWorktrees(workspaceInfo.path, agentName);
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
if (worktrees.length > 0) {
|
|
175
|
-
// worktree_path is like "agents/staff/altman/proletariat-altman"
|
|
176
|
-
// Agent dir is the parent: "agents/staff/altman"
|
|
177
|
-
const worktreePath = worktrees[0].worktree_path;
|
|
178
|
-
const agentDirRelative = path.dirname(worktreePath);
|
|
179
|
-
agentDir = path.join(workspaceInfo.path, agentDirRelative);
|
|
180
|
-
}
|
|
209
|
+
// Resolve agent directory (handles both staff and temp agents)
|
|
210
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
181
211
|
const dirExists = fs.existsSync(agentDir);
|
|
182
212
|
const status = {
|
|
183
213
|
name: agentName,
|
|
@@ -314,7 +344,7 @@ export async function removeAgentsFromWorkspace(workspaceInfo, agentNames) {
|
|
|
314
344
|
const failed = [];
|
|
315
345
|
for (const agentName of agentNames) {
|
|
316
346
|
try {
|
|
317
|
-
const agentDir =
|
|
347
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
318
348
|
// Stop and remove Docker container if it exists
|
|
319
349
|
try {
|
|
320
350
|
const containerId = execSync(`docker ps -aq --filter "label=devcontainer.local_folder=${agentDir}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|