@saleso.innovations/bridge 0.1.15 → 0.1.17

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 (40) hide show
  1. package/INTEGRATION.md +11 -0
  2. package/README.md +8 -1
  3. package/dist/cli.js +25 -0
  4. package/dist/client.d.ts +3 -2
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +6 -2
  7. package/dist/convexRelay.d.ts +24 -0
  8. package/dist/convexRelay.d.ts.map +1 -0
  9. package/dist/convexRelay.js +70 -0
  10. package/dist/cronBackfill.d.ts +16 -0
  11. package/dist/cronBackfill.d.ts.map +1 -0
  12. package/dist/cronBackfill.js +14 -0
  13. package/dist/cronWatcher.d.ts +14 -0
  14. package/dist/cronWatcher.d.ts.map +1 -1
  15. package/dist/cronWatcher.js +85 -41
  16. package/dist/hermesCommands.d.ts +1 -1
  17. package/dist/hermesCommands.d.ts.map +1 -1
  18. package/dist/hermesCommands.js +41 -0
  19. package/dist/hermesFileCommands.d.ts +19 -0
  20. package/dist/hermesFileCommands.d.ts.map +1 -0
  21. package/dist/hermesFileCommands.js +61 -0
  22. package/dist/hermesFiles.d.ts +60 -0
  23. package/dist/hermesFiles.d.ts.map +1 -0
  24. package/dist/hermesFiles.js +266 -0
  25. package/dist/hermesForwarder.d.ts +4 -1
  26. package/dist/hermesForwarder.d.ts.map +1 -1
  27. package/dist/hermesForwarder.js +80 -3
  28. package/dist/hermesSessionDb.d.ts +21 -0
  29. package/dist/hermesSessionDb.d.ts.map +1 -0
  30. package/dist/hermesSessionDb.js +174 -0
  31. package/dist/index.d.ts +5 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -1
  34. package/dist/runtimeVersion.d.ts +14 -0
  35. package/dist/runtimeVersion.d.ts.map +1 -0
  36. package/dist/runtimeVersion.js +105 -0
  37. package/dist/skillsList.d.ts +9 -0
  38. package/dist/skillsList.d.ts.map +1 -0
  39. package/dist/skillsList.js +187 -0
  40. package/package.json +14 -3
package/INTEGRATION.md CHANGED
@@ -109,6 +109,17 @@ Codes expire after 10 minutes. Each code links **one** Hermes instance to **one*
109
109
 
110
110
  After first pairing, credentials are saved to `~/.cleos/agent.json`. Delivered cron files are tracked in `~/.cleos/cron-delivered.json` so restarts do not duplicate Jobs. Use `reconnectHermesAgent()` on restart — no new pairing code required.
111
111
 
112
+ ### Recover missed cron results
113
+
114
+ If cron results reached Telegram but not Cleos (for example after a bridge restart), re-deliver pending output files:
115
+
116
+ ```bash
117
+ cleos-bridge cron-backfill --list # inspect pending files
118
+ cleos-bridge cron-backfill --reset # clear delivery index and re-send all output files
119
+ ```
120
+
121
+ Keep `cleos-bridge start` running so new cron runs are forwarded automatically.
122
+
112
123
  ## Self-hosting Cleos
113
124
 
114
125
  Set `CLEOS_CONVEX_SITE_URL` in the Hermes build to your Convex `.site` URL instead of the default.
package/README.md CHANGED
@@ -34,7 +34,7 @@ This installs the latest package, refreshes the CLI symlink, and restarts `cleos
34
34
  To pin a specific release (e.g. after a Cleos update):
35
35
 
36
36
  ```bash
