@imdeadpool/guardex 7.0.24 → 7.0.26

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,54 @@
1
+ {
2
+ "iconDefinitions": {
3
+ "_gitguardex_agent": {
4
+ "iconPath": "./icons/agent.svg"
5
+ },
6
+ "_gitguardex_branch": {
7
+ "iconPath": "./icons/branch.svg"
8
+ },
9
+ "_gitguardex_config": {
10
+ "iconPath": "./icons/config.svg"
11
+ },
12
+ "_gitguardex_hook": {
13
+ "iconPath": "./icons/hook.svg"
14
+ },
15
+ "_gitguardex_openspec": {
16
+ "iconPath": "./icons/openspec.svg"
17
+ },
18
+ "_gitguardex_plan": {
19
+ "iconPath": "./icons/plan.svg"
20
+ },
21
+ "_gitguardex_spec": {
22
+ "iconPath": "./icons/spec.svg"
23
+ }
24
+ },
25
+ "folderNames": {
26
+ ".agents": "_gitguardex_agent",
27
+ ".githooks": "_gitguardex_hook",
28
+ ".omc": "_gitguardex_agent",
29
+ ".omx": "_gitguardex_agent",
30
+ "agent-worktrees": "_gitguardex_branch",
31
+ "changes": "_gitguardex_openspec",
32
+ "plan": "_gitguardex_plan",
33
+ "rules": "_gitguardex_spec",
34
+ "specs": "_gitguardex_spec"
35
+ },
36
+ "fileNames": {
37
+ ".openspec.yaml": "_gitguardex_config",
38
+ "AGENT.lock": "_gitguardex_agent",
39
+ "AGENTS.md": "_gitguardex_agent",
40
+ "CLAUDE.md": "_gitguardex_agent",
41
+ "config.yaml": "_gitguardex_config",
42
+ "context-docs-cue.md": "_gitguardex_spec",
43
+ "post-checkout": "_gitguardex_hook",
44
+ "pre-commit": "_gitguardex_hook",
45
+ "pre-push": "_gitguardex_hook",
46
+ "proposal.md": "_gitguardex_openspec",
47
+ "spec.md": "_gitguardex_spec",
48
+ "tasks.md": "_gitguardex_plan",
49
+ "plan.md": "_gitguardex_plan"
50
+ },
51
+ "fileExtensions": {
52
+ "openspec.yaml": "_gitguardex_config"
53
+ }
54
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <path fill="#75beff" d="M8 1.5a3 3 0 0 1 3 3v1a3 3 0 1 1-6 0v-1a3 3 0 0 1 3-3Z"/>
3
+ <path fill="#3794ff" d="M2.5 14c.5-2.8 2.6-4.5 5.5-4.5s5 1.7 5.5 4.5H2.5Z"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <path fill="#89d185" d="M4 3.5a2 2 0 1 1 2.5 1.94v3.12A4 4 0 0 0 10 4.6V3h1.5v1.6a5.5 5.5 0 0 1-5 5.47v.49A2 2 0 1 1 4 12.5a2 2 0 0 1 1-1.73V5.23a2 2 0 0 1-1-1.73Zm2-.5a.5.5 0 1 0 0 1 .5.5 0 0 0 0-1Zm0 9a.5.5 0 1 0 0 1 .5.5 0 0 0 0-1Z"/>
3
+ <path fill="#4ec9b0" d="M10.5 1.5H14V5h-3.5V1.5Z"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <path fill="#c586c0" d="M7.2 1h1.6l.4 1.7a5.4 5.4 0 0 1 1.2.5l1.5-.9 1.1 1.1-.9 1.5c.2.4.4.8.5 1.2l1.7.4v1.6l-1.7.4c-.1.4-.3.8-.5 1.2l.9 1.5-1.1 1.1-1.5-.9c-.4.2-.8.4-1.2.5L8.8 15H7.2l-.4-1.7a5.4 5.4 0 0 1-1.2-.5l-1.5.9L3 12.6l.9-1.5a5.4 5.4 0 0 1-.5-1.2l-1.7-.4V7.9l1.7-.4c.1-.4.3-.8.5-1.2L3 4.8l1.1-1.1 1.5.9c.4-.2.8-.4 1.2-.5L7.2 1Z"/>
3
+ <circle cx="8" cy="8" r="2.2" fill="#1e1e1e"/>
4
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <path fill="#ffcc66" d="M8.6 1.5 3.5 8.6h3.2L5.7 14.5l6.8-8.2H9.1l1.4-4.8H8.6Z"/>
3
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <path fill="#4ec9b0" d="M3 1.5h7l3 3V14.5H3V1.5Z"/>
3
+ <path fill="#3794ff" d="M9.5 2.2v3h3L9.5 2.2Z"/>
4
+ <path fill="#ffcc66" d="m6.9 10.6-2-2 1-1 1 1 3.2-3.2 1 1-4.2 4.2Z"/>
5
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <path fill="#dcdcaa" d="M2 2h12v12H2V2Zm2 2v8h2.5V4H4Zm4 0v8h4V4H8Z"/>
3
+ <path fill="#ce9178" d="M8 6h4v1.5H8V6Zm0 3h4v1.5H8V9Z"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <path fill="#b5cea8" d="M3 1.5h10v13H3v-13Z"/>
3
+ <path fill="#1e1e1e" d="M5 4h6v1.2H5V4Zm0 2.8h6V8H5V6.8Zm0 2.8h4.5v1.2H5V9.6Z"/>
4
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path
3
+ fill="currentColor"
4
+ fill-rule="evenodd"
5
+ clip-rule="evenodd"
6
+ d="M12 4.25C9.96 4.25 8.31 5.9 8.31 7.94C8.31 9.32 9.08 10.53 10.22 11.16V12.55C8.15 13.19 6.69 15.12 6.69 17.29V18.31C6.69 19.02 7.27 19.6 7.98 19.6H9.06V20.46C9.06 20.89 9.41 21.25 9.85 21.25H14.15C14.59 21.25 14.94 20.89 14.94 20.46V19.6H16.02C16.73 19.6 17.31 19.02 17.31 18.31V17.29C17.31 15.12 15.85 13.19 13.78 12.55V11.16C14.92 10.53 15.69 9.32 15.69 7.94C15.69 5.9 14.04 4.25 12 4.25Z"
7
+ />
8
+ <path
9
+ d="M9.88 19.6H14.12"
10
+ stroke="currentColor"
11
+ stroke-width="1.4"
12
+ stroke-linecap="round"
13
+ />
14
+ </svg>
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "gitguardex-active-agents",
3
3
  "displayName": "GitGuardex Active Agents",
4
- "description": "Shows live Guardex sandbox sessions and repo changes inside VS Code Source Control.",
4
+ "description": "Shows live Guardex sandbox sessions and repo changes in a dedicated VS Code Active Agents sidebar.",
5
5
  "publisher": "recodeee",
6
- "version": "0.0.7",
6
+ "version": "0.0.17",
7
7
  "license": "MIT",
8
8
  "icon": "icon.png",
9
9
  "engines": {
@@ -31,6 +31,11 @@
31
31
  "command": "gitguardex.activeAgents.refresh",
32
32
  "title": "Refresh Active Agents"
33
33
  },
34
+ {
35
+ "command": "gitguardex.activeAgents.restart",
36
+ "title": "Restart Active Agents",
37
+ "icon": "$(debug-restart)"
38
+ },
34
39
  {
35
40
  "command": "gitguardex.activeAgents.commitSelectedSession",
36
41
  "title": "Commit Selected Session",
@@ -61,16 +66,27 @@
61
66
  "icon": "$(debug-stop)"
62
67
  },
63
68
  {
64
- "command": "gitguardex.activeAgents.openSessionDiff",
65
- "title": "Open Diff",
66
- "icon": "$(diff)"
69
+ "command": "gitguardex.activeAgents.showSessionTerminal",
70
+ "title": "Show Terminal",
71
+ "icon": "$(terminal)"
67
72
  }
68
73
  ],
