Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Why

- The GitGuardEx VS Code companion currently depends on `.omx/state/active-sessions/*.json`.
- That misses real live sandboxes when operators start a worktree with `gx branch start` and the live source is only the worktree-root `AGENT.lock` marker.
- Result: the Source Control view can look empty or stale even while a managed worktree is actively owned and changing.

## What Changes

- Teach the Active Agents companion to discover live managed worktrees from root-level `AGENT.lock` markers in `.omx/agent-worktrees/**` and `.omc/agent-worktrees/**`.
- Merge those synthetic live rows with the existing `.omx/state/active-sessions/*.json` records, preferring the richer session file when both exist for the same worktree.
- Keep the current SCM affordances, lock decorations, and change nesting behavior, but make refresh/watch logic observe worktree-root `AGENT.lock` files too.
- Document the fallback behavior in the extension README.

## Risks

- `AGENT.lock` is optional telemetry, so parsing must degrade safely and ignore invalid or stale files instead of crashing the view.
- Synthetic rows have no launcher PID, so stop/dead semantics must remain tied to wrapper session records only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## ADDED Requirements

### Requirement: Active Agents view falls back to live managed worktree telemetry
The GitGuardEx Active Agents VS Code companion SHALL surface managed worktrees that expose a live root-level `AGENT.lock` marker, even when no `.omx/state/active-sessions/*.json` launcher record exists for that worktree.

#### Scenario: Managed worktree lock creates a synthetic live row
- **WHEN** a managed Guardex worktree under `.omx/agent-worktrees/` or `.omc/agent-worktrees/` contains a valid `AGENT.lock`
- **AND** no `.omx/state/active-sessions/*.json` record exists for that same worktree
- **THEN** the Active Agents SCM view shows a live row for that worktree
- **AND** the row still derives `thinking` versus `working` from the worktree git state.

#### Scenario: Wrapper session record wins over lock fallback
- **WHEN** both a valid `.omx/state/active-sessions/*.json` record and a valid root `AGENT.lock` exist for the same managed worktree
- **THEN** the companion renders a single row for that worktree
- **AND** it prefers the launcher-backed session metadata instead of duplicating the row.

#### Scenario: Lock fallback refreshes with worktree telemetry updates
- **WHEN** a managed worktree `AGENT.lock` file is created, changed, or deleted
- **THEN** the Active Agents companion refreshes the affected SCM rows
- **AND** invalid lock payloads are ignored without crashing the view.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Definition of Done

This change is complete only when **all** of the following are true:

- Every checkbox below is checked.
- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff.
- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline.

## 1. Specification

- [x] 1.1 Capture the live-worktree telemetry problem and acceptance criteria in this change.
- [x] 1.2 Define normative requirements for `AGENT.lock` fallback discovery in `specs/vscode-active-agents-extension/spec.md`.

## 2. Implementation

- [x] 2.1 Add a session-schema fallback that can synthesize live Active Agents rows from managed worktree `AGENT.lock` markers.
- [x] 2.2 Merge fallback worktree rows with existing `.omx/state/active-sessions/*.json` rows, preferring wrapper session data when both point at the same worktree.
- [x] 2.3 Refresh/watch the SCM companion on worktree-root `AGENT.lock` changes and keep current commit/change affordances working for synthetic rows.
- [x] 2.4 Update the extension README to describe the live worktree fallback.

## 3. Verification

- [x] 3.1 Run focused extension/session-state regression coverage.
- [x] 3.2 Run `openspec validate agent-codex-vscode-active-agents-live-worktree-telem-2026-04-22-13-43 --type change --strict`.
- [x] 3.3 Run `openspec validate --specs`.

## 4. Cleanup (mandatory; run before claiming completion)

