@hermespilot/link 0.8.1-beta.1 → 0.8.1-beta.2

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.
@@ -5,7 +5,7 @@ import Router from "@koa/router";
5
5
  // src/conversations/conversation-service.ts
6
6
  import { EventEmitter } from "events";
7
7
  import { createHash as createHash8, randomUUID as randomUUID12 } from "crypto";
8
- import path26 from "path";
8
+ import path27 from "path";
9
9
 
10
10
  // src/database/link-database.ts
11
11
  import { mkdir } from "fs/promises";
@@ -207,6 +207,15 @@ var MIGRATIONS = [
207
207
  ALTER TABLE live_activities ADD COLUMN last_detail_text TEXT;
208
208
  ALTER TABLE live_activities ADD COLUMN last_visible_updated_at TEXT;
209
209
  `
210
+ },
211
+ {
212
+ version: 6,
213
+ name: "live_activity_todo_progress",
214
+ sql: `
215
+ ALTER TABLE live_activities ADD COLUMN last_todo_done_count INTEGER;
216
+ ALTER TABLE live_activities ADD COLUMN last_todo_total_count INTEGER;
217
+ ALTER TABLE live_activities ADD COLUMN last_todo_progress_text TEXT;
218
+ `
210
219
  }
211
220
  ];
212
221
  async function migrateLinkDatabase(paths) {
@@ -335,13 +344,16 @@ async function upsertLiveActivity(paths, input) {
335
344
  last_status_label,
336
345
  last_progress_text,
337
346
  last_detail_text,
347
+ last_todo_done_count,
348
+ last_todo_total_count,
349
+ last_todo_progress_text,
338
350
  last_visible_updated_at,
339
351
  push_count,
340
352
  started_at,
341
353
  ended_at,
342
354
  created_at,
343
355
  updated_at
344
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, NULL, NULL, ?, ?, ?, ?, 0, ?, NULL, ?, ?)
356
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, NULL, NULL, ?, ?, ?, ?, ?, ?, ?, 0, ?, NULL, ?, ?)
345
357
  ON CONFLICT(activity_id) DO UPDATE SET
346
358
  activity_token = excluded.activity_token,
347
359
  apns_environment = excluded.apns_environment,
@@ -364,6 +376,9 @@ async function upsertLiveActivity(paths, input) {
364
376
  last_status_label = excluded.last_status_label,
365
377
  last_progress_text = excluded.last_progress_text,
366
378
  last_detail_text = excluded.last_detail_text,
379
+ last_todo_done_count = excluded.last_todo_done_count,
380
+ last_todo_total_count = excluded.last_todo_total_count,
381
+ last_todo_progress_text = excluded.last_todo_progress_text,
367
382
  last_visible_updated_at = excluded.last_visible_updated_at,
368
383
  push_count = 0,
369
384
  started_at = excluded.started_at,
@@ -390,6 +405,9 @@ async function upsertLiveActivity(paths, input) {
390
405
  restoreState?.statusLabel ?? null,
391
406
  restoreState?.progressText ?? null,
392
407
  restoreState?.detailText ?? null,
408
+ restoreState?.todoDoneCount ?? null,
409
+ restoreState?.todoTotalCount ?? null,
410
+ restoreState?.todoProgressText ?? null,
393
411
  restoreState?.updatedAt ?? null,
394
412
  input.startedAt,
395
413
  now,
@@ -481,6 +499,9 @@ async function updateLiveActivityPushState(paths, input) {
481
499
  last_status_label = ?,
482
500
  last_progress_text = ?,
483
501
  last_detail_text = ?,
502
+ last_todo_done_count = ?,
503
+ last_todo_total_count = ?,
504
+ last_todo_progress_text = ?,
484
505
  last_visible_updated_at = ?,
485
506
  push_count = push_count + 1,
486
507
  status = CASE WHEN ? THEN 'ended' ELSE status END,
@@ -495,6 +516,9 @@ async function updateLiveActivityPushState(paths, input) {
495
516
  normalizeNullableText(input.statusLabel),
496
517
  normalizeNullableText(input.progressText),
497
518
  normalizeNullableText(input.detailText),
519
+ normalizeNullableCount(input.todoDoneCount),
520
+ normalizeNullableCount(input.todoTotalCount),
521
+ normalizeNullableText(input.todoProgressText),
498
522
  input.visibleUpdatedAt,
499
523
  input.terminal ? 1 : 0,
500
524
  input.terminal ? 1 : 0,
@@ -1343,6 +1367,9 @@ function liveActivityFromRow(row) {
1343
1367
  lastStatusLabel: readNullableString(row, "last_status_label"),
1344
1368
  lastProgressText: readNullableString(row, "last_progress_text"),
1345
1369
  lastDetailText: readNullableString(row, "last_detail_text"),
1370
+ lastTodoDoneCount: readNullableNumber(row, "last_todo_done_count"),
1371
+ lastTodoTotalCount: readNullableNumber(row, "last_todo_total_count"),
1372
+ lastTodoProgressText: readNullableString(row, "last_todo_progress_text"),
1346
1373
  lastVisibleUpdatedAt: readNullableString(row, "last_visible_updated_at"),
1347
1374
  pushCount: readNumber(row, "push_count"),
1348
1375
  startedAt,
@@ -1358,14 +1385,38 @@ function liveActivityRestoreStateFromRow(row) {
1358
1385
  if (!phase || !statusLabel || !progressText) {
1359
1386
  return null;
1360
1387
  }
1361
- return {
1388
+ return sanitizeLiveActivityRestoreState({
1362
1389
  phase,
1363
1390
  statusLabel,
1364
1391
  progressText,
1365
1392
  detailText: readNullableString(row, "last_detail_text"),
1393
+ todoDoneCount: readNullableNumber(row, "last_todo_done_count"),
1394
+ todoTotalCount: readNullableNumber(row, "last_todo_total_count"),
1395
+ todoProgressText: readNullableString(row, "last_todo_progress_text"),
1366
1396
  updatedAt: readNullableString(row, "last_visible_updated_at") ?? readNullableString(row, "updated_at")
1397
+ });
1398
+ }
1399
+ function sanitizeLiveActivityRestoreState(state) {
1400
+ if (!isCompletedTodoProgress(state.todoDoneCount, state.todoTotalCount)) {
1401
+ return state;
1402
+ }
1403
+ return {
1404
+ ...state,
1405
+ todoDoneCount: null,
1406
+ todoTotalCount: null,
1407
+ todoProgressText: null
1367
1408
  };
1368
1409
  }
1410
+ function isCompletedTodoProgress(doneCount, totalCount) {
1411
+ if (typeof doneCount !== "number" || !Number.isFinite(doneCount) || typeof totalCount !== "number" || !Number.isFinite(totalCount)) {
1412
+ return false;
1413
+ }
1414
+ const normalizedTotal = Math.max(0, Math.trunc(totalCount));
1415
+ if (normalizedTotal <= 0) {
1416
+ return false;
1417
+ }
1418
+ return Math.max(0, Math.trunc(doneCount)) >= normalizedTotal;
1419
+ }
1369
1420
  function isLiveActivityStatus(value) {
1370
1421
  return value === "active" || value === "ended" || value === "expired" || value === "invalid";
1371
1422
  }
@@ -1373,6 +1424,12 @@ function normalizeNullableText(value) {
1373
1424
  const trimmed = value?.trim();
1374
1425
  return trimmed ? trimmed : null;
1375
1426
  }
1427
+ function normalizeNullableCount(value) {
1428
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1429
+ return null;
1430
+ }
1431
+ return Math.max(0, Math.trunc(value));
1432
+ }
1376
1433
  function rollback(db) {
1377
1434
  try {
1378
1435
  db.exec("ROLLBACK");
@@ -1393,6 +1450,20 @@ function readNumber(row, key) {
1393
1450
  }
1394
1451
  return 0;
1395
1452
  }
1453
+ function readNullableNumber(row, key) {
1454
+ const value = row?.[key];
1455
+ if (typeof value === "number" && Number.isFinite(value)) {
1456
+ return Math.trunc(value);
1457
+ }
1458
+ if (typeof value === "bigint") {
1459
+ return Number(value);
1460
+ }
1461
+ if (typeof value === "string" && value.trim()) {
1462
+ const parsed = Number(value);
1463
+ return Number.isFinite(parsed) ? Math.trunc(parsed) : null;
1464
+ }
1465
+ return null;
1466
+ }
1396
1467
  function readString(row, key) {
1397
1468
  const value = row?.[key];
1398
1469
  return typeof value === "string" && value.trim() ? value : void 0;
@@ -2600,6 +2671,7 @@ var AUTH_BACKED_MODEL_PROVIDERS = [
2600
2671
  var AUTH_BACKED_MODEL_PROVIDER_BY_KEY = new Map(
2601
2672
  AUTH_BACKED_MODEL_PROVIDERS.map((provider) => [provider.provider, provider])
2602
2673
  );
2674
+ var RESERVED_PROVIDER_CONFIG_KEYS = /* @__PURE__ */ new Set(["auto", "main"]);
2603
2675
  function isAuthBackedModelProviderKey(provider) {
2604
2676
  return AUTH_BACKED_MODEL_PROVIDER_BY_KEY.has(provider.trim());
2605
2677
  }
@@ -2859,6 +2931,7 @@ async function readHermesModelConfig(profileName = "default", configPath = resol
2859
2931
  }
2860
2932
  async function listHermesModelConfigs(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
2861
2933
  const { config } = await readHermesConfigDocument(configPath);
2934
+ ensureProvidersRecordWithLegacyMigration(config);
2862
2935
  const env = await readHermesEnvFile(profileName);
2863
2936
  const defaultModel = readModelConfig(config.model).model ?? null;
2864
2937
  const defaultReasoningEffort = readProfileReasoningEffort(config);
@@ -3556,6 +3629,7 @@ async function deleteHermesModelConfig(input, profileName = "default", configPat
3556
3629
  throw new Error("model id is required");
3557
3630
  }
3558
3631
  const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
3632
+ ensureProvidersRecordWithLegacyMigration(config);
3559
3633
  const env = await readHermesEnvFile(profileName);
3560
3634
  const authBackedProviders = await readHermesAuthBackedProviderState(profileName);
3561
3635
  const existingModels = readManagedModelConfigs(
@@ -3669,6 +3743,7 @@ async function saveHermesModelDefaults(input, profileName = "default", configPat
3669
3743
  );
3670
3744
  }
3671
3745
  const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
3746
+ ensureProvidersRecordWithLegacyMigration(config);
3672
3747
  const env = await readHermesEnvFile(profileName);
3673
3748
  const authBackedProviders = await readHermesAuthBackedProviderState(profileName);
3674
3749
  if (taskModelId) {
@@ -4352,10 +4427,32 @@ function ensureProvidersRecordWithLegacyMigration(config) {
4352
4427
  providers[providerKey] = providers[providerKey] ? mergeProviderConfig(toRecord(providers[providerKey]), nextConfig) : nextConfig;
4353
4428
  }
4354
4429
  delete config.custom_providers;
4355
- const providerAliases = mergeDuplicateProviderConfigs(providers);
4430
+ const providerAliases = renameReservedProviderConfigs(providers);
4431
+ mergeProviderAliases(providerAliases, mergeDuplicateProviderConfigs(providers));
4356
4432
  rewriteProviderReferences(config, providerAliases);
4357
4433
  return providers;
4358
4434
  }
4435
+ function renameReservedProviderConfigs(providers) {
4436
+ const aliases = /* @__PURE__ */ new Map();
4437
+ for (const key of Object.keys(providers)) {
4438
+ if (!isReservedProviderConfigKey(key)) {
4439
+ continue;
4440
+ }
4441
+ const nextKey = uniqueProviderConfigKey(
4442
+ `${slugifyProviderKey(key)}-provider`,
4443
+ providers
4444
+ );
4445
+ providers[nextKey] = providers[key];
4446
+ delete providers[key];
4447
+ aliases.set(key, nextKey);
4448
+ }
4449
+ return aliases;
4450
+ }
4451
+ function mergeProviderAliases(aliases, nextAliases) {
4452
+ for (const [from, to] of nextAliases) {
4453
+ aliases.set(from, to);
4454
+ }
4455
+ }
4359
4456
  function mergeDuplicateProviderConfigs(providers) {
4360
4457
  const groupKeys = /* @__PURE__ */ new Map();
4361
4458
  const aliases = /* @__PURE__ */ new Map();
@@ -4545,14 +4642,18 @@ function providerConfigKeyBase(provider, providerName, baseUrl) {
4545
4642
  return slugifyProviderKey(fromProvider || fromName || host || "provider");
4546
4643
  }
4547
4644
  function uniqueProviderConfigKey(base, existing) {
4548
- let key = base || "provider";
4645
+ const normalizedBase = isReservedProviderConfigKey(base) ? `${base}-provider` : base;
4646
+ let key = normalizedBase || "provider";
4549
4647
  let index = 2;
4550
- while (Object.prototype.hasOwnProperty.call(existing, key)) {
4551
- key = `${base}-${index}`;
4648
+ while (isReservedProviderConfigKey(key) || Object.prototype.hasOwnProperty.call(existing, key)) {
4649
+ key = `${normalizedBase}-${index}`;
4552
4650
  index += 1;
4553
4651
  }
4554
4652
  return key;
4555
4653
  }
4654
+ function isReservedProviderConfigKey(key) {
4655
+ return RESERVED_PROVIDER_CONFIG_KEYS.has(key.trim().toLowerCase());
4656
+ }
4556
4657
  function slugifyProviderKey(value) {
4557
4658
  const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "");
4558
4659
  return slug || "provider";
@@ -7524,7 +7625,7 @@ async function listCronOutputFiles(profileName, jobId) {
7524
7625
  orderTimeMs: fileStat.mtimeMs
7525
7626
  });
7526
7627
  }
7527
- return files.sort((left, right) => left.orderTimeMs - right.orderTimeMs).map(({ path: path37, mtime }) => ({ path: path37, mtime }));
7628
+ return files.sort((left, right) => left.orderTimeMs - right.orderTimeMs).map(({ path: path38, mtime }) => ({ path: path38, mtime }));
7528
7629
  }
7529
7630
  function readCronOutputTimestamp(fileName) {
7530
7631
  const match = fileName.match(
@@ -7613,7 +7714,7 @@ function isConversationMissingError(error) {
7613
7714
  }
7614
7715
 
7615
7716
  // src/constants.ts
7616
- var LINK_VERSION = "0.8.1-beta.1";
7717
+ var LINK_VERSION = "0.8.1-beta.2";
7617
7718
  var LINK_COMMAND = "hermeslink";
7618
7719
  var LINK_DEFAULT_PORT = 52379;
7619
7720
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -7962,7 +8063,9 @@ async function reportLiveActivityEventToServerNow(options) {
7962
8063
  const event = liveActivityEventWithInheritedDetail(activity, options.event);
7963
8064
  const occurredAt = normalizeIso2(options.event.occurredAt) ?? (/* @__PURE__ */ new Date()).toISOString();
7964
8065
  const summaryHash = hashLiveActivitySummary(event);
7965
- const skipReason = resolveSkipReason(activity, event, summaryHash);
8066
+ const skipReason = resolveSkipReason(activity, event, summaryHash, {
8067
+ hasExplicitTodoProgress: hasLiveActivityTodoProgress(options.event)
8068
+ });
7966
8069
  if (skipReason) {
7967
8070
  await options.logger.debug("live_activity_event_skipped", {
7968
8071
  activity_id: activity.activityId,
@@ -8001,6 +8104,9 @@ async function reportLiveActivityEventToServerNow(options) {
8001
8104
  addOptional2(payload, "status_label", event.statusLabel, 24);
8002
8105
  addOptional2(payload, "progress_text", event.progressText, 120);
8003
8106
  addOptional2(payload, "detail_text", event.detailText, 240);
8107
+ addOptionalCount(payload, "todo_done_count", event.todoDoneCount);
8108
+ addOptionalCount(payload, "todo_total_count", event.todoTotalCount);
8109
+ addOptional2(payload, "todo_progress_text", event.todoProgressText, 16);
8004
8110
  addOptional2(payload, "language", activity.language, 16);
8005
8111
  addOptional2(payload, "privacy_level", activity.privacyLevel, 32);
8006
8112
  if (typeof event.requiresUserAction === "boolean") {
@@ -8037,7 +8143,10 @@ async function reportLiveActivityEventToServerNow(options) {
8037
8143
  terminal: event.terminal === true,
8038
8144
  statusLabel: event.statusLabel,
8039
8145
  progressText: event.progressText,
8040
- detailText: event.detailText
8146
+ detailText: event.detailText,
8147
+ todoDoneCount: event.todoDoneCount,
8148
+ todoTotalCount: event.todoTotalCount,
8149
+ todoProgressText: event.todoProgressText
8041
8150
  });
8042
8151
  const apnsReason = readApnsReason(body);
8043
8152
  if (apnsReason && shouldInvalidateLiveActivity(apnsReason)) {
@@ -8052,17 +8161,39 @@ function liveActivityEventWithInheritedDetail(activity, event) {
8052
8161
  phase,
8053
8162
  statusLabel: activity.lastStatusLabel?.trim() || event.statusLabel,
8054
8163
  progressText: activity.lastProgressText?.trim() || event.progressText,
8055
- detailText: activity.lastDetailText?.trim() || event.detailText
8164
+ detailText: activity.lastDetailText?.trim() || event.detailText,
8165
+ ...inheritedTodoProgress(activity, event)
8056
8166
  };
8057
8167
  }
8168
+ const eventWithTodoProgress = {
8169
+ ...event,
8170
+ ...inheritedTodoProgress(activity, event)
8171
+ };
8058
8172
  if (event.detailText?.trim() || !isToolPhase(event.phase)) {
8059
- return event;
8173
+ return eventWithTodoProgress;
8060
8174
  }
8061
8175
  const detailText = activity.lastDetailText?.trim();
8062
- return detailText ? { ...event, detailText } : event;
8176
+ return detailText ? { ...eventWithTodoProgress, detailText } : eventWithTodoProgress;
8177
+ }
8178
+ function inheritedTodoProgress(activity, event) {
8179
+ if (typeof event.todoDoneCount === "number" || typeof event.todoTotalCount === "number") {
8180
+ return {
8181
+ todoDoneCount: event.todoDoneCount,
8182
+ todoTotalCount: event.todoTotalCount,
8183
+ todoProgressText: event.todoProgressText
8184
+ };
8185
+ }
8186
+ if (typeof activity.lastTodoDoneCount === "number" && typeof activity.lastTodoTotalCount === "number") {
8187
+ return {
8188
+ todoDoneCount: activity.lastTodoDoneCount,
8189
+ todoTotalCount: activity.lastTodoTotalCount,
8190
+ todoProgressText: activity.lastTodoProgressText
8191
+ };
8192
+ }
8193
+ return {};
8063
8194
  }
8064
- function resolveSkipReason(activity, event, summaryHash) {
8065
- if (shouldThrottleLiveActivityEvent(activity, event)) {
8195
+ function resolveSkipReason(activity, event, summaryHash, options) {
8196
+ if (shouldThrottleLiveActivityEvent(activity, event, options)) {
8066
8197
  return "push_interval_throttled";
8067
8198
  }
8068
8199
  if (activity.pushCount >= 60 && event.terminal !== true) {
@@ -8076,10 +8207,13 @@ function resolveSkipReason(activity, event, summaryHash) {
8076
8207
  }
8077
8208
  return null;
8078
8209
  }
8079
- function shouldThrottleLiveActivityEvent(activity, event) {
8210
+ function shouldThrottleLiveActivityEvent(activity, event, options) {
8080
8211
  if (event.terminal === true || event.requiresUserAction === true || event.phase === "needs_input" || event.phase === "needs_approval") {
8081
8212
  return false;
8082
8213
  }
8214
+ if (options?.hasExplicitTodoProgress === true) {
8215
+ return false;
8216
+ }
8083
8217
  if (isLiveActivityTitleUpdateEvent(event)) {
8084
8218
  return false;
8085
8219
  }
@@ -8121,6 +8255,9 @@ function isLiveActivityTitleUpdateEvent(event) {
8121
8255
  function isToolPhase(phase) {
8122
8256
  return phase === "using_tool" || phase === "tool_completed";
8123
8257
  }
8258
+ function hasLiveActivityTodoProgress(event) {
8259
+ return typeof event.todoDoneCount === "number" && Number.isFinite(event.todoDoneCount) && typeof event.todoTotalCount === "number" && Number.isFinite(event.todoTotalCount);
8260
+ }
8124
8261
  function isLiveActivityPhase(value) {
8125
8262
  return value === "connecting" || value === "accepted" || value === "running" || value === "using_tool" || value === "tool_completed" || value === "needs_input" || value === "needs_approval" || value === "goal_running" || value === "goal_paused" || value === "context_compressing" || value === "context_compressed" || value === "context_compression_failed" || value === "context_compression_timed_out" || value === "paused" || value === "recovering" || value === "completed" || value === "failed" || value === "cancelled";
8126
8263
  }
@@ -8132,10 +8269,19 @@ function hashLiveActivitySummary(event) {
8132
8269
  event.statusLabel?.trim() ?? "",
8133
8270
  event.progressText?.trim() ?? "",
8134
8271
  event.detailText?.trim() ?? "",
8272
+ event.todoDoneCount === void 0 || event.todoDoneCount === null ? "" : String(event.todoDoneCount),
8273
+ event.todoTotalCount === void 0 || event.todoTotalCount === null ? "" : String(event.todoTotalCount),
8274
+ event.todoProgressText?.trim() ?? "",
8135
8275
  event.terminal === true ? "terminal" : "update"
8136
8276
  ].join("|")
8137
8277
  ).digest("hex");
8138
8278
  }
8279
+ function addOptionalCount(payload, key, value) {
8280
+ if (typeof value !== "number" || !Number.isFinite(value)) {
8281
+ return;
8282
+ }
8283
+ payload[key] = Math.max(0, Math.trunc(value));
8284
+ }
8139
8285
  function addOptional2(payload, key, value, maxLength) {
8140
8286
  const normalized = value?.trim();
8141
8287
  if (normalized) {
@@ -10169,8 +10315,8 @@ import { spawn as spawn2 } from "child_process";
10169
10315
  import { randomBytes as randomBytes2 } from "crypto";
10170
10316
  import { readFile as readFile6, stat as stat5 } from "fs/promises";
10171
10317
  import net2 from "net";
10172
- import path9 from "path";
10173
- import { setTimeout as delay3 } from "timers/promises";
10318
+ import path10 from "path";
10319
+ import { setTimeout as delay4 } from "timers/promises";
10174
10320
  import WebSocket from "ws";
10175
10321
 
10176
10322
  // src/conversations/goal-state.ts
@@ -10319,6 +10465,227 @@ function normalizeGoalStatus(value) {
10319
10465
  }
10320
10466
  }
10321
10467
 
10468
+ // src/hermes/tui-gateway-process-registry.ts
10469
+ import { execFile as execFile3 } from "child_process";
10470
+ import path9 from "path";
10471
+ import { promisify as promisify3 } from "util";
10472
+ var execFileAsync3 = promisify3(execFile3);
10473
+ var REGISTRY_VERSION2 = 1;
10474
+ var TERMINATE_GRACE_MS = 5e3;
10475
+ function tuiGatewayProcessRegistryPath(paths) {
10476
+ return path9.join(paths.runDir, "tui-gateway-processes.json");
10477
+ }
10478
+ async function readRegisteredTuiGatewayProcesses(paths) {
10479
+ return normalizeRegistry(
10480
+ await readJsonFile(
10481
+ tuiGatewayProcessRegistryPath(paths)
10482
+ )
10483
+ ).processes;
10484
+ }
10485
+ async function registerTuiGatewayProcess(paths, processRecord) {
10486
+ await updateJsonFile(
10487
+ tuiGatewayProcessRegistryPath(paths),
10488
+ (current) => {
10489
+ const registry = normalizeRegistry(current);
10490
+ return {
10491
+ version: REGISTRY_VERSION2,
10492
+ processes: [
10493
+ ...registry.processes.filter(
10494
+ (item) => item.profile !== processRecord.profile && item.pid !== processRecord.pid
10495
+ ),
10496
+ processRecord
10497
+ ]
10498
+ };
10499
+ }
10500
+ );
10501
+ }
10502
+ async function unregisterTuiGatewayProcess(paths, input) {
10503
+ await updateJsonFile(
10504
+ tuiGatewayProcessRegistryPath(paths),
10505
+ (current) => {
10506
+ const registry = normalizeRegistry(current);
10507
+ return {
10508
+ version: REGISTRY_VERSION2,
10509
+ processes: registry.processes.filter((item) => {
10510
+ if (input.pid !== void 0) {
10511
+ return item.pid !== input.pid;
10512
+ }
10513
+ if (input.profile !== void 0) {
10514
+ return item.profile !== input.profile;
10515
+ }
10516
+ return true;
10517
+ })
10518
+ };
10519
+ }
10520
+ );
10521
+ }
10522
+ async function cleanupRegisteredTuiGatewayProcesses(input) {
10523
+ const excludePids = new Set(
10524
+ [...input.excludePids ?? []].filter(
10525
+ (pid) => typeof pid === "number" && Number.isInteger(pid) && pid > 0
10526
+ )
10527
+ );
10528
+ const processes = await readRegisteredTuiGatewayProcesses(input.paths);
10529
+ const kept = [];
10530
+ const result = {
10531
+ stopped: [],
10532
+ missing: [],
10533
+ skipped: []
10534
+ };
10535
+ for (const processRecord of processes) {
10536
+ if (excludePids.has(processRecord.pid)) {
10537
+ kept.push(processRecord);
10538
+ result.skipped.push({ process: processRecord, reason: "active_backend" });
10539
+ continue;
10540
+ }
10541
+ const state = await inspectRegisteredProcess(processRecord);
10542
+ if (!state.alive) {
10543
+ result.missing.push(processRecord);
10544
+ continue;
10545
+ }
10546
+ if (!state.matches) {
10547
+ kept.push(processRecord);
10548
+ result.skipped.push({
10549
+ process: processRecord,
10550
+ reason: state.reason ?? "command_mismatch"
10551
+ });
10552
+ void input.logger?.warn("tui_gateway_registry_cleanup_skipped", {
10553
+ pid: processRecord.pid,
10554
+ profile: processRecord.profile,
10555
+ port: processRecord.port,
10556
+ reason: state.reason ?? "command_mismatch",
10557
+ command_line: state.commandLine ?? null
10558
+ });
10559
+ continue;
10560
+ }
10561
+ const stopped = await terminatePid(processRecord.pid);
10562
+ if (stopped) {
10563
+ result.stopped.push(processRecord);
10564
+ void input.logger?.warn("tui_gateway_registered_process_stopped", {
10565
+ pid: processRecord.pid,
10566
+ profile: processRecord.profile,
10567
+ port: processRecord.port
10568
+ });
10569
+ } else {
10570
+ kept.push(processRecord);
10571
+ result.skipped.push({ process: processRecord, reason: "terminate_failed" });
10572
+ void input.logger?.warn("tui_gateway_registered_process_stop_failed", {
10573
+ pid: processRecord.pid,
10574
+ profile: processRecord.profile,
10575
+ port: processRecord.port
10576
+ });
10577
+ }
10578
+ }
10579
+ await writeJsonFile(tuiGatewayProcessRegistryPath(input.paths), {
10580
+ version: REGISTRY_VERSION2,
10581
+ processes: kept
10582
+ });
10583
+ return result;
10584
+ }
10585
+ async function inspectRegisteredProcess(processRecord) {
10586
+ if (!isProcessAlive(processRecord.pid)) {
10587
+ return { alive: false, matches: false };
10588
+ }
10589
+ const commandLine = await readProcessCommandLine(processRecord.pid);
10590
+ if (!commandLine) {
10591
+ return {
10592
+ alive: true,
10593
+ matches: false,
10594
+ reason: "command_unavailable"
10595
+ };
10596
+ }
10597
+ if (!commandLineMatchesRecord(commandLine, processRecord)) {
10598
+ return {
10599
+ alive: true,
10600
+ matches: false,
10601
+ reason: "command_mismatch",
10602
+ commandLine
10603
+ };
10604
+ }
10605
+ return { alive: true, matches: true, commandLine };
10606
+ }
10607
+ async function readProcessCommandLine(pid) {
10608
+ if (process.platform === "win32") {
10609
+ return null;
10610
+ }
10611
+ try {
10612
+ const { stdout } = await execFileAsync3(
10613
+ "ps",
10614
+ ["-p", String(pid), "-o", "command="],
10615
+ {
10616
+ timeout: 1e3,
10617
+ windowsHide: true,
10618
+ maxBuffer: 64 * 1024
10619
+ }
10620
+ );
10621
+ return stdout.trim() || null;
10622
+ } catch {
10623
+ return null;
10624
+ }
10625
+ }
10626
+ function commandLineMatchesRecord(commandLine, processRecord) {
10627
+ return /\bhermes\b/u.test(commandLine) && /\bdashboard\b/u.test(commandLine) && hasCommandArgument(commandLine, "--host", "127.0.0.1") && hasCommandArgument(commandLine, "--port", String(processRecord.port));
10628
+ }
10629
+ function hasCommandArgument(commandLine, name, value) {
10630
+ return commandLine.includes(`${name} ${value}`) || commandLine.includes(`${name}=${value}`);
10631
+ }
10632
+ async function terminatePid(pid) {
10633
+ try {
10634
+ process.kill(pid, "SIGTERM");
10635
+ } catch {
10636
+ return !isProcessAlive(pid);
10637
+ }
10638
+ if (await waitForPidExit(pid, TERMINATE_GRACE_MS)) {
10639
+ return true;
10640
+ }
10641
+ try {
10642
+ process.kill(pid, "SIGKILL");
10643
+ } catch {
10644
+ return !isProcessAlive(pid);
10645
+ }
10646
+ return await waitForPidExit(pid, 1e3);
10647
+ }
10648
+ async function waitForPidExit(pid, timeoutMs) {
10649
+ const deadline = Date.now() + timeoutMs;
10650
+ while (Date.now() < deadline) {
10651
+ if (!isProcessAlive(pid)) {
10652
+ return true;
10653
+ }
10654
+ await delay3(100);
10655
+ }
10656
+ return !isProcessAlive(pid);
10657
+ }
10658
+ function isProcessAlive(pid) {
10659
+ try {
10660
+ process.kill(pid, 0);
10661
+ return true;
10662
+ } catch {
10663
+ return false;
10664
+ }
10665
+ }
10666
+ function normalizeRegistry(value) {
10667
+ if (!value || !Array.isArray(value.processes)) {
10668
+ return { version: REGISTRY_VERSION2, processes: [] };
10669
+ }
10670
+ return {
10671
+ version: REGISTRY_VERSION2,
10672
+ processes: value.processes.filter(isProcessRecord)
10673
+ };
10674
+ }
10675
+ function isProcessRecord(value) {
10676
+ if (!value || typeof value !== "object") {
10677
+ return false;
10678
+ }
10679
+ const record = value;
10680
+ return record.owner === "hermeslink" && Number.isInteger(record.pid) && record.pid > 0 && typeof record.profile === "string" && record.profile.length > 0 && Number.isInteger(record.port) && record.port > 0 && typeof record.command === "string" && Array.isArray(record.args) && record.args.every((item) => typeof item === "string") && typeof record.runtime_home === "string" && typeof record.started_at === "string";
10681
+ }
10682
+ function delay3(ms) {
10683
+ return new Promise((resolve) => {
10684
+ const timer = setTimeout(resolve, ms);
10685
+ timer.unref?.();
10686
+ });
10687
+ }
10688
+
10322
10689
  // src/hermes/tui-gateway-rpc.ts
10323
10690
  var CONNECT_TIMEOUT_MS = 15e3;
10324
10691
  var REQUEST_TIMEOUT_MS = 12e4;
@@ -10705,12 +11072,12 @@ async function restartTuiGatewayBackend(input) {
10705
11072
  const entry = backendEntries.get(profileName);
10706
11073
  backendEntries.delete(profileName);
10707
11074
  backendStarts.delete(profileName);
10708
- if (entry && entry.process.exitCode === null) {
10709
- entry.process.kill("SIGTERM");
10710
- await waitForProcessExit(entry.process, BACKEND_RESTART_GRACE_MS);
10711
- if (entry.process.exitCode === null) {
10712
- entry.process.kill("SIGKILL");
10713
- }
11075
+ if (entry) {
11076
+ await terminateBackendEntry(entry, {
11077
+ paths: input.paths,
11078
+ logger: input.logger,
11079
+ graceMs: BACKEND_RESTART_GRACE_MS
11080
+ });
10714
11081
  }
10715
11082
  void input.logger?.warn("tui_gateway_backend_restart_requested", {
10716
11083
  profile: profileName,
@@ -10835,23 +11202,58 @@ async function readTuiGatewayStatus(input = {}) {
10835
11202
  };
10836
11203
  }
10837
11204
  }
10838
- function closeTuiGatewayBackends() {
11205
+ async function closeTuiGatewayBackends(input = {}) {
10839
11206
  for (const client of clients.values()) {
10840
11207
  client.close();
10841
11208
  }
10842
11209
  clients.clear();
10843
11210
  sessionRefs.clear();
10844
- for (const entry of backendEntries.values()) {
10845
- if (!entry.process.killed) {
10846
- entry.process.kill();
10847
- }
10848
- }
11211
+ const entries = [...backendEntries.values()];
10849
11212
  backendEntries.clear();
11213
+ await Promise.all(
11214
+ entries.map(
11215
+ (entry) => terminateBackendEntry(entry, {
11216
+ paths: input.paths,
11217
+ logger: input.logger
11218
+ })
11219
+ )
11220
+ );
11221
+ if (input.cleanupRegistry && input.paths) {
11222
+ await cleanupRegisteredTuiGatewayProcesses({
11223
+ paths: input.paths,
11224
+ logger: input.logger
11225
+ });
11226
+ }
10850
11227
  if (backendReaper) {
10851
11228
  clearInterval(backendReaper);
10852
11229
  backendReaper = null;
10853
11230
  }
10854
11231
  }
11232
+ async function terminateBackendEntry(entry, input = {}) {
11233
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
11234
+ if (!entry.process.killed) {
11235
+ entry.process.kill("SIGTERM");
11236
+ }
11237
+ await waitForProcessExit(
11238
+ entry.process,
11239
+ input.graceMs ?? BACKEND_RESTART_GRACE_MS
11240
+ );
11241
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
11242
+ entry.process.kill("SIGKILL");
11243
+ await waitForProcessExit(entry.process, 1e3);
11244
+ }
11245
+ }
11246
+ await unregisterTuiGatewayProcess(input.paths ?? entry.paths, {
11247
+ pid: entry.process.pid ?? void 0,
11248
+ profile: entry.profile
11249
+ }).catch((error) => {
11250
+ void input.logger?.debug("tui_gateway_registry_unregister_failed", {
11251
+ profile: entry.profile,
11252
+ pid: entry.process.pid ?? null,
11253
+ error: error instanceof Error ? error.message : String(error)
11254
+ });
11255
+ });
11256
+ }
10855
11257
  function formatSessionModelConfigValue(model, provider, sessionOnly = false) {
10856
11258
  const suffix = sessionOnly ? " --session" : "";
10857
11259
  const normalizedProvider = provider?.trim();
@@ -11032,7 +11434,7 @@ async function startTuiGatewayBackend(input) {
11032
11434
  ];
11033
11435
  const logWriter = createRotatingTextLogWriter({
11034
11436
  paths,
11035
- fileName: path9.basename(getGatewayRuntimeLogFile(paths))
11437
+ fileName: path10.basename(getGatewayRuntimeLogFile(paths))
11036
11438
  });
11037
11439
  const hermesBin = resolveHermesBin();
11038
11440
  const child = spawn2(hermesBin, args, {
@@ -11053,6 +11455,16 @@ async function startTuiGatewayBackend(input) {
11053
11455
  clients.get(profileName)?.close();
11054
11456
  clients.delete(profileName);
11055
11457
  backendEntries.delete(profileName);
11458
+ void unregisterTuiGatewayProcess(paths, {
11459
+ pid: child.pid ?? void 0,
11460
+ profile: profileName
11461
+ }).catch((error) => {
11462
+ void input.logger?.debug("tui_gateway_registry_unregister_failed", {
11463
+ profile: profileName,
11464
+ pid: child.pid ?? null,
11465
+ error: error instanceof Error ? error.message : String(error)
11466
+ });
11467
+ });
11056
11468
  void input.logger?.warn("tui_gateway_backend_exited", {
11057
11469
  profile: profileName,
11058
11470
  pid: child.pid ?? null,
@@ -11083,6 +11495,32 @@ async function startTuiGatewayBackend(input) {
11083
11495
  await logWriter.flush().catch(() => void 0);
11084
11496
  throw error;
11085
11497
  }
11498
+ if (!child.pid) {
11499
+ child.kill();
11500
+ await logWriter.flush().catch(() => void 0);
11501
+ throw new LinkHttpError(
11502
+ 503,
11503
+ "tui_gateway_backend_unavailable",
11504
+ "Hermes dashboard backend started without a process id."
11505
+ );
11506
+ }
11507
+ try {
11508
+ await registerTuiGatewayProcess(paths, {
11509
+ owner: "hermeslink",
11510
+ pid: child.pid,
11511
+ profile: profileName,
11512
+ port,
11513
+ command: hermesBin,
11514
+ args,
11515
+ runtime_home: paths.homeDir,
11516
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
11517
+ });
11518
+ } catch (error) {
11519
+ child.kill();
11520
+ await waitForProcessExit(child, BACKEND_RESTART_GRACE_MS);
11521
+ await logWriter.flush().catch(() => void 0);
11522
+ throw error;
11523
+ }
11086
11524
  const entry = {
11087
11525
  profile: profileName,
11088
11526
  port,
@@ -11090,6 +11528,7 @@ async function startTuiGatewayBackend(input) {
11090
11528
  baseUrl,
11091
11529
  wsUrl,
11092
11530
  process: child,
11531
+ paths,
11093
11532
  logPath: logWriter.filePath,
11094
11533
  startedAt: Date.now(),
11095
11534
  lastActiveAt: Date.now()
@@ -11527,14 +11966,14 @@ async function readCurrentStoredSessionId(client, runtimeSessionId, logger) {
11527
11966
  return null;
11528
11967
  }
11529
11968
  async function waitForProcessExit(child, timeoutMs) {
11530
- if (child.killed || child.exitCode !== null) {
11969
+ if (child.exitCode !== null || child.signalCode !== null) {
11531
11970
  return;
11532
11971
  }
11533
11972
  await Promise.race([
11534
11973
  new Promise((resolve) => {
11535
11974
  child.once("exit", () => resolve());
11536
11975
  }),
11537
- delay3(timeoutMs).then(() => void 0)
11976
+ delay4(timeoutMs).then(() => void 0)
11538
11977
  ]);
11539
11978
  }
11540
11979
  var GatewayEventQueue = class {
@@ -11969,7 +12408,7 @@ async function waitForDashboardReady(baseUrl, token, logPath, signal) {
11969
12408
  } catch (error) {
11970
12409
  lastError = error;
11971
12410
  }
11972
- await delay3(250, void 0, { signal }).catch(() => void 0);
12411
+ await delay4(250, void 0, { signal }).catch(() => void 0);
11973
12412
  }
11974
12413
  throw new LinkHttpError(
11975
12414
  503,
@@ -12056,23 +12495,28 @@ function startBackendReaper() {
12056
12495
  return;
12057
12496
  }
12058
12497
  backendReaper = setInterval(() => {
12059
- const now = Date.now();
12060
- for (const [profile, entry] of backendEntries) {
12061
- if (now - entry.lastActiveAt < IDLE_BACKEND_TTL_MS) {
12062
- continue;
12063
- }
12064
- clients.get(profile)?.close();
12065
- clients.delete(profile);
12066
- backendEntries.delete(profile);
12067
- entry.process.kill();
12068
- }
12069
- if (backendEntries.size === 0 && backendReaper) {
12070
- clearInterval(backendReaper);
12071
- backendReaper = null;
12072
- }
12498
+ void reapIdleBackends();
12073
12499
  }, 6e4);
12074
12500
  backendReaper.unref?.();
12075
12501
  }
12502
+ async function reapIdleBackends() {
12503
+ const now = Date.now();
12504
+ const staleEntries = [];
12505
+ for (const [profile, entry] of backendEntries) {
12506
+ if (now - entry.lastActiveAt < IDLE_BACKEND_TTL_MS) {
12507
+ continue;
12508
+ }
12509
+ clients.get(profile)?.close();
12510
+ clients.delete(profile);
12511
+ backendEntries.delete(profile);
12512
+ staleEntries.push(entry);
12513
+ }
12514
+ await Promise.all(staleEntries.map((entry) => terminateBackendEntry(entry)));
12515
+ if (backendEntries.size === 0 && backendReaper) {
12516
+ clearInterval(backendReaper);
12517
+ backendReaper = null;
12518
+ }
12519
+ }
12076
12520
  function normalizeProfileName2(profileName) {
12077
12521
  const value = profileName?.trim() || "default";
12078
12522
  if (!PROFILE_NAME_PATTERN2.test(value)) {
@@ -12867,11 +13311,11 @@ function workspaceNotFound() {
12867
13311
  // src/conversations/blob-store.ts
12868
13312
  import { randomUUID as randomUUID5 } from "crypto";
12869
13313
  import { mkdir as mkdir6, readFile as readFile7, readdir as readdir4, rm as rm3, stat as stat6, writeFile } from "fs/promises";
12870
- import path11 from "path";
13314
+ import path12 from "path";
12871
13315
 
12872
13316
  // src/conversations/media.ts
12873
13317
  import { createHash as createHash4 } from "crypto";
12874
- import path10 from "path";
13318
+ import path11 from "path";
12875
13319
 
12876
13320
  // src/conversations/delivery-contract.ts
12877
13321
  var HERMES_LINK_DELIVERY_TAG_PATTERN = /<hermes_link_delivery>\s*(\{[\s\S]*?\})\s*<\/hermes_link_delivery>/giu;
@@ -12961,7 +13405,7 @@ function cleanMessageTextParts(message) {
12961
13405
  }
12962
13406
  }
12963
13407
  function inferMimeType(filePath) {
12964
- const extension = path10.extname(filePath).toLowerCase();
13408
+ const extension = path11.extname(filePath).toLowerCase();
12965
13409
  return {
12966
13410
  ".png": "image/png",
12967
13411
  ".jpg": "image/jpeg",
@@ -13085,7 +13529,7 @@ function mediaSourceKey(sourcePath) {
13085
13529
  return createHash4("sha256").update(resolveMediaSourcePath(sourcePath)).digest("hex").slice(0, 32);
13086
13530
  }
13087
13531
  function sanitizeFilename(value, fallback) {
13088
- const base = path10.basename((value ?? "").replace(/[\r\n\t]/gu, " ").trim());
13532
+ const base = path11.basename((value ?? "").replace(/[\r\n\t]/gu, " ").trim());
13089
13533
  const safe = base.replace(/[/:\\]/gu, "_").slice(0, 200).trim();
13090
13534
  return safe || fallback;
13091
13535
  }
@@ -13114,9 +13558,9 @@ function isBlobReferencedByMessages(snapshot, blobId) {
13114
13558
  }
13115
13559
  function resolveMediaSourcePath(sourcePath) {
13116
13560
  const trimmed = sourcePath.trim();
13117
- const expanded = trimmed.startsWith("~/") ? path10.join(process.env.HOME ?? "", trimmed.slice(2)) : trimmed;
13118
- const resolved = path10.resolve(expanded);
13119
- if (!path10.isAbsolute(expanded)) {
13561
+ const expanded = trimmed.startsWith("~/") ? path11.join(process.env.HOME ?? "", trimmed.slice(2)) : trimmed;
13562
+ const resolved = path11.resolve(expanded);
13563
+ if (!path11.isAbsolute(expanded)) {
13120
13564
  throw new LinkHttpError(
13121
13565
  400,
13122
13566
  "media_source_path_not_absolute",
@@ -13311,7 +13755,7 @@ function normalizeConversationIds(conversationIds) {
13311
13755
  }
13312
13756
  function blobPath(paths, blobId) {
13313
13757
  assertValidBlobId(blobId);
13314
- return path11.join(paths.blobsDir, `${blobId}.bin`);
13758
+ return path12.join(paths.blobsDir, `${blobId}.bin`);
13315
13759
  }
13316
13760
  async function writeConversationBlob(paths, conversationId, input, options) {
13317
13761
  assertValidConversationId(conversationId);
@@ -13320,7 +13764,7 @@ async function writeConversationBlob(paths, conversationId, input, options) {
13320
13764
  }
13321
13765
  const id = `blob_${randomUUID5().replaceAll("-", "")}`;
13322
13766
  const filePath = blobPath(paths, id);
13323
- await mkdir6(path11.dirname(filePath), { recursive: true, mode: 448 });
13767
+ await mkdir6(path12.dirname(filePath), { recursive: true, mode: 448 });
13324
13768
  await writeFile(filePath, input.bytes, { mode: 384 });
13325
13769
  const blob = {
13326
13770
  id,
@@ -13396,7 +13840,7 @@ async function materializeConversationBlob(paths, conversationId, blobId, manife
13396
13840
  }
13397
13841
  }
13398
13842
  const targetDir = conversationAttachmentsDir(paths, conversationId);
13399
- const targetPath = path11.join(
13843
+ const targetPath = path12.join(
13400
13844
  targetDir,
13401
13845
  materializedAttachmentFilename(blobId, manifest.filename ?? blobId)
13402
13846
  );
@@ -13449,7 +13893,7 @@ async function listConversationBlobIds(paths, conversationId) {
13449
13893
  continue;
13450
13894
  }
13451
13895
  const manifest = await readJsonFile(
13452
- path11.join(paths.blobsDir, entry.name)
13896
+ path12.join(paths.blobsDir, entry.name)
13453
13897
  ).catch(() => null);
13454
13898
  if (manifest?.conversation_ids?.includes(conversationId)) {
13455
13899
  blobIds.push(blobId);
@@ -13459,7 +13903,7 @@ async function listConversationBlobIds(paths, conversationId) {
13459
13903
  }
13460
13904
  function conversationAttachmentsDir(paths, conversationId) {
13461
13905
  assertValidConversationId(conversationId);
13462
- return path11.join(paths.conversationsDir, conversationId, "attachments");
13906
+ return path12.join(paths.conversationsDir, conversationId, "attachments");
13463
13907
  }
13464
13908
  function isNodeError6(error, code) {
13465
13909
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
@@ -13471,7 +13915,7 @@ import { stat as stat8 } from "fs/promises";
13471
13915
  // src/hermes/model-provider-disconnects.ts
13472
13916
  import { stat as stat7 } from "fs/promises";
13473
13917
  import os5 from "os";
13474
- import path12 from "path";
13918
+ import path13 from "path";
13475
13919
  async function markAuthBackedModelProviderDisconnected(paths, profileName, providerKey) {
13476
13920
  const profileKey = normalizeProfileName3(profileName);
13477
13921
  const provider = providerKey.trim();
@@ -13549,15 +13993,15 @@ function credentialFilesForProvider(profileName, providerKey) {
13549
13993
  const profileDir = resolveHermesProfileDir(normalizeProfileName3(profileName));
13550
13994
  switch (providerKey) {
13551
13995
  case "qwen-oauth":
13552
- return [path12.join(os5.homedir(), ".qwen", "oauth_creds.json")];
13996
+ return [path13.join(os5.homedir(), ".qwen", "oauth_creds.json")];
13553
13997
  case "google-gemini-cli":
13554
- return [path12.join(profileDir, "auth", "google_oauth.json")];
13998
+ return [path13.join(profileDir, "auth", "google_oauth.json")];
13555
13999
  default:
13556
- return [path12.join(profileDir, "auth.json")];
14000
+ return [path13.join(profileDir, "auth.json")];
13557
14001
  }
13558
14002
  }
13559
14003
  function disconnectedProvidersPath(paths) {
13560
- return path12.join(paths.homeDir, "model-provider-disconnects.json");
14004
+ return path13.join(paths.homeDir, "model-provider-disconnects.json");
13561
14005
  }
13562
14006
  function normalizeStore(value) {
13563
14007
  return {
@@ -14688,7 +15132,7 @@ function commandText(language, zh, en) {
14688
15132
 
14689
15133
  // src/conversations/delivery-staging.ts
14690
15134
  import { mkdir as mkdir7, rm as rm4 } from "fs/promises";
14691
- import path13 from "path";
15135
+ import path14 from "path";
14692
15136
  async function prepareDeliveryStagingRunDir(paths, conversationId, runId) {
14693
15137
  const directory = deliveryStagingRunDir(paths, conversationId, runId);
14694
15138
  await mkdir7(directory, { recursive: true, mode: 448 });
@@ -14701,14 +15145,14 @@ async function removeConversationDeliveryStaging(paths, conversationId) {
14701
15145
  });
14702
15146
  }
14703
15147
  function deliveryStagingRunDir(paths, conversationId, runId) {
14704
- return path13.join(
15148
+ return path14.join(
14705
15149
  deliveryStagingConversationDir(paths, conversationId),
14706
15150
  safePathSegment(runId, "run")
14707
15151
  );
14708
15152
  }
14709
15153
  function deliveryStagingConversationDir(paths, conversationId) {
14710
15154
  assertValidConversationId(conversationId);
14711
- return path13.join(paths.conversationsDir, conversationId, "delivery-staging");
15155
+ return path14.join(paths.conversationsDir, conversationId, "delivery-staging");
14712
15156
  }
14713
15157
  function safePathSegment(value, fallback) {
14714
15158
  const safe = value.trim().replaceAll(/[^a-zA-Z0-9._-]/gu, "_");
@@ -14718,7 +15162,7 @@ function safePathSegment(value, fallback) {
14718
15162
  // src/conversations/conversation-archive-plans.ts
14719
15163
  import { randomUUID as randomUUID7 } from "crypto";
14720
15164
  import { mkdir as mkdir8 } from "fs/promises";
14721
- import path14 from "path";
15165
+ import path15 from "path";
14722
15166
  var PLAN_ID_PATTERN = /^archive_[a-f0-9]{32}$/u;
14723
15167
  var ConversationArchivePlanStore = class {
14724
15168
  constructor(paths) {
@@ -14761,10 +15205,10 @@ var ConversationArchivePlanStore = class {
14761
15205
  await writeJsonFile(this.planPath(normalizedPlanId), plan);
14762
15206
  }
14763
15207
  plansDir() {
14764
- return path14.join(this.paths.indexesDir, "conversation-archive-plans");
15208
+ return path15.join(this.paths.indexesDir, "conversation-archive-plans");
14765
15209
  }
14766
15210
  planPath(planId) {
14767
- return path14.join(this.plansDir(), `${planId}.json`);
15211
+ return path15.join(this.plansDir(), `${planId}.json`);
14768
15212
  }
14769
15213
  };
14770
15214
  function normalizePlanId(planId) {
@@ -14782,7 +15226,7 @@ function normalizePlanId(planId) {
14782
15226
  // src/conversations/conversation-clear-plans.ts
14783
15227
  import { randomUUID as randomUUID8 } from "crypto";
14784
15228
  import { mkdir as mkdir9 } from "fs/promises";
14785
- import path15 from "path";
15229
+ import path16 from "path";
14786
15230
  var PLAN_ID_PATTERN2 = /^clear_[a-f0-9]{32}$/u;
14787
15231
  var ConversationClearPlanStore = class {
14788
15232
  constructor(paths) {
@@ -14826,10 +15270,10 @@ var ConversationClearPlanStore = class {
14826
15270
  await writeJsonFile(this.planPath(normalizedPlanId), plan);
14827
15271
  }
14828
15272
  plansDir() {
14829
- return path15.join(this.paths.indexesDir, "conversation-clear-plans");
15273
+ return path16.join(this.paths.indexesDir, "conversation-clear-plans");
14830
15274
  }
14831
15275
  planPath(planId) {
14832
- return path15.join(this.plansDir(), `${planId}.json`);
15276
+ return path16.join(this.plansDir(), `${planId}.json`);
14833
15277
  }
14834
15278
  };
14835
15279
  function normalizePlanId2(planId) {
@@ -15684,14 +16128,14 @@ function readAttachmentWaveform(attachment) {
15684
16128
 
15685
16129
  // src/hermes/session-title.ts
15686
16130
  import { stat as stat9 } from "fs/promises";
15687
- import path16 from "path";
16131
+ import path17 from "path";
15688
16132
  async function readHermesSessionTitle(sessionId, paths, profileName) {
15689
16133
  const trimmedSessionId = sessionId.trim();
15690
16134
  if (!trimmedSessionId) {
15691
16135
  return void 0;
15692
16136
  }
15693
16137
  const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
15694
- const dbPath = path16.join(
16138
+ const dbPath = path17.join(
15695
16139
  resolveHermesProfileDir(resolvedProfileName),
15696
16140
  "state.db"
15697
16141
  );
@@ -15712,7 +16156,7 @@ async function readHermesCompressionTip(sessionId, paths, profileName) {
15712
16156
  return void 0;
15713
16157
  }
15714
16158
  const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
15715
- const dbPath = path16.join(
16159
+ const dbPath = path17.join(
15716
16160
  resolveHermesProfileDir(resolvedProfileName),
15717
16161
  "state.db"
15718
16162
  );
@@ -15840,12 +16284,12 @@ var ConversationMetadataCoordinator = class {
15840
16284
  return { ...next, last_event_seq: event.seq, updated_at: event.created_at };
15841
16285
  }
15842
16286
  scheduleGeneratedTitleRefresh(conversationId) {
15843
- for (const delay5 of GENERATED_TITLE_RETRY_DELAYS_MS) {
16287
+ for (const delay6 of GENERATED_TITLE_RETRY_DELAYS_MS) {
15844
16288
  setTimeout(() => {
15845
16289
  void this.generateTitleFromFirstRound(conversationId).catch(
15846
16290
  () => void 0
15847
16291
  );
15848
- }, delay5);
16292
+ }, delay6);
15849
16293
  }
15850
16294
  }
15851
16295
  async renameConversation(conversationId, title, input) {
@@ -16145,7 +16589,7 @@ function stripCompressionTitleSuffix(value) {
16145
16589
 
16146
16590
  // src/conversations/history-builder.ts
16147
16591
  import { readFile as readFile8, stat as stat10 } from "fs/promises";
16148
- import path17 from "path";
16592
+ import path18 from "path";
16149
16593
  var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
16150
16594
  var HERMES_HISTORY_COLUMNS = [
16151
16595
  "role",
@@ -16217,13 +16661,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
16217
16661
  }
16218
16662
  const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
16219
16663
  const profileDir = resolveHermesProfileDir(normalizedProfileName);
16220
- const dbPath = path17.join(profileDir, "state.db");
16664
+ const dbPath = path18.join(profileDir, "state.db");
16221
16665
  const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
16222
16666
  sessionsDir: value.sessionsDir,
16223
16667
  configured: value.configured,
16224
16668
  configError: false
16225
16669
  })).catch(() => ({
16226
- sessionsDir: path17.join(profileDir, "sessions"),
16670
+ sessionsDir: path18.join(profileDir, "sessions"),
16227
16671
  configured: false,
16228
16672
  configError: true
16229
16673
  }));
@@ -16399,8 +16843,8 @@ async function readFirstExistingFile(paths) {
16399
16843
  }
16400
16844
  function candidateTranscriptPaths(sessionsDir, sessionId, extension) {
16401
16845
  return [
16402
- path17.join(sessionsDir, `session_${sessionId}.${extension}`),
16403
- path17.join(sessionsDir, `${sessionId}.${extension}`)
16846
+ path18.join(sessionsDir, `session_${sessionId}.${extension}`),
16847
+ path18.join(sessionsDir, `${sessionId}.${extension}`)
16404
16848
  ];
16405
16849
  }
16406
16850
  function readHistoryRows(dbPath, sessionId) {
@@ -18356,6 +18800,10 @@ var APP_TOOL_EVENT_FIELDS_TO_DROP = /* @__PURE__ */ new Set([
18356
18800
  "message"
18357
18801
  ]);
18358
18802
  var LIFECYCLE_MARKER_FORMAT = "hermes-link-lifecycle-marker";
18803
+ var APP_VISIBLE_LIFECYCLE_MARKER_KINDS = /* @__PURE__ */ new Set([
18804
+ "context_compression",
18805
+ "goal_completion"
18806
+ ]);
18359
18807
  function projectConversationAgentEvent(event, language = readLanguage(event.payload)) {
18360
18808
  if (!isAgentActivityEvent(event.type)) {
18361
18809
  return null;
@@ -18576,7 +19024,7 @@ function retainAppVisibleRaw(value) {
18576
19024
  return null;
18577
19025
  }
18578
19026
  const payload = toRecord8(raw.payload);
18579
- if (payload.kind !== "context_compression") {
19027
+ if (typeof payload.kind !== "string" || !APP_VISIBLE_LIFECYCLE_MARKER_KINDS.has(payload.kind)) {
18580
19028
  return null;
18581
19029
  }
18582
19030
  return {
@@ -18942,11 +19390,14 @@ var ConversationQueryCoordinator = class {
18942
19390
  );
18943
19391
  }
18944
19392
  const startIndex = Math.max(0, endIndex - limit);
19393
+ const events = await this.listEvents(conversationId, 0).catch(() => []);
18945
19394
  const messages2 = await this.hydrateAgentEventsForMessages(
18946
19395
  conversationId,
18947
19396
  snapshot.messages.slice(startIndex, endIndex),
18948
- snapshot
19397
+ snapshot,
19398
+ events
18949
19399
  );
19400
+ const todoSnapshot = latestTodoSnapshotFromEvents(conversationId, events);
18950
19401
  return {
18951
19402
  messages: messages2,
18952
19403
  last_event_seq: manifest.last_event_seq,
@@ -18964,7 +19415,8 @@ var ConversationQueryCoordinator = class {
18964
19415
  },
18965
19416
  event_stream: buildConversationEventStreamState(snapshot),
18966
19417
  input_requests: pendingInputRequests(snapshot),
18967
- ...normalizeConversationGoalState(manifest.command_state?.goal) ? { goal: normalizeConversationGoalState(manifest.command_state?.goal) } : {}
19418
+ ...normalizeConversationGoalState(manifest.command_state?.goal) ? { goal: normalizeConversationGoalState(manifest.command_state?.goal) } : {},
19419
+ ...todoSnapshot ? { todo_snapshot: todoSnapshot } : {}
18968
19420
  };
18969
19421
  }
18970
19422
  async listEvents(conversationId, after = 0) {
@@ -18977,12 +19429,11 @@ var ConversationQueryCoordinator = class {
18977
19429
  profile ?? await readConversationProfileSummary(this.deps.paths, manifest)
18978
19430
  );
18979
19431
  }
18980
- async hydrateAgentEventsForMessages(conversationId, messages2, snapshot) {
19432
+ async hydrateAgentEventsForMessages(conversationId, messages2, snapshot, events) {
18981
19433
  if (!messages2.some((message) => message.role === "assistant")) {
18982
19434
  return messages2;
18983
19435
  }
18984
19436
  const eventsByMessageId = /* @__PURE__ */ new Map();
18985
- const events = await this.listEvents(conversationId, 0).catch(() => []);
18986
19437
  for (const event of events) {
18987
19438
  if (!event.message_id) {
18988
19439
  continue;
@@ -19018,6 +19469,81 @@ var ConversationQueryCoordinator = class {
19018
19469
  });
19019
19470
  }
19020
19471
  };
19472
+ function latestTodoSnapshotFromEvents(conversationId, events) {
19473
+ for (let index = events.length - 1; index >= 0; index -= 1) {
19474
+ const snapshot = todoSnapshotFromEvent(conversationId, events[index]);
19475
+ if (snapshot) {
19476
+ return snapshot;
19477
+ }
19478
+ }
19479
+ return null;
19480
+ }
19481
+ function todoSnapshotFromEvent(conversationId, event) {
19482
+ const type = event.type.toLowerCase();
19483
+ if (type !== "tool.completed" && type !== "tool.complete") {
19484
+ return null;
19485
+ }
19486
+ const payload = readRecord(event.payload);
19487
+ const toolName = readToolName(payload)?.toLowerCase();
19488
+ if (toolName !== "todo") {
19489
+ return null;
19490
+ }
19491
+ const todos = readTodoItems(payload.todos) ?? readTodoItems(readRecord(payload.result).todos) ?? readTodoItems(readRecord(payload.output).todos) ?? readTodoItems(readRecord(payload.content).todos);
19492
+ if (!todos || todos.length === 0) {
19493
+ return null;
19494
+ }
19495
+ return {
19496
+ conversation_id: conversationId,
19497
+ ...event.run_id ? { run_id: event.run_id } : {},
19498
+ updated_at: event.created_at,
19499
+ items: todos
19500
+ };
19501
+ }
19502
+ function readTodoItems(value) {
19503
+ if (!Array.isArray(value)) {
19504
+ return null;
19505
+ }
19506
+ const items = value.map((item, index) => readTodoItem(item, index)).filter((item) => item !== null);
19507
+ return items.length > 0 ? items : null;
19508
+ }
19509
+ function readTodoItem(value, index) {
19510
+ const record = readRecord(value);
19511
+ const content = readNonEmptyString(record.content) ?? readNonEmptyString(record.title);
19512
+ const status = readTodoStatus(readNonEmptyString(record.status));
19513
+ if (!content || !status) {
19514
+ return null;
19515
+ }
19516
+ return {
19517
+ id: readNonEmptyString(record.id) ?? `${index + 1}`,
19518
+ content,
19519
+ status
19520
+ };
19521
+ }
19522
+ function readTodoStatus(value) {
19523
+ switch (value?.trim().toLowerCase()) {
19524
+ case "pending":
19525
+ return "pending";
19526
+ case "in_progress":
19527
+ case "in-progress":
19528
+ case "running":
19529
+ return "in_progress";
19530
+ case "completed":
19531
+ case "complete":
19532
+ case "done":
19533
+ return "completed";
19534
+ case "cancelled":
19535
+ case "canceled":
19536
+ return "cancelled";
19537
+ default:
19538
+ return null;
19539
+ }
19540
+ }
19541
+ function readToolName(record) {
19542
+ return readNonEmptyString(record.tool_name) ?? readNonEmptyString(record.toolName) ?? readNonEmptyString(record.name) ?? readNonEmptyString(record.tool) ?? readNonEmptyString(readRecord(record.tool).name);
19543
+ }
19544
+ function readRecord(value) {
19545
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
19546
+ }
19021
19547
  function readRunLanguage(snapshot, runId) {
19022
19548
  if (!runId) {
19023
19549
  return null;
@@ -19830,7 +20356,7 @@ import {
19830
20356
  rm as rm5,
19831
20357
  writeFile as writeFile2
19832
20358
  } from "fs/promises";
19833
- import path18 from "path";
20359
+ import path19 from "path";
19834
20360
  var ConversationStore = class {
19835
20361
  constructor(paths) {
19836
20362
  this.paths = paths;
@@ -19961,23 +20487,23 @@ var ConversationStore = class {
19961
20487
  return manifest != null && manifest.status !== "deleted_soft";
19962
20488
  }
19963
20489
  removeConversationAttachments(conversationId) {
19964
- return rm5(path18.join(this.conversationDir(conversationId), "attachments"), {
20490
+ return rm5(path19.join(this.conversationDir(conversationId), "attachments"), {
19965
20491
  recursive: true,
19966
20492
  force: true
19967
20493
  });
19968
20494
  }
19969
20495
  conversationDir(conversationId) {
19970
20496
  assertValidConversationId(conversationId);
19971
- return path18.join(this.paths.conversationsDir, conversationId);
20497
+ return path19.join(this.paths.conversationsDir, conversationId);
19972
20498
  }
19973
20499
  manifestPath(conversationId) {
19974
- return path18.join(this.conversationDir(conversationId), "manifest.json");
20500
+ return path19.join(this.conversationDir(conversationId), "manifest.json");
19975
20501
  }
19976
20502
  snapshotPath(conversationId) {
19977
- return path18.join(this.conversationDir(conversationId), "snapshot.json");
20503
+ return path19.join(this.conversationDir(conversationId), "snapshot.json");
19978
20504
  }
19979
20505
  eventsPath(conversationId) {
19980
- return path18.join(this.conversationDir(conversationId), "events.ndjson");
20506
+ return path19.join(this.conversationDir(conversationId), "events.ndjson");
19981
20507
  }
19982
20508
  };
19983
20509
  function createEmptySnapshot2() {
@@ -19990,11 +20516,11 @@ function isNodeError11(error, code) {
19990
20516
  // src/conversations/hermes-session-sync.ts
19991
20517
  import { randomUUID as randomUUID11 } from "crypto";
19992
20518
  import { readdir as readdir7, readFile as readFile11, stat as stat12 } from "fs/promises";
19993
- import path20 from "path";
20519
+ import path21 from "path";
19994
20520
 
19995
20521
  // src/conversations/delivery-import.ts
19996
20522
  import { lstat as lstat2, readFile as readFile10, readdir as readdir6, stat as stat11 } from "fs/promises";
19997
- import path19 from "path";
20523
+ import path20 from "path";
19998
20524
  var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
19999
20525
  var MAX_MEDIA_IMPORT_FAILURES = 20;
20000
20526
  var MAX_DELIVERY_FILES = 50;
@@ -20065,16 +20591,16 @@ var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
20065
20591
  ".m4a"
20066
20592
  ]);
20067
20593
  function resolveDeliveryStagingTarget(paths, stagingDir) {
20068
- const resolvedDir = path19.resolve(stagingDir);
20069
- const relative = path19.relative(path19.resolve(paths.conversationsDir), resolvedDir);
20070
- if (!relative || relative.startsWith("..") || path19.isAbsolute(relative)) {
20594
+ const resolvedDir = path20.resolve(stagingDir);
20595
+ const relative = path20.relative(path20.resolve(paths.conversationsDir), resolvedDir);
20596
+ if (!relative || relative.startsWith("..") || path20.isAbsolute(relative)) {
20071
20597
  throw new LinkHttpError(
20072
20598
  400,
20073
20599
  "delivery_staging_invalid",
20074
20600
  "delivery staging directory must be inside Hermes Link conversations"
20075
20601
  );
20076
20602
  }
20077
- const segments = relative.split(path19.sep);
20603
+ const segments = relative.split(path20.sep);
20078
20604
  if (segments.length !== 3 || segments[1] !== DELIVERY_STAGING_SEGMENT || !segments[0] || !segments[2]) {
20079
20605
  throw new LinkHttpError(
20080
20606
  400,
@@ -20110,7 +20636,7 @@ async function collectStagedDeliveryReferences(stagingDir) {
20110
20636
  return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
20111
20637
  (left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
20112
20638
  ).slice(0, MAX_DELIVERY_FILES).map((entry) => {
20113
- const sourcePath = path19.join(stagingDir, entry.name);
20639
+ const sourcePath = path20.join(stagingDir, entry.name);
20114
20640
  const mime = inferMimeType(sourcePath);
20115
20641
  return {
20116
20642
  path: sourcePath,
@@ -20296,7 +20822,7 @@ async function writeBlobFromFile(deps, conversationId, source) {
20296
20822
  }
20297
20823
  return deps.writeBlob(conversationId, {
20298
20824
  bytes: await readFile10(sourcePath),
20299
- filename: path19.basename(sourcePath),
20825
+ filename: path20.basename(sourcePath),
20300
20826
  mime: source.mime ?? inferMimeType(sourcePath)
20301
20827
  });
20302
20828
  }
@@ -20309,7 +20835,7 @@ function describeMediaImportFailure(reference, sourceKey, error) {
20309
20835
  };
20310
20836
  }
20311
20837
  function isSupportedDeliveryFilename(filename) {
20312
- return SUPPORTED_DELIVERY_EXTENSIONS.has(path19.extname(filename).toLowerCase());
20838
+ return SUPPORTED_DELIVERY_EXTENSIONS.has(path20.extname(filename).toLowerCase());
20313
20839
  }
20314
20840
  function readString10(payload, key) {
20315
20841
  const value = payload[key];
@@ -20367,7 +20893,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
20367
20893
  const candidates = [];
20368
20894
  for (const profileName of profileNames) {
20369
20895
  const profileDir = resolveHermesProfileDir(profileName);
20370
- const dbPath = path20.join(profileDir, "state.db");
20896
+ const dbPath = path21.join(profileDir, "state.db");
20371
20897
  const sessions = await listProfileSessions(dbPath).catch((error) => {
20372
20898
  result.errors.push({
20373
20899
  profile: profileName,
@@ -20483,7 +21009,7 @@ async function syncHermesCronSessionIntoConversations(paths, logger, input) {
20483
21009
  const knownHermesSessions = await readKnownHermesSessions(store);
20484
21010
  const profileName = input.profileName.trim() || DEFAULT_PROFILE_NAME;
20485
21011
  const profileDir = resolveHermesProfileDir(profileName);
20486
- const dbPath = path20.join(profileDir, "state.db");
21012
+ const dbPath = path21.join(profileDir, "state.db");
20487
21013
  const sessions = await listProfileSessionsByIdPrefix(
20488
21014
  dbPath,
20489
21015
  `cron_${jobId}_`
@@ -21794,8 +22320,8 @@ async function readJsonlMessages(profileName, sessionId) {
21794
22320
  return [];
21795
22321
  }
21796
22322
  const profileDir = resolveHermesProfileDir(profileName);
21797
- const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path20.join(profileDir, "sessions"));
21798
- const transcriptPath = path20.join(sessionsDir, `${sessionId}.jsonl`);
22323
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path21.join(profileDir, "sessions"));
22324
+ const transcriptPath = path21.join(sessionsDir, `${sessionId}.jsonl`);
21799
22325
  const raw = await readFile11(transcriptPath, "utf8").catch((error) => {
21800
22326
  if (isNodeError13(error, "ENOENT")) {
21801
22327
  return "";
@@ -22165,7 +22691,7 @@ function isNodeError13(error, code) {
22165
22691
 
22166
22692
  // src/conversations/delivery-context.ts
22167
22693
  import { copyFile, mkdir as mkdir11, stat as stat13 } from "fs/promises";
22168
- import path21 from "path";
22694
+ import path22 from "path";
22169
22695
  var ACTIVE_CONTEXTS = /* @__PURE__ */ new Map();
22170
22696
  var CONTEXT_TTL_MS = 2 * 60 * 60 * 1e3;
22171
22697
  var MAX_DELIVERY_TOOL_FILES = 50;
@@ -22237,10 +22763,10 @@ function findDeliveryContext(input) {
22237
22763
  );
22238
22764
  }
22239
22765
  async function copyFilesIntoDeliveryContext(paths, context, files) {
22240
- const stagingDir = path21.resolve(context.stagingDir);
22241
- const conversationsRoot = path21.resolve(paths.conversationsDir);
22242
- const relative = path21.relative(conversationsRoot, stagingDir);
22243
- if (!relative || relative.startsWith("..") || path21.isAbsolute(relative)) {
22766
+ const stagingDir = path22.resolve(context.stagingDir);
22767
+ const conversationsRoot = path22.resolve(paths.conversationsDir);
22768
+ const relative = path22.relative(conversationsRoot, stagingDir);
22769
+ if (!relative || relative.startsWith("..") || path22.isAbsolute(relative)) {
22244
22770
  throw new LinkHttpError(
22245
22771
  400,
22246
22772
  "delivery_staging_invalid",
@@ -22251,13 +22777,13 @@ async function copyFilesIntoDeliveryContext(paths, context, files) {
22251
22777
  const result = { staged: [], skipped: [] };
22252
22778
  const usedNames = /* @__PURE__ */ new Set();
22253
22779
  for (const [index, file] of files.slice(0, MAX_DELIVERY_TOOL_FILES).entries()) {
22254
- const sourcePath = path21.resolve(file.path);
22780
+ const sourcePath = path22.resolve(file.path);
22255
22781
  let baseName = sanitizeFilename(
22256
- file.caption || path21.basename(sourcePath),
22782
+ file.caption || path22.basename(sourcePath),
22257
22783
  `attachment-${index + 1}`
22258
22784
  );
22259
- const sourceExtension = path21.extname(sourcePath);
22260
- if (!path21.extname(baseName) && sourceExtension) {
22785
+ const sourceExtension = path22.extname(sourcePath);
22786
+ if (!path22.extname(baseName) && sourceExtension) {
22261
22787
  baseName = `${baseName}${sourceExtension}`;
22262
22788
  }
22263
22789
  const filename = uniqueStagingFilename(
@@ -22284,7 +22810,7 @@ async function copyFilesIntoDeliveryContext(paths, context, files) {
22284
22810
  });
22285
22811
  continue;
22286
22812
  }
22287
- const targetPath = path21.join(stagingDir, filename);
22813
+ const targetPath = path22.join(stagingDir, filename);
22288
22814
  await copyFile(sourcePath, targetPath);
22289
22815
  result.staged.push({
22290
22816
  source_path: sourcePath,
@@ -22327,7 +22853,7 @@ function normalizeDeliveryFileInputs(value) {
22327
22853
  });
22328
22854
  }
22329
22855
  function uniqueStagingFilename(filename, usedNames) {
22330
- const extension = path21.extname(filename);
22856
+ const extension = path22.extname(filename);
22331
22857
  const stem = filename.slice(0, filename.length - extension.length);
22332
22858
  let candidate = filename;
22333
22859
  let suffix = 2;
@@ -22828,10 +23354,10 @@ function parseHermesApiCapabilities(payload) {
22828
23354
  sessionKeyHeader: readString13(features, "session_key_header")
22829
23355
  };
22830
23356
  }
22831
- async function callHermesApi(path37, init, options) {
23357
+ async function callHermesApi(path38, init, options) {
22832
23358
  const method = init.method ?? "GET";
22833
23359
  const startedAt = Date.now();
22834
- void options.logger?.debug("hermes_api_request_started", { method, path: path37 });
23360
+ void options.logger?.debug("hermes_api_request_started", { method, path: path38 });
22835
23361
  const availability = await ensureHermesApiServerAvailable({
22836
23362
  fetchImpl: options.fetchImpl,
22837
23363
  logger: options.logger,
@@ -22840,7 +23366,7 @@ async function callHermesApi(path37, init, options) {
22840
23366
  });
22841
23367
  let config = availability.configResult.apiServer;
22842
23368
  const fetcher = options.fetchImpl ?? fetch;
22843
- const request = () => fetchHermesApi(fetcher, config, path37, init, options);
23369
+ const request = () => fetchHermesApi(fetcher, config, path38, init, options);
22844
23370
  let response;
22845
23371
  try {
22846
23372
  response = await request();
@@ -22848,7 +23374,7 @@ async function callHermesApi(path37, init, options) {
22848
23374
  logHermesApiError(
22849
23375
  options.logger,
22850
23376
  method,
22851
- path37,
23377
+ path38,
22852
23378
  options.profileName,
22853
23379
  startedAt,
22854
23380
  error
@@ -22859,7 +23385,7 @@ async function callHermesApi(path37, init, options) {
22859
23385
  logHermesApiResponse(
22860
23386
  options.logger,
22861
23387
  method,
22862
- path37,
23388
+ path38,
22863
23389
  options.profileName,
22864
23390
  startedAt,
22865
23391
  response
@@ -22868,7 +23394,7 @@ async function callHermesApi(path37, init, options) {
22868
23394
  }
22869
23395
  void options.logger?.warn("hermes_api_request_retrying_after_401", {
22870
23396
  method,
22871
- path: path37,
23397
+ path: path38,
22872
23398
  profile: options.profileName ?? "default",
22873
23399
  port: config.port ?? null,
22874
23400
  duration_ms: Date.now() - startedAt
@@ -22887,7 +23413,7 @@ async function callHermesApi(path37, init, options) {
22887
23413
  logHermesApiError(
22888
23414
  options.logger,
22889
23415
  method,
22890
- path37,
23416
+ path38,
22891
23417
  options.profileName,
22892
23418
  startedAt,
22893
23419
  error
@@ -22897,7 +23423,7 @@ async function callHermesApi(path37, init, options) {
22897
23423
  logHermesApiResponse(
22898
23424
  options.logger,
22899
23425
  method,
22900
- path37,
23426
+ path38,
22901
23427
  options.profileName,
22902
23428
  startedAt,
22903
23429
  response
@@ -22907,7 +23433,7 @@ async function callHermesApi(path37, init, options) {
22907
23433
  }
22908
23434
  void options.logger?.warn("hermes_api_request_repairing_after_401", {
22909
23435
  method,
22910
- path: path37,
23436
+ path: path38,
22911
23437
  profile: options.profileName ?? "default",
22912
23438
  port: config.port ?? null,
22913
23439
  duration_ms: Date.now() - startedAt
@@ -22928,7 +23454,7 @@ async function callHermesApi(path37, init, options) {
22928
23454
  logHermesApiError(
22929
23455
  options.logger,
22930
23456
  method,
22931
- path37,
23457
+ path38,
22932
23458
  options.profileName,
22933
23459
  startedAt,
22934
23460
  error
@@ -22938,21 +23464,21 @@ async function callHermesApi(path37, init, options) {
22938
23464
  logHermesApiResponse(
22939
23465
  options.logger,
22940
23466
  method,
22941
- path37,
23467
+ path38,
22942
23468
  options.profileName,
22943
23469
  startedAt,
22944
23470
  response
22945
23471
  );
22946
23472
  return response;
22947
23473
  }
22948
- async function fetchHermesApi(fetcher, config, path37, init, options) {
23474
+ async function fetchHermesApi(fetcher, config, path38, init, options) {
22949
23475
  const headers = new Headers(init.headers);
22950
23476
  headers.set("accept", headers.get("accept") ?? "application/json");
22951
23477
  if (config.key) {
22952
23478
  headers.set("x-api-key", config.key);
22953
23479
  headers.set("authorization", `Bearer ${config.key}`);
22954
23480
  }
22955
- return await fetcher(`http://127.0.0.1:${config.port}${path37}`, {
23481
+ return await fetcher(`http://127.0.0.1:${config.port}${path38}`, {
22956
23482
  ...init,
22957
23483
  headers
22958
23484
  }).catch((error) => {
@@ -22961,10 +23487,10 @@ async function fetchHermesApi(fetcher, config, path37, init, options) {
22961
23487
  }
22962
23488
  void options.logger?.warn("hermes_api_server_connect_failed", {
22963
23489
  method: String(init.method ?? "GET").toUpperCase(),
22964
- path: path37,
23490
+ path: path38,
22965
23491
  profile: options.profileName ?? "default",
22966
23492
  port: config.port ?? null,
22967
- url: `http://127.0.0.1:${config.port}${path37}`,
23493
+ url: `http://127.0.0.1:${config.port}${path38}`,
22968
23494
  error: error instanceof Error ? error.message : String(error)
22969
23495
  });
22970
23496
  throw new LinkHttpError(
@@ -22974,10 +23500,10 @@ async function fetchHermesApi(fetcher, config, path37, init, options) {
22974
23500
  );
22975
23501
  });
22976
23502
  }
22977
- function logHermesApiResponse(logger, method, path37, profileName, startedAt, response) {
23503
+ function logHermesApiResponse(logger, method, path38, profileName, startedAt, response) {
22978
23504
  const fields = {
22979
23505
  method,
22980
- path: path37,
23506
+ path: path38,
22981
23507
  profile: profileName ?? "default",
22982
23508
  status: response.status,
22983
23509
  duration_ms: Date.now() - startedAt
@@ -22998,10 +23524,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
22998
23524
  ...upstreamError ? { upstream_error: upstreamError } : {}
22999
23525
  });
23000
23526
  }
23001
- function logHermesApiError(logger, method, path37, profileName, startedAt, error) {
23527
+ function logHermesApiError(logger, method, path38, profileName, startedAt, error) {
23002
23528
  void logger?.warn("hermes_api_request_failed", {
23003
23529
  method,
23004
- path: path37,
23530
+ path: path38,
23005
23531
  profile: profileName ?? "default",
23006
23532
  duration_ms: Date.now() - startedAt,
23007
23533
  ...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
@@ -23068,12 +23594,12 @@ function readBoolean2(payload, key) {
23068
23594
  }
23069
23595
 
23070
23596
  // src/hermes/stt.ts
23071
- import { execFile as execFile3 } from "child_process";
23597
+ import { execFile as execFile4 } from "child_process";
23072
23598
  import { access as access2, readFile as readFile12, stat as stat14 } from "fs/promises";
23073
23599
  import os6 from "os";
23074
- import path22 from "path";
23075
- import { promisify as promisify3 } from "util";
23076
- var execFileAsync3 = promisify3(execFile3);
23600
+ import path23 from "path";
23601
+ import { promisify as promisify4 } from "util";
23602
+ var execFileAsync4 = promisify4(execFile4);
23077
23603
  var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
23078
23604
  var STT_TIMEOUT_MS = 18e4;
23079
23605
  var STT_MAX_BUFFER_BYTES = 2 * 1024 * 1024;
@@ -23106,7 +23632,7 @@ async function transcribeAudioWithHermesProfile(input) {
23106
23632
  let stdout = "";
23107
23633
  let stderr = "";
23108
23634
  try {
23109
- const output = await execFileAsync3(
23635
+ const output = await execFileAsync4(
23110
23636
  python.command,
23111
23637
  [...python.args, "-c", script, input.audioPath],
23112
23638
  {
@@ -23166,7 +23692,7 @@ async function buildHermesSttEnv(profileName, hermesSourceRoot) {
23166
23692
  };
23167
23693
  const sourceRoot = hermesSourceRoot ?? await findDevHermesAgentSource();
23168
23694
  if (sourceRoot) {
23169
- env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path22.delimiter);
23695
+ env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path23.delimiter);
23170
23696
  }
23171
23697
  return env;
23172
23698
  }
@@ -23261,14 +23787,14 @@ async function resolveHermesPythonRuntime() {
23261
23787
  };
23262
23788
  }
23263
23789
  async function resolveExecutablePath(command) {
23264
- if (path22.isAbsolute(command)) {
23790
+ if (path23.isAbsolute(command)) {
23265
23791
  return await isExecutableFile2(command) ? command : null;
23266
23792
  }
23267
23793
  const pathEnv = process.env.PATH ?? "";
23268
23794
  const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
23269
- for (const dir of pathEnv.split(path22.delimiter)) {
23795
+ for (const dir of pathEnv.split(path23.delimiter)) {
23270
23796
  for (const extension of extensions) {
23271
- const candidate = path22.join(dir, `${command}${extension}`);
23797
+ const candidate = path23.join(dir, `${command}${extension}`);
23272
23798
  if (await isExecutableFile2(candidate)) {
23273
23799
  return candidate;
23274
23800
  }
@@ -23290,11 +23816,11 @@ async function isExecutableFile2(filePath) {
23290
23816
  }
23291
23817
  async function findHermesVenvPython(sourceRoot) {
23292
23818
  const candidates = process.platform === "win32" ? [
23293
- path22.join(sourceRoot, "venv", "Scripts", "python.exe"),
23294
- path22.join(sourceRoot, ".venv", "Scripts", "python.exe")
23819
+ path23.join(sourceRoot, "venv", "Scripts", "python.exe"),
23820
+ path23.join(sourceRoot, ".venv", "Scripts", "python.exe")
23295
23821
  ] : [
23296
- path22.join(sourceRoot, "venv", "bin", "python"),
23297
- path22.join(sourceRoot, ".venv", "bin", "python")
23822
+ path23.join(sourceRoot, "venv", "bin", "python"),
23823
+ path23.join(sourceRoot, ".venv", "bin", "python")
23298
23824
  ];
23299
23825
  for (const candidate of candidates) {
23300
23826
  if (await isExecutableFile2(candidate)) {
@@ -23323,8 +23849,8 @@ function shebangToPythonCommand(shebang) {
23323
23849
  }
23324
23850
  async function findDevHermesAgentSource() {
23325
23851
  const candidates = [
23326
- path22.resolve(process.cwd(), "reference/hermes-agent"),
23327
- path22.resolve(process.cwd(), "../../reference/hermes-agent")
23852
+ path23.resolve(process.cwd(), "reference/hermes-agent"),
23853
+ path23.resolve(process.cwd(), "../../reference/hermes-agent")
23328
23854
  ];
23329
23855
  for (const candidate of candidates) {
23330
23856
  if (await isHermesAgentSourceRoot(candidate)) {
@@ -23340,7 +23866,7 @@ async function readHermesLauncherTarget(filePath) {
23340
23866
  line
23341
23867
  );
23342
23868
  const rawTarget = quoted?.groups?.target ?? /^\s*exec\s+(?<target>\S+)\s+(?:"\$@"|'\$@')/.exec(line)?.groups?.target;
23343
- if (!rawTarget || !path22.isAbsolute(rawTarget)) {
23869
+ if (!rawTarget || !path23.isAbsolute(rawTarget)) {
23344
23870
  continue;
23345
23871
  }
23346
23872
  if (await isExecutableFile2(rawTarget)) {
@@ -23370,12 +23896,12 @@ async function resolveHermesEntrypointRuntime(entrypointPath) {
23370
23896
  return null;
23371
23897
  }
23372
23898
  async function findHermesSourceRoot(executablePath) {
23373
- let cursor = path22.dirname(path22.resolve(executablePath));
23899
+ let cursor = path23.dirname(path23.resolve(executablePath));
23374
23900
  for (let index = 0; index < 6; index += 1) {
23375
23901
  if (await isHermesAgentSourceRoot(cursor)) {
23376
23902
  return cursor;
23377
23903
  }
23378
- const parent = path22.dirname(cursor);
23904
+ const parent = path23.dirname(cursor);
23379
23905
  if (parent === cursor) {
23380
23906
  break;
23381
23907
  }
@@ -23386,14 +23912,14 @@ async function findHermesSourceRoot(executablePath) {
23386
23912
  function hermesSourceRootCandidates() {
23387
23913
  const candidates = [
23388
23914
  process.env.HERMES_PYTHON_SRC_ROOT?.trim(),
23389
- path22.join(os6.homedir(), ".hermes", "hermes-agent"),
23915
+ path23.join(os6.homedir(), ".hermes", "hermes-agent"),
23390
23916
  "/usr/local/lib/hermes-agent",
23391
23917
  "/opt/hermes"
23392
23918
  ].filter((candidate) => Boolean(candidate));
23393
23919
  if (process.platform === "win32") {
23394
23920
  const localAppData = process.env.LOCALAPPDATA?.trim();
23395
23921
  if (localAppData) {
23396
- candidates.unshift(path22.join(localAppData, "hermes", "hermes-agent"));
23922
+ candidates.unshift(path23.join(localAppData, "hermes", "hermes-agent"));
23397
23923
  }
23398
23924
  }
23399
23925
  return candidates;
@@ -23403,7 +23929,7 @@ async function findExplicitHermesSourceRoot() {
23403
23929
  return explicit && await isHermesAgentSourceRoot(explicit) ? explicit : null;
23404
23930
  }
23405
23931
  async function isHermesAgentSourceRoot(candidate) {
23406
- return await isDirectory(candidate) && await isDirectory(path22.join(candidate, "tools")) && await isDirectory(path22.join(candidate, "hermes_cli"));
23932
+ return await isDirectory(candidate) && await isDirectory(path23.join(candidate, "tools")) && await isDirectory(path23.join(candidate, "hermes_cli"));
23407
23933
  }
23408
23934
  async function isDirectory(candidate) {
23409
23935
  return stat14(candidate).then((info) => info.isDirectory()).catch(() => false);
@@ -23415,15 +23941,15 @@ function compactProcessOutput(value) {
23415
23941
 
23416
23942
  // src/hermes/usage-probe.ts
23417
23943
  import { open as open3, readFile as readFile14, rm as rm6, stat as stat16 } from "fs/promises";
23418
- import path24 from "path";
23944
+ import path25 from "path";
23419
23945
  import YAML3 from "yaml";
23420
23946
 
23421
23947
  // src/hermes/profiles.ts
23422
- import { execFile as execFile4 } from "child_process";
23948
+ import { execFile as execFile5 } from "child_process";
23423
23949
  import { readdir as readdir8, readFile as readFile13, rename as rename3, stat as stat15 } from "fs/promises";
23424
- import path23 from "path";
23425
- import { setTimeout as delay4 } from "timers/promises";
23426
- import { promisify as promisify4 } from "util";
23950
+ import path24 from "path";
23951
+ import { setTimeout as delay5 } from "timers/promises";
23952
+ import { promisify as promisify5 } from "util";
23427
23953
  import YAML2 from "yaml";
23428
23954
  var DEFAULT_PROFILE = "default";
23429
23955
  var PROFILE_NAME_PATTERN5 = /^[a-zA-Z0-9._-]{1,64}$/;
@@ -23431,7 +23957,7 @@ var PROFILE_DELETE_TIMEOUT_MS = 3e4;
23431
23957
  var PROFILE_GATEWAY_STOP_TIMEOUT_MS = 3e3;
23432
23958
  var PROFILE_DELETE_STABLE_ABSENCE_MS = 1200;
23433
23959
  var PROFILE_DELETE_VERIFY_INTERVAL_MS = 150;
23434
- var execFileAsync4 = promisify4(execFile4);
23960
+ var execFileAsync5 = promisify5(execFile5);
23435
23961
  async function listHermesProfiles(paths = resolveRuntimePaths()) {
23436
23962
  const profiles = /* @__PURE__ */ new Map();
23437
23963
  if (await hasDefaultProfileConfigSource()) {
@@ -23564,7 +24090,7 @@ async function readHermesProfileCapabilities(name) {
23564
24090
  return {
23565
24091
  defaultModel: listedModels?.defaultModel ?? null,
23566
24092
  modelCount: listedModels?.models.length ?? 0,
23567
- skillCount: await countSkills(path23.join(profileDir, "skills")).catch(
24093
+ skillCount: await countSkills(path24.join(profileDir, "skills")).catch(
23568
24094
  () => 0
23569
24095
  ),
23570
24096
  toolCount: await countConfiguredTools(name).catch(() => 0)
@@ -23625,11 +24151,11 @@ async function hasDefaultProfileConfigSource() {
23625
24151
  if (!profileStat?.isDirectory()) {
23626
24152
  return false;
23627
24153
  }
23628
- return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path23.join(profilePath, ".env"));
24154
+ return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path24.join(profilePath, ".env"));
23629
24155
  }
23630
24156
  async function deleteHermesProfileWithCli(name) {
23631
24157
  try {
23632
- await execFileAsync4(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
24158
+ await execFileAsync5(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
23633
24159
  timeout: PROFILE_DELETE_TIMEOUT_MS,
23634
24160
  windowsHide: true
23635
24161
  });
@@ -23688,7 +24214,7 @@ async function findHermesGatewayProcessIdsForProfile(name) {
23688
24214
  return [];
23689
24215
  }
23690
24216
  try {
23691
- const output = await execFileAsync4("ps", ["-axo", "pid=,command="], {
24217
+ const output = await execFileAsync5("ps", ["-axo", "pid=,command="], {
23692
24218
  timeout: 3e3,
23693
24219
  windowsHide: true
23694
24220
  });
@@ -23728,7 +24254,7 @@ async function waitForProcessesToExit(pids, timeoutMs) {
23728
24254
  const deadline = Date.now() + timeoutMs;
23729
24255
  let remaining = pids.filter(isProcessRunning);
23730
24256
  while (remaining.length > 0 && Date.now() < deadline) {
23731
- await delay4(100);
24257
+ await delay5(100);
23732
24258
  remaining = remaining.filter(isProcessRunning);
23733
24259
  }
23734
24260
  return remaining;
@@ -23747,7 +24273,7 @@ async function waitForProfilePathToRemainAbsent(profilePath) {
23747
24273
  if (await pathExists(profilePath)) {
23748
24274
  return false;
23749
24275
  }
23750
- await delay4(PROFILE_DELETE_VERIFY_INTERVAL_MS);
24276
+ await delay5(PROFILE_DELETE_VERIFY_INTERVAL_MS);
23751
24277
  }
23752
24278
  return !await pathExists(profilePath);
23753
24279
  }
@@ -23795,7 +24321,7 @@ async function countSkills(root) {
23795
24321
  );
23796
24322
  let count = 0;
23797
24323
  for (const entry of entries) {
23798
- const entryPath = path23.join(root, entry.name);
24324
+ const entryPath = path24.join(root, entry.name);
23799
24325
  if (entry.name === ".git" || entry.name === ".hub") {
23800
24326
  continue;
23801
24327
  }
@@ -23888,7 +24414,7 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
23888
24414
  const normalizedProfile = normalizeProfileName6(profileName);
23889
24415
  const profilePath = resolveHermesProfileDir(normalizedProfile);
23890
24416
  const configPath = resolveHermesConfigPath(normalizedProfile);
23891
- const pluginPath = path24.join(
24417
+ const pluginPath = path25.join(
23892
24418
  profilePath,
23893
24419
  "plugins",
23894
24420
  HERMES_USAGE_PROBE_PLUGIN_KEY
@@ -23914,7 +24440,7 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
23914
24440
  return { ...base(), skipped: true };
23915
24441
  }
23916
24442
  try {
23917
- await ensureDirectoryWithInheritedMetadata(path24.dirname(eventsPath), 448);
24443
+ await ensureDirectoryWithInheritedMetadata(path25.dirname(eventsPath), 448);
23918
24444
  const state = await readUsageProbeState(paths);
23919
24445
  const pluginState = state.profiles[normalizedProfile];
23920
24446
  const currentConfig = await readUsageProbeConfigStatus({
@@ -24047,7 +24573,7 @@ async function findHermesUsageProbeEventForRun(input) {
24047
24573
  return aggregateUsageProbeEvents(events.sort(compareUsageProbeEventsByTime));
24048
24574
  }
24049
24575
  function resolveUsageProbeEventsPath(paths = resolveRuntimePaths(), profileName = "default") {
24050
- return path24.join(
24576
+ return path25.join(
24051
24577
  paths.homeDir,
24052
24578
  USAGE_PROBE_DIR,
24053
24579
  safeProfileSegment(profileName),
@@ -24071,8 +24597,8 @@ function summarizeUsageProbeEnsure(result) {
24071
24597
  };
24072
24598
  }
24073
24599
  async function writeUsageProbePlugin(input) {
24074
- const manifestPath = path24.join(input.pluginPath, "plugin.yaml");
24075
- const initPath = path24.join(input.pluginPath, "__init__.py");
24600
+ const manifestPath = path25.join(input.pluginPath, "plugin.yaml");
24601
+ const initPath = path25.join(input.pluginPath, "__init__.py");
24076
24602
  const manifest = usageProbeManifest();
24077
24603
  const source = usageProbePythonSource(
24078
24604
  input.profileName,
@@ -24419,9 +24945,9 @@ async function disableUsageProbeAfterActivationFailure(input) {
24419
24945
  });
24420
24946
  }
24421
24947
  async function cleanupLegacyUsageProbePlugin(profilePath) {
24422
- const legacyPath = path24.join(profilePath, "plugins", LEGACY_PLUGIN_KEY);
24948
+ const legacyPath = path25.join(profilePath, "plugins", LEGACY_PLUGIN_KEY);
24423
24949
  const [manifest, init] = await Promise.all([
24424
- readFile14(path24.join(legacyPath, "plugin.yaml"), "utf8").catch(
24950
+ readFile14(path25.join(legacyPath, "plugin.yaml"), "utf8").catch(
24425
24951
  (error) => {
24426
24952
  if (isNodeError16(error, "ENOENT")) {
24427
24953
  return null;
@@ -24429,7 +24955,7 @@ async function cleanupLegacyUsageProbePlugin(profilePath) {
24429
24955
  throw error;
24430
24956
  }
24431
24957
  ),
24432
- readFile14(path24.join(legacyPath, "__init__.py"), "utf8").catch(
24958
+ readFile14(path25.join(legacyPath, "__init__.py"), "utf8").catch(
24433
24959
  (error) => {
24434
24960
  if (isNodeError16(error, "ENOENT")) {
24435
24961
  return null;
@@ -24478,7 +25004,7 @@ async function rememberUsageProbeState(paths, profileName, patch) {
24478
25004
  }));
24479
25005
  }
24480
25006
  function usageProbeStatePath(paths) {
24481
- return path24.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
25007
+ return path25.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
24482
25008
  }
24483
25009
  async function readHermesConfigDocument2(configPath, language) {
24484
25010
  const existingRaw = await readFile14(configPath, "utf8").catch(
@@ -25046,7 +25572,7 @@ function toRecord14(value) {
25046
25572
 
25047
25573
  // src/conversations/run-transcript-enrichment.ts
25048
25574
  import { readFile as readFile15, stat as stat17 } from "fs/promises";
25049
- import path25 from "path";
25575
+ import path26 from "path";
25050
25576
  var MESSAGE_COLUMNS2 = [
25051
25577
  "id",
25052
25578
  "session_id",
@@ -25122,8 +25648,8 @@ async function readRunFinalAssistantText(input) {
25122
25648
  }
25123
25649
  async function readHermesTranscriptRows(profileName, sessionId) {
25124
25650
  const profileDir = resolveHermesProfileDir(profileName);
25125
- const dbPath = path25.join(profileDir, "state.db");
25126
- const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path25.join(profileDir, "sessions"));
25651
+ const dbPath = path26.join(profileDir, "state.db");
25652
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path26.join(profileDir, "sessions"));
25127
25653
  const [dbRows, jsonlRows] = await Promise.all([
25128
25654
  readStateDbMessages2(dbPath, sessionId),
25129
25655
  readJsonlMessages2(sessionsDir, sessionId)
@@ -25186,7 +25712,7 @@ async function readJsonlMessages2(sessionsDir, sessionId) {
25186
25712
  if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
25187
25713
  return [];
25188
25714
  }
25189
- const transcriptPath = path25.join(sessionsDir, `${sessionId}.jsonl`);
25715
+ const transcriptPath = path26.join(sessionsDir, `${sessionId}.jsonl`);
25190
25716
  const raw = await readFile15(transcriptPath, "utf8").catch((error) => {
25191
25717
  if (isNodeError17(error, "ENOENT")) {
25192
25718
  return "";
@@ -25453,7 +25979,7 @@ var RunToolEventIdCoalescer = class {
25453
25979
  if (!type || hasStableToolEventId(event.payload)) {
25454
25980
  return event;
25455
25981
  }
25456
- const toolKey = normalizeToolKey(readToolName(event.payload));
25982
+ const toolKey = normalizeToolKey(readToolName2(event.payload));
25457
25983
  if (type === "tool.started") {
25458
25984
  return withGeneratedToolEventId(
25459
25985
  event,
@@ -25544,7 +26070,7 @@ function hasStableToolEventId(payload) {
25544
26070
  readString17(payload, "tool_call_id") ?? readString17(payload, "toolCallId") ?? readString17(payload, "tool_id") ?? readString17(payload, "call_id") ?? readString17(payload, "id") ?? readString17(tool, "id") ?? readString17(call, "id") ?? readString17(fn, "id")
25545
26071
  );
25546
26072
  }
25547
- function readToolName(payload) {
26073
+ function readToolName2(payload) {
25548
26074
  const tool = toRecord16(payload.tool);
25549
26075
  const call = toRecord16(payload.tool_call ?? payload.toolCall);
25550
26076
  const fn = toRecord16(call.function ?? payload.function);
@@ -26014,6 +26540,96 @@ function toRecord17(value) {
26014
26540
  return typeof value === "object" && value !== null ? value : {};
26015
26541
  }
26016
26542
 
26543
+ // src/conversations/goal-lifecycle-marker.ts
26544
+ var GOAL_LIFECYCLE_MARKER_FORMAT = "hermes-link-lifecycle-marker";
26545
+ var GOAL_COMPLETION_MARKER_KIND = "goal_completion";
26546
+ function createGoalCompletionMarker(input) {
26547
+ const text = goalCompletionMarkerText(input.goal, input.language);
26548
+ return {
26549
+ id: goalCompletionMarkerId(input.runId),
26550
+ schema_version: 1,
26551
+ conversation_id: input.conversationId,
26552
+ role: "system",
26553
+ status: "completed",
26554
+ run_id: input.runId,
26555
+ created_at: input.completedAt,
26556
+ updated_at: input.completedAt,
26557
+ sender: {
26558
+ id: "hermes_link",
26559
+ type: "system",
26560
+ display_name: "Hermes Link"
26561
+ },
26562
+ parts: [{ type: "text", text }],
26563
+ attachments: [],
26564
+ raw: {
26565
+ format: GOAL_LIFECYCLE_MARKER_FORMAT,
26566
+ payload: {
26567
+ kind: GOAL_COMPLETION_MARKER_KIND,
26568
+ status: "completed",
26569
+ completed_at: input.completedAt,
26570
+ run_id: input.runId,
26571
+ text,
26572
+ ...Number.isFinite(input.goal.turns_used) ? { turns_used: input.goal.turns_used } : {},
26573
+ ...Number.isFinite(input.goal.max_turns) ? { max_turns: input.goal.max_turns } : {},
26574
+ ...input.goal.usage ? { usage: input.goal.usage } : {}
26575
+ }
26576
+ }
26577
+ };
26578
+ }
26579
+ function goalCompletionMarkerText(goal, language) {
26580
+ const parts = [localizedText2(language, "\u76EE\u6807\u5DF2\u5B8C\u6210", "Goal completed")];
26581
+ const turns = goalTurnsLabel(goal, language);
26582
+ if (turns) {
26583
+ parts.push(turns);
26584
+ }
26585
+ const totalTokens = finiteNonNegativeNumber(goal.usage?.total_tokens);
26586
+ if (totalTokens !== void 0) {
26587
+ parts.push(`${formatCompactNumber(totalTokens)} tokens`);
26588
+ }
26589
+ return parts.join(" ");
26590
+ }
26591
+ function goalCompletionMarkerId(runId) {
26592
+ const safeRunId = runId.trim().replace(/[^a-zA-Z0-9_:-]/gu, "_");
26593
+ return `msg_goal_completed_${safeRunId || "run"}`;
26594
+ }
26595
+ function goalTurnsLabel(goal, language) {
26596
+ const turnsUsed = finiteNonNegativeNumber(goal.turns_used);
26597
+ const maxTurns = finiteNonNegativeNumber(goal.max_turns);
26598
+ if (turnsUsed !== void 0 && maxTurns !== void 0) {
26599
+ return localizedText2(
26600
+ language,
26601
+ `${turnsUsed}/${maxTurns} \u8F6E`,
26602
+ `${turnsUsed}/${maxTurns} turns`
26603
+ );
26604
+ }
26605
+ if (turnsUsed !== void 0) {
26606
+ return localizedText2(language, `${turnsUsed} \u8F6E`, `${turnsUsed} turns`);
26607
+ }
26608
+ return null;
26609
+ }
26610
+ function finiteNonNegativeNumber(value) {
26611
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
26612
+ return void 0;
26613
+ }
26614
+ return Math.floor(value);
26615
+ }
26616
+ function formatCompactNumber(value) {
26617
+ if (value >= 1e6) {
26618
+ return `${trimTrailingDecimal(value / 1e6)}m`;
26619
+ }
26620
+ if (value >= 1e3) {
26621
+ return `${trimTrailingDecimal(value / 1e3)}k`;
26622
+ }
26623
+ return value.toLocaleString("en-US");
26624
+ }
26625
+ function trimTrailingDecimal(value) {
26626
+ const rounded = Math.round(value * 10) / 10;
26627
+ return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
26628
+ }
26629
+ function localizedText2(language, zh, en) {
26630
+ return language === "en" ? en : zh;
26631
+ }
26632
+
26017
26633
  // src/conversations/run-lifecycle.ts
26018
26634
  var RUN_STATUS_RECOVERY_TIMEOUT_MS = 10 * 60 * 1e3;
26019
26635
  var RUN_STATUS_RECOVERY_INITIAL_DELAY_MS = 500;
@@ -27957,6 +28573,13 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
27957
28573
  completedAt,
27958
28574
  "expired"
27959
28575
  );
28576
+ const goalCompletionMarker = run.mode === "goal" ? await this.appendGoalCompletionMarkerAfterAssistant({
28577
+ conversationId,
28578
+ snapshot,
28579
+ run,
28580
+ goal: goalUpdate ?? void 0,
28581
+ completedAt
28582
+ }) : null;
27960
28583
  await this.deps.writeSnapshot(conversationId, snapshot);
27961
28584
  if (goalUpdate) {
27962
28585
  await this.deps.appendEvent(conversationId, {
@@ -27968,6 +28591,14 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
27968
28591
  }
27969
28592
  });
27970
28593
  }
28594
+ if (goalCompletionMarker) {
28595
+ await this.deps.appendEvent(conversationId, {
28596
+ type: "message.created",
28597
+ message_id: goalCompletionMarker.id,
28598
+ run_id: runId,
28599
+ payload: { message: goalCompletionMarker }
28600
+ });
28601
+ }
27971
28602
  await this.appendExpiredApprovalEvents(
27972
28603
  conversationId,
27973
28604
  runId,
@@ -28008,6 +28639,24 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
28008
28639
  occurredAt: completedAt
28009
28640
  });
28010
28641
  }
28642
+ async appendGoalCompletionMarkerAfterAssistant(input) {
28643
+ const goal = input.goal ?? (await this.deps.readRunnableManifest(input.conversationId)).command_state?.goal;
28644
+ if (goal?.status !== "done") {
28645
+ return null;
28646
+ }
28647
+ if (findGoalCompletionMarker(input.snapshot, input.run.id)) {
28648
+ return null;
28649
+ }
28650
+ const marker = createGoalCompletionMarker({
28651
+ conversationId: input.conversationId,
28652
+ runId: input.run.id,
28653
+ goal,
28654
+ completedAt: input.completedAt,
28655
+ language: input.run.language === "en" ? "en" : "zh-CN"
28656
+ });
28657
+ insertLifecycleMarkerAfterAssistant(input.snapshot, marker, input.run);
28658
+ return marker;
28659
+ }
28011
28660
  async persistGoalUsageFromRunLocked(conversationId, run) {
28012
28661
  const manifest = await this.deps.readRunnableManifest(conversationId);
28013
28662
  const previousGoal = manifest.command_state?.goal;
@@ -28536,6 +29185,23 @@ function insertLifecycleMarkerBeforeAssistant(snapshot, marker, run) {
28536
29185
  }
28537
29186
  snapshot.messages.push(marker);
28538
29187
  }
29188
+ function insertLifecycleMarkerAfterAssistant(snapshot, marker, run) {
29189
+ const existingIndex = snapshot.messages.findIndex(
29190
+ (message) => message.id === marker.id
29191
+ );
29192
+ if (existingIndex >= 0) {
29193
+ snapshot.messages[existingIndex] = marker;
29194
+ return;
29195
+ }
29196
+ const assistantIndex = snapshot.messages.findIndex(
29197
+ (message) => message.id === run.assistant_message_id
29198
+ );
29199
+ if (assistantIndex >= 0) {
29200
+ snapshot.messages.splice(assistantIndex + 1, 0, marker);
29201
+ return;
29202
+ }
29203
+ snapshot.messages.push(marker);
29204
+ }
28539
29205
  function timestampBeforeAssistantForRun(snapshot, run, fallback) {
28540
29206
  const user = snapshot.messages.find(
28541
29207
  (message) => message.id === run.trigger_message_id
@@ -28561,6 +29227,12 @@ function findContextCompressionMarker(snapshot, operationId) {
28561
29227
  return message.raw?.format === CONTEXT_COMPRESSION_MARKER_FORMAT && payload.kind === CONTEXT_COMPRESSION_MARKER_KIND && payload.operation_id === operationId;
28562
29228
  });
28563
29229
  }
29230
+ function findGoalCompletionMarker(snapshot, runId) {
29231
+ return snapshot.messages.find((message) => {
29232
+ const payload = toRecord18(message.raw?.payload);
29233
+ return message.raw?.format === GOAL_LIFECYCLE_MARKER_FORMAT && payload.kind === GOAL_COMPLETION_MARKER_KIND && payload.run_id === runId;
29234
+ });
29235
+ }
28564
29236
  function nextContextCompressionGeneration(snapshot) {
28565
29237
  let maxGeneration = 0;
28566
29238
  for (const message of snapshot.messages) {
@@ -30659,7 +31331,7 @@ var ConversationService = class {
30659
31331
  }
30660
31332
  }
30661
31333
  hermesArchiveStateSyncMarkerPath() {
30662
- return path26.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
31334
+ return path27.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
30663
31335
  }
30664
31336
  prepareClearAllConversationPlan(targetStatus) {
30665
31337
  return this.maintenance.prepareClearAllConversationPlan(targetStatus);
@@ -30896,6 +31568,7 @@ function buildLiveActivityEvent(input) {
30896
31568
  return null;
30897
31569
  }
30898
31570
  const language = run?.language === "en" ? "en" : "zh";
31571
+ const todoProgress = liveActivityTodoProgressForEvent(input.event);
30899
31572
  const text = liveActivityTextForEvent({
30900
31573
  event: input.event,
30901
31574
  snapshot: input.snapshot,
@@ -30919,11 +31592,56 @@ function buildLiveActivityEvent(input) {
30919
31592
  statusLabel: text.statusLabel,
30920
31593
  progressText: text.progressText,
30921
31594
  detailText: text.detailText,
31595
+ todoDoneCount: todoProgress?.doneCount,
31596
+ todoTotalCount: todoProgress?.totalCount,
31597
+ todoProgressText: todoProgress?.progressText,
30922
31598
  requiresUserAction: phase === "needs_input" || phase === "needs_approval",
30923
31599
  terminal: isLiveActivityTerminalEvent(phase, target.kind),
30924
31600
  occurredAt: input.event.created_at
30925
31601
  };
30926
31602
  }
31603
+ function liveActivityTodoProgressForEvent(event) {
31604
+ if (event.type.toLowerCase() !== "tool.completed") {
31605
+ return null;
31606
+ }
31607
+ const payload = readRecord2(event.payload);
31608
+ const toolName = readToolName3(payload)?.toLowerCase();
31609
+ if (toolName !== "todo") {
31610
+ return null;
31611
+ }
31612
+ const todos = readTodoItems2(payload.todos) ?? readTodoItems2(readRecord2(payload.result).todos);
31613
+ if (todos) {
31614
+ const totalCount2 = todos.length;
31615
+ const doneCount2 = todos.filter((item) => {
31616
+ const status = readString20(item, "status")?.toLowerCase();
31617
+ return status === "completed" || status === "cancelled" || status === "canceled";
31618
+ }).length;
31619
+ return {
31620
+ doneCount: doneCount2,
31621
+ totalCount: totalCount2,
31622
+ progressText: totalCount2 > 0 ? `${doneCount2}/${totalCount2}` : void 0
31623
+ };
31624
+ }
31625
+ const summary = readRecord2(payload.summary).total === void 0 ? readRecord2(readRecord2(payload.result).summary) : readRecord2(payload.summary);
31626
+ const totalCount = readInteger4(summary, "total");
31627
+ if (totalCount === null) {
31628
+ return null;
31629
+ }
31630
+ const completedCount = readInteger4(summary, "completed") ?? 0;
31631
+ const cancelledCount = readInteger4(summary, "cancelled") ?? readInteger4(summary, "canceled") ?? 0;
31632
+ const doneCount = Math.min(totalCount, completedCount + cancelledCount);
31633
+ return {
31634
+ doneCount,
31635
+ totalCount,
31636
+ progressText: totalCount > 0 ? `${doneCount}/${totalCount}` : void 0
31637
+ };
31638
+ }
31639
+ function readTodoItems2(value) {
31640
+ if (!Array.isArray(value)) {
31641
+ return null;
31642
+ }
31643
+ return value.map((item) => readRecord2(item)).filter((item) => Object.keys(item).length > 0);
31644
+ }
30927
31645
  function liveActivityConversationTitle(manifest, event, phase, language) {
30928
31646
  if (phase === "goal_running" || phase === "goal_paused") {
30929
31647
  const goal = manifest.command_state?.goal;
@@ -31009,7 +31727,7 @@ function liveActivityPhaseForEvent(event, run, contextOperation) {
31009
31727
  return run?.mode === "goal" ? "goal_running" : "accepted";
31010
31728
  }
31011
31729
  if (type === "conversation.goal.updated") {
31012
- const goal = readRecord(event.payload).goal;
31730
+ const goal = readRecord2(event.payload).goal;
31013
31731
  const status = readString20(goal, "status");
31014
31732
  return status === "paused" ? "goal_paused" : "goal_running";
31015
31733
  }
@@ -31055,7 +31773,7 @@ function liveActivityPhaseForEvent(event, run, contextOperation) {
31055
31773
  return null;
31056
31774
  }
31057
31775
  function liveActivityTextForEvent(input) {
31058
- const toolName = readToolName2(input.event.payload);
31776
+ const toolName = readToolName3(input.event.payload);
31059
31777
  const assistantText = input.event.message_id ? input.snapshot.messages.find((message) => message.id === input.event.message_id) : null;
31060
31778
  const preview = input.event.type.toLowerCase() === "reasoning.available" ? liveActivityReasoningPreview(input.event.payload) : isLiveActivityTerminalEvent(input.phase, input.run?.kind === "compression" ? "context_compression" : "run") && assistantText ? notificationPreviewText(assistantText) : null;
31061
31779
  if (input.language === "en") {
@@ -31159,7 +31877,7 @@ function isLiveActivityTerminalEvent(phase, targetKind) {
31159
31877
  return targetKind === "context_compression" && (phase === "context_compressed" || phase === "context_compression_failed" || phase === "context_compression_timed_out");
31160
31878
  }
31161
31879
  function readContextCompressionOperation(payload) {
31162
- const operation = readRecord(payload).operation;
31880
+ const operation = readRecord2(payload).operation;
31163
31881
  if (!operation || typeof operation !== "object") {
31164
31882
  return null;
31165
31883
  }
@@ -31176,17 +31894,25 @@ function readContextCompressionOperation(payload) {
31176
31894
  source: readString20(record, "source") === "manual" ? "manual" : "auto"
31177
31895
  };
31178
31896
  }
31179
- function readToolName2(payload) {
31180
- const record = readRecord(payload);
31181
- return readString20(record, "tool_name") ?? readString20(record, "tool") ?? readString20(record, "name") ?? readString20(readRecord(record.tool), "name");
31897
+ function readToolName3(payload) {
31898
+ const record = readRecord2(payload);
31899
+ return readString20(record, "tool_name") ?? readString20(record, "tool") ?? readString20(record, "name") ?? readString20(readRecord2(record.tool), "name");
31182
31900
  }
31183
- function readRecord(value) {
31901
+ function readRecord2(value) {
31184
31902
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
31185
31903
  }
31186
31904
  function readString20(value, key) {
31187
- const raw = readRecord(value)[key];
31905
+ const raw = readRecord2(value)[key];
31188
31906
  return typeof raw === "string" && raw.trim() ? raw.trim() : null;
31189
31907
  }
31908
+ function readInteger4(value, key) {
31909
+ const raw = readRecord2(value)[key];
31910
+ const parsed = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() ? Number(raw) : Number.NaN;
31911
+ if (!Number.isFinite(parsed)) {
31912
+ return null;
31913
+ }
31914
+ return Math.max(0, Math.trunc(parsed));
31915
+ }
31190
31916
  function approvalRestartText(language, zh, en) {
31191
31917
  return language === "en" ? en : zh;
31192
31918
  }
@@ -31806,7 +32532,7 @@ function readQueryString(value) {
31806
32532
  const trimmed = raw.trim();
31807
32533
  return trimmed ? trimmed : void 0;
31808
32534
  }
31809
- function readInteger4(value) {
32535
+ function readInteger5(value) {
31810
32536
  const raw = Array.isArray(value) ? value[0] : value;
31811
32537
  if (typeof raw !== "string") {
31812
32538
  return void 0;
@@ -32905,7 +33631,7 @@ function toConversationSummary(value) {
32905
33631
  return candidate;
32906
33632
  }
32907
33633
  function resolveConversationEventCursor(input) {
32908
- const queryAfter = readInteger4(input.queryAfter) ?? 0;
33634
+ const queryAfter = readInteger5(input.queryAfter) ?? 0;
32909
33635
  const headerAfter = readNonNegativeIntegerHeader(input.lastEventIdHeader) ?? 0;
32910
33636
  return Math.max(queryAfter, headerAfter);
32911
33637
  }
@@ -33051,11 +33777,11 @@ function isSseRequestContext(ctx) {
33051
33777
  }
33052
33778
  return isSseRequestPath(ctx.path) || isActiveSseSocket(ctx.req.socket);
33053
33779
  }
33054
- function isSseRequestPath(path37) {
33055
- if (!path37) {
33780
+ function isSseRequestPath(path38) {
33781
+ if (!path38) {
33056
33782
  return false;
33057
33783
  }
33058
- return path37 === "/api/v1/conversations/events" || path37 === "/api/v1/profile-creation/events" || path37 === "/api/v1/hermes/update/events" || path37 === "/api/v1/link/update/events" || /^\/api\/v1\/conversations\/[^/]+\/events$/u.test(path37) || /^\/api\/v1\/runs\/[^/]+\/events$/u.test(path37);
33784
+ return path38 === "/api/v1/conversations/events" || path38 === "/api/v1/profile-creation/events" || path38 === "/api/v1/hermes/update/events" || path38 === "/api/v1/link/update/events" || /^\/api\/v1\/conversations\/[^/]+\/events$/u.test(path38) || /^\/api\/v1\/runs\/[^/]+\/events$/u.test(path38);
33059
33785
  }
33060
33786
  function isExpectedClientDisconnectError2(error, options = {}) {
33061
33787
  if (!(error instanceof Error)) {
@@ -33494,7 +34220,7 @@ import { createHash as createHash10 } from "crypto";
33494
34220
  // src/model-catalog/catalog.ts
33495
34221
  import { randomInt } from "crypto";
33496
34222
  import { mkdir as mkdir12 } from "fs/promises";
33497
- import path27 from "path";
34223
+ import path28 from "path";
33498
34224
  import { fileURLToPath } from "url";
33499
34225
  var MODEL_CATALOG_CACHE_VERSION = 1;
33500
34226
  var MODEL_CATALOG_FETCH_TIMEOUT_MS = 1e4;
@@ -33698,7 +34424,7 @@ async function readCachedCatalogFile(paths) {
33698
34424
  return cached;
33699
34425
  }
33700
34426
  async function writeCachedCatalog(paths, value) {
33701
- await mkdir12(path27.dirname(modelCatalogCachePath(paths)), {
34427
+ await mkdir12(path28.dirname(modelCatalogCachePath(paths)), {
33702
34428
  recursive: true,
33703
34429
  mode: 448
33704
34430
  });
@@ -33714,7 +34440,7 @@ async function readFallbackCatalog() {
33714
34440
  throw new Error("model capability fallback catalog was not found");
33715
34441
  }
33716
34442
  function modelCatalogCachePath(paths) {
33717
- return path27.join(paths.homeDir, "model-capabilities", "catalog-cache.json");
34443
+ return path28.join(paths.homeDir, "model-capabilities", "catalog-cache.json");
33718
34444
  }
33719
34445
  function normalizeModelCapabilityCatalog(value) {
33720
34446
  if (!value || typeof value !== "object") {
@@ -35193,7 +35919,7 @@ function errorMessage3(error) {
35193
35919
 
35194
35920
  // src/hermes/profile-identity.ts
35195
35921
  import { readFile as readFile16, stat as stat18 } from "fs/promises";
35196
- import path28 from "path";
35922
+ import path29 from "path";
35197
35923
  var MAX_SOUL_MD_LENGTH = 2e4;
35198
35924
  async function readHermesProfileIdentity(profileName, paths) {
35199
35925
  await assertProfileExists3(profileName, paths);
@@ -35250,7 +35976,7 @@ async function assertProfileExists3(profileName, paths) {
35250
35976
  }
35251
35977
  }
35252
35978
  function resolveSoulPath(profileName) {
35253
- return path28.join(resolveHermesProfileDir(profileName), "SOUL.md");
35979
+ return path29.join(resolveHermesProfileDir(profileName), "SOUL.md");
35254
35980
  }
35255
35981
  function isNodeError19(error, code) {
35256
35982
  return error instanceof Error && "code" in error && error.code === code;
@@ -35266,13 +35992,13 @@ import {
35266
35992
  rm as rm7,
35267
35993
  stat as stat20
35268
35994
  } from "fs/promises";
35269
- import path30 from "path";
35995
+ import path31 from "path";
35270
35996
  import YAML5 from "yaml";
35271
35997
 
35272
35998
  // src/hermes/link-skill.ts
35273
35999
  import { readFile as readFile17, stat as stat19 } from "fs/promises";
35274
36000
  import os7 from "os";
35275
- import path29 from "path";
36001
+ import path30 from "path";
35276
36002
  import YAML4 from "yaml";
35277
36003
  var HERMES_LINK_SKILL_ROOT_DIR = "hermes-skills";
35278
36004
  var HERMES_LINK_SKILL_DIR = "hermes-link";
@@ -35357,7 +36083,7 @@ Do not modify Hermes profiles, delete user data, edit config files, or kill proc
35357
36083
  async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
35358
36084
  const paths = options.paths ?? resolveRuntimePaths();
35359
36085
  const externalDir = resolveHermesLinkSkillExternalDir(paths);
35360
- const skillPath = path29.join(
36086
+ const skillPath = path30.join(
35361
36087
  externalDir,
35362
36088
  HERMES_LINK_SKILL_DIR,
35363
36089
  HERMES_LINK_SKILL_FILE
@@ -35439,7 +36165,7 @@ function withDefaultProfilePlaceholder2(profiles) {
35439
36165
  ];
35440
36166
  }
35441
36167
  function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
35442
- return path29.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
36168
+ return path30.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
35443
36169
  }
35444
36170
  async function writeHermesLinkSkill(skillPath) {
35445
36171
  const existing = await readFile17(skillPath, "utf8").catch((error) => {
@@ -35534,11 +36260,11 @@ function appendExternalDir(current, externalDir, hermesHome) {
35534
36260
  const seen = new Set(
35535
36261
  entries.map((entry) => resolveExternalDirEntry(entry, hermesHome))
35536
36262
  );
35537
- const normalizedExternalDir = path29.resolve(externalDir);
36263
+ const normalizedExternalDir = path30.resolve(externalDir);
35538
36264
  return seen.has(normalizedExternalDir) ? entries : [...entries, normalizedExternalDir];
35539
36265
  }
35540
36266
  function externalDirsInclude(current, externalDir, hermesHome) {
35541
- const normalizedExternalDir = path29.resolve(externalDir);
36267
+ const normalizedExternalDir = path30.resolve(externalDir);
35542
36268
  return readExternalDirEntries(current).some(
35543
36269
  (entry) => resolveExternalDirEntry(entry, hermesHome) === normalizedExternalDir
35544
36270
  );
@@ -35549,14 +36275,14 @@ function readExternalDirEntries(value) {
35549
36275
  }
35550
36276
  function resolveExternalDirEntry(entry, hermesHome) {
35551
36277
  const expanded = expandHome(expandEnvVars(entry));
35552
- return path29.resolve(path29.isAbsolute(expanded) ? expanded : path29.join(hermesHome, expanded));
36278
+ return path30.resolve(path30.isAbsolute(expanded) ? expanded : path30.join(hermesHome, expanded));
35553
36279
  }
35554
36280
  function expandHome(value) {
35555
36281
  if (value === "~") {
35556
36282
  return os7.homedir();
35557
36283
  }
35558
- if (value.startsWith(`~${path29.sep}`) || value.startsWith("~/")) {
35559
- return path29.join(os7.homedir(), value.slice(2));
36284
+ if (value.startsWith(`~${path30.sep}`) || value.startsWith("~/")) {
36285
+ return path30.join(os7.homedir(), value.slice(2));
35560
36286
  }
35561
36287
  return value;
35562
36288
  }
@@ -35795,7 +36521,7 @@ async function readHermesProfileCreationStatus(paths) {
35795
36521
  let state = await readJsonFile(
35796
36522
  profileCreationStatePath(paths)
35797
36523
  );
35798
- if (state?.state === "running" && !runningProfileCreation && !isRecentRunningState(state) && !isProcessAlive(state.pid)) {
36524
+ if (state?.state === "running" && !runningProfileCreation && !isRecentRunningState(state) && !isProcessAlive2(state.pid)) {
35799
36525
  state = {
35800
36526
  ...state,
35801
36527
  state: "failed",
@@ -36108,7 +36834,7 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
36108
36834
  return keys;
36109
36835
  }
36110
36836
  async function writeEnvValues(profileName, values) {
36111
- const envPath = path30.join(resolveHermesProfileDir(profileName), ".env");
36837
+ const envPath = path31.join(resolveHermesProfileDir(profileName), ".env");
36112
36838
  const existingRaw = await readFile18(envPath, "utf8").catch((error) => {
36113
36839
  if (isNodeError21(error, "ENOENT")) {
36114
36840
  return "";
@@ -36145,8 +36871,8 @@ async function writeEnvValues(profileName, values) {
36145
36871
  await atomicWriteFilePreservingMetadata(envPath, nextRaw);
36146
36872
  }
36147
36873
  async function copySkills(sourceProfile, targetProfile) {
36148
- const sourceSkills = path30.join(resolveHermesProfileDir(sourceProfile), "skills");
36149
- const targetSkills = path30.join(resolveHermesProfileDir(targetProfile), "skills");
36874
+ const sourceSkills = path31.join(resolveHermesProfileDir(sourceProfile), "skills");
36875
+ const targetSkills = path31.join(resolveHermesProfileDir(targetProfile), "skills");
36150
36876
  if (!await pathExists2(sourceSkills)) {
36151
36877
  return;
36152
36878
  }
@@ -36241,10 +36967,10 @@ async function readProfileCreationLogLines(paths) {
36241
36967
  );
36242
36968
  }
36243
36969
  function profileCreationStatePath(paths) {
36244
- return path30.join(paths.runDir, "profile-create-state.json");
36970
+ return path31.join(paths.runDir, "profile-create-state.json");
36245
36971
  }
36246
36972
  function profileCreationLogPath(paths) {
36247
- return path30.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
36973
+ return path31.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
36248
36974
  }
36249
36975
  async function clearProfileCreationLogFiles(paths) {
36250
36976
  const primary = profileCreationLogPath(paths);
@@ -36270,7 +36996,7 @@ function isRecentRunningState(state) {
36270
36996
  }
36271
36997
  return Date.now() - Date.parse(state.started_at) < 1e4;
36272
36998
  }
36273
- function isProcessAlive(pid) {
36999
+ function isProcessAlive2(pid) {
36274
37000
  if (!pid || pid <= 0) {
36275
37001
  return false;
36276
37002
  }
@@ -36533,7 +37259,7 @@ import {
36533
37259
  readFile as readFile19,
36534
37260
  stat as stat21
36535
37261
  } from "fs/promises";
36536
- import path31 from "path";
37262
+ import path32 from "path";
36537
37263
  import YAML6 from "yaml";
36538
37264
  var ENTRY_DELIMITER = "\n\xA7\n";
36539
37265
  var DEFAULT_MEMORY_LIMIT = 2200;
@@ -36864,7 +37590,7 @@ async function saveProviderSettings(profileName, provider, patch) {
36864
37590
  });
36865
37591
  await patchJsonProviderConfig(
36866
37592
  profileName,
36867
- path31.join("hindsight", "config.json"),
37593
+ path32.join("hindsight", "config.json"),
36868
37594
  {
36869
37595
  mode: patch.mode,
36870
37596
  api_url: patch.apiUrl,
@@ -37067,7 +37793,7 @@ async function patchHermesMemoryLimits(profileName, patch) {
37067
37793
  await atomicWriteFilePreservingMetadata(configPath, document.toString());
37068
37794
  }
37069
37795
  function resolveMemoryDir(profileName) {
37070
- return path31.join(resolveHermesProfileDir(profileName), "memories");
37796
+ return path32.join(resolveHermesProfileDir(profileName), "memories");
37071
37797
  }
37072
37798
  async function readMemoryStore(profileName, target, limits) {
37073
37799
  const filePath = memoryFilePath(profileName, target);
@@ -37128,7 +37854,7 @@ async function writeMemoryEntries(profileName, target, entries) {
37128
37854
  );
37129
37855
  }
37130
37856
  function memoryFilePath(profileName, target) {
37131
- return path31.join(
37857
+ return path32.join(
37132
37858
  resolveMemoryDir(profileName),
37133
37859
  target === "user" ? "USER.md" : "MEMORY.md"
37134
37860
  );
@@ -37188,7 +37914,7 @@ async function readCustomProviderSetupSummary(profileName) {
37188
37914
  configurable: true,
37189
37915
  configured: true,
37190
37916
  configurationIssue: null,
37191
- providerConfigPath: path31.join(
37917
+ providerConfigPath: path32.join(
37192
37918
  resolveHermesProfileDir(profileName),
37193
37919
  "<provider>.json"
37194
37920
  ),
@@ -37582,7 +38308,7 @@ async function readProviderSettings(profileName, provider) {
37582
38308
  stringSetting(
37583
38309
  "dbPath",
37584
38310
  "SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
37585
- config.db_path ?? path31.join(resolveHermesProfileDir(profileName), "memory_store.db")
38311
+ config.db_path ?? path32.join(resolveHermesProfileDir(profileName), "memory_store.db")
37586
38312
  ),
37587
38313
  booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
37588
38314
  numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
@@ -37618,7 +38344,7 @@ async function readProviderSettings(profileName, provider) {
37618
38344
  stringSetting(
37619
38345
  "workingDirectory",
37620
38346
  "\u5DE5\u4F5C\u76EE\u5F55",
37621
- path31.join(resolveHermesProfileDir(profileName), "byterover"),
38347
+ path32.join(resolveHermesProfileDir(profileName), "byterover"),
37622
38348
  false
37623
38349
  )
37624
38350
  ];
@@ -37627,16 +38353,16 @@ async function readProviderSettings(profileName, provider) {
37627
38353
  }
37628
38354
  function memoryProviderConfigPath(profileName, provider) {
37629
38355
  if (provider === "honcho") {
37630
- return path31.join(resolveHermesProfileDir(profileName), "honcho.json");
38356
+ return path32.join(resolveHermesProfileDir(profileName), "honcho.json");
37631
38357
  }
37632
38358
  if (provider === "mem0") {
37633
- return path31.join(resolveHermesProfileDir(profileName), "mem0.json");
38359
+ return path32.join(resolveHermesProfileDir(profileName), "mem0.json");
37634
38360
  }
37635
38361
  if (provider === "supermemory") {
37636
- return path31.join(resolveHermesProfileDir(profileName), "supermemory.json");
38362
+ return path32.join(resolveHermesProfileDir(profileName), "supermemory.json");
37637
38363
  }
37638
38364
  if (provider === "hindsight") {
37639
- return path31.join(
38365
+ return path32.join(
37640
38366
  resolveHermesProfileDir(profileName),
37641
38367
  "hindsight",
37642
38368
  "config.json"
@@ -37645,13 +38371,13 @@ function memoryProviderConfigPath(profileName, provider) {
37645
38371
  return null;
37646
38372
  }
37647
38373
  function customProviderConfigPath(profileName, provider) {
37648
- return path31.join(
38374
+ return path32.join(
37649
38375
  resolveHermesProfileDir(profileName),
37650
38376
  `${normalizeCustomProviderId(provider)}.json`
37651
38377
  );
37652
38378
  }
37653
38379
  function customProviderRegistryPath(profileName) {
37654
- return path31.join(
38380
+ return path32.join(
37655
38381
  resolveHermesProfileDir(profileName),
37656
38382
  CUSTOM_PROVIDER_REGISTRY_FILE
37657
38383
  );
@@ -37703,7 +38429,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
37703
38429
  );
37704
38430
  }
37705
38431
  async function discoverUserMemoryProviderDescriptors(profileName) {
37706
- const pluginsDir = path31.join(resolveHermesProfileDir(profileName), "plugins");
38432
+ const pluginsDir = path32.join(resolveHermesProfileDir(profileName), "plugins");
37707
38433
  const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
37708
38434
  (error) => {
37709
38435
  if (isNodeError22(error, "ENOENT")) {
@@ -37723,7 +38449,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
37723
38449
  } catch {
37724
38450
  continue;
37725
38451
  }
37726
- const providerDir = path31.join(pluginsDir, entry.name);
38452
+ const providerDir = path32.join(pluginsDir, entry.name);
37727
38453
  if (!await isMemoryProviderPluginDir(providerDir)) {
37728
38454
  continue;
37729
38455
  }
@@ -37737,7 +38463,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
37737
38463
  return descriptors;
37738
38464
  }
37739
38465
  async function isUserMemoryProviderInstalled(profileName, provider) {
37740
- const providerDir = path31.join(
38466
+ const providerDir = path32.join(
37741
38467
  resolveHermesProfileDir(profileName),
37742
38468
  "plugins",
37743
38469
  normalizeCustomProviderId(provider)
@@ -37745,7 +38471,7 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
37745
38471
  return isMemoryProviderPluginDir(providerDir);
37746
38472
  }
37747
38473
  async function isMemoryProviderPluginDir(providerDir) {
37748
- const source = await readFile19(path31.join(providerDir, "__init__.py"), "utf8").catch(
38474
+ const source = await readFile19(path32.join(providerDir, "__init__.py"), "utf8").catch(
37749
38475
  (error) => {
37750
38476
  if (isNodeError22(error, "ENOENT")) {
37751
38477
  return "";
@@ -37757,7 +38483,7 @@ async function isMemoryProviderPluginDir(providerDir) {
37757
38483
  return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
37758
38484
  }
37759
38485
  async function readPluginMetadata(providerDir) {
37760
- const raw = await readFile19(path31.join(providerDir, "plugin.yaml"), "utf8").catch(
38486
+ const raw = await readFile19(path32.join(providerDir, "plugin.yaml"), "utf8").catch(
37761
38487
  (error) => {
37762
38488
  if (isNodeError22(error, "ENOENT")) {
37763
38489
  return "";
@@ -37769,10 +38495,10 @@ async function readPluginMetadata(providerDir) {
37769
38495
  }
37770
38496
  async function resolveByteRoverCli() {
37771
38497
  const candidates = [
37772
- ...(process.env.PATH ?? "").split(path31.delimiter).filter(Boolean).map((dir) => path31.join(dir, "brv")),
37773
- path31.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
38498
+ ...(process.env.PATH ?? "").split(path32.delimiter).filter(Boolean).map((dir) => path32.join(dir, "brv")),
38499
+ path32.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
37774
38500
  "/usr/local/bin/brv",
37775
- path31.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
38501
+ path32.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
37776
38502
  ].filter(Boolean);
37777
38503
  for (const candidate of candidates) {
37778
38504
  const found = await access3(candidate).then(() => true).catch(() => false);
@@ -37833,7 +38559,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
37833
38559
  if (entries.length === 0) {
37834
38560
  return;
37835
38561
  }
37836
- const envPath = path31.join(resolveHermesProfileDir(profileName), ".env");
38562
+ const envPath = path32.join(resolveHermesProfileDir(profileName), ".env");
37837
38563
  const existingRaw = await readFile19(envPath, "utf8").catch((error) => {
37838
38564
  if (isNodeError22(error, "ENOENT")) {
37839
38565
  return "";
@@ -38004,7 +38730,7 @@ async function readActiveMemoryProvider(profileName) {
38004
38730
  return provider;
38005
38731
  }
38006
38732
  async function patchJsonProviderConfig(profileName, relativePath, patch) {
38007
- const configPath = path31.join(
38733
+ const configPath = path32.join(
38008
38734
  resolveHermesProfileDir(profileName),
38009
38735
  relativePath
38010
38736
  );
@@ -38033,7 +38759,7 @@ async function readJsonObject(filePath) {
38033
38759
  } catch {
38034
38760
  throw new HermesMemoryError(
38035
38761
  "memory_provider_config_invalid",
38036
- `${path31.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
38762
+ `${path32.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
38037
38763
  );
38038
38764
  }
38039
38765
  }
@@ -38657,7 +39383,7 @@ function toMemoryHttpError(error) {
38657
39383
 
38658
39384
  // src/hermes/skills.ts
38659
39385
  import { readFile as readFile20, readdir as readdir11 } from "fs/promises";
38660
- import path32 from "path";
39386
+ import path33 from "path";
38661
39387
  import YAML7 from "yaml";
38662
39388
  var HermesSkillNotFoundError = class extends Error {
38663
39389
  constructor(skillName) {
@@ -38671,7 +39397,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
38671
39397
  async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
38672
39398
  const profile = await readExistingProfile(profileName, paths);
38673
39399
  const profileDir = resolveHermesProfileDir(profile.name);
38674
- const skillsRoot = path32.join(profileDir, "skills");
39400
+ const skillsRoot = path33.join(profileDir, "skills");
38675
39401
  const [skillFiles, disabled, provenance] = await Promise.all([
38676
39402
  findSkillFiles(skillsRoot),
38677
39403
  readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
@@ -38762,7 +39488,7 @@ async function collectSkillFiles(directory, results) {
38762
39488
  if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
38763
39489
  continue;
38764
39490
  }
38765
- const entryPath = path32.join(directory, entry.name);
39491
+ const entryPath = path33.join(directory, entry.name);
38766
39492
  if (entry.isDirectory()) {
38767
39493
  await collectSkillFiles(entryPath, results);
38768
39494
  continue;
@@ -38784,10 +39510,10 @@ async function readSkillMetadata(input) {
38784
39510
  if (raw === null) {
38785
39511
  return null;
38786
39512
  }
38787
- const skillDir = path32.dirname(input.skillFile);
39513
+ const skillDir = path33.dirname(input.skillFile);
38788
39514
  const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
38789
39515
  const name = normalizeSkillName(
38790
- readString24(frontmatter.name) ?? path32.basename(skillDir)
39516
+ readString24(frontmatter.name) ?? path33.basename(skillDir)
38791
39517
  );
38792
39518
  if (!name) {
38793
39519
  return null;
@@ -38806,7 +39532,7 @@ async function readSkillMetadata(input) {
38806
39532
  enabled: !input.disabled.has(name),
38807
39533
  source: provenance.source,
38808
39534
  trust: provenance.trust,
38809
- relativePath: path32.relative(input.skillsRoot, skillDir)
39535
+ relativePath: path33.relative(input.skillsRoot, skillDir)
38810
39536
  };
38811
39537
  }
38812
39538
  function parseSkillDocument(raw) {
@@ -38827,8 +39553,8 @@ function parseSkillDocument(raw) {
38827
39553
  }
38828
39554
  }
38829
39555
  function categoryFromPath(skillsRoot, skillFile) {
38830
- const relative = path32.relative(skillsRoot, skillFile);
38831
- const parts = relative.split(path32.sep).filter(Boolean);
39556
+ const relative = path33.relative(skillsRoot, skillFile);
39557
+ const parts = relative.split(path33.sep).filter(Boolean);
38832
39558
  return parts.length >= 3 ? parts[0] : null;
38833
39559
  }
38834
39560
  function firstBodyDescription(body) {
@@ -38875,7 +39601,7 @@ async function readSkillProvenance(root) {
38875
39601
  return provenance;
38876
39602
  }
38877
39603
  async function readBundledSkillNames(root) {
38878
- const raw = await readFile20(path32.join(root, ".bundled_manifest"), "utf8").catch(
39604
+ const raw = await readFile20(path33.join(root, ".bundled_manifest"), "utf8").catch(
38879
39605
  (error) => {
38880
39606
  if (isNodeError23(error, "ENOENT")) {
38881
39607
  return "";
@@ -38898,7 +39624,7 @@ async function readBundledSkillNames(root) {
38898
39624
  return names;
38899
39625
  }
38900
39626
  async function readHubInstalledSkills(root) {
38901
- const raw = await readFile20(path32.join(root, ".hub", "lock.json"), "utf8").catch(
39627
+ const raw = await readFile20(path33.join(root, ".hub", "lock.json"), "utf8").catch(
38902
39628
  (error) => {
38903
39629
  if (isNodeError23(error, "ENOENT")) {
38904
39630
  return "";
@@ -39472,7 +40198,7 @@ function registerStatisticsRoutes(router, options) {
39472
40198
  await authenticateRequest(ctx, paths);
39473
40199
  ctx.set("cache-control", "no-store");
39474
40200
  const usage = await readLinkUsageStatistics(paths, {
39475
- days: readInteger4(ctx.query.days),
40201
+ days: readInteger5(ctx.query.days),
39476
40202
  from: readQueryString(ctx.query.from),
39477
40203
  to: readQueryString(ctx.query.to)
39478
40204
  });
@@ -39566,7 +40292,7 @@ function readModelList(payload) {
39566
40292
  import { EventEmitter as EventEmitter3 } from "events";
39567
40293
  import { spawn as spawn4 } from "child_process";
39568
40294
  import { mkdir as mkdir14, readFile as readFile21, rm as rm8 } from "fs/promises";
39569
- import path33 from "path";
40295
+ import path34 from "path";
39570
40296
  var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
39571
40297
  var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
39572
40298
  var RELEASE_FETCH_TIMEOUT_MS = 5e3;
@@ -39713,7 +40439,7 @@ async function startHermesUpdate(options) {
39713
40439
  }
39714
40440
  async function readHermesUpdateStatus(paths) {
39715
40441
  let state = await readJsonFile(updateStatePath(paths));
39716
- if (state?.state === "running" && !runningUpdate && !isRecentRunningState2(state) && !isProcessAlive2(state.pid)) {
40442
+ if (state?.state === "running" && !runningUpdate && !isRecentRunningState2(state) && !isProcessAlive3(state.pid)) {
39717
40443
  state = {
39718
40444
  ...state,
39719
40445
  state: "failed",
@@ -39844,13 +40570,13 @@ async function readUpdateLogLines(paths) {
39844
40570
  );
39845
40571
  }
39846
40572
  function releaseCachePath(paths) {
39847
- return path33.join(paths.indexesDir, "hermes-release-check.json");
40573
+ return path34.join(paths.indexesDir, "hermes-release-check.json");
39848
40574
  }
39849
40575
  function updateStatePath(paths) {
39850
- return path33.join(paths.runDir, "hermes-update-state.json");
40576
+ return path34.join(paths.runDir, "hermes-update-state.json");
39851
40577
  }
39852
40578
  function updateLogPath(paths) {
39853
- return path33.join(paths.logsDir, UPDATE_LOG_FILE);
40579
+ return path34.join(paths.logsDir, UPDATE_LOG_FILE);
39854
40580
  }
39855
40581
  async function clearUpdateLogFiles(paths) {
39856
40582
  const primary = updateLogPath(paths);
@@ -39924,7 +40650,7 @@ async function readHermesReleaseCheckContext(paths) {
39924
40650
  releaseCheckUrl: url.toString()
39925
40651
  };
39926
40652
  }
39927
- function isProcessAlive2(pid) {
40653
+ function isProcessAlive3(pid) {
39928
40654
  if (!pid || pid <= 0) {
39929
40655
  return false;
39930
40656
  }
@@ -39951,12 +40677,12 @@ function readString25(payload, key) {
39951
40677
  import { spawn as spawn6 } from "child_process";
39952
40678
  import { EventEmitter as EventEmitter4 } from "events";
39953
40679
  import { mkdir as mkdir17, readFile as readFile23, rm as rm11 } from "fs/promises";
39954
- import path35 from "path";
40680
+ import path36 from "path";
39955
40681
 
39956
40682
  // src/daemon/process.ts
39957
40683
  import { spawn as spawn5 } from "child_process";
39958
40684
  import { mkdir as mkdir16, readFile as readFile22, rm as rm10, writeFile as writeFile4 } from "fs/promises";
39959
- import path34 from "path";
40685
+ import path35 from "path";
39960
40686
 
39961
40687
  // src/daemon/service.ts
39962
40688
  import { createServer } from "http";
@@ -40117,17 +40843,17 @@ async function fetchRelayStreamBatchPolicy(serverBaseUrl, options = {}) {
40117
40843
  }
40118
40844
  }
40119
40845
  function readRelayStreamBatchPolicy(input) {
40120
- const record = readRecord2(input);
40121
- const body = readRecord2(record?.policy) ?? readRecord2(record?.stream_batching) ?? record;
40846
+ const record = readRecord3(input);
40847
+ const body = readRecord3(record?.policy) ?? readRecord3(record?.stream_batching) ?? record;
40122
40848
  return normalizeRelayStreamBatchPolicy(body);
40123
40849
  }
40124
40850
  function normalizeRelayStreamBatchPolicy(input) {
40125
- const record = readRecord2(input);
40851
+ const record = readRecord3(input);
40126
40852
  if (!record) {
40127
40853
  return null;
40128
40854
  }
40129
- const flushIntervalMs = readInteger5(record.flushIntervalMs ?? record.flush_interval_ms);
40130
- const flushBytes = readInteger5(record.flushBytes ?? record.flush_bytes);
40855
+ const flushIntervalMs = readInteger6(record.flushIntervalMs ?? record.flush_interval_ms);
40856
+ const flushBytes = readInteger6(record.flushBytes ?? record.flush_bytes);
40131
40857
  if (flushIntervalMs === null || flushBytes === null || flushIntervalMs < RELAY_STREAM_POLICY_CONSTRAINTS.flushIntervalMs.min || flushIntervalMs > RELAY_STREAM_POLICY_CONSTRAINTS.flushIntervalMs.max || flushBytes < RELAY_STREAM_POLICY_CONSTRAINTS.flushBytes.min || flushBytes > RELAY_STREAM_POLICY_CONSTRAINTS.flushBytes.max) {
40132
40858
  return null;
40133
40859
  }
@@ -40136,10 +40862,10 @@ function normalizeRelayStreamBatchPolicy(input) {
40136
40862
  flushBytes
40137
40863
  };
40138
40864
  }
40139
- function readRecord2(value) {
40865
+ function readRecord3(value) {
40140
40866
  return value && typeof value === "object" ? value : null;
40141
40867
  }
40142
- function readInteger5(value) {
40868
+ function readInteger6(value) {
40143
40869
  return typeof value === "number" && Number.isInteger(value) ? value : null;
40144
40870
  }
40145
40871
 
@@ -40173,12 +40899,12 @@ function connectRelayControl(options) {
40173
40899
  onUpdate: options.onStreamBatchPolicy
40174
40900
  };
40175
40901
  const startConnect = () => {
40176
- void waitForPersistedCooldown().then((delay5) => {
40902
+ void waitForPersistedCooldown().then((delay6) => {
40177
40903
  if (closedByUser) {
40178
40904
  return;
40179
40905
  }
40180
- if (delay5 > 0) {
40181
- scheduleTimer(delay5, "cooldown", `Relay reconnect cooldown active for ${delay5}ms`);
40906
+ if (delay6 > 0) {
40907
+ scheduleTimer(delay6, "cooldown", `Relay reconnect cooldown active for ${delay6}ms`);
40182
40908
  return;
40183
40909
  }
40184
40910
  connect();
@@ -40316,8 +41042,8 @@ function connectRelayControl(options) {
40316
41042
  }
40317
41043
  if (recorded.cooldownUntilMs !== null) {
40318
41044
  reconnectAttempts = 0;
40319
- const delay6 = Math.max(0, recorded.cooldownUntilMs - Date.now());
40320
- scheduleTimer(delay6, "cooldown", `Relay reconnect storm guard active for ${delay6}ms`);
41045
+ const delay7 = Math.max(0, recorded.cooldownUntilMs - Date.now());
41046
+ scheduleTimer(delay7, "cooldown", `Relay reconnect storm guard active for ${delay7}ms`);
40321
41047
  return;
40322
41048
  }
40323
41049
  reconnectAttempts += 1;
@@ -40325,16 +41051,16 @@ function connectRelayControl(options) {
40325
41051
  baseMs: backoffBaseMs,
40326
41052
  maxMs: backoffMaxMs
40327
41053
  });
40328
- const delay5 = Math.max(backoffMs, relayRetryAfterMs ?? 0);
40329
- scheduleTimer(delay5, "retrying", `Retrying in ${delay5}ms`);
41054
+ const delay6 = Math.max(backoffMs, relayRetryAfterMs ?? 0);
41055
+ scheduleTimer(delay6, "retrying", `Retrying in ${delay6}ms`);
40330
41056
  }
40331
41057
  async function waitForPersistedCooldown() {
40332
41058
  return await readRelayCooldownDelayMs(paths).catch(() => 0);
40333
41059
  }
40334
- function scheduleTimer(delay5, state, message) {
41060
+ function scheduleTimer(delay6, state, message) {
40335
41061
  clearRetryTimer();
40336
41062
  options.onStatus?.({ state, attempt: reconnectAttempts, message });
40337
- retryTimer = setTimeout(connect, delay5);
41063
+ retryTimer = setTimeout(connect, delay6);
40338
41064
  retryTimer.unref?.();
40339
41065
  }
40340
41066
  function clearRetryTimer() {
@@ -41609,6 +42335,22 @@ async function startLinkService(options = {}) {
41609
42335
  await logger.flush();
41610
42336
  throw error;
41611
42337
  }
42338
+ const tuiGatewayCleanup = await cleanupRegisteredTuiGatewayProcesses({
42339
+ paths,
42340
+ logger
42341
+ }).catch((error) => {
42342
+ void logger.warn("tui_gateway_registry_startup_cleanup_failed", {
42343
+ error: error instanceof Error ? error.message : String(error)
42344
+ });
42345
+ return null;
42346
+ });
42347
+ if (tuiGatewayCleanup) {
42348
+ void logger.info("tui_gateway_registry_startup_cleanup_finished", {
42349
+ stopped: tuiGatewayCleanup.stopped.length,
42350
+ missing: tuiGatewayCleanup.missing.length,
42351
+ skipped: tuiGatewayCleanup.skipped.length
42352
+ });
42353
+ }
41612
42354
  server.on("error", (error) => {
41613
42355
  void logger.error("service_error", {
41614
42356
  port: config.port,
@@ -41734,6 +42476,7 @@ async function startLinkService(options = {}) {
41734
42476
  }
41735
42477
  closed = true;
41736
42478
  relay?.close();
42479
+ await closeTuiGatewayBackends({ paths, logger, cleanupRegistry: true });
41737
42480
  await closeServer(server);
41738
42481
  await Promise.all([
41739
42482
  scheduler.close(),
@@ -41949,7 +42692,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
41949
42692
  await mkdir16(paths.logsDir, { recursive: true, mode: 448 });
41950
42693
  const log = createRotatingTextLogWriter({
41951
42694
  paths,
41952
- fileName: path34.basename(daemonLogFile(paths))
42695
+ fileName: path35.basename(daemonLogFile(paths))
41953
42696
  });
41954
42697
  const scriptPath = currentCliScriptPath();
41955
42698
  const write = (chunk) => {
@@ -41968,7 +42711,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
41968
42711
  let stopRequested = false;
41969
42712
  const forwardStop = () => {
41970
42713
  stopRequested = true;
41971
- if (child?.pid && isProcessAlive3(child.pid)) {
42714
+ if (child?.pid && isProcessAlive4(child.pid)) {
41972
42715
  child.kill("SIGTERM");
41973
42716
  }
41974
42717
  };
@@ -42082,23 +42825,23 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
42082
42825
  }
42083
42826
  for (let index = 0; index < 20; index += 1) {
42084
42827
  await wait2(250);
42085
- if (!isProcessAlive3(status.pid)) {
42828
+ if (!isProcessAlive4(status.pid)) {
42086
42829
  break;
42087
42830
  }
42088
42831
  }
42089
- if (isProcessAlive3(status.pid)) {
42832
+ if (isProcessAlive4(status.pid)) {
42090
42833
  try {
42091
42834
  process.kill(status.pid, "SIGKILL");
42092
42835
  } catch {
42093
42836
  }
42094
42837
  for (let index = 0; index < 10; index += 1) {
42095
42838
  await wait2(250);
42096
- if (!isProcessAlive3(status.pid)) {
42839
+ if (!isProcessAlive4(status.pid)) {
42097
42840
  break;
42098
42841
  }
42099
42842
  }
42100
42843
  }
42101
- if (!isProcessAlive3(status.pid) || !await pidBackedServiceIsReachable(paths)) {
42844
+ if (!isProcessAlive4(status.pid) || !await pidBackedServiceIsReachable(paths)) {
42102
42845
  await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
42103
42846
  }
42104
42847
  return await getDaemonStatus(paths);
@@ -42106,7 +42849,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
42106
42849
  async function getDaemonStatus(paths = resolveRuntimePaths()) {
42107
42850
  const pidFile = pidFilePath(paths);
42108
42851
  const pid = await readPid(pidFile);
42109
- if (pid && !isProcessAlive3(pid)) {
42852
+ if (pid && !isProcessAlive4(pid)) {
42110
42853
  await rm10(pidFile, { force: true }).catch(() => void 0);
42111
42854
  return {
42112
42855
  running: false,
@@ -42136,7 +42879,7 @@ async function readPid(filePath) {
42136
42879
  const pid = Number.parseInt(raw.trim(), 10);
42137
42880
  return Number.isInteger(pid) && pid > 0 ? pid : null;
42138
42881
  }
42139
- function isProcessAlive3(pid) {
42882
+ function isProcessAlive4(pid) {
42140
42883
  try {
42141
42884
  process.kill(pid, 0);
42142
42885
  return true;
@@ -42178,7 +42921,7 @@ function startSupervisorHealthMonitor(paths, child, write) {
42178
42921
  );
42179
42922
  terminateChild(child, forceKillTimer);
42180
42923
  forceKillTimer = setTimeout(() => {
42181
- if (child.pid && isProcessAlive3(child.pid)) {
42924
+ if (child.pid && isProcessAlive4(child.pid)) {
42182
42925
  child.kill("SIGKILL");
42183
42926
  }
42184
42927
  }, SUPERVISOR_CHILD_STOP_TIMEOUT_MS);
@@ -42196,7 +42939,7 @@ function startSupervisorHealthMonitor(paths, child, write) {
42196
42939
  closed = true;
42197
42940
  terminateChild(child, forceKillTimer);
42198
42941
  forceKillTimer = setTimeout(() => {
42199
- if (child.pid && isProcessAlive3(child.pid)) {
42942
+ if (child.pid && isProcessAlive4(child.pid)) {
42200
42943
  child.kill("SIGKILL");
42201
42944
  }
42202
42945
  }, SUPERVISOR_CHILD_STOP_TIMEOUT_MS);
@@ -42243,12 +42986,12 @@ function terminateChild(child, previousForceKillTimer) {
42243
42986
  if (previousForceKillTimer) {
42244
42987
  clearTimeout(previousForceKillTimer);
42245
42988
  }
42246
- if (child.pid && isProcessAlive3(child.pid)) {
42989
+ if (child.pid && isProcessAlive4(child.pid)) {
42247
42990
  child.kill("SIGTERM");
42248
42991
  }
42249
42992
  }
42250
42993
  function supervisorStopIntentPath(paths) {
42251
- return path34.join(paths.runDir, "supervisor-stop-intent.json");
42994
+ return path35.join(paths.runDir, "supervisor-stop-intent.json");
42252
42995
  }
42253
42996
  async function writeSupervisorStopIntent(paths, pid) {
42254
42997
  await mkdir16(paths.runDir, { recursive: true, mode: 448 });
@@ -42632,7 +43375,7 @@ async function readLinkUpdateStatus(paths) {
42632
43375
  };
42633
43376
  await writeUpdateState2(paths, state);
42634
43377
  }
42635
- if (state?.state === "running" && !runningUpdate2 && !isRecentRunningState3(state) && !isProcessAlive4(state.pid)) {
43378
+ if (state?.state === "running" && !runningUpdate2 && !isRecentRunningState3(state) && !isProcessAlive5(state.pid)) {
42636
43379
  const reachedTarget = state.target_version && compareSemver3(LINK_VERSION, state.target_version) >= 0;
42637
43380
  state = reachedTarget ? {
42638
43381
  ...state,
@@ -42811,7 +43554,7 @@ async function buildOfficialInstallCommand(options, targetVersion) {
42811
43554
  };
42812
43555
  }
42813
43556
  function buildUnixInstallCommand(installerUrl) {
42814
- const nodeBinDir = path35.dirname(process.execPath);
43557
+ const nodeBinDir = path36.dirname(process.execPath);
42815
43558
  const fetchScript = [
42816
43559
  quoteShellToken(process.execPath),
42817
43560
  "--input-type=module",
@@ -43081,10 +43824,10 @@ async function readUpdateLogLines2(paths) {
43081
43824
  );
43082
43825
  }
43083
43826
  function updateStatePath2(paths) {
43084
- return path35.join(paths.runDir, "link-update-state.json");
43827
+ return path36.join(paths.runDir, "link-update-state.json");
43085
43828
  }
43086
43829
  function updateLogPath2(paths) {
43087
- return path35.join(paths.logsDir, UPDATE_LOG_FILE2);
43830
+ return path36.join(paths.logsDir, UPDATE_LOG_FILE2);
43088
43831
  }
43089
43832
  async function clearUpdateLogFiles2(paths) {
43090
43833
  const primary = updateLogPath2(paths);
@@ -43145,7 +43888,7 @@ function linkUpdateTimeoutError() {
43145
43888
  LINK_UPDATE_TIMEOUT_MS / 6e4
43146
43889
  )} minutes.`;
43147
43890
  }
43148
- function isProcessAlive4(pid) {
43891
+ function isProcessAlive5(pid) {
43149
43892
  if (!pid || pid <= 0) {
43150
43893
  return false;
43151
43894
  }
@@ -43168,7 +43911,7 @@ function readString26(payload, key) {
43168
43911
  }
43169
43912
 
43170
43913
  // src/pairing/pairing.ts
43171
- import path36 from "path";
43914
+ import path37 from "path";
43172
43915
  import { rm as rm12 } from "fs/promises";
43173
43916
 
43174
43917
  // src/relay/bootstrap.ts
@@ -43508,10 +44251,10 @@ async function loadRequiredIdentity2(paths) {
43508
44251
  }
43509
44252
  return identity;
43510
44253
  }
43511
- async function postServerJson(serverBaseUrl, path37, body, options) {
44254
+ async function postServerJson(serverBaseUrl, path38, body, options) {
43512
44255
  let response;
43513
44256
  try {
43514
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path37}`, {
44257
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path38}`, {
43515
44258
  method: "POST",
43516
44259
  headers: {
43517
44260
  accept: "application/json",
@@ -43559,10 +44302,10 @@ function pairingErrorSnapshot(stage, error) {
43559
44302
  occurred_at: (/* @__PURE__ */ new Date()).toISOString()
43560
44303
  };
43561
44304
  }
43562
- async function patchServerJson(serverBaseUrl, path37, token, body, options) {
44305
+ async function patchServerJson(serverBaseUrl, path38, token, body, options) {
43563
44306
  let response;
43564
44307
  try {
43565
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path37}`, {
44308
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path38}`, {
43566
44309
  method: "PATCH",
43567
44310
  headers: {
43568
44311
  accept: "application/json",
@@ -43610,10 +44353,10 @@ function createPairingNetworkError(input) {
43610
44353
  );
43611
44354
  }
43612
44355
  function pairingClaimPath(sessionId, paths) {
43613
- return path36.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
44356
+ return path37.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
43614
44357
  }
43615
44358
  function pairingSessionPath(sessionId, paths) {
43616
- return path36.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
44359
+ return path37.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
43617
44360
  }
43618
44361
  function qrPreferredUrls(routes) {
43619
44362
  return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
@@ -44895,6 +45638,9 @@ function registerLiveActivityRoutes(router, options) {
44895
45638
  status_label: state.statusLabel,
44896
45639
  progress_text: state.progressText,
44897
45640
  detail_text: state.detailText ?? null,
45641
+ todo_done_count: state.todoDoneCount ?? null,
45642
+ todo_total_count: state.todoTotalCount ?? null,
45643
+ todo_progress_text: state.todoProgressText ?? null,
44898
45644
  updated_at: state.updatedAt ?? null
44899
45645
  } : null
44900
45646
  };