@hermespilot/link 0.7.1 → 0.7.3

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.
@@ -1316,10 +1316,10 @@ async function applyOwner(filePath, metadata) {
1316
1316
  }
1317
1317
  try {
1318
1318
  await chown(filePath, metadata.uid, metadata.gid);
1319
- } catch (error) {
1319
+ } catch {
1320
1320
  const current = await stat(filePath);
1321
- if (current.uid !== metadata.uid || current.gid !== metadata.gid) {
1322
- throw error;
1321
+ if (current.uid === metadata.uid && current.gid === metadata.gid) {
1322
+ return;
1323
1323
  }
1324
1324
  }
1325
1325
  }
@@ -5621,7 +5621,7 @@ function isConversationMissingError(error) {
5621
5621
  }
5622
5622
 
5623
5623
  // src/constants.ts
5624
- var LINK_VERSION = "0.7.1";
5624
+ var LINK_VERSION = "0.7.3";
5625
5625
  var LINK_COMMAND = "hermeslink";
5626
5626
  var LINK_DEFAULT_PORT = 52379;
5627
5627
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -13208,6 +13208,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
13208
13208
  reprojected_count: 0,
13209
13209
  skipped_existing: 0,
13210
13210
  skipped_hidden: 0,
13211
+ skipped_empty_transcript: 0,
13211
13212
  skipped_deleted: 0,
13212
13213
  skipped_over_limit: 0,
13213
13214
  errors: []
@@ -13279,11 +13280,28 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
13279
13280
  result.skipped_existing += 1;
13280
13281
  continue;
13281
13282
  }
