@saleso.innovations/bridge 0.1.40 → 0.1.42
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/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/hermesForwarder.d.ts.map +1 -1
- package/dist/hermesForwarder.js +29 -4
- package/dist/hermesSessionDb.d.ts.map +1 -1
- package/dist/hermesSessionDb.js +4 -0
- package/dist/hermesStructuredContent.d.ts +8 -0
- package/dist/hermesStructuredContent.d.ts.map +1 -0
- package/dist/hermesStructuredContent.js +197 -0
- package/dist/hermesStructuredContent.test.d.ts +2 -0
- package/dist/hermesStructuredContent.test.d.ts.map +1 -0
- package/dist/hermesStructuredContent.test.js +15 -0
- package/dist/skillLearnedDetector.d.ts +20 -0
- package/dist/skillLearnedDetector.d.ts.map +1 -0
- package/dist/skillLearnedDetector.js +53 -0
- package/dist/skillLearnedDetector.test.d.ts +2 -0
- package/dist/skillLearnedDetector.test.d.ts.map +1 -0
- package/dist/skillLearnedDetector.test.js +81 -0
- package/dist/skillsList.d.ts +7 -0
- package/dist/skillsList.d.ts.map +1 -1
- package/dist/skillsList.js +7 -0
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
package/dist/client.d.ts.map
CHANGED
|
@@ -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
|
@@ -12,6 +12,7 @@ export function cronDeliveryMessageId(sourceKey) {
|
|
|
12
12
|
function hasTurnActivity(activity) {
|
|
13
13
|
return ((activity.tools?.length ?? 0) > 0 ||
|
|
14
14
|
(activity.skills?.length ?? 0) > 0 ||
|
|
15
|
+
(activity.skillsLearned?.length ?? 0) > 0 ||
|
|
15
16
|
(activity.memories?.length ?? 0) > 0 ||
|
|
16
17
|
(activity.mcps?.length ?? 0) > 0 ||
|
|
17
18
|
typeof activity.model === "string");
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAuBtF,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;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB,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;AAgID,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,mBAAmB,CAAC,CA0G9B;AA2BD,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,sBAA2B,IAC/D,SAAS,MAAM,EAAE,MAAM,eAAe,EAAE,OAAO,UAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CAMxF"}
|
package/dist/hermesForwarder.js
CHANGED
|
@@ -4,8 +4,10 @@ import { join } from "node:path";
|
|
|
4
4
|
import { linkHermesMessageIds, uploadFileToConvex } from "./convexRelay.js";
|
|
5
5
|
import { DEFAULT_HERMES_API_URL } from "./constants.js";
|
|
6
6
|
import { getLatestTurnMessageIds, getSessionUsage, hermesStateDbExists } from "./hermesSessionDb.js";
|
|
7
|
+
import { diffSkillsLearned, snapshotSkills } from "./skillLearnedDetector.js";
|
|
7
8
|
import { buildTurnActivity, createTurnActivityAccumulator, parseSseDataLines, processSseEventBlock, } from "./turnActivity.js";
|
|
8
9
|
import { parseMediaReferences, resolveMediaReferenceBytes, stripMediaReferences, } from "./hermesFiles.js";
|
|
10
|
+
import { looksLikeHermesStructuredStream, unwrapHermesStructuredContent, } from "./hermesStructuredContent.js";
|
|
9
11
|
function readHermesApiKeyFromEnvFile() {
|
|
10
12
|
const envPath = join(homedir(), ".hermes", ".env");
|
|
11
13
|
try {
|
|
@@ -160,6 +162,8 @@ export async function forwardToHermes(content, meta, reply, options = {}) {
|
|
|
160
162
|
messages: [{ role: "user", content: userContent }],
|
|
161
163
|
user: sessionId,
|
|
162
164
|
};
|
|
165
|
+
const turnStartedAt = Date.now();
|
|
166
|
+
const skillsBefore = snapshotSkills();
|
|
163
167
|
const response = await fetch(apiUrl, {
|
|
164
168
|
method: "POST",
|
|
165
169
|
headers,
|
|
@@ -180,6 +184,17 @@ export async function forwardToHermes(content, meta, reply, options = {}) {
|
|
|
180
184
|
const counters = { textSequence: 0, activitySequence: 0 };
|
|
181
185
|
const accumulator = createTurnActivityAccumulator();
|
|
182
186
|
let fullText = "";
|
|
187
|
+
let suppressStreamDeltas = false;
|
|
188
|
+
const streamingReply = {
|
|
189
|
+
delta(text, sequence) {
|
|
190
|
+
if (!suppressStreamDeltas) {
|
|
191
|
+
reply.delta(text, sequence);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
activity: (activity) => reply.activity(activity),
|
|
195
|
+
complete: (...args) => reply.complete(...args),
|
|
196
|
+
failed: (failure) => reply.failed(failure),
|
|
197
|
+
};
|
|
183
198
|
while (true) {
|
|
184
199
|
const { done, value } = await reader.read();
|
|
185
200
|
if (done)
|
|
@@ -188,22 +203,32 @@ export async function forwardToHermes(content, meta, reply, options = {}) {
|
|
|
188
203
|
const { events, rest } = parseSseDataLines(buffer);
|
|
189
204
|
buffer = rest;
|
|
190
205
|
for (const eventBlock of events) {
|
|
191
|
-
processSseEventBlock(eventBlock,
|
|
206
|
+
processSseEventBlock(eventBlock, streamingReply, counters, accumulator, (delta) => {
|
|
192
207
|
fullText += delta;
|
|
208
|
+
if (!suppressStreamDeltas && looksLikeHermesStructuredStream(fullText)) {
|
|
209
|
+
suppressStreamDeltas = true;
|
|
210
|
+
}
|
|
193
211
|
});
|
|
194
212
|
}
|
|
195
213
|
}
|
|
196
214
|
if (!fullText.trim()) {
|
|
197
215
|
fullText = "(Hermes returned an empty response)";
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
if (!suppressStreamDeltas) {
|
|
217
|
+
reply.delta(fullText, counters.textSequence);
|
|
218
|
+
counters.textSequence += 1;
|
|
219
|
+
}
|
|
200
220
|
}
|
|
201
221
|
const mediaRefs = parseMediaReferences(fullText);
|
|
202
222
|
const attachments = mediaRefs.length > 0 ? await uploadMediaAttachments(meta.agentId, mediaRefs) : [];
|
|
203
|
-
const
|
|
223
|
+
const rawDisplayText = attachments.length > 0 ? stripMediaReferences(fullText, mediaRefs) : fullText.trim() || fullText;
|
|
224
|
+
const displayText = unwrapHermesStructuredContent(rawDisplayText);
|
|
204
225
|
const { assistantMessageId } = getLatestTurnMessageIds(resolvedSessionId);
|
|
205
226
|
const replyModel = resolveReplyModel(modelFromHeader, resolvedSessionId, model);
|
|
206
227
|
const turnActivity = buildTurnActivity(accumulator, replyModel);
|
|
228
|
+
const skillsLearned = diffSkillsLearned(skillsBefore, snapshotSkills(), { turnStartedAt });
|
|
229
|
+
if (skillsLearned.length > 0) {
|
|
230
|
+
turnActivity.skillsLearned = skillsLearned;
|
|
231
|
+
}
|
|
207
232
|
reply.complete(displayText, counters.textSequence, attachments.length > 0 ? attachments : undefined, assistantMessageId !== undefined ? String(assistantMessageId) : undefined, turnActivity);
|
|
208
233
|
await linkTurnToConvex(meta, resolvedSessionId);
|
|
209
234
|
return { sessionId: resolvedSessionId };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermesSessionDb.d.ts","sourceRoot":"","sources":["../src/hermesSessionDb.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hermesSessionDb.d.ts","sourceRoot":"","sources":["../src/hermesSessionDb.ts"],"names":[],"mappings":"AAMA,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;AA4IF,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
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import Database from "better-sqlite3";
|
|
5
|
+
import { unwrapHermesStructuredContent } from "./hermesStructuredContent.js";
|
|
5
6
|
function hermesStateDbPath() {
|
|
6
7
|
const home = process.env.HERMES_HOME?.trim() || join(homedir(), ".hermes");
|
|
7
8
|
return join(home, "state.db");
|
|
@@ -24,6 +25,9 @@ function decodeMessageContent(raw) {
|
|
|
24
25
|
.map((part) => part.text)
|
|
25
26
|
.join("\n");
|
|
26
27
|
}
|
|
28
|
+
if (parsed && typeof parsed === "object") {
|
|
29
|
+
return unwrapHermesStructuredContent(raw);
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
catch {
|
|
29
33
|
// Plain text content.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep in sync with @repo/backend-types/src/hermesStructuredContent.ts
|
|
3
|
+
* (duplicated here so the published bridge package stays self-contained).
|
|
4
|
+
*/
|
|
5
|
+
export declare function isHermesStructuredContent(content: string): boolean;
|
|
6
|
+
export declare function unwrapHermesStructuredContent(content: string): string;
|
|
7
|
+
export declare function looksLikeHermesStructuredStream(text: string): boolean;
|
|
8
|
+
//# sourceMappingURL=hermesStructuredContent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermesStructuredContent.d.ts","sourceRoot":"","sources":["../src/hermesStructuredContent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA0CH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AA6JD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIrE"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep in sync with @repo/backend-types/src/hermesStructuredContent.ts
|
|
3
|
+
* (duplicated here so the published bridge package stays self-contained).
|
|
4
|
+
*/
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function asString(value) {
|
|
9
|
+
return typeof value === "string" ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
function stringArray(value) {
|
|
12
|
+
if (!Array.isArray(value))
|
|
13
|
+
return [];
|
|
14
|
+
return value.filter((entry) => typeof entry === "string");
|
|
15
|
+
}
|
|
16
|
+
function stringMatrix(value) {
|
|
17
|
+
if (!Array.isArray(value))
|
|
18
|
+
return [];
|
|
19
|
+
return value
|
|
20
|
+
.filter((row) => Array.isArray(row))
|
|
21
|
+
.map((row) => row.filter((cell) => typeof cell === "string"));
|
|
22
|
+
}
|
|
23
|
+
function parseHermesStructuredRoot(text) {
|
|
24
|
+
const trimmed = text.trim();
|
|
25
|
+
if (!trimmed.startsWith("{"))
|
|
26
|
+
return null;
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(trimmed);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!isRecord(parsed) || typeof parsed.component !== "string") {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
export function isHermesStructuredContent(content) {
|
|
40
|
+
return parseHermesStructuredRoot(content) !== null;
|
|
41
|
+
}
|
|
42
|
+
function hermesChildren(node) {
|
|
43
|
+
const children = node.children;
|
|
44
|
+
if (!Array.isArray(children))
|
|
45
|
+
return [];
|
|
46
|
+
return children.filter(isRecord);
|
|
47
|
+
}
|
|
48
|
+
function joinBlocks(blocks) {
|
|
49
|
+
return blocks
|
|
50
|
+
.map((block) => block.trim())
|
|
51
|
+
.filter((block) => block.length > 0)
|
|
52
|
+
.join("\n\n");
|
|
53
|
+
}
|
|
54
|
+
function markdownTable(headers, rows) {
|
|
55
|
+
if (headers.length === 0)
|
|
56
|
+
return "";
|
|
57
|
+
const separator = headers.map(() => "---");
|
|
58
|
+
const body = rows.map((row) => {
|
|
59
|
+
const cells = headers.map((_, index) => row[index] ?? "");
|
|
60
|
+
return `| ${cells.join(" | ")} |`;
|
|
61
|
+
});
|
|
62
|
+
return joinBlocks([
|
|
63
|
+
`| ${headers.join(" | ")} |`,
|
|
64
|
+
`| ${separator.join(" | ")} |`,
|
|
65
|
+
...body,
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
function nodeToPlainText(node) {
|
|
69
|
+
const component = node.component;
|
|
70
|
+
if (typeof component !== "string")
|
|
71
|
+
return "";
|
|
72
|
+
switch (component) {
|
|
73
|
+
case "root":
|
|
74
|
+
return joinBlocks(hermesChildren(node).map(nodeToPlainText));
|
|
75
|
+
case "text":
|
|
76
|
+
return asString(node.text) ?? "";
|
|
77
|
+
case "heading": {
|
|
78
|
+
const text = asString(node.text);
|
|
79
|
+
if (!text)
|
|
80
|
+
return "";
|
|
81
|
+
const level = node.level === 3 ? 3 : 2;
|
|
82
|
+
return `${"#".repeat(level)} ${text}`;
|
|
83
|
+
}
|
|
84
|
+
case "table": {
|
|
85
|
+
const headers = stringArray(node.headers).length > 0
|
|
86
|
+
? stringArray(node.headers)
|
|
87
|
+
: stringArray(node.columns);
|
|
88
|
+
const rows = stringMatrix(node.rows);
|
|
89
|
+
if (headers.length === 0)
|
|
90
|
+
return "";
|
|
91
|
+
const caption = asString(node.caption);
|
|
92
|
+
const table = markdownTable(headers, rows);
|
|
93
|
+
return caption ? joinBlocks([caption, table]) : table;
|
|
94
|
+
}
|
|
95
|
+
case "list": {
|
|
96
|
+
const items = stringArray(node.items);
|
|
97
|
+
if (items.length === 0)
|
|
98
|
+
return "";
|
|
99
|
+
const style = node.style === "numbered" ? "numbered" : "bullet";
|
|
100
|
+
return items
|
|
101
|
+
.map((item, index) => (style === "numbered" ? `${index + 1}. ${item}` : `- ${item}`))
|
|
102
|
+
.join("\n");
|
|
103
|
+
}
|
|
104
|
+
case "code": {
|
|
105
|
+
const content = asString(node.content) ?? "";
|
|
106
|
+
const language = asString(node.language) ?? "";
|
|
107
|
+
return `\`\`\`${language}\n${content}\n\`\`\``;
|
|
108
|
+
}
|
|
109
|
+
case "link": {
|
|
110
|
+
const label = asString(node.label);
|
|
111
|
+
const url = asString(node.url);
|
|
112
|
+
if (label && url)
|
|
113
|
+
return `${label}: ${url}`;
|
|
114
|
+
return label ?? url ?? "";
|
|
115
|
+
}
|
|
116
|
+
case "key_value":
|
|
117
|
+
case "stat_cards": {
|
|
118
|
+
const items = Array.isArray(node.items) ? node.items.filter(isRecord) : [];
|
|
119
|
+
return joinBlocks(items.map((item) => {
|
|
120
|
+
const label = asString(item.label);
|
|
121
|
+
const value = asString(item.value);
|
|
122
|
+
if (!label || !value)
|
|
123
|
+
return "";
|
|
124
|
+
const hint = asString(item.hint);
|
|
125
|
+
return hint ? `${label}: ${value} (${hint})` : `${label}: ${value}`;
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
case "callout": {
|
|
129
|
+
const title = asString(node.title);
|
|
130
|
+
const body = asString(node.body) ?? "";
|
|
131
|
+
return joinBlocks([title, body].filter((part) => Boolean(part)));
|
|
132
|
+
}
|
|
133
|
+
case "choice": {
|
|
134
|
+
const prompt = asString(node.prompt) ?? "";
|
|
135
|
+
const options = Array.isArray(node.options) ? node.options.filter(isRecord) : [];
|
|
136
|
+
const optionLines = options
|
|
137
|
+
.map((option) => asString(option.label) ?? asString(option.id))
|
|
138
|
+
.filter((label) => Boolean(label))
|
|
139
|
+
.map((label) => `- ${label}`);
|
|
140
|
+
return joinBlocks([prompt, optionLines.join("\n")]);
|
|
141
|
+
}
|
|
142
|
+
case "yes_no":
|
|
143
|
+
return asString(node.prompt) ?? "";
|
|
144
|
+
case "suggestions": {
|
|
145
|
+
const items = Array.isArray(node.items) ? node.items.filter(isRecord) : [];
|
|
146
|
+
return items
|
|
147
|
+
.map((item) => asString(item.label) ?? asString(item.id))
|
|
148
|
+
.filter((label) => Boolean(label))
|
|
149
|
+
.map((label) => `- ${label}`)
|
|
150
|
+
.join("\n");
|
|
151
|
+
}
|
|
152
|
+
case "button_row": {
|
|
153
|
+
const buttons = Array.isArray(node.buttons) ? node.buttons.filter(isRecord) : [];
|
|
154
|
+
return buttons
|
|
155
|
+
.map((button) => asString(button.label) ?? asString(button.id))
|
|
156
|
+
.filter((label) => Boolean(label))
|
|
157
|
+
.join(" · ");
|
|
158
|
+
}
|
|
159
|
+
case "chart": {
|
|
160
|
+
const title = asString(node.title);
|
|
161
|
+
const labels = stringArray(node.labels);
|
|
162
|
+
const values = Array.isArray(node.values)
|
|
163
|
+
? node.values.map((value) => (typeof value === "number" ? String(value) : String(value ?? "")))
|
|
164
|
+
: [];
|
|
165
|
+
const series = labels
|
|
166
|
+
.map((label, index) => {
|
|
167
|
+
const value = values[index];
|
|
168
|
+
return value ? `${label}: ${value}` : label;
|
|
169
|
+
})
|
|
170
|
+
.join("\n");
|
|
171
|
+
return joinBlocks([title, series].filter((part) => Boolean(part)));
|
|
172
|
+
}
|
|
173
|
+
default: {
|
|
174
|
+
const text = asString(node.text) ?? asString(node.body) ?? asString(node.content);
|
|
175
|
+
if (text)
|
|
176
|
+
return text;
|
|
177
|
+
const children = hermesChildren(node);
|
|
178
|
+
if (children.length > 0) {
|
|
179
|
+
return joinBlocks(children.map(nodeToPlainText));
|
|
180
|
+
}
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export function unwrapHermesStructuredContent(content) {
|
|
186
|
+
const root = parseHermesStructuredRoot(content);
|
|
187
|
+
if (!root)
|
|
188
|
+
return content;
|
|
189
|
+
const plain = nodeToPlainText(root).trim();
|
|
190
|
+
return plain.length > 0 ? plain : content;
|
|
191
|
+
}
|
|
192
|
+
export function looksLikeHermesStructuredStream(text) {
|
|
193
|
+
const trimmed = text.trimStart();
|
|
194
|
+
if (!trimmed.startsWith("{"))
|
|
195
|
+
return false;
|
|
196
|
+
return /^\{\s*"component"\s*:/.test(trimmed);
|
|
197
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hermesStructuredContent.test.d.ts","sourceRoot":"","sources":["../src/hermesStructuredContent.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { isHermesStructuredContent, looksLikeHermesStructuredStream, unwrapHermesStructuredContent, } from "./hermesStructuredContent.js";
|
|
4
|
+
test("unwrapHermesStructuredContent extracts simple text block", () => {
|
|
5
|
+
const input = JSON.stringify({
|
|
6
|
+
component: "root",
|
|
7
|
+
children: [{ component: "text", text: "Det går fint! Hvordan står det til med deg?" }],
|
|
8
|
+
});
|
|
9
|
+
assert.equal(isHermesStructuredContent(input), true);
|
|
10
|
+
assert.equal(unwrapHermesStructuredContent(input), "Det går fint! Hvordan står det til med deg?");
|
|
11
|
+
});
|
|
12
|
+
test("looksLikeHermesStructuredStream detects early JSON envelope", () => {
|
|
13
|
+
assert.equal(looksLikeHermesStructuredStream('{"component":"root"'), true);
|
|
14
|
+
assert.equal(looksLikeHermesStructuredStream("Hello"), false);
|
|
15
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentTurnActivity } from "./client.js";
|
|
2
|
+
import { type HermesSkillEntry } from "./skillsList.js";
|
|
3
|
+
export type SkillSnapshotEntry = HermesSkillEntry & {
|
|
4
|
+
mtimeMs?: number;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Captures the set of skills currently on disk, keyed by relative path, with each
|
|
8
|
+
* file's mtime so a later diff can ignore writes that predate the turn.
|
|
9
|
+
*/
|
|
10
|
+
export declare function snapshotSkills(): Map<string, SkillSnapshotEntry>;
|
|
11
|
+
/**
|
|
12
|
+
* Returns skills present in `after` but not in `before` — i.e. newly written during the
|
|
13
|
+
* turn. When `turnStartedAt` is provided, entries whose file mtime predates the turn
|
|
14
|
+
* start (beyond a small grace window) are excluded to guard against concurrent
|
|
15
|
+
* background writes.
|
|
16
|
+
*/
|
|
17
|
+
export declare function diffSkillsLearned(before: Map<string, SkillSnapshotEntry>, after: Map<string, SkillSnapshotEntry>, options?: {
|
|
18
|
+
turnStartedAt?: number;
|
|
19
|
+
}): NonNullable<AgentTurnActivity["skillsLearned"]>;
|
|
20
|
+
//# sourceMappingURL=skillLearnedDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillLearnedDetector.d.ts","sourceRoot":"","sources":["../src/skillLearnedDetector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,cAAc,IAAI,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAchE;AAKD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACvC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACtC,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,WAAW,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC,CAuBjD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveHermesHome } from "./hermesFiles.js";
|
|
4
|
+
import { listSkillsFromFilesystem } from "./skillsList.js";
|
|
5
|
+
/**
|
|
6
|
+
* Captures the set of skills currently on disk, keyed by relative path, with each
|
|
7
|
+
* file's mtime so a later diff can ignore writes that predate the turn.
|
|
8
|
+
*/
|
|
9
|
+
export function snapshotSkills() {
|
|
10
|
+
const home = resolveHermesHome();
|
|
11
|
+
const snapshot = new Map();
|
|
12
|
+
for (const skill of listSkillsFromFilesystem().skills) {
|
|
13
|
+
if (!skill.relativePath)
|
|
14
|
+
continue;
|
|
15
|
+
let mtimeMs;
|
|
16
|
+
try {
|
|
17
|
+
mtimeMs = statSync(join(home, skill.relativePath)).mtimeMs;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// File may be unreadable; record without mtime.
|
|
21
|
+
}
|
|
22
|
+
snapshot.set(skill.relativePath, { ...skill, mtimeMs });
|
|
23
|
+
}
|
|
24
|
+
return snapshot;
|
|
25
|
+
}
|
|
26
|
+
/** Tolerance for clock skew between the turn start and a freshly written skill file. */
|
|
27
|
+
const MTIME_GRACE_MS = 5_000;
|
|
28
|
+
/**
|
|
29
|
+
* Returns skills present in `after` but not in `before` — i.e. newly written during the
|
|
30
|
+
* turn. When `turnStartedAt` is provided, entries whose file mtime predates the turn
|
|
31
|
+
* start (beyond a small grace window) are excluded to guard against concurrent
|
|
32
|
+
* background writes.
|
|
33
|
+
*/
|
|
34
|
+
export function diffSkillsLearned(before, after, options = {}) {
|
|
35
|
+
const learned = [];
|
|
36
|
+
const threshold = typeof options.turnStartedAt === "number" ? options.turnStartedAt - MTIME_GRACE_MS : undefined;
|
|
37
|
+
for (const [relativePath, entry] of after) {
|
|
38
|
+
if (before.has(relativePath))
|
|
39
|
+
continue;
|
|
40
|
+
if (threshold !== undefined &&
|
|
41
|
+
typeof entry.mtimeMs === "number" &&
|
|
42
|
+
entry.mtimeMs < threshold) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
learned.push({
|
|
46
|
+
name: entry.name,
|
|
47
|
+
...(entry.category ? { label: entry.category } : {}),
|
|
48
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
learned.sort((left, right) => left.name.localeCompare(right.name, undefined, { sensitivity: "base" }));
|
|
52
|
+
return learned;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillLearnedDetector.test.d.ts","sourceRoot":"","sources":["../src/skillLearnedDetector.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { diffSkillsLearned } from "./skillLearnedDetector.js";
|
|
4
|
+
function snapshot(entries) {
|
|
5
|
+
const map = new Map();
|
|
6
|
+
for (const entry of entries) {
|
|
7
|
+
if (entry.relativePath)
|
|
8
|
+
map.set(entry.relativePath, entry);
|
|
9
|
+
}
|
|
10
|
+
return map;
|
|
11
|
+
}
|
|
12
|
+
test("returns empty when nothing changed", () => {
|
|
13
|
+
const before = snapshot([
|
|
14
|
+
{ name: "alpha", description: "", relativePath: "skills/alpha/SKILL.md" },
|
|
15
|
+
]);
|
|
16
|
+
const after = snapshot([
|
|
17
|
+
{ name: "alpha", description: "", relativePath: "skills/alpha/SKILL.md" },
|
|
18
|
+
]);
|
|
19
|
+
assert.deepEqual(diffSkillsLearned(before, after), []);
|
|
20
|
+
});
|
|
21
|
+
test("detects a single new skill with label and description", () => {
|
|
22
|
+
const before = snapshot([]);
|
|
23
|
+
const after = snapshot([
|
|
24
|
+
{
|
|
25
|
+
name: "crypto-technical-analysis",
|
|
26
|
+
description: "Analyze crypto charts",
|
|
27
|
+
category: "trading",
|
|
28
|
+
relativePath: "skills/trading/crypto-technical-analysis/SKILL.md",
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
assert.deepEqual(diffSkillsLearned(before, after), [
|
|
32
|
+
{
|
|
33
|
+
name: "crypto-technical-analysis",
|
|
34
|
+
label: "trading",
|
|
35
|
+
description: "Analyze crypto charts",
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
test("detects multiple new skills sorted by name", () => {
|
|
40
|
+
const before = snapshot([
|
|
41
|
+
{ name: "alpha", description: "", relativePath: "skills/alpha/SKILL.md" },
|
|
42
|
+
]);
|
|
43
|
+
const after = snapshot([
|
|
44
|
+
{ name: "alpha", description: "", relativePath: "skills/alpha/SKILL.md" },
|
|
45
|
+
{ name: "zeta", description: "", relativePath: "skills/zeta/SKILL.md" },
|
|
46
|
+
{ name: "beta", description: "", relativePath: "skills/beta/SKILL.md" },
|
|
47
|
+
]);
|
|
48
|
+
assert.deepEqual(diffSkillsLearned(before, after).map((skill) => skill.name), ["beta", "zeta"]);
|
|
49
|
+
});
|
|
50
|
+
test("excludes new entries whose mtime predates the turn start", () => {
|
|
51
|
+
const turnStartedAt = 1_000_000;
|
|
52
|
+
const before = snapshot([]);
|
|
53
|
+
const after = snapshot([
|
|
54
|
+
{
|
|
55
|
+
name: "stale",
|
|
56
|
+
description: "",
|
|
57
|
+
relativePath: "skills/stale/SKILL.md",
|
|
58
|
+
mtimeMs: turnStartedAt - 60_000,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "fresh",
|
|
62
|
+
description: "",
|
|
63
|
+
relativePath: "skills/fresh/SKILL.md",
|
|
64
|
+
mtimeMs: turnStartedAt + 1_000,
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
assert.deepEqual(diffSkillsLearned(before, after, { turnStartedAt }).map((skill) => skill.name), ["fresh"]);
|
|
68
|
+
});
|
|
69
|
+
test("keeps new entries within the mtime grace window", () => {
|
|
70
|
+
const turnStartedAt = 1_000_000;
|
|
71
|
+
const before = snapshot([]);
|
|
72
|
+
const after = snapshot([
|
|
73
|
+
{
|
|
74
|
+
name: "borderline",
|
|
75
|
+
description: "",
|
|
76
|
+
relativePath: "skills/borderline/SKILL.md",
|
|
77
|
+
mtimeMs: turnStartedAt - 2_000,
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
assert.deepEqual(diffSkillsLearned(before, after, { turnStartedAt }).map((skill) => skill.name), ["borderline"]);
|
|
81
|
+
});
|
package/dist/skillsList.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ export type HermesSkillEntry = {
|
|
|
4
4
|
category?: string;
|
|
5
5
|
relativePath?: string;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Synchronously reads the Hermes skills directory from disk. Used for fast turn-boundary
|
|
9
|
+
* snapshots without shelling out to the `hermes` CLI.
|
|
10
|
+
*/
|
|
11
|
+
export declare function listSkillsFromFilesystem(): {
|
|
12
|
+
skills: HermesSkillEntry[];
|
|
13
|
+
};
|
|
7
14
|
export declare function listHermesSkills(): Promise<{
|
|
8
15
|
skills: HermesSkillEntry[];
|
|
9
16
|
}>;
|
package/dist/skillsList.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AA6GF;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAEzE;AA4HD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC,CAOhF"}
|
package/dist/skillsList.js
CHANGED
|
@@ -105,6 +105,13 @@ function toHermesRelativeSkillPath(skillsRootDir, skillFilePath) {
|
|
|
105
105
|
const fromSkillsDir = relative(skillsRootDir, skillFilePath).replace(/\\/g, "/");
|
|
106
106
|
return `${SKILLS_DIR}/${fromSkillsDir}`;
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Synchronously reads the Hermes skills directory from disk. Used for fast turn-boundary
|
|
110
|
+
* snapshots without shelling out to the `hermes` CLI.
|
|
111
|
+
*/
|
|
112
|
+
export function listSkillsFromFilesystem() {
|
|
113
|
+
return readSkillsFromDirectory();
|
|
114
|
+
}
|
|
108
115
|
function readSkillsFromDirectory() {
|
|
109
116
|
const root = skillsRoot();
|
|
110
117
|
if (!existsSync(root)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saleso.innovations/bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
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/hermesSessionDb.test.ts src/renameHermesSession.test.ts src/shellSession.test.ts src/bridgeVersion.test.ts src/turnActivity.test.ts"
|
|
39
|
+
"test": "node --import tsx --test src/hermesFiles.test.ts src/hermesSessionDb.test.ts src/renameHermesSession.test.ts src/shellSession.test.ts src/bridgeVersion.test.ts src/turnActivity.test.ts src/skillLearnedDetector.test.ts src/hermesStructuredContent.test.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"better-sqlite3": "^11.10.0",
|