@hermespilot/link 0.2.5 → 0.2.6

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.6";
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,7 @@ function upsertAgentEventProjection(events, next) {
7906
7937
  ...previous,
7907
7938
  ...next,
7908
7939
  id: previous.id,
7940
+ title: isGenericToolTitle(next.title) ? previous.title : next.title,
7909
7941
  subtitle: nextSubtitleIsFallback ? previous.subtitle ?? next.subtitle : next.subtitle ?? previous.subtitle,
7910
7942
  detail: next.detail ?? previous.detail,
7911
7943
  completed_at: next.completed_at ?? previous.completed_at
@@ -7914,13 +7946,16 @@ function upsertAgentEventProjection(events, next) {
7914
7946
  copy[index] = merged;
7915
7947
  return copy;
7916
7948
  }
7949
+ function isGenericToolTitle(value) {
7950
+ const normalized = value.trim().toLowerCase().replace(/[\s_-]+/gu, "");
7951
+ return normalized === "tool" || normalized === "toolcall" || value === "\u5DE5\u5177\u8C03\u7528";
7952
+ }
7917
7953
  function isToolStatusFallbackSubtitle(value, title) {
7918
7954
  if (!value) {
7919
7955
  return false;
7920
7956
  }
7921
7957
  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");
7958
+ return normalized.startsWith("\u6B63\u5728\u8C03\u7528 ") || normalized.endsWith(" \u5DF2\u5B8C\u6210") || normalized.endsWith(" \u6267\u884C\u5931\u8D25") || normalized === "tool" || normalized === "running";
7924
7959
  }
7925
7960
  function closeRunningAgentEvents(events, status, completedAt) {
7926
7961
  if (!events?.length) {
@@ -7931,7 +7966,7 @@ function closeRunningAgentEvents(events, status, completedAt) {
7931
7966
  );
7932
7967
  }
7933
7968
  function humanizeToolName(value) {
7934
- const normalized = value.trim().replace(/[_-]+/gu, " ").replace(/\s+/gu, " ");
7969
+ const normalized = value.trim().replace(/([a-z0-9])([A-Z])/gu, "$1 $2").replace(/[_-]+/gu, " ").replace(/\s+/gu, " ");
7935
7970
  if (!normalized) {
7936
7971
  return "\u5DE5\u5177\u8C03\u7528";
7937
7972
  }
@@ -8147,10 +8182,34 @@ var ConversationQueryCoordinator = class {
8147
8182
  isMeaningfulAgentEventProjection
8148
8183
  );
8149
8184
  const agentEvents = mergeAgentEventProjections(fromLog, persisted);
8150
- return agentEvents.length > 0 ? { ...message, agent_events: agentEvents } : message;
8185
+ return agentEvents.length > 0 ? {
8186
+ ...message,
8187
+ agent_events: agentEvents,
8188
+ blocks: hydrateAgentEventBlocks(message.blocks, agentEvents)
8189
+ } : message;
8151
8190
  });
8152
8191
  }
8153
8192
  };
8193
+ function hydrateAgentEventBlocks(blocks, agentEvents) {
8194
+ if (!blocks?.length || agentEvents.length === 0) {
8195
+ return blocks;
8196
+ }
8197
+ const agentEventById = new Map(
8198
+ agentEvents.map((event) => [event.id, event])
8199
+ );
8200
+ return blocks.map((block) => {
8201
+ if (block.type !== "agent_events" || block.events.length === 0) {
8202
+ return block;
8203
+ }
8204
+ return {
8205
+ ...block,
8206
+ events: block.events.map((event) => {
8207
+ const hydrated = agentEventById.get(event.id);
8208
+ return hydrated ? upsertAgentEventProjection([event], hydrated)[0] ?? event : event;
8209
+ })
8210
+ };
8211
+ });
8212
+ }
8154
8213
 
8155
8214
  // src/conversations/conversation-store.ts
8156
8215
  import {
@@ -9583,17 +9642,16 @@ function readChatCompletionUsage(payload) {
9583
9642
  if (input === void 0 && output === void 0 && total === void 0) {
9584
9643
  return void 0;
9585
9644
  }
9586
- const contextTokens = explicitContextTokens ?? (contextWindow !== void 0 && input !== void 0 && input <= contextWindow ? input : void 0);
9587
9645
  return {
9588
9646
  input_tokens: input ?? 0,
9589
9647
  output_tokens: output ?? 0,
9590
9648
  total_tokens: total ?? (input ?? 0) + (output ?? 0),
9591
- ...contextTokens !== void 0 ? { context_tokens: contextTokens } : {},
9649
+ ...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
9592
9650
  ...contextWindow !== void 0 ? { context_window: contextWindow } : {},
9593
- ...contextTokens !== void 0 && contextWindow ? {
9651
+ ...explicitContextTokens !== void 0 && contextWindow ? {
9594
9652
  usage_percent: Math.min(
9595
9653
  100,
9596
- Math.round(contextTokens / contextWindow * 100)
9654
+ Math.round(explicitContextTokens / contextWindow * 100)
9597
9655
  )
9598
9656
  } : {}
9599
9657
  };
@@ -9994,7 +10052,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
9994
10052
  conversationId,
9995
10053
  runId,
9996
10054
  delta,
9997
- event.payload
10055
+ event
9998
10056
  );
9999
10057
  }
10000
10058
  return;
@@ -10049,6 +10107,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10049
10107
  assistant.agent_events ?? [],
10050
10108
  agentEvent
10051
10109
  );
10110
+ appendAgentEventBlock(assistant, agentEvent, event.created_at);
10052
10111
  assistant.updated_at = event.created_at;
