@saleso.innovations/bridge 0.1.40 → 0.1.41

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 CHANGED
@@ -48,6 +48,11 @@ export type AgentTurnActivity = {
48
48
  name: string;
49
49
  label?: string;
50
50
  }>;
51
+ skillsLearned?: Array<{
52
+ name: string;
53
+ label?: string;
54
+ description?: string;
55
+ }>;
51
56
  memories?: Array<{
52
57
  name: string;
53
58
  label?: 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
@@ -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;AAkBtF,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,CAkF9B;AA2BD,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;AAmBtF,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,CAyF9B;AA2BD,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,sBAA2B,IAC/D,SAAS,MAAM,EAAE,MAAM,eAAe,EAAE,OAAO,UAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CAMxF"}
@@ -4,6 +4,7 @@ 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";
9
10
  function readHermesApiKeyFromEnvFile() {
@@ -160,6 +161,8 @@ export async function forwardToHermes(content, meta, reply, options = {}) {
160
161
  messages: [{ role: "user", content: userContent }],
161
162
  user: sessionId,
162
163
  };
164
+ const turnStartedAt = Date.now();
165
+ const skillsBefore = snapshotSkills();
163
166
  const response = await fetch(apiUrl, {
164
167
  method: "POST",
165
168
  headers,
@@ -204,6 +207,10 @@ export async function forwardToHermes(content, meta, reply, options = {}) {
204
207
  const { assistantMessageId } = getLatestTurnMessageIds(resolvedSessionId);
205
208
  const replyModel = resolveReplyModel(modelFromHeader, resolvedSessionId, model);
206
209
  const turnActivity = buildTurnActivity(accumulator, replyModel);
210
+ const skillsLearned = diffSkillsLearned(skillsBefore, snapshotSkills(), { turnStartedAt });
211
+ if (skillsLearned.length > 0) {
212
+ turnActivity.skillsLearned = skillsLearned;
213
+ }
207
214
  reply.complete(displayText, counters.textSequence, attachments.length > 0 ? attachments : undefined, assistantMessageId !== undefined ? String(assistantMessageId) : undefined, turnActivity);
208
215
  await linkTurnToConvex(meta, resolvedSessionId);
209
216
  return { sessionId: resolvedSessionId };
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skillLearnedDetector.test.d.ts.map
@@ -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
+ });
@@ -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
  }>;
@@ -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;AAuOF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC,CAOhF"}
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"}
@@ -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.40",
3
+ "version": "0.1.41",
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"
40
40
  },
41
41
  "dependencies": {
42
42
  "better-sqlite3": "^11.10.0",