@agents-tower/core 0.1.0

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.
Files changed (178) hide show
  1. package/dist/adapters/claude.d.ts +2 -0
  2. package/dist/adapters/claude.js +43 -0
  3. package/dist/adapters/claude.js.map +1 -0
  4. package/dist/adapters/codex-cloud.d.ts +4 -0
  5. package/dist/adapters/codex-cloud.js +80 -0
  6. package/dist/adapters/codex-cloud.js.map +1 -0
  7. package/dist/adapters/codex-local.d.ts +17 -0
  8. package/dist/adapters/codex-local.js +180 -0
  9. package/dist/adapters/codex-local.js.map +1 -0
  10. package/dist/adapters/cursor-cloud.d.ts +2 -0
  11. package/dist/adapters/cursor-cloud.js +54 -0
  12. package/dist/adapters/cursor-cloud.js.map +1 -0
  13. package/dist/adapters/cursor-local.d.ts +2 -0
  14. package/dist/adapters/cursor-local.js +37 -0
  15. package/dist/adapters/cursor-local.js.map +1 -0
  16. package/dist/adapters/helpers.d.ts +15 -0
  17. package/dist/adapters/helpers.js +33 -0
  18. package/dist/adapters/helpers.js.map +1 -0
  19. package/dist/adapters/hermes.d.ts +2 -0
  20. package/dist/adapters/hermes.js +41 -0
  21. package/dist/adapters/hermes.js.map +1 -0
  22. package/dist/adapters/index.d.ts +2 -0
  23. package/dist/adapters/index.js +37 -0
  24. package/dist/adapters/index.js.map +1 -0
  25. package/dist/adapters/openclaw.d.ts +2 -0
  26. package/dist/adapters/openclaw.js +41 -0
  27. package/dist/adapters/openclaw.js.map +1 -0
  28. package/dist/adapters/presence.d.ts +2 -0
  29. package/dist/adapters/presence.js +36 -0
  30. package/dist/adapters/presence.js.map +1 -0
  31. package/dist/adapters/static-source.d.ts +15 -0
  32. package/dist/adapters/static-source.js +41 -0
  33. package/dist/adapters/static-source.js.map +1 -0
  34. package/dist/adapters/types.d.ts +44 -0
  35. package/dist/adapters/types.js +3 -0
  36. package/dist/adapters/types.js.map +1 -0
  37. package/dist/app-server.d.ts +94 -0
  38. package/dist/app-server.js +382 -0
  39. package/dist/app-server.js.map +1 -0
  40. package/dist/app-settings.d.ts +52 -0
  41. package/dist/app-settings.js +268 -0
  42. package/dist/app-settings.js.map +1 -0
  43. package/dist/appearance.d.ts +6 -0
  44. package/dist/appearance.js +84 -0
  45. package/dist/appearance.js.map +1 -0
  46. package/dist/aseprite.d.ts +4 -0
  47. package/dist/aseprite.js +42 -0
  48. package/dist/aseprite.js.map +1 -0
  49. package/dist/claude-agent-sdk.d.ts +44 -0
  50. package/dist/claude-agent-sdk.js +347 -0
  51. package/dist/claude-agent-sdk.js.map +1 -0
  52. package/dist/claude-home-cache.d.ts +61 -0
  53. package/dist/claude-home-cache.js +438 -0
  54. package/dist/claude-home-cache.js.map +1 -0
  55. package/dist/claude.d.ts +154 -0
  56. package/dist/claude.js +3495 -0
  57. package/dist/claude.js.map +1 -0
  58. package/dist/cloud.d.ts +2 -0
  59. package/dist/cloud.js +25 -0
  60. package/dist/cloud.js.map +1 -0
  61. package/dist/codex-command.d.ts +25 -0
  62. package/dist/codex-command.js +235 -0
  63. package/dist/codex-command.js.map +1 -0
  64. package/dist/codex-session-files.d.ts +8 -0
  65. package/dist/codex-session-files.js +509 -0
  66. package/dist/codex-session-files.js.map +1 -0
  67. package/dist/codex-thread-query.d.ts +14 -0
  68. package/dist/codex-thread-query.js +82 -0
  69. package/dist/codex-thread-query.js.map +1 -0
  70. package/dist/cursor-cloud-data.d.ts +34 -0
  71. package/dist/cursor-cloud-data.js +344 -0
  72. package/dist/cursor-cloud-data.js.map +1 -0
  73. package/dist/cursor-lib/local-discovery.d.ts +17 -0
  74. package/dist/cursor-lib/local-discovery.js +251 -0
  75. package/dist/cursor-lib/local-discovery.js.map +1 -0
  76. package/dist/cursor-lib/shared.d.ts +5 -0
  77. package/dist/cursor-lib/shared.js +88 -0
  78. package/dist/cursor-lib/shared.js.map +1 -0
  79. package/dist/cursor.d.ts +8 -0
  80. package/dist/cursor.js +1584 -0
  81. package/dist/cursor.js.map +1 -0
  82. package/dist/domain/codex-turn-semantics.d.ts +4 -0
  83. package/dist/domain/codex-turn-semantics.js +49 -0
  84. package/dist/domain/codex-turn-semantics.js.map +1 -0
  85. package/dist/domain/workload-policy.d.ts +8 -0
  86. package/dist/domain/workload-policy.js +268 -0
  87. package/dist/domain/workload-policy.js.map +1 -0
  88. package/dist/domain/workspace-activity.d.ts +10 -0
  89. package/dist/domain/workspace-activity.js +372 -0
  90. package/dist/domain/workspace-activity.js.map +1 -0
  91. package/dist/goal.d.ts +11 -0
  92. package/dist/goal.js +76 -0
  93. package/dist/goal.js.map +1 -0
  94. package/dist/hermes-hook-install.d.ts +12 -0
  95. package/dist/hermes-hook-install.js +335 -0
  96. package/dist/hermes-hook-install.js.map +1 -0
  97. package/dist/hermes.d.ts +97 -0
  98. package/dist/hermes.js +2446 -0
  99. package/dist/hermes.js.map +1 -0
  100. package/dist/index.d.ts +28 -0
  101. package/dist/index.js +47 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/live-monitor-lib/events.d.ts +43 -0
  104. package/dist/live-monitor-lib/events.js +1463 -0
  105. package/dist/live-monitor-lib/events.js.map +1 -0
  106. package/dist/live-monitor-lib/rollout-hooks.d.ts +13 -0
  107. package/dist/live-monitor-lib/rollout-hooks.js +253 -0
  108. package/dist/live-monitor-lib/rollout-hooks.js.map +1 -0
  109. package/dist/live-monitor.d.ts +102 -0
  110. package/dist/live-monitor.js +1461 -0
  111. package/dist/live-monitor.js.map +1 -0
  112. package/dist/local-thread-selection.d.ts +2 -0
  113. package/dist/local-thread-selection.js +58 -0
  114. package/dist/local-thread-selection.js.map +1 -0
  115. package/dist/openclaw.d.ts +41 -0
  116. package/dist/openclaw.js +715 -0
  117. package/dist/openclaw.js.map +1 -0
  118. package/dist/presence.d.ts +9 -0
  119. package/dist/presence.js +108 -0
  120. package/dist/presence.js.map +1 -0
  121. package/dist/project-identity.d.ts +7 -0
  122. package/dist/project-identity.js +162 -0
  123. package/dist/project-identity.js.map +1 -0
  124. package/dist/project-paths.d.ts +23 -0
  125. package/dist/project-paths.js +394 -0
  126. package/dist/project-paths.js.map +1 -0
  127. package/dist/project-storage.d.ts +7 -0
  128. package/dist/project-storage.js +79 -0
  129. package/dist/project-storage.js.map +1 -0
  130. package/dist/room-config.d.ts +21 -0
  131. package/dist/room-config.js +230 -0
  132. package/dist/room-config.js.map +1 -0
  133. package/dist/services/project-discovery.d.ts +1 -0
  134. package/dist/services/project-discovery.js +16 -0
  135. package/dist/services/project-discovery.js.map +1 -0
  136. package/dist/services/project-live-monitor.d.ts +1 -0
  137. package/dist/services/project-live-monitor.js +6 -0
  138. package/dist/services/project-live-monitor.js.map +1 -0
  139. package/dist/services/project-snapshot-coordinator.d.ts +49 -0
  140. package/dist/services/project-snapshot-coordinator.js +152 -0
  141. package/dist/services/project-snapshot-coordinator.js.map +1 -0
  142. package/dist/services/refresh-scheduler.d.ts +8 -0
  143. package/dist/services/refresh-scheduler.js +44 -0
  144. package/dist/services/refresh-scheduler.js.map +1 -0
  145. package/dist/services/snapshot-assembler.d.ts +8 -0
  146. package/dist/services/snapshot-assembler.js +167 -0
  147. package/dist/services/snapshot-assembler.js.map +1 -0
  148. package/dist/services/thread-store.d.ts +4 -0
  149. package/dist/services/thread-store.js +8 -0
  150. package/dist/services/thread-store.js.map +1 -0
  151. package/dist/snapshot-lib/activity-summary.d.ts +3 -0
  152. package/dist/snapshot-lib/activity-summary.js +220 -0
  153. package/dist/snapshot-lib/activity-summary.js.map +1 -0
  154. package/dist/snapshot-lib/dashboard-builder.d.ts +21 -0
  155. package/dist/snapshot-lib/dashboard-builder.js +146 -0
  156. package/dist/snapshot-lib/dashboard-builder.js.map +1 -0
  157. package/dist/snapshot-lib/thread-summary.d.ts +27 -0
  158. package/dist/snapshot-lib/thread-summary.js +953 -0
  159. package/dist/snapshot-lib/thread-summary.js.map +1 -0
  160. package/dist/snapshot.d.ts +2 -0
  161. package/dist/snapshot.js +21 -0
  162. package/dist/snapshot.js.map +1 -0
  163. package/dist/types.d.ts +368 -0
  164. package/dist/types.js +3 -0
  165. package/dist/types.js.map +1 -0
  166. package/dist/utils/json.d.ts +4 -0
  167. package/dist/utils/json.js +29 -0
  168. package/dist/utils/json.js.map +1 -0
  169. package/dist/utils/text.d.ts +3 -0
  170. package/dist/utils/text.js +26 -0
  171. package/dist/utils/text.js.map +1 -0
  172. package/dist/web-search.d.ts +1 -0
  173. package/dist/web-search.js +52 -0
  174. package/dist/web-search.js.map +1 -0
  175. package/dist/workload.d.ts +1 -0
  176. package/dist/workload.js +12 -0
  177. package/dist/workload.js.map +1 -0
  178. package/package.json +19 -0