10053
10112
  await this.deps.writeSnapshot(conversationId, snapshot);
10054
10113
  }
@@ -10103,7 +10162,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10103
10162
  collectMediaReferences(event.payload)
10104
10163
  );
10105
10164
  }
10106
- async appendAssistantDelta(conversationId, runId, delta, rawPayload) {
10165
+ async appendAssistantDelta(conversationId, runId, delta, event) {
10107
10166
  const snapshot = await this.deps.readSnapshot(conversationId);
10108
10167
  const run = snapshot.runs.find((item) => item.id === runId);
10109
10168
  if (!run) {
@@ -10147,7 +10206,10 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10147
10206
  assistant.parts.push({ type: "text", text: extracted.visibleText });
10148
10207
  }
10149
10208
  assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
10150
- assistant.raw = { format: "hermes-run-event", payload: rawPayload };
10209
+ assistant.raw = { format: "hermes-run-event", payload: event.rawPayload };
10210
+ if (extracted.visibleText) {
10211
+ appendTextBlock(assistant, extracted.visibleText, assistant.updated_at);
10212
+ }
10151
10213
  await this.deps.writeSnapshot(conversationId, snapshot);
10152
10214
  if (extracted.visibleText) {
10153
10215
  await this.deps.appendEvent(conversationId, {
@@ -10155,7 +10217,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10155
10217
  message_id: assistant.id,
10156
10218
  run_id: runId,
10157
10219
  payload: { delta: extracted.visibleText },
10158
- raw: { format: "hermes-run-event", payload: rawPayload }
10220
+ raw: { format: "hermes-run-event", payload: event.rawPayload }
10159
10221
  });
10160
10222
  }
10161
10223
  await this.importMediaReferences(
@@ -10704,18 +10766,83 @@ function isVoicePart(part) {
10704
10766
  function normalizeToolName(value) {
10705
10767
  return value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
10706
10768
  }
10769
+ function appendTextBlock(message, delta, updatedAt) {
10770
+ if (!delta) {
10771
+ return;
10772
+ }
10773
+ const blocks = [...message.blocks ?? []];
10774
+ const last = blocks.at(-1);
10775
+ if (last?.type === "text") {
10776
+ blocks[blocks.length - 1] = {
10777
+ ...last,
10778
+ text: `${last.text}${delta}`,
10779
+ updated_at: updatedAt
10780
+ };
10781
+ } else {
10782
+ blocks.push({
10783
+ id: `text_${blocks.length + 1}`,
10784
+ type: "text",
10785
+ text: delta,
10786
+ created_at: updatedAt,
10787
+ updated_at: updatedAt
10788
+ });
10789
+ }
10790
+ message.blocks = blocks;
10791
+ }
10792
+ function appendAgentEventBlock(message, event, updatedAt) {
10793
+ const blocks = [...message.blocks ?? []];
10794
+ const matchingIndex = blocks.findIndex((block) => {
10795
+ if (block.type !== "agent_events") {
10796
+ return false;
10797
+ }
10798
+ return upsertAgentEventProjection(block.events, event).length === block.events.length;
10799
+ });
10800
+ const targetIndex = matchingIndex >= 0 ? matchingIndex : blocks.at(-1)?.type === "agent_events" ? blocks.length - 1 : -1;
10801
+ if (targetIndex >= 0) {
10802
+ const block = blocks[targetIndex];
10803
+ if (block.type === "agent_events") {
10804
+ blocks[targetIndex] = {
10805
+ ...block,
10806
+ events: upsertAgentEventProjection(block.events, event),
10807
+ updated_at: updatedAt
10808
+ };
10809
+ }
10810
+ } else {
10811
+ blocks.push({
10812
+ id: `tools_${blocks.length + 1}`,
10813
+ type: "agent_events",
10814
+ events: [event],
10815
+ created_at: updatedAt,
10816
+ updated_at: updatedAt
10817
+ });
10818
+ }
10819
+ message.blocks = blocks;
10820
+ }
10707
10821
  function contextUsagePayload(run) {
10708
10822
  const usage = run.usage;
10709
- const contextTokens = usage?.context_tokens;
10710
- if (contextTokens === void 0) {
10823
+ if (!usage) {
10711
10824
  return null;
10712
10825
  }
10826
+ const contextTokens = usage?.context_tokens;
10713
10827
  const contextWindow = usage?.context_window ?? run.context_window;
10828
+ if (contextTokens === void 0) {
10829
+ return {
10830
+ input_tokens: 0,
10831
+ output_tokens: usage.output_tokens ?? 0,
10832
+ total_tokens: usage.total_tokens ?? 0,
10833
+ ...contextWindow ? { context_window: contextWindow } : {},
10834
+ ...contextWindow ? { window_tokens: contextWindow } : {},
10835
+ source: "unknown"
10836
+ };
10837
+ }
10714
10838
  return {
10715
10839
  input_tokens: contextTokens,
10716
10840
  output_tokens: usage?.output_tokens ?? 0,
10717
10841
  total_tokens: usage?.total_tokens ?? contextTokens,
10718
10842
  ...contextWindow ? { context_window: contextWindow } : {},
10843
+ used_tokens: contextTokens,
10844
+ ...contextWindow ? { window_tokens: contextWindow } : {},
10845
+ source: "explicit",
10719
10846
  ...usage?.usage_percent !== void 0 ? { usage_percent: usage.usage_percent } : contextWindow ? {
10720
10847
  usage_percent: Math.min(
10721
10848
  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-72RM6Y4H.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-72RM6Y4H.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.6",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",