@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/hermes.js ADDED
@@ -0,0 +1,2446 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadHermesProjectSnapshotData = loadHermesProjectSnapshotData;
4
+ exports.discoverHermesProjects = discoverHermesProjects;
5
+ exports.loadRoamingHermesSnapshotData = loadRoamingHermesSnapshotData;
6
+ exports.summarizeHermesSessionForTest = summarizeHermesSessionForTest;
7
+ exports.summarizeHermesHookSessionForTest = summarizeHermesHookSessionForTest;
8
+ exports.isHermesGatewayDaemonForTest = isHermesGatewayDaemonForTest;
9
+ exports.isHermesRuntimeHomeProcessForTest = isHermesRuntimeHomeProcessForTest;
10
+ exports.isHermesCompressionContinuationForTest = isHermesCompressionContinuationForTest;
11
+ exports.currentHermesSessionProjectRootForTest = currentHermesSessionProjectRootForTest;
12
+ const node_child_process_1 = require("node:child_process");
13
+ const node_fs_1 = require("node:fs");
14
+ const promises_1 = require("node:fs/promises");
15
+ const node_os_1 = require("node:os");
16
+ const node_path_1 = require("node:path");
17
+ const node_process_1 = require("node:process");
18
+ const node_util_1 = require("node:util");
19
+ const appearance_1 = require("./appearance");
20
+ const hermes_hook_install_1 = require("./hermes-hook-install");
21
+ const project_paths_1 = require("./project-paths");
22
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
23
+ const DEFAULT_HERMES_SESSION_LIMIT = 12;
24
+ const HERMES_SESSION_SCAN_LIMIT = 30;
25
+ const HERMES_PROJECT_DISCOVERY_HOOK_WINDOW_MS = 2 * 60 * 60 * 1000;
26
+ const HERMES_RECENT_DONE_WINDOW_MS = 5 * 60 * 1000;
27
+ const HERMES_RECENT_OPEN_WINDOW_MS = 20 * 60 * 1000;
28
+ const HERMES_SQLITE_MESSAGE_LIMIT = 10;
29
+ const HERMES_SQLITE_TEXT_LIMIT = 2000;
30
+ const HERMES_SQLITE_TIMEOUT_MS = 5000;
31
+ const HERMES_SQLITE_MAX_BUFFER = 8 * 1024 * 1024;
32
+ const HERMES_HOOK_FILE_BYTE_LIMIT = 1024 * 1024;
33
+ const HERMES_HOOK_LINE_BYTE_LIMIT = 64 * 1024;
34
+ const HERMES_HOOK_RECORD_LIMIT = 80;
35
+ // Keep a project relation through 20 rootless hook actions, then treat the stream as projectless.
36
+ const HERMES_PROJECT_RELATION_ACTION_WINDOW = 21;
37
+ const HERMES_HOOK_TEXT_LIMIT = 1500;
38
+ const HERMES_HOOK_PATH_LIMIT = 32;
39
+ const HERMES_TRANSIENT_PROJECT_ROOTS = new Set(["/tmp", "/var/tmp", "/dev/shm"]);
40
+ const HERMES_CRON_SESSION_ID_RE = /^cron_[A-Za-z0-9_-]+_\d{8}_\d{6}$/i;
41
+ function hermesLocalSessionLimit() {
42
+ const raw = Number.parseInt(process.env.HERMES_LOCAL_SESSION_LIMIT ?? "", 10);
43
+ if (!Number.isFinite(raw)) {
44
+ return DEFAULT_HERMES_SESSION_LIMIT;
45
+ }
46
+ return Math.max(1, Math.min(50, raw));
47
+ }
48
+ function hermesScanLimit(limit) {
49
+ return Math.max(1, Math.min(HERMES_SESSION_SCAN_LIMIT, limit));
50
+ }
51
+ async function looksLikeHermesRuntimeProjectRoot(root) {
52
+ const normalized = root.replace(/\\/g, "/").toLowerCase();
53
+ if (!normalized.endsWith("/hermes-agent")) {
54
+ return false;
55
+ }
56
+ return await pathExists((0, node_path_1.join)(root, "hermes_cli", "main.py"))
57
+ && await pathExists((0, node_path_1.join)(root, "gateway"))
58
+ && await pathExists((0, node_path_1.join)(root, "pyproject.toml"));
59
+ }
60
+ async function pathExists(path) {
61
+ try {
62
+ await (0, promises_1.access)(hostFilesystemPath(path), node_fs_1.constants.F_OK);
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ function expandHomePath(path) {
70
+ const trimmed = path.trim();
71
+ if (trimmed === "~") {
72
+ return (0, node_os_1.homedir)();
73
+ }
74
+ if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
75
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), trimmed.slice(2));
76
+ }
77
+ return trimmed;
78
+ }
79
+ function normalizeFilesystemPath(path) {
80
+ if (typeof path !== "string" || path.trim().length === 0) {
81
+ return null;
82
+ }
83
+ return (0, project_paths_1.canonicalizeProjectPath)(expandHomePath(path)) ?? null;
84
+ }
85
+ function hostFilesystemPath(path) {
86
+ const canonical = (0, project_paths_1.canonicalizeProjectPath)(path) ?? path;
87
+ const match = canonical.match(/^\/mnt\/([a-zA-Z])\/(.*)$/);
88
+ if (node_process_1.platform !== "win32" || !match) {
89
+ return path;
90
+ }
91
+ const drive = match[1].toUpperCase();
92
+ const rest = match[2].replace(/\//g, "\\");
93
+ return `${drive}:\\${rest}`;
94
+ }
95
+ function hermesHookDirectories() {
96
+ const explicit = normalizeFilesystemPath(process.env.CODEX_AGENTS_OFFICE_HERMES_HOOK_DIR);
97
+ if (explicit) {
98
+ return [explicit];
99
+ }
100
+ const dirs = [];
101
+ const seen = new Set();
102
+ const add = (path) => {
103
+ const normalized = normalizeFilesystemPath(path);
104
+ const key = (0, project_paths_1.projectPathIdentityKey)(normalized);
105
+ if (!normalized || !key || seen.has(key)) {
106
+ return;
107
+ }
108
+ seen.add(key);
109
+ dirs.push(normalized);
110
+ };
111
+ add((0, hermes_hook_install_1.getHermesHooksDir)());
112
+ add((0, node_path_1.join)((0, node_os_1.homedir)(), ".codex", "codex-agents-office", "hermes-hooks"));
113
+ return dirs;
114
+ }
115
+ async function hermesHomeCandidatesFromDisk() {
116
+ const roots = new Set();
117
+ const explicitHome = normalizeFilesystemPath(process.env.HERMES_HOME);
118
+ if (explicitHome) {
119
+ roots.add(explicitHome);
120
+ }
121
+ const defaultRoot = normalizeFilesystemPath((0, node_path_1.join)((0, node_os_1.homedir)(), ".hermes"));
122
+ if (defaultRoot) {
123
+ roots.add(defaultRoot);
124
+ }
125
+ const rootCandidates = [...roots];
126
+ for (const root of rootCandidates) {
127
+ const profilesDir = (0, node_path_1.join)(root, "profiles");
128
+ const profiles = await (0, promises_1.readdir)(hostFilesystemPath(profilesDir), { withFileTypes: true }).catch(() => []);
129
+ for (const entry of profiles) {
130
+ if (!entry.isDirectory()) {
131
+ continue;
132
+ }
133
+ const profileHome = normalizeFilesystemPath((0, node_path_1.join)(profilesDir, entry.name));
134
+ if (profileHome) {
135
+ roots.add(profileHome);
136
+ }
137
+ }
138
+ }
139
+ const explicitStateDb = normalizeFilesystemPath(process.env.HERMES_STATE_DB);
140
+ if (explicitStateDb) {
141
+ roots.add((0, node_path_1.resolve)(explicitStateDb, ".."));
142
+ }
143
+ return [...roots];
144
+ }
145
+ async function readProcFile(path) {
146
+ try {
147
+ return await (0, promises_1.readFile)(path);
148
+ }
149
+ catch {
150
+ return null;
151
+ }
152
+ }
153
+ function splitNulFile(raw) {
154
+ if (!raw) {
155
+ return [];
156
+ }
157
+ return raw
158
+ .toString("utf8")
159
+ .split("\0")
160
+ .map((entry) => entry.trim())
161
+ .filter(Boolean);
162
+ }
163
+ function parseProcEnviron(raw) {
164
+ const env = {};
165
+ for (const entry of splitNulFile(raw)) {
166
+ const equalsIndex = entry.indexOf("=");
167
+ if (equalsIndex <= 0) {
168
+ continue;
169
+ }
170
+ env[entry.slice(0, equalsIndex)] = entry.slice(equalsIndex + 1);
171
+ }
172
+ return env;
173
+ }
174
+ function looksLikeHermesProcess(argv) {
175
+ if (argv.length === 0) {
176
+ return false;
177
+ }
178
+ return argv.some((arg) => {
179
+ const normalized = arg.replace(/\\/g, "/").toLowerCase();
180
+ const base = (0, node_path_1.basename)(normalized);
181
+ return base === "hermes"
182
+ || base === "hermes.exe"
183
+ || base === "hermes.cmd"
184
+ || normalized.endsWith("/hermes_cli/main.py")
185
+ || normalized.endsWith("/gateway/run.py")
186
+ || normalized.endsWith("/run_agent.py")
187
+ || (normalized.endsWith("/cli.py") && normalized.includes("hermes"));
188
+ });
189
+ }
190
+ function looksLikeHermesGatewayDaemon(argv) {
191
+ const normalized = argv.map((arg) => arg.replace(/\\/g, "/").toLowerCase());
192
+ const usesHermesEntrypoint = normalized.some((arg) => {
193
+ const base = (0, node_path_1.basename)(arg);
194
+ return base === "hermes"
195
+ || base === "hermes.exe"
196
+ || base === "hermes.cmd"
197
+ || arg.endsWith("/hermes_cli/main.py")
198
+ || arg === "hermes_cli.main";
199
+ });
200
+ return usesHermesEntrypoint
201
+ && normalized.includes("gateway")
202
+ && normalized.includes("run");
203
+ }
204
+ function looksLikeHermesRuntimeHomeProcess(argv, cwd) {
205
+ const normalizedCwd = (cwd ?? "").replace(/\\/g, "/").toLowerCase();
206
+ if (!normalizedCwd.endsWith("/hermes-agent")) {
207
+ return false;
208
+ }
209
+ const normalized = argv.map((arg) => arg.replace(/\\/g, "/").toLowerCase());
210
+ const launcherIndex = normalized.findIndex((arg) => {
211
+ const base = (0, node_path_1.basename)(arg);
212
+ return base === "hermes"
213
+ || base === "hermes.exe"
214
+ || base === "hermes.cmd";
215
+ });
216
+ return launcherIndex >= 0 && launcherIndex === normalized.length - 1;
217
+ }
218
+ async function scanHermesProcesses() {
219
+ if (process.platform !== "linux") {
220
+ return [];
221
+ }
222
+ const procEntries = await (0, promises_1.readdir)("/proc", { withFileTypes: true }).catch(() => []);
223
+ const processes = [];
224
+ for (const entry of procEntries) {
225
+ if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) {
226
+ continue;
227
+ }
228
+ const pid = Number.parseInt(entry.name, 10);
229
+ if (!Number.isFinite(pid) || pid === process.pid) {
230
+ continue;
231
+ }
232
+ const procRoot = (0, node_path_1.join)("/proc", entry.name);
233
+ const argv = splitNulFile(await readProcFile((0, node_path_1.join)(procRoot, "cmdline")));
234
+ const env = parseProcEnviron(await readProcFile((0, node_path_1.join)(procRoot, "environ")));
235
+ if (looksLikeHermesGatewayDaemon(argv)) {
236
+ continue;
237
+ }
238
+ if (!looksLikeHermesProcess(argv)) {
239
+ continue;
240
+ }
241
+ const cwd = await (0, promises_1.readlink)((0, node_path_1.join)(procRoot, "cwd")).catch(() => null);
242
+ const normalizedCwd = normalizeFilesystemPath(env.TERMINAL_CWD)
243
+ ?? normalizeFilesystemPath(env.HERMES_CWD)
244
+ ?? normalizeFilesystemPath(cwd);
245
+ if (looksLikeHermesRuntimeHomeProcess(argv, normalizedCwd)) {
246
+ continue;
247
+ }
248
+ const normalizedHome = normalizeFilesystemPath(env.HERMES_HOME);
249
+ const procStats = await (0, promises_1.stat)(procRoot).catch(() => null);
250
+ processes.push({
251
+ pid,
252
+ cwd: normalizedCwd,
253
+ hermesHome: normalizedHome,
254
+ command: argv.join(" "),
255
+ updatedAtMs: procStats?.mtimeMs ?? Date.now()
256
+ });
257
+ }
258
+ return processes;
259
+ }
260
+ function pythonCandidates() {
261
+ const candidates = [
262
+ process.env.HERMES_SQLITE_PYTHON,
263
+ "python3",
264
+ "python"
265
+ ].filter((entry) => typeof entry === "string" && entry.trim().length > 0);
266
+ return Array.from(new Set(candidates));
267
+ }
268
+ const HERMES_SQLITE_EXPORT_SCRIPT = `
269
+ import json
270
+ import sqlite3
271
+ import sys
272
+ import urllib.parse
273
+
274
+ db_path = sys.argv[1]
275
+ limit = int(sys.argv[2])
276
+ message_limit = int(sys.argv[3])
277
+ text_limit = int(sys.argv[4])
278
+ uri = "file:" + urllib.parse.quote(db_path) + "?mode=ro"
279
+ conn = sqlite3.connect(uri, uri=True, timeout=1.0)
280
+ conn.row_factory = sqlite3.Row
281
+ conn.execute("PRAGMA busy_timeout=1000")
282
+ sessions = conn.execute("""
283
+ SELECT
284
+ s.id,
285
+ substr(s.source, 1, ?) AS source,
286
+ substr(s.model, 1, ?) AS model,
287
+ s.parent_session_id,
288
+ p.ended_at AS parent_ended_at,
289
+ substr(p.end_reason, 1, ?) AS parent_end_reason,
290
+ s.started_at,
291
+ s.ended_at,
292
+ substr(s.end_reason, 1, ?) AS end_reason,
293
+ s.message_count,
294
+ s.tool_call_count,
295
+ substr(s.title, 1, ?) AS title,
296
+ substr(s.system_prompt, 1, ?) AS system_prompt,
297
+ COALESCE((SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id), s.started_at) AS last_active
298
+ FROM sessions s
299
+ LEFT JOIN sessions p ON p.id = s.parent_session_id
300
+ ORDER BY last_active DESC
301
+ LIMIT ?
302
+ """, (text_limit, text_limit, text_limit, text_limit, text_limit, text_limit, limit)).fetchall()
303
+
304
+ result = []
305
+ for session in sessions:
306
+ messages = conn.execute("""
307
+ SELECT
308
+ id,
309
+ role,
310
+ substr(content, 1, ?) AS content,
311
+ substr(tool_calls, 1, ?) AS tool_calls,
312
+ tool_name,
313
+ tool_call_id,
314
+ timestamp,
315
+ finish_reason,
316
+ NULL AS reasoning,
317
+ NULL AS reasoning_content
318
+ FROM messages
319
+ WHERE session_id = ?
320
+ ORDER BY timestamp DESC, id DESC
321
+ LIMIT ?
322
+ """, (text_limit, text_limit, session["id"], message_limit)).fetchall()
323
+ result.append({
324
+ "id": session["id"],
325
+ "source": session["source"],
326
+ "model": session["model"],
327
+ "parentSessionId": session["parent_session_id"],
328
+ "parentEndedAt": session["parent_ended_at"],
329
+ "parentEndReason": session["parent_end_reason"],
330
+ "startedAt": session["started_at"],
331
+ "endedAt": session["ended_at"],
332
+ "endReason": session["end_reason"],
333
+ "messageCount": session["message_count"],
334
+ "toolCallCount": session["tool_call_count"],
335
+ "title": session["title"],
336
+ "systemPrompt": session["system_prompt"],
337
+ "lastActive": session["last_active"],
338
+ "messages": [dict(row) for row in reversed(messages)],
339
+ })
340
+ print(json.dumps({"sessions": result}, ensure_ascii=False))
341
+ `;
342
+ async function exportHermesSqlite(dbPath, limit) {
343
+ let lastError = null;
344
+ for (const python of pythonCandidates()) {
345
+ try {
346
+ const { stdout } = await execFileAsync(python, ["-c", HERMES_SQLITE_EXPORT_SCRIPT, dbPath, String(limit), String(HERMES_SQLITE_MESSAGE_LIMIT), String(HERMES_SQLITE_TEXT_LIMIT)], {
347
+ timeout: HERMES_SQLITE_TIMEOUT_MS,
348
+ maxBuffer: HERMES_SQLITE_MAX_BUFFER
349
+ });
350
+ return JSON.parse(stdout);
351
+ }
352
+ catch (error) {
353
+ lastError = error instanceof Error ? error : new Error(String(error));
354
+ const code = typeof error === "object" && error !== null && "code" in error
355
+ ? String(error.code)
356
+ : "";
357
+ if (code !== "ENOENT") {
358
+ break;
359
+ }
360
+ }
361
+ }
362
+ throw lastError ?? new Error("No Python executable available for Hermes SQLite reads");
363
+ }
364
+ function normalizeSqliteMessage(raw) {
365
+ return {
366
+ id: typeof raw.id === "number" || typeof raw.id === "string" ? raw.id : "",
367
+ role: typeof raw.role === "string" ? raw.role : null,
368
+ content: raw.content,
369
+ toolCalls: raw.tool_calls,
370
+ toolName: typeof raw.tool_name === "string" ? raw.tool_name : null,
371
+ toolCallId: typeof raw.tool_call_id === "string" ? raw.tool_call_id : null,
372
+ timestamp: typeof raw.timestamp === "number" ? raw.timestamp : 0,
373
+ finishReason: typeof raw.finish_reason === "string" ? raw.finish_reason : null,
374
+ reasoning: typeof raw.reasoning === "string" ? raw.reasoning : null,
375
+ reasoningContent: typeof raw.reasoning_content === "string" ? raw.reasoning_content : null
376
+ };
377
+ }
378
+ async function loadHermesSqliteSessions(home, limit) {
379
+ const explicitDb = normalizeFilesystemPath(process.env.HERMES_STATE_DB);
380
+ const dbPath = explicitDb ?? (0, node_path_1.join)(home, "state.db");
381
+ if (!await pathExists(dbPath)) {
382
+ return [];
383
+ }
384
+ const exported = await exportHermesSqlite(hostFilesystemPath(dbPath), limit);
385
+ return exported.sessions.map((session) => ({
386
+ ...session,
387
+ startedAt: Number(session.startedAt) || 0,
388
+ endedAt: typeof session.endedAt === "number" && Number.isFinite(session.endedAt) && session.endedAt > 0
389
+ ? session.endedAt
390
+ : null,
391
+ parentEndedAt: typeof session.parentEndedAt === "number" && Number.isFinite(session.parentEndedAt) && session.parentEndedAt > 0
392
+ ? session.parentEndedAt
393
+ : null,
394
+ parentEndReason: typeof session.parentEndReason === "string" ? session.parentEndReason : null,
395
+ messageCount: Number(session.messageCount) || 0,
396
+ toolCallCount: Number(session.toolCallCount) || 0,
397
+ systemPrompt: typeof session.systemPrompt === "string" ? session.systemPrompt : null,
398
+ lastActive: Number(session.lastActive) || Number(session.startedAt) || 0,
399
+ home,
400
+ storage: "sqlite",
401
+ messages: session.messages.map((message) => normalizeSqliteMessage(message))
402
+ }));
403
+ }
404
+ async function loadHermesSessions(home, limit, notes) {
405
+ try {
406
+ const sqliteSessions = await loadHermesSqliteSessions(home, limit);
407
+ return sqliteSessions;
408
+ }
409
+ catch (error) {
410
+ const message = error instanceof Error ? error.message : String(error);
411
+ notes.push(`Hermes SQLite sessions unavailable for ${home}: ${message}`);
412
+ return [];
413
+ }
414
+ }
415
+ function parseMaybeJson(value) {
416
+ if (typeof value !== "string") {
417
+ return value;
418
+ }
419
+ const trimmed = value.trim();
420
+ if (!trimmed || !/^[{[]/.test(trimmed)) {
421
+ return value;
422
+ }
423
+ try {
424
+ return JSON.parse(trimmed);
425
+ }
426
+ catch {
427
+ return value;
428
+ }
429
+ }
430
+ function textFromContent(value) {
431
+ const parsed = parseMaybeJson(value);
432
+ if (typeof parsed === "string") {
433
+ return parsed.replace(/\s+/g, " ").trim();
434
+ }
435
+ if (Array.isArray(parsed)) {
436
+ return parsed
437
+ .map((entry) => {
438
+ if (typeof entry === "string") {
439
+ return entry;
440
+ }
441
+ if (entry && typeof entry === "object" && "text" in entry) {
442
+ return String(entry.text ?? "");
443
+ }
444
+ return "";
445
+ })
446
+ .filter(Boolean)
447
+ .join(" ")
448
+ .replace(/\s+/g, " ")
449
+ .trim();
450
+ }
451
+ if (parsed && typeof parsed === "object") {
452
+ const object = parsed;
453
+ for (const key of ["output", "error", "content", "message", "text"]) {
454
+ const candidate = object[key];
455
+ if (typeof candidate === "string" && candidate.trim()) {
456
+ return candidate.replace(/\s+/g, " ").trim();
457
+ }
458
+ }
459
+ }
460
+ return "";
461
+ }
462
+ function shorten(text, maxLength) {
463
+ const normalized = text.replace(/\s+/g, " ").trim();
464
+ if (normalized.length <= maxLength) {
465
+ return normalized;
466
+ }
467
+ return `${normalized.slice(0, Math.max(0, maxLength - 3))}...`;
468
+ }
469
+ function parseToolCalls(value) {
470
+ const parsed = parseMaybeJson(value);
471
+ const list = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
472
+ const calls = [];
473
+ for (const entry of list) {
474
+ if (!entry || typeof entry !== "object") {
475
+ continue;
476
+ }
477
+ const record = entry;
478
+ const functionRecord = record.function && typeof record.function === "object"
479
+ ? record.function
480
+ : null;
481
+ const name = typeof record.name === "string"
482
+ ? record.name
483
+ : typeof functionRecord?.name === "string"
484
+ ? functionRecord.name
485
+ : typeof record.tool_name === "string"
486
+ ? record.tool_name
487
+ : "";
488
+ const rawArgs = record.arguments ?? functionRecord?.arguments ?? record.args ?? {};
489
+ const args = parseMaybeJson(rawArgs);
490
+ calls.push({
491
+ name,
492
+ args: args && typeof args === "object" && !Array.isArray(args)
493
+ ? args
494
+ : {}
495
+ });
496
+ }
497
+ return calls.filter((call) => call.name.length > 0);
498
+ }
499
+ function latestAssistantText(messages) {
500
+ for (const message of [...messages].reverse()) {
501
+ if (message.role !== "assistant") {
502
+ continue;
503
+ }
504
+ const text = textFromContent(message.content);
505
+ if (text) {
506
+ return shorten(text, 240);
507
+ }
508
+ }
509
+ return null;
510
+ }
511
+ function latestUserContent(messages) {
512
+ for (const message of [...messages].reverse()) {
513
+ if (message.role !== "user") {
514
+ continue;
515
+ }
516
+ const text = textFromContent(message.content);
517
+ if (text) {
518
+ return text;
519
+ }
520
+ }
521
+ return null;
522
+ }
523
+ function hermesProjectDisplayLabel(projectRoot) {
524
+ const label = (0, project_paths_1.projectLabelFromRoot)(projectRoot) || "Hermes";
525
+ return label === "Ika Bot" ? "IkaBot" : label;
526
+ }
527
+ function titleCaseHermesWord(word) {
528
+ const lower = word.toLowerCase();
529
+ if (lower === "ikabot") {
530
+ return "IkaBot";
531
+ }
532
+ if (["api", "cli", "ui", "sdk", "mcp"].includes(lower)) {
533
+ return lower.toUpperCase();
534
+ }
535
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
536
+ }
537
+ function humanizeHermesSkillName(skillName) {
538
+ const normalized = skillName
539
+ .replace(/[_-]+/g, " ")
540
+ .replace(/\s+/g, " ")
541
+ .trim();
542
+ if (!normalized) {
543
+ return "";
544
+ }
545
+ return normalized.split(" ").map(titleCaseHermesWord).join(" ");
546
+ }
547
+ function hermesSkillNameFromText(text) {
548
+ const invoked = text.match(/invoked the "([^"]+)" skill/i)?.[1];
549
+ if (invoked && invoked.trim()) {
550
+ return invoked.trim();
551
+ }
552
+ const frontmatter = text.match(/^name:\s*([A-Za-z0-9_-]+)\s*$/m)?.[1];
553
+ return frontmatter && frontmatter.trim() ? frontmatter.trim() : null;
554
+ }
555
+ function hermesTitleFromText(text, maxLength = 54) {
556
+ let normalized = text
557
+ .replace(/`([^`]+)`/g, "$1")
558
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
559
+ .replace(/\s+/g, " ")
560
+ .trim();
561
+ if (!normalized) {
562
+ return null;
563
+ }
564
+ const skillName = hermesSkillNameFromText(normalized);
565
+ if (skillName) {
566
+ return shorten(humanizeHermesSkillName(skillName), maxLength);
567
+ }
568
+ normalized = normalized
569
+ .replace(/^got it\s*[-:\u2014]\s*/i, "")
570
+ .replace(/^done\s*[-:\u2014]\s*/i, "")
571
+ .replace(/^updated\s*[-:\u2014]\s*/i, "Updated: ")
572
+ .replace(/^\s*[-*]\s+/, "");
573
+ const firstBoundary = [
574
+ normalized.indexOf(" - "),
575
+ normalized.indexOf(" \u2014 "),
576
+ normalized.indexOf(". "),
577
+ normalized.indexOf("; ")
578
+ ].filter((index) => index > 12);
579
+ const boundary = firstBoundary.length > 0 ? Math.min(...firstBoundary) : -1;
580
+ let title = boundary > 0 ? normalized.slice(0, boundary) : normalized;
581
+ title = title.replace(/^[a-z]/, (letter) => letter.toUpperCase());
582
+ return shorten(title.replace(/\s+/g, " ").trim(), maxLength) || null;
583
+ }
584
+ function hermesTitleLooksGenericPrompt(title) {
585
+ return /^review the conversation above\b/i.test(title)
586
+ || /^continue\b/i.test(title)
587
+ || /^please continue\b/i.test(title);
588
+ }
589
+ function isHermesCronSessionId(sessionId) {
590
+ return HERMES_CRON_SESSION_ID_RE.test(sessionId);
591
+ }
592
+ function isHermesCronSession(session) {
593
+ const source = (session.source ?? "").trim().toLowerCase();
594
+ return isHermesCronSessionId(session.id) || source === "cron";
595
+ }
596
+ function hermesCronPromptText(text) {
597
+ let normalized = text
598
+ .replace(/^\[IMPORTANT:\s*You are running as a scheduled cron job\.[\s\S]*?\]\s*/i, "")
599
+ .replace(/^Cronjob Response:[\s\S]*?-------------\s*/i, "")
600
+ .trim();
601
+ if (!normalized || normalized === "[SILENT]") {
602
+ return null;
603
+ }
604
+ normalized = normalized.replace(/^#\s*Cron Job:\s*/i, "").trim();
605
+ return normalized || null;
606
+ }
607
+ function latestHermesSessionUserText(session, maxLength = 160) {
608
+ const text = latestUserContent(session.messages);
609
+ if (!text) {
610
+ return null;
611
+ }
612
+ const displayText = isHermesCronSession(session) ? hermesCronPromptText(text) : text;
613
+ return displayText ? shorten(displayText, maxLength) : null;
614
+ }
615
+ function hermesFallbackSessionLabel(projectRoot, sessionId) {
616
+ const projectLabel = hermesProjectDisplayLabel(projectRoot);
617
+ if (isHermesCronSessionId(sessionId)) {
618
+ return `${projectLabel} tick`;
619
+ }
620
+ return `${projectLabel} Hermes`;
621
+ }
622
+ function hermesHookSessionLabel(records, projectRoot, latestMessage) {
623
+ const latest = records[records.length - 1];
624
+ const sessionId = latest?.sessionId ?? "";
625
+ if (isHermesCronSessionId(sessionId)) {
626
+ return hermesFallbackSessionLabel(projectRoot, sessionId);
627
+ }
628
+ const messageTitle = latestMessage ? hermesTitleFromText(latestMessage) : null;
629
+ for (let index = records.length - 1; index >= 0; index -= 1) {
630
+ const record = records[index];
631
+ const userText = hermesHookText(record, ["user_message"]);
632
+ const userTitle = userText ? hermesTitleFromText(userText) : null;
633
+ if (userTitle) {
634
+ if (messageTitle && hermesTitleLooksGenericPrompt(userTitle)) {
635
+ return messageTitle;
636
+ }
637
+ return userTitle;
638
+ }
639
+ }
640
+ if (messageTitle) {
641
+ return messageTitle;
642
+ }
643
+ return hermesFallbackSessionLabel(projectRoot, sessionId);
644
+ }
645
+ function toolKind(toolName) {
646
+ const normalized = toolName.toLowerCase();
647
+ if (normalized === "todo") {
648
+ return { state: "planning", eventType: "plan", eventKind: "turn" };
649
+ }
650
+ if (normalized === "terminal"
651
+ || normalized === "process"
652
+ || normalized === "execute_code"
653
+ || normalized.includes("shell")
654
+ || normalized.includes("bash")) {
655
+ return { state: "running", eventType: "commandExecution", eventKind: "command" };
656
+ }
657
+ if (normalized.startsWith("mcp_")) {
658
+ return { state: "running", eventType: "mcpToolCall", eventKind: "tool" };
659
+ }
660
+ if (normalized === "write_file" || normalized === "patch") {
661
+ return { state: "editing", eventType: "fileChange", eventKind: "fileChange" };
662
+ }
663
+ if (normalized.includes("delegate") || normalized.includes("subagent")) {
664
+ return { state: "delegating", eventType: "collabAgentToolCall", eventKind: "subagent" };
665
+ }
666
+ if (normalized === "web_search" || normalized.includes("browser_search")) {
667
+ return { state: "scanning", eventType: "webSearch", eventKind: "tool" };
668
+ }
669
+ if (normalized === "read_file"
670
+ || normalized === "search_files"
671
+ || normalized === "skills_list"
672
+ || normalized === "skill_view"
673
+ || normalized === "web_extract"
674
+ || normalized.includes("search")
675
+ || normalized.includes("read")
676
+ || normalized.includes("list")) {
677
+ return { state: "scanning", eventType: "dynamicToolCall", eventKind: "tool" };
678
+ }
679
+ return { state: "running", eventType: "dynamicToolCall", eventKind: "tool" };
680
+ }
681
+ function toolTitle(toolName, args) {
682
+ if (toolName === "terminal" && typeof args.command === "string") {
683
+ return shorten(args.command, 120);
684
+ }
685
+ if (toolName === "process") {
686
+ const action = typeof args.action === "string" && args.action.trim() ? args.action.trim() : "manage";
687
+ const sessionId = typeof args.session_id === "string" && args.session_id.trim()
688
+ ? ` ${shorten(args.session_id.trim(), 40)}`
689
+ : "";
690
+ return `process ${action}${sessionId}`;
691
+ }
692
+ if (toolName === "todo") {
693
+ const todos = Array.isArray(args.todos) ? args.todos : null;
694
+ if (!todos) {
695
+ return "todo: reading task list";
696
+ }
697
+ return `todo: ${args.merge === true ? "updating" : "planning"} ${todos.length} task(s)`;
698
+ }
699
+ for (const key of ["path", "file_path", "filepath", "workdir", "query", "pattern", "name", "category"]) {
700
+ if (typeof args[key] === "string" && String(args[key]).trim()) {
701
+ return `${toolName}: ${shorten(String(args[key]), 90)}`;
702
+ }
703
+ }
704
+ return toolName;
705
+ }
706
+ function toolPath(projectRoot, cwd, args) {
707
+ for (const key of ["path", "file_path", "filepath", "workdir", "cwd", "dir", "directory"]) {
708
+ const value = args[key];
709
+ if (typeof value !== "string" || value.trim().length === 0) {
710
+ continue;
711
+ }
712
+ const normalized = normalizeCandidatePath(value, cwd ?? projectRoot);
713
+ if (normalized) {
714
+ return normalized;
715
+ }
716
+ }
717
+ return null;
718
+ }
719
+ function hermesHookChangedPaths(record, projectRoot) {
720
+ const value = record.payload.changed_paths;
721
+ if (!Array.isArray(value)) {
722
+ return [];
723
+ }
724
+ const paths = [];
725
+ for (const entry of value) {
726
+ if (typeof entry !== "string" || entry.trim().length === 0) {
727
+ continue;
728
+ }
729
+ const normalized = normalizeCandidatePath(entry, record.cwd ?? record.processCwd ?? projectRoot);
730
+ if (normalized && !paths.includes(normalized)) {
731
+ paths.push(normalized);
732
+ }
733
+ if (paths.length >= HERMES_HOOK_PATH_LIMIT) {
734
+ break;
735
+ }
736
+ }
737
+ return paths;
738
+ }
739
+ function hermesHookPreVerifyDetail(record, projectRoot) {
740
+ const paths = hermesHookChangedPaths(record, projectRoot);
741
+ const changedCount = paths.length;
742
+ const pathDetail = changedCount === 1
743
+ ? `Verifying ${(0, node_path_1.basename)(paths[0])}`
744
+ : changedCount > 1
745
+ ? `Verifying ${changedCount} changed files`
746
+ : "Hermes verification gate";
747
+ const attempt = hermesHookPayloadNumber(record, "attempt");
748
+ return attempt && attempt > 0 ? `${pathDetail} (attempt ${attempt + 1})` : pathDetail;
749
+ }
750
+ function toolActivityEvent(projectRoot, cwd, call) {
751
+ const kind = toolKind(call.name);
752
+ const path = toolPath(projectRoot, cwd, call.args);
753
+ return {
754
+ type: kind.eventType,
755
+ action: kind.eventKind === "command" ? "ran"
756
+ : kind.eventKind === "fileChange" ? "edited"
757
+ : "updated",
758
+ path,
759
+ title: toolTitle(call.name, call.args),
760
+ isImage: false
761
+ };
762
+ }
763
+ function messageLooksFailed(message) {
764
+ const parsed = parseMaybeJson(message.content);
765
+ if (parsed && typeof parsed === "object") {
766
+ const record = parsed;
767
+ if (typeof record.exit_code === "number" && record.exit_code !== 0) {
768
+ return true;
769
+ }
770
+ if (typeof record.returncode === "number" && record.returncode !== 0) {
771
+ return true;
772
+ }
773
+ if (typeof record.error === "string" && record.error.trim().length > 0) {
774
+ return true;
775
+ }
776
+ }
777
+ return false;
778
+ }
779
+ function latestMessageToolCall(message) {
780
+ if (!message) {
781
+ return null;
782
+ }
783
+ const calls = parseToolCalls(message.toolCalls);
784
+ if (calls.length > 0) {
785
+ return calls[calls.length - 1];
786
+ }
787
+ return null;
788
+ }
789
+ function normalizeCandidatePath(value, cwd) {
790
+ const cleaned = value
791
+ .trim()
792
+ .replace(/^["'`]+|["'`),.;:]+$/g, "");
793
+ if (!cleaned || cleaned.length > 400) {
794
+ return null;
795
+ }
796
+ const absolute = /^([a-zA-Z]:[\\/]|\/)/.test(cleaned)
797
+ ? cleaned
798
+ : cleaned.startsWith("./") || cleaned.startsWith("../")
799
+ ? (0, node_path_1.join)(cwd, cleaned)
800
+ : null;
801
+ return normalizeFilesystemPath(absolute);
802
+ }
803
+ function extractAbsolutePathCandidates(text) {
804
+ const matches = text.match(/(?:[a-zA-Z]:[\\/][^\s"'`<>]+|\/(?:mnt\/[a-zA-Z]|home|Users|workspace|tmp|var|opt|srv)\/[^\s"'`<>]+)/g);
805
+ return matches ?? [];
806
+ }
807
+ function cwdFromSystemPrompt(systemPrompt) {
808
+ if (!systemPrompt) {
809
+ return null;
810
+ }
811
+ for (const pattern of [
812
+ /^Current working directory:\s*(.+)$/im,
813
+ /^Working directory:\s*(.+)$/im,
814
+ /^cwd\s*[:=]\s*(.+)$/im
815
+ ]) {
816
+ const match = systemPrompt.match(pattern);
817
+ if (!match) {
818
+ continue;
819
+ }
820
+ const value = match[1]
821
+ .trim()
822
+ .split(/\s+/)[0]
823
+ ?.replace(/^["'`]+|["'`),.;:]+$/g, "");
824
+ const normalized = normalizeFilesystemPath(value);
825
+ if (normalized) {
826
+ return normalized;
827
+ }
828
+ }
829
+ return null;
830
+ }
831
+ function collectSessionPathCandidates(session, fallbackCwd) {
832
+ const paths = new Set();
833
+ const promptCwd = cwdFromSystemPrompt(session.systemPrompt);
834
+ if (promptCwd) {
835
+ paths.add(promptCwd);
836
+ }
837
+ for (const message of session.messages) {
838
+ for (const candidate of collectMessagePathCandidates(message, fallbackCwd)) {
839
+ paths.add(candidate);
840
+ }
841
+ }
842
+ if (fallbackCwd) {
843
+ paths.add(fallbackCwd);
844
+ }
845
+ return [...paths];
846
+ }
847
+ function collectMessagePathCandidates(message, fallbackCwd) {
848
+ const paths = new Set();
849
+ for (const call of parseToolCalls(message.toolCalls)) {
850
+ const fromTool = toolPath("/", fallbackCwd, call.args);
851
+ if (fromTool) {
852
+ paths.add(fromTool);
853
+ }
854
+ for (const candidate of extractAbsolutePathCandidates(JSON.stringify(call.args))) {
855
+ const normalized = normalizeCandidatePath(candidate, fallbackCwd ?? "/");
856
+ if (normalized) {
857
+ paths.add(normalized);
858
+ }
859
+ }
860
+ }
861
+ const text = textFromContent(message.content);
862
+ for (const candidate of extractAbsolutePathCandidates(text)) {
863
+ const normalized = normalizeCandidatePath(candidate, fallbackCwd ?? "/");
864
+ if (normalized) {
865
+ paths.add(normalized);
866
+ }
867
+ }
868
+ return [...paths];
869
+ }
870
+ function sortedLatestMessages(messages) {
871
+ return messages
872
+ .map((message, index) => ({ message, index }))
873
+ .sort((left, right) => {
874
+ const timeDelta = right.message.timestamp - left.message.timestamp;
875
+ if (timeDelta !== 0) {
876
+ return timeDelta;
877
+ }
878
+ return right.index - left.index;
879
+ })
880
+ .map((entry) => entry.message);
881
+ }
882
+ async function currentProjectRootForHermesSession(session, activeProcess) {
883
+ if (activeProcess?.cwd) {
884
+ const activeRoot = await resolveProjectRootForPath(activeProcess.cwd);
885
+ if (activeRoot) {
886
+ return activeRoot;
887
+ }
888
+ }
889
+ const sessionCwd = cwdFromSystemPrompt(session.systemPrompt);
890
+ for (const message of sortedLatestMessages(session.messages)) {
891
+ for (const candidate of collectMessagePathCandidates(message, sessionCwd)) {
892
+ const root = await resolveProjectRootForPath(candidate);
893
+ if (root) {
894
+ return root;
895
+ }
896
+ }
897
+ }
898
+ if (sessionCwd) {
899
+ return resolveProjectRootForPath(sessionCwd);
900
+ }
901
+ return null;
902
+ }
903
+ function pathWithinProject(projectRoot, candidate) {
904
+ if (!candidate) {
905
+ return false;
906
+ }
907
+ if ((0, project_paths_1.sameProjectPath)(projectRoot, candidate)) {
908
+ return true;
909
+ }
910
+ const rootKey = (0, project_paths_1.projectPathIdentityKey)(projectRoot);
911
+ const candidateKey = (0, project_paths_1.projectPathIdentityKey)(candidate);
912
+ return Boolean(rootKey && candidateKey && candidateKey.startsWith(`${rootKey}/`));
913
+ }
914
+ async function resolveProjectRootForPath(path) {
915
+ const normalized = normalizeFilesystemPath(path);
916
+ if (!normalized) {
917
+ return null;
918
+ }
919
+ const statResult = await (0, promises_1.stat)(hostFilesystemPath(normalized)).catch(() => null);
920
+ let current = statResult && statResult.isDirectory() ? normalized : (0, node_path_1.dirname)(normalized);
921
+ current = (0, project_paths_1.canonicalizeProjectPath)(current) ?? current;
922
+ while (current && current !== (0, node_path_1.dirname)(current)) {
923
+ if (await pathExists((0, node_path_1.join)(current, ".git"))) {
924
+ return current;
925
+ }
926
+ current = (0, node_path_1.dirname)(current);
927
+ }
928
+ return statResult ? (0, project_paths_1.canonicalizeProjectPath)(normalized) : null;
929
+ }
930
+ function sessionLabel(session, projectRoot) {
931
+ if (session.title && session.title.trim()) {
932
+ return shorten(session.title, 42);
933
+ }
934
+ if (isHermesCronSession(session)) {
935
+ return hermesFallbackSessionLabel(projectRoot, session.id);
936
+ }
937
+ const prompt = latestHermesSessionUserText(session, 160);
938
+ if (prompt) {
939
+ return shorten(prompt, 42);
940
+ }
941
+ const model = session.model ? session.model.replace(/[-_]+/g, " ") : "";
942
+ return model ? `Hermes ${shorten(model, 24)}` : `Hermes ${session.id.slice(0, 8)}`;
943
+ }
944
+ function sessionSourceKind(session) {
945
+ if (isHermesCronSession(session)) {
946
+ return "hermes:cron";
947
+ }
948
+ const source = session.source && session.source.trim() ? session.source.trim() : "local";
949
+ return session.model ? `hermes:${source}:${session.model}` : `hermes:${source}`;
950
+ }
951
+ function sessionRole(session) {
952
+ return isHermesCronSession(session) ? "temporary" : "hermes";
953
+ }
954
+ function sessionStatusText(session, isOngoing) {
955
+ if (isOngoing) {
956
+ return "active";
957
+ }
958
+ if (isHermesCronSession(session)) {
959
+ return "temporary";
960
+ }
961
+ return session.endReason ?? (session.endedAt ? "ended" : "open");
962
+ }
963
+ function isCliLikeHermesSource(session) {
964
+ const source = (session.source ?? "").toLowerCase();
965
+ return source === "" || source === "cli" || source === "local" || source === "unknown";
966
+ }
967
+ function isHermesCompressionContinuation(session) {
968
+ return Boolean(session.parentSessionId
969
+ && session.parentEndReason === "compression"
970
+ && session.parentEndedAt !== null
971
+ && session.startedAt >= session.parentEndedAt);
972
+ }
973
+ function hermesHookRecordTimestampMs(record, fallback) {
974
+ const value = record.timestamp;
975
+ if (typeof value === "string") {
976
+ const parsed = Date.parse(value);
977
+ if (Number.isFinite(parsed)) {
978
+ return parsed;
979
+ }
980
+ }
981
+ return fallback;
982
+ }
983
+ function hermesHookString(record, key) {
984
+ const value = record[key];
985
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
986
+ }
987
+ function hermesHookPayloadString(record, key) {
988
+ const value = record.payload[key];
989
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
990
+ }
991
+ function hermesHookPayloadNumber(record, key) {
992
+ const value = record.payload[key];
993
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
994
+ }
995
+ function hermesHookEventTurnId(record) {
996
+ return hermesHookPayloadString(record, "turn_id")
997
+ ?? hermesHookPayloadString(record, "parent_turn_id");
998
+ }
999
+ function hermesHookEventItemId(record) {
1000
+ return hermesHookPayloadString(record, "tool_call_id")
1001
+ ?? hermesHookPayloadString(record, "child_subagent_id")
1002
+ ?? hermesHookPayloadString(record, "child_session_id")
1003
+ ?? hermesHookPayloadString(record, "task_id");
1004
+ }
1005
+ function limitHermesHookValue(value, depth = 0) {
1006
+ if (depth > 5) {
1007
+ return typeof value === "string" ? shorten(value, HERMES_HOOK_TEXT_LIMIT) : String(value);
1008
+ }
1009
+ if (typeof value === "string") {
1010
+ return value.length <= HERMES_HOOK_TEXT_LIMIT
1011
+ ? value
1012
+ : `${value.slice(0, HERMES_HOOK_TEXT_LIMIT)}...[truncated]`;
1013
+ }
1014
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
1015
+ return value;
1016
+ }
1017
+ if (Array.isArray(value)) {
1018
+ return value.slice(0, 40).map((entry) => limitHermesHookValue(entry, depth + 1));
1019
+ }
1020
+ if (value && typeof value === "object") {
1021
+ return Object.fromEntries(Object.entries(value)
1022
+ .slice(0, 60)
1023
+ .map(([key, entry]) => [key, limitHermesHookValue(entry, depth + 1)]));
1024
+ }
1025
+ return undefined;
1026
+ }
1027
+ async function readHermesHookFileTail(path, size) {
1028
+ const hostPath = hostFilesystemPath(path);
1029
+ if (size <= HERMES_HOOK_FILE_BYTE_LIMIT) {
1030
+ return (0, promises_1.readFile)(hostPath, "utf8").catch(() => "");
1031
+ }
1032
+ const handle = await (0, promises_1.open)(hostPath, "r").catch(() => null);
1033
+ if (!handle) {
1034
+ return "";
1035
+ }
1036
+ try {
1037
+ const length = HERMES_HOOK_FILE_BYTE_LIMIT;
1038
+ const position = Math.max(0, size - length);
1039
+ const buffer = Buffer.alloc(length);
1040
+ const { bytesRead } = await handle.read(buffer, 0, length, position);
1041
+ const text = buffer.subarray(0, bytesRead).toString("utf8");
1042
+ const firstNewline = text.indexOf("\n");
1043
+ return firstNewline >= 0 ? text.slice(firstNewline + 1) : text;
1044
+ }
1045
+ finally {
1046
+ await handle.close().catch(() => undefined);
1047
+ }
1048
+ }
1049
+ function normalizeHermesHookRecord(raw, fallback) {
1050
+ const payload = raw.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload)
1051
+ ? limitHermesHookValue(raw.payload)
1052
+ : {};
1053
+ const sessionId = hermesHookString(raw, "session_id")
1054
+ ?? hermesHookString(payload, "session_id")
1055
+ ?? hermesHookString(payload, "parent_session_id")
1056
+ ?? hermesHookString(payload, "child_session_id")
1057
+ ?? fallback.sessionId;
1058
+ return {
1059
+ sessionId,
1060
+ eventName: hermesHookString(raw, "hook_event_name") ?? "unknown",
1061
+ timestampMs: hermesHookRecordTimestampMs(raw, fallback.updatedAtMs),
1062
+ cwd: normalizeFilesystemPath(hermesHookString(raw, "cwd")),
1063
+ processCwd: normalizeFilesystemPath(hermesHookString(raw, "process_cwd")),
1064
+ payload,
1065
+ raw: {}
1066
+ };
1067
+ }
1068
+ async function loadHermesHookSessions(limit) {
1069
+ const files = [];
1070
+ for (const hooksDir of hermesHookDirectories()) {
1071
+ const entries = await (0, promises_1.readdir)(hostFilesystemPath(hooksDir), { withFileTypes: true }).catch(() => []);
1072
+ for (const entry of entries) {
1073
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
1074
+ continue;
1075
+ }
1076
+ const filePath = (0, node_path_1.join)(hooksDir, entry.name);
1077
+ const fileStats = await (0, promises_1.stat)(hostFilesystemPath(filePath)).catch(() => null);
1078
+ if (!fileStats?.isFile()) {
1079
+ continue;
1080
+ }
1081
+ files.push({
1082
+ sessionId: entry.name.replace(/\.jsonl$/i, ""),
1083
+ path: filePath,
1084
+ updatedAtMs: fileStats.mtimeMs,
1085
+ size: fileStats.size
1086
+ });
1087
+ }
1088
+ }
1089
+ const groups = new Map();
1090
+ for (const file of files
1091
+ .sort((left, right) => right.updatedAtMs - left.updatedAtMs)
1092
+ .slice(0, Math.max(limit, HERMES_SESSION_SCAN_LIMIT))) {
1093
+ const raw = await readHermesHookFileTail(file.path, file.size);
1094
+ const records = raw
1095
+ .split(/\r?\n/)
1096
+ .map((line) => line.trim())
1097
+ .filter(Boolean)
1098
+ .slice(-HERMES_HOOK_RECORD_LIMIT)
1099
+ .map((line) => {
1100
+ if (Buffer.byteLength(line, "utf8") > HERMES_HOOK_LINE_BYTE_LIMIT) {
1101
+ return null;
1102
+ }
1103
+ try {
1104
+ return normalizeHermesHookRecord(JSON.parse(line), file);
1105
+ }
1106
+ catch {
1107
+ return null;
1108
+ }
1109
+ })
1110
+ .filter((record) => record !== null)
1111
+ .sort((left, right) => left.timestampMs - right.timestampMs);
1112
+ if (records.length > 0) {
1113
+ const sessionId = records[records.length - 1].sessionId;
1114
+ const existing = groups.get(sessionId) ?? [];
1115
+ groups.set(sessionId, [...existing, ...records].sort((left, right) => left.timestampMs - right.timestampMs));
1116
+ }
1117
+ }
1118
+ return groups;
1119
+ }
1120
+ function collectHermesHookPathCandidates(records) {
1121
+ const paths = new Set();
1122
+ for (const record of records) {
1123
+ for (const candidate of hermesHookPayloadPathCandidates(record)) {
1124
+ paths.add(candidate);
1125
+ }
1126
+ if (record.cwd) {
1127
+ paths.add(record.cwd);
1128
+ }
1129
+ if (record.processCwd) {
1130
+ paths.add(record.processCwd);
1131
+ }
1132
+ }
1133
+ return [...paths].slice(0, HERMES_HOOK_PATH_LIMIT);
1134
+ }
1135
+ function hermesHookPayloadPathCandidates(record) {
1136
+ const paths = new Set();
1137
+ const text = JSON.stringify(record.payload).slice(0, HERMES_HOOK_TEXT_LIMIT * 4);
1138
+ for (const candidate of extractAbsolutePathCandidates(text)) {
1139
+ const normalized = normalizeCandidatePath(candidate, record.cwd ?? record.processCwd ?? "/");
1140
+ if (normalized) {
1141
+ paths.add(normalized);
1142
+ }
1143
+ if (paths.size >= HERMES_HOOK_PATH_LIMIT) {
1144
+ break;
1145
+ }
1146
+ }
1147
+ return [...paths];
1148
+ }
1149
+ function recentHermesHookRecordsForProjectRelation(records) {
1150
+ return records.slice(-HERMES_PROJECT_RELATION_ACTION_WINDOW);
1151
+ }
1152
+ async function currentProjectRootForHermesHookSession(records) {
1153
+ const recentRecords = recentHermesHookRecordsForProjectRelation(records);
1154
+ for (const record of [...recentRecords].reverse()) {
1155
+ const candidates = hermesHookPayloadPathCandidates(record);
1156
+ for (const candidate of candidates) {
1157
+ const root = await resolveProjectRootForPath(candidate);
1158
+ if (root) {
1159
+ return root;
1160
+ }
1161
+ }
1162
+ }
1163
+ for (const record of [...recentRecords].reverse()) {
1164
+ const candidates = [record.cwd, record.processCwd].filter((entry) => Boolean(entry));
1165
+ for (const candidate of candidates) {
1166
+ const root = await resolveProjectRootForPath(candidate);
1167
+ if (root) {
1168
+ return root;
1169
+ }
1170
+ }
1171
+ }
1172
+ return null;
1173
+ }
1174
+ function hermesHookNestedString(record, path) {
1175
+ let value = record.payload;
1176
+ for (const key of path) {
1177
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1178
+ return null;
1179
+ }
1180
+ value = value[key];
1181
+ }
1182
+ return typeof value === "string" && value.trim() ? shorten(value, 240) : null;
1183
+ }
1184
+ function hermesHookText(record, keys) {
1185
+ for (const key of keys) {
1186
+ const value = record.payload[key];
1187
+ if (typeof value === "string" && value.trim()) {
1188
+ return shorten(value, 240);
1189
+ }
1190
+ }
1191
+ if (keys.includes("user_message")) {
1192
+ return hermesHookNestedString(record, ["event", "text"])
1193
+ ?? hermesHookNestedString(record, ["event", "message"]);
1194
+ }
1195
+ return null;
1196
+ }
1197
+ function hermesHookToolName(record) {
1198
+ const value = record.payload.tool_name;
1199
+ if (typeof value === "string" && value.trim()) {
1200
+ return value.trim();
1201
+ }
1202
+ if (record.eventName === "transform_terminal_output") {
1203
+ return "terminal";
1204
+ }
1205
+ return "tool";
1206
+ }
1207
+ function hermesHookPlatform(record) {
1208
+ return hermesHookPayloadString(record, "platform");
1209
+ }
1210
+ function isNonSessionHermesHookId(sessionId) {
1211
+ return sessionId === "default"
1212
+ || sessionId.startsWith("process-")
1213
+ || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId);
1214
+ }
1215
+ function isDurableHermesHookSessionId(sessionId) {
1216
+ return /^\d{8}_\d{6}_[0-9a-f]+$/i.test(sessionId)
1217
+ || isHermesCronSessionId(sessionId);
1218
+ }
1219
+ function hermesHookSourceKind(sessionId) {
1220
+ return isHermesCronSessionId(sessionId) ? "hermes:cron" : "hermes:hook";
1221
+ }
1222
+ function hermesHookRole(sessionId) {
1223
+ return isHermesCronSessionId(sessionId) ? "temporary" : "hermes";
1224
+ }
1225
+ function hermesHookStatusText(sessionId, isOngoing) {
1226
+ if (isOngoing) {
1227
+ return "active";
1228
+ }
1229
+ return isHermesCronSessionId(sessionId) ? "temporary" : "hook";
1230
+ }
1231
+ function canonicalHermesSessionIdForHookRecords(records, sessions) {
1232
+ const sessionById = new Map(sessions.map((session) => [session.id, session]));
1233
+ for (const record of [...records].reverse()) {
1234
+ if (sessionById.has(record.sessionId)) {
1235
+ return record.sessionId;
1236
+ }
1237
+ for (const key of ["session_id", "parent_session_id", "child_session_id", "conversation_id"]) {
1238
+ const candidate = hermesHookPayloadString(record, key);
1239
+ if (candidate && sessionById.has(candidate)) {
1240
+ return candidate;
1241
+ }
1242
+ }
1243
+ }
1244
+ const latest = records[records.length - 1];
1245
+ if (!latest || !isNonSessionHermesHookId(latest.sessionId)) {
1246
+ return null;
1247
+ }
1248
+ const latestSeconds = latest.timestampMs / 1000;
1249
+ const platform = hermesHookPlatform(latest);
1250
+ const candidates = sessions
1251
+ .filter((session) => {
1252
+ const started = Number(session.startedAt);
1253
+ const ended = session.endedAt === null ? Number.POSITIVE_INFINITY : Number(session.endedAt);
1254
+ return Number.isFinite(started)
1255
+ && latestSeconds >= started - 60
1256
+ && latestSeconds <= ended + 10 * 60;
1257
+ })
1258
+ .sort((left, right) => {
1259
+ const leftPlatformScore = platform && left.source === platform ? 1 : 0;
1260
+ const rightPlatformScore = platform && right.source === platform ? 1 : 0;
1261
+ if (rightPlatformScore !== leftPlatformScore) {
1262
+ return rightPlatformScore - leftPlatformScore;
1263
+ }
1264
+ const leftDistance = Math.min(Math.abs(left.lastActive - latestSeconds), Math.abs(left.startedAt - latestSeconds), left.endedAt === null ? Number.POSITIVE_INFINITY : Math.abs(left.endedAt - latestSeconds));
1265
+ const rightDistance = Math.min(Math.abs(right.lastActive - latestSeconds), Math.abs(right.startedAt - latestSeconds), right.endedAt === null ? Number.POSITIVE_INFINITY : Math.abs(right.endedAt - latestSeconds));
1266
+ if (leftDistance !== rightDistance) {
1267
+ return leftDistance - rightDistance;
1268
+ }
1269
+ return right.lastActive - left.lastActive;
1270
+ });
1271
+ return candidates[0]?.id ?? null;
1272
+ }
1273
+ function hermesHookToolArgs(record) {
1274
+ const value = record.payload.args;
1275
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1276
+ return value;
1277
+ }
1278
+ if (record.eventName === "transform_terminal_output" && typeof record.payload.command === "string") {
1279
+ return { command: record.payload.command };
1280
+ }
1281
+ return {};
1282
+ }
1283
+ function hermesHookIsToolActivity(eventName) {
1284
+ return eventName === "pre_tool_call"
1285
+ || eventName === "post_tool_call"
1286
+ || eventName === "transform_tool_result"
1287
+ || eventName === "transform_terminal_output";
1288
+ }
1289
+ function hermesHookIsToolCompletion(eventName) {
1290
+ return eventName === "post_tool_call"
1291
+ || eventName === "transform_tool_result"
1292
+ || eventName === "transform_terminal_output";
1293
+ }
1294
+ function hermesHookResponseText(record) {
1295
+ return hermesHookText(record, ["assistant_response", "response_text"]);
1296
+ }
1297
+ function hermesHookTerminalOutput(record) {
1298
+ return hermesHookText(record, ["output", "result"]);
1299
+ }
1300
+ function hermesHookApiDetail(record) {
1301
+ const provider = typeof record.payload.provider === "string" ? record.payload.provider : "";
1302
+ const model = typeof record.payload.model === "string" ? record.payload.model : "";
1303
+ const finishReason = typeof record.payload.finish_reason === "string" ? record.payload.finish_reason : "";
1304
+ const modelLabel = model.trim() || provider.trim();
1305
+ if (record.eventName === "pre_api_request") {
1306
+ return modelLabel ? `Thinking with ${modelLabel}` : "Thinking with model";
1307
+ }
1308
+ if (/tool/i.test(finishReason)) {
1309
+ return modelLabel ? `Using tools after ${modelLabel}` : "Using tools";
1310
+ }
1311
+ if (/stop|complete|success/i.test(finishReason)) {
1312
+ return modelLabel ? `Answered with ${modelLabel}` : "Answered";
1313
+ }
1314
+ return modelLabel ? `Model request ${modelLabel}` : "Model request";
1315
+ }
1316
+ function hermesHookResultLooksFailed(record) {
1317
+ if (typeof record.payload.returncode === "number"
1318
+ && record.payload.returncode !== 0) {
1319
+ return true;
1320
+ }
1321
+ if (typeof record.payload.status === "string"
1322
+ && /fail|error/i.test(record.payload.status)) {
1323
+ return true;
1324
+ }
1325
+ const result = parseMaybeJson(record.payload.result ?? record.payload.output);
1326
+ if (result && typeof result === "object") {
1327
+ const object = result;
1328
+ if (typeof object.error === "string" && object.error.trim()) {
1329
+ return true;
1330
+ }
1331
+ if (typeof object.exit_code === "number" && object.exit_code !== 0) {
1332
+ return true;
1333
+ }
1334
+ if (typeof object.returncode === "number" && object.returncode !== 0) {
1335
+ return true;
1336
+ }
1337
+ if (typeof object.status === "string" && /fail|error/i.test(object.status)) {
1338
+ return true;
1339
+ }
1340
+ }
1341
+ return false;
1342
+ }
1343
+ function hermesHookToolActivityEvent(input) {
1344
+ const toolName = hermesHookToolName(input.record);
1345
+ const args = hermesHookToolArgs(input.record);
1346
+ const kind = toolKind(toolName);
1347
+ const title = input.failed ? `${toolName} failed` : toolTitle(toolName, args);
1348
+ return {
1349
+ type: kind.eventType,
1350
+ action: kind.eventKind === "command" ? "ran"
1351
+ : kind.eventKind === "fileChange" ? "edited"
1352
+ : "updated",
1353
+ path: toolPath(input.projectRoot, input.record.cwd, args) ?? input.paths[0] ?? input.projectRoot,
1354
+ title,
1355
+ isImage: false
1356
+ };
1357
+ }
1358
+ function hermesHookSubagentStatus(record) {
1359
+ const value = record.payload.child_status;
1360
+ return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : null;
1361
+ }
1362
+ function hermesHookSubagentRole(record) {
1363
+ return hermesHookText(record, ["child_role"]);
1364
+ }
1365
+ function hermesHookSubagentGoal(record) {
1366
+ return hermesHookText(record, ["child_goal"]);
1367
+ }
1368
+ function hermesHookSubagentTitle(record) {
1369
+ const role = hermesHookSubagentRole(record);
1370
+ return role ? `Subagent ${role}` : "Subagent";
1371
+ }
1372
+ function hermesHookToolSummary(record) {
1373
+ const toolName = hermesHookToolName(record);
1374
+ const args = hermesHookToolArgs(record);
1375
+ const failed = hermesHookIsToolCompletion(record.eventName) && hermesHookResultLooksFailed(record);
1376
+ const kind = toolKind(toolName);
1377
+ return {
1378
+ toolName,
1379
+ args,
1380
+ failed,
1381
+ kind,
1382
+ title: failed ? `${toolName} failed` : toolTitle(toolName, args),
1383
+ latestMessage: hermesHookResponseText(record) ?? hermesHookTerminalOutput(record)
1384
+ };
1385
+ }
1386
+ function hermesHookMeaningfulText(record) {
1387
+ const assistantText = hermesHookResponseText(record);
1388
+ if (assistantText) {
1389
+ return assistantText;
1390
+ }
1391
+ const childSummary = hermesHookText(record, ["child_summary"]);
1392
+ if (childSummary) {
1393
+ return childSummary;
1394
+ }
1395
+ const terminalOutput = hermesHookTerminalOutput(record);
1396
+ if (terminalOutput) {
1397
+ return terminalOutput;
1398
+ }
1399
+ if (hermesHookIsToolActivity(record.eventName)) {
1400
+ return hermesHookToolSummary(record).title;
1401
+ }
1402
+ return null;
1403
+ }
1404
+ function hermesHookDisplayUserText(record) {
1405
+ const userText = hermesHookText(record, ["user_message"]);
1406
+ if (!userText) {
1407
+ return null;
1408
+ }
1409
+ if (/^\[IMPORTANT:/i.test(userText) && hermesSkillNameFromText(userText)) {
1410
+ return null;
1411
+ }
1412
+ const title = hermesTitleFromText(userText);
1413
+ if (title && hermesTitleLooksGenericPrompt(title)) {
1414
+ return null;
1415
+ }
1416
+ return userText;
1417
+ }
1418
+ function hermesHookConversationText(record) {
1419
+ return hermesHookResponseText(record)
1420
+ ?? hermesHookText(record, ["child_summary"]);
1421
+ }
1422
+ function latestHermesHookMeaningfulText(records) {
1423
+ for (let index = records.length - 1; index >= 0; index -= 1) {
1424
+ const text = hermesHookMeaningfulText(records[index]);
1425
+ if (text) {
1426
+ return text;
1427
+ }
1428
+ }
1429
+ return null;
1430
+ }
1431
+ function latestHermesHookConversationText(records) {
1432
+ for (let index = records.length - 1; index >= 0; index -= 1) {
1433
+ const text = hermesHookConversationText(records[index]);
1434
+ if (text) {
1435
+ return text;
1436
+ }
1437
+ }
1438
+ return null;
1439
+ }
1440
+ function summarizeHermesHookSession(input) {
1441
+ const latest = input.records[input.records.length - 1];
1442
+ const ageMs = input.now - latest.timestampMs;
1443
+ const recentOpen = ageMs <= HERMES_RECENT_OPEN_WINDOW_MS;
1444
+ const recentDone = ageMs <= HERMES_RECENT_DONE_WINDOW_MS;
1445
+ const isFinalized = latest.eventName === "on_session_finalize";
1446
+ const assistantText = hermesHookResponseText(latest);
1447
+ const userText = hermesHookText(latest, ["user_message"]);
1448
+ const conversationText = latestHermesHookConversationText(input.records);
1449
+ if (hermesHookIsToolActivity(latest.eventName)) {
1450
+ const toolSummary = hermesHookToolSummary(latest);
1451
+ return {
1452
+ state: toolSummary.failed ? "blocked" : toolSummary.kind.state,
1453
+ isOngoing: recentOpen && !isFinalized,
1454
+ detail: toolSummary.title,
1455
+ paths: input.paths,
1456
+ activityEvent: hermesHookToolActivityEvent({
1457
+ record: latest,
1458
+ projectRoot: input.projectRoot,
1459
+ paths: input.paths,
1460
+ failed: toolSummary.failed
1461
+ }),
1462
+ latestMessage: conversationText,
1463
+ updatedAtMs: latest.timestampMs,
1464
+ stoppedAtMs: toolSummary.failed || isFinalized ? latest.timestampMs : null
1465
+ };
1466
+ }
1467
+ if (latest.eventName === "post_llm_call" || latest.eventName === "transform_llm_output") {
1468
+ return {
1469
+ state: recentDone ? "done" : "idle",
1470
+ isOngoing: recentDone && !isFinalized,
1471
+ detail: assistantText ?? "Hermes reply",
1472
+ paths: input.paths,
1473
+ activityEvent: assistantText
1474
+ ? {
1475
+ type: "agentMessage",
1476
+ action: "said",
1477
+ path: input.paths[0] ?? input.projectRoot,
1478
+ title: assistantText,
1479
+ isImage: false
1480
+ }
1481
+ : null,
1482
+ latestMessage: conversationText ?? assistantText,
1483
+ updatedAtMs: latest.timestampMs,
1484
+ stoppedAtMs: recentDone || isFinalized ? latest.timestampMs : null
1485
+ };
1486
+ }
1487
+ if (latest.eventName === "pre_api_request" || latest.eventName === "post_api_request") {
1488
+ const detail = hermesHookApiDetail(latest);
1489
+ return {
1490
+ state: latest.eventName === "pre_api_request" ? "thinking" : (recentOpen ? "thinking" : "idle"),
1491
+ isOngoing: recentOpen && !isFinalized,
1492
+ detail,
1493
+ paths: input.paths,
1494
+ activityEvent: {
1495
+ type: "reasoning",
1496
+ action: "updated",
1497
+ path: input.paths[0] ?? input.projectRoot,
1498
+ title: detail,
1499
+ isImage: false
1500
+ },
1501
+ latestMessage: conversationText,
1502
+ updatedAtMs: latest.timestampMs,
1503
+ stoppedAtMs: recentOpen && !isFinalized ? null : latest.timestampMs
1504
+ };
1505
+ }
1506
+ if (latest.eventName === "pre_verify") {
1507
+ const changedPaths = hermesHookChangedPaths(latest, input.projectRoot);
1508
+ const detail = hermesHookPreVerifyDetail(latest, input.projectRoot);
1509
+ return {
1510
+ state: "validating",
1511
+ isOngoing: recentOpen && !isFinalized,
1512
+ detail,
1513
+ paths: [...new Set([...changedPaths, ...input.paths])].slice(0, HERMES_HOOK_PATH_LIMIT),
1514
+ activityEvent: {
1515
+ type: "other",
1516
+ action: "updated",
1517
+ path: changedPaths[0] ?? input.paths[0] ?? input.projectRoot,
1518
+ title: detail,
1519
+ isImage: false
1520
+ },
1521
+ latestMessage: conversationText,
1522
+ updatedAtMs: latest.timestampMs,
1523
+ stoppedAtMs: recentOpen && !isFinalized ? null : latest.timestampMs
1524
+ };
1525
+ }
1526
+ if (latest.eventName === "subagent_start") {
1527
+ const title = hermesHookSubagentTitle(latest);
1528
+ const childGoal = hermesHookSubagentGoal(latest);
1529
+ return {
1530
+ state: "delegating",
1531
+ isOngoing: recentOpen && !isFinalized,
1532
+ detail: childGoal ?? `${title} starting`,
1533
+ paths: input.paths,
1534
+ activityEvent: {
1535
+ type: "collabAgentToolCall",
1536
+ action: "updated",
1537
+ path: input.paths[0] ?? input.projectRoot,
1538
+ title: childGoal ?? title,
1539
+ isImage: false
1540
+ },
1541
+ latestMessage: conversationText,
1542
+ updatedAtMs: latest.timestampMs,
1543
+ stoppedAtMs: recentOpen && !isFinalized ? null : latest.timestampMs
1544
+ };
1545
+ }
1546
+ if (latest.eventName === "subagent_stop") {
1547
+ const childRole = hermesHookSubagentRole(latest);
1548
+ const childSummary = hermesHookText(latest, ["child_summary"]);
1549
+ const status = hermesHookSubagentStatus(latest);
1550
+ const failed = status ? /fail|error|interrupt/.test(status) : false;
1551
+ const title = childRole ? `Subagent ${childRole}` : "Subagent";
1552
+ return {
1553
+ state: failed ? "blocked" : "delegating",
1554
+ isOngoing: recentOpen && !isFinalized,
1555
+ detail: childSummary ?? (status ? `${title} ${status}` : `${title} stopped`),
1556
+ paths: input.paths,
1557
+ activityEvent: {
1558
+ type: "collabAgentToolCall",
1559
+ action: "updated",
1560
+ path: input.paths[0] ?? input.projectRoot,
1561
+ title: childSummary ?? title,
1562
+ isImage: false
1563
+ },
1564
+ latestMessage: conversationText ?? childSummary,
1565
+ updatedAtMs: latest.timestampMs,
1566
+ stoppedAtMs: failed || !recentOpen ? latest.timestampMs : null
1567
+ };
1568
+ }
1569
+ if (latest.eventName === "on_session_end" || latest.eventName === "on_session_finalize") {
1570
+ const meaningfulText = conversationText ?? latestHermesHookMeaningfulText(input.records);
1571
+ return {
1572
+ state: "done",
1573
+ isOngoing: false,
1574
+ detail: meaningfulText ?? (latest.eventName === "on_session_end" ? "Hermes session ended" : "Hermes session finalized"),
1575
+ paths: input.paths,
1576
+ activityEvent: null,
1577
+ latestMessage: meaningfulText ?? assistantText,
1578
+ updatedAtMs: latest.timestampMs,
1579
+ stoppedAtMs: latest.timestampMs
1580
+ };
1581
+ }
1582
+ return {
1583
+ state: "planning",
1584
+ isOngoing: recentOpen,
1585
+ detail: userText ?? "Hermes session activity",
1586
+ paths: input.paths,
1587
+ activityEvent: {
1588
+ type: latest.eventName === "pre_gateway_dispatch" ? "userMessage" : "plan",
1589
+ action: "updated",
1590
+ path: input.paths[0] ?? input.projectRoot,
1591
+ title: userText ?? latest.eventName,
1592
+ isImage: false
1593
+ },
1594
+ latestMessage: conversationText ?? assistantText,
1595
+ updatedAtMs: latest.timestampMs,
1596
+ stoppedAtMs: recentOpen ? null : latest.timestampMs
1597
+ };
1598
+ }
1599
+ function buildHermesHookEvents(input) {
1600
+ const records = input.records.slice(-20);
1601
+ const startIndex = input.records.length - records.length;
1602
+ return records.map((record, index) => {
1603
+ const toolName = hermesHookToolName(record);
1604
+ const kind = hermesHookIsToolActivity(record.eventName) ? toolKind(toolName) : null;
1605
+ const args = kind ? hermesHookToolArgs(record) : {};
1606
+ const toolEventTitle = kind ? toolTitle(toolName, args) : "";
1607
+ const toolEventPath = kind
1608
+ ? toolPath(input.projectRoot, record.cwd, args) ?? input.projectRoot
1609
+ : input.projectRoot;
1610
+ const command = kind?.eventKind === "command"
1611
+ ? (typeof args.command === "string" && args.command.trim()
1612
+ ? shorten(args.command, 240)
1613
+ : toolName === "process" ? toolEventTitle : undefined)
1614
+ : undefined;
1615
+ const assistantText = hermesHookResponseText(record) ?? hermesHookTerminalOutput(record);
1616
+ const userText = hermesHookText(record, ["user_message"]);
1617
+ const lifecycleText = record.eventName === "on_session_end" || record.eventName === "on_session_finalize"
1618
+ ? latestHermesHookMeaningfulText(input.records.slice(0, startIndex + index + 1))
1619
+ : null;
1620
+ const changedPaths = record.eventName === "pre_verify"
1621
+ ? hermesHookChangedPaths(record, input.projectRoot)
1622
+ : [];
1623
+ const preVerifyDetail = record.eventName === "pre_verify"
1624
+ ? hermesHookPreVerifyDetail(record, input.projectRoot)
1625
+ : null;
1626
+ const subagentLifecycle = record.eventName === "subagent_start" || record.eventName === "subagent_stop";
1627
+ const subagentTitle = subagentLifecycle ? hermesHookSubagentTitle(record) : null;
1628
+ const subagentDetail = record.eventName === "subagent_start"
1629
+ ? hermesHookSubagentGoal(record) ?? `${subagentTitle ?? "Subagent"} starting`
1630
+ : record.eventName === "subagent_stop"
1631
+ ? hermesHookText(record, ["child_summary"])
1632
+ ?? (hermesHookSubagentStatus(record)
1633
+ ? `${subagentTitle ?? "Subagent"} ${hermesHookSubagentStatus(record)}`
1634
+ : `${subagentTitle ?? "Subagent"} stopped`)
1635
+ : null;
1636
+ const eventPath = changedPaths[0] ?? toolEventPath;
1637
+ const method = kind?.eventType === "plan"
1638
+ ? "turn/plan/updated"
1639
+ : kind?.eventKind === "command"
1640
+ ? record.eventName === "pre_tool_call" ? "item/started" : "item/commandExecution/outputDelta"
1641
+ : kind?.eventKind === "fileChange"
1642
+ ? record.eventName === "pre_tool_call" ? "item/started" : "item/fileChange/outputDelta"
1643
+ : kind?.eventKind === "tool"
1644
+ ? toolName.startsWith("mcp_") && record.eventName !== "pre_tool_call" ? "item/mcpToolCall/progress" : "item/tool/call"
1645
+ : record.eventName === "pre_llm_call" || record.eventName === "pre_gateway_dispatch"
1646
+ ? "hermes/userMessage"
1647
+ : record.eventName === "post_llm_call" || record.eventName === "transform_llm_output"
1648
+ ? "hermes/agentMessage"
1649
+ : record.eventName === "pre_verify"
1650
+ ? "hermes/preVerify"
1651
+ : record.eventName === "subagent_start"
1652
+ ? "hermes/subagentStart"
1653
+ : record.eventName === "subagent_stop"
1654
+ ? "hermes/subagentStop"
1655
+ : `hermes/${record.eventName}`;
1656
+ const eventKind = kind?.eventKind
1657
+ ?? (subagentLifecycle ? "subagent"
1658
+ : record.eventName === "pre_api_request" || record.eventName === "post_api_request" || record.eventName === "pre_verify" ? "status" : "message");
1659
+ const apiDetail = record.eventName === "pre_api_request" || record.eventName === "post_api_request"
1660
+ ? hermesHookApiDetail(record)
1661
+ : null;
1662
+ const detail = kind ? toolEventTitle
1663
+ : preVerifyDetail ?? subagentDetail ?? apiDetail ?? lifecycleText ?? assistantText ?? userText ?? record.eventName;
1664
+ const action = kind?.eventKind === "command" ? "ran"
1665
+ : kind?.eventKind === "fileChange" ? "edited"
1666
+ : kind?.eventType === "plan" ? "updated"
1667
+ : eventKind === "message" ? "said"
1668
+ : "updated";
1669
+ return {
1670
+ id: `${input.projectRoot}::hermes-hook::${record.sessionId}::${record.timestampMs}::${index}`,
1671
+ source: "hermes",
1672
+ confidence: "typed",
1673
+ threadId: input.threadId ?? record.sessionId,
1674
+ createdAt: new Date(record.timestampMs).toISOString(),
1675
+ method,
1676
+ turnId: hermesHookEventTurnId(record) ?? undefined,
1677
+ itemId: hermesHookEventItemId(record) ?? undefined,
1678
+ requestId: hermesHookPayloadString(record, "api_request_id") ?? undefined,
1679
+ itemType: kind?.eventType === "mcpToolCall" ? "mcpToolCall"
1680
+ : kind?.eventType === "dynamicToolCall" ? "dynamicToolCall"
1681
+ : subagentLifecycle ? "collabAgentToolCall"
1682
+ : eventKind === "message" && (record.eventName === "pre_llm_call" || record.eventName === "pre_gateway_dispatch") ? "userMessage"
1683
+ : eventKind === "message" ? "agentMessage"
1684
+ : undefined,
1685
+ kind: eventKind,
1686
+ phase: kind?.eventType === "plan"
1687
+ ? "updated"
1688
+ : hermesHookIsToolCompletion(record.eventName)
1689
+ ? hermesHookResultLooksFailed(record) ? "failed" : "completed"
1690
+ : record.eventName === "pre_tool_call" || record.eventName === "pre_verify" || record.eventName === "subagent_start" ? "started" : "updated",
1691
+ title: kind ? toolEventTitle : preVerifyDetail ?? subagentTitle ?? record.eventName,
1692
+ detail,
1693
+ path: eventPath,
1694
+ action,
1695
+ command,
1696
+ cwd: record.cwd ?? undefined,
1697
+ isImage: false
1698
+ };
1699
+ });
1700
+ }
1701
+ function summarizeHermesSession(input) {
1702
+ const { session, projectRoot, cwd, activeProcess, paths, now } = input;
1703
+ const latest = [...session.messages].reverse().find((message) => message.role) ?? null;
1704
+ const latestCall = latestMessageToolCall(latest);
1705
+ const latestMessage = latestAssistantText(session.messages);
1706
+ const latestUser = latestHermesSessionUserText(session);
1707
+ const promptDetail = latestUser ?? (isHermesCronSession(session) ? "Scheduled cron job" : "Hermes prompt");
1708
+ const lastActiveMs = Math.max(session.lastActive * 1000, session.startedAt * 1000);
1709
+ const ageMs = now - lastActiveMs;
1710
+ const isEnded = session.endedAt !== null;
1711
+ const active = Boolean(activeProcess);
1712
+ const freshOpen = !isEnded && ageMs <= HERMES_RECENT_OPEN_WINDOW_MS;
1713
+ const hasUnfinishedTurn = latest?.role === "user"
1714
+ || latest?.role === "tool"
1715
+ || (latest?.role === "assistant" && latestCall !== null);
1716
+ const isOngoing = !isEnded && (freshOpen || (hasUnfinishedTurn && active));
1717
+ if (latest?.role === "tool" && messageLooksFailed(latest)) {
1718
+ const toolName = latest.toolName ?? "tool";
1719
+ return {
1720
+ state: "blocked",
1721
+ isOngoing,
1722
+ detail: `${toolName} failed`,
1723
+ paths,
1724
+ activityEvent: {
1725
+ type: toolKind(toolName).eventType,
1726
+ action: "updated",
1727
+ path: paths[0] ?? projectRoot,
1728
+ title: `${toolName} failed`,
1729
+ isImage: false
1730
+ },
1731
+ latestMessage,
1732
+ updatedAtMs: lastActiveMs,
1733
+ stoppedAtMs: isOngoing ? null : lastActiveMs
1734
+ };
1735
+ }
1736
+ if (latestCall && isOngoing) {
1737
+ const kind = toolKind(latestCall.name);
1738
+ return {
1739
+ state: kind.state,
1740
+ isOngoing: true,
1741
+ detail: toolTitle(latestCall.name, latestCall.args),
1742
+ paths,
1743
+ activityEvent: toolActivityEvent(projectRoot, cwd, latestCall),
1744
+ latestMessage,
1745
+ updatedAtMs: lastActiveMs,
1746
+ stoppedAtMs: null
1747
+ };
1748
+ }
1749
+ if (latest?.role === "tool" && isOngoing) {
1750
+ const toolName = latest.toolName ?? "tool";
1751
+ return {
1752
+ state: "thinking",
1753
+ isOngoing: true,
1754
+ detail: `${toolName} result`,
1755
+ paths,
1756
+ activityEvent: {
1757
+ type: toolKind(toolName).eventType,
1758
+ action: "updated",
1759
+ path: paths[0] ?? projectRoot,
1760
+ title: `${toolName} result`,
1761
+ isImage: false
1762
+ },
1763
+ latestMessage,
1764
+ updatedAtMs: lastActiveMs,
1765
+ stoppedAtMs: null
1766
+ };
1767
+ }
1768
+ if (latest?.role === "user" && isOngoing) {
1769
+ return {
1770
+ state: "planning",
1771
+ isOngoing: true,
1772
+ detail: promptDetail,
1773
+ paths,
1774
+ activityEvent: {
1775
+ type: "userMessage",
1776
+ action: "said",
1777
+ path: paths[0] ?? projectRoot,
1778
+ title: promptDetail,
1779
+ isImage: false
1780
+ },
1781
+ latestMessage,
1782
+ updatedAtMs: lastActiveMs,
1783
+ stoppedAtMs: null
1784
+ };
1785
+ }
1786
+ if (active && session.messages.length === 0) {
1787
+ return {
1788
+ state: "thinking",
1789
+ isOngoing: true,
1790
+ detail: latestMessage ?? latestUser ?? "Hermes is active",
1791
+ paths,
1792
+ activityEvent: latestMessage
1793
+ ? {
1794
+ type: "agentMessage",
1795
+ action: "said",
1796
+ path: paths[0] ?? projectRoot,
1797
+ title: latestMessage,
1798
+ isImage: false
1799
+ }
1800
+ : null,
1801
+ latestMessage,
1802
+ updatedAtMs: lastActiveMs,
1803
+ stoppedAtMs: null
1804
+ };
1805
+ }
1806
+ if (freshOpen) {
1807
+ return {
1808
+ state: "waiting",
1809
+ isOngoing: true,
1810
+ detail: latestMessage ?? latestUser ?? (isHermesCronSession(session) ? "Scheduled cron job open" : "Hermes session open"),
1811
+ paths,
1812
+ activityEvent: latestMessage
1813
+ ? {
1814
+ type: "agentMessage",
1815
+ action: "said",
1816
+ path: paths[0] ?? projectRoot,
1817
+ title: latestMessage,
1818
+ isImage: false
1819
+ }
1820
+ : null,
1821
+ latestMessage,
1822
+ updatedAtMs: lastActiveMs,
1823
+ stoppedAtMs: null
1824
+ };
1825
+ }
1826
+ const recentlyDone = ageMs <= HERMES_RECENT_DONE_WINDOW_MS;
1827
+ return {
1828
+ state: recentlyDone ? "done" : "idle",
1829
+ isOngoing: false,
1830
+ detail: latestMessage ?? latestUser ?? (isEnded ? "Finished" : isHermesCronSession(session) ? "Scheduled cron job" : "Hermes session"),
1831
+ paths,
1832
+ activityEvent: latestMessage
1833
+ ? {
1834
+ type: "agentMessage",
1835
+ action: "said",
1836
+ path: paths[0] ?? projectRoot,
1837
+ title: latestMessage,
1838
+ isImage: false
1839
+ }
1840
+ : null,
1841
+ latestMessage,
1842
+ updatedAtMs: lastActiveMs,
1843
+ stoppedAtMs: isEnded || recentlyDone ? lastActiveMs : null
1844
+ };
1845
+ }
1846
+ function buildHermesMessageEvents(input) {
1847
+ const events = [];
1848
+ for (const message of input.session.messages) {
1849
+ const createdAt = new Date((message.timestamp || input.session.lastActive) * 1000).toISOString();
1850
+ const baseId = `${input.projectRoot}::hermes::${input.session.id}::${message.id}`;
1851
+ if (message.role === "user") {
1852
+ const detail = textFromContent(message.content);
1853
+ if (!detail) {
1854
+ continue;
1855
+ }
1856
+ events.push({
1857
+ id: `${baseId}::user`,
1858
+ source: "hermes",
1859
+ confidence: "inferred",
1860
+ threadId: input.session.id,
1861
+ createdAt,
1862
+ method: "hermes/userMessage",
1863
+ kind: "message",
1864
+ phase: "updated",
1865
+ title: "Hermes prompt",
1866
+ detail: shorten(detail, 240),
1867
+ path: input.projectRoot,
1868
+ action: "said",
1869
+ isImage: false
1870
+ });
1871
+ continue;
1872
+ }
1873
+ if (message.role === "assistant") {
1874
+ const calls = parseToolCalls(message.toolCalls);
1875
+ calls.forEach((call, index) => {
1876
+ const kind = toolKind(call.name);
1877
+ const path = toolPath(input.projectRoot, input.cwd, call.args);
1878
+ events.push({
1879
+ id: `${baseId}::tool-${index}`,
1880
+ source: "hermes",
1881
+ confidence: "inferred",
1882
+ threadId: input.session.id,
1883
+ createdAt,
1884
+ method: "item/started",
1885
+ kind: kind.eventKind,
1886
+ phase: "started",
1887
+ title: call.name,
1888
+ detail: toolTitle(call.name, call.args),
1889
+ path,
1890
+ command: typeof call.args.command === "string" ? call.args.command : undefined,
1891
+ action: kind.eventKind === "command" ? "ran" : "updated",
1892
+ isImage: false
1893
+ });
1894
+ });
1895
+ const detail = textFromContent(message.content);
1896
+ if (detail) {
1897
+ events.push({
1898
+ id: `${baseId}::assistant`,
1899
+ source: "hermes",
1900
+ confidence: "inferred",
1901
+ threadId: input.session.id,
1902
+ createdAt,
1903
+ method: "hermes/agentMessage",
1904
+ kind: "message",
1905
+ phase: "updated",
1906
+ title: "Hermes reply",
1907
+ detail: shorten(detail, 240),
1908
+ path: input.projectRoot,
1909
+ action: "said",
1910
+ isImage: false
1911
+ });
1912
+ }
1913
+ continue;
1914
+ }
1915
+ if (message.role === "tool" && message.toolName) {
1916
+ const kind = toolKind(message.toolName);
1917
+ const failed = messageLooksFailed(message);
1918
+ const detail = textFromContent(message.content);
1919
+ events.push({
1920
+ id: `${baseId}::tool-result`,
1921
+ source: "hermes",
1922
+ confidence: "inferred",
1923
+ threadId: input.session.id,
1924
+ createdAt,
1925
+ method: "item/completed",
1926
+ kind: kind.eventKind,
1927
+ phase: failed ? "failed" : "completed",
1928
+ title: message.toolName,
1929
+ detail: shorten(detail || message.toolName, 240),
1930
+ path: input.projectRoot,
1931
+ action: kind.eventKind === "command" ? "ran" : "updated",
1932
+ isImage: false
1933
+ });
1934
+ }
1935
+ }
1936
+ return events;
1937
+ }
1938
+ async function gitInfoForProject(projectRoot) {
1939
+ void projectRoot;
1940
+ return {
1941
+ sha: null,
1942
+ branch: null,
1943
+ originUrl: null
1944
+ };
1945
+ }
1946
+ async function allHermesHomes(processes) {
1947
+ const homes = new Set(await hermesHomeCandidatesFromDisk());
1948
+ for (const processInfo of processes) {
1949
+ if (processInfo.hermesHome) {
1950
+ homes.add(processInfo.hermesHome);
1951
+ }
1952
+ }
1953
+ return [...homes].filter(Boolean);
1954
+ }
1955
+ function latestActiveSessionForHome(sessions, home) {
1956
+ if (!home) {
1957
+ return null;
1958
+ }
1959
+ return sessions
1960
+ .filter((session) => session.home === home
1961
+ && !session.endedAt
1962
+ && isCliLikeHermesSource(session))
1963
+ .sort((left, right) => right.lastActive - left.lastActive)[0] ?? null;
1964
+ }
1965
+ async function loadHermesProjectSnapshotData(projectRoot, limit = hermesLocalSessionLimit()) {
1966
+ const canonicalRoot = (0, project_paths_1.canonicalizeProjectPath)(projectRoot);
1967
+ if (!canonicalRoot) {
1968
+ return { agents: [], events: [], notes: [] };
1969
+ }
1970
+ const notes = [];
1971
+ const processes = await scanHermesProcesses();
1972
+ const projectProcesses = processes.filter((processInfo) => pathWithinProject(canonicalRoot, processInfo.cwd));
1973
+ const scanLimit = hermesScanLimit(limit);
1974
+ const hookSessions = await loadHermesHookSessions(scanLimit);
1975
+ const homes = await allHermesHomes(processes);
1976
+ const sessionGroups = await Promise.all(homes.map((home) => loadHermesSessions(home, scanLimit, notes)));
1977
+ const sessions = sessionGroups.flat().sort((left, right) => right.lastActive - left.lastActive);
1978
+ const activeSessionIds = new Set();
1979
+ for (const processInfo of projectProcesses) {
1980
+ const activeSession = latestActiveSessionForHome(sessions, processInfo.hermesHome);
1981
+ if (activeSession) {
1982
+ activeSessionIds.add(activeSession.id);
1983
+ }
1984
+ }
1985
+ const now = Date.now();
1986
+ const git = await gitInfoForProject(canonicalRoot);
1987
+ const agents = [];
1988
+ const events = [];
1989
+ const includedSessionIds = new Set();
1990
+ const hookRecordsBySessionId = new Map();
1991
+ for (const records of hookSessions.values()) {
1992
+ const currentRoot = await currentProjectRootForHermesHookSession(records);
1993
+ if (!currentRoot || !(0, project_paths_1.sameProjectPath)(currentRoot, canonicalRoot)) {
1994
+ continue;
1995
+ }
1996
+ const canonicalSessionId = canonicalHermesSessionIdForHookRecords(records, sessions);
1997
+ const latest = records[records.length - 1];
1998
+ const hookOnlySessionId = latest && isDurableHermesHookSessionId(latest.sessionId)
1999
+ ? latest.sessionId
2000
+ : null;
2001
+ const visibleSessionId = canonicalSessionId ?? hookOnlySessionId;
2002
+ if (!visibleSessionId) {
2003
+ continue;
2004
+ }
2005
+ const existing = hookRecordsBySessionId.get(visibleSessionId) ?? [];
2006
+ hookRecordsBySessionId.set(visibleSessionId, [...existing, ...records].sort((left, right) => left.timestampMs - right.timestampMs));
2007
+ }
2008
+ for (const session of sessions) {
2009
+ if (includedSessionIds.has(session.id)) {
2010
+ continue;
2011
+ }
2012
+ const activeProcess = projectProcesses.find((processInfo) => processInfo.hermesHome === session.home && activeSessionIds.has(session.id)) ?? null;
2013
+ const sessionCwd = cwdFromSystemPrompt(session.systemPrompt);
2014
+ const fallbackCwd = activeProcess?.cwd ?? sessionCwd;
2015
+ const currentRoot = await currentProjectRootForHermesSession(session, activeProcess);
2016
+ if (!currentRoot || !(0, project_paths_1.sameProjectPath)(currentRoot, canonicalRoot)) {
2017
+ continue;
2018
+ }
2019
+ const candidatePaths = collectSessionPathCandidates(session, fallbackCwd);
2020
+ const projectPaths = candidatePaths.filter((candidate) => pathWithinProject(canonicalRoot, candidate));
2021
+ const cwd = activeProcess?.cwd ?? (pathWithinProject(canonicalRoot, sessionCwd) ? sessionCwd : null) ?? currentRoot;
2022
+ const paths = projectPaths.length > 0 ? projectPaths : [cwd];
2023
+ const summary = summarizeHermesSession({
2024
+ session,
2025
+ projectRoot: canonicalRoot,
2026
+ cwd,
2027
+ activeProcess,
2028
+ paths,
2029
+ now
2030
+ });
2031
+ const hookRecords = hookRecordsBySessionId.get(session.id) ?? [];
2032
+ const hookPaths = collectHermesHookPathCandidates(hookRecords).filter((candidate) => pathWithinProject(canonicalRoot, candidate));
2033
+ const hookSummary = hookRecords.length > 0
2034
+ ? summarizeHermesHookSession({
2035
+ records: hookRecords,
2036
+ projectRoot: canonicalRoot,
2037
+ paths: hookPaths.length > 0 ? hookPaths : paths,
2038
+ now
2039
+ })
2040
+ : null;
2041
+ const visibleSummary = hookSummary && hookSummary.updatedAtMs >= summary.updatedAtMs
2042
+ ? {
2043
+ ...hookSummary,
2044
+ latestMessage: hookSummary.latestMessage ?? summary.latestMessage,
2045
+ paths: [...new Set([...hookSummary.paths, ...summary.paths])].slice(0, HERMES_HOOK_PATH_LIMIT)
2046
+ }
2047
+ : summary;
2048
+ if (visibleSummary.state === "idle" && !visibleSummary.isOngoing) {
2049
+ continue;
2050
+ }
2051
+ const appearance = await (0, appearance_1.ensureAgentAppearance)(canonicalRoot, `hermes:${session.id}`);
2052
+ const isContinuation = isHermesCompressionContinuation(session);
2053
+ agents.push({
2054
+ id: `hermes:${session.id}`,
2055
+ label: sessionLabel(session, canonicalRoot),
2056
+ source: "hermes",
2057
+ sourceKind: sessionSourceKind(session),
2058
+ parentThreadId: !isContinuation && session.parentSessionId ? `hermes:${session.parentSessionId}` : null,
2059
+ depth: !isContinuation && session.parentSessionId ? 1 : 0,
2060
+ isCurrent: visibleSummary.isOngoing,
2061
+ isOngoing: visibleSummary.isOngoing,
2062
+ statusText: sessionStatusText(session, visibleSummary.isOngoing),
2063
+ role: sessionRole(session),
2064
+ nickname: null,
2065
+ isSubagent: Boolean(!isContinuation && session.parentSessionId),
2066
+ state: visibleSummary.state,
2067
+ detail: visibleSummary.detail,
2068
+ cwd,
2069
+ roomId: null,
2070
+ appearance,
2071
+ updatedAt: new Date(visibleSummary.updatedAtMs).toISOString(),
2072
+ stoppedAt: visibleSummary.stoppedAtMs ? new Date(visibleSummary.stoppedAtMs).toISOString() : null,
2073
+ paths: visibleSummary.paths,
2074
+ activityEvent: visibleSummary.activityEvent,
2075
+ latestMessage: visibleSummary.latestMessage,
2076
+ threadId: session.id,
2077
+ taskId: null,
2078
+ resumeCommand: null,
2079
+ url: null,
2080
+ git,
2081
+ provenance: "hermes",
2082
+ confidence: hookSummary ? "typed" : "inferred",
2083
+ needsUser: null,
2084
+ liveSubscription: "readOnly",
2085
+ network: null
2086
+ });
2087
+ includedSessionIds.add(session.id);
2088
+ events.push(...buildHermesMessageEvents({
2089
+ session,
2090
+ projectRoot: canonicalRoot,
2091
+ cwd
2092
+ }));
2093
+ if (hookRecords.length > 0) {
2094
+ events.push(...buildHermesHookEvents({ records: hookRecords, projectRoot: canonicalRoot, threadId: session.id }));
2095
+ }
2096
+ }
2097
+ for (const [sessionId, hookRecords] of hookRecordsBySessionId) {
2098
+ if (includedSessionIds.has(sessionId) || !isDurableHermesHookSessionId(sessionId)) {
2099
+ continue;
2100
+ }
2101
+ const hookPaths = collectHermesHookPathCandidates(hookRecords).filter((candidate) => pathWithinProject(canonicalRoot, candidate));
2102
+ const latest = hookRecords[hookRecords.length - 1];
2103
+ const cwd = pathWithinProject(canonicalRoot, latest?.cwd) ? latest?.cwd ?? canonicalRoot : canonicalRoot;
2104
+ const paths = hookPaths.length > 0 ? hookPaths : [cwd];
2105
+ const summary = summarizeHermesHookSession({
2106
+ records: hookRecords,
2107
+ projectRoot: canonicalRoot,
2108
+ paths,
2109
+ now
2110
+ });
2111
+ if (summary.state === "idle" && !summary.isOngoing) {
2112
+ continue;
2113
+ }
2114
+ const appearance = await (0, appearance_1.ensureAgentAppearance)(canonicalRoot, `hermes:${sessionId}`);
2115
+ agents.push({
2116
+ id: `hermes:${sessionId}`,
2117
+ label: hermesHookSessionLabel(hookRecords, canonicalRoot, summary.latestMessage),
2118
+ source: "hermes",
2119
+ sourceKind: hermesHookSourceKind(sessionId),
2120
+ parentThreadId: null,
2121
+ depth: 0,
2122
+ isCurrent: summary.isOngoing,
2123
+ isOngoing: summary.isOngoing,
2124
+ statusText: hermesHookStatusText(sessionId, summary.isOngoing),
2125
+ role: hermesHookRole(sessionId),
2126
+ nickname: null,
2127
+ isSubagent: false,
2128
+ state: summary.state,
2129
+ detail: summary.detail,
2130
+ cwd,
2131
+ roomId: null,
2132
+ appearance,
2133
+ updatedAt: new Date(summary.updatedAtMs).toISOString(),
2134
+ stoppedAt: summary.stoppedAtMs ? new Date(summary.stoppedAtMs).toISOString() : null,
2135
+ paths: summary.paths,
2136
+ activityEvent: summary.activityEvent,
2137
+ latestMessage: summary.latestMessage,
2138
+ threadId: sessionId,
2139
+ taskId: null,
2140
+ resumeCommand: null,
2141
+ url: null,
2142
+ git,
2143
+ provenance: "hermes",
2144
+ confidence: "typed",
2145
+ needsUser: null,
2146
+ liveSubscription: "readOnly",
2147
+ network: null
2148
+ });
2149
+ includedSessionIds.add(sessionId);
2150
+ events.push(...buildHermesHookEvents({ records: hookRecords, projectRoot: canonicalRoot, threadId: sessionId }));
2151
+ }
2152
+ for (const processInfo of projectProcesses) {
2153
+ const hasIncludedSession = sessions.some((session) => includedSessionIds.has(session.id)
2154
+ && session.home === processInfo.hermesHome
2155
+ && activeSessionIds.has(session.id));
2156
+ if (hasIncludedSession || !processInfo.cwd) {
2157
+ continue;
2158
+ }
2159
+ const appearance = await (0, appearance_1.ensureAgentAppearance)(canonicalRoot, `hermes-process:${processInfo.pid}`);
2160
+ agents.push({
2161
+ id: `hermes-process:${processInfo.pid}`,
2162
+ label: "Hermes CLI",
2163
+ source: "hermes",
2164
+ sourceKind: "hermes:process",
2165
+ parentThreadId: null,
2166
+ depth: 0,
2167
+ isCurrent: true,
2168
+ isOngoing: true,
2169
+ statusText: "active",
2170
+ role: "hermes",
2171
+ nickname: null,
2172
+ isSubagent: false,
2173
+ state: "planning",
2174
+ detail: "Hermes process in workspace",
2175
+ cwd: processInfo.cwd,
2176
+ roomId: null,
2177
+ appearance,
2178
+ updatedAt: new Date(processInfo.updatedAtMs).toISOString(),
2179
+ stoppedAt: null,
2180
+ paths: [processInfo.cwd],
2181
+ activityEvent: null,
2182
+ latestMessage: null,
2183
+ threadId: `process:${processInfo.pid}`,
2184
+ taskId: null,
2185
+ resumeCommand: null,
2186
+ url: null,
2187
+ git,
2188
+ provenance: "hermes",
2189
+ confidence: "inferred",
2190
+ needsUser: null,
2191
+ liveSubscription: "readOnly",
2192
+ network: null
2193
+ });
2194
+ }
2195
+ return {
2196
+ agents: agents
2197
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
2198
+ .slice(0, limit),
2199
+ events: events.sort((left, right) => right.createdAt.localeCompare(left.createdAt)),
2200
+ notes
2201
+ };
2202
+ }
2203
+ async function discoverHermesProjects(limit = 20) {
2204
+ const projects = new Map();
2205
+ const processes = await scanHermesProcesses();
2206
+ const scanLimit = hermesScanLimit(limit);
2207
+ const hookSessions = await loadHermesHookSessions(scanLimit);
2208
+ const now = Date.now();
2209
+ const notes = [];
2210
+ const upsertProject = async (root, updatedAtSeconds) => {
2211
+ const key = (0, project_paths_1.projectPathIdentityKey)(root);
2212
+ if (!root || !key) {
2213
+ return;
2214
+ }
2215
+ if (HERMES_TRANSIENT_PROJECT_ROOTS.has(root.replace(/\\/g, "/").toLowerCase())) {
2216
+ return;
2217
+ }
2218
+ if (await looksLikeHermesRuntimeProjectRoot(root)) {
2219
+ return;
2220
+ }
2221
+ if (!await pathExists((0, node_path_1.join)(root, ".git"))) {
2222
+ return;
2223
+ }
2224
+ const existing = projects.get(key);
2225
+ if (existing) {
2226
+ existing.updatedAt = Math.max(existing.updatedAt, updatedAtSeconds);
2227
+ existing.count += 1;
2228
+ return;
2229
+ }
2230
+ projects.set(key, {
2231
+ root,
2232
+ label: (0, project_paths_1.projectLabelFromRoot)(root),
2233
+ updatedAt: updatedAtSeconds,
2234
+ count: 1
2235
+ });
2236
+ };
2237
+ for (const processInfo of processes) {
2238
+ if (!processInfo.cwd) {
2239
+ continue;
2240
+ }
2241
+ const root = await resolveProjectRootForPath(processInfo.cwd);
2242
+ await upsertProject(root, Math.floor(processInfo.updatedAtMs / 1000));
2243
+ }
2244
+ for (const records of hookSessions.values()) {
2245
+ const latest = records[records.length - 1];
2246
+ if (!latest || now - latest.timestampMs > HERMES_PROJECT_DISCOVERY_HOOK_WINDOW_MS) {
2247
+ continue;
2248
+ }
2249
+ const summary = summarizeHermesHookSession({
2250
+ records,
2251
+ projectRoot: latest.cwd ?? latest.processCwd ?? "/",
2252
+ paths: collectHermesHookPathCandidates(records),
2253
+ now
2254
+ });
2255
+ if (summary.state === "idle" && !summary.isOngoing) {
2256
+ continue;
2257
+ }
2258
+ const currentRoot = await currentProjectRootForHermesHookSession(records);
2259
+ await upsertProject(currentRoot, Math.floor(latest.timestampMs / 1000));
2260
+ }
2261
+ const homes = await allHermesHomes(processes);
2262
+ const sessionGroups = await Promise.all(homes.map((home) => loadHermesSessions(home, scanLimit, notes)));
2263
+ for (const session of sessionGroups.flat()) {
2264
+ if (session.endedAt !== null) {
2265
+ continue;
2266
+ }
2267
+ const lastActiveMs = Math.max(session.lastActive * 1000, session.startedAt * 1000);
2268
+ if (now - lastActiveMs > HERMES_RECENT_OPEN_WINDOW_MS) {
2269
+ continue;
2270
+ }
2271
+ const root = await currentProjectRootForHermesSession(session, null);
2272
+ await upsertProject(root, Math.floor(lastActiveMs / 1000));
2273
+ }
2274
+ return [...projects.values()]
2275
+ .sort((left, right) => right.updatedAt - left.updatedAt)
2276
+ .slice(0, limit);
2277
+ }
2278
+ async function loadRoamingHermesSnapshotData(input) {
2279
+ const anchorRoot = (0, project_paths_1.canonicalizeProjectPath)(input.anchorProjectRoot);
2280
+ if (!anchorRoot) {
2281
+ return { agents: [], events: [], notes: [] };
2282
+ }
2283
+ const knownRoots = input.knownProjectRoots
2284
+ .map((root) => (0, project_paths_1.canonicalizeProjectPath)(root))
2285
+ .filter((root) => Boolean(root));
2286
+ const limit = input.limit ?? 4;
2287
+ const hookSessions = await loadHermesHookSessions(hermesScanLimit(limit));
2288
+ const now = Date.now();
2289
+ const gitByRoot = new Map();
2290
+ const gitForRoot = async (root) => {
2291
+ const key = (0, project_paths_1.projectPathIdentityKey)(root) ?? root;
2292
+ const existing = gitByRoot.get(key);
2293
+ if (existing) {
2294
+ return existing;
2295
+ }
2296
+ const git = await gitInfoForProject(root);
2297
+ gitByRoot.set(key, git);
2298
+ return git;
2299
+ };
2300
+ const agents = [];
2301
+ const events = [];
2302
+ for (const [sessionId, records] of hookSessions.entries()) {
2303
+ if (isNonSessionHermesHookId(sessionId)) {
2304
+ continue;
2305
+ }
2306
+ const currentRoot = await currentProjectRootForHermesHookSession(records);
2307
+ const candidatePaths = collectHermesHookPathCandidates(records);
2308
+ const isInsideKnownWorkspace = Boolean(currentRoot
2309
+ && knownRoots.some((root) => (0, project_paths_1.sameProjectPath)(root, currentRoot) || pathWithinProject(root, currentRoot)));
2310
+ if (isInsideKnownWorkspace) {
2311
+ continue;
2312
+ }
2313
+ const sourceRoot = currentRoot ?? anchorRoot;
2314
+ const summary = summarizeHermesHookSession({
2315
+ records,
2316
+ projectRoot: sourceRoot,
2317
+ paths: candidatePaths,
2318
+ now
2319
+ });
2320
+ if (summary.state === "idle" && !summary.isOngoing) {
2321
+ continue;
2322
+ }
2323
+ const latest = records[records.length - 1];
2324
+ const appearance = await (0, appearance_1.ensureAgentAppearance)(sourceRoot, `hermes:${sessionId}`);
2325
+ const statusText = isHermesCronSessionId(sessionId)
2326
+ ? hermesHookStatusText(sessionId, summary.isOngoing)
2327
+ : summary.isOngoing ? "roaming" : summary.state;
2328
+ agents.push({
2329
+ id: `hermes:${sessionId}`,
2330
+ label: hermesHookSessionLabel(records, sourceRoot, summary.latestMessage),
2331
+ source: "hermes",
2332
+ sourceKind: "hermes:roaming",
2333
+ parentThreadId: null,
2334
+ depth: 0,
2335
+ isCurrent: false,
2336
+ isOngoing: summary.isOngoing,
2337
+ statusText,
2338
+ role: hermesHookRole(sessionId),
2339
+ nickname: null,
2340
+ isSubagent: false,
2341
+ state: summary.state,
2342
+ detail: summary.detail,
2343
+ cwd: latest.cwd ?? latest.processCwd ?? null,
2344
+ sourceProjectRoot: sourceRoot,
2345
+ roomId: null,
2346
+ appearance,
2347
+ updatedAt: new Date(summary.updatedAtMs).toISOString(),
2348
+ stoppedAt: summary.stoppedAtMs ? new Date(summary.stoppedAtMs).toISOString() : null,
2349
+ paths: [],
2350
+ activityEvent: summary.activityEvent,
2351
+ latestMessage: summary.latestMessage,
2352
+ threadId: sessionId,
2353
+ taskId: null,
2354
+ resumeCommand: null,
2355
+ url: null,
2356
+ git: await gitForRoot(sourceRoot),
2357
+ provenance: "hermes",
2358
+ confidence: "typed",
2359
+ needsUser: null,
2360
+ liveSubscription: "readOnly",
2361
+ network: null
2362
+ });
2363
+ events.push(...buildHermesHookEvents({ records, projectRoot: sourceRoot }));
2364
+ }
2365
+ return {
2366
+ agents: agents
2367
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
2368
+ .slice(0, limit),
2369
+ events: events.sort((left, right) => right.createdAt.localeCompare(left.createdAt)),
2370
+ notes: []
2371
+ };
2372
+ }
2373
+ function summarizeHermesSessionForTest(input) {
2374
+ return summarizeHermesSession({
2375
+ session: input.session,
2376
+ projectRoot: input.projectRoot,
2377
+ cwd: input.cwd ?? input.projectRoot,
2378
+ activeProcess: input.active
2379
+ ? {
2380
+ pid: 1,
2381
+ cwd: input.cwd ?? input.projectRoot,
2382
+ hermesHome: input.session.home,
2383
+ command: "hermes",
2384
+ updatedAtMs: input.now ?? Date.now()
2385
+ }
2386
+ : null,
2387
+ paths: input.paths ?? [input.projectRoot],
2388
+ now: input.now ?? Date.now()
2389
+ });
2390
+ }
2391
+ function summarizeHermesHookSessionForTest(input) {
2392
+ const now = input.now ?? Date.now();
2393
+ return summarizeHermesHookSession({
2394
+ records: input.records.map((record, index) => ({
2395
+ sessionId: record.sessionId ?? "test-session",
2396
+ eventName: record.eventName,
2397
+ timestampMs: record.timestampMs ?? now + index,
2398
+ cwd: record.cwd ?? input.projectRoot,
2399
+ processCwd: record.processCwd ?? input.projectRoot,
2400
+ payload: record.payload,
2401
+ raw: record.raw ?? {}
2402
+ })),
2403
+ projectRoot: input.projectRoot,
2404
+ paths: input.paths ?? [input.projectRoot],
2405
+ now
2406
+ });
2407
+ }
2408
+ function isHermesGatewayDaemonForTest(argv) {
2409
+ return looksLikeHermesGatewayDaemon(argv);
2410
+ }
2411
+ function isHermesRuntimeHomeProcessForTest(argv, cwd) {
2412
+ return looksLikeHermesRuntimeHomeProcess(argv, cwd);
2413
+ }
2414
+ function isHermesCompressionContinuationForTest(input) {
2415
+ return isHermesCompressionContinuation({
2416
+ id: "test-session",
2417
+ source: "cli",
2418
+ model: null,
2419
+ parentSessionId: input.parentSessionId ?? null,
2420
+ parentEndedAt: input.parentEndedAt ?? null,
2421
+ parentEndReason: input.parentEndReason ?? null,
2422
+ startedAt: input.startedAt ?? 0,
2423
+ endedAt: null,
2424
+ endReason: null,
2425
+ messageCount: 0,
2426
+ toolCallCount: 0,
2427
+ title: null,
2428
+ systemPrompt: null,
2429
+ lastActive: input.startedAt ?? 0,
2430
+ home: "/tmp/hermes",
2431
+ storage: "sqlite",
2432
+ messages: []
2433
+ });
2434
+ }
2435
+ async function currentHermesSessionProjectRootForTest(input) {
2436
+ return currentProjectRootForHermesSession(input.session, input.activeCwd
2437
+ ? {
2438
+ pid: 1,
2439
+ cwd: input.activeCwd,
2440
+ hermesHome: input.session.home,
2441
+ command: "hermes",
2442
+ updatedAtMs: Date.now()
2443
+ }
2444
+ : null);
2445
+ }
2446
+ //# sourceMappingURL=hermes.js.map