13283
+ const candidateMessages = await readHermesLineageMessages(candidate).catch(
13284
+ (error) => {
13285
+ result.errors.push({
13286
+ profile: candidate.profileName,
13287
+ message: error instanceof Error ? error.message : String(error)
13288
+ });
13289
+ return null;
13290
+ }
13291
+ );
13292
+ if (!candidateMessages) {
13293
+ continue;
13294
+ }
13295
+ if (candidateMessages.length === 0) {
13296
+ result.skipped_empty_transcript += 1;
13297
+ continue;
13298
+ }
13282
13299
  const importedConversationId = await importHermesSession({
13283
13300
  paths,
13284
13301
  store,
13285
13302
  logger,
13286
- candidate
13303
+ candidate,
13304
+ messages: candidateMessages
13287
13305
  }).catch((error) => {
13288
13306
  result.errors.push({
13289
13307
  profile: candidate.profileName,
@@ -13387,7 +13405,10 @@ async function importHermesSession(input) {
13387
13405
  );
13388
13406
  const sessionId = candidate.session.id;
13389
13407
  const hermesSessionIds = lineageSessionIds(candidate);
13390
- const messages2 = await readHermesLineageMessages(candidate);
13408
+ const messages2 = input.messages ?? await readHermesLineageMessages(candidate);
13409
+ if (messages2.length === 0) {
13410
+ return null;
13411
+ }
13391
13412
  const now = (/* @__PURE__ */ new Date()).toISOString();
13392
13413
  const createdAt = isoFromHermesTime(candidate.session.started_at) ?? now;
13393
13414
  const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? isoFromHermesTime(messages2.at(-1)?.timestamp) ?? createdAt;
@@ -13487,6 +13508,18 @@ async function mergeExistingHermesConversation(input) {
13487
13508
  }
13488
13509
  let changed = false;
13489
13510
  const candidateMessages = await readHermesLineageMessages(input.candidate);
13511
+ if (candidateMessages.length === 0 && isEmptyHermesImportPlaceholder(canonical)) {
13512
+ await softDeleteEmptyHermesImportPlaceholder({
13513
+ paths: input.paths,
13514
+ store: input.store,
13515
+ conversation: canonical,
13516
+ candidate: input.candidate
13517
+ });
13518
+ return true;
13519
+ }
13520
+ if (candidateMessages.length === 0) {
13521
+ return false;
13522
+ }
13490
13523
  const nextCanonical = await mergeHermesCandidateIntoConversation({
13491
13524
  ...input,
13492
13525
  existing: canonical,
@@ -13698,7 +13731,26 @@ function isSafeHermesImportConversation(item) {
13698
13731
  }
13699
13732
  return snapshot.messages.length === 0 || snapshot.messages.every((message) => isHermesImportedMessage(message));
13700
13733
  }
13734
+ function isEmptyHermesImportPlaceholder(item) {
13735
+ return item.manifest.status === "active" && item.snapshot.messages.length === 0 && item.snapshot.runs.length === 0 && item.manifest.title_source === "default" && isDefaultConversationTitle(item.manifest.title) && item.manifest.hermes_session_id !== void 0 && !item.manifest.hermes_session_id.startsWith("hp_");
13736
+ }
13737
+ async function softDeleteEmptyHermesImportPlaceholder(input) {
13738
+ await softDeleteHermesImportConversation({
13739
+ paths: input.paths,
13740
+ store: input.store,
13741
+ conversation: input.conversation,
13742
+ candidate: input.candidate
13743
+ });
13744
+ }
13701
13745
  async function softDeleteMergedHermesDuplicate(input) {
13746
+ await softDeleteHermesImportConversation({
13747
+ paths: input.paths,
13748
+ store: input.store,
13749
+ conversation: input.duplicate,
13750
+ candidate: input.candidate
13751
+ });
13752
+ }
13753
+ async function softDeleteHermesImportConversation(input) {
13702
13754
  const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
13703
13755
  const emptySnapshot3 = {
13704
13756
  schema_version: 1,
@@ -13706,13 +13758,13 @@ async function softDeleteMergedHermesDuplicate(input) {
13706
13758
  runs: []
13707
13759
  };
13708
13760
  const hermesSessionIds = normalizeSessionIds([
13709
- input.duplicate.manifest.hermes_session_id,
13710
- ...input.duplicate.manifest.hermes_session_ids ?? [],
13711
- ...input.duplicate.manifest.hermes_lineage?.session_ids ?? [],
13761
+ input.conversation.manifest.hermes_session_id,
13762
+ ...input.conversation.manifest.hermes_session_ids ?? [],
13763
+ ...input.conversation.manifest.hermes_lineage?.session_ids ?? [],
13712
13764
  ...lineageSessionIds(input.candidate)
13713
13765
  ]);
13714
13766
  const nextManifest = {
13715
- ...input.duplicate.manifest,
13767
+ ...input.conversation.manifest,
13716
13768
  status: "deleted_soft",
13717
13769
  hermes_session_ids: hermesSessionIds,
13718
13770
  ...lineageManifestPatch(input.candidate),
@@ -13720,12 +13772,12 @@ async function softDeleteMergedHermesDuplicate(input) {
13720
13772
  deleted_at: deletedAt
13721
13773
  };
13722
13774
  const stats = buildConversationStats(
13723
- { ...nextManifest, stats: input.duplicate.manifest.stats },
13775
+ { ...nextManifest, stats: input.conversation.manifest.stats },
13724
13776
  emptySnapshot3
13725
13777
  );
13726
13778
  const tombstone = { ...nextManifest, stats };
13727
13779
  await input.store.writeManifest(tombstone);
13728
- await input.store.writeSnapshot(input.duplicate.conversationId, emptySnapshot3);
13780
+ await input.store.writeSnapshot(input.conversation.conversationId, emptySnapshot3);
13729
13781
  await upsertConversationStats(input.paths, toStatsIndexRecord(tombstone, stats));
13730
13782
  }
13731
13783
  function mergeHermesLineageIntoManifest(input) {
@@ -16127,6 +16179,7 @@ function normalizeProfileForCompare(value) {
16127
16179
  // src/hermes/stt.ts
16128
16180
  import { execFile as execFile3 } from "child_process";
16129
16181
  import { access as access2, readFile as readFile11, stat as stat11 } from "fs/promises";
16182
+ import os4 from "os";
16130
16183
  import path18 from "path";
16131
16184
  import { promisify as promisify3 } from "util";
16132
16185
  var execFileAsync3 = promisify3(execFile3);
@@ -16143,8 +16196,8 @@ async function transcribeAudioWithHermesProfile(input) {
16143
16196
  "\u8BED\u97F3\u8F6C\u5199\u672A\u542F\u7528\u3002\u8BF7\u5728 App \u7684 Profile \u5DE5\u5177\u7BA1\u7406\u4E2D\u542F\u7528\u8BED\u97F3\u8F6C\u5199 (STT)\u3002"
16144
16197
  );
16145
16198
  }
16146
- const python = await resolveHermesPythonCommand();
16147
- const env = await buildHermesSttEnv(profileName);
16199
+ const python = await resolveHermesPythonRuntime();
16200
+ const env = await buildHermesSttEnv(profileName, python.sourceRoot);
16148
16201
  const script = [
16149
16202
  "import json, sys",
16150
16203
  "try:",
@@ -16211,7 +16264,7 @@ async function transcribeAudioWithHermesProfile(input) {
16211
16264
  });
16212
16265
  return { transcript, provider: result.provider };
16213
16266
  }
16214
- async function buildHermesSttEnv(profileName) {
16267
+ async function buildHermesSttEnv(profileName, hermesSourceRoot) {
16215
16268
  const hermesEnv = await readHermesEnvFile(profileName);
16216
16269
  const hermesHome = resolveHermesProfileDir(profileName);
16217
16270
  const env = {
@@ -16220,9 +16273,9 @@ async function buildHermesSttEnv(profileName) {
16220
16273
  HERMES_HOME: hermesHome,
16221
16274
  PYTHONIOENCODING: "utf-8"
16222
16275
  };
16223
- const devSource = await findDevHermesAgentSource();
16224
- if (devSource) {
16225
- env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(path18.delimiter);
16276
+ const sourceRoot = hermesSourceRoot ?? await findDevHermesAgentSource();
16277
+ if (sourceRoot) {
16278
+ env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path18.delimiter);
16226
16279
  }
16227
16280
  return env;
16228
16281
  }
@@ -16250,22 +16303,63 @@ function parseSttResult(stdout) {
16250
16303
  return { success: false, error: "Invalid STT JSON result" };
16251
16304
  }
16252
16305
  }
16253
- async function resolveHermesPythonCommand() {
16306
+ async function resolveHermesPythonRuntime() {
16254
16307
  const explicit = process.env.HERMES_PYTHON?.trim();
16255
16308
  if (explicit) {
16256
- return { command: explicit, args: [] };
16309
+ return {
16310
+ command: explicit,
16311
+ args: [],
16312
+ sourceRoot: await findExplicitHermesSourceRoot() ?? await findHermesSourceRoot(explicit)
16313
+ };
16257
16314
  }
16258
16315
  const hermesBin = await resolveExecutablePath2(resolveHermesBin());
16316
+ let shebangRuntime = null;
16259
16317
  if (hermesBin) {
16318
+ const installRoot = await findHermesSourceRoot(hermesBin);
16260
16319
  const shebang = await readShebang(hermesBin);
16261
16320
  const command = shebangToPythonCommand(shebang);
16262
16321
  if (command) {
16263
- return command;
16322
+ const sourceRoot = await findHermesSourceRoot(command.command) ?? installRoot;
16323
+ if (sourceRoot) {
16324
+ return {
16325
+ ...command,
16326
+ sourceRoot
16327
+ };
16328
+ }
16329
+ shebangRuntime = {
16330
+ ...command,
16331
+ sourceRoot: null
16332
+ };
16333
+ }
16334
+ const installPython = installRoot ? await findHermesVenvPython(installRoot) : null;
16335
+ if (installPython) {
16336
+ return {
16337
+ command: installPython,
16338
+ args: [],
16339
+ sourceRoot: installRoot
16340
+ };
16341
+ }
16342
+ }
16343
+ for (const sourceRoot of hermesSourceRootCandidates()) {
16344
+ if (!await isHermesAgentSourceRoot(sourceRoot)) {
16345
+ continue;
16346
+ }
16347
+ const installPython = await findHermesVenvPython(sourceRoot);
16348
+ if (installPython) {
16349
+ return {
16350
+ command: installPython,
16351
+ args: [],
16352
+ sourceRoot
16353
+ };
16264
16354
  }
16265
16355
  }
16356
+ if (shebangRuntime) {
16357
+ return shebangRuntime;
16358
+ }
16266
16359
  return {
16267
16360
  command: process.platform === "win32" ? "python" : "python3",
16268
- args: []
16361
+ args: [],
16362
+ sourceRoot: await findExplicitHermesSourceRoot() ?? await findDevHermesAgentSource()
16269
16363
  };
16270
16364
  }
16271
16365
  async function resolveExecutablePath2(command) {
@@ -16296,6 +16390,21 @@ async function isExecutableFile(filePath) {
16296
16390
  return false;
16297
16391
  }
16298
16392
  }
16393
+ async function findHermesVenvPython(sourceRoot) {
16394
+ const candidates = process.platform === "win32" ? [
16395
+ path18.join(sourceRoot, "venv", "Scripts", "python.exe"),
16396
+ path18.join(sourceRoot, ".venv", "Scripts", "python.exe")
16397
+ ] : [
16398
+ path18.join(sourceRoot, "venv", "bin", "python"),
16399
+ path18.join(sourceRoot, ".venv", "bin", "python")
16400
+ ];
16401
+ for (const candidate of candidates) {
16402
+ if (await isExecutableFile(candidate)) {
16403
+ return candidate;
16404
+ }
16405
+ }
16406
+ return null;
16407
+ }
16299
16408
  async function readShebang(filePath) {
16300
16409
  const raw = await readFile11(filePath, "utf8").catch(() => "");
16301
16410
  const firstLine = raw.split(/\r?\n/u)[0]?.trim() ?? "";
@@ -16320,12 +16429,48 @@ async function findDevHermesAgentSource() {
16320
16429
  path18.resolve(process.cwd(), "../../reference/hermes-agent")
16321
16430
  ];
16322
16431
  for (const candidate of candidates) {
16323
- if (await isDirectory(candidate)) {
16432
+ if (await isHermesAgentSourceRoot(candidate)) {
16324
16433
  return candidate;
16325
16434
  }
16326
16435
  }
16327
16436
  return null;
16328
16437
  }
16438
+ async function findHermesSourceRoot(executablePath) {
16439
+ let cursor = path18.dirname(path18.resolve(executablePath));
16440
+ for (let index = 0; index < 6; index += 1) {
16441
+ if (await isHermesAgentSourceRoot(cursor)) {
16442
+ return cursor;
16443
+ }
16444
+ const parent = path18.dirname(cursor);
16445
+ if (parent === cursor) {
16446
+ break;
16447
+ }
16448
+ cursor = parent;
16449
+ }
16450
+ return null;
16451
+ }
16452
+ function hermesSourceRootCandidates() {
16453
+ const candidates = [
16454
+ process.env.HERMES_PYTHON_SRC_ROOT?.trim(),
16455
+ path18.join(os4.homedir(), ".hermes", "hermes-agent"),
16456
+ "/usr/local/lib/hermes-agent",
16457
+ "/opt/hermes"
16458
+ ].filter((candidate) => Boolean(candidate));
16459
+ if (process.platform === "win32") {
16460
+ const localAppData = process.env.LOCALAPPDATA?.trim();
16461
+ if (localAppData) {
16462
+ candidates.unshift(path18.join(localAppData, "hermes", "hermes-agent"));
16463
+ }
16464
+ }
16465
+ return candidates;
16466
+ }
16467
+ async function findExplicitHermesSourceRoot() {
16468
+ const explicit = process.env.HERMES_PYTHON_SRC_ROOT?.trim();
16469
+ return explicit && await isHermesAgentSourceRoot(explicit) ? explicit : null;
16470
+ }
16471
+ async function isHermesAgentSourceRoot(candidate) {
16472
+ return await isDirectory(candidate) && await isDirectory(path18.join(candidate, "tools")) && await isDirectory(path18.join(candidate, "hermes_cli"));
16473
+ }
16329
16474
  async function isDirectory(candidate) {
16330
16475
  return stat11(candidate).then((info) => info.isDirectory()).catch(() => false);
16331
16476
  }
@@ -24480,7 +24625,7 @@ import YAML5 from "yaml";
24480
24625
 
24481
24626
  // src/hermes/link-skill.ts
24482
24627
  import { readFile as readFile16, stat as stat16 } from "fs/promises";
24483
- import os4 from "os";
24628
+ import os5 from "os";
24484
24629
  import path23 from "path";
24485
24630
  import YAML4 from "yaml";
24486
24631
  var HERMES_LINK_SKILL_ROOT_DIR = "hermes-skills";
@@ -24762,10 +24907,10 @@ function resolveExternalDirEntry(entry, hermesHome) {
24762
24907
  }
24763
24908
  function expandHome(value) {
24764
24909
  if (value === "~") {
24765
- return os4.homedir();
24910
+ return os5.homedir();
24766
24911
  }
24767
24912
  if (value.startsWith(`~${path23.sep}`) || value.startsWith("~/")) {
24768
- return path23.join(os4.homedir(), value.slice(2));
24913
+ return path23.join(os5.homedir(), value.slice(2));
24769
24914
  }
24770
24915
  return value;
24771
24916
  }
@@ -29746,7 +29891,7 @@ function normalizeMessage(value) {
29746
29891
  // src/runtime/system-info.ts
29747
29892
  import { execFileSync } from "child_process";
29748
29893
  import { readFileSync } from "fs";
29749
- import os5 from "os";
29894
+ import os6 from "os";
29750
29895
  function readLinkSystemInfo() {
29751
29896
  const platform = process.platform;
29752
29897
  const hostname = readHostname(platform);
@@ -29785,7 +29930,7 @@ function readHostname(platform) {
29785
29930
  return computerName;
29786
29931
  }
29787
29932
  }
29788
- return normalizeText(os5.hostname());
29933
+ return normalizeText(os6.hostname());
29789
29934
  }
29790
29935
  function readOsLabel(platform) {
29791
29936
  if (platform === "darwin") {
@@ -29793,12 +29938,12 @@ function readOsLabel(platform) {
29793
29938
  return version ? `macOS ${version}` : "macOS";
29794
29939
  }
29795
29940
  if (platform === "linux") {
29796
- return readLinuxOsRelease() ?? `Linux ${os5.release()}`;
29941
+ return readLinuxOsRelease() ?? `Linux ${os6.release()}`;
29797
29942
  }
29798
29943
  if (platform === "win32") {
29799
- return `Windows ${os5.release()}`;
29944
+ return `Windows ${os6.release()}`;
29800
29945
  }
29801
- return `${os5.type()} ${os5.release()}`.trim();
29946
+ return `${os6.type()} ${os6.release()}`.trim();
29802
29947
  }
29803
29948
  function readLinuxOsRelease() {
29804
29949
  for (const file of ["/etc/os-release", "/usr/lib/os-release"]) {
@@ -29845,11 +29990,11 @@ function truncateText(value, maxLength) {
29845
29990
  }
29846
29991
 
29847
29992
  // src/topology/network.ts
29848
- import os7 from "os";
29993
+ import os8 from "os";
29849
29994
 
29850
29995
  // src/topology/environment.ts
29851
29996
  import { existsSync, readFileSync as readFileSync2 } from "fs";
29852
- import os6 from "os";
29997
+ import os7 from "os";
29853
29998
  function detectRuntimeEnvironment(env = process.env) {
29854
29999
  if (isWsl(env)) {
29855
30000
  return {
@@ -29878,7 +30023,7 @@ function isWsl(env) {
29878
30023
  if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) {
29879
30024
  return true;
29880
30025
  }
29881
- const release = os6.release().toLowerCase();
30026
+ const release = os7.release().toLowerCase();
29882
30027
  return release.includes("microsoft") || release.includes("wsl");
29883
30028
  }
29884
30029
  function isContainer(env) {
@@ -29923,7 +30068,7 @@ async function discoverRouteCandidates(options) {
29923
30068
  };
29924
30069
  }
29925
30070
  function discoverLanIps() {
29926
- return discoverLanIpsFromInterfaces(os7.networkInterfaces());
30071
+ return discoverLanIpsFromInterfaces(os8.networkInterfaces());
29927
30072
  }
29928
30073
  function discoverLanIpsFromInterfaces(interfaces) {
29929
30074
  const result = /* @__PURE__ */ new Set();
package/dist/cli/index.js CHANGED
@@ -53,7 +53,7 @@ import {
53
53
  stopDaemonProcess,
54
54
  summarizeUsageProbeEnsure,
55
55
  translate
56
- } from "../chunk-WRXMKEQY.js";
56
+ } from "../chunk-36L5JCHO.js";
57
57
 
58
58
  // src/cli/index.ts
59
59
  import { Command } from "commander";
@@ -264,6 +264,14 @@ function autostartEnvironment() {
264
264
  if (hermesBin) {
265
265
  environment.HERMES_BIN = hermesBin;
266
266
  }
267
+ const hermesPython = process.env.HERMES_PYTHON?.trim();
268
+ if (hermesPython) {
269
+ environment.HERMES_PYTHON = hermesPython;
270
+ }
271
+ const hermesPythonSourceRoot = process.env.HERMES_PYTHON_SRC_ROOT?.trim();
272
+ if (hermesPythonSourceRoot) {
273
+ environment.HERMES_PYTHON_SRC_ROOT = hermesPythonSourceRoot;
274
+ }
267
275
  return environment;
268
276
  }
269
277
  function buildAutostartPath() {
@@ -409,6 +409,7 @@ interface HermesSessionSyncResult {
409
409
  reprojected_count: number;
410
410
  skipped_existing: number;
411
411
  skipped_hidden: number;
412
+ skipped_empty_transcript: number;
412
413
  skipped_deleted: number;
413
414
  skipped_over_limit: number;
414
415
  errors: Array<{
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-WRXMKEQY.js";
3
+ } from "../chunk-36L5JCHO.js";
4
4
  export {
5
5
  createApp
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hermespilot/link",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",