@hermespilot/link 0.4.7 → 0.4.9

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.
@@ -2110,13 +2110,13 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
2110
2110
  const envHost = envOverrides.host?.trim() ? envOverrides.host : null;
2111
2111
  const configPort = readApiServerPort(configOnly.port);
2112
2112
  const envPort = readApiServerPort(envOverrides.port);
2113
- const desiredHost = configHost ?? envHost ?? DEFAULT_HERMES_API_SERVER_HOST;
2113
+ const desiredHost = envHost ?? configHost ?? DEFAULT_HERMES_API_SERVER_HOST;
2114
2114
  const desiredPort = await resolveDesiredApiServerPort({
2115
2115
  profileName,
2116
2116
  configPort,
2117
2117
  envPort
2118
2118
  });
2119
- const desiredKey = configKey ?? envKey ?? randomBytes(32).toString("base64url");
2119
+ const desiredKey = envKey ?? configKey ?? randomBytes(32).toString("base64url");
2120
2120
  let changed = false;
2121
2121
  let enabledAdded = false;
2122
2122
  let hostAdded = false;
@@ -2210,7 +2210,7 @@ async function repairHermesApiServerConfigUnlocked(profileName = "default", conf
2210
2210
  await readHermesApiServerEnvOverrides(profileName),
2211
2211
  true
2212
2212
  );
2213
- const freshPort = await nextProfileApiServerPort(profileName);
2213
+ const freshPort = profileName === "default" ? DEFAULT_HERMES_API_SERVER_PORT : await nextProfileApiServerPort(profileName);
2214
2214
  const freshKey = randomBytes(32).toString("base64url");
2215
2215
  apiServer.enabled = true;
2216
2216
  extra.host = DEFAULT_HERMES_API_SERVER_HOST;
@@ -2239,7 +2239,7 @@ async function repairHermesApiServerConfigUnlocked(profileName = "default", conf
2239
2239
  hostAdded: previous.host !== DEFAULT_HERMES_API_SERVER_HOST,
2240
2240
  portAdded: previous.port !== freshPort,
2241
2241
  backupPath,
2242
- notice: "\u5DF2\u4E3A Hermes API Server \u91CD\u65B0\u5206\u914D\u672C\u673A\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002"
2242
+ notice: profileName === "default" ? "\u5DF2\u4E3A Hermes API Server \u4FDD\u6301\u9ED8\u8BA4\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002" : "\u5DF2\u4E3A Hermes API Server \u91CD\u65B0\u5206\u914D\u672C\u673A\u7AEF\u53E3\u5E76\u8F6E\u6362 key\uFF0C\u7528\u4E8E\u4FEE\u590D\u65E7 Gateway \u6216 key \u4E0D\u4E00\u81F4\u5BFC\u81F4\u7684 401\u3002"
2243
2243
  };
2244
2244
  }