37
- curl -fsSL https://amicable-elephant-407.convex.site/update-bridge.sh | bash -s -- 0.1.15
37
+ curl -fsSL https://amicable-elephant-407.convex.site/update-bridge.sh | bash -s -- 0.1.17
38
38
  ```
39
39
 
40
40
  ## Manual usage
@@ -58,6 +58,13 @@ Credentials are saved to `~/.cleos/agent.json`.
58
58
 
59
59
  The daemon watches `~/.hermes/cron/output/` and forwards new cron results to the Cleos Jobs tab automatically.
60
60
 
61
+ To recover missed cron output (for example after a bridge restart):
62
+
63
+ ```bash
64
+ cleos-bridge cron-backfill --list
65
+ cleos-bridge cron-backfill --reset
66
+ ```
67
+
61
68
  ## Hermes API
62
69
 
63
70
  By default the bridge forwards chat to Hermes at `http://127.0.0.1:8642/v1/chat/completions`.
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import { prepareHermesForBridge } from "./ensureHermesApi.js";
8
8
  import { createHermesMessageHandler } from "./hermesForwarder.js";
9
9
  import { convexSiteUrlFromEnv } from "./resolve.js";
10
10
  import { normalizePairingCode } from "./normalizePairingCode.js";
11
+ import { backfillCronDeliveries, describeCronDeliveryState } from "./cronBackfill.js";
11
12
  async function main() {
12
13
  const [, , command, codeArg] = process.argv;
13
14
  const onUserMessage = createHermesMessageHandler();
@@ -70,6 +71,28 @@ async function main() {
70
71
  runBridgeUpdate({ tag: codeArg || "latest" });
71
72
  return;
72
73
  }
74
+ if (command === "cron-backfill") {
75
+ const reset = process.argv.includes("--reset");
76
+ const listOnly = process.argv.includes("--list");
77
+ if (listOnly) {
78
+ const state = describeCronDeliveryState();
79
+ console.log(JSON.stringify({ event: "cleos-bridge.cron-backfill.list", ...state }, null, 2));
80
+ return;
81
+ }
82
+ const session = await reconnectHermesAgent({ onUserMessage });
83
+ try {
84
+ const summary = await backfillCronDeliveries(session, { reset });
85
+ console.log(JSON.stringify({ event: "cleos-bridge.cron-backfill.complete", ...summary }, null, 2));
86
+ if (summary.remainingCount > 0) {
87
+ console.error(`${summary.remainingCount} cron file(s) still pending. Keep cleos-bridge running or retry when relay is connected.`);
88
+ process.exitCode = 1;
89
+ }
90
+ }
91
+ finally {
92
+ session.close();
93
+ }
94
+ return;
95
+ }
73
96
  printUsage();
74
97
  process.exit(1);
75
98
  }
@@ -80,6 +103,8 @@ function printUsage() {
80
103
  console.error(" cleos-bridge start [CODE] Run as a reconnecting daemon (systemd)");
81
104
  console.error(" cleos-bridge reconnect Reconnect once using saved credentials");
82
105
  console.error(" cleos-bridge update [TAG] Update package and restart systemd service");
106
+ console.error(" cleos-bridge cron-backfill [--reset] [--list]");
107
+ console.error(" Re-deliver pending cron output files to Cleos");
83
108
  }
