@saleso.innovations/bridge 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hermesCommands.d.ts +1 -1
- package/dist/hermesCommands.d.ts.map +1 -1
- package/dist/hermesCommands.js +15 -2
- package/dist/hermesFileCommands.d.ts +4 -0
- package/dist/hermesFileCommands.d.ts.map +1 -1
- package/dist/hermesFileCommands.js +8 -1
- package/dist/hermesFiles.d.ts +7 -0
- package/dist/hermesFiles.d.ts.map +1 -1
- package/dist/hermesFiles.js +33 -2
- package/dist/hermesFiles.test.js +28 -3
- package/dist/hermesSessionDb.d.ts +15 -0
- package/dist/hermesSessionDb.d.ts.map +1 -1
- package/dist/hermesSessionDb.js +109 -5
- package/dist/hermesSessionDb.test.d.ts +2 -0
- package/dist/hermesSessionDb.test.d.ts.map +1 -0
- package/dist/hermesSessionDb.test.js +151 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/skillsList.d.ts +1 -0
- package/dist/skillsList.d.ts.map +1 -1
- package/dist/skillsList.js +50 -12
- package/package.json +2 -2
package/dist/hermesCommands.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ShellDeltaEmitter } from "./shellSession.js";
|
|
2
|
-
export declare const HERMES_COMMAND_NAMES: readonly ["runtime.health", "runtime.detailedHealth", "runtime.version", "runtime.capabilities", "models.list", "model.set", "responses.create", "runs.create", "runs.status", "runs.stop", "jobs.list", "jobs.get", "jobs.create", "jobs.update", "jobs.pause", "jobs.resume", "jobs.runNow", "jobs.delete", "profiles.list", "profiles.create", "gateway.start", "gateway.stop", "gateway.restart", "hermes.update", "sessions.messages.list", "sessions.messages.countSent", "sessions.titles.resolve", "sessions.list", "skills.list", "files.list", "files.read", "files.write", "memories.list", "shell.exec", "shell.session.reset"];
|
|
2
|
+
export declare const HERMES_COMMAND_NAMES: readonly ["runtime.health", "runtime.detailedHealth", "runtime.version", "runtime.capabilities", "models.list", "model.set", "responses.create", "runs.create", "runs.status", "runs.stop", "jobs.list", "jobs.get", "jobs.create", "jobs.update", "jobs.pause", "jobs.resume", "jobs.runNow", "jobs.delete", "profiles.list", "profiles.create", "gateway.start", "gateway.stop", "gateway.restart", "hermes.update", "sessions.messages.list", "sessions.messages.countSent", "sessions.titles.resolve", "sessions.usage.get", "sessions.list", "skills.list", "files.list", "files.read", "files.write", "files.delete", "memories.list", "shell.exec", "shell.session.reset"];
|
|
3
3
|
export type HermesCommandName = (typeof HERMES_COMMAND_NAMES)[number];
|
|
4
4
|
export declare function isHermesCommandName(value: string): value is HermesCommandName;
|
|
5
5
|
export type HermesCommandErrorCode = "command_unsupported" | "hermes_unreachable" | "hermes_request_failed" | "invalid_command_args" | "unsupported_by_http";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermesCommands.d.ts","sourceRoot":"","sources":["../src/hermesCommands.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hermesCommands.d.ts","sourceRoot":"","sources":["../src/hermesCommands.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAuC,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGhG,eAAO,MAAM,oBAAoB,mpBAsCvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,MAAM,MAAM,sBAAsB,GAC9B,qBAAqB,GACrB,oBAAoB,GACpB,uBAAuB,GACvB,sBAAsB,GACtB,qBAAqB,CAAC;AAE1B,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;gBAE1B,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,MAAM;CAI1D;AA6GD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;CAAO,GAC9F,OAAO,CAAC,OAAO,CAAC,CAiOlB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAetG"}
|
package/dist/hermesCommands.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { resolveHermesApiConfig } from "./hermesForwarder.js";
|
|
2
2
|
import { restartHermesGateway, startHermesGateway, stopHermesGateway } from "./gatewayControl.js";
|
|
3
3
|
import { listHermesCronJobs } from "./cronList.js";
|
|
4
|
-
import { listHermesSessions, listSessionMessages, countUserMessagesSent, resolveSessionTitles, } from "./hermesSessionDb.js";
|
|
4
|
+
import { listHermesSessions, listSessionMessages, countUserMessagesSent, getSessionUsage, resolveSessionTitles, } from "./hermesSessionDb.js";
|
|
5
5
|
import { listHermesSkills } from "./skillsList.js";
|
|
6
6
|
import { runHermesUpdate } from "./hermesUpdate.js";
|
|
7
|
-
import { executeFilesList, executeFilesRead, executeFilesWrite, executeMemoriesList, } from "./hermesFileCommands.js";
|
|
7
|
+
import { executeFilesList, executeFilesRead, executeFilesWrite, executeFilesDelete, executeMemoriesList, } from "./hermesFileCommands.js";
|
|
8
8
|
import { executeShellExec, resetShellSession } from "./shellSession.js";
|
|
9
9
|
import { fetchHermesRuntimeVersion } from "./runtimeVersion.js";
|
|
10
10
|
export const HERMES_COMMAND_NAMES = [
|
|
@@ -35,11 +35,13 @@ export const HERMES_COMMAND_NAMES = [
|
|
|
35
35
|
"sessions.messages.list",
|
|
36
36
|
"sessions.messages.countSent",
|
|
37
37
|
"sessions.titles.resolve",
|
|
38
|
+
"sessions.usage.get",
|
|
38
39
|
"sessions.list",
|
|
39
40
|
"skills.list",
|
|
40
41
|
"files.list",
|
|
41
42
|
"files.read",
|
|
42
43
|
"files.write",
|
|
44
|
+
"files.delete",
|
|
43
45
|
"memories.list",
|
|
44
46
|
"shell.exec",
|
|
45
47
|
"shell.session.reset",
|
|
@@ -311,6 +313,10 @@ export async function executeHermesCommand(command, args, options = {}) {
|
|
|
311
313
|
const sessionIds = optionalStringArray(args, "sessionIds") ?? [];
|
|
312
314
|
return { titles: resolveSessionTitles(sessionIds) };
|
|
313
315
|
}
|
|
316
|
+
case "sessions.usage.get": {
|
|
317
|
+
const sessionId = requireString(args, "sessionId");
|
|
318
|
+
return getSessionUsage(sessionId);
|
|
319
|
+
}
|
|
314
320
|
case "sessions.list": {
|
|
315
321
|
const limit = optionalNumber(args, "limit");
|
|
316
322
|
return { sessions: listHermesSessions({ limit: limit ?? 200 }) };
|
|
@@ -337,6 +343,13 @@ export async function executeHermesCommand(command, args, options = {}) {
|
|
|
337
343
|
}
|
|
338
344
|
return await executeFilesWrite({ path, content });
|
|
339
345
|
}
|
|
346
|
+
case "files.delete": {
|
|
347
|
+
const path = optionalString(args, "path");
|
|
348
|
+
if (!path) {
|
|
349
|
+
throw new HermesCommandError("invalid_command_args", 'Missing "path"');
|
|
350
|
+
}
|
|
351
|
+
return await executeFilesDelete({ path });
|
|
352
|
+
}
|
|
340
353
|
case "memories.list":
|
|
341
354
|
return await executeMemoriesList();
|
|
342
355
|
case "shell.exec": {
|
|
@@ -6,6 +6,10 @@ export declare function executeFilesWrite(args: Record<string, unknown>): Promis
|
|
|
6
6
|
relativePath: string;
|
|
7
7
|
size: number;
|
|
8
8
|
}>;
|
|
9
|
+
export declare function executeFilesDelete(args: Record<string, unknown>): Promise<{
|
|
10
|
+
ok: true;
|
|
11
|
+
deletedPath: string;
|
|
12
|
+
}>;
|
|
9
13
|
export declare function executeMemoriesList(): Promise<{
|
|
10
14
|
files: import("./hermesFiles.js").HermesFileEntry[];
|
|
11
15
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermesFileCommands.d.ts","sourceRoot":"","sources":["../src/hermesFileCommands.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hermesFileCommands.d.ts","sourceRoot":"","sources":["../src/hermesFileCommands.ts"],"names":[],"mappings":"AAgCA,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;GAKnE;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;GAUpE;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;GAMrE;AAED,wBAAsB,mBAAmB;;GAExC;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;;;GAkCnE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_FILES_READ_MAX_BYTES, listHermesFiles, listHermesMemoryFiles, readHermesFileBytes, writeHermesFile, } from "./hermesFiles.js";
|
|
1
|
+
import { DEFAULT_FILES_READ_MAX_BYTES, deleteHermesFile, listHermesFiles, listHermesMemoryFiles, readHermesFileBytes, writeHermesFile, } from "./hermesFiles.js";
|
|
2
2
|
import { uploadFileToConvex } from "./convexRelay.js";
|
|
3
3
|
import { loadCredentials } from "./credentials.js";
|
|
4
4
|
function optionalNumber(args, key) {
|
|
@@ -39,6 +39,13 @@ export async function executeFilesWrite(args) {
|
|
|
39
39
|
}
|
|
40
40
|
return writeHermesFile(path, content);
|
|
41
41
|
}
|
|
42
|
+
export async function executeFilesDelete(args) {
|
|
43
|
+
const path = typeof args.path === "string" ? args.path.trim() : "";
|
|
44
|
+
if (!path) {
|
|
45
|
+
throw new Error('Missing "path"');
|
|
46
|
+
}
|
|
47
|
+
return deleteHermesFile(path);
|
|
48
|
+
}
|
|
42
49
|
export async function executeMemoriesList() {
|
|
43
50
|
return listHermesMemoryFiles();
|
|
44
51
|
}
|
package/dist/hermesFiles.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export declare const MAX_RELAY_UPLOAD_BYTES: number;
|
|
|
2
2
|
export declare const DEFAULT_FILES_READ_MAX_BYTES: number;
|
|
3
3
|
export declare const SOUL_RELATIVE_PATH = "SOUL.md";
|
|
4
4
|
export declare const MEMORIES_DIR = "memories";
|
|
5
|
+
export declare const SKILLS_DIR = "skills";
|
|
6
|
+
export declare const SKILL_FILE_NAME = "SKILL.md";
|
|
5
7
|
export declare const MEMORY_MD_RELATIVE_PATH = "memories/MEMORY.md";
|
|
6
8
|
export declare const USER_MD_RELATIVE_PATH = "memories/USER.md";
|
|
7
9
|
export declare const MEMORY_MD_CHAR_LIMIT = 2200;
|
|
@@ -50,12 +52,17 @@ export declare function listHermesFiles(options?: {
|
|
|
50
52
|
files: HermesFileEntry[];
|
|
51
53
|
};
|
|
52
54
|
export declare function normalizeHermesRelativePath(inputPath: string): string;
|
|
55
|
+
export declare function isWritableSkillPath(relativePath: string): boolean;
|
|
53
56
|
export declare function assertWritableHermesPath(inputPath: string): string;
|
|
54
57
|
export declare function writeHermesFile(inputPath: string, content: string): {
|
|
55
58
|
ok: true;
|
|
56
59
|
relativePath: string;
|
|
57
60
|
size: number;
|
|
58
61
|
};
|
|
62
|
+
export declare function deleteHermesFile(inputPath: string): {
|
|
63
|
+
ok: true;
|
|
64
|
+
deletedPath: string;
|
|
65
|
+
};
|
|
59
66
|
export declare function listHermesMemoryFiles(): {
|
|
60
67
|
files: HermesFileEntry[];
|
|
61
68
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermesFiles.d.ts","sourceRoot":"","sources":["../src/hermesFiles.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hermesFiles.d.ts","sourceRoot":"","sources":["../src/hermesFiles.ts"],"names":[],"mappings":"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"}
|
package/dist/hermesFiles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { loadHermesCronJobNames } from "./hermesCronJobs.js";
|
|
@@ -6,6 +6,8 @@ export const MAX_RELAY_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
|
6
6
|
export const DEFAULT_FILES_READ_MAX_BYTES = 512 * 1024;
|
|
7
7
|
export const SOUL_RELATIVE_PATH = "SOUL.md";
|
|
8
8
|
export const MEMORIES_DIR = "memories";
|
|
9
|
+
export const SKILLS_DIR = "skills";
|
|
10
|
+
export const SKILL_FILE_NAME = "SKILL.md";
|
|
9
11
|
export const MEMORY_MD_RELATIVE_PATH = "memories/MEMORY.md";
|
|
10
12
|
export const USER_MD_RELATIVE_PATH = "memories/USER.md";
|
|
11
13
|
export const MEMORY_MD_CHAR_LIMIT = 2200;
|
|
@@ -241,6 +243,15 @@ export function listHermesFiles(options = {}) {
|
|
|
241
243
|
export function normalizeHermesRelativePath(inputPath) {
|
|
242
244
|
return inputPath.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
|
243
245
|
}
|
|
246
|
+
export function isWritableSkillPath(relativePath) {
|
|
247
|
+
if (!relativePath.startsWith(`${SKILLS_DIR}/`)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (relativePath.includes("..")) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return relativePath.endsWith(`/${SKILL_FILE_NAME}`);
|
|
254
|
+
}
|
|
244
255
|
export function assertWritableHermesPath(inputPath) {
|
|
245
256
|
const relativePath = normalizeHermesRelativePath(inputPath);
|
|
246
257
|
if (!relativePath) {
|
|
@@ -249,6 +260,9 @@ export function assertWritableHermesPath(inputPath) {
|
|
|
249
260
|
if (relativePath === SOUL_RELATIVE_PATH) {
|
|
250
261
|
return relativePath;
|
|
251
262
|
}
|
|
263
|
+
if (isWritableSkillPath(relativePath)) {
|
|
264
|
+
return relativePath;
|
|
265
|
+
}
|
|
252
266
|
const memoriesPrefix = `${MEMORIES_DIR}/`;
|
|
253
267
|
if (!relativePath.startsWith(memoriesPrefix)) {
|
|
254
268
|
throw new Error(`Path is not writable: ${inputPath}`);
|
|
@@ -277,13 +291,30 @@ export function writeHermesFile(inputPath, content) {
|
|
|
277
291
|
}
|
|
278
292
|
const home = resolveHermesHome();
|
|
279
293
|
const resolved = resolveSandboxedPath(relativePath);
|
|
280
|
-
if (relativePath.startsWith(`${
|
|
294
|
+
if (relativePath.startsWith(`${SKILLS_DIR}/`)) {
|
|
295
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
else if (relativePath.startsWith(`${MEMORIES_DIR}/`)) {
|
|
281
298
|
mkdirSync(join(home, MEMORIES_DIR), { recursive: true });
|
|
282
299
|
}
|
|
283
300
|
const bytes = Buffer.from(content, "utf8");
|
|
284
301
|
writeFileSync(resolved, bytes);
|
|
285
302
|
return { ok: true, relativePath, size: bytes.length };
|
|
286
303
|
}
|
|
304
|
+
export function deleteHermesFile(inputPath) {
|
|
305
|
+
const relativePath = normalizeHermesRelativePath(inputPath);
|
|
306
|
+
if (!isWritableSkillPath(relativePath)) {
|
|
307
|
+
throw new Error(`Path is not deletable: ${inputPath}`);
|
|
308
|
+
}
|
|
309
|
+
const resolved = resolveSandboxedPath(relativePath);
|
|
310
|
+
if (!existsSync(resolved)) {
|
|
311
|
+
throw new Error(`File not found: ${inputPath}`);
|
|
312
|
+
}
|
|
313
|
+
const skillDir = dirname(resolved);
|
|
314
|
+
const deletedPath = relativePath.slice(0, relativePath.length - `/${SKILL_FILE_NAME}`.length);
|
|
315
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
316
|
+
return { ok: true, deletedPath };
|
|
317
|
+
}
|
|
287
318
|
function memoryFileEntry(home, fileName) {
|
|
288
319
|
const relativePath = `${MEMORIES_DIR}/${fileName}`;
|
|
289
320
|
const fullPath = join(home, MEMORIES_DIR, fileName);
|
package/dist/hermesFiles.test.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
|
-
import { assertWritableHermesPath, cronOutputSourceKey, listHermesFiles, listHermesMemoryFiles, MEMORY_MD_CHAR_LIMIT, MEMORY_MD_RELATIVE_PATH, readHermesFileBytes, SOUL_RELATIVE_PATH, USER_MD_CHAR_LIMIT, writeHermesFile, } from "./hermesFiles.js";
|
|
6
|
+
import { assertWritableHermesPath, cronOutputSourceKey, deleteHermesFile, listHermesFiles, listHermesMemoryFiles, MEMORY_MD_CHAR_LIMIT, MEMORY_MD_RELATIVE_PATH, readHermesFileBytes, SKILLS_DIR, SKILL_FILE_NAME, SOUL_RELATIVE_PATH, USER_MD_CHAR_LIMIT, writeHermesFile, } from "./hermesFiles.js";
|
|
7
7
|
test("cronOutputSourceKey extracts jobId/filename from cron output paths", () => {
|
|
8
8
|
assert.equal(cronOutputSourceKey("cron/output/abc123/2026-05-25_06-30-00.md"), "abc123/2026-05-25_06-30-00.md");
|
|
9
9
|
assert.equal(cronOutputSourceKey("cache/documents/report.pdf"), undefined);
|
|
@@ -36,13 +36,38 @@ test("listHermesFiles includes cron output with source metadata", () => {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
|
-
test("assertWritableHermesPath allows SOUL.md
|
|
39
|
+
test("assertWritableHermesPath allows SOUL.md, memories markdown, and skills", () => {
|
|
40
40
|
assert.equal(assertWritableHermesPath("SOUL.md"), SOUL_RELATIVE_PATH);
|
|
41
41
|
assert.equal(assertWritableHermesPath("memories/MEMORY.md"), MEMORY_MD_RELATIVE_PATH);
|
|
42
|
+
assert.equal(assertWritableHermesPath(`${SKILLS_DIR}/my-skill/${SKILL_FILE_NAME}`), `${SKILLS_DIR}/my-skill/${SKILL_FILE_NAME}`);
|
|
42
43
|
assert.throws(() => assertWritableHermesPath("cron/output/x.md"));
|
|
43
44
|
assert.throws(() => assertWritableHermesPath("memories/nested/x.md"));
|
|
45
|
+
assert.throws(() => assertWritableHermesPath(`${SKILLS_DIR}/my-skill/other.md`));
|
|
44
46
|
assert.throws(() => assertWritableHermesPath("../SOUL.md"));
|
|
45
47
|
});
|
|
48
|
+
test("writeHermesFile creates skill directories and deleteHermesFile removes them", () => {
|
|
49
|
+
const home = mkdtempSync(join(tmpdir(), "hermes-skill-write-test-"));
|
|
50
|
+
const previousHome = process.env.HERMES_HOME;
|
|
51
|
+
process.env.HERMES_HOME = home;
|
|
52
|
+
try {
|
|
53
|
+
const skillPath = `${SKILLS_DIR}/demo-skill/${SKILL_FILE_NAME}`;
|
|
54
|
+
const content = "---\nname: demo-skill\ndescription: Demo\n---\n\n# Demo\n";
|
|
55
|
+
writeHermesFile(skillPath, content);
|
|
56
|
+
const written = readHermesFileBytes(skillPath);
|
|
57
|
+
assert.equal(written.bytes.toString("utf8"), content);
|
|
58
|
+
const result = deleteHermesFile(skillPath);
|
|
59
|
+
assert.equal(result.deletedPath, `${SKILLS_DIR}/demo-skill`);
|
|
60
|
+
assert.equal(existsSync(join(home, SKILLS_DIR, "demo-skill")), false);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (previousHome === undefined) {
|
|
64
|
+
delete process.env.HERMES_HOME;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
process.env.HERMES_HOME = previousHome;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
46
71
|
test("writeHermesFile enforces memory char limits and round-trips", () => {
|
|
47
72
|
const home = mkdtempSync(join(tmpdir(), "hermes-write-test-"));
|
|
48
73
|
const previousHome = process.env.HERMES_HOME;
|
|
@@ -24,9 +24,24 @@ export type HermesSessionListEntry = {
|
|
|
24
24
|
previewText: string;
|
|
25
25
|
messageCount: number;
|
|
26
26
|
};
|
|
27
|
+
/** Hermes-injected prompts for cron runs and background skills — not user chat. */
|
|
28
|
+
export declare function isAutomatedHermesSessionPrompt(content: string): boolean;
|
|
27
29
|
export declare function listHermesSessions(options?: {
|
|
28
30
|
limit?: number;
|
|
29
31
|
}): HermesSessionListEntry[];
|
|
32
|
+
export type HermesSessionUsage = {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
resolvedSessionId: string;
|
|
35
|
+
model: string | null;
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
outputTokens: number;
|
|
38
|
+
cacheReadTokens: number;
|
|
39
|
+
cacheWriteTokens: number;
|
|
40
|
+
reasoningTokens: number;
|
|
41
|
+
estimatedCostUsd: number | null;
|
|
42
|
+
messageCount: number;
|
|
43
|
+
};
|
|
44
|
+
export declare function getSessionUsage(sessionId: string): HermesSessionUsage;
|
|
30
45
|
export declare function hermesStateDbExists(): boolean;
|
|
31
46
|
export declare function countUserMessagesSent(): {
|
|
32
47
|
count: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermesSessionDb.d.ts","sourceRoot":"","sources":["../src/hermesSessionDb.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAyIF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapE;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAuBxF;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACxE,oBAAoB,EAAE,CAwCxB;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,CAmCzD;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,sBAAsB,EAAE,
|
|
1
|
+
{"version":3,"file":"hermesSessionDb.d.ts","sourceRoot":"","sources":["../src/hermesSessionDb.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAyIF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapE;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAuBxF;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACxE,oBAAoB,EAAE,CAwCxB;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,CAmCzD;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAMF,mFAAmF;AACnF,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAqBvE;AAcD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,sBAAsB,EAAE,CAsE7F;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAiBF,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAkErE;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAED,wBAAgB,qBAAqB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAgBzD"}
|
package/dist/hermesSessionDb.js
CHANGED
|
@@ -216,11 +216,40 @@ export function getLatestTurnMessageIds(sessionId) {
|
|
|
216
216
|
function normalizeTimestampMs(value) {
|
|
217
217
|
return value < 1_000_000_000_000 ? value * 1000 : value;
|
|
218
218
|
}
|
|
219
|
+
/** Hermes-injected prompts for cron runs and background skills — not user chat. */
|
|
220
|
+
export function isAutomatedHermesSessionPrompt(content) {
|
|
221
|
+
const trimmed = content.trim();
|
|
222
|
+
if (!trimmed)
|
|
223
|
+
return false;
|
|
224
|
+
if (/\[IMPORTANT:[^\]]*scheduled cron job/i.test(trimmed)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
if (/you are running as .* background skill/i.test(trimmed)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (/^Cronjob Response:\s*.+\r?\n-+\r?\n/i.test(trimmed)) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (/^#\s*Cron Job:/im.test(trimmed) && /\bJob ID:\s*/im.test(trimmed)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
function firstUserMessageContent(db, sessionId) {
|
|
239
|
+
const row = db
|
|
240
|
+
.prepare(`SELECT content FROM messages
|
|
241
|
+
WHERE session_id = ? AND role = 'user'
|
|
242
|
+
ORDER BY timestamp ASC, id ASC
|
|
243
|
+
LIMIT 1`)
|
|
244
|
+
.get(sessionId);
|
|
245
|
+
return decodeMessageContent(row?.content ?? null);
|
|
246
|
+
}
|
|
219
247
|
export function listHermesSessions(options = {}) {
|
|
220
248
|
if (!hermesStateDbExists()) {
|
|
221
249
|
return [];
|
|
222
250
|
}
|
|
223
251
|
const limit = Math.min(Math.max(options.limit ?? 200, 1), 500);
|
|
252
|
+
const fetchLimit = Math.min(limit * 4, 500);
|
|
224
253
|
const db = openReadOnlyDb();
|
|
225
254
|
try {
|
|
226
255
|
const rows = db
|
|
@@ -235,8 +264,12 @@ export function listHermesSessions(options = {}) {
|
|
|
235
264
|
GROUP BY s.id
|
|
236
265
|
ORDER BY last_message_at DESC
|
|
237
266
|
LIMIT ?`)
|
|
238
|
-
.all(
|
|
239
|
-
|
|
267
|
+
.all(fetchLimit);
|
|
268
|
+
const entries = [];
|
|
269
|
+
for (const row of rows) {
|
|
270
|
+
if (isAutomatedHermesSessionPrompt(firstUserMessageContent(db, row.id))) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
240
273
|
const previewRow = db
|
|
241
274
|
.prepare(`SELECT content FROM messages
|
|
242
275
|
WHERE session_id = ? AND role IN ('user', 'assistant')
|
|
@@ -245,15 +278,19 @@ export function listHermesSessions(options = {}) {
|
|
|
245
278
|
.get(row.id);
|
|
246
279
|
const resolvedTitle = sessionTitleForLookupId(db, row.id);
|
|
247
280
|
const previewText = decodeMessageContent(previewRow?.content ?? null).trim().slice(0, 120);
|
|
248
|
-
|
|
281
|
+
entries.push({
|
|
249
282
|
id: row.id,
|
|
250
283
|
title: resolvedTitle ?? row.title?.trim() ?? null,
|
|
251
284
|
startedAt: normalizeTimestampMs(row.started_at ?? row.last_message_at),
|
|
252
285
|
lastMessageAt: normalizeTimestampMs(row.last_message_at),
|
|
253
286
|
previewText,
|
|
254
287
|
messageCount: row.message_count,
|
|
255
|
-
};
|
|
256
|
-
|
|
288
|
+
});
|
|
289
|
+
if (entries.length >= limit) {
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return entries;
|
|
257
294
|
}
|
|
258
295
|
catch {
|
|
259
296
|
return [];
|
|
@@ -262,6 +299,73 @@ export function listHermesSessions(options = {}) {
|
|
|
262
299
|
db.close();
|
|
263
300
|
}
|
|
264
301
|
}
|
|
302
|
+
function emptySessionUsage(sessionId, resolvedSessionId) {
|
|
303
|
+
return {
|
|
304
|
+
sessionId,
|
|
305
|
+
resolvedSessionId,
|
|
306
|
+
model: null,
|
|
307
|
+
inputTokens: 0,
|
|
308
|
+
outputTokens: 0,
|
|
309
|
+
cacheReadTokens: 0,
|
|
310
|
+
cacheWriteTokens: 0,
|
|
311
|
+
reasoningTokens: 0,
|
|
312
|
+
estimatedCostUsd: null,
|
|
313
|
+
messageCount: 0,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
export function getSessionUsage(sessionId) {
|
|
317
|
+
const trimmed = sessionId.trim();
|
|
318
|
+
if (!trimmed) {
|
|
319
|
+
return emptySessionUsage(sessionId, sessionId);
|
|
320
|
+
}
|
|
321
|
+
if (!hermesStateDbExists()) {
|
|
322
|
+
return emptySessionUsage(trimmed, trimmed);
|
|
323
|
+
}
|
|
324
|
+
const db = openReadOnlyDb();
|
|
325
|
+
try {
|
|
326
|
+
const resolved = resolveResumeSessionId(db, trimmed);
|
|
327
|
+
const chain = sessionAncestorChain(db, resolved);
|
|
328
|
+
const placeholders = chain.map(() => "?").join(", ");
|
|
329
|
+
const totals = db
|
|
330
|
+
.prepare(`SELECT
|
|
331
|
+
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
332
|
+
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
333
|
+
COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
|
|
334
|
+
COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
|
|
335
|
+
COALESCE(SUM(reasoning_tokens), 0) AS reasoning_tokens,
|
|
336
|
+
COALESCE(SUM(estimated_cost_usd), 0) AS estimated_cost_usd,
|
|
337
|
+
COALESCE(SUM(message_count), 0) AS message_count
|
|
338
|
+
FROM sessions
|
|
339
|
+
WHERE id IN (${placeholders})`)
|
|
340
|
+
.get(...chain);
|
|
341
|
+
const modelRow = db
|
|
342
|
+
.prepare("SELECT model FROM sessions WHERE id = ?")
|
|
343
|
+
.get(resolved);
|
|
344
|
+
const model = modelRow?.model?.trim() || null;
|
|
345
|
+
const estimatedCost = totals?.estimated_cost_usd;
|
|
346
|
+
const estimatedCostUsd = estimatedCost != null && Number.isFinite(estimatedCost) && estimatedCost > 0
|
|
347
|
+
? estimatedCost
|
|
348
|
+
: null;
|
|
349
|
+
return {
|
|
350
|
+
sessionId: trimmed,
|
|
351
|
+
resolvedSessionId: resolved,
|
|
352
|
+
model,
|
|
353
|
+
inputTokens: totals?.input_tokens ?? 0,
|
|
354
|
+
outputTokens: totals?.output_tokens ?? 0,
|
|
355
|
+
cacheReadTokens: totals?.cache_read_tokens ?? 0,
|
|
356
|
+
cacheWriteTokens: totals?.cache_write_tokens ?? 0,
|
|
357
|
+
reasoningTokens: totals?.reasoning_tokens ?? 0,
|
|
358
|
+
estimatedCostUsd,
|
|
359
|
+
messageCount: totals?.message_count ?? 0,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return emptySessionUsage(trimmed, trimmed);
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
db.close();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
265
369
|
export function hermesStateDbExists() {
|
|
266
370
|
try {
|
|
267
371
|
readFileSync(hermesStateDbPath());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermesSessionDb.test.d.ts","sourceRoot":"","sources":["../src/hermesSessionDb.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdirSync, mkdtempSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import Database from "better-sqlite3";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { getSessionUsage, isAutomatedHermesSessionPrompt } from "./hermesSessionDb.js";
|
|
8
|
+
function withHermesStateDb(setup, run) {
|
|
9
|
+
const home = mkdtempSync(join(tmpdir(), "hermes-session-db-test-"));
|
|
10
|
+
const previousHome = process.env.HERMES_HOME;
|
|
11
|
+
process.env.HERMES_HOME = home;
|
|
12
|
+
try {
|
|
13
|
+
mkdirSync(home, { recursive: true });
|
|
14
|
+
const db = new Database(join(home, "state.db"));
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE sessions (
|
|
17
|
+
id TEXT PRIMARY KEY,
|
|
18
|
+
source TEXT NOT NULL,
|
|
19
|
+
user_id TEXT,
|
|
20
|
+
model TEXT,
|
|
21
|
+
model_config TEXT,
|
|
22
|
+
system_prompt TEXT,
|
|
23
|
+
parent_session_id TEXT,
|
|
24
|
+
started_at REAL NOT NULL,
|
|
25
|
+
ended_at REAL,
|
|
26
|
+
end_reason TEXT,
|
|
27
|
+
message_count INTEGER DEFAULT 0,
|
|
28
|
+
tool_call_count INTEGER DEFAULT 0,
|
|
29
|
+
input_tokens INTEGER DEFAULT 0,
|
|
30
|
+
output_tokens INTEGER DEFAULT 0,
|
|
31
|
+
cache_read_tokens INTEGER DEFAULT 0,
|
|
32
|
+
cache_write_tokens INTEGER DEFAULT 0,
|
|
33
|
+
reasoning_tokens INTEGER DEFAULT 0,
|
|
34
|
+
estimated_cost_usd REAL,
|
|
35
|
+
FOREIGN KEY (parent_session_id) REFERENCES sessions(id)
|
|
36
|
+
);
|
|
37
|
+
CREATE TABLE messages (
|
|
38
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
39
|
+
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
40
|
+
role TEXT NOT NULL,
|
|
41
|
+
content TEXT,
|
|
42
|
+
timestamp REAL NOT NULL
|
|
43
|
+
);
|
|
44
|
+
`);
|
|
45
|
+
setup(db);
|
|
46
|
+
db.close();
|
|
47
|
+
run();
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
if (previousHome === undefined) {
|
|
51
|
+
delete process.env.HERMES_HOME;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
process.env.HERMES_HOME = previousHome;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
test("isAutomatedHermesSessionPrompt detects scheduled cron job instructions", () => {
|
|
59
|
+
const content = "[IMPORTANT: You are running as a scheduled cron job. DELIVERY: Your final response is the delivery channel.]";
|
|
60
|
+
assert.equal(isAutomatedHermesSessionPrompt(content), true);
|
|
61
|
+
});
|
|
62
|
+
test("isAutomatedHermesSessionPrompt detects background skill runs", () => {
|
|
63
|
+
const content = "You are running as Hermes' background skill CURATOR. This is an UMBRELLA-BUILDING task.";
|
|
64
|
+
assert.equal(isAutomatedHermesSessionPrompt(content), true);
|
|
65
|
+
});
|
|
66
|
+
test("isAutomatedHermesSessionPrompt detects Hermes cron wrapper prompts", () => {
|
|
67
|
+
const content = `Cronjob Response: Morning feeds
|
|
68
|
+
-------------
|
|
69
|
+
Summarize today's AI news.
|
|
70
|
+
|
|
71
|
+
Note: The agent cannot see this message, and therefore cannot respond to it.`;
|
|
72
|
+
assert.equal(isAutomatedHermesSessionPrompt(content), true);
|
|
73
|
+
});
|
|
74
|
+
test("isAutomatedHermesSessionPrompt detects cron metadata headers", () => {
|
|
75
|
+
const content = `# Cron Job: Morning briefing
|
|
76
|
+
- Job ID: \`abc123\`
|
|
77
|
+
- Run Time: 2026-06-01 08:41:00`;
|
|
78
|
+
assert.equal(isAutomatedHermesSessionPrompt(content), true);
|
|
79
|
+
});
|
|
80
|
+
test("isAutomatedHermesSessionPrompt returns false for normal user chat", () => {
|
|
81
|
+
assert.equal(isAutomatedHermesSessionPrompt("Kan du lage en cron job som kjører hvert 5. minutt?"), false);
|
|
82
|
+
assert.equal(isAutomatedHermesSessionPrompt("Flott"), false);
|
|
83
|
+
assert.equal(isAutomatedHermesSessionPrompt("Sure, I can help with that."), false);
|
|
84
|
+
});
|
|
85
|
+
test("getSessionUsage returns zeros when state.db is missing", () => {
|
|
86
|
+
const previousHome = process.env.HERMES_HOME;
|
|
87
|
+
process.env.HERMES_HOME = mkdtempSync(join(tmpdir(), "hermes-session-db-missing-"));
|
|
88
|
+
try {
|
|
89
|
+
const usage = getSessionUsage("sess_missing");
|
|
90
|
+
assert.equal(usage.sessionId, "sess_missing");
|
|
91
|
+
assert.equal(usage.inputTokens, 0);
|
|
92
|
+
assert.equal(usage.outputTokens, 0);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
if (previousHome === undefined) {
|
|
96
|
+
delete process.env.HERMES_HOME;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
process.env.HERMES_HOME = previousHome;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
test("getSessionUsage sums token counters across parent and child sessions", () => {
|
|
104
|
+
withHermesStateDb((db) => {
|
|
105
|
+
const now = Date.now() / 1000;
|
|
106
|
+
db.prepare(`INSERT INTO sessions (
|
|
107
|
+
id, source, model, parent_session_id, started_at,
|
|
108
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
109
|
+
reasoning_tokens, estimated_cost_usd, message_count
|
|
110
|
+
) VALUES (?, 'cli', ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?)`).run("parent_sess", "anthropic/claude-sonnet-4", now, 10_000, 2_000, 500, 100, 50, 0.12, 8);
|
|
111
|
+
db.prepare(`INSERT INTO sessions (
|
|
112
|
+
id, source, model, parent_session_id, started_at,
|
|
113
|
+
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
114
|
+
reasoning_tokens, estimated_cost_usd, message_count
|
|
115
|
+
) VALUES (?, 'cli', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run("child_sess", "anthropic/claude-sonnet-4", "parent_sess", now + 1, 5_000, 1_000, 200, 40, 20, 0.05, 4);
|
|
116
|
+
db.prepare(`INSERT INTO messages (session_id, role, content, timestamp)
|
|
117
|
+
VALUES (?, 'user', ?, ?)`).run("child_sess", "hello", now + 2);
|
|
118
|
+
}, () => {
|
|
119
|
+
const usage = getSessionUsage("child_sess");
|
|
120
|
+
assert.equal(usage.resolvedSessionId, "child_sess");
|
|
121
|
+
assert.equal(usage.model, "anthropic/claude-sonnet-4");
|
|
122
|
+
assert.equal(usage.inputTokens, 15_000);
|
|
123
|
+
assert.equal(usage.outputTokens, 3_000);
|
|
124
|
+
assert.equal(usage.cacheReadTokens, 700);
|
|
125
|
+
assert.equal(usage.cacheWriteTokens, 140);
|
|
126
|
+
assert.equal(usage.reasoningTokens, 70);
|
|
127
|
+
assert.equal(usage.messageCount, 12);
|
|
128
|
+
assert.ok(usage.estimatedCostUsd != null && Math.abs(usage.estimatedCostUsd - 0.17) < 0.001);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
test("getSessionUsage resolves parent id to child session with messages", () => {
|
|
132
|
+
withHermesStateDb((db) => {
|
|
133
|
+
const now = Date.now() / 1000;
|
|
134
|
+
db.prepare(`INSERT INTO sessions (
|
|
135
|
+
id, source, model, parent_session_id, started_at,
|
|
136
|
+
input_tokens, output_tokens, message_count
|
|
137
|
+
) VALUES (?, 'cli', ?, NULL, ?, ?, ?, ?)`).run("parent_only", "hermes-agent", now, 100, 50, 2);
|
|
138
|
+
db.prepare(`INSERT INTO sessions (
|
|
139
|
+
id, source, model, parent_session_id, started_at,
|
|
140
|
+
input_tokens, output_tokens, message_count
|
|
141
|
+
) VALUES (?, 'cli', ?, ?, ?, ?, ?, ?)`).run("child_active", "hermes-agent", "parent_only", now + 1, 400, 200, 3);
|
|
142
|
+
db.prepare(`INSERT INTO messages (session_id, role, content, timestamp)
|
|
143
|
+
VALUES (?, 'user', ?, ?)`).run("child_active", "follow-up", now + 2);
|
|
144
|
+
}, () => {
|
|
145
|
+
const usage = getSessionUsage("parent_only");
|
|
146
|
+
assert.equal(usage.resolvedSessionId, "child_active");
|
|
147
|
+
assert.equal(usage.inputTokens, 500);
|
|
148
|
+
assert.equal(usage.outputTokens, 250);
|
|
149
|
+
assert.equal(usage.messageCount, 5);
|
|
150
|
+
});
|
|
151
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export type { PairingInfo } from "./resolve.js";
|
|
|
7
7
|
export { pairWithCleos } from "./pairWithCleos.js";
|
|
8
8
|
export { createHermesMessageHandler, forwardToHermes, resolveHermesApiConfig } from "./hermesForwarder.js";
|
|
9
9
|
export type { HermesForwarderOptions, HermesForwardResult } from "./hermesForwarder.js";
|
|
10
|
-
export { listHermesSessions, listSessionMessages, resolveSessionTitle, resolveSessionTitles, } from "./hermesSessionDb.js";
|
|
11
|
-
export type { HermesSessionListEntry } from "./hermesSessionDb.js";
|
|
10
|
+
export { getSessionUsage, listHermesSessions, listSessionMessages, resolveSessionTitle, resolveSessionTitles, } from "./hermesSessionDb.js";
|
|
11
|
+
export type { HermesSessionListEntry, HermesSessionUsage } from "./hermesSessionDb.js";
|
|
12
12
|
export type { HermesSessionMessage } from "./hermesSessionDb.js";
|
|
13
13
|
export { executeHermesCommand, HERMES_COMMAND_NAMES, isHermesCommandName, userSafeCommandError, } from "./hermesCommands.js";
|
|
14
14
|
export type { HermesCommandErrorCode, HermesCommandName } from "./hermesCommands.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvF,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3G,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvF,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3G,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACvF,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,yBAAyB,EACzB,6BAA6B,EAC7B,sBAAsB,EACtB,2BAA2B,EAC3B,0BAA0B,EAC1B,gCAAgC,EAChC,+BAA+B,EAC/B,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { loadCredentials, saveCredentials, credentialsPathForDisplay } from "./c
|
|
|
3
3
|
export { resolvePairingCode, convexSiteUrlFromEnv } from "./resolve.js";
|
|
4
4
|
export { pairWithCleos } from "./pairWithCleos.js";
|
|
5
5
|
export { createHermesMessageHandler, forwardToHermes, resolveHermesApiConfig } from "./hermesForwarder.js";
|
|
6
|
-
export { listHermesSessions, listSessionMessages, resolveSessionTitle, resolveSessionTitles, } from "./hermesSessionDb.js";
|
|
6
|
+
export { getSessionUsage, listHermesSessions, listSessionMessages, resolveSessionTitle, resolveSessionTitles, } from "./hermesSessionDb.js";
|
|
7
7
|
export { executeHermesCommand, HERMES_COMMAND_NAMES, isHermesCommandName, userSafeCommandError, } from "./hermesCommands.js";
|
|
8
8
|
export { startCronWatcher, listPendingCronFiles, clearDeliveredIndex } from "./cronWatcher.js";
|
|
9
9
|
export { backfillCronDeliveries, describeCronDeliveryState } from "./cronBackfill.js";
|
package/dist/skillsList.d.ts
CHANGED
package/dist/skillsList.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skillsList.d.ts","sourceRoot":"","sources":["../src/skillsList.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skillsList.d.ts","sourceRoot":"","sources":["../src/skillsList.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAuOF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC,CAOhF"}
|
package/dist/skillsList.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
3
|
import { join, relative } from "node:path";
|
|
5
4
|
import { promisify } from "node:util";
|
|
5
|
+
import { resolveHermesHome } from "./hermesFiles.js";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
|
-
const HERMES_SKILLS_DIR = join(homedir(), ".hermes", "skills");
|
|
8
7
|
const LIST_TIMEOUT_MS = 30_000;
|
|
9
8
|
const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
10
9
|
const SKILL_FILE_NAME = "SKILL.md";
|
|
10
|
+
const SKILLS_DIR = "skills";
|
|
11
11
|
const HIDDEN_DIR_NAMES = new Set([".hub", ".git"]);
|
|
12
|
+
function skillsRoot() {
|
|
13
|
+
return join(resolveHermesHome(), SKILLS_DIR);
|
|
14
|
+
}
|
|
12
15
|
function normalizeSkillsPayload(value) {
|
|
13
16
|
if (Array.isArray(value)) {
|
|
14
17
|
return { skills: value.flatMap((item) => parseSkillRecord(item)) };
|
|
@@ -33,11 +36,15 @@ function parseSkillRecord(value) {
|
|
|
33
36
|
return [];
|
|
34
37
|
}
|
|
35
38
|
const description = pickString(record, ["description", "desc", "summary"]) ?? "";
|
|
36
|
-
const category = pickString(record, ["category", "
|
|
39
|
+
const category = pickString(record, ["category", "group"]);
|
|
40
|
+
const relativePath = pickString(record, ["relativePath", "relative_path", "path"]);
|
|
37
41
|
const entry = { name, description };
|
|
38
42
|
if (category) {
|
|
39
43
|
entry.category = category;
|
|
40
44
|
}
|
|
45
|
+
if (relativePath && relativePath.endsWith(`/${SKILL_FILE_NAME}`)) {
|
|
46
|
+
entry.relativePath = relativePath.replace(/\\/g, "/");
|
|
47
|
+
}
|
|
41
48
|
return [entry];
|
|
42
49
|
}
|
|
43
50
|
function pickString(record, keys) {
|
|
@@ -79,9 +86,9 @@ function parseFrontmatter(raw) {
|
|
|
79
86
|
}
|
|
80
87
|
return frontmatter;
|
|
81
88
|
}
|
|
82
|
-
function deriveCategory(
|
|
89
|
+
function deriveCategory(skillsRootDir, skillFilePath) {
|
|
83
90
|
const skillDir = join(skillFilePath, "..");
|
|
84
|
-
const relativePath = relative(
|
|
91
|
+
const relativePath = relative(skillsRootDir, skillDir);
|
|
85
92
|
if (!relativePath || relativePath === ".") {
|
|
86
93
|
return undefined;
|
|
87
94
|
}
|
|
@@ -91,8 +98,16 @@ function deriveCategory(skillsRoot, skillFilePath) {
|
|
|
91
98
|
}
|
|
92
99
|
return segments[0];
|
|
93
100
|
}
|
|
101
|
+
function skillLookupKey(skill) {
|
|
102
|
+
return `${skill.category ?? ""}\0${skill.name.toLowerCase()}`;
|
|
103
|
+
}
|
|
104
|
+
function toHermesRelativeSkillPath(skillsRootDir, skillFilePath) {
|
|
105
|
+
const fromSkillsDir = relative(skillsRootDir, skillFilePath).replace(/\\/g, "/");
|
|
106
|
+
return `${SKILLS_DIR}/${fromSkillsDir}`;
|
|
107
|
+
}
|
|
94
108
|
function readSkillsFromDirectory() {
|
|
95
|
-
|
|
109
|
+
const root = skillsRoot();
|
|
110
|
+
if (!existsSync(root)) {
|
|
96
111
|
return { skills: [] };
|
|
97
112
|
}
|
|
98
113
|
const skills = [];
|
|
@@ -134,22 +149,26 @@ function readSkillsFromDirectory() {
|
|
|
134
149
|
continue;
|
|
135
150
|
}
|
|
136
151
|
const frontmatter = parseFrontmatter(raw);
|
|
137
|
-
const
|
|
138
|
-
const fallbackName =
|
|
152
|
+
const dirRelativePath = relative(root, join(fullPath, ".."));
|
|
153
|
+
const fallbackName = dirRelativePath.split(/[/\\]/).filter(Boolean).pop() ?? dirRelativePath;
|
|
139
154
|
const name = frontmatter.name?.trim() || fallbackName;
|
|
140
155
|
if (!name) {
|
|
141
156
|
continue;
|
|
142
157
|
}
|
|
143
158
|
const description = frontmatter.description?.trim() ?? "";
|
|
144
|
-
const category = deriveCategory(
|
|
145
|
-
const skill = {
|
|
159
|
+
const category = deriveCategory(root, fullPath);
|
|
160
|
+
const skill = {
|
|
161
|
+
name,
|
|
162
|
+
description,
|
|
163
|
+
relativePath: toHermesRelativeSkillPath(root, fullPath),
|
|
164
|
+
};
|
|
146
165
|
if (category) {
|
|
147
166
|
skill.category = category;
|
|
148
167
|
}
|
|
149
168
|
skills.push(skill);
|
|
150
169
|
}
|
|
151
170
|
}
|
|
152
|
-
walk(
|
|
171
|
+
walk(root);
|
|
153
172
|
skills.sort((left, right) => {
|
|
154
173
|
const categoryCompare = (left.category ?? "").localeCompare(right.category ?? "", undefined, {
|
|
155
174
|
sensitivity: "base",
|
|
@@ -161,6 +180,25 @@ function readSkillsFromDirectory() {
|
|
|
161
180
|
});
|
|
162
181
|
return { skills };
|
|
163
182
|
}
|
|
183
|
+
function enrichSkillsWithFilesystemPaths(skills) {
|
|
184
|
+
const filesystemSkills = readSkillsFromDirectory();
|
|
185
|
+
const pathByKey = new Map();
|
|
186
|
+
for (const skill of filesystemSkills.skills) {
|
|
187
|
+
if (skill.relativePath) {
|
|
188
|
+
pathByKey.set(skillLookupKey(skill), skill.relativePath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return skills.map((skill) => {
|
|
192
|
+
if (skill.relativePath) {
|
|
193
|
+
return skill;
|
|
194
|
+
}
|
|
195
|
+
const relativePath = pathByKey.get(skillLookupKey(skill));
|
|
196
|
+
if (!relativePath) {
|
|
197
|
+
return skill;
|
|
198
|
+
}
|
|
199
|
+
return { ...skill, relativePath };
|
|
200
|
+
});
|
|
201
|
+
}
|
|
164
202
|
async function runHermesSkillsListJson() {
|
|
165
203
|
try {
|
|
166
204
|
const { stdout } = await execFileAsync("hermes", ["skills", "list", "--json"], {
|
|
@@ -181,7 +219,7 @@ async function runHermesSkillsListJson() {
|
|
|
181
219
|
export async function listHermesSkills() {
|
|
182
220
|
const jsonResult = await runHermesSkillsListJson();
|
|
183
221
|
if (jsonResult != null) {
|
|
184
|
-
return jsonResult;
|
|
222
|
+
return { skills: enrichSkillsWithFilesystemPaths(jsonResult.skills) };
|
|
185
223
|
}
|
|
186
224
|
return readSkillsFromDirectory();
|
|
187
225
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saleso.innovations/bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Connect your Hermes agent to the Cleos iOS app via pairing code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"lint": "eslint . --max-warnings 0",
|
|
37
37
|
"check-types": "tsc --noEmit",
|
|
38
38
|
"prepublishOnly": "npm run build",
|
|
39
|
-
"test": "node --import tsx --test src/hermesFiles.test.ts src/shellSession.test.ts"
|
|
39
|
+
"test": "node --import tsx --test src/hermesFiles.test.ts src/hermesSessionDb.test.ts src/shellSession.test.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"better-sqlite3": "^11.10.0",
|