@imdeadpool/guardex 7.0.22 → 7.0.24
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 +11 -0
- package/package.json +5 -1
- package/src/cli/args.js +89 -0
- package/src/cli/main.js +54 -75
- package/src/context.js +16 -2
- package/src/core/stdin.js +52 -0
- package/src/core/versions.js +33 -0
- package/src/hooks/index.js +64 -0
- package/src/output/index.js +64 -4
- package/src/report/session-severity.js +282 -0
- package/src/scaffold/index.js +78 -131
- package/src/toolchain/index.js +6 -70
- package/templates/AGENTS.multiagent-safety.md +25 -0
- package/templates/scripts/agent-branch-finish.sh +79 -1
- package/templates/scripts/agent-branch-start.sh +35 -0
- package/templates/scripts/agent-session-state.js +62 -1
- package/templates/scripts/codex-agent.sh +38 -0
- package/templates/scripts/install-vscode-active-agents-extension.js +38 -11
- package/templates/scripts/openspec/init-plan-workspace.sh +34 -3
- package/templates/vscode/guardex-active-agents/README.md +7 -6
- package/templates/vscode/guardex-active-agents/extension.js +523 -73
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/package.json +13 -3
- package/templates/vscode/guardex-active-agents/session-schema.js +311 -4
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
formatElapsedFrom,
|
|
7
7
|
readActiveSessions,
|
|
8
8
|
readRepoChanges,
|
|
9
|
+
readSessionInspectData,
|
|
9
10
|
sanitizeBranchForFile,
|
|
10
11
|
} = require('./session-schema.js');
|
|
11
12
|
|
|
@@ -16,6 +17,7 @@ const LOCK_FILE_RELATIVE = path.join('.omx', 'state', 'agent-file-locks.json');
|
|
|
16
17
|
const ACTIVE_SESSION_FILES_GLOB = '**/.omx/state/active-sessions/*.json';
|
|
17
18
|
const AGENT_FILE_LOCKS_GLOB = '**/.omx/state/agent-file-locks.json';
|
|
18
19
|
const WORKTREE_AGENT_LOCKS_GLOB = '**/{.omx,.omc}/agent-worktrees/**/AGENT.lock';
|
|
20
|
+
const AGENT_LOG_FILES_GLOB = '**/.omx/logs/*.log';
|
|
19
21
|
const SESSION_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git,.omx/agent-worktrees,.omc/agent-worktrees}/**';
|
|
20
22
|
const WORKTREE_LOCK_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git}/**';
|
|
21
23
|
const SESSION_SCAN_LIMIT = 200;
|
|
@@ -24,17 +26,19 @@ const ACTIVE_AGENTS_MANIFEST_RELATIVE = path.join('vscode', 'guardex-active-agen
|
|
|
24
26
|
const ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE = path.join('scripts', 'install-vscode-active-agents-extension.js');
|
|
25
27
|
const RELOAD_WINDOW_ACTION = 'Reload Window';
|
|
26
28
|
const UPDATE_LATER_ACTION = 'Later';
|
|
29
|
+
const REFRESH_POLL_INTERVAL_MS = 30_000;
|
|
30
|
+
const INSPECT_PANEL_VIEW_TYPE = 'gitguardex.activeAgents.inspect';
|
|
27
31
|
const SESSION_ACTIVITY_GROUPS = [
|
|
28
32
|
{ kind: 'blocked', label: 'BLOCKED' },
|
|
29
33
|
{ kind: 'working', label: 'WORKING NOW' },
|
|
30
|
-
{ kind: 'idle', label: '
|
|
34
|
+
{ kind: 'idle', label: 'THINKING' },
|
|
31
35
|
{ kind: 'stalled', label: 'STALLED' },
|
|
32
36
|
{ kind: 'dead', label: 'DEAD' },
|
|
33
37
|
];
|
|
34
38
|
const SESSION_ACTIVITY_ICON_IDS = {
|
|
35
39
|
blocked: 'warning',
|
|
36
|
-
working: '
|
|
37
|
-
idle: '
|
|
40
|
+
working: 'loading~spin',
|
|
41
|
+
idle: 'comment-discussion',
|
|
38
42
|
stalled: 'clock',
|
|
39
43
|
dead: 'error',
|
|
40
44
|
};
|
|
@@ -168,6 +172,134 @@ function buildActiveAgentsStatusTooltip(selectedSession, summary) {
|
|
|
168
172
|
].filter(Boolean).join('\n');
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
function escapeHtml(value) {
|
|
176
|
+
return String(value || '')
|
|
177
|
+
.replace(/&/g, '&')
|
|
178
|
+
.replace(/</g, '<')
|
|
179
|
+
.replace(/>/g, '>')
|
|
180
|
+
.replace(/"/g, '"')
|
|
181
|
+
.replace(/'/g, ''');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function formatInspectBranchSummary(inspectData) {
|
|
185
|
+
if (Number.isInteger(inspectData?.aheadCount) && Number.isInteger(inspectData?.behindCount)) {
|
|
186
|
+
return `${inspectData.aheadCount} ahead · ${inspectData.behindCount} behind vs ${inspectData.compareRef}`;
|
|
187
|
+
}
|
|
188
|
+
return `Branch comparison unavailable vs ${inspectData?.compareRef || 'origin/dev'}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function inspectPanelTitle(session) {
|
|
192
|
+
return `Inspect ${sessionDisplayLabel(session)}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function renderInspectPanelHtml(session, inspectData) {
|
|
196
|
+
const heldLocksMarkup = Array.isArray(inspectData?.heldLocks) && inspectData.heldLocks.length > 0
|
|
197
|
+
? `<ul>${inspectData.heldLocks.map((entry) => (
|
|
198
|
+
`<li><code>${escapeHtml(entry.relativePath)}</code>${entry.allowDelete ? ' <span class="pill">delete ok</span>' : ''}${entry.claimedAt ? ` <span class="muted">${escapeHtml(entry.claimedAt)}</span>` : ''}</li>`
|
|
199
|
+
)).join('')}</ul>`
|
|
200
|
+
: '<p class="muted">No held locks recorded for this session.</p>';
|
|
201
|
+
const logContent = inspectData?.logTailText
|
|
202
|
+
? escapeHtml(inspectData.logTailText)
|
|
203
|
+
: 'No log output available.';
|
|
204
|
+
|
|
205
|
+
return `<!DOCTYPE html>
|
|
206
|
+
<html lang="en">
|
|
207
|
+
<head>
|
|
208
|
+
<meta charset="utf-8" />
|
|
209
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
210
|
+
<style>
|
|
211
|
+
:root {
|
|
212
|
+
color-scheme: light dark;
|
|
213
|
+
font-family: var(--vscode-font-family);
|
|
214
|
+
}
|
|
215
|
+
body {
|
|
216
|
+
padding: 16px;
|
|
217
|
+
color: var(--vscode-foreground);
|
|
218
|
+
background: var(--vscode-editor-background);
|
|
219
|
+
}
|
|
220
|
+
h1, h2 {
|
|
221
|
+
margin: 0 0 12px;
|
|
222
|
+
font-weight: 600;
|
|
223
|
+
}
|
|
224
|
+
h2 {
|
|
225
|
+
margin-top: 20px;
|
|
226
|
+
font-size: 13px;
|
|
227
|
+
text-transform: uppercase;
|
|
228
|
+
letter-spacing: 0.04em;
|
|
229
|
+
color: var(--vscode-descriptionForeground);
|
|
230
|
+
}
|
|
231
|
+
.grid {
|
|
232
|
+
display: grid;
|
|
233
|
+
grid-template-columns: minmax(140px, 220px) 1fr;
|
|
234
|
+
gap: 8px 12px;
|
|
235
|
+
margin: 0;
|
|
236
|
+
}
|
|
237
|
+
dt {
|
|
238
|
+
color: var(--vscode-descriptionForeground);
|
|
239
|
+
}
|
|
240
|
+
dd {
|
|
241
|
+
margin: 0;
|
|
242
|
+
word-break: break-word;
|
|
243
|
+
}
|
|
244
|
+
code, pre {
|
|
245
|
+
font-family: var(--vscode-editor-font-family, monospace);
|
|
246
|
+
font-size: 12px;
|
|
247
|
+
}
|
|
248
|
+
pre {
|
|
249
|
+
margin: 0;
|
|
250
|
+
padding: 12px;
|
|
251
|
+
border-radius: 8px;
|
|
252
|
+
overflow: auto;
|
|
253
|
+
background: var(--vscode-textCodeBlock-background, rgba(127, 127, 127, 0.12));
|
|
254
|
+
border: 1px solid var(--vscode-editorWidget-border, transparent);
|
|
255
|
+
white-space: pre-wrap;
|
|
256
|
+
word-break: break-word;
|
|
257
|
+
}
|
|
258
|
+
ul {
|
|
259
|
+
margin: 0;
|
|
260
|
+
padding-left: 20px;
|
|
261
|
+
}
|
|
262
|
+
li + li {
|
|
263
|
+
margin-top: 6px;
|
|
264
|
+
}
|
|
265
|
+
.muted {
|
|
266
|
+
color: var(--vscode-descriptionForeground);
|
|
267
|
+
}
|
|
268
|
+
.pill {
|
|
269
|
+
display: inline-block;
|
|
270
|
+
margin-left: 6px;
|
|
271
|
+
padding: 1px 6px;
|
|
272
|
+
border-radius: 999px;
|
|
273
|
+
background: var(--vscode-badge-background);
|
|
274
|
+
color: var(--vscode-badge-foreground);
|
|
275
|
+
font-size: 11px;
|
|
276
|
+
}
|
|
277
|
+
</style>
|
|
278
|
+
</head>
|
|
279
|
+
<body>
|
|
280
|
+
<h1>${escapeHtml(sessionIdentityLabel(session))}</h1>
|
|
281
|
+
<dl class="grid">
|
|
282
|
+
<dt>Branch</dt>
|
|
283
|
+
<dd><code>${escapeHtml(session.branch)}</code></dd>
|
|
284
|
+
<dt>Worktree</dt>
|
|
285
|
+
<dd><code>${escapeHtml(session.worktreePath)}</code></dd>
|
|
286
|
+
<dt>Base branch</dt>
|
|
287
|
+
<dd><code>${escapeHtml(inspectData?.baseBranch || 'dev')}</code></dd>
|
|
288
|
+
<dt>Divergence</dt>
|
|
289
|
+
<dd>${escapeHtml(formatInspectBranchSummary(inspectData))}</dd>
|
|
290
|
+
<dt>Held locks</dt>
|
|
291
|
+
<dd>${Array.isArray(inspectData?.heldLocks) ? inspectData.heldLocks.length : 0}</dd>
|
|
292
|
+
<dt>Log file</dt>
|
|
293
|
+
<dd><code>${escapeHtml(inspectData?.logPath || 'Unavailable')}</code></dd>
|
|
294
|
+
</dl>
|
|
295
|
+
<h2>Held Locks</h2>
|
|
296
|
+
${heldLocksMarkup}
|
|
297
|
+
<h2>Agent Log Tail</h2>
|
|
298
|
+
<pre>${logContent}</pre>
|
|
299
|
+
</body>
|
|
300
|
+
</html>`;
|
|
301
|
+
}
|
|
302
|
+
|
|
171
303
|
class SessionDecorationProvider {
|
|
172
304
|
constructor(nowProvider = () => Date.now()) {
|
|
173
305
|
this.nowProvider = nowProvider;
|
|
@@ -265,8 +397,9 @@ class RepoItem extends vscode.TreeItem {
|
|
|
265
397
|
if (workingCount > 0) {
|
|
266
398
|
descriptionParts.push(`${workingCount} working`);
|
|
267
399
|
}
|
|
268
|
-
|
|
269
|
-
|
|
400
|
+
const changedCount = countChangedPaths(repoRoot, sessions, changes);
|
|
401
|
+
if (changedCount > 0) {
|
|
402
|
+
descriptionParts.push(`${changedCount} changed`);
|
|
270
403
|
}
|
|
271
404
|
this.description = descriptionParts.join(' · ');
|
|
272
405
|
this.tooltip = [
|
|
@@ -288,11 +421,49 @@ class SectionItem extends vscode.TreeItem {
|
|
|
288
421
|
}
|
|
289
422
|
}
|
|
290
423
|
|
|
424
|
+
class WorktreeItem extends vscode.TreeItem {
|
|
425
|
+
constructor(worktreePath, sessions, items = [], options = {}) {
|
|
426
|
+
const normalizedWorktreePath = typeof worktreePath === 'string' ? worktreePath.trim() : '';
|
|
427
|
+
const sessionList = Array.isArray(sessions) ? sessions : [];
|
|
428
|
+
const changedCount = Number.isInteger(options.changedCount)
|
|
429
|
+
? options.changedCount
|
|
430
|
+
: sessionList.reduce((total, session) => total + (session.changeCount || 0), 0);
|
|
431
|
+
const descriptionParts = [formatCountLabel(sessionList.length, 'agent')];
|
|
432
|
+
if (changedCount > 0) {
|
|
433
|
+
descriptionParts.push(`${changedCount} changed`);
|
|
434
|
+
}
|
|
435
|
+
super(
|
|
436
|
+
path.basename(normalizedWorktreePath || '') || 'worktree',
|
|
437
|
+
items.length > 0 ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None,
|
|
438
|
+
);
|
|
439
|
+
this.worktreePath = normalizedWorktreePath;
|
|
440
|
+
this.sessions = sessionList;
|
|
441
|
+
this.items = items;
|
|
442
|
+
this.description = options.description || descriptionParts.join(' · ');
|
|
443
|
+
this.tooltip = [
|
|
444
|
+
normalizedWorktreePath,
|
|
445
|
+
...sessionList.map((session) => session.branch).filter(Boolean),
|
|
446
|
+
].filter(Boolean).join('\n');
|
|
447
|
+
this.iconPath = new vscode.ThemeIcon('folder');
|
|
448
|
+
this.contextValue = 'gitguardex.worktree';
|
|
449
|
+
if (sessionList[0]?.worktreePath) {
|
|
450
|
+
this.command = {
|
|
451
|
+
command: 'gitguardex.activeAgents.openWorktree',
|
|
452
|
+
title: 'Open Agent Worktree',
|
|
453
|
+
arguments: [sessionList[0]],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
291
459
|
class SessionItem extends vscode.TreeItem {
|
|
292
|
-
constructor(session, items = []) {
|
|
460
|
+
constructor(session, items = [], options = {}) {
|
|
293
461
|
const lockCount = Number.isFinite(session.lockCount) ? session.lockCount : 0;
|
|
462
|
+
const label = typeof options.label === 'string' && options.label.trim()
|
|
463
|
+
? options.label.trim()
|
|
464
|
+
: session.label;
|
|
294
465
|
super(
|
|
295
|
-
|
|
466
|
+
label,
|
|
296
467
|
items.length > 0 ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None,
|
|
297
468
|
);
|
|
298
469
|
this.session = session;
|
|
@@ -303,6 +474,9 @@ class SessionItem extends vscode.TreeItem {
|
|
|
303
474
|
descriptionParts.push(session.activityCountLabel);
|
|
304
475
|
}
|
|
305
476
|
descriptionParts.push(session.elapsedLabel || formatElapsedFrom(session.startedAt));
|
|
477
|
+
if (lockCount > 0) {
|
|
478
|
+
descriptionParts.push(`${lockCount} $(lock)`);
|
|
479
|
+
}
|
|
306
480
|
this.description = descriptionParts.join(' · ');
|
|
307
481
|
const tooltipLines = [
|
|
308
482
|
session.branch,
|
|
@@ -315,6 +489,7 @@ class SessionItem extends vscode.TreeItem {
|
|
|
315
489
|
? `Changed ${session.activityCountLabel}: ${session.activitySummary}`
|
|
316
490
|
: session.activitySummary,
|
|
317
491
|
`Locks ${lockCount}`,
|
|
492
|
+
session.conflictCount > 0 ? `Conflicts ${session.conflictCount}` : '',
|
|
318
493
|
Number.isInteger(session.pid) && session.pid > 0
|
|
319
494
|
? session.pidAlive === false
|
|
320
495
|
? `PID ${session.pid} not alive`
|
|
@@ -378,10 +553,39 @@ function shellQuote(value) {
|
|
|
378
553
|
return `'${normalized.replace(/'/g, "'\"'\"'")}'`;
|
|
379
554
|
}
|
|
380
555
|
|
|
556
|
+
function readPackageJson(repoRoot) {
|
|
557
|
+
const packageJsonPath = path.join(repoRoot, 'package.json');
|
|
558
|
+
try {
|
|
559
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
560
|
+
} catch (_error) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function resolveStartAgentCommand(repoRoot, details) {
|
|
566
|
+
const taskArg = shellQuote(details.taskName);
|
|
567
|
+
const agentArg = shellQuote(details.agentName);
|
|
568
|
+
const localCodexAgentPath = path.join(repoRoot, 'scripts', 'codex-agent.sh');
|
|
569
|
+
if (fs.existsSync(localCodexAgentPath)) {
|
|
570
|
+
return `bash ./scripts/codex-agent.sh ${taskArg} ${agentArg}`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const agentCodexScript = readPackageJson(repoRoot)?.scripts?.['agent:codex'];
|
|
574
|
+
if (typeof agentCodexScript === 'string' && agentCodexScript.trim().length > 0) {
|
|
575
|
+
return `npm run agent:codex -- ${taskArg} ${agentArg}`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return `gx branch start ${taskArg} ${agentArg}`;
|
|
579
|
+
}
|
|
580
|
+
|
|
381
581
|
function sessionDisplayLabel(session) {
|
|
382
582
|
return session?.taskName || session?.label || session?.branch || path.basename(session?.worktreePath || '') || 'session';
|
|
383
583
|
}
|
|
384
584
|
|
|
585
|
+
function sessionTreeLabel(session) {
|
|
586
|
+
return session?.branch || sessionDisplayLabel(session);
|
|
587
|
+
}
|
|
588
|
+
|
|
385
589
|
function sessionWorktreePath(session) {
|
|
386
590
|
return typeof session?.worktreePath === 'string' ? session.worktreePath.trim() : '';
|
|
387
591
|
}
|
|
@@ -435,16 +639,34 @@ function syncSession(session) {
|
|
|
435
639
|
runSessionTerminalCommand(session, 'Sync', 'sync', 'gx sync');
|
|
436
640
|
}
|
|
437
641
|
|
|
642
|
+
function execFileAsync(command, args, options = {}) {
|
|
643
|
+
return new Promise((resolve, reject) => {
|
|
644
|
+
cp.execFile(command, args, options, (error, stdout = '', stderr = '') => {
|
|
645
|
+
if (error) {
|
|
646
|
+
error.stdout = stdout;
|
|
647
|
+
error.stderr = stderr;
|
|
648
|
+
reject(error);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
resolve({ stdout, stderr });
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
438
656
|
async function stopSession(session, refresh) {
|
|
439
657
|
const pid = Number(session?.pid);
|
|
440
658
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
441
659
|
showSessionMessage('Cannot stop session: missing pid.');
|
|
442
660
|
return;
|
|
443
661
|
}
|
|
662
|
+
if (!session?.branch) {
|
|
663
|
+
showSessionMessage('Cannot stop session: missing branch name.');
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
444
666
|
|
|
445
667
|
const confirmed = await vscode.window.showWarningMessage(
|
|
446
668
|
`Stop ${sessionDisplayLabel(session)}?`,
|
|
447
|
-
{ modal: true, detail: `
|
|
669
|
+
{ modal: true, detail: `Run gx agents stop --pid ${pid}.` },
|
|
448
670
|
'Stop',
|
|
449
671
|
);
|
|
450
672
|
if (confirmed !== 'Stop') {
|
|
@@ -452,42 +674,87 @@ async function stopSession(session, refresh) {
|
|
|
452
674
|
}
|
|
453
675
|
|
|
454
676
|
try {
|
|
455
|
-
process.
|
|
677
|
+
const commandCwd = session?.repoRoot || sessionWorktreePath(session) || process.cwd();
|
|
678
|
+
const args = ['agents', 'stop', '--pid', String(pid)];
|
|
679
|
+
if (session?.repoRoot) {
|
|
680
|
+
args.push('--target', session.repoRoot);
|
|
681
|
+
}
|
|
682
|
+
await execFileAsync('gx', args, {
|
|
683
|
+
cwd: commandCwd,
|
|
684
|
+
encoding: 'utf8',
|
|
685
|
+
maxBuffer: 1024 * 1024,
|
|
686
|
+
});
|
|
456
687
|
refresh();
|
|
457
688
|
} catch (error) {
|
|
458
689
|
showSessionMessage(
|
|
459
|
-
`Failed to stop session ${sessionDisplayLabel(session)}: ${
|
|
690
|
+
`Failed to stop session ${sessionDisplayLabel(session)}: ${formatGitCommandFailure(error)}`,
|
|
460
691
|
);
|
|
461
692
|
}
|
|
462
693
|
}
|
|
463
694
|
|
|
695
|
+
function sessionChangedPaths(session) {
|
|
696
|
+
const directPaths = Array.isArray(session?.changedPaths)
|
|
697
|
+
? session.changedPaths.map(normalizeRelativePath).filter(Boolean)
|
|
698
|
+
: [];
|
|
699
|
+
if (directPaths.length > 0) {
|
|
700
|
+
return [...new Set(directPaths)];
|
|
701
|
+
}
|
|
702
|
+
if (!session?.repoRoot || !session?.branch) {
|
|
703
|
+
return [];
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const liveSession = readActiveSessions(session.repoRoot)
|
|
707
|
+
.find((entry) => sessionSelectionKey(entry) === sessionSelectionKey(session));
|
|
708
|
+
return Array.isArray(liveSession?.changedPaths)
|
|
709
|
+
? [...new Set(liveSession.changedPaths.map(normalizeRelativePath).filter(Boolean))]
|
|
710
|
+
: [];
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async function pickSessionDiffPath(session) {
|
|
714
|
+
const changedPaths = sessionChangedPaths(session);
|
|
715
|
+
if (changedPaths.length === 0) {
|
|
716
|
+
return '';
|
|
717
|
+
}
|
|
718
|
+
if (changedPaths.length === 1 || !vscode.window.showQuickPick) {
|
|
719
|
+
return changedPaths[0];
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const picks = changedPaths.map((relativePath) => ({
|
|
723
|
+
label: path.basename(relativePath),
|
|
724
|
+
description: relativePath,
|
|
725
|
+
relativePath,
|
|
726
|
+
}));
|
|
727
|
+
const selection = await vscode.window.showQuickPick(picks, {
|
|
728
|
+
placeHolder: `Select a changed file for ${sessionDisplayLabel(session)}`,
|
|
729
|
+
ignoreFocusOut: true,
|
|
730
|
+
});
|
|
731
|
+
return selection?.relativePath || '';
|
|
732
|
+
}
|
|
733
|
+
|
|
464
734
|
async function openSessionDiff(session) {
|
|
465
735
|
const worktreePath = ensureSessionWorktree(session, 'open diff');
|
|
466
736
|
if (!worktreePath) {
|
|
467
737
|
return;
|
|
468
738
|
}
|
|
469
739
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
encoding: 'utf8',
|
|
474
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
475
|
-
});
|
|
476
|
-
} catch (error) {
|
|
477
|
-
const detail = [
|
|
478
|
-
error?.stdout,
|
|
479
|
-
error?.stderr,
|
|
480
|
-
error?.message,
|
|
481
|
-
].find((value) => typeof value === 'string' && value.trim().length > 0) || 'git diff failed.';
|
|
482
|
-
showSessionMessage(`Failed to open diff for ${sessionDisplayLabel(session)}: ${detail.trim()}`);
|
|
740
|
+
const relativePath = await pickSessionDiffPath(session);
|
|
741
|
+
if (!relativePath) {
|
|
742
|
+
showSessionMessage(`No changed files to diff for ${sessionDisplayLabel(session)}.`);
|
|
483
743
|
return;
|
|
484
744
|
}
|
|
485
745
|
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
746
|
+
const repoRoot = session?.repoRoot || worktreePath;
|
|
747
|
+
const absolutePath = path.resolve(repoRoot, relativePath);
|
|
748
|
+
const resourceUri = vscode.Uri.file(absolutePath);
|
|
749
|
+
try {
|
|
750
|
+
await vscode.commands.executeCommand('git.openChange', resourceUri);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
if (fs.existsSync(absolutePath)) {
|
|
753
|
+
await vscode.commands.executeCommand('vscode.open', resourceUri);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
showSessionMessage(`Failed to open diff for ${sessionDisplayLabel(session)}: ${formatGitCommandFailure(error)}`);
|
|
757
|
+
}
|
|
491
758
|
}
|
|
492
759
|
|
|
493
760
|
function repoRootFromSessionFile(filePath) {
|
|
@@ -674,7 +941,7 @@ async function maybeAutoUpdateActiveAgentsExtension(context) {
|
|
|
674
941
|
}
|
|
675
942
|
|
|
676
943
|
const selection = await vscode.window.showInformationMessage?.(
|
|
677
|
-
`GitGuardex Active Agents updated to ${candidate.version}. Reload
|
|
944
|
+
`GitGuardex Active Agents updated to ${candidate.version}. Reload this window now, then reload any other already-open VS Code windows to use the newest companion.`,
|
|
678
945
|
RELOAD_WINDOW_ACTION,
|
|
679
946
|
UPDATE_LATER_ACTION,
|
|
680
947
|
);
|
|
@@ -684,9 +951,12 @@ async function maybeAutoUpdateActiveAgentsExtension(context) {
|
|
|
684
951
|
}
|
|
685
952
|
|
|
686
953
|
function decorateSession(session, lockRegistry) {
|
|
954
|
+
const touchedChanges = buildSessionTouchedChanges(session, lockRegistry);
|
|
687
955
|
return {
|
|
688
956
|
...session,
|
|
689
957
|
lockCount: lockRegistry.countsByBranch.get(session.branch) || 0,
|
|
958
|
+
touchedChanges,
|
|
959
|
+
conflictCount: touchedChanges.filter((change) => change.hasForeignLock).length,
|
|
690
960
|
};
|
|
691
961
|
}
|
|
692
962
|
|
|
@@ -700,6 +970,28 @@ function decorateChange(change, lockRegistry, owningBranch) {
|
|
|
700
970
|
};
|
|
701
971
|
}
|
|
702
972
|
|
|
973
|
+
function buildSessionTouchedChanges(session, lockRegistry) {
|
|
974
|
+
const changedPaths = Array.isArray(session.worktreeChangedPaths)
|
|
975
|
+
? session.worktreeChangedPaths
|
|
976
|
+
: [];
|
|
977
|
+
return [...new Set(changedPaths.map(normalizeRelativePath).filter(Boolean))]
|
|
978
|
+
.sort((left, right) => left.localeCompare(right))
|
|
979
|
+
.map((relativePath) => {
|
|
980
|
+
const lockEntry = lockRegistry.entriesByPath.get(relativePath);
|
|
981
|
+
const lockOwnerBranch = lockEntry?.branch || '';
|
|
982
|
+
return {
|
|
983
|
+
relativePath,
|
|
984
|
+
absolutePath: path.join(session.worktreePath, relativePath),
|
|
985
|
+
originalPath: '',
|
|
986
|
+
statusCode: 'M',
|
|
987
|
+
statusLabel: 'M',
|
|
988
|
+
statusText: 'Touched',
|
|
989
|
+
lockOwnerBranch,
|
|
990
|
+
hasForeignLock: Boolean(lockOwnerBranch) && lockOwnerBranch !== session.branch,
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
|
|
703
995
|
function isPathWithin(parentPath, targetPath) {
|
|
704
996
|
const relativePath = path.relative(parentPath, targetPath);
|
|
705
997
|
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
@@ -876,6 +1168,61 @@ function countWorkingSessions(sessions) {
|
|
|
876
1168
|
return sessions.filter((session) => session.activityKind === 'working').length;
|
|
877
1169
|
}
|
|
878
1170
|
|
|
1171
|
+
function countChangedPaths(repoRoot, sessions, changes) {
|
|
1172
|
+
const changedKeys = new Set();
|
|
1173
|
+
|
|
1174
|
+
for (const change of changes || []) {
|
|
1175
|
+
if (change?.relativePath) {
|
|
1176
|
+
changedKeys.add(normalizeRelativePath(change.relativePath));
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
for (const session of sessions || []) {
|
|
1181
|
+
for (const change of session.touchedChanges || []) {
|
|
1182
|
+
const absolutePath = change?.absolutePath
|
|
1183
|
+
|| path.join(session.worktreePath || '', change?.relativePath || '');
|
|
1184
|
+
const normalizedRelativePath = absolutePath && isPathWithin(repoRoot, absolutePath)
|
|
1185
|
+
? normalizeRelativePath(path.relative(repoRoot, absolutePath))
|
|
1186
|
+
: `${session.branch}:${normalizeRelativePath(change?.relativePath)}`;
|
|
1187
|
+
if (normalizedRelativePath) {
|
|
1188
|
+
changedKeys.add(normalizedRelativePath);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return changedKeys.size;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
function groupSessionsByWorktree(sessions) {
|
|
1197
|
+
const sessionsByWorktree = new Map();
|
|
1198
|
+
|
|
1199
|
+
for (const session of sessions || []) {
|
|
1200
|
+
const worktreePath = sessionWorktreePath(session);
|
|
1201
|
+
const key = worktreePath || session?.branch || `session-${sessionsByWorktree.size + 1}`;
|
|
1202
|
+
if (!sessionsByWorktree.has(key)) {
|
|
1203
|
+
sessionsByWorktree.set(key, {
|
|
1204
|
+
worktreePath,
|
|
1205
|
+
sessions: [],
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
sessionsByWorktree.get(key).sessions.push(session);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return [...sessionsByWorktree.values()]
|
|
1212
|
+
.map((entry) => ({
|
|
1213
|
+
...entry,
|
|
1214
|
+
sessions: entry.sessions.sort((left, right) => (
|
|
1215
|
+
sessionTreeLabel(left).localeCompare(sessionTreeLabel(right))
|
|
1216
|
+
)),
|
|
1217
|
+
}))
|
|
1218
|
+
.sort((left, right) => {
|
|
1219
|
+
const leftLabel = path.basename(left.worktreePath || '') || '';
|
|
1220
|
+
const rightLabel = path.basename(right.worktreePath || '') || '';
|
|
1221
|
+
return leftLabel.localeCompare(rightLabel)
|
|
1222
|
+
|| (left.worktreePath || '').localeCompare(right.worktreePath || '');
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
|
|
879
1226
|
function buildGroupedChangeTreeNodes(sessions, changes) {
|
|
880
1227
|
const changesBySession = new Map();
|
|
881
1228
|
const sessionByChangedPath = new Map();
|
|
@@ -908,15 +1255,22 @@ function buildGroupedChangeTreeNodes(sessions, changes) {
|
|
|
908
1255
|
changesBySession.get(session.branch).push(localizedChange);
|
|
909
1256
|
}
|
|
910
1257
|
|
|
911
|
-
const items =
|
|
912
|
-
.
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1258
|
+
const items = groupSessionsByWorktree(
|
|
1259
|
+
sessions.filter((session) => (changesBySession.get(session.branch) || []).length > 0),
|
|
1260
|
+
).map(({ worktreePath, sessions: worktreeSessions }) => {
|
|
1261
|
+
const sessionItems = worktreeSessions.map((session) => (
|
|
1262
|
+
new SessionItem(
|
|
1263
|
+
session,
|
|
1264
|
+
buildChangeTreeNodes(changesBySession.get(session.branch) || []),
|
|
1265
|
+
{ label: sessionTreeLabel(session) },
|
|
1266
|
+
)
|
|
1267
|
+
));
|
|
1268
|
+
const changedCount = worktreeSessions.reduce(
|
|
1269
|
+
(total, session) => total + ((changesBySession.get(session.branch) || []).length),
|
|
1270
|
+
0,
|
|
1271
|
+
);
|
|
1272
|
+
return new WorktreeItem(worktreePath, worktreeSessions, sessionItems, { changedCount });
|
|
1273
|
+
});
|
|
920
1274
|
|
|
921
1275
|
if (repoRootChanges.length > 0) {
|
|
922
1276
|
items.push(new SectionItem('Repo root', buildChangeTreeNodes(repoRootChanges), {
|
|
@@ -956,14 +1310,14 @@ async function pickRepoRoot() {
|
|
|
956
1310
|
repoRoot: folder.uri.fsPath,
|
|
957
1311
|
}));
|
|
958
1312
|
const selection = await vscode.window.showQuickPick?.(picks, {
|
|
959
|
-
placeHolder: 'Select the Guardex repo where
|
|
1313
|
+
placeHolder: 'Select the Guardex repo where the Start agent launcher should run.',
|
|
960
1314
|
});
|
|
961
1315
|
return selection?.repoRoot || null;
|
|
962
1316
|
}
|
|
963
1317
|
|
|
964
1318
|
async function promptStartAgentDetails() {
|
|
965
1319
|
const taskName = await vscode.window.showInputBox?.({
|
|
966
|
-
prompt: 'Task for
|
|
1320
|
+
prompt: 'Task for the Guardex agent launcher',
|
|
967
1321
|
placeHolder: 'vscode active agents welcome view',
|
|
968
1322
|
ignoreFocusOut: true,
|
|
969
1323
|
validateInput: (value) => value.trim() ? undefined : 'Task is required.',
|
|
@@ -973,7 +1327,7 @@ async function promptStartAgentDetails() {
|
|
|
973
1327
|
}
|
|
974
1328
|
|
|
975
1329
|
const agentName = await vscode.window.showInputBox?.({
|
|
976
|
-
prompt: 'Agent name for
|
|
1330
|
+
prompt: 'Agent name for the Guardex agent launcher',
|
|
977
1331
|
placeHolder: 'codex',
|
|
978
1332
|
value: 'codex',
|
|
979
1333
|
ignoreFocusOut: true,
|
|
@@ -1005,10 +1359,7 @@ async function startAgentFromPrompt(refresh) {
|
|
|
1005
1359
|
cwd: repoRoot,
|
|
1006
1360
|
});
|
|
1007
1361
|
terminal?.show(true);
|
|
1008
|
-
terminal?.sendText(
|
|
1009
|
-
`gx branch start ${shellQuote(details.taskName)} ${shellQuote(details.agentName)}`,
|
|
1010
|
-
true,
|
|
1011
|
-
);
|
|
1362
|
+
terminal?.sendText(resolveStartAgentCommand(repoRoot, details), true);
|
|
1012
1363
|
refresh();
|
|
1013
1364
|
}
|
|
1014
1365
|
|
|
@@ -1047,11 +1398,20 @@ function commitWorktree(worktreePath, message) {
|
|
|
1047
1398
|
function buildActiveAgentGroupNodes(sessions) {
|
|
1048
1399
|
const groups = [];
|
|
1049
1400
|
for (const group of SESSION_ACTIVITY_GROUPS) {
|
|
1050
|
-
const groupSessions = sessions
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1401
|
+
const groupSessions = sessions.filter((session) => session.activityKind === group.kind);
|
|
1402
|
+
const worktreeItems = groupSessionsByWorktree(groupSessions).map(({ worktreePath, sessions: worktreeSessions }) => (
|
|
1403
|
+
new WorktreeItem(
|
|
1404
|
+
worktreePath,
|
|
1405
|
+
worktreeSessions,
|
|
1406
|
+
worktreeSessions.map((session) => new SessionItem(
|
|
1407
|
+
session,
|
|
1408
|
+
buildChangeTreeNodes(session.touchedChanges || []),
|
|
1409
|
+
{ label: sessionTreeLabel(session) },
|
|
1410
|
+
)),
|
|
1411
|
+
)
|
|
1412
|
+
));
|
|
1413
|
+
if (worktreeItems.length > 0) {
|
|
1414
|
+
groups.push(new SectionItem(group.label, worktreeItems));
|
|
1055
1415
|
}
|
|
1056
1416
|
}
|
|
1057
1417
|
|
|
@@ -1072,6 +1432,7 @@ class ActiveAgentsProvider {
|
|
|
1072
1432
|
sessionCount: 0,
|
|
1073
1433
|
workingCount: 0,
|
|
1074
1434
|
deadCount: 0,
|
|
1435
|
+
conflictCount: 0,
|
|
1075
1436
|
};
|
|
1076
1437
|
}
|
|
1077
1438
|
|
|
@@ -1118,7 +1479,7 @@ class ActiveAgentsProvider {
|
|
|
1118
1479
|
this.setSelectedSession(nextSession || null);
|
|
1119
1480
|
}
|
|
1120
1481
|
|
|
1121
|
-
updateViewState(sessionCount, workingCount, deadCount) {
|
|
1482
|
+
updateViewState(sessionCount, workingCount, deadCount, conflictCount = 0) {
|
|
1122
1483
|
if (!this.treeView) {
|
|
1123
1484
|
return;
|
|
1124
1485
|
}
|
|
@@ -1128,7 +1489,10 @@ class ActiveAgentsProvider {
|
|
|
1128
1489
|
sessionCount,
|
|
1129
1490
|
workingCount,
|
|
1130
1491
|
deadCount,
|
|
1492
|
+
conflictCount,
|
|
1131
1493
|
};
|
|
1494
|
+
void vscode.commands.executeCommand('setContext', 'guardex.hasAgents', sessionCount > 0);
|
|
1495
|
+
void vscode.commands.executeCommand('setContext', 'guardex.hasConflicts', conflictCount > 0);
|
|
1132
1496
|
const badgeTooltipParts = [];
|
|
1133
1497
|
if (activeCount > 0) {
|
|
1134
1498
|
badgeTooltipParts.push(`${activeCount} active agent${activeCount === 1 ? '' : 's'}`);
|
|
@@ -1139,6 +1503,9 @@ class ActiveAgentsProvider {
|
|
|
1139
1503
|
if (workingCount > 0) {
|
|
1140
1504
|
badgeTooltipParts.push(`${workingCount} working now`);
|
|
1141
1505
|
}
|
|
1506
|
+
if (conflictCount > 0) {
|
|
1507
|
+
badgeTooltipParts.push(`${conflictCount} conflict${conflictCount === 1 ? '' : 's'}`);
|
|
1508
|
+
}
|
|
1142
1509
|
|
|
1143
1510
|
this.treeView.badge = sessionCount > 0
|
|
1144
1511
|
? {
|
|
@@ -1146,9 +1513,7 @@ class ActiveAgentsProvider {
|
|
|
1146
1513
|
tooltip: badgeTooltipParts.join(' · '),
|
|
1147
1514
|
}
|
|
1148
1515
|
: undefined;
|
|
1149
|
-
this.treeView.message =
|
|
1150
|
-
? undefined
|
|
1151
|
-
: 'Start a sandbox session to populate this view.';
|
|
1516
|
+
this.treeView.message = undefined;
|
|
1152
1517
|
}
|
|
1153
1518
|
|
|
1154
1519
|
async syncRepoEntries() {
|
|
@@ -1162,8 +1527,12 @@ class ActiveAgentsProvider {
|
|
|
1162
1527
|
(total, entry) => total + countSessionsByActivityKind(entry.sessions, 'dead'),
|
|
1163
1528
|
0,
|
|
1164
1529
|
);
|
|
1530
|
+
const conflictCount = repoEntries.reduce(
|
|
1531
|
+
(total, entry) => total + countEntryConflicts(entry),
|
|
1532
|
+
0,
|
|
1533
|
+
);
|
|
1165
1534
|
|
|
1166
|
-
this.updateViewState(sessionCount, workingCount, deadCount);
|
|
1535
|
+
this.updateViewState(sessionCount, workingCount, deadCount, conflictCount);
|
|
1167
1536
|
this.decorationProvider?.updateSessions(repoEntries.flatMap((entry) => entry.sessions));
|
|
1168
1537
|
this.decorationProvider?.updateLockEntries(repoEntries);
|
|
1169
1538
|
return repoEntries;
|
|
@@ -1189,20 +1558,6 @@ class ActiveAgentsProvider {
|
|
|
1189
1558
|
this.readLockRegistryForRepo(repoRootFromLockFile(filePath));
|
|
1190
1559
|
}
|
|
1191
1560
|
|
|
1192
|
-
readLockRegistryForRepo(repoRoot) {
|
|
1193
|
-
const lockRegistry = readLockRegistry(repoRoot);
|
|
1194
|
-
this.lockRegistryByRepoRoot.set(repoRoot, lockRegistry);
|
|
1195
|
-
return lockRegistry;
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
getLockRegistryForRepo(repoRoot) {
|
|
1199
|
-
return this.lockRegistryByRepoRoot.get(repoRoot) || this.readLockRegistryForRepo(repoRoot);
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
refreshLockRegistryForFile(filePath) {
|
|
1203
|
-
this.readLockRegistryForRepo(repoRootFromLockFile(filePath));
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
1561
|
async getChildren(element) {
|
|
1207
1562
|
if (element instanceof RepoItem) {
|
|
1208
1563
|
const sectionItems = [
|
|
@@ -1218,7 +1573,7 @@ class ActiveAgentsProvider {
|
|
|
1218
1573
|
return sectionItems;
|
|
1219
1574
|
}
|
|
1220
1575
|
|
|
1221
|
-
if (element instanceof SectionItem || element instanceof FolderItem || element instanceof SessionItem) {
|
|
1576
|
+
if (element instanceof SectionItem || element instanceof FolderItem || element instanceof WorktreeItem || element instanceof SessionItem) {
|
|
1222
1577
|
return element.items;
|
|
1223
1578
|
}
|
|
1224
1579
|
|
|
@@ -1250,9 +1605,95 @@ class ActiveAgentsProvider {
|
|
|
1250
1605
|
}
|
|
1251
1606
|
}
|
|
1252
1607
|
|
|
1608
|
+
function countEntryConflicts(entry) {
|
|
1609
|
+
const sessionConflicts = entry.sessions.reduce(
|
|
1610
|
+
(total, session) => total + (session.conflictCount || 0),
|
|
1611
|
+
0,
|
|
1612
|
+
);
|
|
1613
|
+
const changeConflicts = entry.changes.filter((change) => change.hasForeignLock).length;
|
|
1614
|
+
return sessionConflicts + changeConflicts;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
class SessionInspectPanelManager {
|
|
1618
|
+
constructor() {
|
|
1619
|
+
this.panel = null;
|
|
1620
|
+
this.session = null;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
open(session) {
|
|
1624
|
+
const targetSession = session?.branch ? { ...session } : null;
|
|
1625
|
+
if (!targetSession?.repoRoot || !targetSession?.branch) {
|
|
1626
|
+
showSessionMessage('Pick an Active Agents session first.');
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
if (!vscode.window.createWebviewPanel) {
|
|
1630
|
+
showSessionMessage('Inspect panel is unavailable in this VS Code build.');
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
this.session = targetSession;
|
|
1635
|
+
if (!this.panel) {
|
|
1636
|
+
this.panel = vscode.window.createWebviewPanel(
|
|
1637
|
+
INSPECT_PANEL_VIEW_TYPE,
|
|
1638
|
+
inspectPanelTitle(targetSession),
|
|
1639
|
+
vscode.ViewColumn?.Beside,
|
|
1640
|
+
{
|
|
1641
|
+
enableFindWidget: true,
|
|
1642
|
+
enableScripts: false,
|
|
1643
|
+
retainContextWhenHidden: true,
|
|
1644
|
+
},
|
|
1645
|
+
);
|
|
1646
|
+
this.panel.onDidDispose(() => {
|
|
1647
|
+
this.panel = null;
|
|
1648
|
+
this.session = null;
|
|
1649
|
+
});
|
|
1650
|
+
} else {
|
|
1651
|
+
this.panel.reveal?.(vscode.ViewColumn?.Beside);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
this.render();
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
resolveSession() {
|
|
1658
|
+
if (!this.session?.repoRoot || !this.session?.branch) {
|
|
1659
|
+
return this.session ? { ...this.session } : null;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
return readActiveSessions(this.session.repoRoot, { includeStale: true })
|
|
1663
|
+
.find((entry) => sessionSelectionKey(entry) === sessionSelectionKey(this.session))
|
|
1664
|
+
|| { ...this.session };
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
render() {
|
|
1668
|
+
if (!this.panel || !this.session) {
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
const session = this.resolveSession();
|
|
1673
|
+
if (!session) {
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
this.session = { ...session };
|
|
1678
|
+
this.panel.title = inspectPanelTitle(session);
|
|
1679
|
+
this.panel.webview.html = renderInspectPanelHtml(session, readSessionInspectData(session));
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
refresh() {
|
|
1683
|
+
this.render();
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
dispose() {
|
|
1687
|
+
this.panel?.dispose();
|
|
1688
|
+
this.panel = null;
|
|
1689
|
+
this.session = null;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1253
1693
|
class ActiveAgentsRefreshController {
|
|
1254
|
-
constructor(provider) {
|
|
1694
|
+
constructor(provider, inspectPanelManager = null) {
|
|
1255
1695
|
this.provider = provider;
|
|
1696
|
+
this.inspectPanelManager = inspectPanelManager;
|
|
1256
1697
|
this.refreshTimer = null;
|
|
1257
1698
|
this.sessionWatchers = new Map();
|
|
1258
1699
|
}
|
|
@@ -1270,6 +1711,7 @@ class ActiveAgentsRefreshController {
|
|
|
1270
1711
|
async refreshNow() {
|
|
1271
1712
|
await this.syncSessionWatchers();
|
|
1272
1713
|
await this.provider.refresh();
|
|
1714
|
+
this.inspectPanelManager?.refresh();
|
|
1273
1715
|
}
|
|
1274
1716
|
|
|
1275
1717
|
async syncSessionWatchers() {
|
|
@@ -1320,7 +1762,8 @@ class ActiveAgentsRefreshController {
|
|
|
1320
1762
|
function activate(context) {
|
|
1321
1763
|
const decorationProvider = new SessionDecorationProvider();
|
|
1322
1764
|
const provider = new ActiveAgentsProvider(decorationProvider);
|
|
1323
|
-
const
|
|
1765
|
+
const inspectPanelManager = new SessionInspectPanelManager();
|
|
1766
|
+
const refreshController = new ActiveAgentsRefreshController(provider, inspectPanelManager);
|
|
1324
1767
|
const treeView = vscode.window.createTreeView('gitguardex.activeAgents', {
|
|
1325
1768
|
treeDataProvider: provider,
|
|
1326
1769
|
showCollapseAll: true,
|
|
@@ -1338,6 +1781,7 @@ function activate(context) {
|
|
|
1338
1781
|
const activeSessionsWatcher = vscode.workspace.createFileSystemWatcher(ACTIVE_SESSION_FILES_GLOB);
|
|
1339
1782
|
const lockWatcher = vscode.workspace.createFileSystemWatcher(AGENT_FILE_LOCKS_GLOB);
|
|
1340
1783
|
const worktreeLockWatcher = vscode.workspace.createFileSystemWatcher(WORKTREE_AGENT_LOCKS_GLOB);
|
|
1784
|
+
const logWatcher = vscode.workspace.createFileSystemWatcher(AGENT_LOG_FILES_GLOB);
|
|
1341
1785
|
const updateCommitInput = (session) => {
|
|
1342
1786
|
sourceControl.inputBox.enabled = true;
|
|
1343
1787
|
sourceControl.inputBox.visible = true;
|
|
@@ -1397,7 +1841,7 @@ function activate(context) {
|
|
|
1397
1841
|
command: 'gitguardex.activeAgents.commitSelectedSession',
|
|
1398
1842
|
title: 'Commit Selected Session',
|
|
1399
1843
|
};
|
|
1400
|
-
const interval = setInterval(refresh,
|
|
1844
|
+
const interval = setInterval(refresh, REFRESH_POLL_INTERVAL_MS);
|
|
1401
1845
|
const refreshLockRegistry = (uri) => {
|
|
1402
1846
|
if (uri?.fsPath) {
|
|
1403
1847
|
provider.refreshLockRegistryForFile(uri.fsPath);
|
|
@@ -1419,6 +1863,7 @@ function activate(context) {
|
|
|
1419
1863
|
treeView,
|
|
1420
1864
|
sourceControl,
|
|
1421
1865
|
activeAgentsStatusItem,
|
|
1866
|
+
inspectPanelManager,
|
|
1422
1867
|
refreshController,
|
|
1423
1868
|
vscode.window.registerFileDecorationProvider(decorationProvider),
|
|
1424
1869
|
vscode.commands.registerCommand('gitguardex.activeAgents.startAgent', () => startAgentFromPrompt(refresh)),
|
|
@@ -1450,6 +1895,9 @@ function activate(context) {
|
|
|
1450
1895
|
|
|
1451
1896
|
await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(change.absolutePath));
|
|
1452
1897
|
}),
|
|
1898
|
+
vscode.commands.registerCommand('gitguardex.activeAgents.inspect', (session) => {
|
|
1899
|
+
inspectPanelManager.open(session || provider.getSelectedSession());
|
|
1900
|
+
}),
|
|
1453
1901
|
vscode.commands.registerCommand('gitguardex.activeAgents.finishSession', finishSession),
|
|
1454
1902
|
vscode.commands.registerCommand('gitguardex.activeAgents.syncSession', syncSession),
|
|
1455
1903
|
vscode.commands.registerCommand('gitguardex.activeAgents.stopSession', (session) => stopSession(session, refresh)),
|
|
@@ -1458,6 +1906,7 @@ function activate(context) {
|
|
|
1458
1906
|
activeSessionsWatcher,
|
|
1459
1907
|
lockWatcher,
|
|
1460
1908
|
worktreeLockWatcher,
|
|
1909
|
+
logWatcher,
|
|
1461
1910
|
{ dispose: () => clearInterval(interval) },
|
|
1462
1911
|
);
|
|
1463
1912
|
|
|
@@ -1465,6 +1914,7 @@ function activate(context) {
|
|
|
1465
1914
|
...bindRefreshWatcher(activeSessionsWatcher, scheduleRefresh),
|
|
1466
1915
|
...bindRefreshWatcher(lockWatcher, refreshLockRegistry),
|
|
1467
1916
|
...bindRefreshWatcher(worktreeLockWatcher, scheduleRefresh),
|
|
1917
|
+
...bindRefreshWatcher(logWatcher, scheduleRefresh),
|
|
1468
1918
|
);
|
|
1469
1919
|
void refreshController.refreshNow();
|
|
1470
1920
|
void maybeAutoUpdateActiveAgentsExtension(context);
|