@imdeadpool/guardex 7.0.16 → 7.0.19

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.
@@ -9,10 +9,12 @@ BASE_BRANCH="${GUARDEX_REVIEW_BOT_BASE_BRANCH:-}"
9
9
  ONLY_PR="${GUARDEX_REVIEW_BOT_ONLY_PR:-}"
10
10
  RETRY_FAILED_RAW="${GUARDEX_REVIEW_BOT_RETRY_FAILED:-false}"
11
11
  INCLUDE_DRAFT_RAW="${GUARDEX_REVIEW_BOT_INCLUDE_DRAFT:-false}"
12
+ NODE_BIN="${GUARDEX_NODE_BIN:-node}"
13
+ CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
12
14
 
13
15
  usage() {
14
16
  cat <<'USAGE'
15
- Usage: bash scripts/review-bot-watch.sh [options]
17
+ Usage: gx review [options]
16
18
 
17
19
  Continuously monitor GitHub pull requests targeting a base branch and dispatch
18
20
  one Codex-agent task per newly opened/updated PR.
@@ -34,6 +36,23 @@ Environment overrides:
34
36
  USAGE
35
37
  }
36
38
 
39
+ run_guardex_cli() {
40
+ if [[ -n "$CLI_ENTRY" ]]; then
41
+ "$NODE_BIN" "$CLI_ENTRY" "$@"
42
+ return $?
43
+ fi
44
+ if command -v gx >/dev/null 2>&1; then
45
+ gx "$@"
46
+ return $?
47
+ fi
48
+ if command -v gitguardex >/dev/null 2>&1; then
49
+ gitguardex "$@"
50
+ return $?
51
+ fi
52
+ echo "[review-bot-watch] Guardex CLI entrypoint unavailable; rerun via gx." >&2
53
+ return 127
54
+ }
55
+
37
56
  normalize_bool() {
38
57
  local raw="${1:-}"
39
58
  local fallback="${2:-0}"
@@ -134,16 +153,20 @@ if ! command -v codex >/dev/null 2>&1; then
134
153
  exit 127
135
154
  fi
136
155
 
137
- if [[ ! -x "$repo_root/scripts/codex-agent.sh" ]]; then
138
- echo "[review-bot-watch] Missing scripts/codex-agent.sh. Run: gx setup" >&2
139
- exit 1
140
- fi
141
-
142
156
  if ! gh auth status >/dev/null 2>&1; then
143
157
  echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
144
158
  exit 1
145
159
  fi
146
160
 
161
+ run_codex_agent() {
162
+ local local_script="$repo_root/scripts/codex-agent.sh"
163
+ if [[ -x "$local_script" ]]; then
164
+ bash "$local_script" "$@"
165
+ return $?
166
+ fi
167
+ run_guardex_cli internal run-shell codexAgent --target "$repo_root" "$@"
168
+ }
169
+
147
170
  sanitize_slug() {
148
171
  local raw="$1"
149
172
  local fallback="$2"
@@ -262,7 +285,7 @@ process_one_pr() {
262
285
 
263
286
  echo "[review-bot-watch] Dispatching Codex agent for PR #${pr} (${head_branch})"
264
287
  set +e
265
- bash "$repo_root/scripts/codex-agent.sh" \
288
+ run_codex_agent \
266
289
  --task "$task_name" \
267
290
  --agent "$AGENT_NAME" \
268
291
  --base "$BASE_BRANCH" \
@@ -0,0 +1,22 @@
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
+ - Splits live sessions inside `ACTIVE AGENTS` into `WORKING NOW` and `THINKING` groups so active edit lanes stand out immediately.
10
+ - Shows one row per live Guardex sandbox session inside those activity groups.
11
+ - Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
12
+ - Derives `thinking` versus `working` from the live sandbox worktree, surfaces working counts in the repo/header summary, and shows changed-file counts for active edits.
13
+ - Uses VS Code's native animated `loading~spin` icon for the running-state affordance.
14
+ - Reads repo-local presence files from `.omx/state/active-sessions/`.
15
+
16
+ Install from a Guardex-wired repo:
17
+
18
+ ```sh
19
+ node scripts/install-vscode-active-agents-extension.js
20
+ ```
21
+
22
+ Then reload the VS Code window.
@@ -0,0 +1,357 @@
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
+ const workingCount = countWorkingSessions(sessions);
22
+ if (workingCount > 0) {
23
+ descriptionParts.push(`${workingCount} working`);
24
+ }
25
+ if (changes.length > 0) {
26
+ descriptionParts.push(`${changes.length} changed`);
27
+ }
28
+ this.description = descriptionParts.join(' · ');
29
+ this.tooltip = [
30
+ repoRoot,
31
+ this.description,
32
+ ].join('\n');
33
+ this.iconPath = new vscode.ThemeIcon('repo');
34
+ this.contextValue = 'gitguardex.repo';
35
+ }
36
+ }
37
+
38
+ class SectionItem extends vscode.TreeItem {
39
+ constructor(label, items, options = {}) {
40
+ super(label, vscode.TreeItemCollapsibleState.Expanded);
41
+ this.items = items;
42
+ this.description = options.description
43
+ || (items.length > 0 ? String(items.length) : '');
44
+ this.contextValue = 'gitguardex.section';
45
+ }
46
+ }
47
+
48
+ class SessionItem extends vscode.TreeItem {
49
+ constructor(session) {
50
+ super(session.label, vscode.TreeItemCollapsibleState.None);
51
+ this.session = session;
52
+ const descriptionParts = [session.activityLabel || 'thinking'];
53
+ if (session.activityCountLabel) {
54
+ descriptionParts.push(session.activityCountLabel);
55
+ }
56
+ descriptionParts.push(session.elapsedLabel || formatElapsedFrom(session.startedAt));
57
+ this.description = descriptionParts.join(' · ');
58
+ const tooltipLines = [
59
+ session.branch,
60
+ `${session.agentName} · ${session.taskName}`,
61
+ `Status ${this.description}`,
62
+ session.changeCount > 0
63
+ ? `Changed ${session.activityCountLabel}: ${session.activitySummary}`
64
+ : session.activitySummary,
65
+ `Started ${session.startedAt}`,
66
+ session.worktreePath,
67
+ ];
68
+ this.tooltip = tooltipLines.filter(Boolean).join('\n');
69
+ this.iconPath = session.activityKind === 'working'
70
+ ? new vscode.ThemeIcon('edit')
71
+ : new vscode.ThemeIcon('loading~spin');
72
+ this.contextValue = 'gitguardex.session';
73
+ this.command = {
74
+ command: 'gitguardex.activeAgents.openWorktree',
75
+ title: 'Open Agent Worktree',
76
+ arguments: [session],
77
+ };
78
+ }
79
+ }
80
+
81
+ class FolderItem extends vscode.TreeItem {
82
+ constructor(label, relativePath, items) {
83
+ super(label, vscode.TreeItemCollapsibleState.Expanded);
84
+ this.relativePath = relativePath;
85
+ this.items = items;
86
+ this.tooltip = relativePath;
87
+ this.iconPath = new vscode.ThemeIcon('folder');
88
+ this.contextValue = 'gitguardex.folder';
89
+ }
90
+ }
91
+
92
+ class ChangeItem extends vscode.TreeItem {
93
+ constructor(change) {
94
+ super(path.basename(change.relativePath), vscode.TreeItemCollapsibleState.None);
95
+ this.change = change;
96
+ this.description = change.statusLabel;
97
+ this.tooltip = [
98
+ change.relativePath,
99
+ `Status ${change.statusText}`,
100
+ change.originalPath ? `Renamed from ${change.originalPath}` : '',
101
+ change.absolutePath,
102
+ ].filter(Boolean).join('\n');
103
+ this.resourceUri = vscode.Uri.file(change.absolutePath);
104
+ this.contextValue = 'gitguardex.change';
105
+ this.command = {
106
+ command: 'gitguardex.activeAgents.openChange',
107
+ title: 'Open Changed File',
108
+ arguments: [change],
109
+ };
110
+ }
111
+ }
112
+
113
+ function repoRootFromSessionFile(filePath) {
114
+ return path.resolve(path.dirname(filePath), '..', '..', '..');
115
+ }
116
+
117
+ function buildChangeTreeNodes(changes) {
118
+ const root = [];
119
+
120
+ function sortNodes(nodes) {
121
+ nodes.sort((left, right) => {
122
+ const leftIsFolder = left.kind === 'folder';
123
+ const rightIsFolder = right.kind === 'folder';
124
+ if (leftIsFolder !== rightIsFolder) {
125
+ return leftIsFolder ? -1 : 1;
126
+ }
127
+ return left.label.localeCompare(right.label);
128
+ });
129
+
130
+ for (const node of nodes) {
131
+ if (node.kind === 'folder') {
132
+ sortNodes(node.children);
133
+ }
134
+ }
135
+ }
136
+
137
+ for (const change of changes) {
138
+ const segments = change.relativePath.split(/[\\/]+/).filter(Boolean);
139
+ if (segments.length <= 1) {
140
+ root.push({ kind: 'change', label: change.relativePath, change });
141
+ continue;
142
+ }
143
+
144
+ let nodes = root;
145
+ let folderPath = '';
146
+ for (const segment of segments.slice(0, -1)) {
147
+ folderPath = folderPath ? path.posix.join(folderPath, segment) : segment;
148
+ let folderNode = nodes.find((node) => node.kind === 'folder' && node.relativePath === folderPath);
149
+ if (!folderNode) {
150
+ folderNode = {
151
+ kind: 'folder',
152
+ label: segment,
153
+ relativePath: folderPath,
154
+ children: [],
155
+ };
156
+ nodes.push(folderNode);
157
+ }
158
+ nodes = folderNode.children;
159
+ }
160
+
161
+ nodes.push({ kind: 'change', label: change.relativePath, change });
162
+ }
163
+
164
+ sortNodes(root);
165
+
166
+ function materialize(nodes) {
167
+ return nodes.map((node) => {
168
+ if (node.kind === 'folder') {
169
+ return new FolderItem(node.label, node.relativePath, materialize(node.children));
170
+ }
171
+ return new ChangeItem(node.change);
172
+ });
173
+ }
174
+
175
+ return materialize(root);
176
+ }
177
+
178
+ function countWorkingSessions(sessions) {
179
+ return sessions.filter((session) => session.activityKind === 'working').length;
180
+ }
181
+
182
+ function buildActiveAgentGroupNodes(sessions) {
183
+ const workingSessions = sessions
184
+ .filter((session) => session.activityKind === 'working')
185
+ .map((session) => new SessionItem(session));
186
+ const thinkingSessions = sessions
187
+ .filter((session) => session.activityKind !== 'working')
188
+ .map((session) => new SessionItem(session));
189
+ const groups = [];
190
+
191
+ if (workingSessions.length > 0) {
192
+ groups.push(new SectionItem('WORKING NOW', workingSessions));
193
+ }
194
+ if (thinkingSessions.length > 0) {
195
+ groups.push(new SectionItem('THINKING', thinkingSessions));
196
+ }
197
+
198
+ return groups;
199
+ }
200
+
201
+ class ActiveAgentsProvider {
202
+ constructor() {
203
+ this.onDidChangeTreeDataEmitter = new vscode.EventEmitter();
204
+ this.onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event;
205
+ this.treeView = null;
206
+ }
207
+
208
+ getTreeItem(element) {
209
+ return element;
210
+ }
211
+
212
+ attachTreeView(treeView) {
213
+ this.treeView = treeView;
214
+ this.updateViewState(0, 0);
215
+ }
216
+
217
+ updateViewState(sessionCount, workingCount) {
218
+ if (!this.treeView) {
219
+ return;
220
+ }
221
+
222
+ this.treeView.badge = sessionCount > 0
223
+ ? {
224
+ value: sessionCount,
225
+ tooltip: `${sessionCount} active agent${sessionCount === 1 ? '' : 's'}`
226
+ + (workingCount > 0 ? ` · ${workingCount} working now` : ''),
227
+ }
228
+ : undefined;
229
+ this.treeView.message = sessionCount > 0
230
+ ? undefined
231
+ : 'Start a sandbox session to populate this view.';
232
+ }
233
+
234
+ refresh() {
235
+ this.onDidChangeTreeDataEmitter.fire();
236
+ }
237
+
238
+ async getChildren(element) {
239
+ if (element instanceof RepoItem) {
240
+ const sectionItems = [
241
+ new SectionItem('ACTIVE AGENTS', buildActiveAgentGroupNodes(element.sessions), {
242
+ description: String(element.sessions.length),
243
+ }),
244
+ ];
245
+ if (element.changes.length > 0) {
246
+ sectionItems.push(new SectionItem('CHANGES', buildChangeTreeNodes(element.changes)));
247
+ }
248
+ return sectionItems;
249
+ }
250
+
251
+ if (element instanceof SectionItem || element instanceof FolderItem) {
252
+ return element.items;
253
+ }
254
+
255
+ const repoEntries = await this.loadRepoEntries();
256
+ const sessionCount = repoEntries.reduce((total, entry) => total + entry.sessions.length, 0);
257
+ const workingCount = repoEntries.reduce(
258
+ (total, entry) => total + countWorkingSessions(entry.sessions),
259
+ 0,
260
+ );
261
+ this.updateViewState(sessionCount, workingCount);
262
+
263
+ if (repoEntries.length === 0) {
264
+ return [new InfoItem('No active Guardex agents', 'Open or start a sandbox session.')];
265
+ }
266
+
267
+ return repoEntries.map((entry) => new RepoItem(entry.repoRoot, entry.sessions, entry.changes));
268
+ }
269
+
270
+ async loadRepoEntries() {
271
+ const sessionFiles = await vscode.workspace.findFiles(
272
+ '**/.omx/state/active-sessions/*.json',
273
+ '**/{node_modules,.git,.omx/agent-worktrees,.omc/agent-worktrees}/**',
274
+ 200,
275
+ );
276
+
277
+ const repoRoots = new Set();
278
+ for (const uri of sessionFiles) {
279
+ repoRoots.add(repoRootFromSessionFile(uri.fsPath));
280
+ }
281
+
282
+ if (repoRoots.size === 0) {
283
+ for (const workspaceFolder of vscode.workspace.workspaceFolders || []) {
284
+ repoRoots.add(workspaceFolder.uri.fsPath);
285
+ }
286
+ }
287
+
288
+ const repoEntries = [];
289
+ for (const repoRoot of repoRoots) {
290
+ const sessions = readActiveSessions(repoRoot);
291
+ if (sessions.length > 0) {
292
+ repoEntries.push({
293
+ repoRoot,
294
+ sessions,
295
+ changes: readRepoChanges(repoRoot),
296
+ });
297
+ }
298
+ }
299
+
300
+ repoEntries.sort((left, right) => left.repoRoot.localeCompare(right.repoRoot));
301
+ return repoEntries;
302
+ }
303
+ }
304
+
305
+ function activate(context) {
306
+ const provider = new ActiveAgentsProvider();
307
+ const treeView = vscode.window.createTreeView('gitguardex.activeAgents', {
308
+ treeDataProvider: provider,
309
+ showCollapseAll: true,
310
+ });
311
+ provider.attachTreeView(treeView);
312
+ const refresh = () => provider.refresh();
313
+ const watcher = vscode.workspace.createFileSystemWatcher('**/.omx/state/active-sessions/*.json');
314
+ const interval = setInterval(refresh, 5_000);
315
+
316
+ context.subscriptions.push(
317
+ treeView,
318
+ vscode.commands.registerCommand('gitguardex.activeAgents.refresh', refresh),
319
+ vscode.commands.registerCommand('gitguardex.activeAgents.openWorktree', async (session) => {
320
+ if (!session?.worktreePath) {
321
+ return;
322
+ }
323
+
324
+ await vscode.commands.executeCommand(
325
+ 'vscode.openFolder',
326
+ vscode.Uri.file(session.worktreePath),
327
+ { forceNewWindow: true },
328
+ );
329
+ }),
330
+ vscode.commands.registerCommand('gitguardex.activeAgents.openChange', async (change) => {
331
+ if (!change?.absolutePath) {
332
+ return;
333
+ }
334
+
335
+ if (!fs.existsSync(change.absolutePath)) {
336
+ vscode.window.showInformationMessage?.(`Changed path is no longer on disk: ${change.relativePath}`);
337
+ return;
338
+ }
339
+
340
+ await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(change.absolutePath));
341
+ }),
342
+ vscode.workspace.onDidChangeWorkspaceFolders(refresh),
343
+ watcher,
344
+ { dispose: () => clearInterval(interval) },
345
+ );
346
+
347
+ watcher.onDidCreate(refresh, undefined, context.subscriptions);
348
+ watcher.onDidChange(refresh, undefined, context.subscriptions);
349
+ watcher.onDidDelete(refresh, undefined, context.subscriptions);
350
+ }
351
+
352
+ function deactivate() {}
353
+
354
+ module.exports = {
355
+ activate,
356
+ deactivate,
357
+ };
@@ -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
+ }