package/dist/cursor.js ADDED
@@ -0,0 +1,1584 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadCursorCloudProjectSnapshotData = exports.loadCursorAgents = exports.describeCursorAgentAvailability = exports.cursorApiKeyConfigured = exports.cursorAgentMatchesRepository = exports.sourceKindFromModel = exports.normalizeRepositoryUrl = exports.gitRemoteOriginUrl = exports.cursorStatusToActivityState = void 0;
4
+ exports.cursorHooksFilePath = cursorHooksFilePath;
5
+ exports.loadCursorLocalProjectSnapshotData = loadCursorLocalProjectSnapshotData;
6
+ const node_child_process_1 = require("node:child_process");
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_path_1 = require("node:path");
9
+ const node_sqlite_1 = require("node:sqlite");
10
+ const node_util_1 = require("node:util");
11
+ const appearance_1 = require("./appearance");
12
+ const local_discovery_1 = require("./cursor-lib/local-discovery");
13
+ const shared_1 = require("./cursor-lib/shared");
14
+ var shared_2 = require("./cursor-lib/shared");
15
+ Object.defineProperty(exports, "cursorStatusToActivityState", { enumerable: true, get: function () { return shared_2.cursorStatusToActivityState; } });
16
+ Object.defineProperty(exports, "gitRemoteOriginUrl", { enumerable: true, get: function () { return shared_2.gitRemoteOriginUrl; } });
17
+ Object.defineProperty(exports, "normalizeRepositoryUrl", { enumerable: true, get: function () { return shared_2.normalizeRepositoryUrl; } });
18
+ Object.defineProperty(exports, "sourceKindFromModel", { enumerable: true, get: function () { return shared_2.sourceKindFromModel; } });
19
+ var cursor_cloud_data_1 = require("./cursor-cloud-data");
20
+ Object.defineProperty(exports, "cursorAgentMatchesRepository", { enumerable: true, get: function () { return cursor_cloud_data_1.cursorAgentMatchesRepository; } });
21
+ Object.defineProperty(exports, "cursorApiKeyConfigured", { enumerable: true, get: function () { return cursor_cloud_data_1.cursorApiKeyConfigured; } });
22
+ Object.defineProperty(exports, "describeCursorAgentAvailability", { enumerable: true, get: function () { return cursor_cloud_data_1.describeCursorAgentAvailability; } });
23
+ Object.defineProperty(exports, "loadCursorAgents", { enumerable: true, get: function () { return cursor_cloud_data_1.loadCursorAgents; } });
24
+ Object.defineProperty(exports, "loadCursorCloudProjectSnapshotData", { enumerable: true, get: function () { return cursor_cloud_data_1.loadCursorCloudProjectSnapshotData; } });
25
+ const project_storage_1 = require("./project-storage");
26
+ const project_paths_1 = require("./project-paths");
27
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
28
+ const CURSOR_LOCAL_ACTIVE_WINDOW_MS = 2 * 60 * 1000;
29
+ const CURSOR_LOCAL_DONE_WINDOW_MS = 15 * 60 * 1000;
30
+ const CURSOR_LOCAL_EVENT_WINDOW_MS = 2 * 60 * 1000;
31
+ const CURSOR_HOOK_ACTIVE_WINDOW_MS = 2 * 60 * 1000;
32
+ const CURSOR_HOOK_DONE_WINDOW_MS = 15 * 60 * 1000;
33
+ const CURSOR_HOOK_EVENT_WINDOW_MS = 5 * 60 * 1000;
34
+ const CURSOR_HOOK_FUTURE_TOLERANCE_MS = 30 * 1000;
35
+ function pathExists(path) {
36
+ return (0, promises_1.stat)(path).then(() => true).catch(() => false);
37
+ }
38
+ function cursorComposerTimestamp(composer) {
39
+ return Math.max(composer.lastUpdatedAt ?? 0, composer.createdAt ?? 0, composer.activeBranch?.lastInteractionAt ?? 0, ...(Array.isArray(composer.branches)
40
+ ? composer.branches.map((branch) => branch.lastInteractionAt ?? 0)
41
+ : [0]));
42
+ }
43
+ function extractBalancedJsonSegment(text, startIndex) {
44
+ const opening = text[startIndex];
45
+ const closing = opening === "[" ? "]" : opening === "{" ? "}" : null;
46
+ if (!closing) {
47
+ return null;
48
+ }
49
+ let depth = 0;
50
+ let inString = false;
51
+ let escaping = false;
52
+ for (let index = startIndex; index < text.length; index += 1) {
53
+ const char = text[index];
54
+ if (inString) {
55
+ if (escaping) {
56
+ escaping = false;
57
+ continue;
58
+ }
59
+ if (char === "\\") {
60
+ escaping = true;
61
+ continue;
62
+ }
63
+ if (char === "\"") {
64
+ inString = false;
65
+ }
66
+ continue;
67
+ }
68
+ if (char === "\"") {
69
+ inString = true;
70
+ continue;
71
+ }
72
+ if (char === opening) {
73
+ depth += 1;
74
+ continue;
75
+ }
76
+ if (char === closing) {
77
+ depth -= 1;
78
+ if (depth === 0) {
79
+ return text.slice(startIndex, index + 1);
80
+ }
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function extractAsciiStrings(raw, minimumLength = 4) {
86
+ const strings = [];
87
+ let current = "";
88
+ for (const byte of raw.values()) {
89
+ if (byte >= 32 && byte <= 126) {
90
+ current += String.fromCharCode(byte);
91
+ continue;
92
+ }
93
+ if (current.length >= minimumLength) {
94
+ strings.push(current);
95
+ }
96
+ current = "";
97
+ }
98
+ if (current.length >= minimumLength) {
99
+ strings.push(current);
100
+ }
101
+ return strings;
102
+ }
103
+ function extractEmbeddedJsonTextCandidates(text, key, opening) {
104
+ const candidates = [];
105
+ let fromIndex = 0;
106
+ while (fromIndex < text.length) {
107
+ const keyIndex = text.indexOf(key, fromIndex);
108
+ if (keyIndex < 0) {
109
+ break;
110
+ }
111
+ let jsonStart = keyIndex + key.length;
112
+ const maxLookahead = Math.min(text.length, jsonStart + 12);
113
+ while (jsonStart < maxLookahead && text[jsonStart] !== opening) {
114
+ jsonStart += 1;
115
+ }
116
+ fromIndex = keyIndex + key.length;
117
+ if (text[jsonStart] !== opening) {
118
+ continue;
119
+ }
120
+ const parsed = extractBalancedJsonSegment(text, jsonStart);
121
+ if (!parsed) {
122
+ continue;
123
+ }
124
+ try {
125
+ candidates.push({
126
+ value: JSON.parse(parsed),
127
+ raw: parsed
128
+ });
129
+ }
130
+ catch {
131
+ continue;
132
+ }
133
+ }
134
+ return candidates;
135
+ }
136
+ function extractEmbeddedJsonCandidates(raw, key, opening) {
137
+ const candidates = extractAsciiStrings(raw)
138
+ .flatMap((text) => extractEmbeddedJsonTextCandidates(text, key, opening));
139
+ if (candidates.length > 0) {
140
+ return candidates;
141
+ }
142
+ const rawCandidates = [];
143
+ const keyBytes = Buffer.from(key, "utf8");
144
+ const openingCode = opening.charCodeAt(0);
145
+ const closingCode = (opening === "[" ? "]" : "}").charCodeAt(0);
146
+ let fromIndex = 0;
147
+ while (fromIndex < raw.length) {
148
+ const keyIndex = raw.indexOf(keyBytes, fromIndex);
149
+ if (keyIndex < 0) {
150
+ break;
151
+ }
152
+ let jsonStart = keyIndex + keyBytes.length;
153
+ const maxLookahead = Math.min(raw.length, jsonStart + 64);
154
+ while (jsonStart < maxLookahead && raw[jsonStart] !== openingCode) {
155
+ jsonStart += 1;
156
+ }
157
+ fromIndex = keyIndex + keyBytes.length;
158
+ if (raw[jsonStart] !== openingCode) {
159
+ continue;
160
+ }
161
+ let depth = 0;
162
+ let inString = false;
163
+ let escaping = false;
164
+ let parsed = "";
165
+ let completed = false;
166
+ for (let index = jsonStart; index < raw.length; index += 1) {
167
+ const byte = raw[index];
168
+ if (byte < 32 || byte > 126) {
169
+ continue;
170
+ }
171
+ const char = String.fromCharCode(byte);
172
+ parsed += char;
173
+ if (inString) {
174
+ if (escaping) {
175
+ escaping = false;
176
+ continue;
177
+ }
178
+ if (char === "\\") {
179
+ escaping = true;
180
+ continue;
181
+ }
182
+ if (char === "\"") {
183
+ inString = false;
184
+ }
185
+ continue;
186
+ }
187
+ if (char === "\"") {
188
+ inString = true;
189
+ continue;
190
+ }
191
+ if (byte === openingCode) {
192
+ depth += 1;
193
+ continue;
194
+ }
195
+ if (byte === closingCode) {
196
+ depth -= 1;
197
+ if (depth === 0) {
198
+ completed = true;
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ if (!completed || parsed.length === 0) {
204
+ continue;
205
+ }
206
+ try {
207
+ rawCandidates.push({
208
+ value: JSON.parse(parsed),
209
+ raw: parsed
210
+ });
211
+ }
212
+ catch {
213
+ continue;
214
+ }
215
+ }
216
+ return rawCandidates;
217
+ }
218
+ function pickBestCandidate(candidates, score) {
219
+ return candidates
220
+ .sort((left, right) => {
221
+ const scoreDelta = score(right.value) - score(left.value);
222
+ if (scoreDelta !== 0) {
223
+ return scoreDelta;
224
+ }
225
+ return right.raw.length - left.raw.length;
226
+ })[0]?.value ?? null;
227
+ }
228
+ function parseCursorLocalWorkspaceState(raw) {
229
+ const composerData = pickBestCandidate(extractEmbeddedJsonCandidates(raw, "composer.composerData", "{"), (value) => Math.max(...(Array.isArray(value.allComposers) ? value.allComposers.map((composer) => cursorComposerTimestamp(composer)) : [0])));
230
+ const prompts = pickBestCandidate(extractEmbeddedJsonCandidates(raw, "aiService.prompts", "["), (value) => Array.isArray(value) ? value.length : 0) ?? [];
231
+ const generations = pickBestCandidate(extractEmbeddedJsonCandidates(raw, "aiService.generations", "["), (value) => Math.max(...(Array.isArray(value) ? value.map((entry) => entry.unixMs ?? 0) : [0]))) ?? [];
232
+ const backgroundComposer = pickBestCandidate(extractEmbeddedJsonCandidates(raw, "workbench.backgroundComposer.workspacePersistentData", "{"), (value) => `${value.cachedSelectedGitState?.ref ?? ""}${value.cachedSelectedGitState?.continueRef ?? ""}`.length);
233
+ return {
234
+ composerData,
235
+ prompts,
236
+ generations,
237
+ backgroundComposer
238
+ };
239
+ }
240
+ function parseCursorJsonValue(input) {
241
+ if (typeof input !== "string" || input.trim().length === 0) {
242
+ return null;
243
+ }
244
+ try {
245
+ return JSON.parse(input);
246
+ }
247
+ catch {
248
+ return null;
249
+ }
250
+ }
251
+ function loadCursorLocalWorkspaceStateFromSqlite(filePath) {
252
+ let database = null;
253
+ try {
254
+ database = new node_sqlite_1.DatabaseSync(filePath, { readOnly: true });
255
+ const readValue = (key) => {
256
+ const row = database
257
+ ?.prepare("select value from ItemTable where key = ?")
258
+ .get(key);
259
+ return typeof row?.value === "string" ? row.value : null;
260
+ };
261
+ const composerData = parseCursorJsonValue(readValue("composer.composerData"));
262
+ const prompts = parseCursorJsonValue(readValue("aiService.prompts")) ?? [];
263
+ const generations = parseCursorJsonValue(readValue("aiService.generations")) ?? [];
264
+ const backgroundComposer = parseCursorJsonValue(readValue("workbench.backgroundComposer.workspacePersistentData"));
265
+ if (!composerData && prompts.length === 0 && generations.length === 0 && !backgroundComposer) {
266
+ return null;
267
+ }
268
+ return {
269
+ composerData,
270
+ prompts,
271
+ generations,
272
+ backgroundComposer
273
+ };
274
+ }
275
+ catch {
276
+ return null;
277
+ }
278
+ finally {
279
+ database?.close();
280
+ }
281
+ }
282
+ async function loadCursorLocalWorkspaceState(workspace) {
283
+ for (const filePath of [workspace.stateFilePath, workspace.backupFilePath]) {
284
+ if (!await pathExists(filePath)) {
285
+ continue;
286
+ }
287
+ try {
288
+ const fromSqlite = loadCursorLocalWorkspaceStateFromSqlite(filePath);
289
+ if (fromSqlite?.composerData || fromSqlite?.prompts.length || fromSqlite?.generations.length) {
290
+ return fromSqlite;
291
+ }
292
+ const raw = await (0, promises_1.readFile)(filePath);
293
+ const parsed = parseCursorLocalWorkspaceState(raw);
294
+ if (parsed.composerData || parsed.prompts.length > 0 || parsed.generations.length > 0) {
295
+ return parsed;
296
+ }
297
+ }
298
+ catch {
299
+ continue;
300
+ }
301
+ }
302
+ return null;
303
+ }
304
+ function orderedCursorComposers(composerData) {
305
+ if (!composerData || !Array.isArray(composerData.allComposers)) {
306
+ return [];
307
+ }
308
+ const activeComposers = composerData.allComposers.filter((composer) => composer && composer.isArchived !== true);
309
+ const byId = new Map(activeComposers.map((composer) => [composer.composerId, composer]));
310
+ const orderedIds = [
311
+ ...(Array.isArray(composerData.lastFocusedComposerIds) ? composerData.lastFocusedComposerIds : []),
312
+ ...(Array.isArray(composerData.selectedComposerIds) ? composerData.selectedComposerIds : []),
313
+ ...activeComposers
314
+ .slice()
315
+ .sort((left, right) => cursorComposerTimestamp(right) - cursorComposerTimestamp(left))
316
+ .map((composer) => composer.composerId)
317
+ ];
318
+ const ordered = [];
319
+ const seen = new Set();
320
+ for (const composerId of orderedIds) {
321
+ if (typeof composerId !== "string" || seen.has(composerId)) {
322
+ continue;
323
+ }
324
+ const composer = byId.get(composerId);
325
+ if (!composer) {
326
+ continue;
327
+ }
328
+ seen.add(composerId);
329
+ ordered.push(composer);
330
+ }
331
+ return ordered;
332
+ }
333
+ function cursorPrimaryComposerId(composerData, latestGenerationMs) {
334
+ const composers = orderedCursorComposers(composerData);
335
+ if (composers.length === 0) {
336
+ return null;
337
+ }
338
+ if (Number.isFinite(latestGenerationMs) && latestGenerationMs > 0) {
339
+ const matchedComposer = composers
340
+ .map((composer) => ({
341
+ composer,
342
+ distanceMs: Math.abs(cursorComposerTimestamp(composer) - latestGenerationMs)
343
+ }))
344
+ .sort((left, right) => left.distanceMs - right.distanceMs)[0] ?? null;
345
+ if (matchedComposer && matchedComposer.distanceMs <= CURSOR_LOCAL_ACTIVE_WINDOW_MS) {
346
+ return matchedComposer.composer.composerId;
347
+ }
348
+ }
349
+ return composers[0]?.composerId ?? null;
350
+ }
351
+ async function cursorAgentLoopState() {
352
+ const logsDir = await (0, local_discovery_1.cursorLogsDir)();
353
+ if (!logsDir) {
354
+ return { active: false, updatedAtMs: null };
355
+ }
356
+ const directories = await (0, promises_1.readdir)(logsDir, { withFileTypes: true }).catch(() => []);
357
+ const recentDirs = directories
358
+ .filter((entry) => entry.isDirectory())
359
+ .map((entry) => entry.name)
360
+ .sort()
361
+ .slice(-5)
362
+ .reverse();
363
+ let lastEvent = null;
364
+ const pattern = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}).*?\[PowerMainService\] (Started|Stopping) wakelock .*reason="agent-loop"/;
365
+ for (const dirName of recentDirs) {
366
+ const filePath = (0, node_path_1.join)(logsDir, dirName, "main.log");
367
+ if (!await pathExists(filePath)) {
368
+ continue;
369
+ }
370
+ const raw = await (0, promises_1.readFile)(filePath, "utf8").catch(() => "");
371
+ for (const line of raw.split(/\r?\n/)) {
372
+ const match = line.match(pattern);
373
+ if (!match) {
374
+ continue;
375
+ }
376
+ const updatedAtMs = Date.parse(match[1].replace(" ", "T") + "Z");
377
+ if (!Number.isFinite(updatedAtMs)) {
378
+ continue;
379
+ }
380
+ lastEvent = {
381
+ active: match[2] === "Started",
382
+ updatedAtMs
383
+ };
384
+ }
385
+ if (lastEvent) {
386
+ break;
387
+ }
388
+ }
389
+ return lastEvent ?? { active: false, updatedAtMs: null };
390
+ }
391
+ function cursorLocalPromptDetail(prompt, fallback) {
392
+ if (prompt && prompt.trim().length > 0) {
393
+ const normalized = prompt.replace(/\s+/g, " ").trim();
394
+ return normalized.length <= 88 ? normalized : `${normalized.slice(0, 87)}…`;
395
+ }
396
+ return fallback;
397
+ }
398
+ function cleanCursorTranscriptUserText(text) {
399
+ const normalized = text
400
+ .replace(/<\/?user_query>/gi, " ")
401
+ .replace(/\s+/g, " ")
402
+ .trim();
403
+ return normalized.length > 0 ? normalized : null;
404
+ }
405
+ function cleanCursorTranscriptAssistantText(text) {
406
+ const normalized = text.replace(/\s+/g, " ").trim();
407
+ return normalized.length > 0 ? normalized : null;
408
+ }
409
+ function cursorTranscriptContentText(role, content) {
410
+ if (!Array.isArray(content)) {
411
+ return null;
412
+ }
413
+ const joined = content
414
+ .filter((part) => part && part.type === "text" && typeof part.text === "string")
415
+ .map((part) => part.text ?? "")
416
+ .join(" ");
417
+ if (!joined.trim()) {
418
+ return null;
419
+ }
420
+ return role === "user"
421
+ ? cleanCursorTranscriptUserText(joined)
422
+ : cleanCursorTranscriptAssistantText(joined);
423
+ }
424
+ function cursorTranscriptToolState(toolName) {
425
+ const normalized = typeof toolName === "string" ? toolName.trim().toLowerCase() : "";
426
+ switch (normalized) {
427
+ case "edit":
428
+ case "editfile":
429
+ case "edit_file":
430
+ case "edit_file_v2":
431
+ case "strreplace":
432
+ case "write":
433
+ case "writefile":
434
+ case "applypatch":
435
+ case "apply_patch":
436
+ case "search_replace":
437
+ return "editing";
438
+ case "shell":
439
+ case "run_terminal_cmd":
440
+ case "run_terminal_command_v2":
441
+ return "running";
442
+ case "read":
443
+ case "readfile":
444
+ case "read_file":
445
+ case "read_file_v2":
446
+ case "grep":
447
+ case "glob":
448
+ case "list":
449
+ case "ls":
450
+ case "list_dir":
451
+ case "list_dir_v2":
452
+ case "ripgrep":
453
+ case "ripgrep_raw_search":
454
+ case "semantic_search_full":
455
+ case "codebase_search":
456
+ return "scanning";
457
+ case "task":
458
+ case "task_v2":
459
+ return "delegating";
460
+ case "todowrite":
461
+ case "create_plan":
462
+ return "planning";
463
+ default:
464
+ return null;
465
+ }
466
+ }
467
+ function cursorTranscriptLatestToolState(content) {
468
+ if (!Array.isArray(content)) {
469
+ return null;
470
+ }
471
+ for (let index = content.length - 1; index >= 0; index -= 1) {
472
+ const state = cursorTranscriptToolState(content[index]?.name);
473
+ if (state) {
474
+ return state;
475
+ }
476
+ }
477
+ return null;
478
+ }
479
+ async function summariseCursorTranscriptSession(file) {
480
+ const raw = await (0, promises_1.readFile)(file.filePath, "utf8").catch(() => null);
481
+ if (!raw) {
482
+ return null;
483
+ }
484
+ let initialPrompt = null;
485
+ let latestUserText = null;
486
+ let latestAssistantText = null;
487
+ let latestRole = null;
488
+ let latestToolState = null;
489
+ let model = null;
490
+ for (const line of raw.split(/\r?\n/)) {
491
+ if (!line.trim()) {
492
+ continue;
493
+ }
494
+ let parsed = null;
495
+ try {
496
+ parsed = JSON.parse(line);
497
+ }
498
+ catch {
499
+ continue;
500
+ }
501
+ if (!parsed) {
502
+ continue;
503
+ }
504
+ const role = parsed.role === "user" || parsed.role === "assistant" ? parsed.role : null;
505
+ if (!role) {
506
+ continue;
507
+ }
508
+ const content = Array.isArray(parsed.message?.content) ? parsed.message.content : [];
509
+ const text = cursorTranscriptContentText(role, content);
510
+ if (!model) {
511
+ const candidateModel = parsed.model ?? parsed.message?.model ?? null;
512
+ model = typeof candidateModel === "string" && candidateModel.trim().length > 0
513
+ ? candidateModel.trim()
514
+ : null;
515
+ }
516
+ if (role === "user") {
517
+ if (!initialPrompt && text) {
518
+ initialPrompt = text;
519
+ }
520
+ if (text) {
521
+ latestUserText = text;
522
+ latestRole = "user";
523
+ }
524
+ continue;
525
+ }
526
+ const toolState = cursorTranscriptLatestToolState(content);
527
+ if (text) {
528
+ latestAssistantText = text;
529
+ }
530
+ if (text || toolState) {
531
+ latestRole = "assistant";
532
+ }
533
+ if (toolState) {
534
+ latestToolState = toolState;
535
+ }
536
+ }
537
+ if (!initialPrompt && !latestUserText && !latestAssistantText) {
538
+ return null;
539
+ }
540
+ return {
541
+ sessionId: file.sessionId,
542
+ filePath: file.filePath,
543
+ createdAtMs: file.createdAtMs,
544
+ updatedAtMs: file.updatedAtMs,
545
+ initialPrompt,
546
+ latestUserText,
547
+ latestAssistantText,
548
+ latestRole,
549
+ latestToolState,
550
+ model
551
+ };
552
+ }
553
+ function cursorHooksDir(projectRoot) {
554
+ return (0, project_storage_1.getProjectStoragePath)(projectRoot, "cursor-hooks");
555
+ }
556
+ function cursorHooksFilePath(projectRoot, sessionId) {
557
+ return (0, node_path_1.join)(cursorHooksDir(projectRoot), `${sessionId}.jsonl`);
558
+ }
559
+ function cursorHookRecordTimestampMs(record, fallbackUpdatedAt) {
560
+ for (const key of ["timestamp", "created_at", "createdAt", "updated_at", "updatedAt"]) {
561
+ const value = record[key];
562
+ if (typeof value === "string") {
563
+ const parsed = Date.parse(value);
564
+ if (Number.isFinite(parsed)) {
565
+ if (parsed > fallbackUpdatedAt + CURSOR_HOOK_FUTURE_TOLERANCE_MS) {
566
+ return {
567
+ timestampMs: Math.max(0, fallbackUpdatedAt - CURSOR_HOOK_FUTURE_TOLERANCE_MS),
568
+ wasFutureClamped: true
569
+ };
570
+ }
571
+ return {
572
+ timestampMs: parsed,
573
+ wasFutureClamped: false
574
+ };
575
+ }
576
+ }
577
+ }
578
+ return {
579
+ timestampMs: fallbackUpdatedAt,
580
+ wasFutureClamped: false
581
+ };
582
+ }
583
+ function cursorHookSessionId(record) {
584
+ for (const key of ["conversation_id", "session_id", "parent_conversation_id"]) {
585
+ const value = record[key];
586
+ if (typeof value === "string" && value.trim().length > 0) {
587
+ return value.trim();
588
+ }
589
+ }
590
+ return null;
591
+ }
592
+ function cursorHookEventName(record) {
593
+ const value = record.hook_event_name;
594
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
595
+ }
596
+ function cursorHookModel(record) {
597
+ const value = record.model;
598
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
599
+ }
600
+ function cursorHookString(record, key) {
601
+ const value = record[key];
602
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
603
+ }
604
+ function cursorHookStringList(record, key) {
605
+ const value = record[key];
606
+ if (!Array.isArray(value)) {
607
+ return [];
608
+ }
609
+ return value
610
+ .map((entry) => typeof entry === "string" ? entry.trim() : "")
611
+ .filter((entry) => entry.length > 0);
612
+ }
613
+ function cursorHookAttachmentsPaths(record) {
614
+ const value = record.attachments;
615
+ if (!Array.isArray(value)) {
616
+ return [];
617
+ }
618
+ return value
619
+ .map((entry) => {
620
+ if (!entry || typeof entry !== "object") {
621
+ return null;
622
+ }
623
+ const filePath = entry.file_path;
624
+ return typeof filePath === "string" && filePath.trim().length > 0 ? filePath.trim() : null;
625
+ })
626
+ .filter((entry) => entry !== null);
627
+ }
628
+ function cursorHookPaths(record, fallbackCwd) {
629
+ const values = new Set();
630
+ const cwd = cursorHookString(record, "cwd") ?? fallbackCwd;
631
+ if (cwd) {
632
+ values.add(cwd);
633
+ }
634
+ for (const value of [
635
+ cursorHookString(record, "file_path"),
636
+ cursorHookString(record, "transcript_path"),
637
+ cursorHookString(record, "agent_transcript_path")
638
+ ]) {
639
+ if (value) {
640
+ values.add(value);
641
+ }
642
+ }
643
+ cursorHookStringList(record, "modified_files").forEach((value) => values.add(value));
644
+ cursorHookStringList(record, "workspace_roots").forEach((value) => values.add(value));
645
+ cursorHookAttachmentsPaths(record).forEach((value) => values.add(value));
646
+ return [...values];
647
+ }
648
+ function cursorHookActivityEventForTool(input) {
649
+ const normalizedTool = typeof input.toolName === "string" ? input.toolName.trim() : "";
650
+ const toolInput = input.record.tool_input;
651
+ const toolInputRecord = toolInput && typeof toolInput === "object" ? toolInput : null;
652
+ const command = cursorHookString(input.record, "command")
653
+ ?? (toolInputRecord ? cursorHookString(toolInputRecord, "command") : null);
654
+ const workingDirectory = toolInputRecord
655
+ ? cursorHookString(toolInputRecord, "working_directory")
656
+ : null;
657
+ const filePath = cursorHookString(input.record, "file_path")
658
+ ?? (toolInputRecord ? cursorHookString(toolInputRecord, "file_path") : null);
659
+ const state = cursorTranscriptToolState(normalizedTool);
660
+ if (filePath || state === "editing") {
661
+ return {
662
+ type: "fileChange",
663
+ action: "edited",
664
+ path: filePath ?? input.fallbackCwd,
665
+ title: cursorLocalPromptDetail(filePath ?? "Edited files", "Edited files"),
666
+ isImage: false
667
+ };
668
+ }
669
+ if (normalizedTool.toLowerCase().startsWith("mcp:")) {
670
+ return {
671
+ type: "mcpToolCall",
672
+ action: "ran",
673
+ path: input.fallbackCwd,
674
+ title: cursorLocalPromptDetail(normalizedTool, "MCP tool"),
675
+ isImage: false
676
+ };
677
+ }
678
+ if (command || normalizedTool.toLowerCase() === "shell") {
679
+ return {
680
+ type: "commandExecution",
681
+ action: "ran",
682
+ path: workingDirectory ?? input.fallbackCwd,
683
+ title: cursorLocalPromptDetail(command ?? normalizedTool, input.failed ? "Command failed" : "Command ran"),
684
+ isImage: false
685
+ };
686
+ }
687
+ if (state === "scanning") {
688
+ return {
689
+ type: "other",
690
+ action: "updated",
691
+ path: input.fallbackCwd,
692
+ title: cursorLocalPromptDetail(normalizedTool || "Scanning", "Scanning"),
693
+ isImage: false
694
+ };
695
+ }
696
+ return null;
697
+ }
698
+ function cursorHookToolDetail(toolName, record) {
699
+ const toolInput = record.tool_input;
700
+ const toolInputRecord = toolInput && typeof toolInput === "object" ? toolInput : null;
701
+ const command = cursorHookString(record, "command")
702
+ ?? (toolInputRecord ? cursorHookString(toolInputRecord, "command") : null);
703
+ const filePath = cursorHookString(record, "file_path")
704
+ ?? (toolInputRecord ? cursorHookString(toolInputRecord, "file_path") : null);
705
+ const errorMessage = cursorHookString(record, "error_message");
706
+ if (errorMessage) {
707
+ return cursorLocalPromptDetail(errorMessage, "Tool failed");
708
+ }
709
+ if (command) {
710
+ return cursorLocalPromptDetail(command, "Shell command");
711
+ }
712
+ if (filePath) {
713
+ return cursorLocalPromptDetail(filePath, "Edited file");
714
+ }
715
+ if (toolName) {
716
+ return cursorLocalPromptDetail(toolName, "Tool activity");
717
+ }
718
+ return "Tool activity";
719
+ }
720
+ function cursorHookToolState(record, failed, fallbackCwd) {
721
+ const toolName = cursorHookString(record, "tool_name");
722
+ const detail = cursorHookToolDetail(toolName, record);
723
+ const activityEvent = cursorHookActivityEventForTool({
724
+ toolName,
725
+ record,
726
+ fallbackCwd,
727
+ failed
728
+ });
729
+ if (failed) {
730
+ return {
731
+ state: "blocked",
732
+ detail,
733
+ activityEvent,
734
+ latestMessage: cursorHookString(record, "error_message"),
735
+ isOngoing: false
736
+ };
737
+ }
738
+ const mappedState = cursorTranscriptToolState(toolName);
739
+ return {
740
+ state: mappedState ?? "planning",
741
+ detail,
742
+ activityEvent,
743
+ latestMessage: cursorHookString(record, "agent_message"),
744
+ isOngoing: true
745
+ };
746
+ }
747
+ function cursorHookPhaseSummary(input) {
748
+ const eventName = input.eventName.toLowerCase();
749
+ const { timestampMs: updatedAtMs, wasFutureClamped } = cursorHookRecordTimestampMs(input.record, input.fallbackUpdatedAt);
750
+ const sessionId = cursorHookSessionId(input.record) ?? `cursor-${updatedAtMs}`;
751
+ const model = cursorHookModel(input.record);
752
+ const paths = cursorHookPaths(input.record, input.fallbackCwd);
753
+ const sourceKind = model ? (0, shared_1.sourceKindFromModel)(model) : "cursor:local:hook";
754
+ const prompt = cursorHookString(input.record, "prompt");
755
+ const responseText = cursorHookString(input.record, "text");
756
+ const cwd = cursorHookString(input.record, "cwd") ?? input.fallbackCwd;
757
+ switch (eventName) {
758
+ case "beforesubmitprompt":
759
+ return {
760
+ sessionId,
761
+ createdAtMs: updatedAtMs,
762
+ updatedAtMs,
763
+ timestampWasClamped: wasFutureClamped,
764
+ label: cursorLocalPromptDetail(prompt, `Cursor ${sessionId.slice(0, 4)}`),
765
+ sourceKind,
766
+ state: "planning",
767
+ detail: cursorLocalPromptDetail(prompt, "Preparing prompt"),
768
+ statusText: model ?? "hook",
769
+ paths,
770
+ activityEvent: prompt
771
+ ? {
772
+ type: "userMessage",
773
+ action: "said",
774
+ path: cwd,
775
+ title: cursorLocalPromptDetail(prompt, "Cursor prompt"),
776
+ isImage: false
777
+ }
778
+ : null,
779
+ latestMessage: null,
780
+ isOngoing: true
781
+ };
782
+ case "afteragentresponse":
783
+ return {
784
+ sessionId,
785
+ createdAtMs: updatedAtMs,
786
+ updatedAtMs,
787
+ timestampWasClamped: wasFutureClamped,
788
+ label: cursorLocalPromptDetail(responseText, `Cursor ${sessionId.slice(0, 4)}`),
789
+ sourceKind,
790
+ state: "thinking",
791
+ detail: cursorLocalPromptDetail(responseText, "Cursor response"),
792
+ statusText: model ?? "hook",
793
+ paths,
794
+ activityEvent: responseText
795
+ ? {
796
+ type: "agentMessage",
797
+ action: "said",
798
+ path: cwd,
799
+ title: cursorLocalPromptDetail(responseText, "Cursor response"),
800
+ isImage: false
801
+ }
802
+ : null,
803
+ latestMessage: responseText,
804
+ isOngoing: true
805
+ };
806
+ case "afteragentthought":
807
+ return {
808
+ sessionId,
809
+ createdAtMs: updatedAtMs,
810
+ updatedAtMs,
811
+ timestampWasClamped: wasFutureClamped,
812
+ label: `Cursor ${sessionId.slice(0, 4)}`,
813
+ sourceKind,
814
+ state: "thinking",
815
+ detail: cursorLocalPromptDetail(responseText, "Thinking"),
816
+ statusText: model ?? "hook",
817
+ paths,
818
+ activityEvent: responseText
819
+ ? {
820
+ type: "reasoning",
821
+ action: "updated",
822
+ path: cwd,
823
+ title: cursorLocalPromptDetail(responseText, "Thinking"),
824
+ isImage: false
825
+ }
826
+ : null,
827
+ latestMessage: null,
828
+ isOngoing: true
829
+ };
830
+ case "afterfileedit": {
831
+ const filePath = cursorHookString(input.record, "file_path");
832
+ return {
833
+ sessionId,
834
+ createdAtMs: updatedAtMs,
835
+ updatedAtMs,
836
+ timestampWasClamped: wasFutureClamped,
837
+ label: cursorLocalPromptDetail(filePath, `Cursor ${sessionId.slice(0, 4)}`),
838
+ sourceKind,
839
+ state: "editing",
840
+ detail: cursorLocalPromptDetail(filePath, "Edited file"),
841
+ statusText: model ?? "hook",
842
+ paths,
843
+ activityEvent: {
844
+ type: "fileChange",
845
+ action: "edited",
846
+ path: filePath ?? cwd,
847
+ title: cursorLocalPromptDetail(filePath, "Edited file"),
848
+ isImage: false
849
+ },
850
+ latestMessage: null,
851
+ isOngoing: true
852
+ };
853
+ }
854
+ case "beforetooluse":
855
+ case "pretooluse":
856
+ case "posttooluse":
857
+ return (() => {
858
+ const tool = cursorHookToolState(input.record, false, input.fallbackCwd);
859
+ return {
860
+ sessionId,
861
+ createdAtMs: updatedAtMs,
862
+ updatedAtMs,
863
+ timestampWasClamped: wasFutureClamped,
864
+ label: tool.detail,
865
+ sourceKind,
866
+ state: tool.state,
867
+ detail: tool.detail,
868
+ statusText: model ?? "hook",
869
+ paths,
870
+ activityEvent: tool.activityEvent,
871
+ latestMessage: tool.latestMessage,
872
+ isOngoing: true
873
+ };
874
+ })();
875
+ case "posttoolusefailure":
876
+ return (() => {
877
+ const tool = cursorHookToolState(input.record, true, input.fallbackCwd);
878
+ return {
879
+ sessionId,
880
+ createdAtMs: updatedAtMs,
881
+ updatedAtMs,
882
+ timestampWasClamped: wasFutureClamped,
883
+ label: tool.detail,
884
+ sourceKind,
885
+ state: tool.state,
886
+ detail: tool.detail,
887
+ statusText: model ?? "hook",
888
+ paths,
889
+ activityEvent: tool.activityEvent,
890
+ latestMessage: tool.latestMessage,
891
+ isOngoing: false
892
+ };
893
+ })();
894
+ case "beforeshellexecution":
895
+ case "aftershellexecution": {
896
+ const command = cursorHookString(input.record, "command");
897
+ return {
898
+ sessionId,
899
+ createdAtMs: updatedAtMs,
900
+ updatedAtMs,
901
+ timestampWasClamped: wasFutureClamped,
902
+ label: cursorLocalPromptDetail(command, `Cursor ${sessionId.slice(0, 4)}`),
903
+ sourceKind,
904
+ state: "running",
905
+ detail: cursorLocalPromptDetail(command, "Shell command"),
906
+ statusText: model ?? "hook",
907
+ paths,
908
+ activityEvent: {
909
+ type: "commandExecution",
910
+ action: "ran",
911
+ path: cwd,
912
+ title: cursorLocalPromptDetail(command, "Shell command"),
913
+ isImage: false
914
+ },
915
+ latestMessage: null,
916
+ isOngoing: true
917
+ };
918
+ }
919
+ case "beforemcpexecution":
920
+ case "aftermcpexecution": {
921
+ const toolName = cursorHookString(input.record, "tool_name");
922
+ return {
923
+ sessionId,
924
+ createdAtMs: updatedAtMs,
925
+ updatedAtMs,
926
+ timestampWasClamped: wasFutureClamped,
927
+ label: cursorLocalPromptDetail(toolName, `Cursor ${sessionId.slice(0, 4)}`),
928
+ sourceKind,
929
+ state: "running",
930
+ detail: cursorLocalPromptDetail(toolName, "MCP tool"),
931
+ statusText: model ?? "hook",
932
+ paths,
933
+ activityEvent: {
934
+ type: "mcpToolCall",
935
+ action: "ran",
936
+ path: cwd,
937
+ title: cursorLocalPromptDetail(toolName, "MCP tool"),
938
+ isImage: false
939
+ },
940
+ latestMessage: null,
941
+ isOngoing: true
942
+ };
943
+ }
944
+ case "subagentstart": {
945
+ const task = cursorHookString(input.record, "task") ?? cursorHookString(input.record, "description");
946
+ return {
947
+ sessionId,
948
+ createdAtMs: updatedAtMs,
949
+ updatedAtMs,
950
+ timestampWasClamped: wasFutureClamped,
951
+ label: cursorLocalPromptDetail(task, `Cursor ${sessionId.slice(0, 4)}`),
952
+ sourceKind,
953
+ state: "delegating",
954
+ detail: cursorLocalPromptDetail(task, "Delegating to subagent"),
955
+ statusText: model ?? "hook",
956
+ paths,
957
+ activityEvent: {
958
+ type: "other",
959
+ action: "updated",
960
+ path: cwd,
961
+ title: cursorLocalPromptDetail(task, "Delegating to subagent"),
962
+ isImage: false
963
+ },
964
+ latestMessage: null,
965
+ isOngoing: true
966
+ };
967
+ }
968
+ case "subagentstop": {
969
+ const status = cursorHookString(input.record, "status");
970
+ const summary = cursorHookString(input.record, "summary") ?? cursorHookString(input.record, "task");
971
+ const state = status === "error" ? "blocked"
972
+ : "done";
973
+ return {
974
+ sessionId,
975
+ createdAtMs: updatedAtMs,
976
+ updatedAtMs,
977
+ timestampWasClamped: wasFutureClamped,
978
+ label: cursorLocalPromptDetail(summary, `Cursor ${sessionId.slice(0, 4)}`),
979
+ sourceKind,
980
+ state,
981
+ detail: cursorLocalPromptDetail(summary, "Subagent completed"),
982
+ statusText: model ?? "hook",
983
+ paths,
984
+ activityEvent: {
985
+ type: "other",
986
+ action: "updated",
987
+ path: cwd,
988
+ title: cursorLocalPromptDetail(summary, "Subagent completed"),
989
+ isImage: false
990
+ },
991
+ latestMessage: null,
992
+ isOngoing: state !== "blocked"
993
+ };
994
+ }
995
+ case "stop": {
996
+ const status = cursorHookString(input.record, "status");
997
+ return {
998
+ sessionId,
999
+ createdAtMs: updatedAtMs,
1000
+ updatedAtMs,
1001
+ timestampWasClamped: wasFutureClamped,
1002
+ label: `Cursor ${sessionId.slice(0, 4)}`,
1003
+ sourceKind,
1004
+ state: status === "error" ? "blocked" : "done",
1005
+ detail: status === "error" ? "Cursor session failed"
1006
+ : status === "aborted" ? "Cursor session stopped"
1007
+ : "Cursor session completed",
1008
+ statusText: model ?? "hook",
1009
+ paths,
1010
+ activityEvent: null,
1011
+ latestMessage: null,
1012
+ isOngoing: false
1013
+ };
1014
+ }
1015
+ case "sessionstart": {
1016
+ const composerMode = cursorHookString(input.record, "composer_mode");
1017
+ const state = composerMode === "edit" ? "editing"
1018
+ : "planning";
1019
+ return {
1020
+ sessionId,
1021
+ createdAtMs: updatedAtMs,
1022
+ updatedAtMs,
1023
+ timestampWasClamped: wasFutureClamped,
1024
+ label: `Cursor ${sessionId.slice(0, 4)}`,
1025
+ sourceKind,
1026
+ state,
1027
+ detail: composerMode ? `Session started · ${composerMode}` : "Session started",
1028
+ statusText: model ?? "hook",
1029
+ paths,
1030
+ activityEvent: null,
1031
+ latestMessage: null,
1032
+ isOngoing: true
1033
+ };
1034
+ }
1035
+ case "sessionend": {
1036
+ const reason = cursorHookString(input.record, "reason");
1037
+ return {
1038
+ sessionId,
1039
+ createdAtMs: updatedAtMs,
1040
+ updatedAtMs,
1041
+ timestampWasClamped: wasFutureClamped,
1042
+ label: `Cursor ${sessionId.slice(0, 4)}`,
1043
+ sourceKind,
1044
+ state: reason === "error" ? "blocked" : "done",
1045
+ detail: reason ? `Session ended · ${reason}` : "Session ended",
1046
+ statusText: model ?? "hook",
1047
+ paths,
1048
+ activityEvent: null,
1049
+ latestMessage: null,
1050
+ isOngoing: false
1051
+ };
1052
+ }
1053
+ case "precompact":
1054
+ return {
1055
+ sessionId,
1056
+ createdAtMs: updatedAtMs,
1057
+ updatedAtMs,
1058
+ timestampWasClamped: wasFutureClamped,
1059
+ label: `Cursor ${sessionId.slice(0, 4)}`,
1060
+ sourceKind,
1061
+ state: "thinking",
1062
+ detail: "Compacting context",
1063
+ statusText: model ?? "hook",
1064
+ paths,
1065
+ activityEvent: {
1066
+ type: "contextCompaction",
1067
+ action: "updated",
1068
+ path: cwd,
1069
+ title: "Compacting context",
1070
+ isImage: false
1071
+ },
1072
+ latestMessage: null,
1073
+ isOngoing: true
1074
+ };
1075
+ default:
1076
+ return null;
1077
+ }
1078
+ }
1079
+ function ageCursorHookSummary(summary, now = Date.now()) {
1080
+ if (!summary.isOngoing) {
1081
+ const ageMs = now - summary.updatedAtMs;
1082
+ if (ageMs <= CURSOR_HOOK_DONE_WINDOW_MS) {
1083
+ return summary;
1084
+ }
1085
+ return {
1086
+ ...summary,
1087
+ state: "idle",
1088
+ detail: "Idle",
1089
+ activityEvent: null,
1090
+ latestMessage: null,
1091
+ timestampWasClamped: false
1092
+ };
1093
+ }
1094
+ const ageMs = now - summary.updatedAtMs;
1095
+ if (ageMs <= CURSOR_HOOK_ACTIVE_WINDOW_MS) {
1096
+ return summary;
1097
+ }
1098
+ if (ageMs <= CURSOR_HOOK_DONE_WINDOW_MS) {
1099
+ return {
1100
+ ...summary,
1101
+ state: "done",
1102
+ detail: summary.detail === "Idle" ? "Done" : summary.detail,
1103
+ activityEvent: null,
1104
+ isOngoing: false,
1105
+ timestampWasClamped: false
1106
+ };
1107
+ }
1108
+ return {
1109
+ ...summary,
1110
+ state: "idle",
1111
+ detail: "Idle",
1112
+ activityEvent: null,
1113
+ latestMessage: null,
1114
+ isOngoing: false,
1115
+ timestampWasClamped: false
1116
+ };
1117
+ }
1118
+ function cursorHookEventKind(event) {
1119
+ if (!event) {
1120
+ return "other";
1121
+ }
1122
+ if (event.type === "fileChange") {
1123
+ return "fileChange";
1124
+ }
1125
+ if (event.type === "commandExecution") {
1126
+ return "command";
1127
+ }
1128
+ if (event.type === "userMessage" || event.type === "agentMessage") {
1129
+ return "message";
1130
+ }
1131
+ if (event.type === "mcpToolCall") {
1132
+ return "tool";
1133
+ }
1134
+ return "other";
1135
+ }
1136
+ function cursorHookEventMethod(eventName, summary) {
1137
+ const normalized = typeof eventName === "string" ? eventName.trim() : "activity";
1138
+ switch (summary.activityEvent?.type) {
1139
+ case "userMessage":
1140
+ return "cursor/local/userMessage";
1141
+ case "agentMessage":
1142
+ return "cursor/local/agentMessage";
1143
+ case "fileChange":
1144
+ return "cursor/local/fileChange";
1145
+ case "commandExecution":
1146
+ return "cursor/local/commandExecution";
1147
+ case "mcpToolCall":
1148
+ return "cursor/local/mcpToolCall";
1149
+ case "reasoning":
1150
+ return "cursor/local/reasoning";
1151
+ case "contextCompaction":
1152
+ return "cursor/local/contextCompaction";
1153
+ default:
1154
+ return `cursor/local/hook/${normalized}`;
1155
+ }
1156
+ }
1157
+ function cursorHookEventFromSummary(input) {
1158
+ if (Date.now() - input.createdAtMs > CURSOR_HOOK_EVENT_WINDOW_MS) {
1159
+ return null;
1160
+ }
1161
+ if (!input.summary.activityEvent && !input.summary.latestMessage) {
1162
+ return null;
1163
+ }
1164
+ return {
1165
+ id: `${input.projectRoot}::cursor-hook::${input.sessionId}::${input.index}::${input.eventName ?? "activity"}`,
1166
+ source: "cursor",
1167
+ confidence: "typed",
1168
+ threadId: input.sessionId,
1169
+ createdAt: new Date(input.createdAtMs).toISOString(),
1170
+ method: cursorHookEventMethod(input.eventName, input.summary),
1171
+ kind: cursorHookEventKind(input.summary.activityEvent),
1172
+ phase: input.summary.state === "blocked" ? "failed"
1173
+ : input.summary.state === "done" ? "completed"
1174
+ : "updated",
1175
+ title: input.summary.detail,
1176
+ detail: input.summary.latestMessage ?? input.summary.detail,
1177
+ path: input.summary.activityEvent?.path ?? input.projectRoot,
1178
+ action: input.summary.activityEvent?.action,
1179
+ isImage: input.summary.activityEvent?.isImage
1180
+ };
1181
+ }
1182
+ async function cursorHookFiles(projectRoot) {
1183
+ const hooksDirs = await (0, project_storage_1.listExistingProjectStoragePaths)(projectRoot, "cursor-hooks");
1184
+ if (hooksDirs.length === 0) {
1185
+ return [];
1186
+ }
1187
+ const files = [];
1188
+ for (const hooksDir of hooksDirs) {
1189
+ const entries = await (0, promises_1.readdir)(hooksDir, { withFileTypes: true }).catch(() => []);
1190
+ for (const entry of entries) {
1191
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
1192
+ continue;
1193
+ }
1194
+ const filePath = (0, node_path_1.join)(hooksDir, entry.name);
1195
+ const fileStats = await (0, promises_1.stat)(filePath).catch(() => null);
1196
+ if (!fileStats?.isFile()) {
1197
+ continue;
1198
+ }
1199
+ files.push({
1200
+ sessionId: entry.name.replace(/\.jsonl$/i, ""),
1201
+ filePath,
1202
+ updatedAtMs: fileStats.mtimeMs
1203
+ });
1204
+ }
1205
+ }
1206
+ return files.sort((left, right) => right.updatedAtMs - left.updatedAtMs);
1207
+ }
1208
+ async function summariseCursorHookSession(file, fallbackCwd) {
1209
+ const raw = await (0, promises_1.readFile)(file.filePath, "utf8").catch(() => null);
1210
+ if (!raw) {
1211
+ return null;
1212
+ }
1213
+ const rawRecords = raw
1214
+ .split(/\r?\n/)
1215
+ .map((line) => line.trim())
1216
+ .filter((line) => line.length > 0)
1217
+ .map((line) => {
1218
+ try {
1219
+ return JSON.parse(line);
1220
+ }
1221
+ catch {
1222
+ return null;
1223
+ }
1224
+ })
1225
+ .filter((record) => record !== null);
1226
+ if (rawRecords.length === 0) {
1227
+ return null;
1228
+ }
1229
+ const sortedRecords = rawRecords
1230
+ .slice()
1231
+ .sort((left, right) => cursorHookRecordTimestampMs(left, file.updatedAtMs).timestampMs
1232
+ - cursorHookRecordTimestampMs(right, file.updatedAtMs).timestampMs);
1233
+ let latestSummary = null;
1234
+ let latestPrompt = null;
1235
+ let latestAssistantText = null;
1236
+ const pathSet = new Set();
1237
+ const events = [];
1238
+ for (const [index, record] of sortedRecords.entries()) {
1239
+ const eventName = cursorHookEventName(record);
1240
+ const summary = cursorHookPhaseSummary({
1241
+ eventName: eventName ?? "activity",
1242
+ record,
1243
+ fallbackCwd,
1244
+ fallbackUpdatedAt: file.updatedAtMs
1245
+ });
1246
+ if (!summary) {
1247
+ continue;
1248
+ }
1249
+ summary.paths.forEach((path) => pathSet.add(path));
1250
+ if ((eventName ?? "").toLowerCase() === "beforesubmitprompt") {
1251
+ latestPrompt = cursorHookString(record, "prompt") ?? latestPrompt;
1252
+ }
1253
+ if ((eventName ?? "").toLowerCase() === "afteragentresponse") {
1254
+ latestAssistantText = cursorHookString(record, "text") ?? latestAssistantText;
1255
+ }
1256
+ const shouldReplaceLatest = !latestSummary
1257
+ || summary.updatedAtMs >= latestSummary.updatedAtMs
1258
+ || (latestSummary.timestampWasClamped
1259
+ && !summary.timestampWasClamped
1260
+ && summary.updatedAtMs >= latestSummary.updatedAtMs - CURSOR_HOOK_FUTURE_TOLERANCE_MS);
1261
+ if (shouldReplaceLatest) {
1262
+ latestSummary = summary;
1263
+ }
1264
+ const event = cursorHookEventFromSummary({
1265
+ projectRoot: fallbackCwd,
1266
+ sessionId: file.sessionId,
1267
+ eventName,
1268
+ summary,
1269
+ createdAtMs: summary.updatedAtMs,
1270
+ index
1271
+ });
1272
+ if (event) {
1273
+ events.push(event);
1274
+ }
1275
+ }
1276
+ if (!latestSummary) {
1277
+ return null;
1278
+ }
1279
+ const agedSummary = ageCursorHookSummary({
1280
+ ...latestSummary,
1281
+ label: cursorLocalPromptDetail(latestPrompt ?? latestAssistantText ?? latestSummary.label, `Cursor ${file.sessionId.slice(0, 4)}`),
1282
+ latestMessage: latestAssistantText ?? latestSummary.latestMessage,
1283
+ paths: pathSet.size > 0 ? [...pathSet] : latestSummary.paths
1284
+ });
1285
+ return {
1286
+ summary: agedSummary,
1287
+ events: events.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
1288
+ };
1289
+ }
1290
+ async function loadCursorHookProjectSnapshotData(projectRoot, limit = (0, local_discovery_1.cursorLocalSessionLimit)()) {
1291
+ const canonicalRoot = (0, project_paths_1.canonicalizeProjectPath)(projectRoot);
1292
+ if (!canonicalRoot) {
1293
+ return { agents: [], events: [] };
1294
+ }
1295
+ const files = await cursorHookFiles(canonicalRoot);
1296
+ if (files.length === 0) {
1297
+ return { agents: [], events: [] };
1298
+ }
1299
+ const [originUrl, branch] = await Promise.all([
1300
+ (0, shared_1.gitRemoteOriginUrl)(canonicalRoot),
1301
+ gitCurrentBranch(canonicalRoot)
1302
+ ]);
1303
+ const summaries = (await Promise.all(files.slice(0, Math.max(limit, 24)).map((file) => summariseCursorHookSession(file, canonicalRoot))))
1304
+ .filter((entry) => entry !== null);
1305
+ const agents = [];
1306
+ const events = [];
1307
+ for (const entry of summaries.slice(0, limit)) {
1308
+ if (entry.summary.state === "idle" && !entry.summary.isOngoing) {
1309
+ continue;
1310
+ }
1311
+ const appearance = await (0, appearance_1.ensureAgentAppearance)(canonicalRoot, `cursor-local:${entry.summary.sessionId}`);
1312
+ agents.push({
1313
+ id: `cursor-local:${entry.summary.sessionId}`,
1314
+ label: entry.summary.label,
1315
+ source: "cursor",
1316
+ sourceKind: entry.summary.sourceKind,
1317
+ parentThreadId: null,
1318
+ depth: 0,
1319
+ isCurrent: false,
1320
+ isOngoing: entry.summary.isOngoing,
1321
+ statusText: entry.summary.statusText,
1322
+ role: "cursor",
1323
+ nickname: null,
1324
+ isSubagent: false,
1325
+ state: entry.summary.state,
1326
+ detail: entry.summary.detail,
1327
+ cwd: canonicalRoot,
1328
+ roomId: null,
1329
+ appearance,
1330
+ updatedAt: new Date(entry.summary.updatedAtMs).toISOString(),
1331
+ stoppedAt: entry.summary.isOngoing ? null : new Date(entry.summary.updatedAtMs).toISOString(),
1332
+ paths: entry.summary.paths.length > 0 ? entry.summary.paths : [canonicalRoot],
1333
+ activityEvent: entry.summary.activityEvent,
1334
+ latestMessage: entry.summary.latestMessage,
1335
+ threadId: entry.summary.sessionId,
1336
+ taskId: null,
1337
+ resumeCommand: null,
1338
+ url: null,
1339
+ git: {
1340
+ sha: null,
1341
+ branch,
1342
+ originUrl
1343
+ },
1344
+ provenance: "cursor",
1345
+ confidence: "typed",
1346
+ needsUser: null,
1347
+ liveSubscription: "readOnly",
1348
+ network: null
1349
+ });
1350
+ events.push(...entry.events);
1351
+ }
1352
+ return {
1353
+ agents: agents.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)),
1354
+ events: events.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
1355
+ };
1356
+ }
1357
+ function cursorLocalLabel(composer, prompt) {
1358
+ const name = typeof composer.name === "string" ? composer.name.trim() : "";
1359
+ if (name.length > 0) {
1360
+ return name;
1361
+ }
1362
+ const subtitle = typeof composer.subtitle === "string" ? composer.subtitle.trim() : "";
1363
+ if (subtitle.length > 0) {
1364
+ return cursorLocalPromptDetail(subtitle, "Cursor local");
1365
+ }
1366
+ if (prompt) {
1367
+ return cursorLocalPromptDetail(prompt, "Cursor local");
1368
+ }
1369
+ return `Cursor ${composer.composerId.slice(0, 4)}`;
1370
+ }
1371
+ function cursorLocalDetail(composer, prompt, state) {
1372
+ const subtitle = typeof composer.subtitle === "string" ? composer.subtitle.trim() : "";
1373
+ const changedFiles = composer.filesChangedCount ?? 0;
1374
+ const added = composer.totalLinesAdded ?? 0;
1375
+ const removed = composer.totalLinesRemoved ?? 0;
1376
+ if (state === "editing" && changedFiles > 0) {
1377
+ return `${changedFiles} file${changedFiles === 1 ? "" : "s"} changed · +${added} -${removed}`;
1378
+ }
1379
+ if (subtitle.length > 0) {
1380
+ return cursorLocalPromptDetail(subtitle, "Cursor local");
1381
+ }
1382
+ if (prompt) {
1383
+ return cursorLocalPromptDetail(prompt, "Cursor local");
1384
+ }
1385
+ return state === "idle" ? "Idle" : "Cursor local";
1386
+ }
1387
+ function cursorTranscriptActivityState(summary, now) {
1388
+ const ageMs = now - summary.updatedAtMs;
1389
+ if (ageMs <= CURSOR_LOCAL_ACTIVE_WINDOW_MS) {
1390
+ if (summary.latestToolState) {
1391
+ return { state: summary.latestToolState, isOngoing: true };
1392
+ }
1393
+ return { state: "planning", isOngoing: true };
1394
+ }
1395
+ if (ageMs <= CURSOR_LOCAL_DONE_WINDOW_MS) {
1396
+ return { state: "done", isOngoing: false };
1397
+ }
1398
+ return { state: "idle", isOngoing: false };
1399
+ }
1400
+ function cursorLocalActivityState(input) {
1401
+ const composerUpdatedAt = cursorComposerTimestamp(input.composer);
1402
+ const latestGenerationMs = input.isPrimaryComposer ? input.latestGenerationMs : 0;
1403
+ const agentLoopUpdatedAtMs = input.isPrimaryComposer ? (input.agentLoop.updatedAtMs ?? 0) : 0;
1404
+ const agentLoopActive = input.isPrimaryComposer ? input.agentLoop.active : false;
1405
+ const referenceUpdatedAt = Math.max(composerUpdatedAt, latestGenerationMs, agentLoopUpdatedAtMs);
1406
+ const ageMs = input.now - referenceUpdatedAt;
1407
+ const changedFiles = input.composer.filesChangedCount ?? 0;
1408
+ const changedLines = (input.composer.totalLinesAdded ?? 0) + (input.composer.totalLinesRemoved ?? 0);
1409
+ if (input.composer.hasBlockingPendingActions) {
1410
+ return { state: "blocked", isOngoing: true };
1411
+ }
1412
+ if (input.isPrimaryComposer && (agentLoopActive || ageMs <= CURSOR_LOCAL_ACTIVE_WINDOW_MS)) {
1413
+ if (changedFiles > 0 || changedLines > 0) {
1414
+ return { state: "editing", isOngoing: true };
1415
+ }
1416
+ return { state: "planning", isOngoing: true };
1417
+ }
1418
+ if (changedFiles > 0 || changedLines > 0) {
1419
+ if (ageMs <= CURSOR_LOCAL_DONE_WINDOW_MS) {
1420
+ return { state: "done", isOngoing: false };
1421
+ }
1422
+ return { state: "idle", isOngoing: false };
1423
+ }
1424
+ if (ageMs <= CURSOR_LOCAL_DONE_WINDOW_MS) {
1425
+ return { state: "done", isOngoing: false };
1426
+ }
1427
+ return { state: "idle", isOngoing: false };
1428
+ }
1429
+ function cursorTranscriptMessageEvent(input) {
1430
+ if (!input.role || !input.detail || Date.now() - input.createdAtMs > CURSOR_LOCAL_EVENT_WINDOW_MS) {
1431
+ return null;
1432
+ }
1433
+ return {
1434
+ id: `${input.projectRoot}::cursor-local::${input.sessionId}::${input.createdAtMs}`,
1435
+ source: "cursor",
1436
+ confidence: "inferred",
1437
+ threadId: input.sessionId,
1438
+ createdAt: new Date(input.createdAtMs).toISOString(),
1439
+ method: input.role === "user" ? "cursor/local/userMessage" : "cursor/local/agentMessage",
1440
+ kind: "message",
1441
+ phase: "updated",
1442
+ title: input.role === "user" ? "Cursor prompt" : "Cursor reply",
1443
+ detail: cursorLocalPromptDetail(input.detail, "Cursor local"),
1444
+ path: input.projectRoot,
1445
+ action: "said",
1446
+ isImage: false
1447
+ };
1448
+ }
1449
+ function cursorLocalPromptEvent(projectRoot, composerId, prompt, createdAtMs) {
1450
+ if (!prompt || !Number.isFinite(createdAtMs) || Date.now() - createdAtMs > CURSOR_LOCAL_EVENT_WINDOW_MS) {
1451
+ return null;
1452
+ }
1453
+ return {
1454
+ id: `${projectRoot}::cursor-local::${composerId}::${createdAtMs}`,
1455
+ source: "cursor",
1456
+ confidence: "inferred",
1457
+ threadId: composerId,
1458
+ createdAt: new Date(createdAtMs).toISOString(),
1459
+ method: "cursor/local/prompt",
1460
+ kind: "message",
1461
+ phase: "updated",
1462
+ title: "Cursor prompt",
1463
+ detail: cursorLocalPromptDetail(prompt, "Cursor local prompt"),
1464
+ path: projectRoot,
1465
+ action: "said",
1466
+ isImage: false
1467
+ };
1468
+ }
1469
+ async function gitCurrentBranch(projectRoot) {
1470
+ try {
1471
+ const { stdout } = await execFileAsync("git", ["-C", projectRoot, "rev-parse", "--abbrev-ref", "HEAD"]);
1472
+ const branch = stdout.trim();
1473
+ return branch.length > 0 && branch !== "HEAD" ? branch : null;
1474
+ }
1475
+ catch {
1476
+ return null;
1477
+ }
1478
+ }
1479
+ async function loadCursorTranscriptProjectSnapshotData(projectRoot, limit = (0, local_discovery_1.cursorLocalSessionLimit)()) {
1480
+ const canonicalRoot = (0, project_paths_1.canonicalizeProjectPath)(projectRoot);
1481
+ if (!canonicalRoot) {
1482
+ return { agents: [], events: [] };
1483
+ }
1484
+ const transcriptFiles = (await (0, local_discovery_1.cursorTranscriptFiles)(canonicalRoot)).slice(0, Math.max(limit, 24));
1485
+ if (transcriptFiles.length === 0) {
1486
+ return { agents: [], events: [] };
1487
+ }
1488
+ const summaries = (await Promise.all(transcriptFiles.map((file) => summariseCursorTranscriptSession(file))))
1489
+ .filter((summary) => summary !== null);
1490
+ if (summaries.length === 0) {
1491
+ return { agents: [], events: [] };
1492
+ }
1493
+ const now = Date.now();
1494
+ const [originUrl, branch] = await Promise.all([
1495
+ (0, shared_1.gitRemoteOriginUrl)(canonicalRoot),
1496
+ gitCurrentBranch(canonicalRoot)
1497
+ ]);
1498
+ const agents = [];
1499
+ const events = [];
1500
+ for (const summary of summaries.slice(0, limit)) {
1501
+ const { state, isOngoing } = cursorTranscriptActivityState(summary, now);
1502
+ if (state === "idle" && !isOngoing) {
1503
+ continue;
1504
+ }
1505
+ const labelText = summary.initialPrompt
1506
+ ?? summary.latestUserText
1507
+ ?? summary.latestAssistantText
1508
+ ?? `Cursor ${summary.sessionId.slice(0, 4)}`;
1509
+ const detailText = summary.latestAssistantText
1510
+ ?? summary.latestUserText
1511
+ ?? summary.initialPrompt
1512
+ ?? "Cursor local";
1513
+ const activityText = summary.latestRole === "assistant"
1514
+ ? (summary.latestAssistantText ?? detailText)
1515
+ : (summary.latestUserText ?? summary.initialPrompt ?? detailText);
1516
+ const updatedAt = new Date(summary.updatedAtMs || now).toISOString();
1517
+ const appearance = await (0, appearance_1.ensureAgentAppearance)(canonicalRoot, `cursor-local:${summary.sessionId}`);
1518
+ const activityEvent = activityText
1519
+ ? {
1520
+ type: summary.latestRole === "user" ? "userMessage" : "agentMessage",
1521
+ action: "said",
1522
+ path: canonicalRoot,
1523
+ title: cursorLocalPromptDetail(activityText, "Cursor local"),
1524
+ isImage: false
1525
+ }
1526
+ : null;
1527
+ agents.push({
1528
+ id: `cursor-local:${summary.sessionId}`,
1529
+ label: cursorLocalPromptDetail(labelText, `Cursor ${summary.sessionId.slice(0, 4)}`),
1530
+ source: "cursor",
1531
+ sourceKind: summary.model ? `cursor:${summary.model}` : "cursor:local:agent-transcript",
1532
+ parentThreadId: null,
1533
+ depth: 0,
1534
+ isCurrent: false,
1535
+ isOngoing,
1536
+ statusText: summary.model ?? "agent transcript",
1537
+ role: "cursor",
1538
+ nickname: null,
1539
+ isSubagent: false,
1540
+ state,
1541
+ detail: cursorLocalPromptDetail(detailText, "Cursor local"),
1542
+ cwd: canonicalRoot,
1543
+ roomId: null,
1544
+ appearance,
1545
+ updatedAt,
1546
+ stoppedAt: isOngoing ? null : updatedAt,
1547
+ paths: [canonicalRoot],
1548
+ activityEvent,
1549
+ latestMessage: summary.latestAssistantText,
1550
+ threadId: summary.sessionId,
1551
+ taskId: null,
1552
+ resumeCommand: null,
1553
+ url: null,
1554
+ git: {
1555
+ sha: null,
1556
+ branch,
1557
+ originUrl
1558
+ },
1559
+ provenance: "cursor",
1560
+ confidence: "inferred",
1561
+ needsUser: null,
1562
+ liveSubscription: "readOnly",
1563
+ network: null
1564
+ });
1565
+ const event = cursorTranscriptMessageEvent({
1566
+ projectRoot: canonicalRoot,
1567
+ sessionId: summary.sessionId,
1568
+ role: summary.latestRole,
1569
+ detail: activityText,
1570
+ createdAtMs: summary.updatedAtMs
1571
+ });
1572
+ if (event) {
1573
+ events.push(event);
1574
+ }
1575
+ }
1576
+ return {
1577
+ agents: agents.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)),
1578
+ events: events.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
1579
+ };
1580
+ }
1581
+ async function loadCursorLocalProjectSnapshotData(projectRoot, limit = (0, local_discovery_1.cursorLocalSessionLimit)()) {
1582
+ return loadCursorHookProjectSnapshotData(projectRoot, limit);
1583
+ }
1584
+ //# sourceMappingURL=cursor.js.map