- [ ] 4.1 Run `gx branch finish --branch agent/codex/vscode-active-agents-live-worktree-telem-2026-04-22-13-43 --base main --via-pr --wait-for-merge --cleanup`.
- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff.
- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch).
2 changes: 1 addition & 1 deletion templates/vscode/guardex-active-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ What it does:
- Shows repo-root git changes in a sibling `CHANGES` section when the guarded repo itself is dirty.
- 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.
- Uses distinct VS Code codicons for each session state: `warning`, `edit`, `loading~spin`, `clock`, and `error`.
- Reads repo-local presence files from `.omx/state/active-sessions/`.
- Reads repo-local presence files from `.omx/state/active-sessions/` and falls back to managed worktree-root `AGENT.lock` telemetry when the launcher session file is absent.
45 changes: 38 additions & 7 deletions templates/vscode/guardex-active-agents/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const IDLE_ERROR_MS = 30 * 60 * 1000;
const LOCK_FILE_RELATIVE = path.join('.omx', 'state', 'agent-file-locks.json');
const ACTIVE_SESSION_FILES_GLOB = '**/.omx/state/active-sessions/*.json';
const AGENT_FILE_LOCKS_GLOB = '**/.omx/state/agent-file-locks.json';
const WORKTREE_AGENT_LOCKS_GLOB = '**/{.omx,.omc}/agent-worktrees/**/AGENT.lock';
const SESSION_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git,.omx/agent-worktrees,.omc/agent-worktrees}/**';
const WORKTREE_LOCK_SCAN_EXCLUDE_GLOB = '**/{node_modules,.git}/**';
const SESSION_SCAN_LIMIT = 200;
const REFRESH_DEBOUNCE_MS = 250;
const SESSION_ACTIVITY_GROUPS = [
Expand Down Expand Up @@ -187,14 +189,23 @@ class SessionItem extends vscode.TreeItem {
const tooltipLines = [
session.branch,
`${session.agentName} · ${session.taskName}`,
session.latestTaskPreview && session.latestTaskPreview !== session.taskName
? `Live task ${session.latestTaskPreview}`
: '',
`Status ${this.description}`,
session.changeCount > 0
? `Changed ${session.activityCountLabel}: ${session.activitySummary}`
: session.activitySummary,
`Locks ${lockCount}`,
session.pidAlive === false ? `PID ${session.pid} not alive` : `PID ${session.pid} alive`,
Number.isInteger(session.pid) && session.pid > 0
? session.pidAlive === false
? `PID ${session.pid} not alive`
: `PID ${session.pid} alive`
: '',
session.lastFileActivityAt ? `Last file activity ${session.lastFileActivityAt}` : '',
`Started ${session.startedAt}`,
session.sourceKind === 'worktree-lock'
? `Telemetry updated ${session.telemetryUpdatedAt || session.startedAt}`
: `Started ${session.startedAt}`,
session.worktreePath,
];
this.tooltip = tooltipLines.filter(Boolean).join('\n');
Expand Down Expand Up @@ -365,6 +376,10 @@ function repoRootFromSessionFile(filePath) {
return path.resolve(path.dirname(filePath), '..', '..', '..');
}

function repoRootFromWorktreeLockFile(filePath) {
return path.resolve(path.dirname(filePath), '..', '..', '..');
}

function repoRootFromLockFile(filePath) {
return path.resolve(path.dirname(filePath), '..', '..');
}
Expand Down Expand Up @@ -479,16 +494,29 @@ function localizeChangeForSession(session, change) {
}

async function findRepoSessionEntries() {
const sessionFiles = await vscode.workspace.findFiles(
ACTIVE_SESSION_FILES_GLOB,
SESSION_SCAN_EXCLUDE_GLOB,
SESSION_SCAN_LIMIT,
);
const [sessionFiles, worktreeLockFiles] = await Promise.all([
vscode.workspace.findFiles(
ACTIVE_SESSION_FILES_GLOB,
SESSION_SCAN_EXCLUDE_GLOB,
SESSION_SCAN_LIMIT,
),
vscode.workspace.findFiles(
WORKTREE_AGENT_LOCKS_GLOB,
WORKTREE_LOCK_SCAN_EXCLUDE_GLOB,
SESSION_SCAN_LIMIT,
),
]);

const repoRoots = new Set();
for (const uri of sessionFiles) {
repoRoots.add(repoRootFromSessionFile(uri.fsPath));
}
for (const uri of worktreeLockFiles) {
if (path.basename(uri.fsPath) !== 'AGENT.lock') {
continue;
}
repoRoots.add(repoRootFromWorktreeLockFile(uri.fsPath));
}

if (repoRoots.size === 0) {
for (const workspaceFolder of vscode.workspace.workspaceFolders || []) {
Expand Down Expand Up @@ -1057,6 +1085,7 @@ function activate(context) {
const refresh = () => void refreshController.refreshNow();
const activeSessionsWatcher = vscode.workspace.createFileSystemWatcher(ACTIVE_SESSION_FILES_GLOB);
const lockWatcher = vscode.workspace.createFileSystemWatcher(AGENT_FILE_LOCKS_GLOB);
const worktreeLockWatcher = vscode.workspace.createFileSystemWatcher(WORKTREE_AGENT_LOCKS_GLOB);
const updateCommitInput = (session) => {
sourceControl.inputBox.enabled = true;
sourceControl.inputBox.visible = true;
Expand Down Expand Up @@ -1151,12 +1180,14 @@ function activate(context) {
vscode.workspace.onDidChangeWorkspaceFolders(scheduleRefresh),
activeSessionsWatcher,
lockWatcher,
worktreeLockWatcher,
{ dispose: () => clearInterval(interval) },
);

context.subscriptions.push(
...bindRefreshWatcher(activeSessionsWatcher, scheduleRefresh),
...bindRefreshWatcher(lockWatcher, refreshLockRegistry),
...bindRefreshWatcher(worktreeLockWatcher, scheduleRefresh),
);
void refreshController.refreshNow();
}
Expand Down
2 changes: 2 additions & 0 deletions templates/vscode/guardex-active-agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
],
"activationEvents": [
"workspaceContains:.omx/state/active-sessions",
"workspaceContains:.omx/agent-worktrees",
"workspaceContains:.omc/agent-worktrees",
"onView:gitguardex.activeAgents"
],
"main": "./extension.js",
Expand Down
Loading