@saleso.innovations/bridge 0.1.4 → 0.1.6

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/INTEGRATION.md CHANGED
@@ -53,6 +53,28 @@ export async function resumeCleosConnection() {
53
53
  - Relay routing (Railway) between iOS app and Hermes WebSocket
54
54
  - Message history in Convex
55
55
 
56
+ ## Cron job delivery
57
+
58
+ When a Hermes cron job completes and should appear in the Cleos **Jobs** tab (not chat), deliver the result over the existing agent WebSocket:
59
+
60
+ ```typescript
61
+ import { reconnectHermesAgent } from "@saleso.innovations/bridge";
62
+
63
+ const connection = await reconnectHermesAgent({ onUserMessage: handleCleosMessage });
64
+
65
+ // On cron completion — conversationId is the active Cleos conversation for this agent.
66
+ connection.deliverCronResult(finalText, {
67
+ conversationId: "<convex-conversation-id>",
68
+ jobId: job.id,
69
+ jobName: job.name,
70
+ runAt: Date.now(),
71
+ });
72
+ ```
73
+
74
+ Cleos routes messages with `metadata.cron` directly to the Jobs inbox. Hermes default cron wrappers (`Cronjob Response: ...`) are also detected automatically.
75
+
76
+ Store the Cleos `conversationId` when pairing (fetch via Convex `conversations:getActive` for the new `agentId`) so cron delivery has a valid target.
77
+
56
78
  ## What Hermes handles
57
79
 
58
80
  - Pairing UI (code entry)
package/dist/client.d.ts CHANGED
@@ -8,20 +8,35 @@ export type ConnectOptions = {
8
8
  capabilities?: string[];
9
9
  onUserMessage?: (content: string, meta: UserMessageMeta, reply: AgentReply) => Promise<void> | void;
10
10
  };
11
+ export type UserMessageAttachment = {
12
+ storageId: string;
13
+ mimeType: string;
14
+ fileName: string;
15
+ size?: number;
16
+ url?: string;
17
+ };
11
18
  export type UserMessageMeta = {
12
19
  agentId: string;
13
20
  conversationId: string;
14
21
  messageId: string;
22
+ attachments?: UserMessageAttachment[];
15
23
  };
16
24
  export type AgentReply = {
17
25
  delta: (text: string, sequence: number) => void;
18
26
  complete: (text: string, sequence: number) => void;
19
27
  };
28
+ export type CronDeliveryMeta = {
29
+ conversationId: string;
30
+ jobId: string;
31
+ jobName: string;
32
+ runAt?: number;
33
+ };
20
34
  export type ConnectResult = {
21
35
  agentId: string;
22
36
  agentToken: string;
23
37
  close: () => void;
24
38
  closed: Promise<void>;
39
+ deliverCronResult: (content: string, meta: CronDeliveryMeta) => void;
25
40
  };
26
41
  export declare function pairCleosAgent(options: Omit<ConnectOptions, "onUserMessage">): Promise<{
27
42
  agentId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAIhG,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrG,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB,CAAC;AA8HF,wBAAsB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CA0BD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAcxF;AAED,wBAAsB,oBAAoB,CAAC,OAAO,GAAE;IAClD,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAC5C,GAAG,OAAO,CAAC,aAAa,CAAC,CAc9B"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAIhG,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrG,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtE,CAAC;AAwLF,wBAAsB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CA0BD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAcxF;AAED,wBAAsB,oBAAoB,CAAC,OAAO,GAAE;IAClD,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAC5C,GAAG,OAAO,CAAC,aAAa,CAAC,CAc9B"}
package/dist/client.js CHANGED
@@ -3,6 +3,47 @@ import WebSocket from "ws";
3
3
  import { saveCredentials, loadCredentials } from "./credentials.js";
4
4
  import { convexSiteUrlFromEnv, resolvePairingCode } from "./resolve.js";
5
5
  import { normalizePairingCode } from "./normalizePairingCode.js";
6
+ function parseUserMessageAttachments(raw) {
7
+ if (!Array.isArray(raw))
8
+ return undefined;
9
+ const attachments = [];
10
+ for (const item of raw) {
11
+ if (!item || typeof item !== "object")
12
+ continue;
13
+ const record = item;
14
+ const storageId = typeof record.storageId === "string" ? record.storageId : "";
15
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : "";
16
+ const fileName = typeof record.fileName === "string" ? record.fileName : "";
17
+ if (!storageId || !mimeType || !fileName)
18
+ continue;
19
+ attachments.push({
20
+ storageId,
21
+ mimeType,
22
+ fileName,
23
+ size: typeof record.size === "number" ? record.size : undefined,
24
+ url: typeof record.url === "string" ? record.url : undefined,
25
+ });
26
+ }
27
+ return attachments.length > 0 ? attachments : undefined;
28
+ }
29
+ function sendCronResult(ws, agentId, content, meta) {
30
+ ws.send(JSON.stringify({
31
+ type: "agent.message",
32
+ agentId,
33
+ conversationId: meta.conversationId,
34
+ messageId: randomUUID(),
35
+ content,
36
+ sequence: 0,
37
+ final: true,
38
+ metadata: {
39
+ cron: {
40
+ jobId: meta.jobId,
41
+ jobName: meta.jobName,
42
+ runAt: meta.runAt,
43
+ },
44
+ },
45
+ }));
46
+ }
6
47
  function createReplySender(ws, agentId, conversationId, messageId) {
7
48
  return {
8
49
  delta(text, sequence) {
@@ -52,10 +93,11 @@ async function openAgentConnection(options) {
52
93
  const content = typeof envelope.content === "string" ? envelope.content : "";
53
94
  const agentId = typeof envelope.agentId === "string" ? envelope.agentId : options.agentId;
54
95
  const conversationId = typeof envelope.conversationId === "string" ? envelope.conversationId : "unknown";
55
- const messageId = typeof envelope.clientMessageId === "string" ? envelope.clientMessageId : randomUUID();
56
- const reply = createReplySender(ws, agentId, conversationId, messageId);
96
+ const attachments = parseUserMessageAttachments(envelope.attachments);
97
+ const replyMessageId = randomUUID();
98
+ const reply = createReplySender(ws, agentId, conversationId, replyMessageId);
57
99
  if (options.onUserMessage) {
58
- await options.onUserMessage(content, { agentId, conversationId, messageId }, reply);
100
+ await options.onUserMessage(content, { agentId, conversationId, messageId: replyMessageId, attachments }, reply);
59
101
  return;
60
102
  }
61
103
  const echo = `Echo: ${content}`;
@@ -80,6 +122,12 @@ async function openAgentConnection(options) {
80
122
  ws.close();
81
123
  },
82
124
  closed,
125
+ deliverCronResult(content, meta) {
126
+ if (ws.readyState !== WebSocket.OPEN) {
127
+ throw new Error("Cleos relay connection is not open");
128
+ }
129
+ sendCronResult(ws, options.agentId, content, meta);
130
+ },
83
131
  };
84
132
  }
85
133
  async function resolveRelayTargets(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"hermesForwarder.d.ts","sourceRoot":"","sources":["../src/hermesForwarder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG/D,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAkBF,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,sBAA2B,GAAG;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,CAUA;AAqCD,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAwEf;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,sBAA2B,IAC/D,SAAS,MAAM,EAAE,MAAM,eAAe,EAAE,OAAO,UAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CAMxF"}
1
+ {"version":3,"file":"hermesForwarder.d.ts","sourceRoot":"","sources":["../src/hermesForwarder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAyB,eAAe,EAAE,MAAM,aAAa,CAAC;AAGtF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAkBF,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,sBAA2B,GAAG;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,CAUA;AA2ED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAyEf;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,sBAA2B,IAC/D,SAAS,MAAM,EAAE,MAAM,eAAe,EAAE,OAAO,UAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CAMxF"}
@@ -64,6 +64,32 @@ function extractDeltaFromChunk(payload) {
64
64
  const content = delta.content;
65
65
  return typeof content === "string" ? content : null;
66
66
  }
67
+ function buildHermesUserContent(text, attachments) {
68
+ const trimmed = text.trim();
69
+ const imageParts = (attachments ?? [])
70
+ .filter((attachment) => attachment.mimeType.startsWith("image/") && attachment.url)
71
+ .map((attachment) => ({
72
+ type: "image_url",
73
+ image_url: { url: attachment.url },
74
+ }));
75
+ const fileNames = (attachments ?? [])
76
+ .filter((attachment) => !attachment.mimeType.startsWith("image/"))
77
+ .map((attachment) => attachment.fileName);
78
+ let prose = trimmed;
79
+ if (fileNames.length > 0) {
80
+ const fileNote = `[Attached files: ${fileNames.join(", ")}]`;
81
+ prose = prose.length === 0 ? fileNote : `${prose}\n\n${fileNote}`;
82
+ }
83
+ if (imageParts.length === 0) {
84
+ return prose.length === 0 ? "(attachment)" : prose;
85
+ }
86
+ const parts = [];
87
+ if (prose.length > 0) {
88
+ parts.push({ type: "text", text: prose });
89
+ }
90
+ parts.push(...imageParts);
91
+ return parts;
92
+ }
67
93
  export async function forwardToHermes(content, meta, reply, options = {}) {
68
94
  const { apiUrl, apiKey, model } = resolveHermesApiConfig(options);
69
95
  await ensureHermesReachable(apiUrl, apiKey);
@@ -73,10 +99,11 @@ export async function forwardToHermes(content, meta, reply, options = {}) {
73
99
  if (apiKey)
74
100
  headers.authorization = `Bearer ${apiKey}`;
75
101
  const conversationKey = options.conversationId ?? meta.conversationId;
102
+ const userContent = buildHermesUserContent(content, meta.attachments);
76
103
  const body = {
77
104
  model,
78
105
  stream: true,
79
- messages: [{ role: "user", content }],
106
+ messages: [{ role: "user", content: userContent }],
80
107
  user: conversationKey,
81
108
  };
82
109
  const response = await fetch(apiUrl, {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { connectHermesAgent, pairCleosAgent, reconnectHermesAgent } from "./client.js";
2
- export type { AgentReply, ConnectOptions, ConnectResult, UserMessageMeta } from "./client.js";
2
+ export type { AgentReply, ConnectOptions, ConnectResult, CronDeliveryMeta, UserMessageMeta } from "./client.js";
3
3
  export { loadCredentials, saveCredentials, credentialsPathForDisplay } from "./credentials.js";
4
4
  export type { SavedAgentCredentials } from "./credentials.js";
5
5
  export { resolvePairingCode, convexSiteUrlFromEnv } from "./resolve.js";
@@ -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,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9F,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,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,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,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChH,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,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saleso.innovations/bridge",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Connect your Hermes agent to the Cleos iOS app via pairing code.",
5
5
  "type": "module",
6
6
  "license": "MIT",