@imdeadpool/guardex 7.0.15 → 7.0.18

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.
@@ -0,0 +1,21 @@
1
+ # GitGuardex Active Agents
2
+
3
+ Local VS Code companion for Guardex-managed repos.
4
+
5
+ What it does:
6
+
7
+ - Adds an `Active Agents` view to the Source Control container.
8
+ - Renders one repo node per live Guardex workspace with grouped `ACTIVE AGENTS` and `CHANGES` sections.
9
+ - Shows one row per live Guardex sandbox session inside the repo's `ACTIVE AGENTS` section.
10
+ - Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
11
+ - Derives `thinking` versus `working` from the live sandbox worktree and shows changed-file counts for active edits.
12
+ - Uses VS Code's native animated `loading~spin` icon for the running-state affordance.
13
+ - Reads repo-local presence files from `.omx/state/active-sessions/`.
14
+
15
+ Install from a Guardex-wired repo:
16
+
17
+ ```sh
18
+ node scripts/install-vscode-active-agents-extension.js
19
+ ```
20
+
21
+ Then reload the VS Code window.
@@ -0,0 +1,317 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+ const vscode = require('vscode');
4
+ const { formatElapsedFrom, readActiveSessions, readRepoChanges } = require('./session-schema.js');
5
+
6
+ class InfoItem extends vscode.TreeItem {
7
+ constructor(label, description = '') {
8
+ super(label, vscode.TreeItemCollapsibleState.None);
9
+ this.description = description;
10
+ this.iconPath = new vscode.ThemeIcon('info');
11
+ }
12
+ }
13
+
14
+ class RepoItem extends vscode.TreeItem {
15
+ constructor(repoRoot, sessions, changes) {
16
+ super(path.basename(repoRoot), vscode.TreeItemCollapsibleState.Expanded);
17
+ this.repoRoot = repoRoot;
18
+ this.sessions = sessions;
19
+ this.changes = changes;
20
+ const descriptionParts = [`${sessions.length} active`];
21
+ if (changes.length > 0) {
22
+ descriptionParts.push(`${changes.length} changed`);
23
+ }
24
+ this.description = descriptionParts.join(' · ');
25
+ this.tooltip = repoRoot;
26
+ this.iconPath = new vscode.ThemeIcon('repo');
27
+ this.contextValue = 'gitguardex.repo';
28
+ }
29
+ }
30
+
31
+ class SectionItem extends vscode.TreeItem {
32
+ constructor(label, items) {
33
+ super(label, vscode.TreeItemCollapsibleState.Expanded);
34
+ this.items = items;
35
+ this.description = items.length > 0 ? String(items.length) : '';
36
+ this.contextValue = 'gitguardex.section';
37
+ }
38
+ }
39
+
40
+ class SessionItem extends vscode.TreeItem {
41
+ constructor(session) {
42
+ super(session.label, vscode.TreeItemCollapsibleState.None);
43
+ this.session = session;
44
+ const descriptionParts = [session.activityLabel || 'thinking'];
45
+ if (session.activityCountLabel) {
46
+ descriptionParts.push(session.activityCountLabel);
47
+ }
48
+ descriptionParts.push(session.elapsedLabel || formatElapsedFrom(session.startedAt));
49
+ this.description = descriptionParts.join(' · ');
50
+ const tooltipLines = [
51
+ session.branch,
52
+ `${session.agentName} · ${session.taskName}`,
53
+ `Status ${this.description}`,
54
+ session.changeCount > 0
55
+ ? `Changed ${session.activityCountLabel}: ${session.activitySummary}`
56
+ : session.activitySummary,
57
+ `Started ${session.startedAt}`,
58
+ session.worktreePath,
59
+ ];
60
+ this.tooltip = tooltipLines.filter(Boolean).join('\n');
61
+ this.iconPath = new vscode.ThemeIcon('loading~spin');
62
+ this.contextValue = 'gitguardex.session';
63
+ this.command = {
64
+ command: 'gitguardex.activeAgents.openWorktree',
65
+ title: 'Open Agent Worktree',
66
+ arguments: [session],
67
+ };
68
+ }
69
+ }
70
+
71
+ class FolderItem extends vscode.TreeItem {
72
+ constructor(label, relativePath, items) {
73
+ super(label, vscode.TreeItemCollapsibleState.Expanded);
74
+ this.relativePath = relativePath;
75
+ this.items = items;
76
+ this.tooltip = relativePath;
77
+ this.iconPath = new vscode.ThemeIcon('folder');
78
+ this.contextValue = 'gitguardex.folder';
79
+ }
80
+ }
81
+
82
+ class ChangeItem extends vscode.TreeItem {
83
+ constructor(change) {
84
+ super(path.basename(change.relativePath), vscode.TreeItemCollapsibleState.None);
85
+ this.change = change;
86
+ this.description = change.statusLabel;
87
+ this.tooltip = [
88
+ change.relativePath,
89
+ `Status ${change.statusText}`,
90
+ change.originalPath ? `Renamed from ${change.originalPath}` : '',
91
+ change.absolutePath,
92
+ ].filter(Boolean).join('\n');
93
+ this.resourceUri = vscode.Uri.file(change.absolutePath);
94
+ this.contextValue = 'gitguardex.change';
95
+ this.command = {
96
+ command: 'gitguardex.activeAgents.openChange',
97
+ title: 'Open Changed File',
98
+ arguments: [change],
99
+ };
100
+ }
101
+ }
102
+
103
+ function repoRootFromSessionFile(filePath) {
104
+ return path.resolve(path.dirname(filePath), '..', '..', '..');
105
+ }
106
+
107
+ function buildChangeTreeNodes(changes) {
108
+ const root = [];
109
+
110
+ function sortNodes(nodes) {
111
+ nodes.sort((left, right) => {
112
+ const leftIsFolder = left.kind === 'folder';
113
+ const rightIsFolder = right.kind === 'folder';
114
+ if (leftIsFolder !== rightIsFolder) {
115
+ return leftIsFolder ? -1 : 1;
116
+ }
117
+ return left.label.localeCompare(right.label);
118
+ });
119
+
120
+ for (const node of nodes) {
121
+ if (node.kind === 'folder') {
122
+ sortNodes(node.children);
123
+ }
124
+ }
125
+ }
126
+
127
+ for (const change of changes) {
128
+ const segments = change.relativePath.split(/[\\/]+/).filter(Boolean);
129
+ if (segments.length <= 1) {
130
+ root.push({ kind: 'change', label: change.relativePath, change });
131
+ continue;
132
+ }
133
+
134
+ let nodes = root;
135
+ let folderPath = '';
136
+ for (const segment of segments.slice(0, -1)) {
137
+ folderPath = folderPath ? path.posix.join(folderPath, segment) : segment;
138
+ let folderNode = nodes.find((node) => node.kind === 'folder' && node.relativePath === folderPath);
139
+ if (!folderNode) {
140
+ folderNode = {
141
+ kind: 'folder',
142
+ label: segment,
143
+ relativePath: folderPath,
144
+ children: [],
145
+ };
146
+ nodes.push(folderNode);
147
+ }
148
+ nodes = folderNode.children;
149
+ }
150
+
151
+ nodes.push({ kind: 'change', label: change.relativePath, change });
152
+ }
153
+
154
+ sortNodes(root);
155
+
156
+ function materialize(nodes) {
157
+ return nodes.map((node) => {
158
+ if (node.kind === 'folder') {
159
+ return new FolderItem(node.label, node.relativePath, materialize(node.children));
160
+ }
161
+ return new ChangeItem(node.change);
162
+ });
163
+ }
164
+
165
+ return materialize(root);
166
+ }
167
+
168
+ class ActiveAgentsProvider {
169
+ constructor() {
170
+ this.onDidChangeTreeDataEmitter = new vscode.EventEmitter();
171
+ this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event;
172
+ this.treeView = null;
173
+ }
174
+
175
+ getTreeItem(element) {
176
+ return element;
177
+ }
178
+
179
+ attachTreeView(treeView) {
180
+ this.treeView = treeView;
181
+ this.updateViewState(0);
182
+ }
183
+
184
+ updateViewState(sessionCount) {
185
+ if (!this.treeView) {
186
+ return;
187
+ }
188
+
189
+ this.treeView.badge = sessionCount > 0
190
+ ? {
191
+ value: sessionCount,
192
+ tooltip: `${sessionCount} active agent${sessionCount === 1 ? '' : 's'}`,
193
+ }
194
+ : undefined;
195
+ this.treeView.message = sessionCount > 0
196
+ ? undefined
197
+ : 'Start a sandbox session to populate this view.';
198
+ }
199
+
200
+ refresh() {
201
+ this.onDidChangeTreeDataEmitter.fire();
202
+ }
203
+
204
+ async getChildren(element) {
205
+ if (element instanceof RepoItem) {
206
+ const sectionItems = [
207
+ new SectionItem('ACTIVE AGENTS', element.sessions.map((session) => new SessionItem(session))),
208
+ ];
209
+ if (element.changes.length > 0) {
210
+ sectionItems.push(new SectionItem('CHANGES', buildChangeTreeNodes(element.changes)));
211
+ }
212
+ return sectionItems;
213
+ }
214
+
215
+ if (element instanceof SectionItem || element instanceof FolderItem) {
216
+ return element.items;
217
+ }
218
+
219
+ const repoEntries = await this.loadRepoEntries();
220
+ const sessionCount = repoEntries.reduce((total, entry) => total + entry.sessions.length, 0);
221
+ this.updateViewState(sessionCount);
222
+
223
+ if (repoEntries.length === 0) {
224
+ return [new InfoItem('No active Guardex agents', 'Open or start a sandbox session.')];
225
+ }
226
+
227
+ return repoEntries.map((entry) => new RepoItem(entry.repoRoot, entry.sessions, entry.changes));
228
+ }
229
+
230
+ async loadRepoEntries() {
231
+ const sessionFiles = await vscode.workspace.findFiles(
232
+ '**/.omx/state/active-sessions/*.json',
233
+ '**/{node_modules,.git,.omx/agent-worktrees,.omc/agent-worktrees}/**',
234
+ 200,
235
+ );
236
+
237
+ const repoRoots = new Set();
238
+ for (const uri of sessionFiles) {
239
+ repoRoots.add(repoRootFromSessionFile(uri.fsPath));
240
+ }
241
+
242
+ if (repoRoots.size === 0) {
243
+ for (const workspaceFolder of vscode.workspace.workspaceFolders || []) {
244
+ repoRoots.add(workspaceFolder.uri.fsPath);
245
+ }
246
+ }
247
+
248
+ const repoEntries = [];
249
+ for (const repoRoot of repoRoots) {
250
+ const sessions = readActiveSessions(repoRoot);
251
+ if (sessions.length > 0) {
252
+ repoEntries.push({
253
+ repoRoot,
254
+ sessions,
255
+ changes: readRepoChanges(repoRoot),
256
+ });
257
+ }
258
+ }
259
+
260
+ repoEntries.sort((left, right) => left.repoRoot.localeCompare(right.repoRoot));
261
+ return repoEntries;
262
+ }
263
+ }
264
+
265
+ function activate(context) {
266
+ const provider = new ActiveAgentsProvider();
267
+ const treeView = vscode.window.createTreeView('gitguardex.activeAgents', {
268
+ treeDataProvider: provider,
269
+ showCollapseAll: true,
270
+ });
271
+ provider.attachTreeView(treeView);
272
+ const refresh = () => provider.refresh();
273
+ const watcher = vscode.workspace.createFileSystemWatcher('**/.omx/state/active-sessions/*.json');
274
+ const interval = setInterval(refresh, 5_000);
275
+
276
+ context.subscriptions.push(
277
+ treeView,
278
+ vscode.commands.registerCommand('gitguardex.activeAgents.refresh', refresh),
279
+ vscode.commands.registerCommand('gitguardex.activeAgents.openWorktree', async (session) => {
280
+ if (!session?.worktreePath) {
281
+ return;
282
+ }
283
+
284
+ await vscode.commands.executeCommand(
285
+ 'vscode.openFolder',
286
+ vscode.Uri.file(session.worktreePath),
287
+ { forceNewWindow: true },
288
+ );
289
+ }),
290
+ vscode.commands.registerCommand('gitguardex.activeAgents.openChange', async (change) => {
291
+ if (!change?.absolutePath) {
292
+ return;
293
+ }
294
+
295
+ if (!fs.existsSync(change.absolutePath)) {
296
+ vscode.window.showInformationMessage?.(`Changed path is no longer on disk: ${change.relativePath}`);
297
+ return;
298
+ }
299
+
300
+ await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(change.absolutePath));
301
+ }),
302
+ vscode.workspace.onDidChangeWorkspaceFolders(refresh),
303
+ watcher,
304
+ { dispose: () => clearInterval(interval) },
305
+ );
306
+
307
+ watcher.onDidCreate(refresh, undefined, context.subscriptions);
308
+ watcher.onDidChange(refresh, undefined, context.subscriptions);
309
+ watcher.onDidDelete(refresh, undefined, context.subscriptions);
310
+ }
311
+
312
+ function deactivate() {}
313
+
314
+ module.exports = {
315
+ activate,
316
+ deactivate,
317
+ };
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "gitguardex-active-agents",
3
+ "displayName": "GitGuardex Active Agents",
4
+ "description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.",
5
+ "publisher": "recodeee",
6
+ "version": "0.0.1",
7
+ "license": "MIT",
8
+ "engines": {
9
+ "vscode": "^1.88.0"
10
+ },
11
+ "categories": [
12
+ "Source Control",
13
+ "Other"
14
+ ],
15
+ "activationEvents": [
16
+ "workspaceContains:.omx/state/active-sessions",
17
+ "onView:gitguardex.activeAgents"
18
+ ],
19
+ "main": "./extension.js",
20
+ "contributes": {
21
+ "commands": [
22
+ {
23
+ "command": "gitguardex.activeAgents.refresh",
24
+ "title": "Refresh Active Agents"
25
+ },
26
+ {
27
+ "command": "gitguardex.activeAgents.openWorktree",
28
+ "title": "Open Agent Worktree"
29
+ }
30
+ ],
31
+ "views": {
32
+ "scm": [
33
+ {
34
+ "id": "gitguardex.activeAgents",
35
+ "name": "Active Agents",
36
+ "visibility": "visible"
37
+ }
38
+ ]
39
+ },
40
+ "menus": {
41
+ "view/title": [
42
+ {
43
+ "command": "gitguardex.activeAgents.refresh",
44
+ "when": "view == gitguardex.activeAgents",
45
+ "group": "navigation"
46
+ }
47
+ ],
48
+ "view/item/context": [
49
+ {
50
+ "command": "gitguardex.activeAgents.openWorktree",
51
+ "when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
52
+ "group": "inline"
53
+ }
54
+ ]
55
+ }
56
+ }
57
+ }