@saleso.innovations/bridge 0.1.16 → 0.1.18

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/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.16
37
+ curl -fsSL https://amicable-elephant-407.convex.site/update-bridge.sh | bash -s -- 0.1.18
38
38
  ```
39
39
 
40
40
  ## Manual usage
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[], hermesAssistantMessageId?: string) => void;
40
41
  failed: (failure: AgentFailedPayload) => void;
41
42
  };
42
43
  export type CronDeliveryMeta = {
@@ -50,7 +51,7 @@ export type ConnectResult = {
50
51
  agentToken: string;
51
52
  close: () => void;
52
53
  closed: Promise<void>;
53
- deliverCronResult: (content: string, meta: CronDeliveryMeta) => void;
54
+ deliverCronResult: (content: string, meta: CronDeliveryMeta) => Promise<void>;
54
55
  };
55
56
  export declare function pairCleosAgent(options: Omit<ConnectOptions, "onUserMessage">): Promise<{
56
57
  agentId: string;
@@ -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
@@ -29,12 +29,13 @@ function parseUserMessageAttachments(raw) {
29
29
  }
30
30
  return attachments.length > 0 ? attachments : undefined;
31
31
  }
32
- function sendCronResult(ws, agentId, content, meta) {
32
+ const CRON_DELIVERY_TIMEOUT_MS = 45_000;
33
+ function sendCronResult(ws, agentId, messageId, content, meta) {
33
34
  ws.send(JSON.stringify({
34
35
  type: "agent.message",
35
36
  agentId,
36
37
  conversationId: meta.conversationId,
37
- messageId: randomUUID(),
38
+ messageId,
38
39
  content,
39
40
  sequence: 0,
40
41
  final: true,
@@ -72,7 +73,7 @@ function createReplySender(ws, agentId, conversationId, messageId) {
72
73
  sequence: activity.sequence,
73
74
  }));
74
75
  },
75
- complete(text, sequence) {
76
+ complete(text, sequence, attachments, hermesAssistantMessageId) {
76
77
  ws.send(JSON.stringify({
77
78
  type: "agent.message",
78
79
  agentId,
@@ -81,6 +82,10 @@ function createReplySender(ws, agentId, conversationId, messageId) {
81
82
  content: text,
82
83
  sequence,
83
84
  final: true,
85
+ ...(attachments && attachments.length > 0 ? { attachments } : {}),
86
+ ...(hermesAssistantMessageId
87
+ ? { metadata: { hermes: { assistantMessageId: hermesAssistantMessageId } } }
88
+ : {}),
84
89
  }));
85
90
  },
86
91
  failed(failure) {
@@ -165,6 +170,7 @@ function userSafeHermesError(error) {
165
170
  }
166
171
  async function openAgentConnection(options) {
167
172
  const ws = new WebSocket(options.relayWsUrl);
173
+ const pendingAcks = new Map();
168
174
  let closedResolve = null;
169
175
  const closed = new Promise((resolve) => {
170
176
  closedResolve = resolve;
@@ -182,6 +188,15 @@ async function openAgentConnection(options) {
182
188
  ws.on("message", (raw) => {
183
189
  void (async () => {
184
190
  const envelope = JSON.parse(raw.toString());
191
+ if (envelope.type === "ack") {
192
+ const requestId = typeof envelope.requestId === "string" ? envelope.requestId : "";
193
+ const resolveAck = requestId ? pendingAcks.get(requestId) : undefined;
194
+ if (resolveAck) {
195
+ pendingAcks.delete(requestId);
196
+ resolveAck(envelope.ok === true);
197
+ }
198
+ return;
199
+ }
185
200
  if (envelope.type === "hermes.command") {
186
201
  await handleHermesCommandEnvelope(ws, envelope, options.agentId);
187
202
  return;
@@ -195,11 +210,14 @@ async function openAgentConnection(options) {
195
210
  rememberConversationId(conversationId);
196
211
  }
197
212
  const attachments = parseUserMessageAttachments(envelope.attachments);
213
+ const clientMessageId = typeof envelope.clientMessageId === "string" && envelope.clientMessageId.trim().length > 0
214
+ ? envelope.clientMessageId.trim()
215
+ : randomUUID();
198
216
  const replyMessageId = randomUUID();
199
217
  const reply = createReplySender(ws, agentId, conversationId, replyMessageId);
200
218
  if (options.onUserMessage) {
201
219
  try {
202
- await options.onUserMessage(content, { agentId, conversationId, messageId: replyMessageId, attachments }, reply);
220
+ await options.onUserMessage(content, { agentId, conversationId, clientMessageId, replyMessageId, attachments }, reply);
203
221
  }
204
222
  catch (error) {
205
223
  const { message, code } = userSafeHermesError(error);
@@ -220,6 +238,10 @@ async function openAgentConnection(options) {
220
238
  }, 30_000);
221
239
  ws.on("close", () => {
222
240
  clearInterval(heartbeat);
241
+ for (const [requestId, resolveAck] of pendingAcks.entries()) {
242
+ pendingAcks.delete(requestId);
243
+ resolveAck(false);
244
+ }
223
245
  closedResolve?.();
224
246
  });
225
247
  return {
@@ -230,11 +252,26 @@ async function openAgentConnection(options) {
230
252
  ws.close();
231
253
  },
232
254
  closed,
233
- deliverCronResult(content, meta) {
255
+ async deliverCronResult(content, meta) {
234
256
  if (ws.readyState !== WebSocket.OPEN) {
235
257
  throw new Error("Cleos relay connection is not open");
236
258
  }
237
- sendCronResult(ws, options.agentId, content, meta);
259
+ const messageId = randomUUID();
260
+ await new Promise((resolve, reject) => {
261
+ const timer = setTimeout(() => {
262
+ pendingAcks.delete(messageId);
263
+ reject(new Error("Cron delivery ack timed out"));
264
+ }, CRON_DELIVERY_TIMEOUT_MS);
265
+ pendingAcks.set(messageId, (ok) => {
266
+ clearTimeout(timer);
267
+ if (ok) {
268
+ resolve();
269
+ return;
270
+ }
271
+ reject(new Error("Cron delivery persist failed"));
272
+ });
273
+ sendCronResult(ws, options.agentId, messageId, content, meta);
274
+ });
238
275
  },
239
276
  };
240
277
  }
@@ -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
+ }
@@ -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;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"}
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;AAsID,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"}
@@ -83,6 +83,13 @@ function parseRunAtFromFileName(filePath) {
83
83
  const base = filePath.split("/").pop()?.replace(/\.md$/i, "") ?? "";
84
84
  if (!base)
85
85
  return undefined;
86
+ const underscoreMatch = /^(\d{4}-\d{2}-\d{2})_(\d{2})-(\d{2})-(\d{2})$/.exec(base);
87
+ if (underscoreMatch) {
88
+ const [, datePart, hour, minute, second] = underscoreMatch;
89
+ const parsed = Date.parse(`${datePart}T${hour}:${minute}:${second}`);
90
+ if (Number.isFinite(parsed))
91
+ return parsed;
92
+ }
86
93
  const asNumber = Number(base);
87
94
  if (Number.isFinite(asNumber) && asNumber > 0)
88
95
  return asNumber;
@@ -130,7 +137,7 @@ async function deliverPendingFiles(session, delivered, conversationId, options)
130
137
  continue;
131
138
  }
132
139
  try {
133
- session.deliverCronResult(content, {
140
+ await session.deliverCronResult(content, {
134
141
  conversationId,
135
142
  jobId,
136
143
  jobName,
@@ -146,7 +153,14 @@ async function deliverPendingFiles(session, delivered, conversationId, options)
146
153
  }));
147
154
  return;
148
155
  }
149
- throw error;
156
+ const message = error instanceof Error ? error.message : String(error);
157
+ options.onError?.(message);
158
+ console.error(JSON.stringify({
159
+ event: "cleos-bridge.cron-delivery-failed",
160
+ file: key,
161
+ message,
162
+ }));
163
+ continue;
150
164
  }
151
165
  delivered.add(key);
152
166
  writeDeliveredIndex(delivered);
@@ -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"}
@@ -0,0 +1,61 @@
1
+ import { DEFAULT_FILES_READ_MAX_BYTES, listHermesFiles, readHermesFileBytes, } from "./hermesFiles.js";
2
+ import { uploadFileToConvex } from "./convexRelay.js";
3
+ import { loadCredentials } from "./credentials.js";
4
+ function optionalNumber(args, key) {
5
+ const value = args[key];
6
+ if (typeof value === "number" && Number.isFinite(value))
7
+ return value;
8
+ if (typeof value === "string" && value.trim().length > 0) {
9
+ const parsed = Number.parseInt(value, 10);
10
+ if (Number.isFinite(parsed))
11
+ return parsed;
12
+ }
13
+ return undefined;
14
+ }
15
+ function optionalCategory(args) {
16
+ const value = args.category;
17
+ if (typeof value !== "string")
18
+ return "all";
19
+ const normalized = value.trim().toLowerCase();
20
+ if (normalized === "image" || normalized === "video" || normalized === "document") {
21
+ return normalized;
22
+ }
23
+ return "all";
24
+ }
25
+ export async function executeFilesList(args) {
26
+ const limit = optionalNumber(args, "limit");
27
+ const offset = optionalNumber(args, "offset");
28
+ const category = optionalCategory(args);
29
+ return listHermesFiles({ limit, offset, category });
30
+ }
31
+ export async function executeFilesRead(args) {
32
+ const path = typeof args.path === "string" ? args.path.trim() : "";
33
+ const maxBytes = optionalNumber(args, "maxBytes") ?? DEFAULT_FILES_READ_MAX_BYTES;
34
+ const { bytes, mimeType, fileName, size } = readHermesFileBytes(path);
35
+ if (size <= maxBytes) {
36
+ return {
37
+ fileName,
38
+ mimeType,
39
+ size,
40
+ contentBase64: bytes.toString("base64"),
41
+ };
42
+ }
43
+ const credentials = loadCredentials();
44
+ const agentId = credentials?.agentId;
45
+ if (!agentId) {
46
+ throw new Error("Large files require bridge credentials with agentId for Convex upload.");
47
+ }
48
+ const uploaded = await uploadFileToConvex({
49
+ agentId,
50
+ fileName,
51
+ mimeType,
52
+ contentBase64: bytes.toString("base64"),
53
+ });
54
+ return {
55
+ fileName,
56
+ mimeType,
57
+ size,
58
+ storageId: uploaded.storageId,
59
+ url: uploaded.url ?? undefined,
60
+ };
61
+ }
@@ -0,0 +1,60 @@
1
+ export declare const MAX_RELAY_UPLOAD_BYTES: number;
2
+ export declare const DEFAULT_FILES_READ_MAX_BYTES: number;
3
+ export type HermesFileCategory = "image" | "video" | "document" | "other";
4
+ export type HermesFileEntry = {
5
+ relativePath: string;
6
+ fileName: string;
7
+ size: number;
8
+ mtime: number;
9
+ mimeType: string;
10
+ category: HermesFileCategory;
11
+ };
12
+ export type HermesFileReadResult = {
13
+ fileName: string;
14
+ mimeType: string;
15
+ size: number;
16
+ contentBase64?: string;
17
+ storageId?: string;
18
+ url?: string;
19
+ };
20
+ export type MediaReference = {
21
+ kind: "local";
22
+ absolutePath: string;
23
+ raw: string;
24
+ } | {
25
+ kind: "url";
26
+ url: string;
27
+ raw: string;
28
+ };
29
+ export declare function resolveHermesHome(): string;
30
+ export declare function inferMimeType(fileName: string): string;
31
+ export declare function inferCategory(fileName: string): HermesFileCategory;
32
+ export declare function resolveSandboxedPath(inputPath: string): string;
33
+ export declare function listHermesFiles(options?: {
34
+ limit?: number;
35
+ offset?: number;
36
+ category?: HermesFileCategory | "all";
37
+ }): {
38
+ files: HermesFileEntry[];
39
+ };
40
+ export declare function readHermesFileBytes(inputPath: string): {
41
+ bytes: Buffer;
42
+ mimeType: string;
43
+ fileName: string;
44
+ size: number;
45
+ };
46
+ export declare function parseMediaReferences(text: string): MediaReference[];
47
+ export declare function stripMediaReferences(text: string, refs: MediaReference[]): string;
48
+ export declare function downloadUrlToBytes(url: string, timeoutMs?: number): Promise<{
49
+ bytes: Buffer;
50
+ mimeType: string;
51
+ fileName: string;
52
+ size: number;
53
+ }>;
54
+ export declare function resolveMediaReferenceBytes(ref: MediaReference): Promise<{
55
+ bytes: Buffer;
56
+ mimeType: string;
57
+ fileName: string;
58
+ size: number;
59
+ } | null>;
60
+ //# sourceMappingURL=hermesFiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermesFiles.d.ts","sourceRoot":"","sources":["../src/hermesFiles.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,QAAmB,CAAC;AACvD,eAAO,MAAM,4BAA4B,QAAa,CAAC;AAgCvD,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;AAE1E,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAiCtD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAMlE;AAQD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAe9D;AA8DD,wBAAgB,eAAe,CAAC,OAAO,GAAE;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,kBAAkB,GAAG,KAAK,CAAC;CAClC,GAAG;IAAE,KAAK,EAAE,eAAe,EAAE,CAAA;CAAE,CAepC;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAe1H;AAMD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CA6BnE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,MAAM,CAMjF;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBtJ;AAED,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CASzJ"}