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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,8 +4,8 @@ import Router from "@koa/router";
4
4
 
5
5
  // src/conversations/conversation-service.ts
6
6
  import { EventEmitter } from "events";
7
- import { createHash as createHash8, randomUUID as randomUUID12 } from "crypto";
8
- import path26 from "path";
7
+ import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
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.3";
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,7 +10465,235 @@ 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
10690
+ var TuiGatewaySessionBusyError = class extends LinkHttpError {
10691
+ constructor(message, details = { profileName: "default" }) {
10692
+ super(409, "tui_gateway_session_busy", message);
10693
+ this.details = details;
10694
+ }
10695
+ details;
10696
+ };
10323
10697
  var CONNECT_TIMEOUT_MS = 15e3;
10324
10698
  var REQUEST_TIMEOUT_MS = 12e4;
10325
10699
  var USAGE_REQUEST_TIMEOUT_MS = 1e4;
@@ -10332,12 +10706,60 @@ var WS_HEARTBEAT_INTERVAL_MS = 6e4;
10332
10706
  var WS_RECONNECT_BASE_DELAY_MS = 1e3;
10333
10707
  var WS_RECONNECT_MAX_DELAY_MS = 15e3;
10334
10708
  var INTERRUPT_DRAIN_TIMEOUT_MS = 15e3;
10709
+ var SESSION_BUSY_RETRY_TIMEOUT_MS = 6e3;
10710
+ var SESSION_BUSY_RETRY_INTERVAL_MS = 150;
10335
10711
  var PROFILE_NAME_PATTERN2 = /^[a-zA-Z0-9._-]{1,64}$/u;
10336
10712
  var backendEntries = /* @__PURE__ */ new Map();
10337
10713
  var backendStarts = /* @__PURE__ */ new Map();
10338
10714
  var clients = /* @__PURE__ */ new Map();
10339
10715
  var sessionRefs = /* @__PURE__ */ new Map();
10340
10716
  var backendReaper = null;