2245
2245
  async function readHermesConfigDocument(configPath) {
@@ -3579,16 +3579,14 @@ function readApiServerPort(value) {
3579
3579
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
3580
3580
  }
3581
3581
  async function resolveDesiredApiServerPort(input) {
3582
+ const effectivePort = input.envPort ?? input.configPort;
3582
3583
  if (shouldAssignDedicatedProfileApiServerPort(
3583
3584
  input.profileName,
3584
- input.configPort ?? void 0
3585
+ effectivePort ?? void 0
3585
3586
  )) {
3586
- if (input.profileName !== "default" && input.envPort !== null && input.envPort !== DEFAULT_HERMES_API_SERVER_PORT) {
3587
- return input.envPort;
3588
- }
3589
3587
  return nextProfileApiServerPort(input.profileName);
3590
3588
  }
3591
- return input.configPort ?? input.envPort ?? DEFAULT_HERMES_API_SERVER_PORT;
3589
+ return effectivePort ?? DEFAULT_HERMES_API_SERVER_PORT;
3592
3590
  }
3593
3591
  async function nextProfileApiServerPort(profileName) {
3594
3592
  const usedPorts = await readConfiguredApiServerPorts(profileName);
@@ -3877,6 +3875,9 @@ function buildNotice(flags) {
3877
3875
  if (flags.keyAdded) {
3878
3876
  fields.push("key");
3879
3877
  }
3878
+ if (fields.length === 0) {
3879
+ return "\u5DF2\u540C\u6B65 Hermes API Server config.yaml \u4E0E Profile .env \u7684\u6709\u6548\u914D\u7F6E\u3002";
3880
+ }
3880
3881
  return `\u5DF2\u4E3A Hermes API Server \u81EA\u52A8\u8865\u5145 ${fields.join("\u3001")}\uFF1B\u672A\u8986\u76D6\u5DF2\u6709 port/host/key\u3002`;
3881
3882
  }
3882
3883
  function toRecord(value) {
@@ -4117,7 +4118,8 @@ function isConversationMissingError(error) {
4117
4118
 
4118
4119
  // src/hermes/gateway.ts
4119
4120
  import { execFile as execFile2, spawn } from "child_process";
4120
- import { access, readFile as readFile5, stat as stat4 } from "fs/promises";
4121
+ import { constants as fsConstants } from "fs";
4122
+ import { access, readFile as readFile5, realpath, stat as stat4 } from "fs/promises";
4121
4123
  import path7 from "path";
4122
4124
  import { setTimeout as delay } from "timers/promises";
4123
4125
  import { promisify as promisify2 } from "util";
@@ -4132,7 +4134,7 @@ import os2 from "os";
4132
4134
  import path5 from "path";
4133
4135
 
4134
4136
  // src/constants.ts
4135
- var LINK_VERSION = "0.4.7";
4137
+ var LINK_VERSION = "0.4.9";
4136
4138
  var LINK_COMMAND = "hermeslink";
4137
4139
  var LINK_DEFAULT_PORT = 52379;
4138
4140
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -4631,6 +4633,8 @@ var PROFILE_NAME_PATTERN = /^[a-zA-Z0-9._-]{1,64}$/u;
4631
4633
  var DASHBOARD_STATUS_URL = "http://127.0.0.1:9119/api/status";
4632
4634
  var DASHBOARD_STATUS_TIMEOUT_MS = 1500;
4633
4635
  var DEFAULT_VERSION_CACHE_TTL_MS = 6e4;
4636
+ var VERSION_METADATA_TIMEOUT_MS = 3e3;
4637
+ var VERSION_COMMAND_TIMEOUT_MS = 5e3;
4634
4638
  var MAX_VERSION_LOG_OUTPUT_LENGTH = 1200;
4635
4639
  var HERMES_GATEWAY_ENV_BLOCKLIST_PREFIXES = ["API_SERVER_"];
4636
4640
  var gatewayStartInFlightByProfile = /* @__PURE__ */ new Map();
@@ -4826,9 +4830,26 @@ async function readHermesVersion(options = {}) {
4826
4830
  }
4827
4831
  async function execHermesVersion(hermesBin, logger) {
4828
4832
  const failures = [];
4833
+ try {
4834
+ const metadataVersion = await readHermesPythonMetadataVersion(
4835
+ hermesBin,
4836
+ VERSION_METADATA_TIMEOUT_MS
4837
+ );
4838
+ if (metadataVersion) {
4839
+ return metadataVersion;
4840
+ }
4841
+ } catch (error) {
4842
+ const failure = describeVersionMetadataFailure(hermesBin, error);
4843
+ failures.push(failure);
4844
+ void logger?.debug("hermes_version_metadata_probe_failed", failure.fields);
4845
+ }
4829
4846
  for (const args of [["--version"], ["version"]]) {
4830
4847
  try {
4831
- return await spawnHermesVersionCommand(hermesBin, args, 5e3);
4848
+ return await spawnHermesVersionCommand(
4849
+ hermesBin,
4850
+ args,
4851
+ VERSION_COMMAND_TIMEOUT_MS
4852
+ );
4832
4853
  } catch (error) {
4833
4854
  const failure = describeVersionCommandFailure(hermesBin, args, error);
4834
4855
  failures.push(failure);
@@ -4844,6 +4865,109 @@ async function execHermesVersion(hermesBin, logger) {
4844
4865
  });
4845
4866
  throw new Error(summary);
4846
4867
  }
4868
+ async function readHermesPythonMetadataVersion(hermesBin, timeoutMs) {
4869
+ const hermesPath = await resolveExecutablePath(hermesBin);
4870
+ if (!hermesPath) {
4871
+ return null;
4872
+ }
4873
+ const firstLine = await readFirstLine(hermesPath);
4874
+ const interpreter = parsePythonShebang(firstLine);
4875
+ if (!interpreter) {
4876
+ return null;
4877
+ }
4878
+ return await spawnHermesMetadataCommand(interpreter, timeoutMs);
4879
+ }
4880
+ async function spawnHermesMetadataCommand(interpreter, timeoutMs) {
4881
+ const script = [
4882
+ "import importlib.metadata as metadata",
4883
+ "try:",
4884
+ ' version = metadata.version("hermes-agent")',
4885
+ ' release_date = ""',
4886
+ "except Exception:",
4887
+ " import hermes_cli",
4888
+ ' version = getattr(hermes_cli, "__version__", "")',
4889
+ ' release_date = getattr(hermes_cli, "__release_date__", "")',
4890
+ "if not version:",
4891
+ ' raise SystemExit("Hermes Agent version metadata not found")',
4892
+ 'suffix = f" ({release_date})" if release_date else ""',
4893
+ 'print(f"Hermes Agent v{version}{suffix}")',
4894
+ ""
4895
+ ].join("\n");
4896
+ return await new Promise((resolve, reject) => {
4897
+ const child = spawn(
4898
+ interpreter.command,
4899
+ [...interpreter.args, "-c", script],
4900
+ {
4901
+ stdio: ["ignore", "pipe", "pipe"],
4902
+ windowsHide: true,
4903
+ env: { ...process.env, HERMES_NONINTERACTIVE: "1" }
4904
+ }
4905
+ );
4906
+ let stdout = "";
4907
+ let stderr = "";
4908
+ let settled = false;
4909
+ const timer = setTimeout(() => {
4910
+ finish(() => {
4911
+ reject(
4912
+ createVersionCommandError(
4913
+ `Hermes Python metadata probe timed out after ${timeoutMs}ms`,
4914
+ { stdout, stderr, killed: true }
4915
+ )
4916
+ );
4917
+ }, true);
4918
+ }, timeoutMs);
4919
+ const finish = (callback, kill = false) => {
4920
+ if (settled) {
4921
+ return;
4922
+ }
4923
+ settled = true;
4924
+ clearTimeout(timer);
4925
+ if (kill && child.pid && !child.killed) {
4926
+ child.kill();
4927
+ }
4928
+ callback();
4929
+ };
4930
+ child.stdout?.on("data", (chunk) => {
4931
+ stdout += chunk.toString();
4932
+ });
4933
+ child.stderr?.on("data", (chunk) => {
4934
+ stderr += chunk.toString();
4935
+ });
4936
+ child.once("error", (error) => {
4937
+ finish(() => {
4938
+ reject(
4939
+ createVersionCommandError(error.message, {
4940
+ stdout,
4941
+ stderr,
4942
+ cause: error
4943
+ })
4944
+ );
4945
+ });
4946
+ });
4947
+ child.once("close", (code, signal) => {
4948
+ finish(() => {
4949
+ const raw = `${stdout}
4950
+ ${stderr}`.trim();
4951
+ if (code === 0 && parseHermesVersion(raw)) {
4952
+ resolve({ stdout: raw });
4953
+ return;
4954
+ }
4955
+ reject(
4956
+ createVersionCommandError(
4957
+ `Hermes Python metadata probe exited with code ${code ?? "null"}`,
4958
+ {
4959
+ stdout,
4960
+ stderr,
4961
+ code,
4962
+ signal,
4963
+ killed: child.killed
4964
+ }
4965
+ )
4966
+ );
4967
+ });
4968
+ });
4969
+ });
4970
+ }
4847
4971
  async function spawnHermesVersionCommand(hermesBin, args, timeoutMs) {
4848
4972
  return await new Promise((resolve, reject) => {
4849
4973
  const child = spawn(hermesBin, args, {
@@ -5424,6 +5548,20 @@ function createVersionCommandError(message, details) {
5424
5548
  }
5425
5549
  return error;
5426
5550
  }
5551
+ function describeVersionMetadataFailure(hermesBin, error) {
5552
+ const message = error instanceof Error ? error.message : String(error);
5553
+ const details = readExecErrorDetails(error, message);
5554
+ return {
5555
+ summary: `${hermesBin} Python metadata probe failed: ${message}`,
5556
+ fields: {
5557
+ hermes_bin: hermesBin,
5558
+ command: "python-metadata",
5559
+ cwd: process.cwd(),
5560
+ error: message,
5561
+ ...details
5562
+ }
5563
+ };
5564
+ }
5427
5565
  function describeVersionCommandFailure(hermesBin, args, error) {
5428
5566
  const message = error instanceof Error ? error.message : String(error);
5429
5567
  const details = readExecErrorDetails(error, message);
@@ -5473,6 +5611,91 @@ function readExecErrorDetails(error, message) {
5473
5611
  function truncateVersionLogOutput(value) {
5474
5612
  return value.length > MAX_VERSION_LOG_OUTPUT_LENGTH ? `${value.slice(0, MAX_VERSION_LOG_OUTPUT_LENGTH)}...` : value;
5475
5613
  }
5614
+ async function resolveExecutablePath(command) {
5615
+ const candidates = executablePathCandidates(command);
5616
+ for (const candidate of candidates) {
5617
+ try {
5618
+ await access(candidate, fsConstants.X_OK);
5619
+ return await realpath(candidate).catch(() => candidate);
5620
+ } catch {
5621
+ }
5622
+ }
5623
+ return null;
5624
+ }
5625
+ function executablePathCandidates(command) {
5626
+ const normalized = command.trim();
5627
+ if (!normalized) {
5628
+ return [];
5629
+ }
5630
+ if (/[\\/]/u.test(normalized)) {
5631
+ return withWindowsExecutableExtensions(normalized);
5632
+ }
5633
+ const pathEntries = (process.env.PATH ?? "").split(path7.delimiter).filter(Boolean);
5634
+ return pathEntries.flatMap(
5635
+ (entry) => withWindowsExecutableExtensions(path7.join(entry, normalized))
5636
+ );
5637
+ }
5638
+ function withWindowsExecutableExtensions(filePath) {
5639
+ if (process.platform !== "win32" || path7.extname(filePath)) {
5640
+ return [filePath];
5641
+ }
5642
+ const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
5643
+ return [filePath, ...extensions.map((extension) => `${filePath}${extension}`)];
5644
+ }
5645
+ async function readFirstLine(filePath) {
5646
+ const raw = await readFile5(filePath, "utf8");
5647
+ return raw.split(/\r?\n/u, 1)[0] ?? "";
5648
+ }
5649
+ function parsePythonShebang(line) {
5650
+ if (!line.startsWith("#!")) {
5651
+ return null;
5652
+ }
5653
+ const parts = splitShellWords(line.slice(2).trim());
5654
+ if (parts.length === 0) {
5655
+ return null;
5656
+ }
5657
+ const commandName = path7.basename(parts[0] ?? "").toLowerCase();
5658
+ if (commandName === "env") {
5659
+ const args = expandEnvShebangArgs(parts.slice(1));
5660
+ const pythonIndex = args.findIndex((part) => isPythonCommandName(part));
5661
+ if (pythonIndex < 0) {
5662
+ return null;
5663
+ }
5664
+ return {
5665
+ command: parts[0],
5666
+ args
5667
+ };
5668
+ }
5669
+ if (!isPythonCommandName(commandName)) {
5670
+ return null;
5671
+ }
5672
+ return {
5673
+ command: parts[0],
5674
+ args: parts.slice(1)
5675
+ };
5676
+ }
5677
+ function isPythonCommandName(command) {
5678
+ return /^python(?:\d+(?:\.\d+)?)?$/iu.test(path7.basename(command));
5679
+ }
5680
+ function expandEnvShebangArgs(args) {
5681
+ const expanded = [];
5682
+ for (let index = 0; index < args.length; index += 1) {
5683
+ const arg = args[index];
5684
+ if (arg === "-S" && args[index + 1]) {
5685
+ expanded.push(...splitShellWords(args[index + 1]));
5686
+ index += 1;
5687
+ continue;
5688
+ }
5689
+ if (arg !== "-S") {
5690
+ expanded.push(arg);
5691
+ }
5692
+ }
5693
+ return expanded;
5694
+ }
5695
+ function splitShellWords(value) {
5696
+ const matches = value.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/gu);
5697
+ return [...matches].map((match) => match[1] ?? match[2] ?? match[3] ?? "");
5698
+ }
5476
5699
  function compareSemver(left, right) {
5477
5700
  const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
5478
5701
  const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
@@ -12677,7 +12900,7 @@ async function resolveHermesPythonCommand() {
12677
12900
  if (explicit) {
12678
12901
  return { command: explicit, args: [] };
12679
12902
  }
12680
- const hermesBin = await resolveExecutablePath(resolveHermesBin());
12903
+ const hermesBin = await resolveExecutablePath2(resolveHermesBin());
12681
12904
  if (hermesBin) {
12682
12905
  const shebang = await readShebang(hermesBin);
12683
12906
  const command = shebangToPythonCommand(shebang);
@@ -12690,7 +12913,7 @@ async function resolveHermesPythonCommand() {
12690
12913
  args: []
12691
12914
  };
12692
12915
  }
12693
- async function resolveExecutablePath(command) {
12916
+ async function resolveExecutablePath2(command) {
12694
12917
  if (path17.isAbsolute(command)) {
12695
12918
  return await isExecutableFile(command) ? command : null;
12696
12919
  }
@@ -12907,8 +13130,15 @@ function normalizeHermesResponseEvent(event) {
12907
13130
  return normalizeResponseCompleted(event);
12908
13131
  case "response.failed":
12909
13132
  return normalizeResponseFailed(event);
13133
+ case "response.output_text.done": {
13134
+ const delta = readDelta(event.payload);
13135
+ return delta ? {
13136
+ ...event,
13137
+ payloadType: "message.delta",
13138
+ payload: { type: "message.delta", delta }
13139
+ } : null;
13140
+ }
12910
13141
  case "response.created":
12911
- case "response.output_text.done":
12912
13142
  return null;
12913
13143
  default:
12914
13144
  return null;
@@ -12938,6 +13168,14 @@ function normalizeResponseOutputItemAdded(event) {
12938
13168
  }
12939
13169
  function normalizeResponseOutputItemDone(event) {
12940
13170
  const item = toRecord10(event.payload.item);
13171
+ if (readString12(item, "type") === "message") {
13172
+ const delta = extractResponseAssistantText({ output: [item] });
13173
+ return delta ? {
13174
+ ...event,
13175
+ payloadType: "message.delta",
13176
+ payload: { type: "message.delta", delta }
13177
+ } : null;
13178
+ }
12941
13179
  if (readString12(item, "type") !== "function_call_output") {
12942
13180
  return null;
12943
13181
  }
@@ -13012,6 +13250,30 @@ function readErrorMessage2(payload) {
13012
13250
  function readDelta(payload) {
13013
13251
  return readText2(payload, "delta") ?? readText2(payload, "text") ?? readText2(payload, "content");
13014
13252
  }
13253
+ function extractResponseAssistantText(value) {
13254
+ if (typeof value === "string") {
13255
+ return value.trim().length > 0 ? value : null;
13256
+ }
13257
+ const payload = toRecord10(value);
13258
+ const response = toRecord10(payload.response ?? value);
13259
+ const directText = readText2(response, "output_text");
13260
+ if (directText?.trim()) {
13261
+ return directText;
13262
+ }
13263
+ const output = response.output;
13264
+ if (Array.isArray(output)) {
13265
+ const messages = output.map(readResponseOutputItemText).filter((text) => Boolean(text?.trim()));
13266
+ if (messages.length > 0) {
13267
+ return messages.join("\n\n");
13268
+ }
13269
+ }
13270
+ const choicesText = readAssistantTextFromChoices(response);
13271
+ if (choicesText) {
13272
+ return choicesText;
13273
+ }
13274
+ const fallbackText = readResponseMessageText(response);
13275
+ return fallbackText?.trim() ? fallbackText : null;
13276
+ }
13015
13277
  function emptyHermesResponseMessage() {
13016
13278
  return "Hermes \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u5185\u5BB9\u3002\u8BF7\u5148\u770B Gateway / \u6A21\u578B\u914D\u7F6E\u662F\u5426\u6B63\u5E38\uFF0C\u6216\u67E5\u770B\u4E0B\u65B9\u5931\u8D25\u8BE6\u60C5\u3002";
13017
13279
  }
@@ -13081,6 +13343,17 @@ function readFirstChoice(payload) {
13081
13343
  }
13082
13344
  return toRecord10(choices[0]);
13083
13345
  }
13346
+ function readAssistantTextFromChoices(payload) {
13347
+ const choices = payload.choices;
13348
+ if (!Array.isArray(choices)) {
13349
+ return null;
13350
+ }
13351
+ const messages = choices.map(toRecord10).map((choice) => toRecord10(choice.message ?? choice.delta)).filter((message) => {
13352
+ const role = readString12(message, "role");
13353
+ return !role || role === "assistant";
13354
+ }).map(readResponseMessageText).filter((text) => Boolean(text?.trim()));
13355
+ return messages.length > 0 ? messages.join("\n\n") : null;
13356
+ }
13084
13357
  function readInteger2(payload, key) {
13085
13358
  const value = payload[key];
13086
13359
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -13100,6 +13373,50 @@ function readText2(payload, key) {
13100
13373
  const value = payload[key];
13101
13374
  return typeof value === "string" && value.length > 0 ? value : null;
13102
13375
  }
13376
+ function readResponseOutputItemText(value) {
13377
+ if (typeof value === "string") {
13378
+ return value;
13379
+ }
13380
+ const item = toRecord10(value);
13381
+ const type = readString12(item, "type");
13382
+ const role = readString12(item, "role");
13383
+ if (type && type !== "message" && type !== "output_text" && type !== "text") {
13384
+ return null;
13385
+ }
13386
+ if (role && role !== "assistant") {
13387
+ return null;
13388
+ }
13389
+ return readResponseMessageText(item);
13390
+ }
13391
+ function readResponseMessageText(payload) {
13392
+ const contentText = readResponseContentText(payload.content);
13393
+ return contentText ?? readText2(payload, "output_text") ?? readText2(payload, "text") ?? readText2(payload, "content") ?? readText2(payload, "refusal");
13394
+ }
13395
+ function readResponseContentText(value) {
13396
+ if (typeof value === "string") {
13397
+ return value;
13398
+ }
13399
+ if (!Array.isArray(value)) {
13400
+ const record = toRecord10(value);
13401
+ return readText2(record, "text") ?? readText2(record, "content") ?? readText2(record, "output_text") ?? readText2(record, "refusal");
13402
+ }
13403
+ const chunks = value.map((partValue) => {
13404
+ if (typeof partValue === "string") {
13405
+ return partValue;
13406
+ }
13407
+ const part = toRecord10(partValue);
13408
+ const type = readString12(part, "type");
13409
+ if (type && !isVisibleResponseTextPart(type)) {
13410
+ return null;
13411
+ }
13412
+ return readText2(part, "text") ?? readText2(part, "content") ?? readText2(part, "output_text") ?? readText2(part, "refusal");
13413
+ }).filter((text2) => Boolean(text2));
13414
+ const text = chunks.join("");
13415
+ return text.trim() ? text : null;
13416
+ }
13417
+ function isVisibleResponseTextPart(type) {
13418
+ return type === "output_text" || type === "text" || type === "message_text" || type === "refusal";
13419
+ }
13103
13420
  function readResponseItemOutput(value) {
13104
13421
  if (typeof value === "string") {
13105
13422
  return value;
@@ -13280,6 +13597,11 @@ var ConversationRunLifecycle = class {
13280
13597
  error: error instanceof Error ? error.message : String(error)
13281
13598
  });
13282
13599
  });
13600
+ await this.appendAssistantTextFromCompletedResponse(
13601
+ conversationId,
13602
+ runId,
13603
+ event
13604
+ );
13283
13605
  await this.importMediaReferencesForEvent(conversationId, runId, event);
13284
13606
  if (!await this.runHasAssistantOutput(conversationId, runId)) {
13285
13607
  await this.failRun(
@@ -13325,7 +13647,7 @@ var ConversationRunLifecycle = class {
13325
13647
  conversationId,
13326
13648
  runId,
13327
13649
  await this.buildEmptyHermesResponseMessage({
13328
- source: "final-empty-response"
13650
+ source: "stream-ended-without-terminal-event"
13329
13651
  })
13330
13652
  );
13331
13653
  }
@@ -13465,6 +13787,31 @@ ${attachmentLines.join("\n")}`
13465
13787
  }
13466
13788
  return messageText(assistant).length > 0 || (assistant.agent_events?.length ?? 0) > 0 || (assistant.approvals?.length ?? 0) > 0 || assistant.parts.some((part) => part.type !== "text");
13467
13789
  }
13790
+ async appendAssistantTextFromCompletedResponse(conversationId, runId, event) {
13791
+ const terminalText = extractResponseAssistantText(event.payload);
13792
+ if (!terminalText?.trim()) {
13793
+ return;
13794
+ }
13795
+ await this.deps.withConversationLock(conversationId, async () => {
13796
+ const snapshot = await this.deps.readSnapshot(conversationId);
13797
+ const run = snapshot.runs.find((item) => item.id === runId);
13798
+ const assistant = snapshot.messages.find(
13799
+ (item) => item.id === run?.assistant_message_id
13800
+ );
13801
+ if (!assistant) {
13802
+ return;
13803
+ }
13804
+ const currentText = assistant.parts.find((part) => part.type === "text")?.text ?? "";
13805
+ const delta = normalizeTerminalResponseTextDelta(
13806
+ currentText,
13807
+ terminalText
13808
+ );
13809
+ if (!delta) {
13810
+ return;
13811
+ }
13812
+ await this.appendAssistantDelta(conversationId, runId, delta, event);
13813
+ });
13814
+ }
13468
13815
  async buildEmptyHermesResponseMessage(input) {
13469
13816
  const runtime = await readCurrentConversationRuntime(this.deps.paths).catch(
13470
13817
  () => null
@@ -13485,6 +13832,14 @@ ${attachmentLines.join("\n")}`
13485
13832
  );
13486
13833
  }
13487
13834
  }
13835
+ if (input?.source === "stream-ended-without-terminal-event") {
13836
+ details.unshift(
13837
+ "Hermes \u7684\u8FD0\u884C\u4E8B\u4EF6\u6D41\u63D0\u524D\u7ED3\u675F\uFF0CLink \u6CA1\u6709\u6536\u5230\u5B8C\u6210\u6216\u5931\u8D25\u4E8B\u4EF6\u3002"
13838
+ );
13839
+ details.push(
13840
+ "\u8FD9\u66F4\u50CF\u662F Gateway \u6216 provider \u7684\u6D41\u5F0F\u4E8B\u4EF6\u4E2D\u65AD\uFF0C\u4E0D\u662F\u6A21\u578B\u660E\u786E\u5B8C\u6210\u4E86\u7A7A\u56DE\u590D\u3002"
13841
+ );
13842
+ }
13488
13843
  return details.length > 0 ? `Hermes \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u5185\u5BB9\u3002
13489
13844
  ${details.join("\n")}` : emptyHermesResponseMessage();
13490
13845
  }
@@ -14073,6 +14428,29 @@ function isVoicePart(part) {
14073
14428
  function normalizeToolName(value) {
14074
14429
  return value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
14075
14430
  }
14431
+ function normalizeTerminalResponseTextDelta(currentText, terminalText) {
14432
+ if (!terminalText.trim()) {
14433
+ return "";
14434
+ }
14435
+ if (!currentText) {
14436
+ return terminalText;
14437
+ }
14438
+ if (terminalText === currentText) {
14439
+ return "";
14440
+ }
14441
+ if (terminalText.startsWith(currentText)) {
14442
+ return terminalText.slice(currentText.length);
14443
+ }
14444
+ const normalizedCurrent = currentText.trim();
14445
+ const normalizedTerminal = terminalText.trim();
14446
+ if (!normalizedCurrent || normalizedTerminal === normalizedCurrent) {
14447
+ return "";
14448
+ }
14449
+ if (normalizedTerminal.startsWith(normalizedCurrent)) {
14450
+ return normalizedTerminal.slice(normalizedCurrent.length);
14451
+ }
14452
+ return "";
14453
+ }
14076
14454
  function appendTextBlock2(message, delta, updatedAt) {
14077
14455
  if (!delta) {
14078
14456
  return;
package/dist/cli/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  startDaemonProcess,
37
37
  startLinkService,
38
38
  stopDaemonProcess
39
- } from "../chunk-PRYXZRQI.js";
39
+ } from "../chunk-XQNMUEOZ.js";
40
40
 
41
41
  // src/cli/index.ts
42
42
  import { Command } from "commander";
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-PRYXZRQI.js";
3
+ } from "../chunk-XQNMUEOZ.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.4.7",
3
+ "version": "0.4.9",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",