@hermespilot/link 0.2.5 → 0.2.7

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.
@@ -3724,7 +3724,7 @@ import os2 from "os";
3724
3724
  import path5 from "path";
3725
3725
 
3726
3726
  // src/constants.ts
3727
- var LINK_VERSION = "0.2.5";
3727
+ var LINK_VERSION = "0.2.7";
3728
3728
  var LINK_COMMAND = "hermeslink";
3729
3729
  var LINK_DEFAULT_PORT = 52379;
3730
3730
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -4941,17 +4941,16 @@ function readUsage(payload) {
4941
4941
  if (!inputTokens && !outputTokens && !totalTokens) {
4942
4942
  return void 0;
4943
4943
  }
4944
- const contextTokens = explicitContextTokens ?? (contextWindow !== void 0 && inputTokens !== void 0 && inputTokens <= contextWindow ? inputTokens : void 0);
4945
4944
  return {
4946
4945
  input_tokens: inputTokens ?? 0,
4947
4946
  output_tokens: outputTokens ?? 0,
4948
4947
  total_tokens: totalTokens,
4949
- ...contextTokens !== void 0 ? { context_tokens: contextTokens } : {},
4948
+ ...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
4950
4949
  ...contextWindow !== void 0 ? { context_window: contextWindow } : {},
4951
- ...contextTokens !== void 0 && contextWindow ? {
4950
+ ...explicitContextTokens !== void 0 && contextWindow ? {
4952
4951
  usage_percent: Math.min(
4953
4952
  100,
4954
- Math.round(contextTokens / contextWindow * 100)
4953
+ Math.round(explicitContextTokens / contextWindow * 100)
4955
4954
  )
4956
4955
  } : {}
4957
4956
  };
@@ -5690,7 +5689,8 @@ async function buildConversationRuntimeMetadata(paths, manifest, snapshot) {
5690
5689
  total_tokens: 0
5691
5690
  };
5692
5691
  const contextWindow = current.contextWindow ?? usage.context_window ?? usageRun?.context_window;
5693
- const contextTokens = usage.context_tokens ?? (contextWindow && usage.input_tokens <= contextWindow ? usage.input_tokens : 0);
5692
+ const contextTokens = usage.context_tokens;
5693
+ const contextSource = contextTokens === void 0 ? "unknown" : "explicit";
5694
5694
  const provider = current.provider ?? usageRun?.provider;
5695
5695
  const reasoningEffort = current.reasoningEffort;
5696
5696
  return {
@@ -5707,11 +5707,14 @@ async function buildConversationRuntimeMetadata(paths, manifest, snapshot) {
5707
5707
  ...reasoningEffort ? { reasoning_effort: reasoningEffort } : {}
5708
5708
  },
5709
5709
  context: {
5710
- input_tokens: contextTokens,
5710
+ input_tokens: contextTokens ?? 0,
5711
5711
  output_tokens: usage.output_tokens,
5712
5712
  total_tokens: usage.total_tokens,
5713
5713
  ...contextWindow ? { context_window: contextWindow } : {},
5714
- ...contextWindow && contextTokens ? {
5714
+ ...contextTokens !== void 0 ? { used_tokens: contextTokens } : {},
5715
+ ...contextWindow ? { window_tokens: contextWindow } : {},
5716
+ source: contextSource,
5717
+ ...contextWindow && contextTokens !== void 0 ? {
5715
5718
  usage_percent: Math.min(
5716
5719
  100,
5717
5720
  usage.usage_percent ?? Math.round(contextTokens / contextWindow * 100)
@@ -6048,12 +6051,15 @@ var ConversationCommandHandlers = class {
6048
6051
  manifest,
6049
6052
  snapshot
6050
6053
  );
6051
- const context = runtime.context.context_window ? ` / ${runtime.context.context_window}` : "";
6052
6054
  return [
6053
6055
  `\u6D88\u606F\u6570\uFF1A${stats.message_count}`,
6054
6056
  `Agent run \u6570\uFF1A${stats.run_count}`,
6055
- `Token\uFF1A${stats.total_tokens}\uFF08\u8F93\u5165 ${stats.input_tokens}\uFF0C\u8F93\u51FA ${stats.output_tokens}\uFF09`,
6056
- `\u4E0A\u4E0B\u6587\uFF1A${runtime.context.input_tokens}${context}`,
6057
+ "",
6058
+ `\u7D2F\u8BA1\u6D88\u8017\uFF1A${stats.total_tokens} tokens`,
6059
+ `\u8F93\u5165\uFF1A${stats.input_tokens}`,
6060
+ `\u8F93\u51FA\uFF1A${stats.output_tokens}`,
6061
+ "",
6062
+ ...formatContextUsageLines(runtime),
6057
6063
  `\u6A21\u578B\uFF1A${runtime.model.id}`,
6058
6064
  `Profile\uFF1A${runtime.profile.name}`
6059
6065
  ].join("\n");
@@ -6276,6 +6282,21 @@ var ConversationCommandHandlers = class {
6276
6282
  }
6277
6283
  }
6278
6284
  };
6285
+ function formatContextUsageLines(runtime) {
6286
+ const windowTokens = runtime.context.window_tokens ?? runtime.context.context_window;
6287
+ if (runtime.context.source === "explicit") {
6288
+ const usedTokens = runtime.context.used_tokens ?? runtime.context.input_tokens;
6289
+ const percent = runtime.context.usage_percent ?? (windowTokens && windowTokens > 0 ? Math.min(100, Math.round(usedTokens / windowTokens * 100)) : void 0);
6290
+ return [
6291
+ `\u4E0A\u4E0B\u6587\uFF1A${usedTokens}${windowTokens ? ` / ${windowTokens}` : ""}${percent === void 0 ? "" : `\uFF08${percent}%\uFF09`}`,
6292
+ "\u6765\u6E90\uFF1A\u6A21\u578B\u8FD4\u56DE"
6293
+ ];
6294
+ }
6295
+ return [
6296
+ `\u4E0A\u4E0B\u6587\uFF1A\u672A\u77E5${windowTokens ? ` / ${windowTokens}` : ""}`,
6297
+ "\u539F\u56E0\uFF1A\u4E0A\u6E38\u672A\u8FD4\u56DE\u5F53\u524D\u4E0A\u4E0B\u6587\u5360\u7528"
6298
+ ];
6299
+ }
6279
6300
 
6280
6301
  // src/conversations/delivery-staging.ts
6281
6302
  import { mkdir as mkdir7, rm as rm4 } from "fs/promises";
@@ -7886,13 +7907,23 @@ function mergeAgentEventProjections(base, overlay) {
7886
7907
  function upsertAgentEventProjection(events, next) {
7887
7908
  let index = events.findIndex((event) => event.id === next.id);
7888
7909
  if (index < 0 && next.status !== "running") {
7910
+ let matchingIndex = -1;
7889
7911
  for (let candidateIndex = events.length - 1; candidateIndex >= 0; candidateIndex -= 1) {
7890
7912
  const candidate = events[candidateIndex];
7891
- if (candidate?.status === "running" && candidate.title === next.title) {
7892
- index = candidateIndex;
7893
- break;
7913
+ if (candidate?.status !== "running") {
7914
+ continue;
7915
+ }
7916
+ if (candidate.title === next.title || isGenericToolTitle(next.title)) {
7917
+ if (matchingIndex !== -1) {
7918
+ matchingIndex = -1;
7919
+ break;
7920
+ }
7921
+ matchingIndex = candidateIndex;
7894
7922
  }
7895
7923
  }
7924
+ if (matchingIndex !== -1) {
7925
+ index = matchingIndex;
7926
+ }
7896
7927
  }
7897
7928
  if (index < 0) {
7898
7929
  return [...events, next];
@@ -7906,6 +7937,8 @@ function upsertAgentEventProjection(events, next) {
7906
7937
  ...previous,
7907
7938
  ...next,
7908
7939
  id: previous.id,
7940
+ title: isGenericToolTitle(next.title) ? previous.title : next.title,
7941
+ created_at: earliestTimestamp(previous.created_at, next.created_at),
7909
7942
  subtitle: nextSubtitleIsFallback ? previous.subtitle ?? next.subtitle : next.subtitle ?? previous.subtitle,
7910
7943
  detail: next.detail ?? previous.detail,
7911
7944
  completed_at: next.completed_at ?? previous.completed_at
@@ -7914,13 +7947,27 @@ function upsertAgentEventProjection(events, next) {
7914
7947
  copy[index] = merged;
7915
7948
  return copy;
7916
7949
  }
7950
+ function earliestTimestamp(left, right) {
7951
+ const leftTime = Date.parse(left);
7952
+ const rightTime = Date.parse(right);
7953
+ if (Number.isNaN(leftTime)) {
7954
+ return right;
7955
+ }
7956
+ if (Number.isNaN(rightTime)) {
7957
+ return left;
7958
+ }
7959
+ return leftTime <= rightTime ? left : right;
7960
+ }
7961
+ function isGenericToolTitle(value) {
7962
+ const normalized = value.trim().toLowerCase().replace(/[\s_-]+/gu, "");
7963
+ return normalized === "tool" || normalized === "toolcall" || value === "\u5DE5\u5177\u8C03\u7528";
7964
+ }
7917
7965
  function isToolStatusFallbackSubtitle(value, title) {
7918
7966
  if (!value) {
7919
7967
  return false;
7920
7968
  }
7921
7969
  const normalized = value.trim().toLowerCase();
7922
- const normalizedTitle = title.trim().toLowerCase();
7923
- return normalized === normalizedTitle || normalized.startsWith("\u6B63\u5728\u8C03\u7528 ") || normalized.endsWith(" \u5DF2\u5B8C\u6210") || normalized.endsWith(" \u6267\u884C\u5931\u8D25");
7970
+ return normalized.startsWith("\u6B63\u5728\u8C03\u7528 ") || normalized.endsWith(" \u5DF2\u5B8C\u6210") || normalized.endsWith(" \u6267\u884C\u5931\u8D25") || normalized === "tool" || normalized === "running";
7924
7971
  }
7925
7972
  function closeRunningAgentEvents(events, status, completedAt) {
7926
7973
  if (!events?.length) {
@@ -7931,7 +7978,7 @@ function closeRunningAgentEvents(events, status, completedAt) {
7931
7978
  );
7932
7979
  }
7933
7980
  function humanizeToolName(value) {
7934
- const normalized = value.trim().replace(/[_-]+/gu, " ").replace(/\s+/gu, " ");
7981
+ const normalized = value.trim().replace(/([a-z0-9])([A-Z])/gu, "$1 $2").replace(/[_-]+/gu, " ").replace(/\s+/gu, " ");
7935
7982
  if (!normalized) {
7936
7983
  return "\u5DE5\u5177\u8C03\u7528";
7937
7984
  }
@@ -8147,10 +8194,34 @@ var ConversationQueryCoordinator = class {
8147
8194
  isMeaningfulAgentEventProjection
8148
8195
  );
8149
8196
  const agentEvents = mergeAgentEventProjections(fromLog, persisted);
8150
- return agentEvents.length > 0 ? { ...message, agent_events: agentEvents } : message;
8197
+ return agentEvents.length > 0 ? {
8198
+ ...message,
8199
+ agent_events: agentEvents,
8200
+ blocks: hydrateAgentEventBlocks(message.blocks, agentEvents)
8201
+ } : message;
8151
8202
  });
8152
8203
  }
8153
8204
  };
8205
+ function hydrateAgentEventBlocks(blocks, agentEvents) {
8206
+ if (!blocks?.length || agentEvents.length === 0) {
8207
+ return blocks;
8208
+ }
8209
+ const agentEventById = new Map(
8210
+ agentEvents.map((event) => [event.id, event])
8211
+ );
8212
+ return blocks.map((block) => {
8213
+ if (block.type !== "agent_events" || block.events.length === 0) {
8214
+ return block;
8215
+ }
8216
+ return {
8217
+ ...block,
8218
+ events: block.events.map((event) => {
8219
+ const hydrated = agentEventById.get(event.id);
8220
+ return hydrated ? upsertAgentEventProjection([event], hydrated)[0] ?? event : event;
8221
+ })
8222
+ };
8223
+ });
8224
+ }
8154
8225
 
8155
8226
  // src/conversations/conversation-store.ts
8156
8227
  import {
@@ -9583,17 +9654,16 @@ function readChatCompletionUsage(payload) {
9583
9654
  if (input === void 0 && output === void 0 && total === void 0) {
9584
9655
  return void 0;
9585
9656
  }
9586
- const contextTokens = explicitContextTokens ?? (contextWindow !== void 0 && input !== void 0 && input <= contextWindow ? input : void 0);
9587
9657
  return {
9588
9658
  input_tokens: input ?? 0,
9589
9659
  output_tokens: output ?? 0,
9590
9660
  total_tokens: total ?? (input ?? 0) + (output ?? 0),
9591
- ...contextTokens !== void 0 ? { context_tokens: contextTokens } : {},
9661
+ ...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
9592
9662
  ...contextWindow !== void 0 ? { context_window: contextWindow } : {},
9593
- ...contextTokens !== void 0 && contextWindow ? {
9663
+ ...explicitContextTokens !== void 0 && contextWindow ? {
9594
9664
  usage_percent: Math.min(
9595
9665
  100,
9596
- Math.round(contextTokens / contextWindow * 100)
9666
+ Math.round(explicitContextTokens / contextWindow * 100)
9597
9667
  )
9598
9668
  } : {}
9599
9669
  };
@@ -9994,7 +10064,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
9994
10064
  conversationId,
9995
10065
  runId,
9996
10066
  delta,
9997
- event.payload
10067
+ event
9998
10068
  );
9999
10069
  }
10000
10070
  return;
@@ -10049,6 +10119,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10049
10119
  assistant.agent_events ?? [],
10050
10120
  agentEvent
10051
10121
  );
10122
+ appendAgentEventBlock(assistant, agentEvent, event.created_at);
10052
10123
  assistant.updated_at = event.created_at;
10053
10124
  await this.deps.writeSnapshot(conversationId, snapshot);
10054
10125
  }
@@ -10103,7 +10174,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10103
10174
  collectMediaReferences(event.payload)
10104
10175
  );
10105
10176
  }
10106
- async appendAssistantDelta(conversationId, runId, delta, rawPayload) {
10177
+ async appendAssistantDelta(conversationId, runId, delta, event) {
10107
10178
  const snapshot = await this.deps.readSnapshot(conversationId);
10108
10179
  const run = snapshot.runs.find((item) => item.id === runId);
10109
10180
  if (!run) {
@@ -10147,7 +10218,10 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10147
10218
  assistant.parts.push({ type: "text", text: extracted.visibleText });
10148
10219
  }
10149
10220
  assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
10150
- assistant.raw = { format: "hermes-run-event", payload: rawPayload };
10221
+ assistant.raw = { format: "hermes-run-event", payload: event.rawPayload };
10222
+ if (extracted.visibleText) {
10223
+ appendTextBlock(assistant, extracted.visibleText, assistant.updated_at);
10224
+ }
10151
10225
  await this.deps.writeSnapshot(conversationId, snapshot);
10152
10226
  if (extracted.visibleText) {
10153
10227
  await this.deps.appendEvent(conversationId, {
@@ -10155,7 +10229,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10155
10229
  message_id: assistant.id,
10156
10230
  run_id: runId,
10157
10231
  payload: { delta: extracted.visibleText },
10158
- raw: { format: "hermes-run-event", payload: rawPayload }
10232
+ raw: { format: "hermes-run-event", payload: event.rawPayload }
10159
10233
  });
10160
10234
  }
10161
10235
  await this.importMediaReferences(
@@ -10704,18 +10778,83 @@ function isVoicePart(part) {
10704
10778
  function normalizeToolName(value) {
10705
10779
  return value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
10706
10780
  }
10781
+ function appendTextBlock(message, delta, updatedAt) {
10782
+ if (!delta) {
10783
+ return;
10784
+ }
10785
+ const blocks = [...message.blocks ?? []];
10786
+ const last = blocks.at(-1);
10787
+ if (last?.type === "text") {
10788
+ blocks[blocks.length - 1] = {
10789
+ ...last,
10790
+ text: `${last.text}${delta}`,
10791
+ updated_at: updatedAt
10792
+ };
10793
+ } else {
10794
+ blocks.push({
10795
+ id: `text_${blocks.length + 1}`,
10796
+ type: "text",
10797
+ text: delta,
10798
+ created_at: updatedAt,
10799
+ updated_at: updatedAt
10800
+ });
10801
+ }
10802
+ message.blocks = blocks;
10803
+ }
10804
+ function appendAgentEventBlock(message, event, updatedAt) {
10805
+ const blocks = [...message.blocks ?? []];
10806
+ const matchingIndex = blocks.findIndex((block) => {
10807
+ if (block.type !== "agent_events") {
10808
+ return false;
10809
+ }
10810
+ return upsertAgentEventProjection(block.events, event).length === block.events.length;
10811
+ });
10812
+ const targetIndex = matchingIndex >= 0 ? matchingIndex : blocks.at(-1)?.type === "agent_events" ? blocks.length - 1 : -1;
10813
+ if (targetIndex >= 0) {
10814
+ const block = blocks[targetIndex];
10815
+ if (block.type === "agent_events") {
10816
+ blocks[targetIndex] = {
10817
+ ...block,
10818
+ events: upsertAgentEventProjection(block.events, event),
10819
+ updated_at: updatedAt
10820
+ };
10821
+ }
10822
+ } else {
10823
+ blocks.push({
10824
+ id: `tools_${blocks.length + 1}`,
10825
+ type: "agent_events",
10826
+ events: [event],
10827
+ created_at: updatedAt,
10828
+ updated_at: updatedAt
10829
+ });
10830
+ }
10831
+ message.blocks = blocks;
10832
+ }
10707
10833
  function contextUsagePayload(run) {
10708
10834
  const usage = run.usage;
10709
- const contextTokens = usage?.context_tokens;
10710
- if (contextTokens === void 0) {
10835
+ if (!usage) {
10711
10836
  return null;
10712
10837
  }
10838
+ const contextTokens = usage?.context_tokens;
10713
10839
  const contextWindow = usage?.context_window ?? run.context_window;
10840
+ if (contextTokens === void 0) {
10841
+ return {
10842
+ input_tokens: 0,
10843
+ output_tokens: usage.output_tokens ?? 0,
10844
+ total_tokens: usage.total_tokens ?? 0,
10845
+ ...contextWindow ? { context_window: contextWindow } : {},
10846
+ ...contextWindow ? { window_tokens: contextWindow } : {},
10847
+ source: "unknown"
10848
+ };
10849
+ }
10714
10850
  return {
10715
10851
  input_tokens: contextTokens,
10716
10852
  output_tokens: usage?.output_tokens ?? 0,
10717
10853
  total_tokens: usage?.total_tokens ?? contextTokens,
10718
10854
  ...contextWindow ? { context_window: contextWindow } : {},
10855
+ used_tokens: contextTokens,
10856
+ ...contextWindow ? { window_tokens: contextWindow } : {},
10857
+ source: "explicit",
10719
10858
  ...usage?.usage_percent !== void 0 ? { usage_percent: usage.usage_percent } : contextWindow ? {
10720
10859
  usage_percent: Math.min(
10721
10860
  100,
package/dist/cli/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  startDaemonProcess,
27
27
  startLinkService,
28
28
  stopDaemonProcess
29
- } from "../chunk-N4CZ2ASZ.js";
29
+ } from "../chunk-SSJ7WDD7.js";
30
30
 
31
31
  // src/cli/index.ts
32
32
  import { Command } from "commander";
@@ -115,7 +115,10 @@ interface ConversationRuntimeMetadata {
115
115
  output_tokens: number;
116
116
  total_tokens: number;
117
117
  context_window?: number;
118
+ used_tokens?: number;
119
+ window_tokens?: number;
118
120
  usage_percent?: number;
121
+ source: 'explicit' | 'unknown';
119
122
  updated_at?: string;
120
123
  };
121
124
  }
@@ -142,17 +145,33 @@ interface LinkMessagePart {
142
145
  }
143
146
  interface LinkMessageAgentEvent {
144
147
  id: string;
148
+ kind?: 'tool' | 'thinking_delta';
145
149
  title: string;
146
150
  status: 'running' | 'completed' | 'failed' | 'info';
147
151
  created_at: string;
148
152
  subtitle?: string;
149
153
  detail?: string;
154
+ text?: string;
155
+ phase?: 'thinking' | 'final';
150
156
  completed_at?: string;
151
157
  raw?: {
152
158
  format: string;
153
159
  payload: unknown;
154
160
  };
155
161
  }
162
+ type LinkMessageBlock = {
163
+ id: string;
164
+ type: 'text';
165
+ text: string;
166
+ created_at: string;
167
+ updated_at?: string;
168
+ } | {
169
+ id: string;
170
+ type: 'agent_events';
171
+ events: LinkMessageAgentEvent[];
172
+ created_at: string;
173
+ updated_at?: string;
174
+ };
156
175
  type LinkApprovalDecisionScope = 'once' | 'session' | 'always';
157
176
  type LinkApprovalDecision = LinkApprovalDecisionScope | 'deny';
158
177
  type LinkApprovalStatus = 'pending' | 'approved' | 'denied' | 'expired';
@@ -193,6 +212,7 @@ interface LinkMessage {
193
212
  };
194
213
  parts: LinkMessagePart[];
195
214
  attachments: unknown[];
215
+ blocks?: LinkMessageBlock[];
196
216
  agent_events?: LinkMessageAgentEvent[];
197
217
  approvals?: LinkApprovalRequest[];
198
218
  hermes?: Record<string, unknown>;
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-N4CZ2ASZ.js";
3
+ } from "../chunk-SSJ7WDD7.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.2.5",
3
+ "version": "0.2.7",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",