10717
+ function isTuiGatewaySessionBusyError(error) {
10718
+ if (error instanceof TuiGatewaySessionBusyError) {
10719
+ return true;
10720
+ }
10721
+ const message = error instanceof Error ? error.message : String(error);
10722
+ return /session busy/i.test(message);
10723
+ }
10724
+ function createTuiGatewayRpcError(error) {
10725
+ const message = error?.message ?? "Hermes RPC failed";
10726
+ if (error?.code === 4009 && /session busy/i.test(message)) {
10727
+ return new TuiGatewaySessionBusyError(message);
10728
+ }
10729
+ return new Error(message);
10730
+ }
10731
+ function withBusyContext(error, details) {
10732
+ if (error instanceof TuiGatewaySessionBusyError) {
10733
+ return new TuiGatewaySessionBusyError(error.message, {
10734
+ ...error.details,
10735
+ ...details
10736
+ });
10737
+ }
10738
+ if (isTuiGatewaySessionBusyError(error)) {
10739
+ return new TuiGatewaySessionBusyError(
10740
+ error instanceof Error ? error.message : String(error),
10741
+ details
10742
+ );
10743
+ }
10744
+ return error instanceof Error ? error : new Error(String(error));
10745
+ }
10746
+ async function withSessionBusyRetry(call, signal) {
10747
+ const deadline = Date.now() + SESSION_BUSY_RETRY_TIMEOUT_MS;
10748
+ for (; ; ) {
10749
+ try {
10750
+ return await call();
10751
+ } catch (error) {
10752
+ if (!isTuiGatewaySessionBusyError(error) || Date.now() >= deadline) {
10753
+ throw error;
10754
+ }
10755
+ await delay4(
10756
+ Math.min(SESSION_BUSY_RETRY_INTERVAL_MS, deadline - Date.now()),
10757
+ void 0,
10758
+ { signal }
10759
+ );
10760
+ }
10761
+ }
10762
+ }
10341
10763
  async function streamTuiGatewayRun(input) {
10342
10764
  const profileName = normalizeProfileName2(input.profileName);
10343
10765
  const client = await getTuiGatewayClient({
@@ -10395,6 +10817,77 @@ async function interruptTuiGatewaySession(input) {
10395
10817
  });
10396
10818
  await client.interruptSession(sessionId);
10397
10819
  }
10820
+ async function readTuiGatewayLiveSession(input) {
10821
+ const runtimeSessionId = input.runtimeSessionId?.trim();
10822
+ const storedSessionId = input.storedSessionId?.trim();
10823
+ if (!runtimeSessionId && !storedSessionId) {
10824
+ return null;
10825
+ }
10826
+ const profileName = normalizeProfileName2(input.profileName);
10827
+ const client = await getTuiGatewayClient({
10828
+ profileName,
10829
+ paths: input.paths,
10830
+ logger: input.logger
10831
+ });
10832
+ try {
10833
+ const result = await client.request(
10834
+ "session.active_list",
10835
+ runtimeSessionId ? { current_session_id: runtimeSessionId } : {},
10836
+ 1e4
10837
+ );
10838
+ const sessions = Array.isArray(result.sessions) ? result.sessions : [];
10839
+ for (const item of sessions) {
10840
+ const liveSession = normalizeTuiGatewayLiveSessionRecord(item);
10841
+ if (!liveSession) {
10842
+ continue;
10843
+ }
10844
+ const runtimeMatches = runtimeSessionId !== void 0 && liveSession.runtimeSessionId === runtimeSessionId;
10845
+ const storedMatches = storedSessionId !== void 0 && liveSession.storedSessionId === storedSessionId;
10846
+ if (!runtimeMatches && !storedMatches) {
10847
+ continue;
10848
+ }
10849
+ return liveSession;
10850
+ }
10851
+ } catch (error) {
10852
+ void input.logger?.debug("tui_gateway_live_session_read_failed", {
10853
+ profile: profileName,
10854
+ runtime_session_id: runtimeSessionId ?? null,
10855
+ stored_session_id: storedSessionId ?? null,
10856
+ error: error instanceof Error ? error.message : String(error)
10857
+ });
10858
+ }
10859
+ return null;
10860
+ }
10861
+ function normalizeTuiGatewayLiveSessionRecord(value) {
10862
+ const record = toRecord3(value);
10863
+ const runtimeId = readString5(record, "id");
10864
+ if (!runtimeId) {
10865
+ return null;
10866
+ }
10867
+ const storedId = readString5(record, "session_key") ?? readString5(record, "sessionKey");
10868
+ const status = (readString5(record, "status") ?? "unknown").toLowerCase();
10869
+ const inflight = typeof record.inflight === "object" && record.inflight !== null ? toRecord3(record.inflight) : void 0;
10870
+ const running = readBoolean(record.running) || status === "working" || status === "waiting" || status === "starting" || inflight !== void 0 && Object.keys(inflight).length > 0;
10871
+ const title = readString5(record, "title");
10872
+ const preview = readString5(record, "preview");
10873
+ const model = readString5(record, "model");
10874
+ const lastActiveAt = readFiniteNumber(record.last_active);
10875
+ const startedAt = readFiniteNumber(record.started_at);
10876
+ return {
10877
+ runtimeSessionId: runtimeId,
10878
+ storedSessionId: storedId ?? runtimeId,
10879
+ status,
10880
+ running,
10881
+ ...readBoolean(record.current) ? { current: true } : {},
10882
+ ...title ? { title } : {},
10883
+ ...preview ? { preview } : {},
10884
+ ...model ? { model } : {},
10885
+ ...lastActiveAt !== void 0 ? { lastActiveAt } : {},
10886
+ ...startedAt !== void 0 ? { startedAt } : {},
10887
+ ...inflight ? { inflight } : {},
10888
+ raw: record
10889
+ };
10890
+ }
10398
10891
  async function dispatchTuiGatewayCommand(input) {
10399
10892
  const profileName = normalizeProfileName2(input.profileName);
10400
10893
  const client = await getTuiGatewayClient({
@@ -10642,32 +11135,50 @@ async function setTuiGatewaySessionModelConfig(input) {
10642
11135
  }
10643
11136
  const raw = [];
10644
11137
  if (model) {
10645
- const result = await client.request(
10646
- "config.set",
10647
- {
10648
- session_id: started.runtimeSessionId,
10649
- key: "model",
10650
- value: formatSessionModelConfigValue(
10651
- model,
10652
- input.provider,
10653
- hasNativeSessionModelConfig
10654
- ),
10655
- confirm_expensive_model: true
10656
- },
10657
- REQUEST_TIMEOUT_MS
10658
- );
11138
+ const result = await withSessionBusyRetry(
11139
+ () => client.request(
11140
+ "config.set",
11141
+ {
11142
+ session_id: started.runtimeSessionId,
11143
+ key: "model",
11144
+ value: formatSessionModelConfigValue(
11145
+ model,
11146
+ input.provider,
11147
+ hasNativeSessionModelConfig
11148
+ ),
11149
+ confirm_expensive_model: true
11150
+ },
11151
+ REQUEST_TIMEOUT_MS
11152
+ )
11153
+ ).catch((error) => {
11154
+ throw withBusyContext(error, {
11155
+ profileName,
11156
+ runtimeSessionId: started.runtimeSessionId,
11157
+ storedSessionId: started.storedSessionId,
11158
+ method: "config.set:model"
11159
+ });
11160
+ });
10659
11161
  raw.push(result);
10660
11162
  }
10661
11163
  if (reasoningEffort) {
10662
- const result = await client.request(
10663
- "config.set",
10664
- {
10665
- session_id: started.runtimeSessionId,
10666
- key: "reasoning",
10667
- value: reasoningEffort
10668
- },
10669
- REQUEST_TIMEOUT_MS
10670
- );
11164
+ const result = await withSessionBusyRetry(
11165
+ () => client.request(
11166
+ "config.set",
11167
+ {
11168
+ session_id: started.runtimeSessionId,
11169
+ key: "reasoning",
11170
+ value: reasoningEffort
11171
+ },
11172
+ REQUEST_TIMEOUT_MS
11173
+ )
11174
+ ).catch((error) => {
11175
+ throw withBusyContext(error, {
11176
+ profileName,
11177
+ runtimeSessionId: started.runtimeSessionId,
11178
+ storedSessionId: started.storedSessionId,
11179
+ method: "config.set:reasoning"
11180
+ });
11181
+ });
10671
11182
  raw.push(result);
10672
11183
  }
10673
11184
  rememberSessionRef(profileName, input.hermesSessionId, started);
@@ -10705,12 +11216,12 @@ async function restartTuiGatewayBackend(input) {
10705
11216
  const entry = backendEntries.get(profileName);
10706
11217
  backendEntries.delete(profileName);
10707
11218
  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
- }
11219
+ if (entry) {
11220
+ await terminateBackendEntry(entry, {
11221
+ paths: input.paths,
11222
+ logger: input.logger,
11223
+ graceMs: BACKEND_RESTART_GRACE_MS
11224
+ });
10714
11225
  }
10715
11226
  void input.logger?.warn("tui_gateway_backend_restart_requested", {
10716
11227
  profile: profileName,
@@ -10835,23 +11346,58 @@ async function readTuiGatewayStatus(input = {}) {
10835
11346
  };
10836
11347
  }
10837
11348
  }
10838
- function closeTuiGatewayBackends() {
11349
+ async function closeTuiGatewayBackends(input = {}) {
10839
11350
  for (const client of clients.values()) {
10840
11351
  client.close();
10841
11352
  }
10842
11353
  clients.clear();
10843
11354
  sessionRefs.clear();
10844
- for (const entry of backendEntries.values()) {
10845
- if (!entry.process.killed) {
10846
- entry.process.kill();
10847
- }
10848
- }
11355
+ const entries = [...backendEntries.values()];
10849
11356
  backendEntries.clear();
11357
+ await Promise.all(
11358
+ entries.map(
11359
+ (entry) => terminateBackendEntry(entry, {
11360
+ paths: input.paths,
11361
+ logger: input.logger
11362
+ })
11363
+ )
11364
+ );
11365
+ if (input.cleanupRegistry && input.paths) {
11366
+ await cleanupRegisteredTuiGatewayProcesses({
11367
+ paths: input.paths,
11368
+ logger: input.logger
11369
+ });
11370
+ }
10850
11371
  if (backendReaper) {
10851
11372
  clearInterval(backendReaper);
10852
11373
  backendReaper = null;
10853
11374
  }
10854
11375
  }
11376
+ async function terminateBackendEntry(entry, input = {}) {
11377
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
11378
+ if (!entry.process.killed) {
11379
+ entry.process.kill("SIGTERM");
11380
+ }
11381
+ await waitForProcessExit(
11382
+ entry.process,
11383
+ input.graceMs ?? BACKEND_RESTART_GRACE_MS
11384
+ );
11385
+ if (entry.process.exitCode === null && entry.process.signalCode === null) {
11386
+ entry.process.kill("SIGKILL");
11387
+ await waitForProcessExit(entry.process, 1e3);
11388
+ }
11389
+ }
11390
+ await unregisterTuiGatewayProcess(input.paths ?? entry.paths, {
11391
+ pid: entry.process.pid ?? void 0,
11392
+ profile: entry.profile
11393
+ }).catch((error) => {
11394
+ void input.logger?.debug("tui_gateway_registry_unregister_failed", {
11395
+ profile: entry.profile,
11396
+ pid: entry.process.pid ?? null,
11397
+ error: error instanceof Error ? error.message : String(error)
11398
+ });
11399
+ });
11400
+ }
10855
11401
  function formatSessionModelConfigValue(model, provider, sessionOnly = false) {
10856
11402
  const suffix = sessionOnly ? " --session" : "";
10857
11403
  const normalizedProvider = provider?.trim();
@@ -11032,7 +11578,7 @@ async function startTuiGatewayBackend(input) {
11032
11578
  ];
11033
11579
  const logWriter = createRotatingTextLogWriter({
11034
11580
  paths,
11035
- fileName: path9.basename(getGatewayRuntimeLogFile(paths))
11581
+ fileName: path10.basename(getGatewayRuntimeLogFile(paths))
11036
11582
  });
11037
11583
  const hermesBin = resolveHermesBin();
11038
11584
  const child = spawn2(hermesBin, args, {
@@ -11053,6 +11599,16 @@ async function startTuiGatewayBackend(input) {
11053
11599
  clients.get(profileName)?.close();
11054
11600
  clients.delete(profileName);
11055
11601
  backendEntries.delete(profileName);
11602
+ void unregisterTuiGatewayProcess(paths, {
11603
+ pid: child.pid ?? void 0,
11604
+ profile: profileName
11605
+ }).catch((error) => {
11606
+ void input.logger?.debug("tui_gateway_registry_unregister_failed", {
11607
+ profile: profileName,
11608
+ pid: child.pid ?? null,
11609
+ error: error instanceof Error ? error.message : String(error)
11610
+ });
11611
+ });
11056
11612
  void input.logger?.warn("tui_gateway_backend_exited", {
11057
11613
  profile: profileName,
11058
11614
  pid: child.pid ?? null,
@@ -11083,6 +11639,32 @@ async function startTuiGatewayBackend(input) {
11083
11639
  await logWriter.flush().catch(() => void 0);
11084
11640
  throw error;
11085
11641
  }
11642
+ if (!child.pid) {
11643
+ child.kill();
11644
+ await logWriter.flush().catch(() => void 0);
11645
+ throw new LinkHttpError(
11646
+ 503,
11647
+ "tui_gateway_backend_unavailable",
11648
+ "Hermes dashboard backend started without a process id."
11649
+ );
11650
+ }
11651
+ try {
11652
+ await registerTuiGatewayProcess(paths, {
11653
+ owner: "hermeslink",
11654
+ pid: child.pid,
11655
+ profile: profileName,
11656
+ port,
11657
+ command: hermesBin,
11658
+ args,
11659
+ runtime_home: paths.homeDir,
11660
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
11661
+ });
11662
+ } catch (error) {
11663
+ child.kill();
11664
+ await waitForProcessExit(child, BACKEND_RESTART_GRACE_MS);
11665
+ await logWriter.flush().catch(() => void 0);
11666
+ throw error;
11667
+ }
11086
11668
  const entry = {
11087
11669
  profile: profileName,
11088
11670
  port,
@@ -11090,6 +11672,7 @@ async function startTuiGatewayBackend(input) {
11090
11672
  baseUrl,
11091
11673
  wsUrl,
11092
11674
  process: child,
11675
+ paths,
11093
11676
  logPath: logWriter.filePath,
11094
11677
  startedAt: Date.now(),
11095
11678
  lastActiveAt: Date.now()
@@ -11267,12 +11850,21 @@ var TuiGatewayClient = class {
11267
11850
  cleanup();
11268
11851
  };
11269
11852
  input.signal?.addEventListener("abort", abort, { once: true });
11270
- void this.request(
11271
- "prompt.submit",
11272
- { session_id: input.sessionId, text: input.text },
11273
- PROMPT_SUBMIT_TIMEOUT_MS
11853
+ void withSessionBusyRetry(
11854
+ () => this.request(
11855
+ "prompt.submit",
11856
+ { session_id: input.sessionId, text: input.text },
11857
+ PROMPT_SUBMIT_TIMEOUT_MS
11858
+ ),
11859
+ input.signal
11274
11860
  ).catch((error) => {
11275
- queue.pushError(error);
11861
+ queue.pushError(
11862
+ withBusyContext(error, {
11863
+ profileName: this.profileName,
11864
+ runtimeSessionId: input.sessionId,
11865
+ method: "prompt.submit"
11866
+ })
11867
+ );
11276
11868
  });
11277
11869
  queue.closed.finally(cleanup).catch(() => void 0);
11278
11870
  return queue;
@@ -11406,7 +11998,7 @@ var TuiGatewayClient = class {
11406
11998
  this.pending.delete(frame.id);
11407
11999
  clearTimeout(pending.timer);
11408
12000
  if (frame.error) {
11409
- pending.reject(new Error(frame.error.message ?? "Hermes RPC failed"));
12001
+ pending.reject(createTuiGatewayRpcError(frame.error));
11410
12002
  } else {
11411
12003
  pending.resolve(frame.result);
11412
12004
  }
@@ -11527,14 +12119,14 @@ async function readCurrentStoredSessionId(client, runtimeSessionId, logger) {
11527
12119
  return null;
11528
12120
  }
11529
12121
  async function waitForProcessExit(child, timeoutMs) {
11530
- if (child.killed || child.exitCode !== null) {
12122
+ if (child.exitCode !== null || child.signalCode !== null) {
11531
12123
  return;
11532
12124
  }
11533
12125
  await Promise.race([
11534
12126
  new Promise((resolve) => {
11535
12127
  child.once("exit", () => resolve());
11536
12128
  }),
11537
- delay3(timeoutMs).then(() => void 0)
12129
+ delay4(timeoutMs).then(() => void 0)
11538
12130
  ]);
11539
12131
  }
11540
12132
  var GatewayEventQueue = class {
@@ -11969,7 +12561,7 @@ async function waitForDashboardReady(baseUrl, token, logPath, signal) {
11969
12561
  } catch (error) {
11970
12562
  lastError = error;
11971
12563
  }
11972
- await delay3(250, void 0, { signal }).catch(() => void 0);
12564
+ await delay4(250, void 0, { signal }).catch(() => void 0);
11973
12565
  }
11974
12566
  throw new LinkHttpError(
11975
12567
  503,
@@ -12056,23 +12648,28 @@ function startBackendReaper() {
12056
12648
  return;
12057
12649
  }
12058
12650
  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
- }
12651
+ void reapIdleBackends();
12073
12652
  }, 6e4);
12074
12653
  backendReaper.unref?.();
12075
12654
  }
12655
+ async function reapIdleBackends() {
12656
+ const now = Date.now();
12657
+ const staleEntries = [];
12658
+ for (const [profile, entry] of backendEntries) {
12659
+ if (now - entry.lastActiveAt < IDLE_BACKEND_TTL_MS) {
12660
+ continue;
12661
+ }
12662
+ clients.get(profile)?.close();
12663
+ clients.delete(profile);
12664
+ backendEntries.delete(profile);
12665
+ staleEntries.push(entry);
12666
+ }
12667
+ await Promise.all(staleEntries.map((entry) => terminateBackendEntry(entry)));
12668
+ if (backendEntries.size === 0 && backendReaper) {
12669
+ clearInterval(backendReaper);
12670
+ backendReaper = null;
12671
+ }
12672
+ }
12076
12673
  function normalizeProfileName2(profileName) {
12077
12674
  const value = profileName?.trim() || "default";
12078
12675
  if (!PROFILE_NAME_PATTERN2.test(value)) {
@@ -12102,6 +12699,19 @@ function readString5(payload, key) {
12102
12699
  const value = payload[key];
12103
12700
  return typeof value === "string" && value.trim() ? value.trim() : null;
12104
12701
  }
12702
+ function readBoolean(value) {
12703
+ return value === true || value === "true" || value === 1;
12704
+ }
12705
+ function readFiniteNumber(value) {
12706
+ if (typeof value === "number" && Number.isFinite(value)) {
12707
+ return value;
12708
+ }
12709
+ if (typeof value === "string" && value.trim()) {
12710
+ const parsed = Number.parseFloat(value);
12711
+ return Number.isFinite(parsed) ? parsed : void 0;
12712
+ }
12713
+ return void 0;
12714
+ }
12105
12715
  function readStringList2(value) {
12106
12716
  if (!Array.isArray(value)) {
12107
12717
  return [];
@@ -12867,11 +13477,11 @@ function workspaceNotFound() {
12867
13477
  // src/conversations/blob-store.ts
12868
13478
  import { randomUUID as randomUUID5 } from "crypto";
12869
13479
  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";
13480
+ import path12 from "path";
12871
13481
 
12872
13482
  // src/conversations/media.ts
12873
13483
  import { createHash as createHash4 } from "crypto";
12874
- import path10 from "path";
13484
+ import path11 from "path";
12875
13485
 
12876
13486
  // src/conversations/delivery-contract.ts
12877
13487
  var HERMES_LINK_DELIVERY_TAG_PATTERN = /<hermes_link_delivery>\s*(\{[\s\S]*?\})\s*<\/hermes_link_delivery>/giu;
@@ -12961,7 +13571,7 @@ function cleanMessageTextParts(message) {
12961
13571
  }
12962
13572
  }
12963
13573
  function inferMimeType(filePath) {
12964
- const extension = path10.extname(filePath).toLowerCase();
13574
+ const extension = path11.extname(filePath).toLowerCase();
12965
13575
  return {
12966
13576
  ".png": "image/png",
12967
13577
  ".jpg": "image/jpeg",
@@ -13085,7 +13695,7 @@ function mediaSourceKey(sourcePath) {
13085
13695
  return createHash4("sha256").update(resolveMediaSourcePath(sourcePath)).digest("hex").slice(0, 32);
13086
13696
  }
13087
13697
  function sanitizeFilename(value, fallback) {
13088
- const base = path10.basename((value ?? "").replace(/[\r\n\t]/gu, " ").trim());
13698
+ const base = path11.basename((value ?? "").replace(/[\r\n\t]/gu, " ").trim());
13089
13699
  const safe = base.replace(/[/:\\]/gu, "_").slice(0, 200).trim();
13090
13700
  return safe || fallback;
13091
13701
  }
@@ -13114,9 +13724,9 @@ function isBlobReferencedByMessages(snapshot, blobId) {
13114
13724
  }
13115
13725
  function resolveMediaSourcePath(sourcePath) {
13116
13726
  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)) {
13727
+ const expanded = trimmed.startsWith("~/") ? path11.join(process.env.HOME ?? "", trimmed.slice(2)) : trimmed;
13728
+ const resolved = path11.resolve(expanded);
13729
+ if (!path11.isAbsolute(expanded)) {
13120
13730
  throw new LinkHttpError(
13121
13731
  400,
13122
13732
  "media_source_path_not_absolute",
@@ -13311,7 +13921,7 @@ function normalizeConversationIds(conversationIds) {
13311
13921
  }
13312
13922
  function blobPath(paths, blobId) {
13313
13923
  assertValidBlobId(blobId);
13314
- return path11.join(paths.blobsDir, `${blobId}.bin`);
13924
+ return path12.join(paths.blobsDir, `${blobId}.bin`);
13315
13925
  }
13316
13926
  async function writeConversationBlob(paths, conversationId, input, options) {
13317
13927
  assertValidConversationId(conversationId);
@@ -13320,7 +13930,7 @@ async function writeConversationBlob(paths, conversationId, input, options) {
13320
13930
  }
13321
13931
  const id = `blob_${randomUUID5().replaceAll("-", "")}`;
13322
13932
  const filePath = blobPath(paths, id);
13323
- await mkdir6(path11.dirname(filePath), { recursive: true, mode: 448 });
13933
+ await mkdir6(path12.dirname(filePath), { recursive: true, mode: 448 });
13324
13934
  await writeFile(filePath, input.bytes, { mode: 384 });
13325
13935
  const blob = {
13326
13936
  id,
@@ -13396,7 +14006,7 @@ async function materializeConversationBlob(paths, conversationId, blobId, manife
13396
14006
  }
13397
14007
  }
13398
14008
  const targetDir = conversationAttachmentsDir(paths, conversationId);
13399
- const targetPath = path11.join(
14009
+ const targetPath = path12.join(
13400
14010
  targetDir,
13401
14011
  materializedAttachmentFilename(blobId, manifest.filename ?? blobId)
13402
14012
  );
@@ -13449,7 +14059,7 @@ async function listConversationBlobIds(paths, conversationId) {
13449
14059
  continue;
13450
14060
  }
13451
14061
  const manifest = await readJsonFile(
13452
- path11.join(paths.blobsDir, entry.name)
14062
+ path12.join(paths.blobsDir, entry.name)
13453
14063
  ).catch(() => null);
13454
14064
  if (manifest?.conversation_ids?.includes(conversationId)) {
13455
14065
  blobIds.push(blobId);
@@ -13459,7 +14069,7 @@ async function listConversationBlobIds(paths, conversationId) {
13459
14069
  }
13460
14070
  function conversationAttachmentsDir(paths, conversationId) {
13461
14071
  assertValidConversationId(conversationId);
13462
- return path11.join(paths.conversationsDir, conversationId, "attachments");
14072
+ return path12.join(paths.conversationsDir, conversationId, "attachments");
13463
14073
  }
13464
14074
  function isNodeError6(error, code) {
13465
14075
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
@@ -13471,7 +14081,7 @@ import { stat as stat8 } from "fs/promises";
13471
14081
  // src/hermes/model-provider-disconnects.ts
13472
14082
  import { stat as stat7 } from "fs/promises";
13473
14083
  import os5 from "os";
13474
- import path12 from "path";
14084
+ import path13 from "path";
13475
14085
  async function markAuthBackedModelProviderDisconnected(paths, profileName, providerKey) {
13476
14086
  const profileKey = normalizeProfileName3(profileName);
13477
14087
  const provider = providerKey.trim();
@@ -13549,15 +14159,15 @@ function credentialFilesForProvider(profileName, providerKey) {
13549
14159
  const profileDir = resolveHermesProfileDir(normalizeProfileName3(profileName));
13550
14160
  switch (providerKey) {
13551
14161
  case "qwen-oauth":
13552
- return [path12.join(os5.homedir(), ".qwen", "oauth_creds.json")];
14162
+ return [path13.join(os5.homedir(), ".qwen", "oauth_creds.json")];
13553
14163
  case "google-gemini-cli":
13554
- return [path12.join(profileDir, "auth", "google_oauth.json")];
14164
+ return [path13.join(profileDir, "auth", "google_oauth.json")];
13555
14165
  default:
13556
- return [path12.join(profileDir, "auth.json")];
14166
+ return [path13.join(profileDir, "auth.json")];
13557
14167
  }
13558
14168
  }
13559
14169
  function disconnectedProvidersPath(paths) {
13560
- return path12.join(paths.homeDir, "model-provider-disconnects.json");
14170
+ return path13.join(paths.homeDir, "model-provider-disconnects.json");
13561
14171
  }
13562
14172
  function normalizeStore(value) {
13563
14173
  return {
@@ -14688,7 +15298,7 @@ function commandText(language, zh, en) {
14688
15298
 
14689
15299
  // src/conversations/delivery-staging.ts
14690
15300
  import { mkdir as mkdir7, rm as rm4 } from "fs/promises";
14691
- import path13 from "path";
15301
+ import path14 from "path";
14692
15302
  async function prepareDeliveryStagingRunDir(paths, conversationId, runId) {
14693
15303
  const directory = deliveryStagingRunDir(paths, conversationId, runId);
14694
15304
  await mkdir7(directory, { recursive: true, mode: 448 });
@@ -14701,14 +15311,14 @@ async function removeConversationDeliveryStaging(paths, conversationId) {
14701
15311
  });
14702
15312
  }
14703
15313
  function deliveryStagingRunDir(paths, conversationId, runId) {
14704
- return path13.join(
15314
+ return path14.join(
14705
15315
  deliveryStagingConversationDir(paths, conversationId),
14706
15316
  safePathSegment(runId, "run")
14707
15317
  );
14708
15318
  }
14709
15319
  function deliveryStagingConversationDir(paths, conversationId) {
14710
15320
  assertValidConversationId(conversationId);
14711
- return path13.join(paths.conversationsDir, conversationId, "delivery-staging");
15321
+ return path14.join(paths.conversationsDir, conversationId, "delivery-staging");
14712
15322
  }
14713
15323
  function safePathSegment(value, fallback) {
14714
15324
  const safe = value.trim().replaceAll(/[^a-zA-Z0-9._-]/gu, "_");
@@ -14718,7 +15328,7 @@ function safePathSegment(value, fallback) {
14718
15328
  // src/conversations/conversation-archive-plans.ts
14719
15329
  import { randomUUID as randomUUID7 } from "crypto";
14720
15330
  import { mkdir as mkdir8 } from "fs/promises";
14721
- import path14 from "path";
15331
+ import path15 from "path";
14722
15332
  var PLAN_ID_PATTERN = /^archive_[a-f0-9]{32}$/u;
14723
15333
  var ConversationArchivePlanStore = class {
14724
15334
  constructor(paths) {
@@ -14761,10 +15371,10 @@ var ConversationArchivePlanStore = class {
14761
15371
  await writeJsonFile(this.planPath(normalizedPlanId), plan);
14762
15372
  }
14763
15373
  plansDir() {
14764
- return path14.join(this.paths.indexesDir, "conversation-archive-plans");
15374
+ return path15.join(this.paths.indexesDir, "conversation-archive-plans");
14765
15375
  }
14766
15376
  planPath(planId) {
14767
- return path14.join(this.plansDir(), `${planId}.json`);
15377
+ return path15.join(this.plansDir(), `${planId}.json`);
14768
15378
  }
14769
15379
  };
14770
15380
  function normalizePlanId(planId) {
@@ -14782,7 +15392,7 @@ function normalizePlanId(planId) {
14782
15392
  // src/conversations/conversation-clear-plans.ts
14783
15393
  import { randomUUID as randomUUID8 } from "crypto";
14784
15394
  import { mkdir as mkdir9 } from "fs/promises";
14785
- import path15 from "path";
15395
+ import path16 from "path";
14786
15396
  var PLAN_ID_PATTERN2 = /^clear_[a-f0-9]{32}$/u;
14787
15397
  var ConversationClearPlanStore = class {
14788
15398
  constructor(paths) {
@@ -14826,10 +15436,10 @@ var ConversationClearPlanStore = class {
14826
15436
  await writeJsonFile(this.planPath(normalizedPlanId), plan);
14827
15437
  }
14828
15438
  plansDir() {
14829
- return path15.join(this.paths.indexesDir, "conversation-clear-plans");
15439
+ return path16.join(this.paths.indexesDir, "conversation-clear-plans");
14830
15440
  }
14831
15441
  planPath(planId) {
14832
- return path15.join(this.plansDir(), `${planId}.json`);
15442
+ return path16.join(this.plansDir(), `${planId}.json`);
14833
15443
  }
14834
15444
  };
14835
15445
  function normalizePlanId2(planId) {
@@ -15684,14 +16294,14 @@ function readAttachmentWaveform(attachment) {
15684
16294
 
15685
16295
  // src/hermes/session-title.ts
15686
16296
  import { stat as stat9 } from "fs/promises";
15687
- import path16 from "path";
16297
+ import path17 from "path";
15688
16298
  async function readHermesSessionTitle(sessionId, paths, profileName) {
15689
16299
  const trimmedSessionId = sessionId.trim();
15690
16300
  if (!trimmedSessionId) {
15691
16301
  return void 0;
15692
16302
  }
15693
16303
  const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
15694
- const dbPath = path16.join(
16304
+ const dbPath = path17.join(
15695
16305
  resolveHermesProfileDir(resolvedProfileName),
15696
16306
  "state.db"
15697
16307
  );
@@ -15712,7 +16322,7 @@ async function readHermesCompressionTip(sessionId, paths, profileName) {
15712
16322
  return void 0;
15713
16323
  }
15714
16324
  const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
15715
- const dbPath = path16.join(
16325
+ const dbPath = path17.join(
15716
16326
  resolveHermesProfileDir(resolvedProfileName),
15717
16327
  "state.db"
15718
16328
  );
@@ -15840,12 +16450,12 @@ var ConversationMetadataCoordinator = class {
15840
16450
  return { ...next, last_event_seq: event.seq, updated_at: event.created_at };
15841
16451
  }
15842
16452
  scheduleGeneratedTitleRefresh(conversationId) {
15843
- for (const delay5 of GENERATED_TITLE_RETRY_DELAYS_MS) {
16453
+ for (const delay6 of GENERATED_TITLE_RETRY_DELAYS_MS) {
15844
16454
  setTimeout(() => {
15845
16455
  void this.generateTitleFromFirstRound(conversationId).catch(
15846
16456
  () => void 0
15847
16457
  );
15848
- }, delay5);
16458
+ }, delay6);
15849
16459
  }
15850
16460
  }
15851
16461
  async renameConversation(conversationId, title, input) {
@@ -16145,7 +16755,7 @@ function stripCompressionTitleSuffix(value) {
16145
16755
 
16146
16756
  // src/conversations/history-builder.ts
16147
16757
  import { readFile as readFile8, stat as stat10 } from "fs/promises";
16148
- import path17 from "path";
16758
+ import path18 from "path";
16149
16759
  var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
16150
16760
  var HERMES_HISTORY_COLUMNS = [
16151
16761
  "role",
@@ -16217,13 +16827,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
16217
16827
  }
16218
16828
  const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
16219
16829
  const profileDir = resolveHermesProfileDir(normalizedProfileName);
16220
- const dbPath = path17.join(profileDir, "state.db");
16830
+ const dbPath = path18.join(profileDir, "state.db");
16221
16831
  const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
16222
16832
  sessionsDir: value.sessionsDir,
16223
16833
  configured: value.configured,
16224
16834
  configError: false
16225
16835
  })).catch(() => ({
16226
- sessionsDir: path17.join(profileDir, "sessions"),
16836
+ sessionsDir: path18.join(profileDir, "sessions"),
16227
16837
  configured: false,
16228
16838
  configError: true
16229
16839
  }));
@@ -16399,8 +17009,8 @@ async function readFirstExistingFile(paths) {
16399
17009
  }
16400
17010
  function candidateTranscriptPaths(sessionsDir, sessionId, extension) {
16401
17011
  return [
16402
- path17.join(sessionsDir, `session_${sessionId}.${extension}`),
16403
- path17.join(sessionsDir, `${sessionId}.${extension}`)
17012
+ path18.join(sessionsDir, `session_${sessionId}.${extension}`),
17013
+ path18.join(sessionsDir, `${sessionId}.${extension}`)
16404
17014
  ];
16405
17015
  }
16406
17016
  function readHistoryRows(dbPath, sessionId) {
@@ -17037,7 +17647,10 @@ var ConversationOrchestrationCoordinator = class {
17037
17647
  );
17038
17648
  }
17039
17649
  startRunWorkerAndDrain(conversationId, runId, input) {
17040
- void this.deps.runLifecycle.startRunWorker(conversationId, runId, input).catch(async (error) => {
17650
+ let shouldDrainQueue = true;
17651
+ void this.deps.runLifecycle.startRunWorker(conversationId, runId, input).then((outcome) => {
17652
+ shouldDrainQueue = outcome.shouldDrainQueue;
17653
+ }).catch(async (error) => {
17041
17654
  if (isConversationNotFoundError(error)) {
17042
17655
  return;
17043
17656
  }
@@ -17063,6 +17676,9 @@ var ConversationOrchestrationCoordinator = class {
17063
17676
  });
17064
17677
  }
17065
17678
  }).finally(() => {
17679
+ if (!shouldDrainQueue) {
17680
+ return;
17681
+ }
17066
17682
  void this.startNextQueuedRun(conversationId).catch((error) => {
17067
17683
  void this.deps.logger.warn("conversation_queue_drain_failed", {
17068
17684
  conversation_id: conversationId,
@@ -18356,6 +18972,10 @@ var APP_TOOL_EVENT_FIELDS_TO_DROP = /* @__PURE__ */ new Set([
18356
18972
  "message"
18357
18973
  ]);
18358
18974
  var LIFECYCLE_MARKER_FORMAT = "hermes-link-lifecycle-marker";
18975
+ var APP_VISIBLE_LIFECYCLE_MARKER_KINDS = /* @__PURE__ */ new Set([
18976
+ "context_compression",
18977
+ "goal_completion"
18978
+ ]);
18359
18979
  function projectConversationAgentEvent(event, language = readLanguage(event.payload)) {
18360
18980
  if (!isAgentActivityEvent(event.type)) {
18361
18981
  return null;
@@ -18576,7 +19196,7 @@ function retainAppVisibleRaw(value) {
18576
19196
  return null;
18577
19197
  }
18578
19198
  const payload = toRecord8(raw.payload);
18579
- if (payload.kind !== "context_compression") {
19199
+ if (typeof payload.kind !== "string" || !APP_VISIBLE_LIFECYCLE_MARKER_KINDS.has(payload.kind)) {
18580
19200
  return null;
18581
19201
  }
18582
19202
  return {
@@ -18942,11 +19562,14 @@ var ConversationQueryCoordinator = class {
18942
19562
  );
18943
19563
  }
18944
19564
  const startIndex = Math.max(0, endIndex - limit);
19565
+ const events = await this.listEvents(conversationId, 0).catch(() => []);
18945
19566
  const messages2 = await this.hydrateAgentEventsForMessages(
18946
19567
  conversationId,
18947
19568
  snapshot.messages.slice(startIndex, endIndex),
18948
- snapshot
19569
+ snapshot,
19570
+ events
18949
19571
  );
19572
+ const todoSnapshot = latestTodoSnapshotFromEvents(conversationId, events);
18950
19573
  return {
18951
19574
  messages: messages2,
18952
19575
  last_event_seq: manifest.last_event_seq,
@@ -18964,7 +19587,8 @@ var ConversationQueryCoordinator = class {
18964
19587
  },
18965
19588
  event_stream: buildConversationEventStreamState(snapshot),
18966
19589
  input_requests: pendingInputRequests(snapshot),
18967
- ...normalizeConversationGoalState(manifest.command_state?.goal) ? { goal: normalizeConversationGoalState(manifest.command_state?.goal) } : {}
19590
+ ...normalizeConversationGoalState(manifest.command_state?.goal) ? { goal: normalizeConversationGoalState(manifest.command_state?.goal) } : {},
19591
+ ...todoSnapshot ? { todo_snapshot: todoSnapshot } : {}
18968
19592
  };
18969
19593
  }
18970
19594
  async listEvents(conversationId, after = 0) {
@@ -18977,12 +19601,11 @@ var ConversationQueryCoordinator = class {
18977
19601
  profile ?? await readConversationProfileSummary(this.deps.paths, manifest)
18978
19602
  );
18979
19603
  }
18980
- async hydrateAgentEventsForMessages(conversationId, messages2, snapshot) {
19604
+ async hydrateAgentEventsForMessages(conversationId, messages2, snapshot, events) {
18981
19605
  if (!messages2.some((message) => message.role === "assistant")) {
18982
19606
  return messages2;
18983
19607
  }
18984
19608
  const eventsByMessageId = /* @__PURE__ */ new Map();
18985
- const events = await this.listEvents(conversationId, 0).catch(() => []);
18986
19609
  for (const event of events) {
18987
19610
  if (!event.message_id) {
18988
19611
  continue;
@@ -19018,6 +19641,81 @@ var ConversationQueryCoordinator = class {
19018
19641
  });
19019
19642
  }
19020
19643
  };
19644
+ function latestTodoSnapshotFromEvents(conversationId, events) {
19645
+ for (let index = events.length - 1; index >= 0; index -= 1) {
19646
+ const snapshot = todoSnapshotFromEvent(conversationId, events[index]);
19647
+ if (snapshot) {
19648
+ return snapshot;
19649
+ }
19650
+ }
19651
+ return null;
19652
+ }
19653
+ function todoSnapshotFromEvent(conversationId, event) {
19654
+ const type = event.type.toLowerCase();
19655
+ if (type !== "tool.completed" && type !== "tool.complete") {
19656
+ return null;
19657
+ }
19658
+ const payload = readRecord(event.payload);
19659
+ const toolName = readToolName(payload)?.toLowerCase();
19660
+ if (toolName !== "todo") {
19661
+ return null;
19662
+ }
19663
+ const todos = readTodoItems(payload.todos) ?? readTodoItems(readRecord(payload.result).todos) ?? readTodoItems(readRecord(payload.output).todos) ?? readTodoItems(readRecord(payload.content).todos);
19664
+ if (!todos || todos.length === 0) {
19665
+ return null;
19666
+ }
19667
+ return {
19668
+ conversation_id: conversationId,
19669
+ ...event.run_id ? { run_id: event.run_id } : {},
19670
+ updated_at: event.created_at,
19671
+ items: todos
19672
+ };
19673
+ }
19674
+ function readTodoItems(value) {
19675
+ if (!Array.isArray(value)) {
19676
+ return null;
19677
+ }
19678
+ const items = value.map((item, index) => readTodoItem(item, index)).filter((item) => item !== null);
19679
+ return items.length > 0 ? items : null;
19680
+ }
19681
+ function readTodoItem(value, index) {
19682
+ const record = readRecord(value);
19683
+ const content = readNonEmptyString(record.content) ?? readNonEmptyString(record.title);
19684
+ const status = readTodoStatus(readNonEmptyString(record.status));
19685
+ if (!content || !status) {
19686
+ return null;
19687
+ }
19688
+ return {
19689
+ id: readNonEmptyString(record.id) ?? `${index + 1}`,
19690
+ content,
19691
+ status
19692
+ };
19693
+ }
19694
+ function readTodoStatus(value) {
19695
+ switch (value?.trim().toLowerCase()) {
19696
+ case "pending":
19697
+ return "pending";
19698
+ case "in_progress":
19699
+ case "in-progress":
19700
+ case "running":
19701
+ return "in_progress";
19702
+ case "completed":
19703
+ case "complete":
19704
+ case "done":
19705
+ return "completed";
19706
+ case "cancelled":
19707
+ case "canceled":
19708
+ return "cancelled";
19709
+ default:
19710
+ return null;
19711
+ }
19712
+ }
19713
+ function readToolName(record) {
19714
+ return readNonEmptyString(record.tool_name) ?? readNonEmptyString(record.toolName) ?? readNonEmptyString(record.name) ?? readNonEmptyString(record.tool) ?? readNonEmptyString(readRecord(record.tool).name);
19715
+ }
19716
+ function readRecord(value) {
19717
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
19718
+ }
19021
19719
  function readRunLanguage(snapshot, runId) {
19022
19720
  if (!runId) {
19023
19721
  return null;
@@ -19830,7 +20528,7 @@ import {
19830
20528
  rm as rm5,
19831
20529
  writeFile as writeFile2
19832
20530
  } from "fs/promises";
19833
- import path18 from "path";
20531
+ import path19 from "path";
19834
20532
  var ConversationStore = class {
19835
20533
  constructor(paths) {
19836
20534
  this.paths = paths;
@@ -19961,23 +20659,23 @@ var ConversationStore = class {
19961
20659
  return manifest != null && manifest.status !== "deleted_soft";
19962
20660
  }
19963
20661
  removeConversationAttachments(conversationId) {
19964
- return rm5(path18.join(this.conversationDir(conversationId), "attachments"), {
20662
+ return rm5(path19.join(this.conversationDir(conversationId), "attachments"), {
19965
20663
  recursive: true,
19966
20664
  force: true
19967
20665
  });
19968
20666
  }
19969
20667
  conversationDir(conversationId) {
19970
20668
  assertValidConversationId(conversationId);
19971
- return path18.join(this.paths.conversationsDir, conversationId);
20669
+ return path19.join(this.paths.conversationsDir, conversationId);
19972
20670
  }
19973
20671
  manifestPath(conversationId) {
19974
- return path18.join(this.conversationDir(conversationId), "manifest.json");
20672
+ return path19.join(this.conversationDir(conversationId), "manifest.json");
19975
20673
  }
19976
20674
  snapshotPath(conversationId) {
19977
- return path18.join(this.conversationDir(conversationId), "snapshot.json");
20675
+ return path19.join(this.conversationDir(conversationId), "snapshot.json");
19978
20676
  }
19979
20677
  eventsPath(conversationId) {
19980
- return path18.join(this.conversationDir(conversationId), "events.ndjson");
20678
+ return path19.join(this.conversationDir(conversationId), "events.ndjson");
19981
20679
  }
19982
20680
  };
19983
20681
  function createEmptySnapshot2() {
@@ -19988,13 +20686,13 @@ function isNodeError11(error, code) {
19988
20686
  }
19989
20687
 
19990
20688
  // src/conversations/hermes-session-sync.ts
19991
- import { randomUUID as randomUUID11 } from "crypto";
20689
+ import { createHash as createHash6, randomUUID as randomUUID11 } from "crypto";
19992
20690
  import { readdir as readdir7, readFile as readFile11, stat as stat12 } from "fs/promises";
19993
- import path20 from "path";
20691
+ import path21 from "path";
19994
20692
 
19995
20693
  // src/conversations/delivery-import.ts
19996
20694
  import { lstat as lstat2, readFile as readFile10, readdir as readdir6, stat as stat11 } from "fs/promises";
19997
- import path19 from "path";
20695
+ import path20 from "path";
19998
20696
  var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
19999
20697
  var MAX_MEDIA_IMPORT_FAILURES = 20;
20000
20698
  var MAX_DELIVERY_FILES = 50;
@@ -20065,16 +20763,16 @@ var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
20065
20763
  ".m4a"
20066
20764
  ]);
20067
20765
  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)) {
20766
+ const resolvedDir = path20.resolve(stagingDir);
20767
+ const relative = path20.relative(path20.resolve(paths.conversationsDir), resolvedDir);
20768
+ if (!relative || relative.startsWith("..") || path20.isAbsolute(relative)) {
20071
20769
  throw new LinkHttpError(
20072
20770
  400,
20073
20771
  "delivery_staging_invalid",
20074
20772
  "delivery staging directory must be inside Hermes Link conversations"
20075
20773
  );
20076
20774
  }
20077
- const segments = relative.split(path19.sep);
20775
+ const segments = relative.split(path20.sep);
20078
20776
  if (segments.length !== 3 || segments[1] !== DELIVERY_STAGING_SEGMENT || !segments[0] || !segments[2]) {
20079
20777
  throw new LinkHttpError(
20080
20778
  400,
@@ -20110,7 +20808,7 @@ async function collectStagedDeliveryReferences(stagingDir) {
20110
20808
  return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
20111
20809
  (left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
20112
20810
  ).slice(0, MAX_DELIVERY_FILES).map((entry) => {
20113
- const sourcePath = path19.join(stagingDir, entry.name);
20811
+ const sourcePath = path20.join(stagingDir, entry.name);
20114
20812
  const mime = inferMimeType(sourcePath);
20115
20813
  return {
20116
20814
  path: sourcePath,
@@ -20296,7 +20994,7 @@ async function writeBlobFromFile(deps, conversationId, source) {
20296
20994
  }
20297
20995
  return deps.writeBlob(conversationId, {
20298
20996
  bytes: await readFile10(sourcePath),
20299
- filename: path19.basename(sourcePath),
20997
+ filename: path20.basename(sourcePath),
20300
20998
  mime: source.mime ?? inferMimeType(sourcePath)
20301
20999
  });
20302
21000
  }
@@ -20309,7 +21007,7 @@ function describeMediaImportFailure(reference, sourceKey, error) {
20309
21007
  };
20310
21008
  }
20311
21009
  function isSupportedDeliveryFilename(filename) {
20312
- return SUPPORTED_DELIVERY_EXTENSIONS.has(path19.extname(filename).toLowerCase());
21010
+ return SUPPORTED_DELIVERY_EXTENSIONS.has(path20.extname(filename).toLowerCase());
20313
21011
  }
20314
21012
  function readString10(payload, key) {
20315
21013
  const value = payload[key];
@@ -20367,7 +21065,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
20367
21065
  const candidates = [];
20368
21066
  for (const profileName of profileNames) {
20369
21067
  const profileDir = resolveHermesProfileDir(profileName);
20370
- const dbPath = path20.join(profileDir, "state.db");
21068
+ const dbPath = path21.join(profileDir, "state.db");
20371
21069
  const sessions = await listProfileSessions(dbPath).catch((error) => {
20372
21070
  result.errors.push({
20373
21071
  profile: profileName,
@@ -20483,7 +21181,7 @@ async function syncHermesCronSessionIntoConversations(paths, logger, input) {
20483
21181
  const knownHermesSessions = await readKnownHermesSessions(store);
20484
21182
  const profileName = input.profileName.trim() || DEFAULT_PROFILE_NAME;
20485
21183
  const profileDir = resolveHermesProfileDir(profileName);
20486
- const dbPath = path20.join(profileDir, "state.db");
21184
+ const dbPath = path21.join(profileDir, "state.db");
20487
21185
  const sessions = await listProfileSessionsByIdPrefix(
20488
21186
  dbPath,
20489
21187
  `cron_${jobId}_`
@@ -20548,6 +21246,131 @@ async function syncHermesCronSessionIntoConversations(paths, logger, input) {
20548
21246
  reprojected: false
20549
21247
  };
20550
21248
  }
21249
+ async function syncHermesConversationMessages(paths, logger, input) {
21250
+ const store = input.store ?? new ConversationStore(paths);
21251
+ const manifest = await store.readActiveManifest(input.conversationId);
21252
+ const snapshot = await store.readSnapshot(input.conversationId);
21253
+ const targets = collectHermesSessionDeleteTargets(manifest, snapshot);
21254
+ const result = {
21255
+ conversation_id: input.conversationId,
21256
+ hermes_session_ids: targets.map((target) => target.sessionId),
21257
+ appended_count: 0,
21258
+ changed: false,
21259
+ errors: []
21260
+ };
21261
+ if (targets.length === 0) {
21262
+ return result;
21263
+ }
21264
+ const candidates = await collectConversationSyncCandidates(targets);
21265
+ const knownExactKeys = collectKnownHermesRowKeys(snapshot);
21266
+ const representedMessages = collectRepresentedMessageSignatures(snapshot);
21267
+ const projectedMessages = [];
21268
+ const candidateProfiles = /* @__PURE__ */ new Map();
21269
+ for (const candidate of candidates) {
21270
+ try {
21271
+ const candidateMessages = await readHermesLineageMessages(candidate);
21272
+ if (candidateMessages.length === 0) {
21273
+ continue;
21274
+ }
21275
+ const profile = await resolveConversationProfileTarget(
21276
+ paths,
21277
+ candidate.profileName
21278
+ );
21279
+ candidateProfiles.set(conversationSyncCandidateKey(candidate), {
21280
+ candidate,
21281
+ profileUid: profile.profileUid,
21282
+ profileName: profile.profileName
21283
+ });
21284
+ projectedMessages.push(
21285
+ ...toLinkMessages({
21286
+ conversationId: input.conversationId,
21287
+ profileName: profile.profileName,
21288
+ profileUid: profile.profileUid,
21289
+ profileDisplayName: profile.profileDisplayName,
21290
+ sessionId: candidate.session.id,
21291
+ messages: candidateMessages
21292
+ })
21293
+ );
21294
+ } catch (error) {
21295
+ result.errors.push({
21296
+ profile: candidate.profileName,
21297
+ session_id: candidate.session.id,
21298
+ message: error instanceof Error ? error.message : String(error)
21299
+ });
21300
+ }
21301
+ }
21302
+ const appendedMessages = [];
21303
+ for (const message of projectedMessages.sort(compareLinkMessagesByCreatedAt)) {
21304
+ const exactKeys = collectMessageHermesRowKeys(message);
21305
+ if (exactKeys.length > 0 && exactKeys.every((key) => knownExactKeys.has(key))) {
21306
+ consumeRepresentedMessageSignature(representedMessages, message);
21307
+ continue;
21308
+ }
21309
+ if (consumeRepresentedMessageSignature(representedMessages, message)) {
21310
+ exactKeys.forEach((key) => knownExactKeys.add(key));
21311
+ continue;
21312
+ }
21313
+ appendedMessages.push(message);
21314
+ exactKeys.forEach((key) => knownExactKeys.add(key));
21315
+ }
21316
+ if (appendedMessages.length === 0) {
21317
+ return result;
21318
+ }
21319
+ await store.writeSnapshot(input.conversationId, {
21320
+ ...snapshot,
21321
+ messages: [...snapshot.messages, ...appendedMessages]
21322
+ });
21323
+ await hydrateImportedConversationMedia({
21324
+ paths,
21325
+ store,
21326
+ logger,
21327
+ conversationId: input.conversationId
21328
+ });
21329
+ const hydratedSnapshot = await store.readSnapshot(input.conversationId);
21330
+ const appendedIds = new Set(appendedMessages.map((message) => message.id));
21331
+ const hydratedAppendedMessages = hydratedSnapshot.messages.filter(
21332
+ (message) => appendedIds.has(message.id)
21333
+ );
21334
+ let nextManifest = await store.readManifest(input.conversationId);
21335
+ for (const profile of candidateProfiles.values()) {
21336
+ nextManifest = mergeHermesLineageIntoManifest({
21337
+ manifest: nextManifest,
21338
+ candidate: profile.candidate,
21339
+ snapshot: hydratedSnapshot,
21340
+ profileUid: profile.profileUid,
21341
+ profileName: profile.profileName,
21342
+ updatedAt: isoFromHermesTime(profile.candidate.session.last_active) ?? nextManifest.updated_at
21343
+ });
21344
+ }
21345
+ const stats = buildConversationStats(nextManifest, hydratedSnapshot);
21346
+ nextManifest = {
21347
+ ...nextManifest,
21348
+ stats,
21349
+ updated_at: latestTimestamp(
21350
+ nextManifest.updated_at,
21351
+ latestMessageTimestamp(hydratedAppendedMessages)
21352
+ )
21353
+ };
21354
+ await store.writeManifest(nextManifest);
21355
+ await upsertConversationStats(paths, toStatsIndexRecord(nextManifest, stats));
21356
+ const appendEvent = input.appendEvent ?? ((conversationId, event) => store.appendEvent(conversationId, event));
21357
+ let lastEventSeq;
21358
+ for (const message of hydratedAppendedMessages) {
21359
+ const event = await appendEvent(input.conversationId, {
21360
+ type: "message.created",
21361
+ message_id: message.id,
21362
+ payload: { message, imported_from: "hermes" },
21363
+ raw: message.raw
21364
+ });
21365
+ lastEventSeq = event.seq;
21366
+ }
21367
+ result.appended_count = hydratedAppendedMessages.length;
21368
+ result.changed = hydratedAppendedMessages.length > 0;
21369
+ if (lastEventSeq !== void 0) {
21370
+ result.last_event_seq = lastEventSeq;
21371
+ }
21372
+ return result;
21373
+ }
20551
21374
  async function importHermesSession(input) {
20552
21375
  const { paths, store, logger, candidate } = input;
20553
21376
  const profile = await resolveConversationProfileTarget(
@@ -20759,6 +21582,143 @@ function findKnownConversationIdsForCandidate(known, candidate) {
20759
21582
  }
20760
21583
  return conversationIds;
20761
21584
  }
21585
+ async function collectConversationSyncCandidates(targets) {
21586
+ const sessionIdsByProfile = /* @__PURE__ */ new Map();
21587
+ for (const target of targets) {
21588
+ const profileName = target.profileName.trim() || DEFAULT_PROFILE_NAME;
21589
+ const sessionId = target.sessionId.trim();
21590
+ if (!sessionId) {
21591
+ continue;
21592
+ }
21593
+ const current = sessionIdsByProfile.get(profileName) ?? /* @__PURE__ */ new Set();
21594
+ current.add(sessionId);
21595
+ sessionIdsByProfile.set(profileName, current);
21596
+ }
21597
+ const candidates = [];
21598
+ const seenCandidates = /* @__PURE__ */ new Set();
21599
+ for (const [profileName, targetSessionIds] of sessionIdsByProfile) {
21600
+ const profileDir = resolveHermesProfileDir(profileName);
21601
+ const dbPath = path21.join(profileDir, "state.db");
21602
+ const coveredSessionIds = /* @__PURE__ */ new Set();
21603
+ const sessions = await listProfileSessions(dbPath).catch(() => []);
21604
+ for (const session of sessions) {
21605
+ if (isDeletedSession(session) || isHiddenSession(session)) {
21606
+ continue;
21607
+ }
21608
+ const candidate = {
21609
+ profileName,
21610
+ profileDir,
21611
+ dbPath,
21612
+ session
21613
+ };
21614
+ const candidateSessionIds = lineageSessionIds(candidate);
21615
+ if (!candidateSessionIds.some(
21616
+ (sessionId) => targetSessionIds.has(sessionId)
21617
+ )) {
21618
+ continue;
21619
+ }
21620
+ for (const sessionId of candidateSessionIds) {
21621
+ if (targetSessionIds.has(sessionId)) {
21622
+ coveredSessionIds.add(sessionId);
21623
+ }
21624
+ }
21625
+ const key = conversationSyncCandidateKey(candidate);
21626
+ if (!seenCandidates.has(key)) {
21627
+ seenCandidates.add(key);
21628
+ candidates.push(candidate);
21629
+ }
21630
+ }
21631
+ for (const sessionId of targetSessionIds) {
21632
+ if (coveredSessionIds.has(sessionId)) {
21633
+ continue;
21634
+ }
21635
+ const candidate = {
21636
+ profileName,
21637
+ profileDir,
21638
+ dbPath,
21639
+ session: { id: sessionId }
21640
+ };
21641
+ const key = conversationSyncCandidateKey(candidate);
21642
+ if (!seenCandidates.has(key)) {
21643
+ seenCandidates.add(key);
21644
+ candidates.push(candidate);
21645
+ }
21646
+ }
21647
+ }
21648
+ return candidates;
21649
+ }
21650
+ function conversationSyncCandidateKey(candidate) {
21651
+ return `${candidate.profileName}\0${candidate.session.id}`;
21652
+ }
21653
+ function collectKnownHermesRowKeys(snapshot) {
21654
+ const keys = /* @__PURE__ */ new Set();
21655
+ for (const message of snapshot.messages) {
21656
+ collectMessageHermesRowKeys(message).forEach((key) => keys.add(key));
21657
+ }
21658
+ return keys;
21659
+ }
21660
+ function collectMessageHermesRowKeys(message) {
21661
+ const keys = [];
21662
+ readHermesRawMessageRows(message.raw).forEach((row, index) => {
21663
+ keys.push(hermesRowKey(row, index));
21664
+ });
21665
+ const hermes = toRecord11(message.hermes);
21666
+ const sessionId = readString11(hermes, "session_id") ?? "";
21667
+ const messageIds = Array.isArray(hermes.message_ids) ? hermes.message_ids : hermes.message_id === void 0 || hermes.message_id === null ? [] : [hermes.message_id];
21668
+ for (const messageId of messageIds) {
21669
+ if (messageId === void 0 || messageId === null) {
21670
+ continue;
21671
+ }
21672
+ keys.push(hermesMessageIdKey(sessionId, messageId));
21673
+ }
21674
+ return [...new Set(keys)];
21675
+ }
21676
+ function collectRepresentedMessageSignatures(snapshot) {
21677
+ const counts = /* @__PURE__ */ new Map();
21678
+ for (const message of snapshot.messages) {
21679
+ const signature = representedMessageSignature(message);
21680
+ if (!signature) {
21681
+ continue;
21682
+ }
21683
+ counts.set(signature, (counts.get(signature) ?? 0) + 1);
21684
+ }
21685
+ return counts;
21686
+ }
21687
+ function consumeRepresentedMessageSignature(counts, message) {
21688
+ const signature = representedMessageSignature(message);
21689
+ if (!signature) {
21690
+ return false;
21691
+ }
21692
+ const count = counts.get(signature) ?? 0;
21693
+ if (count <= 0) {
21694
+ return false;
21695
+ }
21696
+ if (count === 1) {
21697
+ counts.delete(signature);
21698
+ } else {
21699
+ counts.set(signature, count - 1);
21700
+ }
21701
+ return true;
21702
+ }
21703
+ function representedMessageSignature(message) {
21704
+ const text = messageText(message);
21705
+ if (!text) {
21706
+ return null;
21707
+ }
21708
+ return `${message.role}:${hashText(text)}`;
21709
+ }
21710
+ function hashText(value) {
21711
+ return createHash6("sha256").update(value).digest("hex");
21712
+ }
21713
+ function compareLinkMessagesByCreatedAt(left, right) {
21714
+ return Date.parse(left.created_at) - Date.parse(right.created_at) || left.id.localeCompare(right.id);
21715
+ }
21716
+ function latestMessageTimestamp(messages2) {
21717
+ return messages2.reduce(
21718
+ (latest, message) => latestTimestamp(latest, message.updated_at || message.created_at),
21719
+ messages2[0]?.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
21720
+ );
21721
+ }
20762
21722
  function lineageSessionIds(candidate) {
20763
21723
  return normalizeSessionIds([
20764
21724
  candidate.session._lineage_root_id,
@@ -21048,9 +22008,12 @@ function appendHermesRowOnce(rows, seen, row) {
21048
22008
  }
21049
22009
  function hermesRowKey(row, fallbackIndex) {
21050
22010
  if (row.id !== void 0 && row.id !== null) {
21051
- return `id:${row.id}`;
22011
+ return hermesMessageIdKey(readString11(row, "session_id") ?? "", row.id);
21052
22012
  }
21053
- return `fallback:${fallbackIndex}:${row.role ?? ""}:${row.timestamp ?? ""}:${normalizeContent2(row.content)}`;
22013
+ return `fallback:${readString11(row, "session_id") ?? ""}:${fallbackIndex}:${row.role ?? ""}:${row.timestamp ?? ""}:${normalizeContent2(row.content)}`;
22014
+ }
22015
+ function hermesMessageIdKey(sessionId, messageId) {
22016
+ return `id:${sessionId}:${String(messageId)}`;
21054
22017
  }
21055
22018
  function hasHermesToolMetadata(row) {
21056
22019
  return normalizeMessageRole(row.role) === "tool" || readHermesToolCalls(row).length > 0 || Boolean(readString11(row, "tool_call_id")) || Boolean(readString11(row, "tool_name"));
@@ -21794,8 +22757,8 @@ async function readJsonlMessages(profileName, sessionId) {
21794
22757
  return [];
21795
22758
  }
21796
22759
  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`);
22760
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path21.join(profileDir, "sessions"));
22761
+ const transcriptPath = path21.join(sessionsDir, `${sessionId}.jsonl`);
21799
22762
  const raw = await readFile11(transcriptPath, "utf8").catch((error) => {
21800
22763
  if (isNodeError13(error, "ENOENT")) {
21801
22764
  return "";
@@ -22103,13 +23066,13 @@ function toRecord11(value) {
22103
23066
  return typeof value === "object" && value !== null ? value : {};
22104
23067
  }
22105
23068
  function isDeletedSession(session) {
22106
- return readBoolean(session.deleted) || readBoolean(session.is_deleted) || Boolean(readString11(session, "deleted_at")) || ["deleted", "removed"].includes(readString11(session, "status") ?? "");
23069
+ return readBoolean2(session.deleted) || readBoolean2(session.is_deleted) || Boolean(readString11(session, "deleted_at")) || ["deleted", "removed"].includes(readString11(session, "status") ?? "");
22107
23070
  }
22108
23071
  function isHiddenSession(session) {
22109
23072
  const source = readString11(session, "source")?.toLowerCase();
22110
23073
  const status = readString11(session, "status")?.toLowerCase();
22111
23074
  const visibility = readString11(session, "visibility")?.toLowerCase();
22112
- return Boolean(source && HIDDEN_SESSION_SOURCES.has(source)) || readBoolean(session.hidden) || readBoolean(session.archived) || Boolean(readString11(session, "archived_at")) || status === "hidden" || status === "archived" || visibility === "hidden" || visibility === "hide";
23075
+ return Boolean(source && HIDDEN_SESSION_SOURCES.has(source)) || readBoolean2(session.hidden) || readBoolean2(session.archived) || Boolean(readString11(session, "archived_at")) || status === "hidden" || status === "archived" || visibility === "hidden" || visibility === "hide";
22113
23076
  }
22114
23077
  function readTableColumns2(db, tableName) {
22115
23078
  try {
@@ -22150,7 +23113,7 @@ function readString11(payload, key) {
22150
23113
  function readNumber3(value) {
22151
23114
  return typeof value === "number" && Number.isFinite(value) ? value : null;
22152
23115
  }
22153
- function readBoolean(value) {
23116
+ function readBoolean2(value) {
22154
23117
  if (value === true || value === 1) {
22155
23118
  return true;
22156
23119
  }
@@ -22165,7 +23128,7 @@ function isNodeError13(error, code) {
22165
23128
 
22166
23129
  // src/conversations/delivery-context.ts
22167
23130
  import { copyFile, mkdir as mkdir11, stat as stat13 } from "fs/promises";
22168
- import path21 from "path";
23131
+ import path22 from "path";
22169
23132
  var ACTIVE_CONTEXTS = /* @__PURE__ */ new Map();
22170
23133
  var CONTEXT_TTL_MS = 2 * 60 * 60 * 1e3;
22171
23134
  var MAX_DELIVERY_TOOL_FILES = 50;
@@ -22237,10 +23200,10 @@ function findDeliveryContext(input) {
22237
23200
  );
22238
23201
  }
22239
23202
  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)) {
23203
+ const stagingDir = path22.resolve(context.stagingDir);
23204
+ const conversationsRoot = path22.resolve(paths.conversationsDir);
23205
+ const relative = path22.relative(conversationsRoot, stagingDir);
23206
+ if (!relative || relative.startsWith("..") || path22.isAbsolute(relative)) {
22244
23207
  throw new LinkHttpError(
22245
23208
  400,
22246
23209
  "delivery_staging_invalid",
@@ -22251,13 +23214,13 @@ async function copyFilesIntoDeliveryContext(paths, context, files) {
22251
23214
  const result = { staged: [], skipped: [] };
22252
23215
  const usedNames = /* @__PURE__ */ new Set();
22253
23216
  for (const [index, file] of files.slice(0, MAX_DELIVERY_TOOL_FILES).entries()) {
22254
- const sourcePath = path21.resolve(file.path);
23217
+ const sourcePath = path22.resolve(file.path);
22255
23218
  let baseName = sanitizeFilename(
22256
- file.caption || path21.basename(sourcePath),
23219
+ file.caption || path22.basename(sourcePath),
22257
23220
  `attachment-${index + 1}`
22258
23221
  );
22259
- const sourceExtension = path21.extname(sourcePath);
22260
- if (!path21.extname(baseName) && sourceExtension) {
23222
+ const sourceExtension = path22.extname(sourcePath);
23223
+ if (!path22.extname(baseName) && sourceExtension) {
22261
23224
  baseName = `${baseName}${sourceExtension}`;
22262
23225
  }
22263
23226
  const filename = uniqueStagingFilename(
@@ -22284,7 +23247,7 @@ async function copyFilesIntoDeliveryContext(paths, context, files) {
22284
23247
  });
22285
23248
  continue;
22286
23249
  }
22287
- const targetPath = path21.join(stagingDir, filename);
23250
+ const targetPath = path22.join(stagingDir, filename);
22288
23251
  await copyFile(sourcePath, targetPath);
22289
23252
  result.staged.push({
22290
23253
  source_path: sourcePath,
@@ -22327,7 +23290,7 @@ function normalizeDeliveryFileInputs(value) {
22327
23290
  });
22328
23291
  }
22329
23292
  function uniqueStagingFilename(filename, usedNames) {
22330
- const extension = path21.extname(filename);
23293
+ const extension = path22.extname(filename);
22331
23294
  const stem = filename.slice(0, filename.length - extension.length);
22332
23295
  let candidate = filename;
22333
23296
  let suffix = 2;
@@ -22368,7 +23331,7 @@ function isNodeError14(error, code) {
22368
23331
  }
22369
23332
 
22370
23333
  // src/conversations/run-lifecycle.ts
22371
- import { createHash as createHash7 } from "crypto";
23334
+ import { createHash as createHash8 } from "crypto";
22372
23335
  import { readdir as readdir9 } from "fs/promises";
22373
23336
 
22374
23337
  // src/hermes/api-server.ts
@@ -22818,9 +23781,9 @@ function parseHermesApiCapabilities(payload) {
22818
23781
  const runStop = isRecord2(endpoints.run_stop) ? endpoints.run_stop : {};
22819
23782
  return {
22820
23783
  source: "reported",
22821
- authRequired: readBoolean2(auth, "required"),
22822
- responsesStreaming: readBoolean2(features, "responses_streaming"),
22823
- runStopPath: readBoolean2(features, "run_stop") === false ? null : readString13(runStop, "path"),
23784
+ authRequired: readBoolean3(auth, "required"),
23785
+ responsesStreaming: readBoolean3(features, "responses_streaming"),
23786
+ runStopPath: readBoolean3(features, "run_stop") === false ? null : readString13(runStop, "path"),
22824
23787
  sessionContinuityHeader: readString13(
22825
23788
  features,
22826
23789
  "session_continuity_header"
@@ -22828,10 +23791,10 @@ function parseHermesApiCapabilities(payload) {
22828
23791
  sessionKeyHeader: readString13(features, "session_key_header")
22829
23792
  };
22830
23793
  }
22831
- async function callHermesApi(path37, init, options) {
23794
+ async function callHermesApi(path38, init, options) {
22832
23795
  const method = init.method ?? "GET";
22833
23796
  const startedAt = Date.now();
22834
- void options.logger?.debug("hermes_api_request_started", { method, path: path37 });
23797
+ void options.logger?.debug("hermes_api_request_started", { method, path: path38 });
22835
23798
  const availability = await ensureHermesApiServerAvailable({
22836
23799
  fetchImpl: options.fetchImpl,
22837
23800
  logger: options.logger,
@@ -22840,7 +23803,7 @@ async function callHermesApi(path37, init, options) {
22840
23803
  });
22841
23804
  let config = availability.configResult.apiServer;
22842
23805
  const fetcher = options.fetchImpl ?? fetch;
22843
- const request = () => fetchHermesApi(fetcher, config, path37, init, options);
23806
+ const request = () => fetchHermesApi(fetcher, config, path38, init, options);
22844
23807
  let response;
22845
23808
  try {
22846
23809
  response = await request();
@@ -22848,7 +23811,7 @@ async function callHermesApi(path37, init, options) {
22848
23811
  logHermesApiError(
22849
23812
  options.logger,
22850
23813
  method,
22851
- path37,
23814
+ path38,
22852
23815
  options.profileName,
22853
23816
  startedAt,
22854
23817
  error
@@ -22859,7 +23822,7 @@ async function callHermesApi(path37, init, options) {
22859
23822
  logHermesApiResponse(
22860
23823
  options.logger,
22861
23824
  method,
22862
- path37,
23825
+ path38,
22863
23826
  options.profileName,
22864
23827
  startedAt,
22865
23828
  response
@@ -22868,7 +23831,7 @@ async function callHermesApi(path37, init, options) {
22868
23831
  }
22869
23832
  void options.logger?.warn("hermes_api_request_retrying_after_401", {
22870
23833
  method,
22871
- path: path37,
23834
+ path: path38,
22872
23835
  profile: options.profileName ?? "default",
22873
23836
  port: config.port ?? null,
22874
23837
  duration_ms: Date.now() - startedAt
@@ -22887,7 +23850,7 @@ async function callHermesApi(path37, init, options) {
22887
23850
  logHermesApiError(
22888
23851
  options.logger,
22889
23852
  method,
22890
- path37,
23853
+ path38,
22891
23854
  options.profileName,
22892
23855
  startedAt,
22893
23856
  error
@@ -22897,7 +23860,7 @@ async function callHermesApi(path37, init, options) {
22897
23860
  logHermesApiResponse(
22898
23861
  options.logger,
22899
23862
  method,
22900
- path37,
23863
+ path38,
22901
23864
  options.profileName,
22902
23865
  startedAt,
22903
23866
  response
@@ -22907,7 +23870,7 @@ async function callHermesApi(path37, init, options) {
22907
23870
  }
22908
23871
  void options.logger?.warn("hermes_api_request_repairing_after_401", {
22909
23872
  method,
22910
- path: path37,
23873
+ path: path38,
22911
23874
  profile: options.profileName ?? "default",
22912
23875
  port: config.port ?? null,
22913
23876
  duration_ms: Date.now() - startedAt
@@ -22928,7 +23891,7 @@ async function callHermesApi(path37, init, options) {
22928
23891
  logHermesApiError(
22929
23892
  options.logger,
22930
23893
  method,
22931
- path37,
23894
+ path38,
22932
23895
  options.profileName,
22933
23896
  startedAt,
22934
23897
  error
@@ -22938,21 +23901,21 @@ async function callHermesApi(path37, init, options) {
22938
23901
  logHermesApiResponse(
22939
23902
  options.logger,
22940
23903
  method,
22941
- path37,
23904
+ path38,
22942
23905
  options.profileName,
22943
23906
  startedAt,
22944
23907
  response
22945
23908
  );
22946
23909
  return response;
22947
23910
  }
22948
- async function fetchHermesApi(fetcher, config, path37, init, options) {
23911
+ async function fetchHermesApi(fetcher, config, path38, init, options) {
22949
23912
  const headers = new Headers(init.headers);
22950
23913
  headers.set("accept", headers.get("accept") ?? "application/json");
22951
23914
  if (config.key) {
22952
23915
  headers.set("x-api-key", config.key);
22953
23916
  headers.set("authorization", `Bearer ${config.key}`);
22954
23917
  }
22955
- return await fetcher(`http://127.0.0.1:${config.port}${path37}`, {
23918
+ return await fetcher(`http://127.0.0.1:${config.port}${path38}`, {
22956
23919
  ...init,
22957
23920
  headers
22958
23921
  }).catch((error) => {
@@ -22961,10 +23924,10 @@ async function fetchHermesApi(fetcher, config, path37, init, options) {
22961
23924
  }
22962
23925
  void options.logger?.warn("hermes_api_server_connect_failed", {
22963
23926
  method: String(init.method ?? "GET").toUpperCase(),
22964
- path: path37,
23927
+ path: path38,
22965
23928
  profile: options.profileName ?? "default",
22966
23929
  port: config.port ?? null,
22967
- url: `http://127.0.0.1:${config.port}${path37}`,
23930
+ url: `http://127.0.0.1:${config.port}${path38}`,
22968
23931
  error: error instanceof Error ? error.message : String(error)
22969
23932
  });
22970
23933
  throw new LinkHttpError(
@@ -22974,10 +23937,10 @@ async function fetchHermesApi(fetcher, config, path37, init, options) {
22974
23937
  );
22975
23938
  });
22976
23939
  }
22977
- function logHermesApiResponse(logger, method, path37, profileName, startedAt, response) {
23940
+ function logHermesApiResponse(logger, method, path38, profileName, startedAt, response) {
22978
23941
  const fields = {
22979
23942
  method,
22980
- path: path37,
23943
+ path: path38,
22981
23944
  profile: profileName ?? "default",
22982
23945
  status: response.status,
22983
23946
  duration_ms: Date.now() - startedAt
@@ -22998,10 +23961,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
22998
23961
  ...upstreamError ? { upstream_error: upstreamError } : {}
22999
23962
  });
23000
23963
  }
23001
- function logHermesApiError(logger, method, path37, profileName, startedAt, error) {
23964
+ function logHermesApiError(logger, method, path38, profileName, startedAt, error) {
23002
23965
  void logger?.warn("hermes_api_request_failed", {
23003
23966
  method,
23004
- path: path37,
23967
+ path: path38,
23005
23968
  profile: profileName ?? "default",
23006
23969
  duration_ms: Date.now() - startedAt,
23007
23970
  ...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
@@ -23062,18 +24025,18 @@ function readString13(payload, key) {
23062
24025
  const value = payload[key];
23063
24026
  return typeof value === "string" && value.trim() ? value.trim() : null;
23064
24027
  }
23065
- function readBoolean2(payload, key) {
24028
+ function readBoolean3(payload, key) {
23066
24029
  const value = payload[key];
23067
24030
  return typeof value === "boolean" ? value : null;
23068
24031
  }
23069
24032
 
23070
24033
  // src/hermes/stt.ts
23071
- import { execFile as execFile3 } from "child_process";
24034
+ import { execFile as execFile4 } from "child_process";
23072
24035
  import { access as access2, readFile as readFile12, stat as stat14 } from "fs/promises";
23073
24036
  import os6 from "os";
23074
- import path22 from "path";
23075
- import { promisify as promisify3 } from "util";
23076
- var execFileAsync3 = promisify3(execFile3);
24037
+ import path23 from "path";
24038
+ import { promisify as promisify4 } from "util";
24039
+ var execFileAsync4 = promisify4(execFile4);
23077
24040
  var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
23078
24041
  var STT_TIMEOUT_MS = 18e4;
23079
24042
  var STT_MAX_BUFFER_BYTES = 2 * 1024 * 1024;
@@ -23106,7 +24069,7 @@ async function transcribeAudioWithHermesProfile(input) {
23106
24069
  let stdout = "";
23107
24070
  let stderr = "";
23108
24071
  try {
23109
- const output = await execFileAsync3(
24072
+ const output = await execFileAsync4(
23110
24073
  python.command,
23111
24074
  [...python.args, "-c", script, input.audioPath],
23112
24075
  {
@@ -23166,7 +24129,7 @@ async function buildHermesSttEnv(profileName, hermesSourceRoot) {
23166
24129
  };
23167
24130
  const sourceRoot = hermesSourceRoot ?? await findDevHermesAgentSource();
23168
24131
  if (sourceRoot) {
23169
- env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path22.delimiter);
24132
+ env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path23.delimiter);
23170
24133
  }
23171
24134
  return env;
23172
24135
  }
@@ -23261,14 +24224,14 @@ async function resolveHermesPythonRuntime() {
23261
24224
  };
23262
24225
  }
23263
24226
  async function resolveExecutablePath(command) {
23264
- if (path22.isAbsolute(command)) {
24227
+ if (path23.isAbsolute(command)) {
23265
24228
  return await isExecutableFile2(command) ? command : null;
23266
24229
  }
23267
24230
  const pathEnv = process.env.PATH ?? "";
23268
24231
  const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
23269
- for (const dir of pathEnv.split(path22.delimiter)) {
24232
+ for (const dir of pathEnv.split(path23.delimiter)) {
23270
24233
  for (const extension of extensions) {
23271
- const candidate = path22.join(dir, `${command}${extension}`);
24234
+ const candidate = path23.join(dir, `${command}${extension}`);
23272
24235
  if (await isExecutableFile2(candidate)) {
23273
24236
  return candidate;
23274
24237
  }
@@ -23290,11 +24253,11 @@ async function isExecutableFile2(filePath) {
23290
24253
  }
23291
24254
  async function findHermesVenvPython(sourceRoot) {
23292
24255
  const candidates = process.platform === "win32" ? [
23293
- path22.join(sourceRoot, "venv", "Scripts", "python.exe"),
23294
- path22.join(sourceRoot, ".venv", "Scripts", "python.exe")
24256
+ path23.join(sourceRoot, "venv", "Scripts", "python.exe"),
24257
+ path23.join(sourceRoot, ".venv", "Scripts", "python.exe")
23295
24258
  ] : [
23296
- path22.join(sourceRoot, "venv", "bin", "python"),
23297
- path22.join(sourceRoot, ".venv", "bin", "python")
24259
+ path23.join(sourceRoot, "venv", "bin", "python"),
24260
+ path23.join(sourceRoot, ".venv", "bin", "python")
23298
24261
  ];
23299
24262
  for (const candidate of candidates) {
23300
24263
  if (await isExecutableFile2(candidate)) {
@@ -23323,8 +24286,8 @@ function shebangToPythonCommand(shebang) {
23323
24286
  }
23324
24287
  async function findDevHermesAgentSource() {
23325
24288
  const candidates = [
23326
- path22.resolve(process.cwd(), "reference/hermes-agent"),
23327
- path22.resolve(process.cwd(), "../../reference/hermes-agent")
24289
+ path23.resolve(process.cwd(), "reference/hermes-agent"),
24290
+ path23.resolve(process.cwd(), "../../reference/hermes-agent")
23328
24291
  ];
23329
24292
  for (const candidate of candidates) {
23330
24293
  if (await isHermesAgentSourceRoot(candidate)) {
@@ -23340,7 +24303,7 @@ async function readHermesLauncherTarget(filePath) {
23340
24303
  line
23341
24304
  );
23342
24305
  const rawTarget = quoted?.groups?.target ?? /^\s*exec\s+(?<target>\S+)\s+(?:"\$@"|'\$@')/.exec(line)?.groups?.target;
23343
- if (!rawTarget || !path22.isAbsolute(rawTarget)) {
24306
+ if (!rawTarget || !path23.isAbsolute(rawTarget)) {
23344
24307
  continue;
23345
24308
  }
23346
24309
  if (await isExecutableFile2(rawTarget)) {
@@ -23370,12 +24333,12 @@ async function resolveHermesEntrypointRuntime(entrypointPath) {
23370
24333
  return null;
23371
24334
  }
23372
24335
  async function findHermesSourceRoot(executablePath) {
23373
- let cursor = path22.dirname(path22.resolve(executablePath));
24336
+ let cursor = path23.dirname(path23.resolve(executablePath));
23374
24337
  for (let index = 0; index < 6; index += 1) {
23375
24338
  if (await isHermesAgentSourceRoot(cursor)) {
23376
24339
  return cursor;
23377
24340
  }
23378
- const parent = path22.dirname(cursor);
24341
+ const parent = path23.dirname(cursor);
23379
24342
  if (parent === cursor) {
23380
24343
  break;
23381
24344
  }
@@ -23386,14 +24349,14 @@ async function findHermesSourceRoot(executablePath) {
23386
24349
  function hermesSourceRootCandidates() {
23387
24350
  const candidates = [
23388
24351
  process.env.HERMES_PYTHON_SRC_ROOT?.trim(),
23389
- path22.join(os6.homedir(), ".hermes", "hermes-agent"),
24352
+ path23.join(os6.homedir(), ".hermes", "hermes-agent"),
23390
24353
  "/usr/local/lib/hermes-agent",
23391
24354
  "/opt/hermes"
23392
24355
  ].filter((candidate) => Boolean(candidate));
23393
24356
  if (process.platform === "win32") {
23394
24357
  const localAppData = process.env.LOCALAPPDATA?.trim();
23395
24358
  if (localAppData) {
23396
- candidates.unshift(path22.join(localAppData, "hermes", "hermes-agent"));
24359
+ candidates.unshift(path23.join(localAppData, "hermes", "hermes-agent"));
23397
24360
  }
23398
24361
  }
23399
24362
  return candidates;
@@ -23403,7 +24366,7 @@ async function findExplicitHermesSourceRoot() {
23403
24366
  return explicit && await isHermesAgentSourceRoot(explicit) ? explicit : null;
23404
24367
  }
23405
24368
  async function isHermesAgentSourceRoot(candidate) {
23406
- return await isDirectory(candidate) && await isDirectory(path22.join(candidate, "tools")) && await isDirectory(path22.join(candidate, "hermes_cli"));
24369
+ return await isDirectory(candidate) && await isDirectory(path23.join(candidate, "tools")) && await isDirectory(path23.join(candidate, "hermes_cli"));
23407
24370
  }
23408
24371
  async function isDirectory(candidate) {
23409
24372
  return stat14(candidate).then((info) => info.isDirectory()).catch(() => false);
@@ -23415,15 +24378,15 @@ function compactProcessOutput(value) {
23415
24378
 
23416
24379
  // src/hermes/usage-probe.ts
23417
24380
  import { open as open3, readFile as readFile14, rm as rm6, stat as stat16 } from "fs/promises";
23418
- import path24 from "path";
24381
+ import path25 from "path";
23419
24382
  import YAML3 from "yaml";
23420
24383
 
23421
24384
  // src/hermes/profiles.ts
23422
- import { execFile as execFile4 } from "child_process";
24385
+ import { execFile as execFile5 } from "child_process";
23423
24386
  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";
24387
+ import path24 from "path";
24388
+ import { setTimeout as delay5 } from "timers/promises";
24389
+ import { promisify as promisify5 } from "util";
23427
24390
  import YAML2 from "yaml";
23428
24391
  var DEFAULT_PROFILE = "default";
23429
24392
  var PROFILE_NAME_PATTERN5 = /^[a-zA-Z0-9._-]{1,64}$/;
@@ -23431,7 +24394,7 @@ var PROFILE_DELETE_TIMEOUT_MS = 3e4;
23431
24394
  var PROFILE_GATEWAY_STOP_TIMEOUT_MS = 3e3;
23432
24395
  var PROFILE_DELETE_STABLE_ABSENCE_MS = 1200;
23433
24396
  var PROFILE_DELETE_VERIFY_INTERVAL_MS = 150;
23434
- var execFileAsync4 = promisify4(execFile4);
24397
+ var execFileAsync5 = promisify5(execFile5);
23435
24398
  async function listHermesProfiles(paths = resolveRuntimePaths()) {
23436
24399
  const profiles = /* @__PURE__ */ new Map();
23437
24400
  if (await hasDefaultProfileConfigSource()) {
@@ -23564,7 +24527,7 @@ async function readHermesProfileCapabilities(name) {
23564
24527
  return {
23565
24528
  defaultModel: listedModels?.defaultModel ?? null,
23566
24529
  modelCount: listedModels?.models.length ?? 0,
23567
- skillCount: await countSkills(path23.join(profileDir, "skills")).catch(
24530
+ skillCount: await countSkills(path24.join(profileDir, "skills")).catch(
23568
24531
  () => 0
23569
24532
  ),
23570
24533
  toolCount: await countConfiguredTools(name).catch(() => 0)
@@ -23625,11 +24588,11 @@ async function hasDefaultProfileConfigSource() {
23625
24588
  if (!profileStat?.isDirectory()) {
23626
24589
  return false;
23627
24590
  }
23628
- return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path23.join(profilePath, ".env"));
24591
+ return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path24.join(profilePath, ".env"));
23629
24592
  }
23630
24593
  async function deleteHermesProfileWithCli(name) {
23631
24594
  try {
23632
- await execFileAsync4(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
24595
+ await execFileAsync5(resolveHermesBin(), ["profile", "delete", name, "--yes"], {
23633
24596
  timeout: PROFILE_DELETE_TIMEOUT_MS,
23634
24597
  windowsHide: true
23635
24598
  });
@@ -23688,7 +24651,7 @@ async function findHermesGatewayProcessIdsForProfile(name) {
23688
24651
  return [];
23689
24652
  }
23690
24653
  try {
23691
- const output = await execFileAsync4("ps", ["-axo", "pid=,command="], {
24654
+ const output = await execFileAsync5("ps", ["-axo", "pid=,command="], {
23692
24655
  timeout: 3e3,
23693
24656
  windowsHide: true
23694
24657
  });
@@ -23728,7 +24691,7 @@ async function waitForProcessesToExit(pids, timeoutMs) {
23728
24691
  const deadline = Date.now() + timeoutMs;
23729
24692
  let remaining = pids.filter(isProcessRunning);
23730
24693
  while (remaining.length > 0 && Date.now() < deadline) {
23731
- await delay4(100);
24694
+ await delay5(100);
23732
24695
  remaining = remaining.filter(isProcessRunning);
23733
24696
  }
23734
24697
  return remaining;
@@ -23747,7 +24710,7 @@ async function waitForProfilePathToRemainAbsent(profilePath) {
23747
24710
  if (await pathExists(profilePath)) {
23748
24711
  return false;
23749
24712
  }
23750
- await delay4(PROFILE_DELETE_VERIFY_INTERVAL_MS);
24713
+ await delay5(PROFILE_DELETE_VERIFY_INTERVAL_MS);
23751
24714
  }
23752
24715
  return !await pathExists(profilePath);
23753
24716
  }
@@ -23795,7 +24758,7 @@ async function countSkills(root) {
23795
24758
  );
23796
24759
  let count = 0;
23797
24760
  for (const entry of entries) {
23798
- const entryPath = path23.join(root, entry.name);
24761
+ const entryPath = path24.join(root, entry.name);
23799
24762
  if (entry.name === ".git" || entry.name === ".hub") {
23800
24763
  continue;
23801
24764
  }
@@ -23888,7 +24851,7 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
23888
24851
  const normalizedProfile = normalizeProfileName6(profileName);
23889
24852
  const profilePath = resolveHermesProfileDir(normalizedProfile);
23890
24853
  const configPath = resolveHermesConfigPath(normalizedProfile);
23891
- const pluginPath = path24.join(
24854
+ const pluginPath = path25.join(
23892
24855
  profilePath,
23893
24856
  "plugins",
23894
24857
  HERMES_USAGE_PROBE_PLUGIN_KEY
@@ -23914,7 +24877,7 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
23914
24877
  return { ...base(), skipped: true };
23915
24878
  }
23916
24879
  try {
23917
- await ensureDirectoryWithInheritedMetadata(path24.dirname(eventsPath), 448);
24880
+ await ensureDirectoryWithInheritedMetadata(path25.dirname(eventsPath), 448);
23918
24881
  const state = await readUsageProbeState(paths);
23919
24882
  const pluginState = state.profiles[normalizedProfile];
23920
24883
  const currentConfig = await readUsageProbeConfigStatus({
@@ -24047,7 +25010,7 @@ async function findHermesUsageProbeEventForRun(input) {
24047
25010
  return aggregateUsageProbeEvents(events.sort(compareUsageProbeEventsByTime));
24048
25011
  }
24049
25012
  function resolveUsageProbeEventsPath(paths = resolveRuntimePaths(), profileName = "default") {
24050
- return path24.join(
25013
+ return path25.join(
24051
25014
  paths.homeDir,
24052
25015
  USAGE_PROBE_DIR,
24053
25016
  safeProfileSegment(profileName),
@@ -24071,8 +25034,8 @@ function summarizeUsageProbeEnsure(result) {
24071
25034
  };
24072
25035
  }
24073
25036
  async function writeUsageProbePlugin(input) {
24074
- const manifestPath = path24.join(input.pluginPath, "plugin.yaml");
24075
- const initPath = path24.join(input.pluginPath, "__init__.py");
25037
+ const manifestPath = path25.join(input.pluginPath, "plugin.yaml");
25038
+ const initPath = path25.join(input.pluginPath, "__init__.py");
24076
25039
  const manifest = usageProbeManifest();
24077
25040
  const source = usageProbePythonSource(
24078
25041
  input.profileName,
@@ -24419,9 +25382,9 @@ async function disableUsageProbeAfterActivationFailure(input) {
24419
25382
  });
24420
25383
  }
24421
25384
  async function cleanupLegacyUsageProbePlugin(profilePath) {
24422
- const legacyPath = path24.join(profilePath, "plugins", LEGACY_PLUGIN_KEY);
25385
+ const legacyPath = path25.join(profilePath, "plugins", LEGACY_PLUGIN_KEY);
24423
25386
  const [manifest, init] = await Promise.all([
24424
- readFile14(path24.join(legacyPath, "plugin.yaml"), "utf8").catch(
25387
+ readFile14(path25.join(legacyPath, "plugin.yaml"), "utf8").catch(
24425
25388
  (error) => {
24426
25389
  if (isNodeError16(error, "ENOENT")) {
24427
25390
  return null;
@@ -24429,7 +25392,7 @@ async function cleanupLegacyUsageProbePlugin(profilePath) {
24429
25392
  throw error;
24430
25393
  }
24431
25394
  ),
24432
- readFile14(path24.join(legacyPath, "__init__.py"), "utf8").catch(
25395
+ readFile14(path25.join(legacyPath, "__init__.py"), "utf8").catch(
24433
25396
  (error) => {
24434
25397
  if (isNodeError16(error, "ENOENT")) {
24435
25398
  return null;
@@ -24478,7 +25441,7 @@ async function rememberUsageProbeState(paths, profileName, patch) {
24478
25441
  }));
24479
25442
  }
24480
25443
  function usageProbeStatePath(paths) {
24481
- return path24.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
25444
+ return path25.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
24482
25445
  }
24483
25446
  async function readHermesConfigDocument2(configPath, language) {
24484
25447
  const existingRaw = await readFile14(configPath, "utf8").catch(
@@ -25046,7 +26009,7 @@ function toRecord14(value) {
25046
26009
 
25047
26010
  // src/conversations/run-transcript-enrichment.ts
25048
26011
  import { readFile as readFile15, stat as stat17 } from "fs/promises";
25049
- import path25 from "path";
26012
+ import path26 from "path";
25050
26013
  var MESSAGE_COLUMNS2 = [
25051
26014
  "id",
25052
26015
  "session_id",
@@ -25122,8 +26085,8 @@ async function readRunFinalAssistantText(input) {
25122
26085
  }
25123
26086
  async function readHermesTranscriptRows(profileName, sessionId) {
25124
26087
  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"));
26088
+ const dbPath = path26.join(profileDir, "state.db");
26089
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path26.join(profileDir, "sessions"));
25127
26090
  const [dbRows, jsonlRows] = await Promise.all([
25128
26091
  readStateDbMessages2(dbPath, sessionId),
25129
26092
  readJsonlMessages2(sessionsDir, sessionId)
@@ -25186,7 +26149,7 @@ async function readJsonlMessages2(sessionsDir, sessionId) {
25186
26149
  if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
25187
26150
  return [];
25188
26151
  }
25189
- const transcriptPath = path25.join(sessionsDir, `${sessionId}.jsonl`);
26152
+ const transcriptPath = path26.join(sessionsDir, `${sessionId}.jsonl`);
25190
26153
  const raw = await readFile15(transcriptPath, "utf8").catch((error) => {
25191
26154
  if (isNodeError17(error, "ENOENT")) {
25192
26155
  return "";
@@ -25438,7 +26401,7 @@ function isNodeError17(error, code) {
25438
26401
  }
25439
26402
 
25440
26403
  // src/conversations/run-tool-event-ids.ts
25441
- import { createHash as createHash6 } from "crypto";
26404
+ import { createHash as createHash7 } from "crypto";
25442
26405
  var RunToolEventIdCoalescer = class {
25443
26406
  scope;
25444
26407
  ordinal = 0;
@@ -25453,7 +26416,7 @@ var RunToolEventIdCoalescer = class {
25453
26416
  if (!type || hasStableToolEventId(event.payload)) {
25454
26417
  return event;
25455
26418
  }
25456
- const toolKey = normalizeToolKey(readToolName(event.payload));
26419
+ const toolKey = normalizeToolKey(readToolName2(event.payload));
25457
26420
  if (type === "tool.started") {
25458
26421
  return withGeneratedToolEventId(
25459
26422
  event,
@@ -25544,7 +26507,7 @@ function hasStableToolEventId(payload) {
25544
26507
  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
26508
  );
25546
26509
  }
25547
- function readToolName(payload) {
26510
+ function readToolName2(payload) {
25548
26511
  const tool = toRecord16(payload.tool);
25549
26512
  const call = toRecord16(payload.tool_call ?? payload.toolCall);
25550
26513
  const fn = toRecord16(call.function ?? payload.function);
@@ -25603,7 +26566,7 @@ function stableStringify2(value) {
25603
26566
  }
25604
26567
  }
25605
26568
  function hashStableValue(value) {
25606
- return createHash6("sha256").update(value).digest("hex").slice(0, 16);
26569
+ return createHash7("sha256").update(value).digest("hex").slice(0, 16);
25607
26570
  }
25608
26571
  function readString17(payload, key) {
25609
26572
  const value = payload[key];
@@ -26014,6 +26977,96 @@ function toRecord17(value) {
26014
26977
  return typeof value === "object" && value !== null ? value : {};
26015
26978
  }
26016
26979
 
26980
+ // src/conversations/goal-lifecycle-marker.ts
26981
+ var GOAL_LIFECYCLE_MARKER_FORMAT = "hermes-link-lifecycle-marker";
26982
+ var GOAL_COMPLETION_MARKER_KIND = "goal_completion";
26983
+ function createGoalCompletionMarker(input) {
26984
+ const text = goalCompletionMarkerText(input.goal, input.language);
26985
+ return {
26986
+ id: goalCompletionMarkerId(input.runId),
26987
+ schema_version: 1,
26988
+ conversation_id: input.conversationId,
26989
+ role: "system",
26990
+ status: "completed",
26991
+ run_id: input.runId,
26992
+ created_at: input.completedAt,
26993
+ updated_at: input.completedAt,
26994
+ sender: {
26995
+ id: "hermes_link",
26996
+ type: "system",
26997
+ display_name: "Hermes Link"
26998
+ },
26999
+ parts: [{ type: "text", text }],
27000
+ attachments: [],
27001
+ raw: {
27002
+ format: GOAL_LIFECYCLE_MARKER_FORMAT,
27003
+ payload: {
27004
+ kind: GOAL_COMPLETION_MARKER_KIND,
27005
+ status: "completed",
27006
+ completed_at: input.completedAt,
27007
+ run_id: input.runId,
27008
+ text,
27009
+ ...Number.isFinite(input.goal.turns_used) ? { turns_used: input.goal.turns_used } : {},
27010
+ ...Number.isFinite(input.goal.max_turns) ? { max_turns: input.goal.max_turns } : {},
27011
+ ...input.goal.usage ? { usage: input.goal.usage } : {}
27012
+ }
27013
+ }
27014
+ };
27015
+ }
27016
+ function goalCompletionMarkerText(goal, language) {
27017
+ const parts = [localizedText2(language, "\u76EE\u6807\u5DF2\u5B8C\u6210", "Goal completed")];
27018
+ const turns = goalTurnsLabel(goal, language);
27019
+ if (turns) {
27020
+ parts.push(turns);
27021
+ }
27022
+ const totalTokens = finiteNonNegativeNumber(goal.usage?.total_tokens);
27023
+ if (totalTokens !== void 0) {
27024
+ parts.push(`${formatCompactNumber(totalTokens)} tokens`);
27025
+ }
27026
+ return parts.join(" ");
27027
+ }
27028
+ function goalCompletionMarkerId(runId) {
27029
+ const safeRunId = runId.trim().replace(/[^a-zA-Z0-9_:-]/gu, "_");
27030
+ return `msg_goal_completed_${safeRunId || "run"}`;
27031
+ }
27032
+ function goalTurnsLabel(goal, language) {
27033
+ const turnsUsed = finiteNonNegativeNumber(goal.turns_used);
27034
+ const maxTurns = finiteNonNegativeNumber(goal.max_turns);
27035
+ if (turnsUsed !== void 0 && maxTurns !== void 0) {
27036
+ return localizedText2(
27037
+ language,
27038
+ `${turnsUsed}/${maxTurns} \u8F6E`,
27039
+ `${turnsUsed}/${maxTurns} turns`
27040
+ );
27041
+ }
27042
+ if (turnsUsed !== void 0) {
27043
+ return localizedText2(language, `${turnsUsed} \u8F6E`, `${turnsUsed} turns`);
27044
+ }
27045
+ return null;
27046
+ }
27047
+ function finiteNonNegativeNumber(value) {
27048
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
27049
+ return void 0;
27050
+ }
27051
+ return Math.floor(value);
27052
+ }
27053
+ function formatCompactNumber(value) {
27054
+ if (value >= 1e6) {
27055
+ return `${trimTrailingDecimal(value / 1e6)}m`;
27056
+ }
27057
+ if (value >= 1e3) {
27058
+ return `${trimTrailingDecimal(value / 1e3)}k`;
27059
+ }
27060
+ return value.toLocaleString("en-US");
27061
+ }
27062
+ function trimTrailingDecimal(value) {
27063
+ const rounded = Math.round(value * 10) / 10;
27064
+ return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
27065
+ }
27066
+ function localizedText2(language, zh, en) {
27067
+ return language === "en" ? en : zh;
27068
+ }
27069
+
26017
27070
  // src/conversations/run-lifecycle.ts
26018
27071
  var RUN_STATUS_RECOVERY_TIMEOUT_MS = 10 * 60 * 1e3;
26019
27072
  var RUN_STATUS_RECOVERY_INITIAL_DELAY_MS = 500;
@@ -26030,7 +27083,7 @@ var ConversationRunLifecycle = class {
26030
27083
  const snapshot = await this.deps.readSnapshot(conversationId);
26031
27084
  const run = snapshot.runs.find((item) => item.id === runId);
26032
27085
  if (!run || run.status !== "running") {
26033
- return;
27086
+ return { shouldDrainQueue: true };
26034
27087
  }
26035
27088
  const controller = new AbortController();
26036
27089
  this.deps.activeRunControllers.set(runId, { conversationId, controller });
@@ -26384,7 +27437,7 @@ var ConversationRunLifecycle = class {
26384
27437
  return null;
26385
27438
  });
26386
27439
  if (!response) {
26387
- return;
27440
+ return { shouldDrainQueue: true };
26388
27441
  }
26389
27442
  await this.consumeHermesEventStream({
26390
27443
  backend,
@@ -26406,7 +27459,15 @@ var ConversationRunLifecycle = class {
26406
27459
  reason: "cancelled by app"
26407
27460
  })
26408
27461
  );
26409
- return;
27462
+ return { shouldDrainQueue: true };
27463
+ }
27464
+ if (isTuiGatewaySessionBusyError(error)) {
27465
+ await this.markRunBlockedByTuiGatewayBusy({
27466
+ conversationId,
27467
+ runId,
27468
+ error
27469
+ });
27470
+ return { shouldDrainQueue: false };
26410
27471
  }
26411
27472
  throw error;
26412
27473
  } finally {
@@ -26415,6 +27476,148 @@ var ConversationRunLifecycle = class {
26415
27476
  }
26416
27477
  unregisterDeliveryContext(runId);
26417
27478
  }
27479
+ return { shouldDrainQueue: true };
27480
+ }
27481
+ async markRunBlockedByTuiGatewayBusy(input) {
27482
+ const details = input.error instanceof TuiGatewaySessionBusyError ? input.error.details : void 0;
27483
+ const snapshot = await this.deps.readSnapshot(input.conversationId);
27484
+ const run = snapshot.runs.find((item) => item.id === input.runId);
27485
+ if (!run) {
27486
+ return;
27487
+ }
27488
+ const liveSession = await readTuiGatewayLiveSession({
27489
+ profileName: run.profile,
27490
+ runtimeSessionId: details?.runtimeSessionId ?? run.hermes_rpc_session_id,
27491
+ storedSessionId: details?.storedSessionId ?? run.hermes_session_id,
27492
+ logger: this.deps.logger,
27493
+ paths: this.deps.paths
27494
+ }).catch((error) => {
27495
+ void this.deps.logger.debug("tui_gateway_busy_live_probe_failed", {
27496
+ conversation_id: input.conversationId,
27497
+ run_id: input.runId,
27498
+ error: error instanceof Error ? error.message : String(error)
27499
+ });
27500
+ return null;
27501
+ });
27502
+ await this.markRunBlockedByTuiGatewayBusyLocked({
27503
+ conversationId: input.conversationId,
27504
+ runId: input.runId,
27505
+ error: input.error,
27506
+ liveSession,
27507
+ runtimeSessionId: liveSession?.runtimeSessionId ?? details?.runtimeSessionId ?? run.hermes_rpc_session_id,
27508
+ storedSessionId: liveSession?.storedSessionId ?? details?.storedSessionId ?? run.hermes_session_id,
27509
+ method: details?.method
27510
+ });
27511
+ await this.deps.syncConversationMessages(input.conversationId).catch(
27512
+ (error) => {
27513
+ void this.deps.logger.warn("tui_gateway_busy_message_sync_failed", {
27514
+ conversation_id: input.conversationId,
27515
+ run_id: input.runId,
27516
+ error: error instanceof Error ? error.message : String(error)
27517
+ });
27518
+ }
27519
+ );
27520
+ }
27521
+ async markRunBlockedByTuiGatewayBusyLocked(input) {
27522
+ await this.deps.withConversationLock(input.conversationId, async () => {
27523
+ const snapshot = await this.deps.readSnapshot(input.conversationId);
27524
+ const run = snapshot.runs.find((item) => item.id === input.runId);
27525
+ if (!run || run.status !== "running") {
27526
+ return;
27527
+ }
27528
+ const now = (/* @__PURE__ */ new Date()).toISOString();
27529
+ const assistant = snapshot.messages.find(
27530
+ (item) => item.id === run.assistant_message_id
27531
+ );
27532
+ const errorMessage5 = input.error instanceof Error ? input.error.message : String(input.error);
27533
+ run.hermes_backend = "tui_gateway";
27534
+ run.hermes_rpc_session_id = input.runtimeSessionId;
27535
+ if (input.storedSessionId) {
27536
+ run.hermes_session_id = input.storedSessionId;
27537
+ }
27538
+ run.error_detail = "tui_gateway_session_busy";
27539
+ run.error_message = errorMessage5;
27540
+ if (assistant) {
27541
+ assistant.status = "streaming";
27542
+ assistant.updated_at = now;
27543
+ assistant.hermes = {
27544
+ ...assistant.hermes ?? {},
27545
+ upstream_busy: true,
27546
+ upstream_busy_at: now,
27547
+ upstream_busy_message: errorMessage5,
27548
+ ...input.method ? { upstream_busy_method: input.method } : {},
27549
+ ...input.runtimeSessionId ? { upstream_runtime_session_id: input.runtimeSessionId } : {},
27550
+ ...input.storedSessionId ? { upstream_session_id: input.storedSessionId } : {},
27551
+ ...input.liveSession ? {
27552
+ upstream_status: input.liveSession.status,
27553
+ upstream_running: input.liveSession.running,
27554
+ ...input.liveSession.preview ? { upstream_preview: input.liveSession.preview } : {}
27555
+ } : {}
27556
+ };
27557
+ }
27558
+ await this.deps.writeSnapshot(input.conversationId, snapshot);
27559
+ if (input.storedSessionId) {
27560
+ const manifest = await this.deps.readRunnableManifest(
27561
+ input.conversationId
27562
+ );
27563
+ const nextManifest = addHermesSessionIdForProfileToManifest(
27564
+ manifest,
27565
+ input.storedSessionId,
27566
+ run.profile
27567
+ );
27568
+ if (nextManifest !== manifest) {
27569
+ await this.deps.writeManifest(nextManifest);
27570
+ }
27571
+ }
27572
+ if (assistant) {
27573
+ await this.deps.appendEvent(input.conversationId, {
27574
+ type: "message.updated",
27575
+ message_id: assistant.id,
27576
+ run_id: run.id,
27577
+ payload: {
27578
+ message: assistant,
27579
+ reason: "tui_gateway_session_busy",
27580
+ upstream_busy: true,
27581
+ ...input.liveSession ? {
27582
+ upstream_session: sanitizeLiveSessionForEvent(
27583
+ input.liveSession
27584
+ )
27585
+ } : {}
27586
+ }
27587
+ });
27588
+ }
27589
+ const event = await this.deps.appendEvent(input.conversationId, {
27590
+ type: "run.upstream_busy",
27591
+ message_id: assistant?.id,
27592
+ run_id: run.id,
27593
+ payload: {
27594
+ run,
27595
+ reason: "tui_gateway_session_busy",
27596
+ error: errorMessage5,
27597
+ ...input.method ? { method: input.method } : {},
27598
+ ...input.liveSession ? {
27599
+ upstream_session: sanitizeLiveSessionForEvent(
27600
+ input.liveSession
27601
+ )
27602
+ } : {
27603
+ upstream_session: {
27604
+ ...input.runtimeSessionId ? { id: input.runtimeSessionId } : {},
27605
+ ...input.storedSessionId ? { session_key: input.storedSessionId } : {}
27606
+ }
27607
+ }
27608
+ }
27609
+ });
27610
+ await this.deps.persistConversationStats(input.conversationId, snapshot);
27611
+ void this.deps.logger.warn("conversation_run_upstream_busy", {
27612
+ conversation_id: input.conversationId,
27613
+ run_id: input.runId,
27614
+ event_seq: event.seq,
27615
+ runtime_session_id: input.runtimeSessionId ?? null,
27616
+ stored_session_id: input.storedSessionId ?? null,
27617
+ upstream_status: input.liveSession?.status ?? null,
27618
+ method: input.method ?? null
27619
+ });
27620
+ });
26418
27621
  }
26419
27622
  async ensureUsageProbeBeforeRun(input) {
26420
27623
  if (!shouldUseHermesUsageProbe(input.backend)) {
@@ -26640,6 +27843,9 @@ var ConversationRunLifecycle = class {
26640
27843
  await this.cancelRunAfterAbort(input.conversationId, input.runId);
26641
27844
  return;
26642
27845
  }
27846
+ if (isTuiGatewaySessionBusyError(error)) {
27847
+ throw error;
27848
+ }
26643
27849
  streamError = error;
26644
27850
  await this.deps.logger.warn("tui_gateway_event_stream_interrupted", {
26645
27851
  backend: input.backend,
@@ -27957,6 +29163,13 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
27957
29163
  completedAt,
27958
29164
  "expired"
27959
29165
  );
29166
+ const goalCompletionMarker = run.mode === "goal" ? await this.appendGoalCompletionMarkerAfterAssistant({
29167
+ conversationId,
29168
+ snapshot,
29169
+ run,
29170
+ goal: goalUpdate ?? void 0,
29171
+ completedAt
29172
+ }) : null;
27960
29173
  await this.deps.writeSnapshot(conversationId, snapshot);
27961
29174
  if (goalUpdate) {
27962
29175
  await this.deps.appendEvent(conversationId, {
@@ -27968,6 +29181,14 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
27968
29181
  }
27969
29182
  });
27970
29183
  }
29184
+ if (goalCompletionMarker) {
29185
+ await this.deps.appendEvent(conversationId, {
29186
+ type: "message.created",
29187
+ message_id: goalCompletionMarker.id,
29188
+ run_id: runId,
29189
+ payload: { message: goalCompletionMarker }
29190
+ });
29191
+ }
27971
29192
  await this.appendExpiredApprovalEvents(
27972
29193
  conversationId,
27973
29194
  runId,
@@ -28008,6 +29229,24 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
28008
29229
  occurredAt: completedAt
28009
29230
  });
28010
29231
  }
29232
+ async appendGoalCompletionMarkerAfterAssistant(input) {
29233
+ const goal = input.goal ?? (await this.deps.readRunnableManifest(input.conversationId)).command_state?.goal;
29234
+ if (goal?.status !== "done") {
29235
+ return null;
29236
+ }
29237
+ if (findGoalCompletionMarker(input.snapshot, input.run.id)) {
29238
+ return null;
29239
+ }
29240
+ const marker = createGoalCompletionMarker({
29241
+ conversationId: input.conversationId,
29242
+ runId: input.run.id,
29243
+ goal,
29244
+ completedAt: input.completedAt,
29245
+ language: input.run.language === "en" ? "en" : "zh-CN"
29246
+ });
29247
+ insertLifecycleMarkerAfterAssistant(input.snapshot, marker, input.run);
29248
+ return marker;
29249
+ }
28011
29250
  async persistGoalUsageFromRunLocked(conversationId, run) {
28012
29251
  const manifest = await this.deps.readRunnableManifest(conversationId);
28013
29252
  const previousGoal = manifest.command_state?.goal;
@@ -28391,7 +29630,7 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
28391
29630
  if (raw.length <= 200) {
28392
29631
  return raw;
28393
29632
  }
28394
- return `hermespilot:${createHash7("sha256").update(raw).digest("hex")}`;
29633
+ return `hermespilot:${createHash8("sha256").update(raw).digest("hex")}`;
28395
29634
  }
28396
29635
  async assistantMessageIdForRun(conversationId, runId) {
28397
29636
  const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
@@ -28536,6 +29775,23 @@ function insertLifecycleMarkerBeforeAssistant(snapshot, marker, run) {
28536
29775
  }
28537
29776
  snapshot.messages.push(marker);
28538
29777
  }
29778
+ function insertLifecycleMarkerAfterAssistant(snapshot, marker, run) {
29779
+ const existingIndex = snapshot.messages.findIndex(
29780
+ (message) => message.id === marker.id
29781
+ );
29782
+ if (existingIndex >= 0) {
29783
+ snapshot.messages[existingIndex] = marker;
29784
+ return;
29785
+ }
29786
+ const assistantIndex = snapshot.messages.findIndex(
29787
+ (message) => message.id === run.assistant_message_id
29788
+ );
29789
+ if (assistantIndex >= 0) {
29790
+ snapshot.messages.splice(assistantIndex + 1, 0, marker);
29791
+ return;
29792
+ }
29793
+ snapshot.messages.push(marker);
29794
+ }
28539
29795
  function timestampBeforeAssistantForRun(snapshot, run, fallback) {
28540
29796
  const user = snapshot.messages.find(
28541
29797
  (message) => message.id === run.trigger_message_id
@@ -28561,6 +29817,12 @@ function findContextCompressionMarker(snapshot, operationId) {
28561
29817
  return message.raw?.format === CONTEXT_COMPRESSION_MARKER_FORMAT && payload.kind === CONTEXT_COMPRESSION_MARKER_KIND && payload.operation_id === operationId;
28562
29818
  });
28563
29819
  }
29820
+ function findGoalCompletionMarker(snapshot, runId) {
29821
+ return snapshot.messages.find((message) => {
29822
+ const payload = toRecord18(message.raw?.payload);
29823
+ return message.raw?.format === GOAL_LIFECYCLE_MARKER_FORMAT && payload.kind === GOAL_COMPLETION_MARKER_KIND && payload.run_id === runId;
29824
+ });
29825
+ }
28564
29826
  function nextContextCompressionGeneration(snapshot) {
28565
29827
  let maxGeneration = 0;
28566
29828
  for (const message of snapshot.messages) {
@@ -29000,6 +30262,21 @@ function expirePendingMessageApprovals(message, resolvedAt) {
29000
30262
  }
29001
30263
  return expired;
29002
30264
  }
30265
+ function sanitizeLiveSessionForEvent(liveSession) {
30266
+ return {
30267
+ id: liveSession.runtimeSessionId,
30268
+ session_key: liveSession.storedSessionId,
30269
+ status: liveSession.status,
30270
+ running: liveSession.running,
30271
+ ...liveSession.current ? { current: true } : {},
30272
+ ...liveSession.title ? { title: liveSession.title } : {},
30273
+ ...liveSession.preview ? { preview: liveSession.preview } : {},
30274
+ ...liveSession.model ? { model: liveSession.model } : {},
30275
+ ...liveSession.lastActiveAt !== void 0 ? { last_active: liveSession.lastActiveAt } : {},
30276
+ ...liveSession.startedAt !== void 0 ? { started_at: liveSession.startedAt } : {},
30277
+ ...liveSession.inflight ? { inflight: liveSession.inflight } : {}
30278
+ };
30279
+ }
29003
30280
  function previewText2(message) {
29004
30281
  if (!message) {
29005
30282
  return null;
@@ -29008,15 +30285,15 @@ function previewText2(message) {
29008
30285
  return text ? text.slice(0, 512) : null;
29009
30286
  }
29010
30287
  function runNotificationSourceEventId(conversationId, runId, eventKind) {
29011
- const digest = createHash7("sha256").update(`${conversationId}:${runId}:${eventKind}`).digest("hex").slice(0, 24);
30288
+ const digest = createHash8("sha256").update(`${conversationId}:${runId}:${eventKind}`).digest("hex").slice(0, 24);
29012
30289
  return `${conversationId}:${eventKind}:${digest}`;
29013
30290
  }
29014
30291
  function approvalNotificationSourceEventId(conversationId, runId, approvalId) {
29015
- const digest = createHash7("sha256").update(`${conversationId}:${runId}:${approvalId}:approval_required`).digest("hex").slice(0, 24);
30292
+ const digest = createHash8("sha256").update(`${conversationId}:${runId}:${approvalId}:approval_required`).digest("hex").slice(0, 24);
29016
30293
  return `${conversationId}:approval_required:${digest}`;
29017
30294
  }
29018
30295
  function inputRequestNotificationSourceEventId(conversationId, runId, inputRequestId) {
29019
- const digest = createHash7("sha256").update(`${conversationId}:${runId}:${inputRequestId}:input_required`).digest("hex").slice(0, 24);
30296
+ const digest = createHash8("sha256").update(`${conversationId}:${runId}:${inputRequestId}:input_required`).digest("hex").slice(0, 24);
29020
30297
  return `${conversationId}:input_required:${digest}`;
29021
30298
  }
29022
30299
  async function closeSseIterator(iterator) {
@@ -29127,6 +30404,7 @@ var ConversationService = class {
29127
30404
  isConversationRunnable: (conversationId) => this.store.isConversationRunnable(conversationId),
29128
30405
  writeBlob: (conversationId, input) => this.maintenance.writeBlob(conversationId, input),
29129
30406
  syncCronDeliveries: () => this.syncCronDeliveries(),
30407
+ syncConversationMessages: (conversationId) => this.syncHermesConversationMessages(conversationId),
29130
30408
  scheduleTitleRefresh: (conversationId) => this.metadata.scheduleGeneratedTitleRefresh(conversationId)
29131
30409
  });
29132
30410
  this.orchestration = new ConversationOrchestrationCoordinator({
@@ -29705,6 +30983,16 @@ var ConversationService = class {
29705
30983
  }
29706
30984
  }
29707
30985
  }
30986
+ async syncHermesConversationMessages(conversationId) {
30987
+ return this.withConversationLock(
30988
+ conversationId,
30989
+ () => syncHermesConversationMessages(this.paths, this.logger, {
30990
+ conversationId,
30991
+ store: this.store,
30992
+ appendEvent: (targetConversationId, event) => this.appendEvent(targetConversationId, event)
30993
+ })
30994
+ );
30995
+ }
29708
30996
  async deliverStagedFiles(stagingDir) {
29709
30997
  const target = resolveDeliveryStagingTarget(this.paths, stagingDir);
29710
30998
  return this.withConversationLock(target.conversationId, async () => {
@@ -29774,6 +31062,16 @@ var ConversationService = class {
29774
31062
  };
29775
31063
  }
29776
31064
  async getMessages(conversationId, options = {}) {
31065
+ if (options.syncHermes === true && !options.beforeMessageId) {
31066
+ await this.syncHermesConversationMessages(conversationId).catch(
31067
+ (error) => {
31068
+ void this.logger.warn("hermes_conversation_message_sync_failed", {
31069
+ conversation_id: conversationId,
31070
+ error: error instanceof Error ? error.message : String(error)
31071
+ });
31072
+ }
31073
+ );
31074
+ }
29777
31075
  return this.queries.getMessages(conversationId, options);
29778
31076
  }
29779
31077
  async setConversationModel(conversationId, input) {
@@ -30659,7 +31957,7 @@ var ConversationService = class {
30659
31957
  }
30660
31958
  }
30661
31959
  hermesArchiveStateSyncMarkerPath() {
30662
- return path26.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
31960
+ return path27.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
30663
31961
  }
30664
31962
  prepareClearAllConversationPlan(targetStatus) {
30665
31963
  return this.maintenance.prepareClearAllConversationPlan(targetStatus);
@@ -30818,7 +32116,7 @@ function findApproval(snapshot, approvalId) {
30818
32116
  return null;
30819
32117
  }
30820
32118
  function cronNotificationSourceEventId(conversationId, jobId, outputPath, eventKind) {
30821
- const digest = createHash8("sha256").update(`${conversationId}:${jobId}:${outputPath}:${eventKind}`).digest("hex").slice(0, 24);
32119
+ const digest = createHash9("sha256").update(`${conversationId}:${jobId}:${outputPath}:${eventKind}`).digest("hex").slice(0, 24);
30822
32120
  return `${conversationId}:${eventKind}:${digest}`;
30823
32121
  }
30824
32122
  function conversationHermesSessionIds(manifest) {
@@ -30896,6 +32194,7 @@ function buildLiveActivityEvent(input) {
30896
32194
  return null;
30897
32195
  }
30898
32196
  const language = run?.language === "en" ? "en" : "zh";
32197
+ const todoProgress = liveActivityTodoProgressForEvent(input.event);
30899
32198
  const text = liveActivityTextForEvent({
30900
32199
  event: input.event,
30901
32200
  snapshot: input.snapshot,
@@ -30919,11 +32218,56 @@ function buildLiveActivityEvent(input) {
30919
32218
  statusLabel: text.statusLabel,
30920
32219
  progressText: text.progressText,
30921
32220
  detailText: text.detailText,
32221
+ todoDoneCount: todoProgress?.doneCount,
32222
+ todoTotalCount: todoProgress?.totalCount,
32223
+ todoProgressText: todoProgress?.progressText,
30922
32224
  requiresUserAction: phase === "needs_input" || phase === "needs_approval",
30923
32225
  terminal: isLiveActivityTerminalEvent(phase, target.kind),
30924
32226
  occurredAt: input.event.created_at
30925
32227
  };
30926
32228
  }
32229
+ function liveActivityTodoProgressForEvent(event) {
32230
+ if (event.type.toLowerCase() !== "tool.completed") {
32231
+ return null;
32232
+ }
32233
+ const payload = readRecord2(event.payload);
32234
+ const toolName = readToolName3(payload)?.toLowerCase();
32235
+ if (toolName !== "todo") {
32236
+ return null;
32237
+ }
32238
+ const todos = readTodoItems2(payload.todos) ?? readTodoItems2(readRecord2(payload.result).todos);
32239
+ if (todos) {
32240
+ const totalCount2 = todos.length;
32241
+ const doneCount2 = todos.filter((item) => {
32242
+ const status = readString20(item, "status")?.toLowerCase();
32243
+ return status === "completed" || status === "cancelled" || status === "canceled";
32244
+ }).length;
32245
+ return {
32246
+ doneCount: doneCount2,
32247
+ totalCount: totalCount2,
32248
+ progressText: totalCount2 > 0 ? `${doneCount2}/${totalCount2}` : void 0
32249
+ };
32250
+ }
32251
+ const summary = readRecord2(payload.summary).total === void 0 ? readRecord2(readRecord2(payload.result).summary) : readRecord2(payload.summary);
32252
+ const totalCount = readInteger4(summary, "total");
32253
+ if (totalCount === null) {
32254
+ return null;
32255
+ }
32256
+ const completedCount = readInteger4(summary, "completed") ?? 0;
32257
+ const cancelledCount = readInteger4(summary, "cancelled") ?? readInteger4(summary, "canceled") ?? 0;
32258
+ const doneCount = Math.min(totalCount, completedCount + cancelledCount);
32259
+ return {
32260
+ doneCount,
32261
+ totalCount,
32262
+ progressText: totalCount > 0 ? `${doneCount}/${totalCount}` : void 0
32263
+ };
32264
+ }
32265
+ function readTodoItems2(value) {
32266
+ if (!Array.isArray(value)) {
32267
+ return null;
32268
+ }
32269
+ return value.map((item) => readRecord2(item)).filter((item) => Object.keys(item).length > 0);
32270
+ }
30927
32271
  function liveActivityConversationTitle(manifest, event, phase, language) {
30928
32272
  if (phase === "goal_running" || phase === "goal_paused") {
30929
32273
  const goal = manifest.command_state?.goal;
@@ -31009,7 +32353,7 @@ function liveActivityPhaseForEvent(event, run, contextOperation) {
31009
32353
  return run?.mode === "goal" ? "goal_running" : "accepted";
31010
32354
  }
31011
32355
  if (type === "conversation.goal.updated") {
31012
- const goal = readRecord(event.payload).goal;
32356
+ const goal = readRecord2(event.payload).goal;
31013
32357
  const status = readString20(goal, "status");
31014
32358
  return status === "paused" ? "goal_paused" : "goal_running";
31015
32359
  }
@@ -31055,7 +32399,7 @@ function liveActivityPhaseForEvent(event, run, contextOperation) {
31055
32399
  return null;
31056
32400
  }
31057
32401
  function liveActivityTextForEvent(input) {
31058
- const toolName = readToolName2(input.event.payload);
32402
+ const toolName = readToolName3(input.event.payload);
31059
32403
  const assistantText = input.event.message_id ? input.snapshot.messages.find((message) => message.id === input.event.message_id) : null;
31060
32404
  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
32405
  if (input.language === "en") {
@@ -31159,7 +32503,7 @@ function isLiveActivityTerminalEvent(phase, targetKind) {
31159
32503
  return targetKind === "context_compression" && (phase === "context_compressed" || phase === "context_compression_failed" || phase === "context_compression_timed_out");
31160
32504
  }
31161
32505
  function readContextCompressionOperation(payload) {
31162
- const operation = readRecord(payload).operation;
32506
+ const operation = readRecord2(payload).operation;
31163
32507
  if (!operation || typeof operation !== "object") {
31164
32508
  return null;
31165
32509
  }
@@ -31176,23 +32520,31 @@ function readContextCompressionOperation(payload) {
31176
32520
  source: readString20(record, "source") === "manual" ? "manual" : "auto"
31177
32521
  };
31178
32522
  }
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");
32523
+ function readToolName3(payload) {
32524
+ const record = readRecord2(payload);
32525
+ return readString20(record, "tool_name") ?? readString20(record, "tool") ?? readString20(record, "name") ?? readString20(readRecord2(record.tool), "name");
31182
32526
  }
31183
- function readRecord(value) {
32527
+ function readRecord2(value) {
31184
32528
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
31185
32529
  }
31186
32530
  function readString20(value, key) {
31187
- const raw = readRecord(value)[key];
32531
+ const raw = readRecord2(value)[key];
31188
32532
  return typeof raw === "string" && raw.trim() ? raw.trim() : null;
31189
32533
  }
32534
+ function readInteger4(value, key) {
32535
+ const raw = readRecord2(value)[key];
32536
+ const parsed = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() ? Number(raw) : Number.NaN;
32537
+ if (!Number.isFinite(parsed)) {
32538
+ return null;
32539
+ }
32540
+ return Math.max(0, Math.trunc(parsed));
32541
+ }
31190
32542
  function approvalRestartText(language, zh, en) {
31191
32543
  return language === "en" ? en : zh;
31192
32544
  }
31193
32545
 
31194
32546
  // src/security/devices.ts
31195
- import { randomBytes as randomBytes3, randomUUID as randomUUID13, timingSafeEqual, createHash as createHash9 } from "crypto";
32547
+ import { randomBytes as randomBytes3, randomUUID as randomUUID13, timingSafeEqual, createHash as createHash10 } from "crypto";
31196
32548
  var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
31197
32549
  var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
31198
32550
  var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
@@ -31488,7 +32840,7 @@ function randomToken(prefix) {
31488
32840
  return `${prefix}${randomBytes3(24).toString("base64url")}`;
31489
32841
  }
31490
32842
  function sha256(value) {
31491
- return createHash9("sha256").update(value).digest("hex");
32843
+ return createHash10("sha256").update(value).digest("hex");
31492
32844
  }
31493
32845
  function safeEqual(left, right) {
31494
32846
  const leftBytes = Buffer.from(left);
@@ -31768,7 +33120,7 @@ function readPositiveInteger2(value) {
31768
33120
  }
31769
33121
  return void 0;
31770
33122
  }
31771
- function readBoolean3(value) {
33123
+ function readBoolean4(value) {
31772
33124
  if (typeof value === "boolean") {
31773
33125
  return value;
31774
33126
  }
@@ -31806,7 +33158,7 @@ function readQueryString(value) {
31806
33158
  const trimmed = raw.trim();
31807
33159
  return trimmed ? trimmed : void 0;
31808
33160
  }
31809
- function readInteger4(value) {
33161
+ function readInteger5(value) {
31810
33162
  const raw = Array.isArray(value) ? value[0] : value;
31811
33163
  if (typeof raw !== "string") {
31812
33164
  return void 0;
@@ -31889,7 +33241,7 @@ function readMessageAttachments(value) {
31889
33241
  }
31890
33242
  const kind = readAttachmentString(record.kind);
31891
33243
  const type = readAttachmentString(record.type);
31892
- const isVoiceNote = readBoolean3(record.is_voice_note) ?? readBoolean3(record.isVoiceNote);
33244
+ const isVoiceNote = readBoolean4(record.is_voice_note) ?? readBoolean4(record.isVoiceNote);
31893
33245
  const durationMs = readPositiveInteger2(record.duration_ms) ?? readPositiveInteger2(record.durationMs);
31894
33246
  const waveform = readAttachmentWaveform2(
31895
33247
  record.waveform ?? record.waveform_samples ?? record.waveformSamples
@@ -32183,7 +33535,8 @@ function registerConversationRoutes(router, options) {
32183
33535
  ctx.set("cache-control", "no-store");
32184
33536
  const result = await conversations.getMessages(ctx.params.conversationId, {
32185
33537
  limit: readLimit(ctx.query.limit),
32186
- beforeMessageId: readQueryString(ctx.query.before_message_id) ?? readQueryString(ctx.query.before)
33538
+ beforeMessageId: readQueryString(ctx.query.before_message_id) ?? readQueryString(ctx.query.before),
33539
+ syncHermes: readConversationMessagesSyncHermes(ctx.query)
32187
33540
  });
32188
33541
  ctx.body = {
32189
33542
  ok: true,
@@ -32770,6 +34123,13 @@ function registerConversationRoutes(router, options) {
32770
34123
  }
32771
34124
  );
32772
34125
  }
34126
+ function readConversationMessagesSyncHermes(query) {
34127
+ const explicit = readBoolean4(query.sync_hermes) ?? readBoolean4(query.syncHermes);
34128
+ if (explicit !== void 0) {
34129
+ return explicit;
34130
+ }
34131
+ return readQueryString(query.sync)?.toLowerCase() === "hermes";
34132
+ }
32773
34133
  async function prepareConversationListRead(conversations, logger, auth, options = {}) {
32774
34134
  if (options.syncHermesSessions) {
32775
34135
  await conversations.syncHermesSessions().catch((error) => {
@@ -32799,7 +34159,7 @@ function readConversationListCursor(query) {
32799
34159
  }
32800
34160
  function readConversationListForce(query) {
32801
34161
  const raw = Array.isArray(query.force) ? query.force[0] : query.force;
32802
- return readBoolean3(raw) === true;
34162
+ return readBoolean4(raw) === true;
32803
34163
  }
32804
34164
  function readConversationWorkspaceFilter(query) {
32805
34165
  const workspace = readQueryString(query.workspace) ?? readQueryString(query.workspace_id);
@@ -32905,7 +34265,7 @@ function toConversationSummary(value) {
32905
34265
  return candidate;
32906
34266
  }
32907
34267
  function resolveConversationEventCursor(input) {
32908
- const queryAfter = readInteger4(input.queryAfter) ?? 0;
34268
+ const queryAfter = readInteger5(input.queryAfter) ?? 0;
32909
34269
  const headerAfter = readNonNegativeIntegerHeader(input.lastEventIdHeader) ?? 0;
32910
34270
  return Math.max(queryAfter, headerAfter);
32911
34271
  }
@@ -33051,11 +34411,11 @@ function isSseRequestContext(ctx) {
33051
34411
  }
33052
34412
  return isSseRequestPath(ctx.path) || isActiveSseSocket(ctx.req.socket);
33053
34413
  }
33054
- function isSseRequestPath(path37) {
33055
- if (!path37) {
34414
+ function isSseRequestPath(path38) {
34415
+ if (!path38) {
33056
34416
  return false;
33057
34417
  }
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);
34418
+ 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
34419
  }
33060
34420
  function isExpectedClientDisconnectError2(error, options = {}) {
33061
34421
  if (!(error instanceof Error)) {
@@ -33419,7 +34779,7 @@ function readCronJobUpdateInput(body) {
33419
34779
  if (repeat !== void 0) {
33420
34780
  input.repeat = repeat;
33421
34781
  }
33422
- const enabled = readBoolean3(body.enabled);
34782
+ const enabled = readBoolean4(body.enabled);
33423
34783
  if (enabled !== void 0) {
33424
34784
  input.enabled = enabled;
33425
34785
  }
@@ -33489,12 +34849,12 @@ function assertCronJobId(jobId) {
33489
34849
  }
33490
34850
 
33491
34851
  // src/http/routes/model-configs.ts
33492
- import { createHash as createHash10 } from "crypto";
34852
+ import { createHash as createHash11 } from "crypto";
33493
34853
 
33494
34854
  // src/model-catalog/catalog.ts
33495
34855
  import { randomInt } from "crypto";
33496
34856
  import { mkdir as mkdir12 } from "fs/promises";
33497
- import path27 from "path";
34857
+ import path28 from "path";
33498
34858
  import { fileURLToPath } from "url";
33499
34859
  var MODEL_CATALOG_CACHE_VERSION = 1;
33500
34860
  var MODEL_CATALOG_FETCH_TIMEOUT_MS = 1e4;
@@ -33698,7 +35058,7 @@ async function readCachedCatalogFile(paths) {
33698
35058
  return cached;
33699
35059
  }
33700
35060
  async function writeCachedCatalog(paths, value) {
33701
- await mkdir12(path27.dirname(modelCatalogCachePath(paths)), {
35061
+ await mkdir12(path28.dirname(modelCatalogCachePath(paths)), {
33702
35062
  recursive: true,
33703
35063
  mode: 448
33704
35064
  });
@@ -33714,7 +35074,7 @@ async function readFallbackCatalog() {
33714
35074
  throw new Error("model capability fallback catalog was not found");
33715
35075
  }
33716
35076
  function modelCatalogCachePath(paths) {
33717
- return path27.join(paths.homeDir, "model-capabilities", "catalog-cache.json");
35077
+ return path28.join(paths.homeDir, "model-capabilities", "catalog-cache.json");
33718
35078
  }
33719
35079
  function normalizeModelCapabilityCatalog(value) {
33720
35080
  if (!value || typeof value !== "object") {
@@ -34718,7 +36078,7 @@ function modelProviderIdFromParts(input) {
34718
36078
  input.keyEnv ?? "",
34719
36079
  input.authType ?? ""
34720
36080
  ].join("");
34721
- return `mp_${createHash10("sha256").update(identity).digest("hex").slice(0, 18)}`;
36081
+ return `mp_${createHash11("sha256").update(identity).digest("hex").slice(0, 18)}`;
34722
36082
  }
34723
36083
  function mergeCredentialState(left, right) {
34724
36084
  if (left === "configured" || right === "configured") {
@@ -35017,7 +36377,7 @@ function readModelConfigInput(body) {
35017
36377
  body.context_length ?? body.contextLength
35018
36378
  ),
35019
36379
  keyEnv: readString21(body, "key_env") ?? readString21(body, "keyEnv") ?? void 0,
35020
- setDefault: readBoolean3(body.set_default ?? body.setDefault),
36380
+ setDefault: readBoolean4(body.set_default ?? body.setDefault),
35021
36381
  reasoningEffort: readString21(body, "reasoning_effort") ?? readString21(body, "reasoningEffort") ?? void 0,
35022
36382
  reasoningSupportPolicy: readString21(body, "reasoning_support_policy") ?? readString21(body, "reasoningSupportPolicy") ?? readString21(body, "reasoning_support") ?? readString21(body, "reasoningSupport") ?? void 0,
35023
36383
  supportsVision: readNullableBoolean2(
@@ -35086,14 +36446,14 @@ function readModelConfigImportInput(body) {
35086
36446
  provider: readString21(body, "provider") ?? readString21(body, "provider_key") ?? readString21(body, "providerKey") ?? void 0,
35087
36447
  baseUrl: readString21(body, "base_url") ?? readString21(body, "baseUrl") ?? void 0,
35088
36448
  apiMode: readString21(body, "api_mode") ?? readString21(body, "apiMode") ?? void 0,
35089
- setDefault: readBoolean3(body.set_default ?? body.setDefault)
36449
+ setDefault: readBoolean4(body.set_default ?? body.setDefault)
35090
36450
  };
35091
36451
  }
35092
36452
  function shouldReloadGatewayAfterModelConfigChange(body, options = {}) {
35093
- if (readBoolean3(body.skip_gateway_reload ?? body.skipGatewayReload) === true) {
36453
+ if (readBoolean4(body.skip_gateway_reload ?? body.skipGatewayReload) === true) {
35094
36454
  return false;
35095
36455
  }
35096
- if (readBoolean3(body.reload_gateway ?? body.reloadGateway) === true) {
36456
+ if (readBoolean4(body.reload_gateway ?? body.reloadGateway) === true) {
35097
36457
  return true;
35098
36458
  }
35099
36459
  return options.defaultReload === true;
@@ -35193,7 +36553,7 @@ function errorMessage3(error) {
35193
36553
 
35194
36554
  // src/hermes/profile-identity.ts
35195
36555
  import { readFile as readFile16, stat as stat18 } from "fs/promises";
35196
- import path28 from "path";
36556
+ import path29 from "path";
35197
36557
  var MAX_SOUL_MD_LENGTH = 2e4;
35198
36558
  async function readHermesProfileIdentity(profileName, paths) {
35199
36559
  await assertProfileExists3(profileName, paths);
@@ -35250,7 +36610,7 @@ async function assertProfileExists3(profileName, paths) {
35250
36610
  }
35251
36611
  }
35252
36612
  function resolveSoulPath(profileName) {
35253
- return path28.join(resolveHermesProfileDir(profileName), "SOUL.md");
36613
+ return path29.join(resolveHermesProfileDir(profileName), "SOUL.md");
35254
36614
  }
35255
36615
  function isNodeError19(error, code) {
35256
36616
  return error instanceof Error && "code" in error && error.code === code;
@@ -35266,13 +36626,13 @@ import {
35266
36626
  rm as rm7,
35267
36627
  stat as stat20
35268
36628
  } from "fs/promises";
35269
- import path30 from "path";
36629
+ import path31 from "path";
35270
36630
  import YAML5 from "yaml";
35271
36631
 
35272
36632
  // src/hermes/link-skill.ts
35273
36633
  import { readFile as readFile17, stat as stat19 } from "fs/promises";
35274
36634
  import os7 from "os";
35275
- import path29 from "path";
36635
+ import path30 from "path";
35276
36636
  import YAML4 from "yaml";
35277
36637
  var HERMES_LINK_SKILL_ROOT_DIR = "hermes-skills";
35278
36638
  var HERMES_LINK_SKILL_DIR = "hermes-link";
@@ -35329,13 +36689,20 @@ hermeslink logs -f
35329
36689
  hermeslink logs --all --level debug -f
35330
36690
  \`\`\`
35331
36691
 
35332
- If the daemon appears stuck, suggest:
36692
+ If the daemon appears stuck, tell the user to run this in a separate computer
36693
+ terminal, outside of Hermes Agent:
35333
36694
 
35334
36695
  \`\`\`bash
35335
36696
  hermeslink restart
35336
36697
  \`\`\`
35337
36698
 
35338
- Explain that restarting Hermes Link may briefly disconnect the mobile app.
36699
+ Never execute \`hermeslink restart\` yourself through Hermes Agent, terminal
36700
+ tools, shell tools, code execution, or any other in-session tool call.
36701
+ Restarting Hermes Link from inside the current HermesPilot App run can kill the
36702
+ Link/tui_gateway process that is carrying the response, interrupt the event
36703
+ stream, and leave the current run unrecoverable. If a restart is needed, only
36704
+ instruct the user to run it manually in their own terminal, then wait for the
36705
+ mobile app to reconnect.
35339
36706
 
35340
36707
  ## Troubleshooting Flow
35341
36708
 
@@ -35352,12 +36719,17 @@ Never reveal API keys, access tokens, refresh tokens, private keys, or full .env
35352
36719
 
35353
36720
  Do not recommend exposing port 52379 directly to the public internet without TLS, VPN, Tailscale, WireGuard, or another access-control layer.
35354
36721
 
36722
+ Do not execute Hermes Link service-control commands yourself from inside a
36723
+ Hermes Agent session. This includes \`hermeslink restart\`, \`hermeslink stop\`,
36724
+ \`hermeslink uninstall\`, and Link package update commands. Provide the command
36725
+ for the user to run in an external terminal instead.
36726
+
35355
36727
  Do not modify Hermes profiles, delete user data, edit config files, or kill processes unless the user explicitly asks.
35356
36728
  `;
35357
36729
  async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
35358
36730
  const paths = options.paths ?? resolveRuntimePaths();
35359
36731
  const externalDir = resolveHermesLinkSkillExternalDir(paths);
35360
- const skillPath = path29.join(
36732
+ const skillPath = path30.join(
35361
36733
  externalDir,
35362
36734
  HERMES_LINK_SKILL_DIR,
35363
36735
  HERMES_LINK_SKILL_FILE
@@ -35439,7 +36811,7 @@ function withDefaultProfilePlaceholder2(profiles) {
35439
36811
  ];
35440
36812
  }
35441
36813
  function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
35442
- return path29.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
36814
+ return path30.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
35443
36815
  }
35444
36816
  async function writeHermesLinkSkill(skillPath) {
35445
36817
  const existing = await readFile17(skillPath, "utf8").catch((error) => {
@@ -35534,11 +36906,11 @@ function appendExternalDir(current, externalDir, hermesHome) {
35534
36906
  const seen = new Set(
35535
36907
  entries.map((entry) => resolveExternalDirEntry(entry, hermesHome))
35536
36908
  );
35537
- const normalizedExternalDir = path29.resolve(externalDir);
36909
+ const normalizedExternalDir = path30.resolve(externalDir);
35538
36910
  return seen.has(normalizedExternalDir) ? entries : [...entries, normalizedExternalDir];
35539
36911
  }
35540
36912
  function externalDirsInclude(current, externalDir, hermesHome) {
35541
- const normalizedExternalDir = path29.resolve(externalDir);
36913
+ const normalizedExternalDir = path30.resolve(externalDir);
35542
36914
  return readExternalDirEntries(current).some(
35543
36915
  (entry) => resolveExternalDirEntry(entry, hermesHome) === normalizedExternalDir
35544
36916
  );
@@ -35549,14 +36921,14 @@ function readExternalDirEntries(value) {
35549
36921
  }
35550
36922
  function resolveExternalDirEntry(entry, hermesHome) {
35551
36923
  const expanded = expandHome(expandEnvVars(entry));
35552
- return path29.resolve(path29.isAbsolute(expanded) ? expanded : path29.join(hermesHome, expanded));
36924
+ return path30.resolve(path30.isAbsolute(expanded) ? expanded : path30.join(hermesHome, expanded));
35553
36925
  }
35554
36926
  function expandHome(value) {
35555
36927
  if (value === "~") {
35556
36928
  return os7.homedir();
35557
36929
  }
35558
- if (value.startsWith(`~${path29.sep}`) || value.startsWith("~/")) {
35559
- return path29.join(os7.homedir(), value.slice(2));
36930
+ if (value.startsWith(`~${path30.sep}`) || value.startsWith("~/")) {
36931
+ return path30.join(os7.homedir(), value.slice(2));
35560
36932
  }
35561
36933
  return value;
35562
36934
  }
@@ -35795,7 +37167,7 @@ async function readHermesProfileCreationStatus(paths) {
35795
37167
  let state = await readJsonFile(
35796
37168
  profileCreationStatePath(paths)
35797
37169
  );
35798
- if (state?.state === "running" && !runningProfileCreation && !isRecentRunningState(state) && !isProcessAlive(state.pid)) {
37170
+ if (state?.state === "running" && !runningProfileCreation && !isRecentRunningState(state) && !isProcessAlive2(state.pid)) {
35799
37171
  state = {
35800
37172
  ...state,
35801
37173
  state: "failed",
@@ -36108,7 +37480,7 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
36108
37480
  return keys;
36109
37481
  }
36110
37482
  async function writeEnvValues(profileName, values) {
36111
- const envPath = path30.join(resolveHermesProfileDir(profileName), ".env");
37483
+ const envPath = path31.join(resolveHermesProfileDir(profileName), ".env");
36112
37484
  const existingRaw = await readFile18(envPath, "utf8").catch((error) => {
36113
37485
  if (isNodeError21(error, "ENOENT")) {
36114
37486
  return "";
@@ -36145,8 +37517,8 @@ async function writeEnvValues(profileName, values) {
36145
37517
  await atomicWriteFilePreservingMetadata(envPath, nextRaw);
36146
37518
  }
36147
37519
  async function copySkills(sourceProfile, targetProfile) {
36148
- const sourceSkills = path30.join(resolveHermesProfileDir(sourceProfile), "skills");
36149
- const targetSkills = path30.join(resolveHermesProfileDir(targetProfile), "skills");
37520
+ const sourceSkills = path31.join(resolveHermesProfileDir(sourceProfile), "skills");
37521
+ const targetSkills = path31.join(resolveHermesProfileDir(targetProfile), "skills");
36150
37522
  if (!await pathExists2(sourceSkills)) {
36151
37523
  return;
36152
37524
  }
@@ -36241,10 +37613,10 @@ async function readProfileCreationLogLines(paths) {
36241
37613
  );
36242
37614
  }
36243
37615
  function profileCreationStatePath(paths) {
36244
- return path30.join(paths.runDir, "profile-create-state.json");
37616
+ return path31.join(paths.runDir, "profile-create-state.json");
36245
37617
  }
36246
37618
  function profileCreationLogPath(paths) {
36247
- return path30.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
37619
+ return path31.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
36248
37620
  }
36249
37621
  async function clearProfileCreationLogFiles(paths) {
36250
37622
  const primary = profileCreationLogPath(paths);
@@ -36270,7 +37642,7 @@ function isRecentRunningState(state) {
36270
37642
  }
36271
37643
  return Date.now() - Date.parse(state.started_at) < 1e4;
36272
37644
  }
36273
- function isProcessAlive(pid) {
37645
+ function isProcessAlive2(pid) {
36274
37646
  if (!pid || pid <= 0) {
36275
37647
  return false;
36276
37648
  }
@@ -36409,14 +37781,14 @@ function readProfilePermissionsInput(body) {
36409
37781
  containerDisk: readPositiveInteger2(
36410
37782
  terminal.container_disk ?? terminal.containerDisk
36411
37783
  ),
36412
- containerPersistent: readBoolean3(
37784
+ containerPersistent: readBoolean4(
36413
37785
  terminal.container_persistent ?? terminal.containerPersistent
36414
37786
  )
36415
37787
  };
36416
37788
  }
36417
37789
  const sudo = readOptionalObject(body, "sudo");
36418
37790
  if (sudo) {
36419
- const clear = readBoolean3(sudo.clear ?? sudo.remove ?? sudo.delete);
37791
+ const clear = readBoolean4(sudo.clear ?? sudo.remove ?? sudo.delete);
36420
37792
  const sudoInput = {
36421
37793
  password: readRawString2(sudo, "password") ?? readRawString2(sudo, "sudo_password") ?? readRawString2(sudo, "sudoPassword") ?? void 0,
36422
37794
  clear: clear === true ? true : void 0
@@ -36432,7 +37804,7 @@ function readProfilePermissionsInput(body) {
36432
37804
  toolsets.enabled_toolsets ?? toolsets.enabledToolsets ?? toolsets.enabled,
36433
37805
  "toolsets.enabled"
36434
37806
  ) ?? void 0,
36435
- mcpEnabled: readBoolean3(toolsets.mcp_enabled ?? toolsets.mcpEnabled)
37807
+ mcpEnabled: readBoolean4(toolsets.mcp_enabled ?? toolsets.mcpEnabled)
36436
37808
  };
36437
37809
  }
36438
37810
  if (Object.keys(input).length === 0) {
@@ -36533,7 +37905,7 @@ import {
36533
37905
  readFile as readFile19,
36534
37906
  stat as stat21
36535
37907
  } from "fs/promises";
36536
- import path31 from "path";
37908
+ import path32 from "path";
36537
37909
  import YAML6 from "yaml";
36538
37910
  var ENTRY_DELIMITER = "\n\xA7\n";
36539
37911
  var DEFAULT_MEMORY_LIMIT = 2200;
@@ -36864,7 +38236,7 @@ async function saveProviderSettings(profileName, provider, patch) {
36864
38236
  });
36865
38237
  await patchJsonProviderConfig(
36866
38238
  profileName,
36867
- path31.join("hindsight", "config.json"),
38239
+ path32.join("hindsight", "config.json"),
36868
38240
  {
36869
38241
  mode: patch.mode,
36870
38242
  api_url: patch.apiUrl,
@@ -37067,7 +38439,7 @@ async function patchHermesMemoryLimits(profileName, patch) {
37067
38439
  await atomicWriteFilePreservingMetadata(configPath, document.toString());
37068
38440
  }
37069
38441
  function resolveMemoryDir(profileName) {
37070
- return path31.join(resolveHermesProfileDir(profileName), "memories");
38442
+ return path32.join(resolveHermesProfileDir(profileName), "memories");
37071
38443
  }
37072
38444
  async function readMemoryStore(profileName, target, limits) {
37073
38445
  const filePath = memoryFilePath(profileName, target);
@@ -37128,7 +38500,7 @@ async function writeMemoryEntries(profileName, target, entries) {
37128
38500
  );
37129
38501
  }
37130
38502
  function memoryFilePath(profileName, target) {
37131
- return path31.join(
38503
+ return path32.join(
37132
38504
  resolveMemoryDir(profileName),
37133
38505
  target === "user" ? "USER.md" : "MEMORY.md"
37134
38506
  );
@@ -37188,7 +38560,7 @@ async function readCustomProviderSetupSummary(profileName) {
37188
38560
  configurable: true,
37189
38561
  configured: true,
37190
38562
  configurationIssue: null,
37191
- providerConfigPath: path31.join(
38563
+ providerConfigPath: path32.join(
37192
38564
  resolveHermesProfileDir(profileName),
37193
38565
  "<provider>.json"
37194
38566
  ),
@@ -37582,7 +38954,7 @@ async function readProviderSettings(profileName, provider) {
37582
38954
  stringSetting(
37583
38955
  "dbPath",
37584
38956
  "SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
37585
- config.db_path ?? path31.join(resolveHermesProfileDir(profileName), "memory_store.db")
38957
+ config.db_path ?? path32.join(resolveHermesProfileDir(profileName), "memory_store.db")
37586
38958
  ),
37587
38959
  booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
37588
38960
  numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
@@ -37618,7 +38990,7 @@ async function readProviderSettings(profileName, provider) {
37618
38990
  stringSetting(
37619
38991
  "workingDirectory",
37620
38992
  "\u5DE5\u4F5C\u76EE\u5F55",
37621
- path31.join(resolveHermesProfileDir(profileName), "byterover"),
38993
+ path32.join(resolveHermesProfileDir(profileName), "byterover"),
37622
38994
  false
37623
38995
  )
37624
38996
  ];
@@ -37627,16 +38999,16 @@ async function readProviderSettings(profileName, provider) {
37627
38999
  }
37628
39000
  function memoryProviderConfigPath(profileName, provider) {
37629
39001
  if (provider === "honcho") {
37630
- return path31.join(resolveHermesProfileDir(profileName), "honcho.json");
39002
+ return path32.join(resolveHermesProfileDir(profileName), "honcho.json");
37631
39003
  }
37632
39004
  if (provider === "mem0") {
37633
- return path31.join(resolveHermesProfileDir(profileName), "mem0.json");
39005
+ return path32.join(resolveHermesProfileDir(profileName), "mem0.json");
37634
39006
  }
37635
39007
  if (provider === "supermemory") {
37636
- return path31.join(resolveHermesProfileDir(profileName), "supermemory.json");
39008
+ return path32.join(resolveHermesProfileDir(profileName), "supermemory.json");
37637
39009
  }
37638
39010
  if (provider === "hindsight") {
37639
- return path31.join(
39011
+ return path32.join(
37640
39012
  resolveHermesProfileDir(profileName),
37641
39013
  "hindsight",
37642
39014
  "config.json"
@@ -37645,13 +39017,13 @@ function memoryProviderConfigPath(profileName, provider) {
37645
39017
  return null;
37646
39018
  }
37647
39019
  function customProviderConfigPath(profileName, provider) {
37648
- return path31.join(
39020
+ return path32.join(
37649
39021
  resolveHermesProfileDir(profileName),
37650
39022
  `${normalizeCustomProviderId(provider)}.json`
37651
39023
  );
37652
39024
  }
37653
39025
  function customProviderRegistryPath(profileName) {
37654
- return path31.join(
39026
+ return path32.join(
37655
39027
  resolveHermesProfileDir(profileName),
37656
39028
  CUSTOM_PROVIDER_REGISTRY_FILE
37657
39029
  );
@@ -37703,7 +39075,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
37703
39075
  );
37704
39076
  }
37705
39077
  async function discoverUserMemoryProviderDescriptors(profileName) {
37706
- const pluginsDir = path31.join(resolveHermesProfileDir(profileName), "plugins");
39078
+ const pluginsDir = path32.join(resolveHermesProfileDir(profileName), "plugins");
37707
39079
  const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
37708
39080
  (error) => {
37709
39081
  if (isNodeError22(error, "ENOENT")) {
@@ -37723,7 +39095,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
37723
39095
  } catch {
37724
39096
  continue;
37725
39097
  }
37726
- const providerDir = path31.join(pluginsDir, entry.name);
39098
+ const providerDir = path32.join(pluginsDir, entry.name);
37727
39099
  if (!await isMemoryProviderPluginDir(providerDir)) {
37728
39100
  continue;
37729
39101
  }
@@ -37737,7 +39109,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
37737
39109
  return descriptors;
37738
39110
  }
37739
39111
  async function isUserMemoryProviderInstalled(profileName, provider) {
37740
- const providerDir = path31.join(
39112
+ const providerDir = path32.join(
37741
39113
  resolveHermesProfileDir(profileName),
37742
39114
  "plugins",
37743
39115
  normalizeCustomProviderId(provider)
@@ -37745,7 +39117,7 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
37745
39117
  return isMemoryProviderPluginDir(providerDir);
37746
39118
  }
37747
39119
  async function isMemoryProviderPluginDir(providerDir) {
37748
- const source = await readFile19(path31.join(providerDir, "__init__.py"), "utf8").catch(
39120
+ const source = await readFile19(path32.join(providerDir, "__init__.py"), "utf8").catch(
37749
39121
  (error) => {
37750
39122
  if (isNodeError22(error, "ENOENT")) {
37751
39123
  return "";
@@ -37757,7 +39129,7 @@ async function isMemoryProviderPluginDir(providerDir) {
37757
39129
  return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
37758
39130
  }
37759
39131
  async function readPluginMetadata(providerDir) {
37760
- const raw = await readFile19(path31.join(providerDir, "plugin.yaml"), "utf8").catch(
39132
+ const raw = await readFile19(path32.join(providerDir, "plugin.yaml"), "utf8").catch(
37761
39133
  (error) => {
37762
39134
  if (isNodeError22(error, "ENOENT")) {
37763
39135
  return "";
@@ -37769,10 +39141,10 @@ async function readPluginMetadata(providerDir) {
37769
39141
  }
37770
39142
  async function resolveByteRoverCli() {
37771
39143
  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"),
39144
+ ...(process.env.PATH ?? "").split(path32.delimiter).filter(Boolean).map((dir) => path32.join(dir, "brv")),
39145
+ path32.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
37774
39146
  "/usr/local/bin/brv",
37775
- path31.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
39147
+ path32.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
37776
39148
  ].filter(Boolean);
37777
39149
  for (const candidate of candidates) {
37778
39150
  const found = await access3(candidate).then(() => true).catch(() => false);
@@ -37833,7 +39205,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
37833
39205
  if (entries.length === 0) {
37834
39206
  return;
37835
39207
  }
37836
- const envPath = path31.join(resolveHermesProfileDir(profileName), ".env");
39208
+ const envPath = path32.join(resolveHermesProfileDir(profileName), ".env");
37837
39209
  const existingRaw = await readFile19(envPath, "utf8").catch((error) => {
37838
39210
  if (isNodeError22(error, "ENOENT")) {
37839
39211
  return "";
@@ -38004,7 +39376,7 @@ async function readActiveMemoryProvider(profileName) {
38004
39376
  return provider;
38005
39377
  }
38006
39378
  async function patchJsonProviderConfig(profileName, relativePath, patch) {
38007
- const configPath = path31.join(
39379
+ const configPath = path32.join(
38008
39380
  resolveHermesProfileDir(profileName),
38009
39381
  relativePath
38010
39382
  );
@@ -38033,7 +39405,7 @@ async function readJsonObject(filePath) {
38033
39405
  } catch {
38034
39406
  throw new HermesMemoryError(
38035
39407
  "memory_provider_config_invalid",
38036
- `${path31.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
39408
+ `${path32.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
38037
39409
  );
38038
39410
  }
38039
39411
  }
@@ -38041,7 +39413,7 @@ function booleanSetting(key, label, value) {
38041
39413
  return {
38042
39414
  key,
38043
39415
  label,
38044
- value: readBoolean4(value) ?? false,
39416
+ value: readBoolean5(value) ?? false,
38045
39417
  editable: true,
38046
39418
  kind: "boolean"
38047
39419
  };
@@ -38159,7 +39531,7 @@ function readPositiveInteger4(value) {
38159
39531
  const numberValue = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim()) : NaN;
38160
39532
  return Number.isFinite(numberValue) && numberValue > 0 ? Math.floor(numberValue) : void 0;
38161
39533
  }
38162
- function readBoolean4(value) {
39534
+ function readBoolean5(value) {
38163
39535
  if (typeof value === "boolean") {
38164
39536
  return value;
38165
39537
  }
@@ -38425,15 +39797,15 @@ function readMemorySettingsPatch(body, options = {}) {
38425
39797
  if (containerTag !== void 0) {
38426
39798
  input.containerTag = containerTag;
38427
39799
  }
38428
- const autoRecall = readBoolean3(body.auto_recall ?? body.autoRecall);
39800
+ const autoRecall = readBoolean4(body.auto_recall ?? body.autoRecall);
38429
39801
  if (autoRecall !== void 0) {
38430
39802
  input.autoRecall = autoRecall;
38431
39803
  }
38432
- const autoCapture = readBoolean3(body.auto_capture ?? body.autoCapture);
39804
+ const autoCapture = readBoolean4(body.auto_capture ?? body.autoCapture);
38433
39805
  if (autoCapture !== void 0) {
38434
39806
  input.autoCapture = autoCapture;
38435
39807
  }
38436
- const autoRetain = readBoolean3(body.auto_retain ?? body.autoRetain);
39808
+ const autoRetain = readBoolean4(body.auto_retain ?? body.autoRetain);
38437
39809
  if (autoRetain !== void 0) {
38438
39810
  input.autoRetain = autoRetain;
38439
39811
  }
@@ -38503,7 +39875,7 @@ function readMemorySettingsPatch(body, options = {}) {
38503
39875
  if (writeFrequency) {
38504
39876
  input.writeFrequency = writeFrequency;
38505
39877
  }
38506
- const saveMessages = readBoolean3(body.save_messages ?? body.saveMessages);
39878
+ const saveMessages = readBoolean4(body.save_messages ?? body.saveMessages);
38507
39879
  if (saveMessages !== void 0) {
38508
39880
  input.saveMessages = saveMessages;
38509
39881
  }
@@ -38543,7 +39915,7 @@ function readMemorySettingsPatch(body, options = {}) {
38543
39915
  if (agentId !== void 0) {
38544
39916
  input.agentId = agentId;
38545
39917
  }
38546
- const rerank = readBoolean3(body.rerank);
39918
+ const rerank = readBoolean4(body.rerank);
38547
39919
  if (rerank !== void 0) {
38548
39920
  input.rerank = rerank;
38549
39921
  }
@@ -38567,7 +39939,7 @@ function readMemorySettingsPatch(body, options = {}) {
38567
39939
  if (dbPath !== void 0) {
38568
39940
  input.dbPath = dbPath;
38569
39941
  }
38570
- const autoExtract = readBoolean3(body.auto_extract ?? body.autoExtract);
39942
+ const autoExtract = readBoolean4(body.auto_extract ?? body.autoExtract);
38571
39943
  if (autoExtract !== void 0) {
38572
39944
  input.autoExtract = autoExtract;
38573
39945
  }
@@ -38657,7 +40029,7 @@ function toMemoryHttpError(error) {
38657
40029
 
38658
40030
  // src/hermes/skills.ts
38659
40031
  import { readFile as readFile20, readdir as readdir11 } from "fs/promises";
38660
- import path32 from "path";
40032
+ import path33 from "path";
38661
40033
  import YAML7 from "yaml";
38662
40034
  var HermesSkillNotFoundError = class extends Error {
38663
40035
  constructor(skillName) {
@@ -38671,7 +40043,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
38671
40043
  async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
38672
40044
  const profile = await readExistingProfile(profileName, paths);
38673
40045
  const profileDir = resolveHermesProfileDir(profile.name);
38674
- const skillsRoot = path32.join(profileDir, "skills");
40046
+ const skillsRoot = path33.join(profileDir, "skills");
38675
40047
  const [skillFiles, disabled, provenance] = await Promise.all([
38676
40048
  findSkillFiles(skillsRoot),
38677
40049
  readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
@@ -38762,7 +40134,7 @@ async function collectSkillFiles(directory, results) {
38762
40134
  if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
38763
40135
  continue;
38764
40136
  }
38765
- const entryPath = path32.join(directory, entry.name);
40137
+ const entryPath = path33.join(directory, entry.name);
38766
40138
  if (entry.isDirectory()) {
38767
40139
  await collectSkillFiles(entryPath, results);
38768
40140
  continue;
@@ -38784,10 +40156,10 @@ async function readSkillMetadata(input) {
38784
40156
  if (raw === null) {
38785
40157
  return null;
38786
40158
  }
38787
- const skillDir = path32.dirname(input.skillFile);
40159
+ const skillDir = path33.dirname(input.skillFile);
38788
40160
  const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
38789
40161
  const name = normalizeSkillName(
38790
- readString24(frontmatter.name) ?? path32.basename(skillDir)
40162
+ readString24(frontmatter.name) ?? path33.basename(skillDir)
38791
40163
  );
38792
40164
  if (!name) {
38793
40165
  return null;
@@ -38806,7 +40178,7 @@ async function readSkillMetadata(input) {
38806
40178
  enabled: !input.disabled.has(name),
38807
40179
  source: provenance.source,
38808
40180
  trust: provenance.trust,
38809
- relativePath: path32.relative(input.skillsRoot, skillDir)
40181
+ relativePath: path33.relative(input.skillsRoot, skillDir)
38810
40182
  };
38811
40183
  }
38812
40184
  function parseSkillDocument(raw) {
@@ -38827,8 +40199,8 @@ function parseSkillDocument(raw) {
38827
40199
  }
38828
40200
  }
38829
40201
  function categoryFromPath(skillsRoot, skillFile) {
38830
- const relative = path32.relative(skillsRoot, skillFile);
38831
- const parts = relative.split(path32.sep).filter(Boolean);
40202
+ const relative = path33.relative(skillsRoot, skillFile);
40203
+ const parts = relative.split(path33.sep).filter(Boolean);
38832
40204
  return parts.length >= 3 ? parts[0] : null;
38833
40205
  }
38834
40206
  function firstBodyDescription(body) {
@@ -38875,7 +40247,7 @@ async function readSkillProvenance(root) {
38875
40247
  return provenance;
38876
40248
  }
38877
40249
  async function readBundledSkillNames(root) {
38878
- const raw = await readFile20(path32.join(root, ".bundled_manifest"), "utf8").catch(
40250
+ const raw = await readFile20(path33.join(root, ".bundled_manifest"), "utf8").catch(
38879
40251
  (error) => {
38880
40252
  if (isNodeError23(error, "ENOENT")) {
38881
40253
  return "";
@@ -38898,7 +40270,7 @@ async function readBundledSkillNames(root) {
38898
40270
  return names;
38899
40271
  }
38900
40272
  async function readHubInstalledSkills(root) {
38901
- const raw = await readFile20(path32.join(root, ".hub", "lock.json"), "utf8").catch(
40273
+ const raw = await readFile20(path33.join(root, ".hub", "lock.json"), "utf8").catch(
38902
40274
  (error) => {
38903
40275
  if (isNodeError23(error, "ENOENT")) {
38904
40276
  return "";
@@ -39034,7 +40406,7 @@ function registerProfileSkillRoutes(router, options) {
39034
40406
  router.patch("/api/v1/profiles/:name/skills/:skillName", async (ctx) => {
39035
40407
  await authenticateRequest(ctx, paths);
39036
40408
  const body = await readJsonBody(ctx.req);
39037
- const enabled = readBoolean3(body.enabled);
40409
+ const enabled = readBoolean4(body.enabled);
39038
40410
  if (enabled === void 0) {
39039
40411
  throw new LinkHttpError(
39040
40412
  400,
@@ -39472,7 +40844,7 @@ function registerStatisticsRoutes(router, options) {
39472
40844
  await authenticateRequest(ctx, paths);
39473
40845
  ctx.set("cache-control", "no-store");
39474
40846
  const usage = await readLinkUsageStatistics(paths, {
39475
- days: readInteger4(ctx.query.days),
40847
+ days: readInteger5(ctx.query.days),
39476
40848
  from: readQueryString(ctx.query.from),
39477
40849
  to: readQueryString(ctx.query.to)
39478
40850
  });
@@ -39566,7 +40938,7 @@ function readModelList(payload) {
39566
40938
  import { EventEmitter as EventEmitter3 } from "events";
39567
40939
  import { spawn as spawn4 } from "child_process";
39568
40940
  import { mkdir as mkdir14, readFile as readFile21, rm as rm8 } from "fs/promises";
39569
- import path33 from "path";
40941
+ import path34 from "path";
39570
40942
  var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
39571
40943
  var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
39572
40944
  var RELEASE_FETCH_TIMEOUT_MS = 5e3;
@@ -39713,7 +41085,7 @@ async function startHermesUpdate(options) {
39713
41085
  }
39714
41086
  async function readHermesUpdateStatus(paths) {
39715
41087
  let state = await readJsonFile(updateStatePath(paths));
39716
- if (state?.state === "running" && !runningUpdate && !isRecentRunningState2(state) && !isProcessAlive2(state.pid)) {
41088
+ if (state?.state === "running" && !runningUpdate && !isRecentRunningState2(state) && !isProcessAlive3(state.pid)) {
39717
41089
  state = {
39718
41090
  ...state,
39719
41091
  state: "failed",
@@ -39844,13 +41216,13 @@ async function readUpdateLogLines(paths) {
39844
41216
  );
39845
41217
  }
39846
41218
  function releaseCachePath(paths) {
39847
- return path33.join(paths.indexesDir, "hermes-release-check.json");
41219
+ return path34.join(paths.indexesDir, "hermes-release-check.json");
39848
41220
  }
39849
41221
  function updateStatePath(paths) {
39850
- return path33.join(paths.runDir, "hermes-update-state.json");
41222
+ return path34.join(paths.runDir, "hermes-update-state.json");
39851
41223
  }
39852
41224
  function updateLogPath(paths) {
39853
- return path33.join(paths.logsDir, UPDATE_LOG_FILE);
41225
+ return path34.join(paths.logsDir, UPDATE_LOG_FILE);
39854
41226
  }
39855
41227
  async function clearUpdateLogFiles(paths) {
39856
41228
  const primary = updateLogPath(paths);
@@ -39924,7 +41296,7 @@ async function readHermesReleaseCheckContext(paths) {
39924
41296
  releaseCheckUrl: url.toString()
39925
41297
  };
39926
41298
  }
39927
- function isProcessAlive2(pid) {
41299
+ function isProcessAlive3(pid) {
39928
41300
  if (!pid || pid <= 0) {
39929
41301
  return false;
39930
41302
  }
@@ -39951,12 +41323,12 @@ function readString25(payload, key) {
39951
41323
  import { spawn as spawn6 } from "child_process";
39952
41324
  import { EventEmitter as EventEmitter4 } from "events";
39953
41325
  import { mkdir as mkdir17, readFile as readFile23, rm as rm11 } from "fs/promises";
39954
- import path35 from "path";
41326
+ import path36 from "path";
39955
41327
 
39956
41328
  // src/daemon/process.ts
39957
41329
  import { spawn as spawn5 } from "child_process";
39958
41330
  import { mkdir as mkdir16, readFile as readFile22, rm as rm10, writeFile as writeFile4 } from "fs/promises";
39959
- import path34 from "path";
41331
+ import path35 from "path";
39960
41332
 
39961
41333
  // src/daemon/service.ts
39962
41334
  import { createServer } from "http";
@@ -40117,17 +41489,17 @@ async function fetchRelayStreamBatchPolicy(serverBaseUrl, options = {}) {
40117
41489
  }
40118
41490
  }
40119
41491
  function readRelayStreamBatchPolicy(input) {
40120
- const record = readRecord2(input);
40121
- const body = readRecord2(record?.policy) ?? readRecord2(record?.stream_batching) ?? record;
41492
+ const record = readRecord3(input);
41493
+ const body = readRecord3(record?.policy) ?? readRecord3(record?.stream_batching) ?? record;
40122
41494
  return normalizeRelayStreamBatchPolicy(body);
40123
41495
  }
40124
41496
  function normalizeRelayStreamBatchPolicy(input) {
40125
- const record = readRecord2(input);
41497
+ const record = readRecord3(input);
40126
41498
  if (!record) {
40127
41499
  return null;
40128
41500
  }
40129
- const flushIntervalMs = readInteger5(record.flushIntervalMs ?? record.flush_interval_ms);
40130
- const flushBytes = readInteger5(record.flushBytes ?? record.flush_bytes);
41501
+ const flushIntervalMs = readInteger6(record.flushIntervalMs ?? record.flush_interval_ms);
41502
+ const flushBytes = readInteger6(record.flushBytes ?? record.flush_bytes);
40131
41503
  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
41504
  return null;
40133
41505
  }
@@ -40136,10 +41508,10 @@ function normalizeRelayStreamBatchPolicy(input) {
40136
41508
  flushBytes
40137
41509
  };
40138
41510
  }
40139
- function readRecord2(value) {
41511
+ function readRecord3(value) {
40140
41512
  return value && typeof value === "object" ? value : null;
40141
41513
  }
40142
- function readInteger5(value) {
41514
+ function readInteger6(value) {
40143
41515
  return typeof value === "number" && Number.isInteger(value) ? value : null;
40144
41516
  }
40145
41517
 
@@ -40173,12 +41545,12 @@ function connectRelayControl(options) {
40173
41545
  onUpdate: options.onStreamBatchPolicy
40174
41546
  };
40175
41547
  const startConnect = () => {
40176
- void waitForPersistedCooldown().then((delay5) => {
41548
+ void waitForPersistedCooldown().then((delay6) => {
40177
41549
  if (closedByUser) {
40178
41550
  return;
40179
41551
  }
40180
- if (delay5 > 0) {
40181
- scheduleTimer(delay5, "cooldown", `Relay reconnect cooldown active for ${delay5}ms`);
41552
+ if (delay6 > 0) {
41553
+ scheduleTimer(delay6, "cooldown", `Relay reconnect cooldown active for ${delay6}ms`);
40182
41554
  return;
40183
41555
  }
40184
41556
  connect();
@@ -40316,8 +41688,8 @@ function connectRelayControl(options) {
40316
41688
  }
40317
41689
  if (recorded.cooldownUntilMs !== null) {
40318
41690
  reconnectAttempts = 0;
40319
- const delay6 = Math.max(0, recorded.cooldownUntilMs - Date.now());
40320
- scheduleTimer(delay6, "cooldown", `Relay reconnect storm guard active for ${delay6}ms`);
41691
+ const delay7 = Math.max(0, recorded.cooldownUntilMs - Date.now());
41692
+ scheduleTimer(delay7, "cooldown", `Relay reconnect storm guard active for ${delay7}ms`);
40321
41693
  return;
40322
41694
  }
40323
41695
  reconnectAttempts += 1;
@@ -40325,16 +41697,16 @@ function connectRelayControl(options) {
40325
41697
  baseMs: backoffBaseMs,
40326
41698
  maxMs: backoffMaxMs
40327
41699
  });
40328
- const delay5 = Math.max(backoffMs, relayRetryAfterMs ?? 0);
40329
- scheduleTimer(delay5, "retrying", `Retrying in ${delay5}ms`);
41700
+ const delay6 = Math.max(backoffMs, relayRetryAfterMs ?? 0);
41701
+ scheduleTimer(delay6, "retrying", `Retrying in ${delay6}ms`);
40330
41702
  }
40331
41703
  async function waitForPersistedCooldown() {
40332
41704
  return await readRelayCooldownDelayMs(paths).catch(() => 0);
40333
41705
  }
40334
- function scheduleTimer(delay5, state, message) {
41706
+ function scheduleTimer(delay6, state, message) {
40335
41707
  clearRetryTimer();
40336
41708
  options.onStatus?.({ state, attempt: reconnectAttempts, message });
40337
- retryTimer = setTimeout(connect, delay5);
41709
+ retryTimer = setTimeout(connect, delay6);
40338
41710
  retryTimer.unref?.();
40339
41711
  }
40340
41712
  function clearRetryTimer() {
@@ -40838,6 +42210,7 @@ async function observePublicRoute(options) {
40838
42210
  "content-type": "application/json",
40839
42211
  ...options.relayBootstrapToken ? { authorization: `Bearer ${options.relayBootstrapToken}` } : {}
40840
42212
  },
42213
+ signal: options.signal,
40841
42214
  body: JSON.stringify({
40842
42215
  install_id: options.installId,
40843
42216
  link_id: options.linkId,
@@ -41123,7 +42496,8 @@ async function reportLinkStatusToServer(options = {}) {
41123
42496
  publicKeyPem: identity.public_key_pem,
41124
42497
  observePublicRoute: true,
41125
42498
  configuredLanHost: config.lanHost,
41126
- fetchImpl: options.fetchImpl
42499
+ fetchImpl: options.fetchImpl,
42500
+ signal: options.signal
41127
42501
  });
41128
42502
  const routes = await mergeLastReportedPublicRoutes(paths, discoveredRoutes);
41129
42503
  const systemInfo = readLinkSystemInfo();
@@ -41154,7 +42528,8 @@ async function reportLinkStatusToServer(options = {}) {
41154
42528
  ...payload,
41155
42529
  public_key_pem: identity.public_key_pem,
41156
42530
  signature
41157
- })
42531
+ }),
42532
+ signal: options.signal
41158
42533
  }
41159
42534
  );
41160
42535
  const body = await response.json().catch(() => null);
@@ -41202,19 +42577,28 @@ function startLanIpMonitor(options) {
41202
42577
  let running = false;
41203
42578
  let closed = false;
41204
42579
  let current = Promise.resolve();
42580
+ let currentAbortController = null;
41205
42581
  const check = (context = {}) => {
41206
42582
  if (running || closed) {
41207
42583
  return current;
41208
42584
  }
41209
42585
  running = true;
42586
+ const abortController = new AbortController();
42587
+ currentAbortController = abortController;
41210
42588
  current = (async () => {
41211
42589
  try {
41212
- await checkLanIpChange(options, context);
42590
+ await checkLanIpChange({ ...options, signal: abortController.signal }, context);
41213
42591
  } catch (error) {
42592
+ if (closed && isAbortError3(error)) {
42593
+ return;
42594
+ }
41214
42595
  void options.logger.warn("lan_ip_monitor_failed", {
41215
42596
  error: error instanceof Error ? error.message : String(error)
41216
42597
  });
41217
42598
  } finally {
42599
+ if (currentAbortController === abortController) {
42600
+ currentAbortController = null;
42601
+ }
41218
42602
  running = false;
41219
42603
  }
41220
42604
  })();
@@ -41233,6 +42617,7 @@ function startLanIpMonitor(options) {
41233
42617
  async close() {
41234
42618
  closed = true;
41235
42619
  clearInterval(timer);
42620
+ currentAbortController?.abort();
41236
42621
  await current.catch(() => void 0);
41237
42622
  }
41238
42623
  };
@@ -41253,7 +42638,8 @@ async function checkLanIpChange(options, context = {}) {
41253
42638
  publicKeyPem: identity.public_key_pem,
41254
42639
  observePublicRoute: context.observePublicRoute === true,
41255
42640
  configuredLanHost: config.lanHost,
41256
- fetchImpl: options.fetchImpl
42641
+ fetchImpl: options.fetchImpl,
42642
+ signal: options.signal
41257
42643
  });
41258
42644
  const routes = await mergeLastReportedPublicRoutes(options.paths, discoveredRoutes);
41259
42645
  if (context.publishToRelay) {
@@ -41278,7 +42664,8 @@ async function checkLanIpChange(options, context = {}) {
41278
42664
  const result = await reportLinkStatusToServer({
41279
42665
  paths: options.paths,
41280
42666
  fetchImpl: options.fetchImpl,
41281
- routes
42667
+ routes,
42668
+ signal: options.signal
41282
42669
  });
41283
42670
  if (result) {
41284
42671
  options.onNetworkRoutes?.(routes);
@@ -41296,6 +42683,9 @@ async function checkLanIpChange(options, context = {}) {
41296
42683
  });
41297
42684
  }
41298
42685
  }
42686
+ function isAbortError3(error) {
42687
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
42688
+ }
41299
42689
 
41300
42690
  // src/daemon/process-guard.ts
41301
42691
  var installed = false;
@@ -41609,6 +42999,22 @@ async function startLinkService(options = {}) {
41609
42999
  await logger.flush();
41610
43000
  throw error;
41611
43001
  }
43002
+ const tuiGatewayCleanup = await cleanupRegisteredTuiGatewayProcesses({
43003
+ paths,
43004
+ logger
43005
+ }).catch((error) => {
43006
+ void logger.warn("tui_gateway_registry_startup_cleanup_failed", {
43007
+ error: error instanceof Error ? error.message : String(error)
43008
+ });
43009
+ return null;
43010
+ });
43011
+ if (tuiGatewayCleanup) {
43012
+ void logger.info("tui_gateway_registry_startup_cleanup_finished", {
43013
+ stopped: tuiGatewayCleanup.stopped.length,
43014
+ missing: tuiGatewayCleanup.missing.length,
43015
+ skipped: tuiGatewayCleanup.skipped.length
43016
+ });
43017
+ }
41612
43018
  server.on("error", (error) => {
41613
43019
  void logger.error("service_error", {
41614
43020
  port: config.port,
@@ -41734,6 +43140,7 @@ async function startLinkService(options = {}) {
41734
43140
  }
41735
43141
  closed = true;
41736
43142
  relay?.close();
43143
+ await closeTuiGatewayBackends({ paths, logger, cleanupRegistry: true });
41737
43144
  await closeServer(server);
41738
43145
  await Promise.all([
41739
43146
  scheduler.close(),
@@ -41949,7 +43356,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
41949
43356
  await mkdir16(paths.logsDir, { recursive: true, mode: 448 });
41950
43357
  const log = createRotatingTextLogWriter({
41951
43358
  paths,
41952
- fileName: path34.basename(daemonLogFile(paths))
43359
+ fileName: path35.basename(daemonLogFile(paths))
41953
43360
  });
41954
43361
  const scriptPath = currentCliScriptPath();
41955
43362
  const write = (chunk) => {
@@ -41968,7 +43375,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
41968
43375
  let stopRequested = false;
41969
43376
  const forwardStop = () => {
41970
43377
  stopRequested = true;
41971
- if (child?.pid && isProcessAlive3(child.pid)) {
43378
+ if (child?.pid && isProcessAlive4(child.pid)) {
41972
43379
  child.kill("SIGTERM");
41973
43380
  }
41974
43381
  };
@@ -42082,23 +43489,23 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
42082
43489
  }
42083
43490
  for (let index = 0; index < 20; index += 1) {
42084
43491
  await wait2(250);
42085
- if (!isProcessAlive3(status.pid)) {
43492
+ if (!isProcessAlive4(status.pid)) {
42086
43493
  break;
42087
43494
  }
42088
43495
  }
42089
- if (isProcessAlive3(status.pid)) {
43496
+ if (isProcessAlive4(status.pid)) {
42090
43497
  try {
42091
43498
  process.kill(status.pid, "SIGKILL");
42092
43499
  } catch {
42093
43500
  }
42094
43501
  for (let index = 0; index < 10; index += 1) {
42095
43502
  await wait2(250);
42096
- if (!isProcessAlive3(status.pid)) {
43503
+ if (!isProcessAlive4(status.pid)) {
42097
43504
  break;
42098
43505
  }
42099
43506
  }
42100
43507
  }
42101
- if (!isProcessAlive3(status.pid) || !await pidBackedServiceIsReachable(paths)) {
43508
+ if (!isProcessAlive4(status.pid) || !await pidBackedServiceIsReachable(paths)) {
42102
43509
  await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
42103
43510
  }
42104
43511
  return await getDaemonStatus(paths);
@@ -42106,7 +43513,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
42106
43513
  async function getDaemonStatus(paths = resolveRuntimePaths()) {
42107
43514
  const pidFile = pidFilePath(paths);
42108
43515
  const pid = await readPid(pidFile);
42109
- if (pid && !isProcessAlive3(pid)) {
43516
+ if (pid && !isProcessAlive4(pid)) {
42110
43517
  await rm10(pidFile, { force: true }).catch(() => void 0);
42111
43518
  return {
42112
43519
  running: false,
@@ -42136,7 +43543,7 @@ async function readPid(filePath) {
42136
43543
  const pid = Number.parseInt(raw.trim(), 10);
42137
43544
  return Number.isInteger(pid) && pid > 0 ? pid : null;
42138
43545
  }
42139
- function isProcessAlive3(pid) {
43546
+ function isProcessAlive4(pid) {
42140
43547
  try {
42141
43548
  process.kill(pid, 0);
42142
43549
  return true;
@@ -42178,7 +43585,7 @@ function startSupervisorHealthMonitor(paths, child, write) {
42178
43585
  );
42179
43586
  terminateChild(child, forceKillTimer);
42180
43587
  forceKillTimer = setTimeout(() => {
42181
- if (child.pid && isProcessAlive3(child.pid)) {
43588
+ if (child.pid && isProcessAlive4(child.pid)) {
42182
43589
  child.kill("SIGKILL");
42183
43590
  }
42184
43591
  }, SUPERVISOR_CHILD_STOP_TIMEOUT_MS);
@@ -42196,7 +43603,7 @@ function startSupervisorHealthMonitor(paths, child, write) {
42196
43603
  closed = true;
42197
43604
  terminateChild(child, forceKillTimer);
42198
43605
  forceKillTimer = setTimeout(() => {
42199
- if (child.pid && isProcessAlive3(child.pid)) {
43606
+ if (child.pid && isProcessAlive4(child.pid)) {
42200
43607
  child.kill("SIGKILL");
42201
43608
  }
42202
43609
  }, SUPERVISOR_CHILD_STOP_TIMEOUT_MS);
@@ -42243,12 +43650,12 @@ function terminateChild(child, previousForceKillTimer) {
42243
43650
  if (previousForceKillTimer) {
42244
43651
  clearTimeout(previousForceKillTimer);
42245
43652
  }
42246
- if (child.pid && isProcessAlive3(child.pid)) {
43653
+ if (child.pid && isProcessAlive4(child.pid)) {
42247
43654
  child.kill("SIGTERM");
42248
43655
  }
42249
43656
  }
42250
43657
  function supervisorStopIntentPath(paths) {
42251
- return path34.join(paths.runDir, "supervisor-stop-intent.json");
43658
+ return path35.join(paths.runDir, "supervisor-stop-intent.json");
42252
43659
  }
42253
43660
  async function writeSupervisorStopIntent(paths, pid) {
42254
43661
  await mkdir16(paths.runDir, { recursive: true, mode: 448 });
@@ -42632,7 +44039,7 @@ async function readLinkUpdateStatus(paths) {
42632
44039
  };
42633
44040
  await writeUpdateState2(paths, state);
42634
44041
  }
42635
- if (state?.state === "running" && !runningUpdate2 && !isRecentRunningState3(state) && !isProcessAlive4(state.pid)) {
44042
+ if (state?.state === "running" && !runningUpdate2 && !isRecentRunningState3(state) && !isProcessAlive5(state.pid)) {
42636
44043
  const reachedTarget = state.target_version && compareSemver3(LINK_VERSION, state.target_version) >= 0;
42637
44044
  state = reachedTarget ? {
42638
44045
  ...state,
@@ -42811,7 +44218,7 @@ async function buildOfficialInstallCommand(options, targetVersion) {
42811
44218
  };
42812
44219
  }
42813
44220
  function buildUnixInstallCommand(installerUrl) {
42814
- const nodeBinDir = path35.dirname(process.execPath);
44221
+ const nodeBinDir = path36.dirname(process.execPath);
42815
44222
  const fetchScript = [
42816
44223
  quoteShellToken(process.execPath),
42817
44224
  "--input-type=module",
@@ -43081,10 +44488,10 @@ async function readUpdateLogLines2(paths) {
43081
44488
  );
43082
44489
  }
43083
44490
  function updateStatePath2(paths) {
43084
- return path35.join(paths.runDir, "link-update-state.json");
44491
+ return path36.join(paths.runDir, "link-update-state.json");
43085
44492
  }
43086
44493
  function updateLogPath2(paths) {
43087
- return path35.join(paths.logsDir, UPDATE_LOG_FILE2);
44494
+ return path36.join(paths.logsDir, UPDATE_LOG_FILE2);
43088
44495
  }
43089
44496
  async function clearUpdateLogFiles2(paths) {
43090
44497
  const primary = updateLogPath2(paths);
@@ -43145,7 +44552,7 @@ function linkUpdateTimeoutError() {
43145
44552
  LINK_UPDATE_TIMEOUT_MS / 6e4
43146
44553
  )} minutes.`;
43147
44554
  }
43148
- function isProcessAlive4(pid) {
44555
+ function isProcessAlive5(pid) {
43149
44556
  if (!pid || pid <= 0) {
43150
44557
  return false;
43151
44558
  }
@@ -43168,7 +44575,7 @@ function readString26(payload, key) {
43168
44575
  }
43169
44576
 
43170
44577
  // src/pairing/pairing.ts
43171
- import path36 from "path";
44578
+ import path37 from "path";
43172
44579
  import { rm as rm12 } from "fs/promises";
43173
44580
 
43174
44581
  // src/relay/bootstrap.ts
@@ -43508,10 +44915,10 @@ async function loadRequiredIdentity2(paths) {
43508
44915
  }
43509
44916
  return identity;
43510
44917
  }
43511
- async function postServerJson(serverBaseUrl, path37, body, options) {
44918
+ async function postServerJson(serverBaseUrl, path38, body, options) {
43512
44919
  let response;
43513
44920
  try {
43514
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path37}`, {
44921
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path38}`, {
43515
44922
  method: "POST",
43516
44923
  headers: {
43517
44924
  accept: "application/json",
@@ -43559,10 +44966,10 @@ function pairingErrorSnapshot(stage, error) {
43559
44966
  occurred_at: (/* @__PURE__ */ new Date()).toISOString()
43560
44967
  };
43561
44968
  }
43562
- async function patchServerJson(serverBaseUrl, path37, token, body, options) {
44969
+ async function patchServerJson(serverBaseUrl, path38, token, body, options) {
43563
44970
  let response;
43564
44971
  try {
43565
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path37}`, {
44972
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path38}`, {
43566
44973
  method: "PATCH",
43567
44974
  headers: {
43568
44975
  accept: "application/json",
@@ -43610,10 +45017,10 @@ function createPairingNetworkError(input) {
43610
45017
  );
43611
45018
  }
43612
45019
  function pairingClaimPath(sessionId, paths) {
43613
- return path36.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
45020
+ return path37.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
43614
45021
  }
43615
45022
  function pairingSessionPath(sessionId, paths) {
43616
- return path36.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
45023
+ return path37.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
43617
45024
  }
43618
45025
  function qrPreferredUrls(routes) {
43619
45026
  return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
@@ -44155,7 +45562,7 @@ async function readActiveCronJobCount(profiles, logger) {
44155
45562
  }, 0);
44156
45563
  }
44157
45564
  function isActiveCronJob(job) {
44158
- const enabled = readBoolean3(job.enabled) ?? true;
45565
+ const enabled = readBoolean4(job.enabled) ?? true;
44159
45566
  if (!enabled) {
44160
45567
  return false;
44161
45568
  }
@@ -44895,6 +46302,9 @@ function registerLiveActivityRoutes(router, options) {
44895
46302
  status_label: state.statusLabel,
44896
46303
  progress_text: state.progressText,
44897
46304
  detail_text: state.detailText ?? null,
46305
+ todo_done_count: state.todoDoneCount ?? null,
46306
+ todo_total_count: state.todoTotalCount ?? null,
46307
+ todo_progress_text: state.todoProgressText ?? null,
44898
46308
  updated_at: state.updatedAt ?? null
44899
46309
  } : null
44900
46310
  };