@imdeadpool/guardex 7.0.21 → 7.0.22
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 +33 -29
- package/package.json +1 -1
- package/src/cli/main.js +594 -2796
- package/src/context.js +180 -30
- package/src/doctor/index.js +1071 -0
- package/src/finish/index.js +456 -358
- package/src/git/index.js +604 -1
- package/src/output/index.js +8 -1
- package/src/sandbox/index.js +301 -52
- package/src/scaffold/index.js +680 -0
- package/src/toolchain/index.js +622 -178
- package/templates/scripts/agent-branch-finish.sh +56 -5
- package/templates/scripts/agent-worktree-prune.sh +15 -1
- package/templates/vscode/guardex-active-agents/README.md +2 -0
- package/templates/vscode/guardex-active-agents/extension.js +283 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/package.json +3 -1
|
@@ -218,18 +218,48 @@ fi
|
|
|
218
218
|
|
|
219
219
|
get_worktree_for_branch() {
|
|
220
220
|
local branch="$1"
|
|
221
|
-
git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" '
|
|
221
|
+
git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${agent_worktree_root}/__source-probe-" '
|
|
222
222
|
$1 == "worktree" { wt = $2 }
|
|
223
|
-
$1 == "branch" && $2 == target {
|
|
223
|
+
$1 == "branch" && $2 == target {
|
|
224
|
+
if (index(wt, probe_prefix) != 1) {
|
|
225
|
+
print wt
|
|
226
|
+
exit
|
|
227
|
+
}
|
|
228
|
+
}
|
|
224
229
|
'
|
|
225
230
|
}
|
|
226
231
|
|
|
232
|
+
remove_stale_source_probe_worktrees() {
|
|
233
|
+
local branch="$1"
|
|
234
|
+
local stale_probe=""
|
|
235
|
+
|
|
236
|
+
while IFS= read -r stale_probe; do
|
|
237
|
+
[[ -z "$stale_probe" ]] && continue
|
|
238
|
+
[[ "$stale_probe" == "$current_worktree" ]] && continue
|
|
239
|
+
|
|
240
|
+
echo "[agent-branch-finish] Removing stale source-probe worktree for '${branch}': ${stale_probe}" >&2
|
|
241
|
+
git -C "$stale_probe" rebase --abort >/dev/null 2>&1 || true
|
|
242
|
+
git -C "$stale_probe" merge --abort >/dev/null 2>&1 || true
|
|
243
|
+
git -C "$repo_root" worktree remove "$stale_probe" --force >/dev/null 2>&1 || true
|
|
244
|
+
done < <(
|
|
245
|
+
git -C "$repo_root" worktree list --porcelain | awk -v target="refs/heads/${branch}" -v probe_prefix="${agent_worktree_root}/__source-probe-" '
|
|
246
|
+
$1 == "worktree" { wt = $2 }
|
|
247
|
+
$1 == "branch" && $2 == target {
|
|
248
|
+
if (index(wt, probe_prefix) == 1) {
|
|
249
|
+
print wt
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
'
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
227
256
|
is_clean_worktree() {
|
|
228
257
|
local wt="$1"
|
|
229
258
|
git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
|
|
230
259
|
&& git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"
|
|
231
260
|
}
|
|
232
261
|
|
|
262
|
+
remove_stale_source_probe_worktrees "$SOURCE_BRANCH"
|
|
233
263
|
source_worktree="$(get_worktree_for_branch "$SOURCE_BRANCH")"
|
|
234
264
|
created_source_probe=0
|
|
235
265
|
source_probe_path=""
|
|
@@ -295,8 +325,13 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
|
|
|
295
325
|
|
|
296
326
|
echo "[agent-sync-guard] Auto-sync failed while rebasing '${SOURCE_BRANCH}' onto origin/${BASE_BRANCH}." >&2
|
|
297
327
|
if [[ "$rebase_active" -eq 1 ]]; then
|
|
298
|
-
|
|
299
|
-
|
|
328
|
+
if [[ "$created_source_probe" -eq 1 ]]; then
|
|
329
|
+
echo "[agent-sync-guard] Temporary source-probe worktree will be cleaned up on exit." >&2
|
|
330
|
+
echo "[agent-sync-guard] Reattach '${SOURCE_BRANCH}' in a regular worktree, then rebase it onto origin/${BASE_BRANCH} manually." >&2
|
|
331
|
+
else
|
|
332
|
+
echo "[agent-sync-guard] Resolve conflicts, then run: git -C \"$source_worktree\" rebase --continue" >&2
|
|
333
|
+
echo "[agent-sync-guard] Or abort: git -C \"$source_worktree\" rebase --abort" >&2
|
|
334
|
+
fi
|
|
300
335
|
fi
|
|
301
336
|
exit 1
|
|
302
337
|
fi
|
|
@@ -366,6 +401,14 @@ is_local_branch_delete_error() {
|
|
|
366
401
|
return 1
|
|
367
402
|
}
|
|
368
403
|
|
|
404
|
+
is_remote_branch_missing_error() {
|
|
405
|
+
local output="$1"
|
|
406
|
+
if [[ "$output" == *"remote ref does not exist"* ]] || [[ "$output" == *"failed to push some refs"* ]]; then
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
return 1
|
|
410
|
+
}
|
|
411
|
+
|
|
369
412
|
read_pr_state() {
|
|
370
413
|
local state_line
|
|
371
414
|
state_line="$("$GH_BIN" pr view "$SOURCE_BRANCH" --json state,mergedAt,url --jq '[.state, (.mergedAt // ""), (.url // "")] | join("\u001f")' 2>/dev/null || true)"
|
|
@@ -568,7 +611,15 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
568
611
|
|
|
569
612
|
if [[ "$PUSH_ENABLED" -eq 1 && "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
|
|
570
613
|
if git -C "$repo_root" ls-remote --exit-code --heads origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
|
|
571
|
-
|
|
614
|
+
remote_delete_output=""
|
|
615
|
+
if ! remote_delete_output="$(git -C "$repo_root" push origin --delete "$SOURCE_BRANCH" 2>&1)"; then
|
|
616
|
+
if is_remote_branch_missing_error "$remote_delete_output"; then
|
|
617
|
+
echo "[agent-branch-finish] Remote branch '${SOURCE_BRANCH}' was already deleted; continuing cleanup." >&2
|
|
618
|
+
else
|
|
619
|
+
echo "$remote_delete_output" >&2
|
|
620
|
+
exit 1
|
|
621
|
+
fi
|
|
622
|
+
fi
|
|
572
623
|
fi
|
|
573
624
|
fi
|
|
574
625
|
|
|
@@ -111,6 +111,13 @@ is_managed_worktree_path() {
|
|
|
111
111
|
return 1
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
is_temporary_worktree_path() {
|
|
115
|
+
local entry="$1"
|
|
116
|
+
local name
|
|
117
|
+
name="$(basename "$entry")"
|
|
118
|
+
[[ "$name" == __agent_integrate-* || "$name" == __source-probe-* ]]
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
resolve_base_branch() {
|
|
115
122
|
local configured=""
|
|
116
123
|
local current=""
|
|
@@ -425,7 +432,9 @@ process_entry() {
|
|
|
425
432
|
local remove_reason=""
|
|
426
433
|
local branch_delete_mode="safe"
|
|
427
434
|
|
|
428
|
-
if
|
|
435
|
+
if is_temporary_worktree_path "$wt"; then
|
|
436
|
+
remove_reason="temporary-worktree"
|
|
437
|
+
elif [[ -z "$branch_ref" ]]; then
|
|
429
438
|
remove_reason="detached-worktree"
|
|
430
439
|
elif ! git -C "$repo_root" show-ref --verify --quiet "refs/heads/${branch}"; then
|
|
431
440
|
remove_reason="missing-branch"
|
|
@@ -452,6 +461,11 @@ process_entry() {
|
|
|
452
461
|
return
|
|
453
462
|
fi
|
|
454
463
|
|
|
464
|
+
if [[ "$remove_reason" == "temporary-worktree" ]]; then
|
|
465
|
+
git -C "$wt" rebase --abort >/dev/null 2>&1 || true
|
|
466
|
+
git -C "$wt" merge --abort >/dev/null 2>&1 || true
|
|
467
|
+
fi
|
|
468
|
+
|
|
455
469
|
if [[ "$FORCE_DIRTY" -ne 1 ]] && ! is_clean_worktree "$wt"; then
|
|
456
470
|
skipped_dirty=$((skipped_dirty + 1))
|
|
457
471
|
echo "[agent-worktree-prune] Skipping dirty worktree (${remove_reason}): ${wt}"
|
|
@@ -17,9 +17,11 @@ node scripts/install-vscode-active-agents-extension.js
|
|
|
17
17
|
|
|
18
18
|
What it does:
|
|
19
19
|
|
|
20
|
+
- Bundles a local GitGuardex icon so repo installs show branded extension metadata inside VS Code.
|
|
20
21
|
- Adds an `Active Agents` view to the Source Control container.
|
|
21
22
|
- Renders one repo node per live Guardex workspace with grouped `ACTIVE AGENTS` and `CHANGES` sections.
|
|
22
23
|
- Splits live sessions inside `ACTIVE AGENTS` into `BLOCKED`, `WORKING NOW`, `IDLE`, `STALLED`, and `DEAD` groups so stuck, active, and inactive lanes stand out immediately.
|
|
24
|
+
- Mirrors the same live state in the VS Code status bar so the selected session or active-agent count stays visible outside the tree.
|
|
23
25
|
- Shows one row per live Guardex sandbox session inside those activity groups.
|
|
24
26
|
- Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
|
|
25
27
|
- Derives session state from dirty worktree status, git conflict markers, PID liveness, and recent file mtimes, surfaces working/dead counts in the repo/header summary, and shows changed-file counts for active edits.
|
|
@@ -20,6 +20,10 @@ const SESSION_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git,.omx/agent-worktrees,.o
|
|
|
20
20
|
const WORKTREE_LOCK_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git}/**';
|
|
21
21
|
const SESSION_SCAN_LIMIT = 200;
|
|
22
22
|
const REFRESH_DEBOUNCE_MS = 250;
|
|
23
|
+
const ACTIVE_AGENTS_MANIFEST_RELATIVE = path.join('vscode', 'guardex-active-agents', 'package.json');
|
|
24
|
+
const ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE = path.join('scripts', 'install-vscode-active-agents-extension.js');
|
|
25
|
+
const RELOAD_WINDOW_ACTION = 'Reload Window';
|
|
26
|
+
const UPDATE_LATER_ACTION = 'Later';
|
|
23
27
|
const SESSION_ACTIVITY_GROUPS = [
|
|
24
28
|
{ kind: 'blocked', label: 'BLOCKED' },
|
|
25
29
|
{ kind: 'working', label: 'WORKING NOW' },
|
|
@@ -93,10 +97,83 @@ function sessionIdleDecoration(session, now = Date.now()) {
|
|
|
93
97
|
return undefined;
|
|
94
98
|
}
|
|
95
99
|
|
|
100
|
+
function formatCountLabel(count, singular, plural = `${singular}s`) {
|
|
101
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function sessionIdentityLabel(session) {
|
|
105
|
+
const agentName = typeof session?.agentName === 'string' ? session.agentName.trim() : '';
|
|
106
|
+
const taskName = typeof session?.taskName === 'string' ? session.taskName.trim() : '';
|
|
107
|
+
const label = typeof session?.label === 'string' ? session.label.trim() : '';
|
|
108
|
+
|
|
109
|
+
if (agentName && taskName) {
|
|
110
|
+
return `${agentName} · ${taskName}`;
|
|
111
|
+
}
|
|
112
|
+
if (agentName && label) {
|
|
113
|
+
return `${agentName} · ${label}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return agentName || taskName || label || 'session';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function sessionCommitPlaceholder(session) {
|
|
120
|
+
if (!session?.branch) {
|
|
121
|
+
return 'Pick an Active Agents session to commit its worktree.';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return `Commit ${sessionIdentityLabel(session)} on ${session.branch} · ${formatCountLabel(session.lockCount || 0, 'lock')} (Ctrl+Enter)`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function agentNameFromBranch(branch) {
|
|
128
|
+
const segments = String(branch || '')
|
|
129
|
+
.split('/')
|
|
130
|
+
.map((segment) => segment.trim())
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
if (segments[0] === 'agent' && segments[1]) {
|
|
133
|
+
return segments[1];
|
|
134
|
+
}
|
|
135
|
+
return segments[0] || 'lock';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function agentBadgeFromBranch(branch) {
|
|
139
|
+
const normalized = agentNameFromBranch(branch).toUpperCase().replace(/[^A-Z0-9]/g, '');
|
|
140
|
+
return normalized.slice(0, 2) || 'LK';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function buildActiveAgentsStatusSummary(summary) {
|
|
144
|
+
const activeCount = Math.max(0, (summary?.sessionCount || 0) - (summary?.deadCount || 0));
|
|
145
|
+
if (activeCount > 0) {
|
|
146
|
+
return `$(git-branch) ${formatCountLabel(activeCount, 'active agent')}`;
|
|
147
|
+
}
|
|
148
|
+
return `$(git-branch) ${formatCountLabel(summary?.sessionCount || 0, 'tracked session')}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildActiveAgentsStatusTooltip(selectedSession, summary) {
|
|
152
|
+
if (selectedSession?.branch) {
|
|
153
|
+
return [
|
|
154
|
+
selectedSession.branch,
|
|
155
|
+
sessionIdentityLabel(selectedSession),
|
|
156
|
+
formatCountLabel(selectedSession.lockCount || 0, 'lock'),
|
|
157
|
+
selectedSession.worktreePath,
|
|
158
|
+
'Click to open Source Control.',
|
|
159
|
+
].filter(Boolean).join('\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const activeCount = Math.max(0, (summary?.sessionCount || 0) - (summary?.deadCount || 0));
|
|
163
|
+
return [
|
|
164
|
+
formatCountLabel(activeCount, 'active agent'),
|
|
165
|
+
formatCountLabel(summary?.workingCount || 0, 'working now session', 'working now sessions'),
|
|
166
|
+
summary?.deadCount ? formatCountLabel(summary.deadCount, 'dead session') : '',
|
|
167
|
+
'Click to open Source Control.',
|
|
168
|
+
].filter(Boolean).join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
96
171
|
class SessionDecorationProvider {
|
|
97
172
|
constructor(nowProvider = () => Date.now()) {
|
|
98
173
|
this.nowProvider = nowProvider;
|
|
99
174
|
this.sessionsByUri = new Map();
|
|
175
|
+
this.lockEntriesByFileUri = new Map();
|
|
176
|
+
this.selectedBranch = '';
|
|
100
177
|
this.onDidChangeFileDecorationsEmitter = new vscode.EventEmitter();
|
|
101
178
|
this.onDidChangeFileDecorations = this.onDidChangeFileDecorationsEmitter.event;
|
|
102
179
|
}
|
|
@@ -107,13 +184,54 @@ class SessionDecorationProvider {
|
|
|
107
184
|
);
|
|
108
185
|
}
|
|
109
186
|
|
|
187
|
+
updateLockEntries(repoEntries) {
|
|
188
|
+
const nextEntriesByUri = new Map();
|
|
189
|
+
for (const entry of repoEntries || []) {
|
|
190
|
+
for (const [relativePath, lockEntry] of entry.lockEntries || []) {
|
|
191
|
+
nextEntriesByUri.set(
|
|
192
|
+
vscode.Uri.file(path.join(entry.repoRoot, relativePath)).toString(),
|
|
193
|
+
{ branch: lockEntry.branch },
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this.lockEntriesByFileUri = nextEntriesByUri;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setSelectedBranch(branch) {
|
|
201
|
+
this.selectedBranch = typeof branch === 'string' ? branch.trim() : '';
|
|
202
|
+
}
|
|
203
|
+
|
|
110
204
|
refresh() {
|
|
111
205
|
this.onDidChangeFileDecorationsEmitter.fire();
|
|
112
206
|
}
|
|
113
207
|
|
|
114
208
|
provideFileDecoration(uri) {
|
|
115
209
|
if (!uri || uri.scheme !== SESSION_DECORATION_SCHEME) {
|
|
116
|
-
|
|
210
|
+
if (!uri || uri.scheme !== 'file') {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const lockEntry = this.lockEntriesByFileUri.get(uri.toString());
|
|
215
|
+
if (!lockEntry?.branch) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const ownsSelectedSession = Boolean(this.selectedBranch) && lockEntry.branch === this.selectedBranch;
|
|
220
|
+
return {
|
|
221
|
+
badge: agentBadgeFromBranch(lockEntry.branch),
|
|
222
|
+
tooltip: ownsSelectedSession
|
|
223
|
+
? `Locked by selected session ${lockEntry.branch}`
|
|
224
|
+
: this.selectedBranch
|
|
225
|
+
? `Locked by ${lockEntry.branch} (selected session: ${this.selectedBranch})`
|
|
226
|
+
: `Locked by ${lockEntry.branch}`,
|
|
227
|
+
color: new vscode.ThemeColor(
|
|
228
|
+
ownsSelectedSession
|
|
229
|
+
? 'gitDecoration.modifiedResourceForeground'
|
|
230
|
+
: this.selectedBranch
|
|
231
|
+
? 'list.errorForeground'
|
|
232
|
+
: 'list.warningForeground',
|
|
233
|
+
),
|
|
234
|
+
};
|
|
117
235
|
}
|
|
118
236
|
|
|
119
237
|
return sessionIdleDecoration(this.sessionsByUri.get(uri.toString()), this.nowProvider());
|
|
@@ -451,6 +569,120 @@ function readCurrentBranch(repoRoot) {
|
|
|
451
569
|
}
|
|
452
570
|
}
|
|
453
571
|
|
|
572
|
+
function parseSimpleSemver(version) {
|
|
573
|
+
const parts = String(version || '')
|
|
574
|
+
.split('.')
|
|
575
|
+
.map((part) => Number.parseInt(part, 10));
|
|
576
|
+
if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
return parts;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function compareSimpleSemver(left, right) {
|
|
583
|
+
const leftParts = parseSimpleSemver(left);
|
|
584
|
+
const rightParts = parseSimpleSemver(right);
|
|
585
|
+
if (!leftParts || !rightParts) {
|
|
586
|
+
return 0;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
for (let index = 0; index < leftParts.length; index += 1) {
|
|
590
|
+
if (leftParts[index] !== rightParts[index]) {
|
|
591
|
+
return leftParts[index] - rightParts[index];
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return 0;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function readJsonFile(filePath) {
|
|
599
|
+
try {
|
|
600
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
601
|
+
} catch (_error) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function resolveActiveAgentsAutoUpdateCandidate(installedVersion) {
|
|
607
|
+
const candidates = [];
|
|
608
|
+
|
|
609
|
+
for (const workspaceFolder of vscode.workspace.workspaceFolders || []) {
|
|
610
|
+
const repoRoot = workspaceFolder?.uri?.fsPath;
|
|
611
|
+
if (!repoRoot) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const manifestPath = path.join(repoRoot, ACTIVE_AGENTS_MANIFEST_RELATIVE);
|
|
616
|
+
const installScriptPath = path.join(repoRoot, ACTIVE_AGENTS_INSTALL_SCRIPT_RELATIVE);
|
|
617
|
+
if (!fs.existsSync(manifestPath) || !fs.existsSync(installScriptPath)) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const manifest = readJsonFile(manifestPath);
|
|
622
|
+
const nextVersion = typeof manifest?.version === 'string' ? manifest.version.trim() : '';
|
|
623
|
+
if (!nextVersion || compareSimpleSemver(nextVersion, installedVersion) <= 0) {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
candidates.push({ repoRoot, installScriptPath, version: nextVersion });
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
candidates.sort((left, right) => compareSimpleSemver(right.version, left.version));
|
|
631
|
+
return candidates[0] || null;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function runActiveAgentsInstallScript(repoRoot, installScriptPath) {
|
|
635
|
+
return new Promise((resolve, reject) => {
|
|
636
|
+
cp.execFile(
|
|
637
|
+
process.execPath,
|
|
638
|
+
[installScriptPath],
|
|
639
|
+
{ cwd: repoRoot, encoding: 'utf8' },
|
|
640
|
+
(error, stdout, stderr) => {
|
|
641
|
+
if (error) {
|
|
642
|
+
reject(new Error(String(stderr || stdout || error.message || '').trim() || 'install failed'));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
resolve({ stdout, stderr });
|
|
646
|
+
},
|
|
647
|
+
);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async function maybeAutoUpdateActiveAgentsExtension(context) {
|
|
652
|
+
const installedVersion = typeof context?.extension?.packageJSON?.version === 'string'
|
|
653
|
+
? context.extension.packageJSON.version.trim()
|
|
654
|
+
: '';
|
|
655
|
+
if (!installedVersion) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const candidate = resolveActiveAgentsAutoUpdateCandidate(installedVersion);
|
|
660
|
+
if (!candidate) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
await runActiveAgentsInstallScript(candidate.repoRoot, candidate.installScriptPath);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
const failure = typeof error?.message === 'string' && error.message.trim()
|
|
668
|
+
? error.message.trim()
|
|
669
|
+
: 'install failed';
|
|
670
|
+
vscode.window.showWarningMessage?.(
|
|
671
|
+
`GitGuardex Active Agents could not auto-update to ${candidate.version}: ${failure}`,
|
|
672
|
+
);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const selection = await vscode.window.showInformationMessage?.(
|
|
677
|
+
`GitGuardex Active Agents updated to ${candidate.version}. Reload Window to use the newest companion.`,
|
|
678
|
+
RELOAD_WINDOW_ACTION,
|
|
679
|
+
UPDATE_LATER_ACTION,
|
|
680
|
+
);
|
|
681
|
+
if (selection === RELOAD_WINDOW_ACTION) {
|
|
682
|
+
await vscode.commands.executeCommand('workbench.action.reloadWindow');
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
454
686
|
function decorateSession(session, lockRegistry) {
|
|
455
687
|
return {
|
|
456
688
|
...session,
|
|
@@ -836,6 +1068,11 @@ class ActiveAgentsProvider {
|
|
|
836
1068
|
this.treeView = null;
|
|
837
1069
|
this.lockRegistryByRepoRoot = new Map();
|
|
838
1070
|
this.selectedSession = null;
|
|
1071
|
+
this.viewSummary = {
|
|
1072
|
+
sessionCount: 0,
|
|
1073
|
+
workingCount: 0,
|
|
1074
|
+
deadCount: 0,
|
|
1075
|
+
};
|
|
839
1076
|
}
|
|
840
1077
|
|
|
841
1078
|
getTreeItem(element) {
|
|
@@ -856,6 +1093,7 @@ class ActiveAgentsProvider {
|
|
|
856
1093
|
const currentKey = sessionSelectionKey(this.selectedSession);
|
|
857
1094
|
const nextKey = sessionSelectionKey(nextSession);
|
|
858
1095
|
this.selectedSession = nextSession;
|
|
1096
|
+
this.decorationProvider?.setSelectedBranch(nextSession?.branch || '');
|
|
859
1097
|
if (currentKey !== nextKey) {
|
|
860
1098
|
this.onDidChangeSelectedSessionEmitter.fire(this.selectedSession);
|
|
861
1099
|
}
|
|
@@ -865,6 +1103,10 @@ class ActiveAgentsProvider {
|
|
|
865
1103
|
return this.selectedSession ? { ...this.selectedSession } : null;
|
|
866
1104
|
}
|
|
867
1105
|
|
|
1106
|
+
getViewSummary() {
|
|
1107
|
+
return { ...this.viewSummary };
|
|
1108
|
+
}
|
|
1109
|
+
|
|
868
1110
|
syncSelectedSession(repoEntries) {
|
|
869
1111
|
if (!this.selectedSession) {
|
|
870
1112
|
return;
|
|
@@ -882,6 +1124,11 @@ class ActiveAgentsProvider {
|
|
|
882
1124
|
}
|
|
883
1125
|
|
|
884
1126
|
const activeCount = Math.max(0, sessionCount - deadCount);
|
|
1127
|
+
this.viewSummary = {
|
|
1128
|
+
sessionCount,
|
|
1129
|
+
workingCount,
|
|
1130
|
+
deadCount,
|
|
1131
|
+
};
|
|
885
1132
|
const badgeTooltipParts = [];
|
|
886
1133
|
if (activeCount > 0) {
|
|
887
1134
|
badgeTooltipParts.push(`${activeCount} active agent${activeCount === 1 ? '' : 's'}`);
|
|
@@ -918,6 +1165,7 @@ class ActiveAgentsProvider {
|
|
|
918
1165
|
|
|
919
1166
|
this.updateViewState(sessionCount, workingCount, deadCount);
|
|
920
1167
|
this.decorationProvider?.updateSessions(repoEntries.flatMap((entry) => entry.sessions));
|
|
1168
|
+
this.decorationProvider?.updateLockEntries(repoEntries);
|
|
921
1169
|
return repoEntries;
|
|
922
1170
|
}
|
|
923
1171
|
|
|
@@ -996,6 +1244,7 @@ class ActiveAgentsProvider {
|
|
|
996
1244
|
changes: readRepoChanges(repoRoot).map((change) => (
|
|
997
1245
|
decorateChange(change, lockRegistry, currentBranch)
|
|
998
1246
|
)),
|
|
1247
|
+
lockEntries: Array.from(lockRegistry.entriesByPath.entries()),
|
|
999
1248
|
};
|
|
1000
1249
|
});
|
|
1001
1250
|
}
|
|
@@ -1080,6 +1329,9 @@ function activate(context) {
|
|
|
1080
1329
|
'gitguardex.activeAgents.commitInput',
|
|
1081
1330
|
'Active Agents Commit',
|
|
1082
1331
|
);
|
|
1332
|
+
const activeAgentsStatusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10);
|
|
1333
|
+
activeAgentsStatusItem.name = 'GitGuardex Active Agents';
|
|
1334
|
+
activeAgentsStatusItem.command = 'gitguardex.activeAgents.focus';
|
|
1083
1335
|
provider.attachTreeView(treeView);
|
|
1084
1336
|
const scheduleRefresh = () => refreshController.scheduleRefresh();
|
|
1085
1337
|
const refresh = () => void refreshController.refreshNow();
|
|
@@ -1089,11 +1341,24 @@ function activate(context) {
|
|
|
1089
1341
|
const updateCommitInput = (session) => {
|
|
1090
1342
|
sourceControl.inputBox.enabled = true;
|
|
1091
1343
|
sourceControl.inputBox.visible = true;
|
|
1092
|
-
sourceControl.inputBox.placeholder = session
|
|
1093
|
-
|
|
1094
|
-
|
|
1344
|
+
sourceControl.inputBox.placeholder = sessionCommitPlaceholder(session);
|
|
1345
|
+
};
|
|
1346
|
+
const updateStatusBar = () => {
|
|
1347
|
+
const selectedSession = provider.getSelectedSession();
|
|
1348
|
+
const summary = provider.getViewSummary();
|
|
1349
|
+
if ((summary.sessionCount || 0) <= 0) {
|
|
1350
|
+
activeAgentsStatusItem.hide();
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
activeAgentsStatusItem.text = selectedSession?.branch
|
|
1355
|
+
? `$(git-branch) ${sessionIdentityLabel(selectedSession)} · ${formatCountLabel(selectedSession.lockCount || 0, 'lock')}`
|
|
1356
|
+
: buildActiveAgentsStatusSummary(summary);
|
|
1357
|
+
activeAgentsStatusItem.tooltip = buildActiveAgentsStatusTooltip(selectedSession, summary);
|
|
1358
|
+
activeAgentsStatusItem.show();
|
|
1095
1359
|
};
|
|
1096
1360
|
updateCommitInput(null);
|
|
1361
|
+
updateStatusBar();
|
|
1097
1362
|
const commitSelectedSession = async () => {
|
|
1098
1363
|
const selectedSession = provider.getSelectedSession();
|
|
1099
1364
|
if (!selectedSession?.worktreePath) {
|
|
@@ -1140,15 +1405,27 @@ function activate(context) {
|
|
|
1140
1405
|
scheduleRefresh();
|
|
1141
1406
|
};
|
|
1142
1407
|
|
|
1143
|
-
provider.onDidChangeSelectedSession(
|
|
1408
|
+
provider.onDidChangeSelectedSession((session) => {
|
|
1409
|
+
updateCommitInput(session);
|
|
1410
|
+
updateStatusBar();
|
|
1411
|
+
decorationProvider.refresh();
|
|
1412
|
+
});
|
|
1413
|
+
provider.onDidChangeTreeData(() => {
|
|
1414
|
+
updateCommitInput(provider.getSelectedSession());
|
|
1415
|
+
updateStatusBar();
|
|
1416
|
+
});
|
|
1144
1417
|
|
|
1145
1418
|
context.subscriptions.push(
|
|
1146
1419
|
treeView,
|
|
1147
1420
|
sourceControl,
|
|
1421
|
+
activeAgentsStatusItem,
|
|
1148
1422
|
refreshController,
|
|
1149
1423
|
vscode.window.registerFileDecorationProvider(decorationProvider),
|
|
1150
1424
|
vscode.commands.registerCommand('gitguardex.activeAgents.startAgent', () => startAgentFromPrompt(refresh)),
|
|
1151
1425
|
vscode.commands.registerCommand('gitguardex.activeAgents.refresh', refresh),
|
|
1426
|
+
vscode.commands.registerCommand('gitguardex.activeAgents.focus', async () => {
|
|
1427
|
+
await vscode.commands.executeCommand('workbench.view.scm');
|
|
1428
|
+
}),
|
|
1152
1429
|
vscode.commands.registerCommand('gitguardex.activeAgents.commitSelectedSession', commitSelectedSession),
|
|
1153
1430
|
vscode.commands.registerCommand('gitguardex.activeAgents.openWorktree', async (session) => {
|
|
1154
1431
|
if (!session?.worktreePath) {
|
|
@@ -1190,6 +1467,7 @@ function activate(context) {
|
|
|
1190
1467
|
...bindRefreshWatcher(worktreeLockWatcher, scheduleRefresh),
|
|
1191
1468
|
);
|
|
1192
1469
|
void refreshController.refreshNow();
|
|
1470
|
+
void maybeAutoUpdateActiveAgentsExtension(context);
|
|
1193
1471
|
}
|
|
1194
1472
|
|
|
1195
1473
|
function deactivate() {}
|
|
Binary file
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
"displayName": "GitGuardex Active Agents",
|
|
4
4
|
"description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.",
|
|
5
5
|
"publisher": "recodeee",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.3",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"icon": "icon.png",
|
|
8
9
|
"engines": {
|
|
9
10
|
"vscode": "^1.88.0"
|
|
10
11
|
},
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
"Other"
|
|
14
15
|
],
|
|
15
16
|
"activationEvents": [
|
|
17
|
+
"onStartupFinished",
|
|
16
18
|
"workspaceContains:.omx/state/active-sessions",
|
|
17
19
|
"workspaceContains:.omx/agent-worktrees",
|
|
18
20
|
"workspaceContains:.omc/agent-worktrees",
|