74
+ "viewsContainers": {
75
+ "activitybar": [
76
+ {
77
+ "id": "gitguardex.activeAgentsContainer",
78
+ "title": "Active Agents",
79
+ "icon": "media/active-agents-hivemind.svg"
80
+ }
81
+ ]
82
+ },
69
83
  "views": {
70
- "scm": [
84
+ "gitguardex.activeAgentsContainer": [
71
85
  {
72
86
  "id": "gitguardex.activeAgents",
73
87
  "name": "Active Agents",
88
+ "contextualTitle": "Active Agents",
89
+ "icon": "media/active-agents-hivemind.svg",
74
90
  "visibility": "visible"
75
91
  }
76
92
  ]
@@ -78,7 +94,7 @@
78
94
  "viewsWelcome": [
79
95
  {
80
96
  "view": "gitguardex.activeAgents",
81
- "contents": "No live Guardex agents are visible in this workspace yet.\n\nThis view tracks Guardex session files and managed worktree telemetry, not every repo visible in Source Control.\n\n[Start agent](command:gitguardex.activeAgents.startAgent)\n[Open guide](https://github.com/recodeee/gitguardex/blob/main/vscode/guardex-active-agents/README.md#quick-start)\n[Refresh](command:gitguardex.activeAgents.refresh)"
97
+ "contents": "No live Guardex agents are visible in this workspace yet.\n\nThis sidebar tracks Guardex session files and managed worktree telemetry without taking over Source Control.\n\n[Start agent](command:gitguardex.activeAgents.startAgent)\n[Open guide](https://github.com/recodeee/gitguardex/blob/main/vscode/guardex-active-agents/README.md#quick-start)\n[Refresh](command:gitguardex.activeAgents.refresh)"
82
98
  }
83
99
  ],
84
100
  "menus": {
@@ -88,12 +104,24 @@
88
104
  "when": "view == gitguardex.activeAgents && guardex.hasAgents",
89
105
  "group": "navigation@1"
90
106
  },
107
+ {
108
+ "command": "gitguardex.activeAgents.restart",
109
+ "when": "view == gitguardex.activeAgents",
110
+ "group": "navigation@8"
111
+ },
91
112
  {
92
113
  "command": "gitguardex.activeAgents.refresh",
93
114
  "when": "view == gitguardex.activeAgents",
94
115
  "group": "navigation@9"
95
116
  }
96
117
  ],
118
+ "extension/context": [
119
+ {
120
+ "command": "gitguardex.activeAgents.restart",
121
+ "when": "extension == recodeee.gitguardex-active-agents && extensionStatus == installed",
122
+ "group": "2_configure@2"
123
+ }
124
+ ],
97
125
  "view/item/context": [
98
126
  {
99
127
  "command": "gitguardex.activeAgents.openWorktree",
@@ -106,22 +134,22 @@
106
134
  "group": "inline"
107
135
  },
108
136
  {
109
- "command": "gitguardex.activeAgents.finishSession",
137
+ "command": "gitguardex.activeAgents.showSessionTerminal",
110
138
  "when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
111
139
  "group": "inline"
112
140
  },
113
141
  {
114
- "command": "gitguardex.activeAgents.syncSession",
142
+ "command": "gitguardex.activeAgents.finishSession",
115
143
  "when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
116
144
  "group": "inline"
117
145
  },
118
146
  {
119
- "command": "gitguardex.activeAgents.stopSession",
147
+ "command": "gitguardex.activeAgents.syncSession",
120
148
  "when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
121
149
  "group": "inline"
122
150
  },
123
151
  {
124
- "command": "gitguardex.activeAgents.openSessionDiff",
152
+ "command": "gitguardex.activeAgents.stopSession",
125
153
  "when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
126
154
  "group": "inline"
127
155
  }
@@ -76,6 +76,46 @@ function toPositiveInteger(value) {
76
76
  return Number.isInteger(normalized) && normalized > 0 ? normalized : null;
77
77
  }
78
78
 
79
+ function toBoundedInteger(value, min, max) {
80
+ const normalized = Number.parseInt(String(value ?? ''), 10);
81
+ if (!Number.isInteger(normalized) || normalized < min || normalized > max) {
82
+ return null;
83
+ }
84
+ return normalized;
85
+ }
86
+
87
+ function normalizeStringList(values) {
88
+ if (!Array.isArray(values)) {
89
+ return [];
90
+ }
91
+
92
+ return values
93
+ .map((value) => toNonEmptyString(value))
94
+ .filter(Boolean);
95
+ }
96
+
97
+ function normalizeSessionHealthPayload(input) {
98
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
99
+ return null;
100
+ }
101
+
102
+ const rawScores = input.scores && typeof input.scores === 'object' && !Array.isArray(input.scores)
103
+ ? input.scores
104
+ : null;
105
+ const score = toBoundedInteger(input.score ?? input.total ?? rawScores?.total, 0, 100);
106
+ if (score === null) {
107
+ return null;
108
+ }
109
+
110
+ return {
111
+ score,
112
+ label: toNonEmptyString(input.label),
113
+ primaryDriver: toNonEmptyString(input.primaryDriver),
114
+ secondaries: normalizeStringList(input.secondaries),
115
+ outputLine: toNonEmptyString(input.outputLine),
116
+ };
117
+ }
118
+
79
119
  function normalizeTaskMode(value) {
80
120
  const normalized = toNonEmptyString(value).toLowerCase();
81
121
  return normalized === 'caveman' || normalized === 'omx' ? normalized : '';
@@ -126,6 +166,17 @@ function normalizeRelativePath(value) {
126
166
  return toNonEmptyString(value).replace(/\\/g, '/').replace(/^\.\//, '');
127
167
  }
128
168
 
169
+ function normalizeProjectPath(value) {
170
+ const normalized = toNonEmptyString(value);
171
+ if (!normalized) {
172
+ return '';
173
+ }
174
+
175
+ return path.isAbsolute(normalized)
176
+ ? path.resolve(normalized)
177
+ : normalizeRelativePath(normalized);
178
+ }
179
+
129
180
  function readJsonFile(filePath) {
130
181
  try {
131
182
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
@@ -742,8 +793,12 @@ function buildSessionRecord(input) {
742
793
  repoRoot,
743
794
  branch,
744
795
  taskName: toNonEmptyString(input.taskName, 'task'),
745
- latestTaskPreview: '',
796
+ latestTaskPreview: toNonEmptyString(input.latestTaskPreview),
746
797
  agentName: toNonEmptyString(input.agentName, 'agent'),
798
+ projectName: toNonEmptyString(input.projectName),
799
+ projectPath: normalizeProjectPath(input.projectPath),
800
+ snapshotName: toNonEmptyString(input.snapshotName),
801
+ snapshotEmail: toNonEmptyString(input.snapshotEmail || input.email),
747
802
  worktreePath,
748
803
  pid,
749
804
  cliName: toNonEmptyString(input.cliName, 'codex'),
@@ -753,6 +808,7 @@ function buildSessionRecord(input) {
753
808
  startedAt: startedAt.toISOString(),
754
809
  lastHeartbeatAt: lastHeartbeatAt.toISOString(),
755
810
  state: normalizeAdvisoryState(input.state),
811
+ sessionHealth: normalizeSessionHealthPayload(input.sessionHealth || input.sessionSeverity),
756
812
  };
757
813
  }
758
814
 
@@ -792,8 +848,12 @@ function normalizeSessionRecord(input, options = {}) {
792
848
  repoRoot: path.resolve(repoRoot),
793
849
  branch,
794
850
  taskName: toNonEmptyString(input.taskName, 'task'),
795
- latestTaskPreview: '',
851
+ latestTaskPreview: toNonEmptyString(input.latestTaskPreview),
796
852
  agentName: toNonEmptyString(input.agentName, 'agent'),
853
+ projectName: toNonEmptyString(input.projectName),
854
+ projectPath: normalizeProjectPath(input.projectPath),
855
+ snapshotName: toNonEmptyString(input.snapshotName),
856
+ snapshotEmail: toNonEmptyString(input.snapshotEmail || input.email),
797
857
  worktreePath: path.resolve(worktreePath),
798
858
  pid,
799
859
  cliName: toNonEmptyString(input.cliName, 'codex'),
@@ -813,6 +873,7 @@ function normalizeSessionRecord(input, options = {}) {
813
873
  lockSnapshotCount: 0,
814
874
  lockSessionCount: 0,
815
875
  collaboration: false,
876
+ sessionHealth: normalizeSessionHealthPayload(input.sessionHealth || input.sessionSeverity),
816
877
  };
817
878
  }
818
879
 
@@ -867,6 +928,23 @@ function deriveAgentNameFromBranch(branch) {
867
928
  return 'agent';
868
929
  }
869
930
 
931
+ function isManagedAgentBranch(branch) {
932
+ return toNonEmptyString(branch).startsWith('agent/');
933
+ }
934
+
935
+ function deriveManagedWorktreeStartedAt(worktreePath, now = Date.now()) {
936
+ try {
937
+ const stats = fs.statSync(worktreePath);
938
+ if (Number.isFinite(stats.mtimeMs)) {
939
+ return new Date(stats.mtimeMs).toISOString();
940
+ }
941
+ } catch (_error) {
942
+ // Directory mtime is best-effort context only; fall back to current scan time.
943
+ }
944
+
945
+ return new Date(now).toISOString();
946
+ }
947
+
870
948
  function flattenTelemetrySnapshotSessions(lockPayload) {
871
949
  const flattened = [];
872
950
  const snapshots = Array.isArray(lockPayload?.snapshots) ? lockPayload.snapshots : [];
@@ -880,6 +958,9 @@ function flattenTelemetrySnapshotSessions(lockPayload) {
880
958
  projectPath: toNonEmptyString(session?.projectPath),
881
959
  snapshotName: toNonEmptyString(snapshot?.snapshotName),
882
960
  email: toNonEmptyString(snapshot?.email),
961
+ sessionHealth: normalizeSessionHealthPayload(
962
+ session?.sessionHealth || session?.sessionSeverity || snapshot?.sessionHealth || snapshot?.sessionSeverity,
963
+ ),
883
964
  });
884
965
  }
885
966
  }
@@ -898,7 +979,19 @@ function sortSessionsByTimestamp(sessions) {
898
979
  }
899
980
 
900
981
  function deriveLockTaskAnchor(entries, fallbackTaskName, fallbackTimestamp) {
901
- const sortedEntries = [...entries].sort((left, right) => {
982
+ const sortedEntries = sortTelemetryEntriesForAnchor(entries);
983
+
984
+ const latestEntry = sortedEntries[0] || null;
985
+ return {
986
+ taskName: latestEntry?.taskPreview || fallbackTaskName || 'task',
987
+ latestTaskPreview: latestEntry?.taskPreview || '',
988
+ timestamp: latestEntry?.taskUpdatedAt || fallbackTimestamp || '',
989
+ sessionHealth: latestEntry?.sessionHealth || null,
990
+ };
991
+ }
992
+
993
+ function sortTelemetryEntriesForAnchor(entries) {
994
+ return [...entries].sort((left, right) => {
902
995
  const timeDelta = Date.parse(right.taskUpdatedAt || '') - Date.parse(left.taskUpdatedAt || '');
903
996
  if (timeDelta !== 0) {
904
997
  return timeDelta;
@@ -908,12 +1001,23 @@ function deriveLockTaskAnchor(entries, fallbackTaskName, fallbackTimestamp) {
908
1001
  }
909
1002
  return (right.projectPath || '').localeCompare(left.projectPath || '');
910
1003
  });
1004
+ }
911
1005
 
912
- const latestEntry = sortedEntries[0] || null;
1006
+ function deriveLockSnapshotIdentity(entries) {
1007
+ const latestEntry = sortTelemetryEntriesForAnchor(entries)
1008
+ .find((entry) => entry?.snapshotName || entry?.email) || null;
913
1009
  return {
914
- taskName: latestEntry?.taskPreview || fallbackTaskName || 'task',
915
- latestTaskPreview: latestEntry?.taskPreview || '',
916
- timestamp: latestEntry?.taskUpdatedAt || fallbackTimestamp || '',
1010
+ snapshotName: toNonEmptyString(latestEntry?.snapshotName),
1011
+ snapshotEmail: toNonEmptyString(latestEntry?.email),
1012
+ };
1013
+ }
1014
+
1015
+ function deriveLockProjectMetadata(entries) {
1016
+ const latestEntry = sortTelemetryEntriesForAnchor(entries)
1017
+ .find((entry) => entry?.projectPath || entry?.projectName) || null;
1018
+ return {
1019
+ projectName: toNonEmptyString(latestEntry?.projectName),
1020
+ projectPath: normalizeProjectPath(latestEntry?.projectPath),
917
1021
  };
918
1022
  }
919
1023
 
@@ -927,6 +1031,8 @@ function buildWorktreeLockSession(repoRoot, worktreePath, lockPayload, options =
927
1031
  : `agent/telemetry/${path.basename(worktreePath)}`;
928
1032
  const label = deriveSessionLabel(effectiveBranch, worktreePath);
929
1033
  const taskAnchor = deriveLockTaskAnchor(telemetryEntries, label, telemetryUpdatedAt);
1034
+ const snapshotIdentity = deriveLockSnapshotIdentity(telemetryEntries);
1035
+ const projectMetadata = deriveLockProjectMetadata(telemetryEntries);
930
1036
  const startedAt = taskAnchor.timestamp || telemetryUpdatedAt || new Date(now).toISOString();
931
1037
 
932
1038
  const session = {
@@ -936,6 +1042,10 @@ function buildWorktreeLockSession(repoRoot, worktreePath, lockPayload, options =
936
1042
  taskName: taskAnchor.taskName,
937
1043
  latestTaskPreview: taskAnchor.latestTaskPreview,
938
1044
  agentName: deriveAgentNameFromBranch(effectiveBranch),
1045
+ projectName: projectMetadata.projectName,
1046
+ projectPath: projectMetadata.projectPath,
1047
+ snapshotName: snapshotIdentity.snapshotName,
1048
+ snapshotEmail: snapshotIdentity.snapshotEmail,
939
1049
  worktreePath: path.resolve(worktreePath),
940
1050
  pid: null,
941
1051
  cliName: 'codex',
@@ -955,6 +1065,56 @@ function buildWorktreeLockSession(repoRoot, worktreePath, lockPayload, options =
955
1065
  lockSnapshotCount: toPositiveInteger(lockPayload?.snapshotCount) || 0,
956
1066
  lockSessionCount: toPositiveInteger(lockPayload?.sessionCount) || telemetryEntries.length,
957
1067
  collaboration: Boolean(lockPayload?.collaboration),
1068
+ sessionHealth: taskAnchor.sessionHealth || normalizeSessionHealthPayload(
1069
+ lockPayload?.sessionHealth || lockPayload?.sessionSeverity,
1070
+ ),
1071
+ };
1072
+
1073
+ session.elapsedLabel = formatElapsedFrom(session.startedAt, now);
1074
+ Object.assign(session, deriveSessionActivity(session, { now }));
1075
+ return session;
1076
+ }
1077
+
1078
+ function buildManagedWorktreeSession(repoRoot, worktreePath, options = {}) {
1079
+ const now = options.now || Date.now();
1080
+ const branch = readWorktreeBranch(worktreePath);
1081
+ if (!branch || branch === 'HEAD' || !isManagedAgentBranch(branch)) {
1082
+ return null;
1083
+ }
1084
+
1085
+ const label = deriveSessionLabel(branch, worktreePath);
1086
+ const startedAt = deriveManagedWorktreeStartedAt(worktreePath, now);
1087
+ const session = {
1088
+ schemaVersion: SESSION_SCHEMA_VERSION,
1089
+ repoRoot: path.resolve(repoRoot),
1090
+ branch,
1091
+ taskName: label,
1092
+ latestTaskPreview: '',
1093
+ agentName: deriveAgentNameFromBranch(branch),
1094
+ projectName: '',
1095
+ projectPath: '',
1096
+ snapshotName: '',
1097
+ snapshotEmail: '',
1098
+ worktreePath: path.resolve(worktreePath),
1099
+ pid: null,
1100
+ cliName: 'gx',
1101
+ taskMode: '',
1102
+ openspecTier: '',
1103
+ taskRoutingReason: '',
1104
+ startedAt,
1105
+ lastHeartbeatAt: '',
1106
+ state: '',
1107
+ filePath: path.join(worktreePath, '.git'),
1108
+ label,
1109
+ changedPaths: [],
1110
+ worktreeChangedPaths: [],
1111
+ sourceKind: 'managed-worktree',
1112
+ telemetryUpdatedAt: '',
1113
+ telemetrySource: 'managed-worktree',
1114
+ lockSnapshotCount: 0,
1115
+ lockSessionCount: 0,
1116
+ collaboration: false,
1117
+ sessionHealth: null,
958
1118
  };
959
1119
 
960
1120
  session.elapsedLabel = formatElapsedFrom(session.startedAt, now);
@@ -1004,6 +1164,48 @@ function readWorktreeLockSessions(repoRoot, options = {}) {
1004
1164
  return sortSessionsByTimestamp(sessions);
1005
1165
  }
1006
1166
 
1167
+ function readManagedWorktreeSessions(repoRoot, options = {}) {
1168
+ const lockSessions = readWorktreeLockSessions(repoRoot, options);
1169
+ const lockSessionsByWorktree = new Map(
1170
+ lockSessions.map((session) => [path.resolve(session.worktreePath), session]),
1171
+ );
1172
+ const sessions = [];
1173
+
1174
+ for (const managedRoot of resolveManagedWorktreeRoots(repoRoot)) {
1175
+ if (!fs.existsSync(managedRoot)) {
1176
+ continue;
1177
+ }
1178
+
1179
+ let entries;
1180
+ try {
1181
+ entries = fs.readdirSync(managedRoot, { withFileTypes: true });
1182
+ } catch (_error) {
1183
+ continue;
1184
+ }
1185
+
1186
+ for (const entry of entries) {
1187
+ if (!entry.isDirectory()) {
1188
+ continue;
1189
+ }
1190
+
1191
+ const worktreePath = path.join(managedRoot, entry.name);
1192
+ const worktreeKey = path.resolve(worktreePath);
1193
+ const lockSession = lockSessionsByWorktree.get(worktreeKey);
1194
+ if (lockSession) {
1195
+ sessions.push(lockSession);
1196
+ continue;
1197
+ }
1198
+
1199
+ const managedSession = buildManagedWorktreeSession(repoRoot, worktreePath, options);
1200
+ if (managedSession) {
1201
+ sessions.push(managedSession);
1202
+ }
1203
+ }
1204
+ }
1205
+
1206
+ return sortSessionsByTimestamp(sessions);
1207
+ }
1208
+
1007
1209
  function mergeSessionSources(primarySessions, lockSessions) {
1008
1210
  const lockSessionsByWorktree = new Map(
1009
1211
  lockSessions.map((session) => [path.resolve(session.worktreePath), session]),
@@ -1019,6 +1221,21 @@ function mergeSessionSources(primarySessions, lockSessions) {
1019
1221
  }
1020
1222
  if (lockSession) {
1021
1223
  consumedLockWorktrees.add(worktreeKey);
1224
+ merged.push({
1225
+ ...session,
1226
+ latestTaskPreview: session.latestTaskPreview || lockSession.latestTaskPreview,
1227
+ projectName: session.projectName || lockSession.projectName,
1228
+ projectPath: session.projectPath || lockSession.projectPath,
1229
+ snapshotName: session.snapshotName || lockSession.snapshotName,
1230
+ snapshotEmail: session.snapshotEmail || lockSession.snapshotEmail,
1231
+ telemetryUpdatedAt: session.telemetryUpdatedAt || lockSession.telemetryUpdatedAt,
1232
+ telemetrySource: session.telemetrySource || lockSession.telemetrySource,
1233
+ lockSnapshotCount: session.lockSnapshotCount || lockSession.lockSnapshotCount,
1234
+ lockSessionCount: session.lockSessionCount || lockSession.lockSessionCount,
1235
+ collaboration: session.collaboration || lockSession.collaboration,
1236
+ sessionHealth: session.sessionHealth || lockSession.sessionHealth,
1237
+ });
1238
+ continue;
1022
1239
  }
1023
1240
  merged.push(session);
1024
1241
  }
@@ -1061,7 +1278,7 @@ function readActiveSessions(repoRoot, options = {}) {
1061
1278
 
1062
1279
  return mergeSessionSources(
1063
1280
  sortSessionsByTimestamp(sessionFileSessions),
1064
- readWorktreeLockSessions(repoRoot, { now }),
1281
+ readManagedWorktreeSessions(repoRoot, { now }),
1065
1282
  );
1066
1283
  }
1067
1284
 
@@ -1096,6 +1313,7 @@ module.exports = {
1096
1313
  parseRepoChangeLine,
1097
1314
  previewChangedPaths,
1098
1315
  readActiveSessions,
1316
+ readManagedWorktreeSessions,
1099
1317
  readWorktreeLockSessions,
1100
1318
  readRepoChanges,
1101
1319
  deriveRepoChangeStatus,