s9n-devops-agent 2.0.18-dev.1 → 2.0.18-dev.11
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 +1 -0
- package/bin/cs-devops-agent +16 -20
- package/docs/RELEASE_NOTES.md +15 -0
- package/package.json +1 -1
- package/scripts/deploy-local.sh +100 -0
- package/src/agent-chat.js +299 -36
- package/src/credentials-manager.js +28 -6
- package/src/cs-devops-agent-worker.js +446 -87
- package/src/kora-skills.json +47 -0
- package/src/session-coordinator.js +499 -70
- package/src/setup-cs-devops-agent.js +298 -42
- package/src/ui-utils.js +1 -1
- package/start-devops-session.sh +4 -27
|
@@ -133,17 +133,22 @@ export class SessionCoordinator {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
cleanupStaleLocks() {
|
|
136
|
-
// Clean up locks older than 1 hour
|
|
137
|
-
const
|
|
136
|
+
// Clean up locks older than 24 hours (increased from 1 hour to allow resuming next day)
|
|
137
|
+
const staleThreshold = Date.now() - 86400000;
|
|
138
138
|
|
|
139
139
|
if (fs.existsSync(this.locksPath)) {
|
|
140
140
|
const locks = fs.readdirSync(this.locksPath);
|
|
141
141
|
locks.forEach(lockFile => {
|
|
142
142
|
const lockPath = path.join(this.locksPath, lockFile);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
try {
|
|
144
|
+
const stats = fs.statSync(lockPath);
|
|
145
|
+
// Only cleanup if VERY old
|
|
146
|
+
if (stats.mtimeMs < staleThreshold) {
|
|
147
|
+
fs.unlinkSync(lockPath);
|
|
148
|
+
console.log(`${CONFIG.colors.dim}Cleaned stale lock: ${lockFile}${CONFIG.colors.reset}`);
|
|
149
|
+
}
|
|
150
|
+
} catch (e) {
|
|
151
|
+
// Ignore errors
|
|
147
152
|
}
|
|
148
153
|
});
|
|
149
154
|
}
|
|
@@ -152,7 +157,9 @@ export class SessionCoordinator {
|
|
|
152
157
|
/**
|
|
153
158
|
* Check for newer version on npm registry
|
|
154
159
|
*/
|
|
155
|
-
async checkForUpdates() {
|
|
160
|
+
async checkForUpdates(skip = false) {
|
|
161
|
+
if (skip) return;
|
|
162
|
+
|
|
156
163
|
const globalSettings = this.loadGlobalSettings();
|
|
157
164
|
const now = Date.now();
|
|
158
165
|
|
|
@@ -165,22 +172,42 @@ export class SessionCoordinator {
|
|
|
165
172
|
// Show checking message
|
|
166
173
|
console.log(`${CONFIG.colors.dim}🔍 Checking for DevOps Agent updates...${CONFIG.colors.reset}`);
|
|
167
174
|
|
|
168
|
-
// Check npm for
|
|
169
|
-
const
|
|
175
|
+
// Check npm for dist-tags
|
|
176
|
+
const distTags = JSON.parse(execSync('npm view s9n-devops-agent dist-tags --json', {
|
|
170
177
|
encoding: 'utf8',
|
|
171
178
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
172
179
|
timeout: 5000
|
|
173
|
-
}).trim();
|
|
180
|
+
}).trim());
|
|
181
|
+
|
|
182
|
+
const latest = distTags.latest;
|
|
183
|
+
const dev = distTags.dev;
|
|
174
184
|
|
|
175
185
|
// Update last check time
|
|
176
186
|
globalSettings.lastUpdateCheck = now;
|
|
177
187
|
this.saveGlobalSettings(globalSettings);
|
|
178
188
|
|
|
179
|
-
//
|
|
180
|
-
|
|
189
|
+
// Determine which version to compare against
|
|
190
|
+
// If current is a dev version, we check dev tag as well
|
|
191
|
+
const isDev = this.currentVersion.includes('dev') || this.currentVersion.includes('-');
|
|
192
|
+
|
|
193
|
+
let updateAvailable = false;
|
|
194
|
+
let targetVersion = latest;
|
|
195
|
+
let updateTag = 'latest';
|
|
196
|
+
|
|
197
|
+
if (isDev && dev && this.compareVersions(dev, this.currentVersion) > 0) {
|
|
198
|
+
updateAvailable = true;
|
|
199
|
+
targetVersion = dev;
|
|
200
|
+
updateTag = 'dev';
|
|
201
|
+
} else if (this.compareVersions(latest, this.currentVersion) > 0) {
|
|
202
|
+
updateAvailable = true;
|
|
203
|
+
targetVersion = latest;
|
|
204
|
+
updateTag = 'latest';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (updateAvailable) {
|
|
181
208
|
console.log(`\n${CONFIG.colors.yellow}▲ Update Available!${CONFIG.colors.reset}`);
|
|
182
209
|
console.log(`${CONFIG.colors.dim}Current version: ${this.currentVersion}${CONFIG.colors.reset}`);
|
|
183
|
-
console.log(`${CONFIG.colors.bright}
|
|
210
|
+
console.log(`${CONFIG.colors.bright}New version: ${targetVersion} (${updateTag})${CONFIG.colors.reset}`);
|
|
184
211
|
console.log();
|
|
185
212
|
|
|
186
213
|
// Ask if user wants to update now
|
|
@@ -199,7 +226,7 @@ export class SessionCoordinator {
|
|
|
199
226
|
if (updateNow) {
|
|
200
227
|
console.log(`\n${CONFIG.colors.blue}Updating s9n-devops-agent...${CONFIG.colors.reset}`);
|
|
201
228
|
try {
|
|
202
|
-
execSync(
|
|
229
|
+
execSync(`npm install -g s9n-devops-agent@${updateTag}`, {
|
|
203
230
|
stdio: 'inherit',
|
|
204
231
|
cwd: process.cwd()
|
|
205
232
|
});
|
|
@@ -207,10 +234,10 @@ export class SessionCoordinator {
|
|
|
207
234
|
process.exit(0);
|
|
208
235
|
} catch (err) {
|
|
209
236
|
console.log(`\n${CONFIG.colors.red}✗ Update failed: ${err.message}${CONFIG.colors.reset}`);
|
|
210
|
-
console.log(`${CONFIG.colors.dim}You can manually update with: npm install -g s9n-devops-agent
|
|
237
|
+
console.log(`${CONFIG.colors.dim}You can manually update with: npm install -g s9n-devops-agent@${updateTag}${CONFIG.colors.reset}`);
|
|
211
238
|
}
|
|
212
239
|
} else {
|
|
213
|
-
console.log(`${CONFIG.colors.dim}You can update later with: npm install -g s9n-devops-agent
|
|
240
|
+
console.log(`${CONFIG.colors.dim}You can update later with: npm install -g s9n-devops-agent@${updateTag}${CONFIG.colors.reset}`);
|
|
214
241
|
}
|
|
215
242
|
console.log();
|
|
216
243
|
} else {
|
|
@@ -224,19 +251,22 @@ export class SessionCoordinator {
|
|
|
224
251
|
}
|
|
225
252
|
|
|
226
253
|
/**
|
|
227
|
-
* Compare semantic versions
|
|
254
|
+
* Compare semantic versions (robust to suffixes)
|
|
228
255
|
* Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
|
229
256
|
*/
|
|
230
257
|
compareVersions(v1, v2) {
|
|
231
|
-
|
|
232
|
-
const parts2 = v2.split('.').map(Number);
|
|
258
|
+
if (!v1 || !v2) return 0;
|
|
233
259
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
260
|
+
const normalize = v => v.replace(/^v/, '').split('.').map(p => parseInt(p, 10));
|
|
261
|
+
const p1 = normalize(v1);
|
|
262
|
+
const p2 = normalize(v2);
|
|
263
|
+
|
|
264
|
+
for (let i = 0; i < Math.max(p1.length, p2.length); i++) {
|
|
265
|
+
const n1 = isNaN(p1[i]) ? 0 : p1[i];
|
|
266
|
+
const n2 = isNaN(p2[i]) ? 0 : p2[i];
|
|
237
267
|
|
|
238
|
-
if (
|
|
239
|
-
if (
|
|
268
|
+
if (n1 > n2) return 1;
|
|
269
|
+
if (n1 < n2) return -1;
|
|
240
270
|
}
|
|
241
271
|
|
|
242
272
|
return 0;
|
|
@@ -245,7 +275,9 @@ export class SessionCoordinator {
|
|
|
245
275
|
/**
|
|
246
276
|
* Ensure developer initials are configured globally
|
|
247
277
|
*/
|
|
248
|
-
async ensureGlobalSetup() {
|
|
278
|
+
async ensureGlobalSetup(skip = false) {
|
|
279
|
+
if (skip) return;
|
|
280
|
+
|
|
249
281
|
const globalSettings = this.loadGlobalSettings();
|
|
250
282
|
|
|
251
283
|
// Check if global setup is needed (developer initials)
|
|
@@ -321,7 +353,9 @@ export class SessionCoordinator {
|
|
|
321
353
|
/**
|
|
322
354
|
* Ensure house rules are set up for the project
|
|
323
355
|
*/
|
|
324
|
-
async ensureHouseRulesSetup() {
|
|
356
|
+
async ensureHouseRulesSetup(skip = false) {
|
|
357
|
+
if (skip) return;
|
|
358
|
+
|
|
325
359
|
const houseRulesManager = new HouseRulesManager(this.repoRoot);
|
|
326
360
|
const houseRulesPath = path.join(this.repoRoot, 'houserules.md');
|
|
327
361
|
|
|
@@ -427,13 +461,20 @@ export class SessionCoordinator {
|
|
|
427
461
|
/**
|
|
428
462
|
* Ensure project-specific version settings are configured
|
|
429
463
|
*/
|
|
430
|
-
async ensureProjectSetup() {
|
|
464
|
+
async ensureProjectSetup(options = {}) {
|
|
465
|
+
if (options.skip) return;
|
|
466
|
+
|
|
431
467
|
const projectSettings = this.loadProjectSettings();
|
|
432
468
|
|
|
433
469
|
// Check if project setup is needed (version strategy)
|
|
434
|
-
if (!projectSettings.versioningStrategy || !projectSettings.versioningStrategy.configured) {
|
|
435
|
-
console.log(`\n${CONFIG.colors.yellow}
|
|
436
|
-
|
|
470
|
+
if (options.force || !projectSettings.versioningStrategy || !projectSettings.versioningStrategy.configured) {
|
|
471
|
+
console.log(`\n${CONFIG.colors.yellow}Project Versioning Setup${CONFIG.colors.reset}`);
|
|
472
|
+
if (options.force) {
|
|
473
|
+
console.log(`${CONFIG.colors.dim}Reconfiguring version strategy...${CONFIG.colors.reset}`);
|
|
474
|
+
} else {
|
|
475
|
+
console.log(`${CONFIG.colors.yellow}First-time project setup for this repository!${CONFIG.colors.reset}`);
|
|
476
|
+
console.log(`${CONFIG.colors.dim}Let's configure the versioning strategy for this project${CONFIG.colors.reset}`);
|
|
477
|
+
}
|
|
437
478
|
|
|
438
479
|
const versionInfo = await this.promptForStartingVersion();
|
|
439
480
|
projectSettings.versioningStrategy = {
|
|
@@ -555,7 +596,7 @@ export class SessionCoordinator {
|
|
|
555
596
|
return {
|
|
556
597
|
...global,
|
|
557
598
|
...project,
|
|
558
|
-
developerInitials: global.developerInitials,
|
|
599
|
+
developerInitials: project.developerInitials || global.developerInitials,
|
|
559
600
|
configured: global.configured
|
|
560
601
|
};
|
|
561
602
|
}
|
|
@@ -657,6 +698,8 @@ export class SessionCoordinator {
|
|
|
657
698
|
const projectSettings = this.loadProjectSettings();
|
|
658
699
|
if (projectSettings.dockerConfig && projectSettings.dockerConfig.neverAsk === true) {
|
|
659
700
|
// User selected 'Never' - skip Docker configuration
|
|
701
|
+
// Show a subtle message so they know why it's skipped
|
|
702
|
+
console.log(`${CONFIG.colors.dim}Skipping Docker config (User preference: Never ask). Edit local_deploy/project-settings.json to enable.${CONFIG.colors.reset}`);
|
|
660
703
|
return { enabled: false, neverAsk: true };
|
|
661
704
|
}
|
|
662
705
|
|
|
@@ -797,6 +840,73 @@ export class SessionCoordinator {
|
|
|
797
840
|
return config;
|
|
798
841
|
}
|
|
799
842
|
|
|
843
|
+
/**
|
|
844
|
+
* Prompt for base branch (source)
|
|
845
|
+
*/
|
|
846
|
+
async promptForBaseBranch() {
|
|
847
|
+
console.log(`\n${CONFIG.colors.yellow}═══ Base Branch Selection ═══${CONFIG.colors.reset}`);
|
|
848
|
+
console.log(`${CONFIG.colors.dim}Which branch should I use as the starting point for your work?${CONFIG.colors.reset}`);
|
|
849
|
+
|
|
850
|
+
// Get available branches
|
|
851
|
+
const branches = this.getAvailableBranches();
|
|
852
|
+
// Prioritize main/develop/master
|
|
853
|
+
const priorityBranches = ['main', 'master', 'develop', 'development'];
|
|
854
|
+
|
|
855
|
+
const sortedBranches = branches.sort((a, b) => {
|
|
856
|
+
const aP = priorityBranches.indexOf(a);
|
|
857
|
+
const bP = priorityBranches.indexOf(b);
|
|
858
|
+
if (aP !== -1 && bP !== -1) return aP - bP;
|
|
859
|
+
if (aP !== -1) return -1;
|
|
860
|
+
if (bP !== -1) return 1;
|
|
861
|
+
return a.localeCompare(b);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
const uniqueBranches = [...new Set(sortedBranches)].slice(0, 10);
|
|
865
|
+
|
|
866
|
+
console.log();
|
|
867
|
+
uniqueBranches.forEach((branch, index) => {
|
|
868
|
+
const isPriority = priorityBranches.includes(branch);
|
|
869
|
+
const marker = isPriority ? ` ${CONFIG.colors.green}⭐${CONFIG.colors.reset}` : '';
|
|
870
|
+
console.log(` ${index + 1}) ${branch}${marker}`);
|
|
871
|
+
});
|
|
872
|
+
console.log(` 0) Enter a different branch name`);
|
|
873
|
+
console.log(` Hit Enter for default (HEAD)`);
|
|
874
|
+
|
|
875
|
+
const rl = readline.createInterface({
|
|
876
|
+
input: process.stdin,
|
|
877
|
+
output: process.stdout
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
return new Promise((resolve) => {
|
|
881
|
+
rl.question(`\nSelect base branch (1-${uniqueBranches.length}, 0, or Enter): `, (answer) => {
|
|
882
|
+
rl.close();
|
|
883
|
+
const choice = answer.trim();
|
|
884
|
+
|
|
885
|
+
if (choice === '') {
|
|
886
|
+
resolve('HEAD');
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const num = parseInt(choice);
|
|
891
|
+
|
|
892
|
+
if (num === 0) {
|
|
893
|
+
const rl2 = readline.createInterface({
|
|
894
|
+
input: process.stdin,
|
|
895
|
+
output: process.stdout
|
|
896
|
+
});
|
|
897
|
+
rl2.question('Enter custom branch name: ', (custom) => {
|
|
898
|
+
rl2.close();
|
|
899
|
+
resolve(custom.trim() || 'HEAD');
|
|
900
|
+
});
|
|
901
|
+
} else if (num >= 1 && num <= uniqueBranches.length) {
|
|
902
|
+
resolve(uniqueBranches[num - 1]);
|
|
903
|
+
} else {
|
|
904
|
+
resolve('HEAD');
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
800
910
|
/**
|
|
801
911
|
* Prompt for auto-merge configuration
|
|
802
912
|
*/
|
|
@@ -1112,15 +1222,50 @@ export class SessionCoordinator {
|
|
|
1112
1222
|
* Create a new session and generate Claude instructions
|
|
1113
1223
|
*/
|
|
1114
1224
|
async createSession(options = {}) {
|
|
1115
|
-
// Check for updates (once per day)
|
|
1116
|
-
await this.checkForUpdates();
|
|
1225
|
+
// Check for updates (once per day) - skip if requested (e.g. called from Kora)
|
|
1226
|
+
await this.checkForUpdates(options.skipUpdate);
|
|
1117
1227
|
|
|
1118
1228
|
// Ensure both global and project setup are complete
|
|
1119
|
-
await this.ensureGlobalSetup(); // Developer initials (once per user)
|
|
1120
|
-
await this.ensureProjectSetup(); // Version strategy (once per project)
|
|
1121
|
-
await this.ensureHouseRulesSetup(); // House rules setup (once per project)
|
|
1229
|
+
await this.ensureGlobalSetup(options.skipSetup); // Developer initials (once per user)
|
|
1230
|
+
await this.ensureProjectSetup({ force: false, skip: options.skipSetup }); // Version strategy (once per project)
|
|
1231
|
+
await this.ensureHouseRulesSetup(options.skipSetup); // House rules setup (once per project)
|
|
1122
1232
|
await this.ensureGroqApiKey(); // GROQ API key for AI commits (once per user)
|
|
1123
1233
|
|
|
1234
|
+
// Resume Check: If task provided, check for existing sessions with similar task names
|
|
1235
|
+
if (options.task && options.task !== 'development') {
|
|
1236
|
+
const matchingSession = this.findSessionByTask(options.task);
|
|
1237
|
+
if (matchingSession) {
|
|
1238
|
+
console.log(`\\n${CONFIG.colors.yellow}Found existing session for '${options.task}'${CONFIG.colors.reset}`);
|
|
1239
|
+
console.log(`Session ID: ${CONFIG.colors.bright}${matchingSession.sessionId}${CONFIG.colors.reset}`);
|
|
1240
|
+
console.log(`Status: ${matchingSession.status}`);
|
|
1241
|
+
console.log(`Branch: ${matchingSession.branchName}`);
|
|
1242
|
+
|
|
1243
|
+
const rlResume = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1244
|
+
const resume = await new Promise(resolve => {
|
|
1245
|
+
rlResume.question(`\\nDo you want to resume this session instead? (Y/n): `, ans => {
|
|
1246
|
+
rlResume.close();
|
|
1247
|
+
resolve(ans.trim().toLowerCase() !== 'n');
|
|
1248
|
+
});
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
if (resume) {
|
|
1252
|
+
// Return existing session info structure similar to createSession
|
|
1253
|
+
// but we might need to "claim" it if it's inactive
|
|
1254
|
+
if (matchingSession.status !== 'active') {
|
|
1255
|
+
// Claim/Restart it
|
|
1256
|
+
return this.claimSession(matchingSession, options.agent || 'claude');
|
|
1257
|
+
} else {
|
|
1258
|
+
// It's already active, just return info so startAgent can pick it up
|
|
1259
|
+
// But startAgent requires lock file integrity.
|
|
1260
|
+
// If it's active, we might be double-attaching unless we check PID.
|
|
1261
|
+
// findAvailableSession logic already handles dead PIDs.
|
|
1262
|
+
// If PID is alive, we probably shouldn't interfere, but here we assume user knows best.
|
|
1263
|
+
return matchingSession;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1124
1269
|
const sessionId = this.generateSessionId();
|
|
1125
1270
|
const task = options.task || 'development';
|
|
1126
1271
|
|
|
@@ -1172,6 +1317,12 @@ export class SessionCoordinator {
|
|
|
1172
1317
|
// Ask for auto-merge configuration
|
|
1173
1318
|
const mergeConfig = await this.promptForMergeConfig();
|
|
1174
1319
|
|
|
1320
|
+
// Ask for base branch (where to start work from)
|
|
1321
|
+
const baseBranch = await this.promptForBaseBranch();
|
|
1322
|
+
|
|
1323
|
+
// Ask for auto-rebase interval
|
|
1324
|
+
const rebaseInterval = await this.promptForRebaseInterval();
|
|
1325
|
+
|
|
1175
1326
|
// Check for Docker configuration and ask about restart preference
|
|
1176
1327
|
let dockerConfig = null;
|
|
1177
1328
|
|
|
@@ -1321,9 +1472,15 @@ export class SessionCoordinator {
|
|
|
1321
1472
|
|
|
1322
1473
|
// Create worktree
|
|
1323
1474
|
console.log(`\n${CONFIG.colors.yellow}Creating worktree...${CONFIG.colors.reset}`);
|
|
1324
|
-
|
|
1475
|
+
const baseRef = baseBranch || 'HEAD';
|
|
1476
|
+
console.log(`${CONFIG.colors.dim}Branching off: ${baseRef}${CONFIG.colors.reset}`);
|
|
1477
|
+
|
|
1478
|
+
execSync(`git worktree add -b ${branchName} "${worktreePath}" ${baseRef}`, { stdio: 'pipe' });
|
|
1325
1479
|
console.log(`${CONFIG.colors.green}✓${CONFIG.colors.reset} Worktree created at: ${worktreePath}`);
|
|
1326
1480
|
|
|
1481
|
+
// Store base branch in session data for rebase logic
|
|
1482
|
+
const sessionBaseBranch = baseRef === 'HEAD' ? await this.resolveHeadBranch() : baseRef;
|
|
1483
|
+
|
|
1327
1484
|
// If we're in a submodule, set up the correct remote for the worktree
|
|
1328
1485
|
if (isSubmodule && parentRemote) {
|
|
1329
1486
|
console.log(`${CONFIG.colors.yellow}Configuring worktree to use parent repository remote...${CONFIG.colors.reset}`);
|
|
@@ -1345,16 +1502,17 @@ export class SessionCoordinator {
|
|
|
1345
1502
|
task,
|
|
1346
1503
|
worktreePath,
|
|
1347
1504
|
branchName,
|
|
1505
|
+
baseBranch: sessionBaseBranch,
|
|
1348
1506
|
created: new Date().toISOString(),
|
|
1349
1507
|
status: 'active',
|
|
1350
1508
|
pid: process.pid,
|
|
1351
1509
|
developerInitials: devInitials,
|
|
1352
1510
|
mergeConfig: mergeConfig,
|
|
1511
|
+
rebaseInterval: rebaseInterval,
|
|
1353
1512
|
dockerConfig: dockerConfig
|
|
1354
1513
|
};
|
|
1355
1514
|
|
|
1356
1515
|
const lockFile = path.join(this.locksPath, `${sessionId}.lock`);
|
|
1357
|
-
fs.writeFileSync(lockFile, JSON.stringify(lockData, null, 2));
|
|
1358
1516
|
|
|
1359
1517
|
// Generate Claude instructions
|
|
1360
1518
|
const instructions = this.generateClaudeInstructions(lockData);
|
|
@@ -1372,6 +1530,9 @@ export class SessionCoordinator {
|
|
|
1372
1530
|
// Store instructions in lockData so createAndStart can access them
|
|
1373
1531
|
lockData.instructions = instructions;
|
|
1374
1532
|
|
|
1533
|
+
// Write lock file with instructions included
|
|
1534
|
+
fs.writeFileSync(lockFile, JSON.stringify(lockData, null, 2));
|
|
1535
|
+
|
|
1375
1536
|
return {
|
|
1376
1537
|
sessionId,
|
|
1377
1538
|
worktreePath,
|
|
@@ -1387,6 +1548,46 @@ export class SessionCoordinator {
|
|
|
1387
1548
|
}
|
|
1388
1549
|
}
|
|
1389
1550
|
|
|
1551
|
+
/**
|
|
1552
|
+
* Prompt for auto-rebase interval
|
|
1553
|
+
*/
|
|
1554
|
+
async promptForRebaseInterval() {
|
|
1555
|
+
console.log(`\n${CONFIG.colors.yellow}═══ Auto-Rebase Configuration ═══${CONFIG.colors.reset}`);
|
|
1556
|
+
console.log(`${CONFIG.colors.dim}I can automatically pull updates from the base branch to keep you up-to-date.${CONFIG.colors.reset}`);
|
|
1557
|
+
console.log(`${CONFIG.colors.dim}This helps prevent conflicts later by rebasing your work periodically.${CONFIG.colors.reset}`);
|
|
1558
|
+
|
|
1559
|
+
const rl = readline.createInterface({
|
|
1560
|
+
input: process.stdin,
|
|
1561
|
+
output: process.stdout
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
return new Promise((resolve) => {
|
|
1565
|
+
rl.question(`\nHow often should I rebase? (in hours, 0 to disable) [0]: `, (answer) => {
|
|
1566
|
+
rl.close();
|
|
1567
|
+
const hours = parseFloat(answer.trim());
|
|
1568
|
+
if (isNaN(hours) || hours <= 0) {
|
|
1569
|
+
console.log(`${CONFIG.colors.dim}Auto-rebase disabled. I'll let you manage updates manually.${CONFIG.colors.reset}`);
|
|
1570
|
+
resolve(0);
|
|
1571
|
+
} else {
|
|
1572
|
+
console.log(`${CONFIG.colors.green}✓ I'll check for updates and rebase every ${hours} hour(s).${CONFIG.colors.reset}`);
|
|
1573
|
+
resolve(hours);
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Resolve HEAD to actual branch name
|
|
1581
|
+
*/
|
|
1582
|
+
async resolveHeadBranch() {
|
|
1583
|
+
try {
|
|
1584
|
+
const head = execSync('git rev-parse --abbrev-ref HEAD', { cwd: this.repoRoot, encoding: 'utf8' }).trim();
|
|
1585
|
+
return head;
|
|
1586
|
+
} catch (e) {
|
|
1587
|
+
return 'main';
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1390
1591
|
/**
|
|
1391
1592
|
* Generate instructions for the coding agent
|
|
1392
1593
|
*/
|
|
@@ -1403,7 +1604,7 @@ INSTRUCTIONS:
|
|
|
1403
1604
|
1. Change to worktree directory: cd "${worktreePath}"
|
|
1404
1605
|
2. Verify branch: git branch --show-current
|
|
1405
1606
|
3. Make your changes for: ${task}
|
|
1406
|
-
4. Write commit message to: .devops-commit-${sessionId}.msg
|
|
1607
|
+
4. Write commit message to: .devops-commit-${sessionId}.msg (use >> to append)
|
|
1407
1608
|
5. The DevOps agent will auto-commit and push your changes
|
|
1408
1609
|
`;
|
|
1409
1610
|
|
|
@@ -1459,7 +1660,7 @@ Make changes for: **${task}**
|
|
|
1459
1660
|
### Step 5: Commit Your Changes
|
|
1460
1661
|
Write your commit message to the session-specific file:
|
|
1461
1662
|
\`\`\`bash
|
|
1462
|
-
echo "feat: your commit message here"
|
|
1663
|
+
echo "feat: your commit message here" >> .devops-commit-${sessionId}.msg
|
|
1463
1664
|
\`\`\`
|
|
1464
1665
|
|
|
1465
1666
|
### Step 6: Release Your File Locks
|
|
@@ -1550,6 +1751,7 @@ The DevOps agent will automatically:
|
|
|
1550
1751
|
console.log(`}`);
|
|
1551
1752
|
console.log(``);
|
|
1552
1753
|
console.log(`Write commit messages to: .devops-commit-${sessionId}.msg`);
|
|
1754
|
+
console.log(`(Use '>>' to append if you want to add to an existing message)`);
|
|
1553
1755
|
console.log(`The DevOps agent will automatically commit and push changes.`);
|
|
1554
1756
|
console.log(``);
|
|
1555
1757
|
console.log(`⛔ IMPORTANT: STOP HERE AND WAIT`);
|
|
@@ -1709,7 +1911,33 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1709
1911
|
}
|
|
1710
1912
|
|
|
1711
1913
|
/**
|
|
1712
|
-
* Find
|
|
1914
|
+
* Find a session by task name (fuzzy match)
|
|
1915
|
+
*/
|
|
1916
|
+
findSessionByTask(taskName) {
|
|
1917
|
+
if (!fs.existsSync(this.locksPath)) return null;
|
|
1918
|
+
|
|
1919
|
+
const locks = fs.readdirSync(this.locksPath).filter(f => f.endsWith('.lock'));
|
|
1920
|
+
const search = taskName.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
1921
|
+
|
|
1922
|
+
for (const lockFile of locks) {
|
|
1923
|
+
try {
|
|
1924
|
+
const lockPath = path.join(this.locksPath, lockFile);
|
|
1925
|
+
const session = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
1926
|
+
const task = (session.task || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
1927
|
+
|
|
1928
|
+
// Exact match or significant partial match
|
|
1929
|
+
if (task === search || (task.length > 4 && task.includes(search)) || (search.length > 4 && search.includes(task))) {
|
|
1930
|
+
return session;
|
|
1931
|
+
}
|
|
1932
|
+
} catch (e) {
|
|
1933
|
+
// Ignore invalid locks
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* Find an available unclaimed session or orphaned session
|
|
1713
1941
|
*/
|
|
1714
1942
|
findAvailableSession() {
|
|
1715
1943
|
if (!fs.existsSync(this.locksPath)) {
|
|
@@ -1720,11 +1948,33 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1720
1948
|
|
|
1721
1949
|
for (const lockFile of locks) {
|
|
1722
1950
|
const lockPath = path.join(this.locksPath, lockFile);
|
|
1723
|
-
|
|
1951
|
+
try {
|
|
1952
|
+
const lockData = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
1724
1953
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1954
|
+
// Check if session is available (not claimed)
|
|
1955
|
+
if (lockData.status === 'waiting' && !lockData.claimedBy) {
|
|
1956
|
+
return lockData;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// Check if session is orphaned/stopped but not cleaned up
|
|
1960
|
+
// If the PID is no longer running, it might be orphaned
|
|
1961
|
+
if (lockData.status === 'active' && lockData.agentPid) {
|
|
1962
|
+
try {
|
|
1963
|
+
// Check if process exists
|
|
1964
|
+
process.kill(lockData.agentPid, 0);
|
|
1965
|
+
} catch (e) {
|
|
1966
|
+
// Process doesn't exist - it's orphaned!
|
|
1967
|
+
console.log(`${CONFIG.colors.yellow}Found orphaned session: ${lockData.sessionId} (PID ${lockData.agentPid} dead)${CONFIG.colors.reset}`);
|
|
1968
|
+
// Mark as stopped so it can be reclaimed
|
|
1969
|
+
lockData.status = 'stopped';
|
|
1970
|
+
lockData.agentPid = null;
|
|
1971
|
+
lockData.agentStopped = new Date().toISOString();
|
|
1972
|
+
fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
|
|
1973
|
+
return lockData;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
} catch (e) {
|
|
1977
|
+
// Invalid lock file
|
|
1728
1978
|
}
|
|
1729
1979
|
}
|
|
1730
1980
|
|
|
@@ -1745,10 +1995,11 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1745
1995
|
const instructions = this.generateClaudeInstructions(session);
|
|
1746
1996
|
// Don't display instructions here - they'll be shown after agent starts
|
|
1747
1997
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1998
|
+
// Add instructions to session object and save to lock file
|
|
1999
|
+
session.instructions = instructions;
|
|
2000
|
+
fs.writeFileSync(lockFile, JSON.stringify(session, null, 2));
|
|
2001
|
+
|
|
2002
|
+
return session;
|
|
1752
2003
|
}
|
|
1753
2004
|
|
|
1754
2005
|
/**
|
|
@@ -1759,7 +2010,7 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1759
2010
|
|
|
1760
2011
|
if (!fs.existsSync(lockFile)) {
|
|
1761
2012
|
console.error(`${CONFIG.colors.red}Session not found: ${sessionId}${CONFIG.colors.reset}`);
|
|
1762
|
-
return;
|
|
2013
|
+
return false;
|
|
1763
2014
|
}
|
|
1764
2015
|
|
|
1765
2016
|
const sessionData = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
|
|
@@ -1794,7 +2045,11 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1794
2045
|
AC_DATE_STYLE: process.env.AC_DATE_STYLE || 'dash', // Preserve date style
|
|
1795
2046
|
// Apply version configuration if set
|
|
1796
2047
|
...(projectSettings.versioningStrategy?.prefix && { AC_VERSION_PREFIX: projectSettings.versioningStrategy.prefix }),
|
|
1797
|
-
...(projectSettings.versioningStrategy?.startMinor && { AC_VERSION_START_MINOR: projectSettings.versioningStrategy.startMinor.toString() })
|
|
2048
|
+
...(projectSettings.versioningStrategy?.startMinor && { AC_VERSION_START_MINOR: projectSettings.versioningStrategy.startMinor.toString() }),
|
|
2049
|
+
|
|
2050
|
+
// Rebase configuration
|
|
2051
|
+
AC_REBASE_INTERVAL: (sessionData.rebaseInterval || 0).toString(),
|
|
2052
|
+
AC_BASE_BRANCH: sessionData.baseBranch || 'HEAD' // We need to pass the base branch for rebasing
|
|
1798
2053
|
};
|
|
1799
2054
|
|
|
1800
2055
|
const agentScript = path.join(__dirname, 'cs-devops-agent-worker.js');
|
|
@@ -1812,16 +2067,6 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1812
2067
|
silent: false
|
|
1813
2068
|
});
|
|
1814
2069
|
|
|
1815
|
-
// Wait for agent to initialize and display its interactive commands
|
|
1816
|
-
// Then show the copy-paste instructions
|
|
1817
|
-
setTimeout(async () => {
|
|
1818
|
-
console.log('\n'); // Add spacing
|
|
1819
|
-
|
|
1820
|
-
// Generate and display instructions
|
|
1821
|
-
const instructions = this.generateClaudeInstructions(sessionData);
|
|
1822
|
-
this.displayInstructions(instructions, sessionId, sessionData.task);
|
|
1823
|
-
}, 3000); // Wait 3 seconds for agent to show interactive commands
|
|
1824
|
-
|
|
1825
2070
|
child.on('exit', (code) => {
|
|
1826
2071
|
console.log(`${CONFIG.colors.yellow}Agent exited with code: ${code}${CONFIG.colors.reset}`);
|
|
1827
2072
|
|
|
@@ -1837,6 +2082,8 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
1837
2082
|
child.kill('SIGINT');
|
|
1838
2083
|
setTimeout(() => process.exit(0), 1000);
|
|
1839
2084
|
});
|
|
2085
|
+
|
|
2086
|
+
return true;
|
|
1840
2087
|
}
|
|
1841
2088
|
|
|
1842
2089
|
/**
|
|
@@ -2171,8 +2418,114 @@ The DevOps agent is monitoring this worktree for changes.
|
|
|
2171
2418
|
}
|
|
2172
2419
|
|
|
2173
2420
|
/**
|
|
2174
|
-
*
|
|
2421
|
+
* Recover sessions from existing worktrees that are missing lock files
|
|
2175
2422
|
*/
|
|
2423
|
+
async recoverSessions() {
|
|
2424
|
+
console.log(`\n${CONFIG.colors.yellow}Scanning for recoverable sessions...${CONFIG.colors.reset}`);
|
|
2425
|
+
|
|
2426
|
+
if (!fs.existsSync(this.worktreesPath)) {
|
|
2427
|
+
console.log('No worktrees directory found.');
|
|
2428
|
+
return 0;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
const worktrees = fs.readdirSync(this.worktreesPath);
|
|
2432
|
+
let recovered = 0;
|
|
2433
|
+
|
|
2434
|
+
for (const dir of worktrees) {
|
|
2435
|
+
// Skip .DS_Store and other system files
|
|
2436
|
+
if (dir.startsWith('.')) continue;
|
|
2437
|
+
|
|
2438
|
+
const worktreePath = path.join(this.worktreesPath, dir);
|
|
2439
|
+
|
|
2440
|
+
// Ensure it's a directory
|
|
2441
|
+
try {
|
|
2442
|
+
if (!fs.statSync(worktreePath).isDirectory()) continue;
|
|
2443
|
+
} catch (e) { continue; }
|
|
2444
|
+
|
|
2445
|
+
const configPath = path.join(worktreePath, '.devops-session.json');
|
|
2446
|
+
|
|
2447
|
+
if (fs.existsSync(configPath)) {
|
|
2448
|
+
try {
|
|
2449
|
+
const sessionData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
2450
|
+
|
|
2451
|
+
if (!sessionData.sessionId) continue;
|
|
2452
|
+
|
|
2453
|
+
const lockFile = path.join(this.locksPath, `${sessionData.sessionId}.lock`);
|
|
2454
|
+
|
|
2455
|
+
if (!fs.existsSync(lockFile)) {
|
|
2456
|
+
// Restore lock file
|
|
2457
|
+
// Reset status to 'stopped' so it can be resumed/claimed
|
|
2458
|
+
sessionData.status = 'stopped';
|
|
2459
|
+
sessionData.agentPid = null;
|
|
2460
|
+
sessionData.agentStopped = new Date().toISOString();
|
|
2461
|
+
sessionData.recoveredAt = new Date().toISOString();
|
|
2462
|
+
|
|
2463
|
+
fs.writeFileSync(lockFile, JSON.stringify(sessionData, null, 2));
|
|
2464
|
+
console.log(`${CONFIG.colors.green}✓ Recovered session ${sessionData.sessionId} (${sessionData.task})${CONFIG.colors.reset}`);
|
|
2465
|
+
recovered++;
|
|
2466
|
+
|
|
2467
|
+
// Check for uncommitted changes in the recovered session
|
|
2468
|
+
try {
|
|
2469
|
+
const status = execSync(`git -C "${worktreePath}" status --porcelain`, { encoding: 'utf8' });
|
|
2470
|
+
if (status.trim()) {
|
|
2471
|
+
console.log(`\n${CONFIG.colors.yellow}Uncommitted changes found in recovered session ${sessionData.sessionId}${CONFIG.colors.reset}`);
|
|
2472
|
+
|
|
2473
|
+
const rl = readline.createInterface({
|
|
2474
|
+
input: process.stdin,
|
|
2475
|
+
output: process.stdout
|
|
2476
|
+
});
|
|
2477
|
+
|
|
2478
|
+
const commitNow = await new Promise(resolve => {
|
|
2479
|
+
rl.question('Would you like to commit these changes now? (Y/n): ', answer => {
|
|
2480
|
+
rl.close();
|
|
2481
|
+
resolve(answer.toLowerCase() !== 'n' && answer.toLowerCase() !== 'no');
|
|
2482
|
+
});
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
if (commitNow) {
|
|
2486
|
+
const timestamp = new Date().toISOString();
|
|
2487
|
+
execSync(`git -C "${worktreePath}" add -A`, { stdio: 'ignore' });
|
|
2488
|
+
execSync(`git -C "${worktreePath}" commit -m "chore: recovered session auto-commit at ${timestamp}"`, { stdio: 'ignore' });
|
|
2489
|
+
console.log(`${CONFIG.colors.green}✓ Changes committed.${CONFIG.colors.reset}`);
|
|
2490
|
+
|
|
2491
|
+
// Ask to push
|
|
2492
|
+
const rlPush = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2493
|
+
const pushNow = await new Promise(resolve => {
|
|
2494
|
+
rlPush.question('Push changes to remote? (Y/n): ', answer => {
|
|
2495
|
+
rlPush.close();
|
|
2496
|
+
resolve(answer.toLowerCase() !== 'n' && answer.toLowerCase() !== 'no');
|
|
2497
|
+
});
|
|
2498
|
+
});
|
|
2499
|
+
|
|
2500
|
+
if (pushNow) {
|
|
2501
|
+
try {
|
|
2502
|
+
execSync(`git -C "${worktreePath}" push origin ${sessionData.branchName}`, { stdio: 'ignore' });
|
|
2503
|
+
console.log(`${CONFIG.colors.green}✓ Changes pushed to ${sessionData.branchName}.${CONFIG.colors.reset}`);
|
|
2504
|
+
} catch (e) {
|
|
2505
|
+
console.log(`${CONFIG.colors.red}✗ Push failed. You may need to pull first or check remote.${CONFIG.colors.reset}`);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
} catch (e) {
|
|
2511
|
+
// Ignore git errors during recovery scan
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
} catch (err) {
|
|
2515
|
+
console.error(`Failed to recover ${dir}: ${err.message}`);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
if (recovered === 0) {
|
|
2521
|
+
console.log('No orphaned sessions found to recover.');
|
|
2522
|
+
} else {
|
|
2523
|
+
console.log(`\n${CONFIG.colors.green}Recovered ${recovered} sessions. You can now resume them.${CONFIG.colors.reset}`);
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
return recovered;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2176
2529
|
async cleanupAll() {
|
|
2177
2530
|
console.log(`\n${CONFIG.colors.yellow}Cleaning up stale sessions and worktrees...${CONFIG.colors.reset}`);
|
|
2178
2531
|
|
|
@@ -2248,7 +2601,7 @@ async function main() {
|
|
|
2248
2601
|
console.log(" CS_DevOpsAgent - Intelligent Git Automation System");
|
|
2249
2602
|
console.log(` Version ${packageJson.version} | Build ${new Date().toISOString().split('T')[0].replace(/-/g, '')}`);
|
|
2250
2603
|
console.log(" ");
|
|
2251
|
-
console.log(" Copyright (c)
|
|
2604
|
+
console.log(" Copyright (c) 2026 SeKondBrain AI Labs Limited");
|
|
2252
2605
|
console.log(" Author: Sachin Dev Duggal");
|
|
2253
2606
|
console.log(" ");
|
|
2254
2607
|
console.log(" Licensed under the MIT License");
|
|
@@ -2281,8 +2634,36 @@ async function main() {
|
|
|
2281
2634
|
// Start agent for a session
|
|
2282
2635
|
const sessionId = args[1];
|
|
2283
2636
|
if (!sessionId) {
|
|
2637
|
+
// Ask if user wants Kora assistance
|
|
2638
|
+
const koraRl = readline.createInterface({
|
|
2639
|
+
input: process.stdin,
|
|
2640
|
+
output: process.stdout
|
|
2641
|
+
});
|
|
2642
|
+
|
|
2643
|
+
console.log(`\n${CONFIG.colors.magenta}🤖 Kora AI Assistant Available${CONFIG.colors.reset}`);
|
|
2644
|
+
const useKora = await new Promise(resolve => {
|
|
2645
|
+
koraRl.question(`Would you like Kora to guide you? (Y/n): `, answer => {
|
|
2646
|
+
koraRl.close();
|
|
2647
|
+
resolve(answer.toLowerCase() !== 'n' && answer.toLowerCase() !== 'no');
|
|
2648
|
+
});
|
|
2649
|
+
});
|
|
2650
|
+
|
|
2651
|
+
if (useKora) {
|
|
2652
|
+
console.log(`\n${CONFIG.colors.magenta}Launching Kora...${CONFIG.colors.reset}`);
|
|
2653
|
+
const chatScript = path.join(__dirname, 'agent-chat.js');
|
|
2654
|
+
const child = spawn('node', [chatScript], {
|
|
2655
|
+
stdio: 'inherit',
|
|
2656
|
+
env: process.env
|
|
2657
|
+
});
|
|
2658
|
+
|
|
2659
|
+
child.on('exit', (code) => {
|
|
2660
|
+
process.exit(code);
|
|
2661
|
+
});
|
|
2662
|
+
return; // Hand off to Kora
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2284
2665
|
// No session ID provided - show interactive menu
|
|
2285
|
-
console.log(
|
|
2666
|
+
console.log(`\n${CONFIG.colors.bright}DevOps Agent Session Manager${CONFIG.colors.reset}\n`);
|
|
2286
2667
|
|
|
2287
2668
|
// Show existing sessions first
|
|
2288
2669
|
const locks = fs.existsSync(coordinator.locksPath) ?
|
|
@@ -2376,7 +2757,10 @@ async function main() {
|
|
|
2376
2757
|
args[args.indexOf('--agent') + 1] :
|
|
2377
2758
|
undefined; // Pass undefined to trigger prompt in createSession
|
|
2378
2759
|
|
|
2379
|
-
|
|
2760
|
+
const skipSetup = args.includes('--skip-setup');
|
|
2761
|
+
const skipUpdate = args.includes('--skip-update');
|
|
2762
|
+
|
|
2763
|
+
await coordinator.createAndStart({ task, agent, skipSetup, skipUpdate });
|
|
2380
2764
|
break;
|
|
2381
2765
|
}
|
|
2382
2766
|
|
|
@@ -2386,6 +2770,39 @@ async function main() {
|
|
|
2386
2770
|
await coordinator.requestSession(agent);
|
|
2387
2771
|
break;
|
|
2388
2772
|
}
|
|
2773
|
+
|
|
2774
|
+
case 'resume': {
|
|
2775
|
+
// Resume an existing session by ID or Task
|
|
2776
|
+
const sessionId = args.includes('--session-id') ?
|
|
2777
|
+
args[args.indexOf('--session-id') + 1] :
|
|
2778
|
+
undefined;
|
|
2779
|
+
|
|
2780
|
+
const task = args.includes('--task') ?
|
|
2781
|
+
args[args.indexOf('--task') + 1] :
|
|
2782
|
+
undefined;
|
|
2783
|
+
|
|
2784
|
+
let targetSessionId = sessionId;
|
|
2785
|
+
|
|
2786
|
+
if (!targetSessionId && task) {
|
|
2787
|
+
const session = coordinator.findSessionByTask(task);
|
|
2788
|
+
if (session) {
|
|
2789
|
+
targetSessionId = session.sessionId;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
if (targetSessionId) {
|
|
2794
|
+
// Check if session is already active/claimed?
|
|
2795
|
+
// startAgent checks existence.
|
|
2796
|
+
const success = await coordinator.startAgent(targetSessionId);
|
|
2797
|
+
if (!success) process.exit(1);
|
|
2798
|
+
} else {
|
|
2799
|
+
console.error(`${CONFIG.colors.red}Error: Could not find session to resume.${CONFIG.colors.reset}`);
|
|
2800
|
+
if (task) console.error(`No session found matching task: ${task}`);
|
|
2801
|
+
else console.error(`Please provide --session-id or --task`);
|
|
2802
|
+
process.exit(1);
|
|
2803
|
+
}
|
|
2804
|
+
break;
|
|
2805
|
+
}
|
|
2389
2806
|
|
|
2390
2807
|
case 'list': {
|
|
2391
2808
|
coordinator.listSessions();
|
|
@@ -2404,11 +2821,23 @@ async function main() {
|
|
|
2404
2821
|
break;
|
|
2405
2822
|
}
|
|
2406
2823
|
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2824
|
+
case 'cleanup': {
|
|
2825
|
+
// Clean up stale sessions and worktrees
|
|
2826
|
+
await coordinator.cleanupAll();
|
|
2827
|
+
break;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
case 'recover': {
|
|
2831
|
+
// Recover orphaned sessions from worktrees
|
|
2832
|
+
await coordinator.recoverSessions();
|
|
2833
|
+
break;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
case 'recover': {
|
|
2837
|
+
// Recover orphaned sessions from worktrees
|
|
2838
|
+
await coordinator.recoverSessions();
|
|
2839
|
+
break;
|
|
2840
|
+
}
|
|
2412
2841
|
|
|
2413
2842
|
case 'help':
|
|
2414
2843
|
default: {
|