84
109
  async function waitForExit(session, onStart) {
85
110
  const stop = onStart?.();
package/dist/client.d.ts CHANGED
@@ -18,7 +18,8 @@ export type UserMessageAttachment = {
18
18
  export type UserMessageMeta = {
19
19
  agentId: string;
20
20
  conversationId: string;
21
- messageId: string;
21
+ clientMessageId: string;
22
+ replyMessageId: string;
22
23
  attachments?: UserMessageAttachment[];
23
24
  };
24
25
  export type AgentActivityPayload = {
@@ -36,7 +37,7 @@ export type AgentFailedPayload = {
36
37
  export type AgentReply = {
37
38
  delta: (text: string, sequence: number) => void;
38
39
  activity: (activity: AgentActivityPayload) => void;
39
- complete: (text: string, sequence: number) => void;
40
+ complete: (text: string, sequence: number, attachments?: UserMessageAttachment[]) => void;
40
41
  failed: (failure: AgentFailedPayload) => void;
41
42
  };
42
43
  export type CronDeliveryMeta = {
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -72,7 +72,7 @@ function createReplySender(ws, agentId, conversationId, messageId) {
72
72
  sequence: activity.sequence,
73
73
  }));
74
74
  },
75
- complete(text, sequence) {
75
+ complete(text, sequence, attachments) {
76
76
  ws.send(JSON.stringify({
77
77
  type: "agent.message",
78
78
  agentId,
@@ -81,6 +81,7 @@ function createReplySender(ws, agentId, conversationId, messageId) {
81
81
  content: text,
82
82
  sequence,
83
83
  final: true,
84
+ ...(attachments && attachments.length > 0 ? { attachments } : {}),
84
85
  }));
85
86
  },
86
87
  failed(failure) {
@@ -195,11 +196,14 @@ async function openAgentConnection(options) {
195
196
  rememberConversationId(conversationId);
196
197
  }
197
198
  const attachments = parseUserMessageAttachments(envelope.attachments);
199
+ const clientMessageId = typeof envelope.clientMessageId === "string" && envelope.clientMessageId.trim().length > 0
200
+ ? envelope.clientMessageId.trim()
201
+ : randomUUID();
198
202
  const replyMessageId = randomUUID();
199
203
  const reply = createReplySender(ws, agentId, conversationId, replyMessageId);
200
204
  if (options.onUserMessage) {
201
205
  try {
202
- await options.onUserMessage(content, { agentId, conversationId, messageId: replyMessageId, attachments }, reply);
206
+ await options.onUserMessage(content, { agentId, conversationId, clientMessageId, replyMessageId, attachments }, reply);
203
207
  }
204
208
  catch (error) {
205
209
  const { message, code } = userSafeHermesError(error);
@@ -0,0 +1,24 @@
1
+ export type ConvexUploadedFile = {
2
+ storageId: string;
3
+ url: string | null;
4
+ mimeType: string;
5
+ fileName: string;
6
+ size: number;
7
+ };
8
+ export type HermesMessageLink = {
9
+ clientMessageId?: string;
10
+ externalMessageId?: string;
11
+ hermesMessageId: string;
12
+ };
13
+ export declare function linkHermesMessageIds(args: {
14
+ agentId: string;
15
+ conversationId: string;
16
+ links: HermesMessageLink[];
17
+ }): Promise<void>;
18
+ export declare function uploadFileToConvex(args: {
19
+ agentId: string;
20
+ fileName: string;
21
+ mimeType: string;
22
+ contentBase64: string;
23
+ }): Promise<ConvexUploadedFile>;
24
+ //# sourceMappingURL=convexRelay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convexRelay.d.ts","sourceRoot":"","sources":["../src/convexRelay.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BhB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA4C9B"}
@@ -0,0 +1,70 @@
1
+ import { loadCredentials } from "./credentials.js";
2
+ import { MAX_RELAY_UPLOAD_BYTES } from "./hermesFiles.js";
3
+ import { convexSiteUrlFromEnv } from "./resolve.js";
4
+ export async function linkHermesMessageIds(args) {
5
+ if (args.links.length === 0)
6
+ return;
7
+ const credentials = loadCredentials();
8
+ const convexSiteUrl = credentials?.convexSiteUrl ?? convexSiteUrlFromEnv();
9
+ const relaySecret = process.env.CLEOS_RELAY_SECRET?.trim() || "dev-relay-secret";
10
+ const response = await fetch(`${convexSiteUrl.replace(/\/$/, "")}/relay/messages`, {
11
+ method: "POST",
12
+ headers: {
13
+ "content-type": "application/json",
14
+ "x-cleos-relay-secret": relaySecret,
15
+ },
16
+ body: JSON.stringify({
17
+ envelope: {
18
+ type: "hermes.link",
19
+ agentId: args.agentId,
20
+ conversationId: args.conversationId,
21
+ links: args.links,
22
+ },
23
+ }),
24
+ });
25
+ if (!response.ok) {
26
+ const text = await response.text();
27
+ throw new Error(`Failed to link Hermes message IDs (${response.status}): ${text}`);
28
+ }
29
+ }
30
+ export async function uploadFileToConvex(args) {
31
+ if (args.contentBase64.length === 0) {
32
+ throw new Error("contentBase64 is required");
33
+ }
34
+ const byteLength = Buffer.byteLength(args.contentBase64, "base64");
35
+ if (byteLength > MAX_RELAY_UPLOAD_BYTES) {
36
+ throw new Error(`File exceeds maximum upload size (${MAX_RELAY_UPLOAD_BYTES} bytes)`);
37
+ }
38
+ const credentials = loadCredentials();
39
+ const convexSiteUrl = credentials?.convexSiteUrl ?? convexSiteUrlFromEnv();
40
+ const relaySecret = process.env.CLEOS_RELAY_SECRET?.trim() || "dev-relay-secret";
41
+ const response = await fetch(`${convexSiteUrl.replace(/\/$/, "")}/relay/files`, {
42
+ method: "POST",
43
+ headers: {
44
+ "content-type": "application/json",
45
+ "x-cleos-relay-secret": relaySecret,
46
+ },
47
+ body: JSON.stringify({
48
+ agentId: args.agentId,
49
+ fileName: args.fileName,
50
+ mimeType: args.mimeType,
51
+ contentBase64: args.contentBase64,
52
+ }),
53
+ });
54
+ if (!response.ok) {
55
+ const text = await response.text();
56
+ throw new Error(`Failed to upload file to Convex (${response.status}): ${text}`);
57
+ }
58
+ const payload = (await response.json());
59
+ const storageId = typeof payload.storageId === "string" ? payload.storageId : "";
60
+ if (!storageId) {
61
+ throw new Error("Upload response missing storageId");
62
+ }
63
+ return {
64
+ storageId,
65
+ url: typeof payload.url === "string" ? payload.url : null,
66
+ mimeType: args.mimeType,
67
+ fileName: args.fileName,
68
+ size: byteLength,
69
+ };
70
+ }
@@ -0,0 +1,16 @@
1
+ import type { ConnectResult } from "./client.js";
2
+ export type CronBackfillSummary = {
3
+ clearedEntries: number;
4
+ pendingBefore: number;
5
+ deliveredCount: number;
6
+ remainingCount: number;
7
+ };
8
+ export declare function describeCronDeliveryState(): {
9
+ outputFileCount: number;
10
+ deliveredCount: number;
11
+ pendingFiles: string[];
12
+ };
13
+ export declare function backfillCronDeliveries(session: ConnectResult, options: {
14
+ reset: boolean;
15
+ }): Promise<CronBackfillSummary>;
16
+ //# sourceMappingURL=cronBackfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cronBackfill.d.ts","sourceRoot":"","sources":["../src/cronBackfill.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,yBAAyB,IAAI;IAC3C,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAUA;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAC1B,OAAO,CAAC,mBAAmB,CAAC,CAE9B"}
@@ -0,0 +1,14 @@
1
+ import { listCronOutputFiles, listPendingCronFiles, readDeliveredIndex, relativeOutputKey, runCronBackfill, } from "./cronWatcher.js";
2
+ export function describeCronDeliveryState() {
3
+ const delivered = readDeliveredIndex();
4
+ const outputFiles = listCronOutputFiles();
5
+ const pendingFiles = listPendingCronFiles(delivered);
6
+ return {
7
+ outputFileCount: outputFiles.length,
8
+ deliveredCount: delivered.size,
9
+ pendingFiles: pendingFiles.map(relativeOutputKey),
10
+ };
11
+ }
12
+ export async function backfillCronDeliveries(session, options) {
13
+ return runCronBackfill(session, options.reset);
14
+ }
@@ -1,4 +1,6 @@
1
1
  import type { ConnectResult } from "./client.js";
2
+ export declare const HERMES_CRON_OUTPUT_DIR: string;
3
+ export declare const DELIVERED_INDEX_PATH: string;
2
4
  type CronWatcherOptions = {
3
5
  session: ConnectResult;
4
6
  onDelivered?: (info: {
@@ -8,6 +10,18 @@ type CronWatcherOptions = {
8
10
  }) => void;
9
11
  onError?: (message: string) => void;
10
12
  };
13
+ export declare function readDeliveredIndex(): Set<string>;
14
+ export declare function writeDeliveredIndex(delivered: Set<string>): void;
15
+ export declare function clearDeliveredIndex(): number;
16
+ export declare function listCronOutputFiles(rootDir?: string): string[];
17
+ export declare function relativeOutputKey(filePath: string): string;
18
+ export declare function listPendingCronFiles(delivered?: Set<string>): string[];
11
19
  export declare function startCronWatcher(options: CronWatcherOptions): () => void;
20
+ export declare function runCronBackfill(session: ConnectResult, resetIndex: boolean): Promise<{
21
+ clearedEntries: number;
22
+ pendingBefore: number;
23
+ deliveredCount: number;
24
+ remainingCount: number;
25
+ }>;
12
26
  export {};
13
27
  //# sourceMappingURL=cronWatcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cronWatcher.d.ts","sourceRoot":"","sources":["../src/cronWatcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAejD,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACnF,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAiGF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAoExE"}
1
+ {"version":3,"file":"cronWatcher.d.ts","sourceRoot":"","sources":["../src/cronWatcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,eAAO,MAAM,sBAAsB,QAA+C,CAAC;AAEnF,eAAO,MAAM,oBAAoB,QAAmD,CAAC;AASrF,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,aAAa,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACnF,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,GAAG,CAAC,MAAM,CAAC,CAQhD;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAGhE;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAI5C;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,MAA+B,GAAG,MAAM,EAAE,CAoBtF;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,oBAAoB,CAAC,SAAS,GAAE,GAAG,CAAC,MAAM,CAAwB,GAAG,MAAM,EAAE,CAE5F;AAqHD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,IAAI,CA8BxE;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC;IAC1F,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CAsBD"}
@@ -3,12 +3,12 @@ import { homedir } from "node:os";
3
3
  import { dirname, join, relative } from "node:path";
4
4
  import { resolveActiveConversationId } from "./activeConversation.js";
5
5
  import { loadCredentials } from "./credentials.js";
6
- const HERMES_CRON_OUTPUT_DIR = join(homedir(), ".hermes", "cron", "output");
6
+ export const HERMES_CRON_OUTPUT_DIR = join(homedir(), ".hermes", "cron", "output");
7
7
  const HERMES_CRON_JOBS_FILE = join(homedir(), ".hermes", "cron", "jobs.json");
8
- const DELIVERED_INDEX_PATH = join(homedir(), ".cleos", "cron-delivered.json");
8
+ export const DELIVERED_INDEX_PATH = join(homedir(), ".cleos", "cron-delivered.json");
9
9
  const POLL_INTERVAL_MS = 5_000;
10
10
  const FILE_SETTLE_MS = 750;
11
- function readDeliveredIndex() {
11
+ export function readDeliveredIndex() {
12
12
  try {
13
13
  const parsed = JSON.parse(readFileSync(DELIVERED_INDEX_PATH, "utf8"));
14
14
  if (!Array.isArray(parsed))
@@ -19,11 +19,16 @@ function readDeliveredIndex() {
19
19
  return new Set();
20
20
  }
21
21
  }
22
- function writeDeliveredIndex(delivered) {
22
+ export function writeDeliveredIndex(delivered) {
23
23
  mkdirSync(dirname(DELIVERED_INDEX_PATH), { recursive: true });
24
24
  writeFileSync(DELIVERED_INDEX_PATH, JSON.stringify([...delivered].sort(), null, 2));
25
25
  }
26
- function listCronOutputFiles(rootDir) {
26
+ export function clearDeliveredIndex() {
27
+ const previous = readDeliveredIndex().size;
28
+ writeDeliveredIndex(new Set());
29
+ return previous;
30
+ }
31
+ export function listCronOutputFiles(rootDir = HERMES_CRON_OUTPUT_DIR) {
27
32
  if (!existsSync(rootDir))
28
33
  return [];
29
34
  const files = [];
@@ -46,6 +51,12 @@ function listCronOutputFiles(rootDir) {
46
51
  }
47
52
  return files.sort();
48
53
  }
54
+ export function relativeOutputKey(filePath) {
55
+ return relative(HERMES_CRON_OUTPUT_DIR, filePath).replace(/\\/g, "/");
56
+ }
57
+ export function listPendingCronFiles(delivered = readDeliveredIndex()) {
58
+ return listCronOutputFiles().filter((filePath) => !delivered.has(relativeOutputKey(filePath)));
59
+ }
49
60
  function loadHermesCronJobs() {
50
61
  const names = new Map();
51
62
  if (!existsSync(HERMES_CRON_JOBS_FILE))
@@ -68,9 +79,6 @@ function loadHermesCronJobs() {
68
79
  }
69
80
  return names;
70
81
  }
71
- function relativeOutputKey(filePath) {
72
- return relative(HERMES_CRON_OUTPUT_DIR, filePath).replace(/\\/g, "/");
73
- }
74
82
  function parseRunAtFromFileName(filePath) {
75
83
  const base = filePath.split("/").pop()?.replace(/\.md$/i, "") ?? "";
76
84
  if (!base)
@@ -103,14 +111,58 @@ async function readFileWhenStable(filePath) {
103
111
  function sleep(ms) {
104
112
  return new Promise((resolve) => setTimeout(resolve, ms));
105
113
  }
114
+ function isRelayNotOpenError(error) {
115
+ return error instanceof Error && error.message === "Cleos relay connection is not open";
116
+ }
117
+ async function deliverPendingFiles(session, delivered, conversationId, options) {
118
+ const jobNames = loadHermesCronJobs();
119
+ for (const filePath of listCronOutputFiles()) {
120
+ const key = relativeOutputKey(filePath);
121
+ if (delivered.has(key))
122
+ continue;
123
+ const parts = key.split("/");
124
+ const jobId = parts[0] ?? "unknown";
125
+ const jobName = jobNames.get(jobId) ?? jobId;
126
+ const content = await readFileWhenStable(filePath);
127
+ if (!content) {
128
+ delivered.add(key);
129
+ writeDeliveredIndex(delivered);
130
+ continue;
131
+ }
132
+ try {
133
+ session.deliverCronResult(content, {
134
+ conversationId,
135
+ jobId,
136
+ jobName,
137
+ runAt: parseRunAtFromFileName(filePath),
138
+ });
139
+ }
140
+ catch (error) {
141
+ if (isRelayNotOpenError(error)) {
142
+ console.log(JSON.stringify({
143
+ event: "cleos-bridge.cron-deferred",
144
+ file: key,
145
+ reason: "relay_not_open",
146
+ }));
147
+ return;
148
+ }
149
+ throw error;
150
+ }
151
+ delivered.add(key);
152
+ writeDeliveredIndex(delivered);
153
+ options.onDelivered?.({ jobId, jobName, filePath });
154
+ console.log(JSON.stringify({
155
+ event: "cleos-bridge.cron-delivered",
156
+ jobId,
157
+ jobName,
158
+ file: key,
159
+ }));
160
+ }
161
+ }
106
162
  export function startCronWatcher(options) {
107
163
  const delivered = readDeliveredIndex();
108
164
  let conversationId = loadCredentials()?.conversationId ?? null;
109
165
  let stopped = false;
110
- for (const filePath of listCronOutputFiles(HERMES_CRON_OUTPUT_DIR)) {
111
- delivered.add(relativeOutputKey(filePath));
112
- }
113
- writeDeliveredIndex(delivered);
114
166
  const tick = () => {
115
167
  void (async () => {
116
168
  if (stopped)
@@ -119,35 +171,7 @@ export function startCronWatcher(options) {
119
171
  if (!conversationId) {
120
172
  conversationId = await resolveActiveConversationId();
121
173
  }
122
- const jobNames = loadHermesCronJobs();
123
- for (const filePath of listCronOutputFiles(HERMES_CRON_OUTPUT_DIR)) {
124
- const key = relativeOutputKey(filePath);
125
- if (delivered.has(key))
126
- continue;
127
- const parts = key.split("/");
128
- const jobId = parts[0] ?? "unknown";
129
- const jobName = jobNames.get(jobId) ?? jobId;
130
- const content = await readFileWhenStable(filePath);
131
- if (!content) {
132
- delivered.add(key);
133
- continue;
134
- }
135
- options.session.deliverCronResult(content, {
136
- conversationId: conversationId,
137
- jobId,
138
- jobName,
139
- runAt: parseRunAtFromFileName(filePath),
140
- });
141
- delivered.add(key);
142
- writeDeliveredIndex(delivered);
143
- options.onDelivered?.({ jobId, jobName, filePath });
144
- console.log(JSON.stringify({
145
- event: "cleos-bridge.cron-delivered",
146
- jobId,
147
- jobName,
148
- file: key,
149
- }));
150
- }
174
+ await deliverPendingFiles(options.session, delivered, conversationId, options);
151
175
  }
152
176
  catch (error) {
153
177
  const message = error instanceof Error ? error.message : String(error);
@@ -163,3 +187,23 @@ export function startCronWatcher(options) {
163
187
  clearInterval(timer);
164
188
  };
165
189
  }
190
+ export async function runCronBackfill(session, resetIndex) {
191
+ const pendingBefore = listPendingCronFiles().length;
192
+ const clearedEntries = resetIndex ? clearDeliveredIndex() : 0;
193
+ const delivered = readDeliveredIndex();
194
+ const pendingAfterReset = listPendingCronFiles(delivered).length;
195
+ let conversationId = loadCredentials()?.conversationId ?? null;
196
+ if (!conversationId) {
197
+ conversationId = await resolveActiveConversationId();
198
+ }
199
+ const deliveredBefore = delivered.size;
200
+ await deliverPendingFiles(session, delivered, conversationId, {});
201
+ const deliveredCount = delivered.size - deliveredBefore;
202
+ const remainingCount = listPendingCronFiles(delivered).length;
203
+ return {
204
+ clearedEntries,
205
+ pendingBefore: resetIndex ? pendingAfterReset : pendingBefore,
206
+ deliveredCount,
207
+ remainingCount,
208
+ };
209
+ }
@@ -1,4 +1,4 @@
1
- export declare const HERMES_COMMAND_NAMES: readonly ["runtime.health", "runtime.detailedHealth", "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"];
1
+ 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", "skills.list", "files.list", "files.read"];
2
2
  export type HermesCommandName = (typeof HERMES_COMMAND_NAMES)[number];
3
3
  export declare function isHermesCommandName(value: string): value is HermesCommandName;
4
4
  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":"AAKA,eAAO,MAAM,oBAAoB,qYAwBvB,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;AAuFD,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,CAAA;CAAO,GACjE,OAAO,CAAC,OAAO,CAAC,CAkKlB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAetG"}
1
+ {"version":3,"file":"hermesCommands.d.ts","sourceRoot":"","sources":["../src/hermesCommands.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,oBAAoB,4fA8BvB,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;AAgGD,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,CAAA;CAAO,GACjE,OAAO,CAAC,OAAO,CAAC,CAwLlB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAetG"}
@@ -1,10 +1,15 @@
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 { listSessionMessages, countUserMessagesSent } from "./hermesSessionDb.js";
5
+ import { listHermesSkills } from "./skillsList.js";
4
6
  import { runHermesUpdate } from "./hermesUpdate.js";
7
+ import { executeFilesList, executeFilesRead } from "./hermesFileCommands.js";
8
+ import { fetchHermesRuntimeVersion } from "./runtimeVersion.js";
5
9
  export const HERMES_COMMAND_NAMES = [
6
10
  "runtime.health",
7
11
  "runtime.detailedHealth",
12
+ "runtime.version",
8
13
  "runtime.capabilities",
9
14
  "models.list",
10
15
  "model.set",
@@ -26,6 +31,11 @@ export const HERMES_COMMAND_NAMES = [
26
31
  "gateway.stop",
27
32
  "gateway.restart",
28
33
  "hermes.update",
34
+ "sessions.messages.list",
35
+ "sessions.messages.countSent",
36
+ "skills.list",
37
+ "files.list",
38
+ "files.read",
29
39
  ];
30
40
  export function isHermesCommandName(value) {
31
41
  return HERMES_COMMAND_NAMES.includes(value);
@@ -71,6 +81,15 @@ function optionalRecord(args, key) {
71
81
  }
72
82
  return value;
73
83
  }
84
+ function optionalNumber(args, key) {
85
+ const value = args[key];
86
+ if (value === undefined)
87
+ return undefined;
88
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
89
+ throw new HermesCommandError("invalid_command_args", `Invalid "${key}"`);
90
+ }
91
+ return Math.floor(value);
92
+ }
74
93
  async function hermesFetchJson(config, path, init = {}) {
75
94
  const url = `${hermesBaseUrl(config.apiUrl)}${path}`;
76
95
  const headers = {
@@ -109,6 +128,8 @@ export async function executeHermesCommand(command, args, options = {}) {
109
128
  return await hermesFetchJson(config, "/health");
110
129
  case "runtime.detailedHealth":
111
130
  return await hermesFetchJson(config, "/health/detailed");
131
+ case "runtime.version":
132
+ return await fetchHermesRuntimeVersion();
112
133
  case "runtime.capabilities":
113
134
  return await hermesFetchJson(config, "/v1/capabilities");
114
135
  case "models.list":
@@ -256,6 +277,26 @@ export async function executeHermesCommand(command, args, options = {}) {
256
277
  const restartGateway = args.restartGateway === true || args.restart_gateway === true;
257
278
  return await runHermesUpdate({ restartGateway });
258
279
  }
280
+ case "sessions.messages.list": {
281
+ const sessionId = requireString(args, "sessionId");
282
+ const limit = optionalNumber(args, "limit");
283
+ const offset = optionalNumber(args, "offset");
284
+ const offsetFromEnd = optionalNumber(args, "offsetFromEnd");
285
+ return listSessionMessages(sessionId, { limit, offset, offsetFromEnd });
286
+ }
287
+ case "sessions.messages.countSent":
288
+ return countUserMessagesSent();
289
+ case "skills.list":
290
+ return await listHermesSkills();
291
+ case "files.list":
292
+ return await executeFilesList(args);
293
+ case "files.read": {
294
+ const path = optionalString(args, "path");
295
+ if (!path) {
296
+ throw new HermesCommandError("invalid_command_args", 'Missing "path"');
297
+ }
298
+ return await executeFilesRead({ ...args, path });
299
+ }
259
300
  default: {
260
301
  const _exhaustive = command;
261
302
  throw new HermesCommandError("command_unsupported", `Unsupported command: ${String(_exhaustive)}`);
@@ -0,0 +1,19 @@
1
+ export declare function executeFilesList(args: Record<string, unknown>): Promise<{
2
+ files: import("./hermesFiles.js").HermesFileEntry[];
3
+ }>;
4
+ export declare function executeFilesRead(args: Record<string, unknown>): Promise<{
5
+ fileName: string;
6
+ mimeType: string;
7
+ size: number;
8
+ contentBase64: string;
9
+ storageId?: undefined;
10
+ url?: undefined;
11
+ } | {
12
+ fileName: string;
13
+ mimeType: string;
14
+ size: number;
15
+ storageId: string;
16
+ url: string | undefined;
17
+ contentBase64?: undefined;
18
+ }>;
19
+ //# sourceMappingURL=hermesFileCommands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermesFileCommands.d.ts","sourceRoot":"","sources":["../src/hermesFileCommands.ts"],"names":[],"mappings":"AA6BA,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;GAKnE;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;;;GAkCnE"}