@adhdev/daemon-standalone 1.0.29 → 1.0.30-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2887,6 +2887,7 @@ var require_dist = __commonJS({
2887
2887
  isDefaultInstanceConfigDir: () => isDefaultInstanceConfigDir,
2888
2888
  isSessionHostLiveRuntime: () => isSessionHostLiveRuntime2,
2889
2889
  isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot2,
2890
+ isTerminalRecord: () => isTerminalRecord,
2890
2891
  partitionSessionHostDiagnosticsSessions: () => partitionSessionHostDiagnosticsSessions2,
2891
2892
  partitionSessionHostRecords: () => partitionSessionHostRecords2,
2892
2893
  resolveAttachableRuntimeRecord: () => resolveAttachableRuntimeRecord,
@@ -3062,10 +3063,17 @@ var require_dist = __commonJS({
3062
3063
  return candidate;
3063
3064
  }
3064
3065
  var LIVE_LIFECYCLES = /* @__PURE__ */ new Set(["starting", "running", "stopping", "interrupted"]);
3066
+ var TERMINAL_LIFECYCLES = /* @__PURE__ */ new Set(["stopped", "failed"]);
3067
+ function isTerminalRecord(record2) {
3068
+ if (!record2) return false;
3069
+ if (record2.termination) return true;
3070
+ return TERMINAL_LIFECYCLES.has(String(record2.lifecycle || "").trim());
3071
+ }
3065
3072
  function isSessionHostLiveRuntime2(record2) {
3066
3073
  if (!record2) return false;
3067
3074
  if (record2.surfaceKind === "live_runtime") return true;
3068
3075
  if (record2.surfaceKind === "recovery_snapshot" || record2.surfaceKind === "inactive_record") return false;
3076
+ if (record2.termination) return false;
3069
3077
  const lifecycle = String(record2.lifecycle || "").trim();
3070
3078
  return LIVE_LIFECYCLES.has(lifecycle);
3071
3079
  }
@@ -33311,10 +33319,10 @@ var require_dist3 = __commonJS({
33311
33319
  }
33312
33320
  function getDaemonBuildInfo() {
33313
33321
  if (cached2) return cached2;
33314
- const commit = readInjected(true ? "949f72794613d4854c7cf869992acbf9b003675b" : void 0) ?? "unknown";
33315
- const commitShort = readInjected(true ? "949f727" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
33316
- const version2 = readInjected(true ? "1.0.29" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
33317
- const builtAt = readInjected(true ? "2026-08-02T08:25:35.500Z" : void 0);
33322
+ const commit = readInjected(true ? "db8aa7a995a36bb687757d1ebcac1e394ee764a6" : void 0) ?? "unknown";
33323
+ const commitShort = readInjected(true ? "db8aa7a9" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
33324
+ const version2 = readInjected(true ? "1.0.30-rc.2" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
33325
+ const builtAt = readInjected(true ? "2026-08-03T02:08:19.187Z" : void 0);
33318
33326
  cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
33319
33327
  return cached2;
33320
33328
  }
@@ -35574,11 +35582,15 @@ ${error48.message || ""}`;
35574
35582
  __export2(dist_exports, {
35575
35583
  CANONICAL_MESH_TOOL_COUNT: () => CANONICAL_MESH_TOOL_COUNT,
35576
35584
  CANONICAL_MESH_TOOL_NAMES: () => CANONICAL_MESH_TOOL_NAMES,
35585
+ CLI_SLOT_RECIPES: () => CLI_SLOT_RECIPES,
35577
35586
  DEFAULT_DIFFICULTY_BRAINS: () => DEFAULT_DIFFICULTY_BRAINS,
35578
35587
  MAGI_RAW_ANSWER_CAP: () => MAGI_RAW_ANSWER_CAP,
35579
35588
  MESH_TASK_DIFFICULTIES: () => MESH_TASK_DIFFICULTIES,
35580
35589
  STATUS_PROBE_ARG_KEY: () => STATUS_PROBE_ARG_KEY,
35590
+ UNKNOWN_CLI_SLOT_RECIPE: () => UNKNOWN_CLI_SLOT_RECIPE,
35581
35591
  argsCarryStatusProbeMarker: () => argsCarryStatusProbeMarker,
35592
+ buildMagiPanelProposal: () => buildMagiPanelProposal,
35593
+ buildSlotProposal: () => buildSlotProposal,
35582
35594
  canonicalDaemonId: () => canonicalDaemonId,
35583
35595
  daemonIdsEquivalent: () => daemonIdsEquivalent,
35584
35596
  deriveSlotsFromLegacy: () => deriveSlotsFromLegacy,
@@ -36033,6 +36045,93 @@ ${error48.message || ""}`;
36033
36045
  };
36034
36046
  });
36035
36047
  }
36048
+ function slotKey(slot) {
36049
+ return [
36050
+ slot.provider,
36051
+ slot.model ?? "",
36052
+ slot.thinkingLevel ?? "",
36053
+ [...slot.difficulty ?? []].sort().join("|"),
36054
+ [...slot.capability ?? []].sort().join("|"),
36055
+ slot.maxParallel ?? ""
36056
+ ].join("\0");
36057
+ }
36058
+ function dedupeDetected(detected) {
36059
+ const seen = /* @__PURE__ */ new Set();
36060
+ const out = [];
36061
+ for (const d of detected) {
36062
+ const type2 = typeof d?.type === "string" ? d.type.trim() : "";
36063
+ if (!type2 || seen.has(type2)) continue;
36064
+ seen.add(type2);
36065
+ out.push({ ...d, type: type2 });
36066
+ }
36067
+ return out;
36068
+ }
36069
+ function buildSlotProposal(detected, currentSlots = []) {
36070
+ const providers = dedupeDetected(detected ?? []);
36071
+ const proposedSlots = [];
36072
+ const entries = [];
36073
+ const unknownProviders = [];
36074
+ const provisionalProviders = [];
36075
+ for (const provider of providers) {
36076
+ const known = CLI_SLOT_RECIPES[provider.type];
36077
+ const recipes = known ?? [UNKNOWN_CLI_SLOT_RECIPE];
36078
+ const isUnknown = !known;
36079
+ if (isUnknown) unknownProviders.push(provider.type);
36080
+ let providerProvisional = false;
36081
+ for (const recipe of recipes) {
36082
+ const slot = normalizeNodeCapabilitySlot({
36083
+ provider: provider.type,
36084
+ model: recipe.model,
36085
+ thinkingLevel: recipe.thinkingLevel,
36086
+ difficulty: recipe.difficulty,
36087
+ maxParallel: recipe.maxParallel
36088
+ });
36089
+ if (!slot) continue;
36090
+ const provisional = recipe.provisional === true;
36091
+ if (provisional) providerProvisional = true;
36092
+ proposedSlots.push(slot);
36093
+ entries.push({
36094
+ slot,
36095
+ unknownProvider: isUnknown,
36096
+ provisional,
36097
+ ...recipe.rationale ? { rationale: recipe.rationale } : {}
36098
+ });
36099
+ }
36100
+ if (providerProvisional) provisionalProviders.push(provider.type);
36101
+ }
36102
+ const proposedKeys = new Set(proposedSlots.map(slotKey));
36103
+ const droppedSlots = currentSlots.filter((slot) => !proposedKeys.has(slotKey(slot)));
36104
+ const proposedProviders = new Set(proposedSlots.map((s2) => s2.provider));
36105
+ const droppedProviders = [...new Set(
36106
+ droppedSlots.map((s2) => s2.provider).filter((p) => !proposedProviders.has(p))
36107
+ )];
36108
+ return {
36109
+ proposedSlots,
36110
+ entries,
36111
+ unknownProviders,
36112
+ provisionalProviders,
36113
+ droppedSlots,
36114
+ droppedProviders,
36115
+ destructive: droppedSlots.length > 0
36116
+ };
36117
+ }
36118
+ function buildMagiPanelProposal(detected, opts = {}) {
36119
+ const providers = dedupeDetected(detected ?? []);
36120
+ const tableOrder = Object.keys(CLI_SLOT_RECIPES);
36121
+ const rank = (type2) => {
36122
+ const i = tableOrder.indexOf(type2);
36123
+ return i === -1 ? Number.MAX_SAFE_INTEGER : i;
36124
+ };
36125
+ const ordered = [...providers].sort((a, b) => rank(a.type) - rank(b.type));
36126
+ const limit = Number.isFinite(opts.maxSlots) && opts.maxSlots > 0 ? Math.floor(opts.maxSlots) : ordered.length;
36127
+ return ordered.slice(0, limit).map((p) => ({
36128
+ ...opts.nodeId ? { nodeId: opts.nodeId } : {},
36129
+ provider: p.type
36130
+ // A model is intentionally NOT pinned: the panel's job is cross-provider
36131
+ // independence, and pinning models here would silently couple the panel
36132
+ // to this table's cost assumptions rather than to review quality.
36133
+ }));
36134
+ }
36036
36135
  function interpolateArgs(args, context) {
36037
36136
  const result = {};
36038
36137
  for (const [k, v] of Object.entries(args)) {
@@ -36061,6 +36160,8 @@ ${error48.message || ""}`;
36061
36160
  var MAGI_RAW_ANSWER_CAP;
36062
36161
  var MESH_TASK_DIFFICULTIES;
36063
36162
  var DEFAULT_DIFFICULTY_BRAINS;
36163
+ var CLI_SLOT_RECIPES;
36164
+ var UNKNOWN_CLI_SLOT_RECIPE;
36064
36165
  var CANONICAL_MESH_TOOL_NAMES;
36065
36166
  var CANONICAL_MESH_TOOL_COUNT;
36066
36167
  var STATUS_PROBE_ARG_KEY;
@@ -36075,6 +36176,73 @@ ${error48.message || ""}`;
36075
36176
  medium: { model: "sonnet", thinkingLevel: "medium" },
36076
36177
  difficult: { model: "opus", thinkingLevel: "high" }
36077
36178
  };
36179
+ CLI_SLOT_RECIPES = Object.freeze({
36180
+ "claude-cli": [
36181
+ {
36182
+ model: "sonnet",
36183
+ thinkingLevel: "high",
36184
+ difficulty: ["medium", "easy"],
36185
+ maxParallel: 5,
36186
+ rationale: "Primary workhorse \u2014 widest parallelism for routine work."
36187
+ },
36188
+ {
36189
+ model: "opus",
36190
+ thinkingLevel: "high",
36191
+ difficulty: ["difficult"],
36192
+ maxParallel: 1,
36193
+ rationale: "Reserved for hard tasks; capped at 1 to bound cost."
36194
+ }
36195
+ ],
36196
+ "kimi": [
36197
+ {
36198
+ model: "kimi-code/k3",
36199
+ difficulty: ["medium", "difficult"],
36200
+ maxParallel: 2,
36201
+ rationale: "Independent second opinion on mid/hard work."
36202
+ }
36203
+ ],
36204
+ "codex-cli": [
36205
+ {
36206
+ difficulty: ["medium", "difficult", "freeform"],
36207
+ maxParallel: 2,
36208
+ rationale: "Broad range including freeform; no model pin."
36209
+ }
36210
+ ],
36211
+ "antigravity-cli": [
36212
+ {
36213
+ model: "Gemini 3.1 Pro (High)",
36214
+ difficulty: ["easy"],
36215
+ maxParallel: 2,
36216
+ rationale: "Cheap capacity for easy tasks."
36217
+ }
36218
+ ],
36219
+ "cursor-cli": [
36220
+ {
36221
+ model: "auto",
36222
+ difficulty: ["easy"],
36223
+ maxParallel: 1,
36224
+ rationale: "Easy tasks only; auto model selection."
36225
+ }
36226
+ ],
36227
+ "hermes-cli": [
36228
+ {
36229
+ difficulty: ["medium"],
36230
+ maxParallel: 2,
36231
+ provisional: true,
36232
+ // NOTE: ESTIMATE, NOT OBSERVED. hermes-cli is absent from the live
36233
+ // slot configuration this table was seeded from, so `medium` is a
36234
+ // conservative placement rather than a transcription. Revisit once
36235
+ // it has real usage data.
36236
+ rationale: "ESTIMATE \u2014 no live slot to transcribe; conservative mid placement. Adjust after real use."
36237
+ }
36238
+ ]
36239
+ });
36240
+ UNKNOWN_CLI_SLOT_RECIPE = Object.freeze({
36241
+ difficulty: ["medium"],
36242
+ maxParallel: 1,
36243
+ provisional: true,
36244
+ rationale: "Unrecognized provider \u2014 conservative default (medium, maxParallel 1). Review before relying on it."
36245
+ });
36078
36246
  CANONICAL_MESH_TOOL_NAMES = [
36079
36247
  "mesh_status",
36080
36248
  "mesh_list_nodes",
@@ -36126,7 +36294,8 @@ ${error48.message || ""}`;
36126
36294
  "mesh_magi_kind_panel_set",
36127
36295
  "mesh_magi_kind_panel_list",
36128
36296
  "mesh_node_slots_set",
36129
- "mesh_node_slots_list"
36297
+ "mesh_node_slots_list",
36298
+ "mesh_node_slots_propose"
36130
36299
  ];
36131
36300
  CANONICAL_MESH_TOOL_COUNT = CANONICAL_MESH_TOOL_NAMES.length;
36132
36301
  STATUS_PROBE_ARG_KEY = "_statusProbe";
@@ -37438,26 +37607,48 @@ Next step: ${nextStep}`;
37438
37607
  function getLogLevel() {
37439
37608
  return currentLevel;
37440
37609
  }
37610
+ function resolveLogDir() {
37611
+ const override = process.env.ADHDEV_CONFIG_DIR;
37612
+ const home = override && override.trim() ? override.trim() : path9.join(os42.homedir(), ".adhdev");
37613
+ return path9.join(home, "logs");
37614
+ }
37615
+ function ensureLogDir(dir) {
37616
+ try {
37617
+ fs32.mkdirSync(dir, { recursive: true });
37618
+ } catch {
37619
+ }
37620
+ }
37441
37621
  function getDateStr() {
37442
37622
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
37443
37623
  }
37444
37624
  function getDaemonLogDir() {
37445
- return LOG_DIR;
37625
+ const dir = resolveLogDir();
37626
+ prepareLogDirOnce(dir);
37627
+ return dir;
37446
37628
  }
37447
37629
  function getCurrentDaemonLogPath(date5 = /* @__PURE__ */ new Date()) {
37448
- return path9.join(LOG_DIR, `daemon-${date5.toISOString().slice(0, 10)}.log`);
37630
+ return path9.join(getDaemonLogDir(), `daemon-${date5.toISOString().slice(0, 10)}.log`);
37449
37631
  }
37450
- function checkDateRotation() {
37632
+ function refreshCurrentLogFile() {
37451
37633
  const today = getDateStr();
37452
- if (today !== currentDate) {
37453
- currentDate = today;
37454
- currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
37455
- cleanOldLogs();
37456
- }
37634
+ const dir = resolveLogDir();
37635
+ const dateChanged = today !== currentDate;
37636
+ const dirChanged = dir !== currentLogDir;
37637
+ if (!dateChanged && !dirChanged) return false;
37638
+ currentDate = today;
37639
+ currentLogDir = dir;
37640
+ currentLogDirEnv = process.env.ADHDEV_CONFIG_DIR;
37641
+ currentLogFile = path9.join(dir, `daemon-${today}.log`);
37642
+ if (dirChanged) prepareLogDirOnce(dir);
37643
+ else if (dateChanged) cleanOldLogs(dir);
37644
+ return true;
37457
37645
  }
37458
- function cleanOldLogs() {
37646
+ function checkDateRotation() {
37647
+ refreshCurrentLogFile();
37648
+ }
37649
+ function cleanOldLogs(logDir) {
37459
37650
  try {
37460
- const files = fs32.readdirSync(LOG_DIR).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
37651
+ const files = fs32.readdirSync(logDir).filter((f) => f.startsWith("daemon-") && f.endsWith(".log"));
37461
37652
  const cutoff = /* @__PURE__ */ new Date();
37462
37653
  cutoff.setDate(cutoff.getDate() - MAX_LOG_DAYS);
37463
37654
  const cutoffStr = cutoff.toISOString().slice(0, 10);
@@ -37465,7 +37656,7 @@ Next step: ${nextStep}`;
37465
37656
  const dateMatch = file2.match(/daemon-(\d{4}-\d{2}-\d{2})/);
37466
37657
  if (dateMatch && dateMatch[1] < cutoffStr) {
37467
37658
  try {
37468
- fs32.unlinkSync(path9.join(LOG_DIR, file2));
37659
+ fs32.unlinkSync(path9.join(logDir, file2));
37469
37660
  } catch {
37470
37661
  }
37471
37662
  }
@@ -37502,12 +37693,35 @@ Next step: ${nextStep}`;
37502
37693
  } catch {
37503
37694
  }
37504
37695
  }
37696
+ function prepareLogDirOnce(dir) {
37697
+ if (preparedLogDirs.has(dir)) return;
37698
+ preparedLogDirs.add(dir);
37699
+ ensureLogDir(dir);
37700
+ cleanOldLogs(dir);
37701
+ try {
37702
+ const oldLog = path9.join(dir, "daemon.log");
37703
+ if (fs32.existsSync(oldLog)) {
37704
+ const stat2 = fs32.statSync(oldLog);
37705
+ const oldDate = stat2.mtime.toISOString().slice(0, 10);
37706
+ fs32.renameSync(oldLog, path9.join(dir, `daemon-${oldDate}.log`));
37707
+ }
37708
+ const oldLogBackup = path9.join(dir, "daemon.log.old");
37709
+ if (fs32.existsSync(oldLogBackup)) {
37710
+ fs32.unlinkSync(oldLogBackup);
37711
+ }
37712
+ } catch {
37713
+ }
37714
+ }
37505
37715
  function writeToFile(line) {
37506
37716
  try {
37507
37717
  if (++writeCount % 1e3 === 0) {
37508
37718
  checkDateRotation();
37509
37719
  rotateSizeIfNeeded();
37720
+ } else if (process.env.ADHDEV_CONFIG_DIR !== currentLogDirEnv) {
37721
+ currentLogDirEnv = process.env.ADHDEV_CONFIG_DIR;
37722
+ refreshCurrentLogFile();
37510
37723
  }
37724
+ prepareLogDirOnce(currentLogDir);
37511
37725
  AsyncBatchWriter.write(currentLogFile, line + "\n");
37512
37726
  } catch {
37513
37727
  }
@@ -37602,14 +37816,15 @@ Next step: ${nextStep}`;
37602
37816
  var LEVEL_NUM;
37603
37817
  var LEVEL_LABEL;
37604
37818
  var currentLevel;
37605
- var ADHDEV_HOME;
37606
- var LOG_DIR;
37607
37819
  var MAX_LOG_SIZE;
37608
37820
  var MAX_LOG_DAYS;
37609
37821
  var MAX_SIZE_ROTATION_GENERATIONS;
37610
37822
  var currentDate;
37823
+ var currentLogDir;
37611
37824
  var currentLogFile;
37825
+ var preparedLogDirs;
37612
37826
  var writeCount;
37827
+ var currentLogDirEnv;
37613
37828
  var RING_BUFFER_SIZE;
37614
37829
  var ringBuffer;
37615
37830
  var origConsoleLog;
@@ -37617,7 +37832,6 @@ Next step: ${nextStep}`;
37617
37832
  var origConsoleWarn;
37618
37833
  var LOG2;
37619
37834
  var interceptorInstalled;
37620
- var LOG_PATH;
37621
37835
  var init_logger = __esm2({
37622
37836
  "src/logging/logger.ts"() {
37623
37837
  "use strict";
@@ -37628,32 +37842,15 @@ Next step: ${nextStep}`;
37628
37842
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
37629
37843
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
37630
37844
  currentLevel = "info";
37631
- ADHDEV_HOME = process.env.ADHDEV_CONFIG_DIR && process.env.ADHDEV_CONFIG_DIR.trim() ? process.env.ADHDEV_CONFIG_DIR.trim() : path9.join(os42.homedir(), ".adhdev");
37632
- LOG_DIR = path9.join(ADHDEV_HOME, "logs");
37633
37845
  MAX_LOG_SIZE = 5 * 1024 * 1024;
37634
37846
  MAX_LOG_DAYS = 7;
37635
37847
  MAX_SIZE_ROTATION_GENERATIONS = 3;
37636
- try {
37637
- fs32.mkdirSync(LOG_DIR, { recursive: true });
37638
- } catch {
37639
- }
37640
37848
  currentDate = getDateStr();
37641
- currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
37642
- cleanOldLogs();
37643
- try {
37644
- const oldLog = path9.join(LOG_DIR, "daemon.log");
37645
- if (fs32.existsSync(oldLog)) {
37646
- const stat2 = fs32.statSync(oldLog);
37647
- const oldDate = stat2.mtime.toISOString().slice(0, 10);
37648
- fs32.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
37649
- }
37650
- const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
37651
- if (fs32.existsSync(oldLogBackup)) {
37652
- fs32.unlinkSync(oldLogBackup);
37653
- }
37654
- } catch {
37655
- }
37849
+ currentLogDir = resolveLogDir();
37850
+ currentLogFile = path9.join(currentLogDir, `daemon-${currentDate}.log`);
37851
+ preparedLogDirs = /* @__PURE__ */ new Set();
37656
37852
  writeCount = 0;
37853
+ currentLogDirEnv = process.env.ADHDEV_CONFIG_DIR;
37657
37854
  RING_BUFFER_SIZE = 200;
37658
37855
  ringBuffer = [];
37659
37856
  origConsoleLog = console.log.bind(console);
@@ -37679,7 +37876,6 @@ Next step: ${nextStep}`;
37679
37876
  }
37680
37877
  };
37681
37878
  interceptorInstalled = false;
37682
- LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
37683
37879
  }
37684
37880
  });
37685
37881
  function loadBetterSqlite3() {
@@ -45689,6 +45885,7 @@ ${rules.join("\n")}`;
45689
45885
  - **Worktree affinity.** A worktree is a durable per-branch workspace; keep all of a branch's code_change/fix/review work on its worktree node by targeting \`required_tags: ["worktree=<branch>"]\` or \`target_node_id\`. Get the id/branch from the \`mesh_clone_node\` result or a live \`mesh_status\` \u2014 the Configured Nodes snapshot won't list a worktree cloned after launch. Untargeted same-branch follow-ups drift to the base node. Only \`convergence\` (merge/push) runs on the base, never pinned to the worktree.
45690
45886
  - **Classify task difficulty to save tokens.** For each task you enqueue, judge its execution difficulty and pass \`difficulty\`: \`easy\` (extraction, renames, doc tweaks, trivial fixes), \`medium\` (ordinary feature/bugfix work), \`difficult\` (architecture, tricky debugging, multi-file refactors, subtle reasoning), or \`freeform\`. The mesh's per-difficulty brain preset then runs easy tasks on a cheaper model at low reasoning effort and hard tasks on a stronger model at high effort \u2014 real token savings on simple work. The current presets are shown in the "Brain presets" section below. You may still pass an explicit \`model\`/\`thinkingLevel\` to override the preset for one task.
45691
45887
  - **Retune node profiles when routing is a poor fit \u2014 but only with approval.** A node's capability slots (its provider/model/thinking + difficulty range + capability tags, seen via \`mesh_node_slots_list\`) are what task\u2192node fitness routing matches against. If you notice a persistent mismatch \u2014 e.g. every \`difficult\` task lands on a node whose only slot is a cheap model, or a capability a node clearly has isn't declared \u2014 you MAY propose a slot change with \`mesh_node_slots_set\` (write=false). That returns current-vs-proposed; present that diff to the user with a one-line reason and apply (write=true) ONLY after they approve. It is a WHOLESALE replacement of the node's slots, so include the slots you want to keep. Never rewrite a node's profile silently or without a clear routing reason.
45888
+ - **Bootstrap a node's slots from what's actually installed.** When a node has NO slots configured (routing then falls back to "first available provider"), or CLI agents were newly installed on it, call \`mesh_node_slots_propose({ node_id })\` instead of hand-writing a profile. It detects the node's installed CLI agents and drafts a slot list from them \u2014 read-only, it never writes. Present its \`proposedSlots\` with the \`droppedSlots\` / \`destructive\` fields it reports (a wholesale write would delete any existing hand-tuned slot the draft doesn't reproduce, including providers not currently on PATH), then apply with \`mesh_node_slots_set({ slots: proposedSlots, write: true })\` after approval. It flags \`unknownProvider\` / \`provisional\` slots whose placement is a conservative guess rather than an attested one \u2014 call those out rather than presenting them as settled.
45692
45889
  - **Respect explicit provider requests.** Map: Hermes \u2192 \`hermes-cli\`, Claude/Claude Code \u2192 \`claude-cli\`, Codex \u2192 \`codex-cli\`, Gemini \u2192 \`gemini-cli\`, Antigravity \u2192 \`antigravity-cli\`. Never substitute the coordinator's own runtime.
45693
45890
  - **Verify via git, not source.** Use \`mesh_git_status\` to confirm side effects. Treat agent summaries as self-reports, not verification.
45694
45891
  - **Limit parallelism.** Start with 1\u20132 tasks; scale only on success. Never duplicate a session because \`mesh_read_chat\` shows no final message while tool/terminal activity is ongoing. This caps *concurrent* load \u2014 it does not mean serialize independent work: when a new, independent request arrives and there is headroom under \`maxParallelTasks\`, dispatch it right away rather than waiting for an in-flight task or a user nudge (read-only diagnosis especially, since it has no merge cost).
@@ -45707,10 +45904,8 @@ ${rules.join("\n")}`;
45707
45904
 
45708
45905
  ### Task Messaging Requirements
45709
45906
 
45710
- When you compose the task message you dispatch to a node, include these requirements so the worker follows repo conventions the daemon can't enforce for it:
45907
+ When you compose the task message you dispatch to a node, include this requirement so the worker's completion report is verifiable:
45711
45908
 
45712
- - **OSS English commits.** If a task commits anything under \`oss/\` (an AGPL public repo whose history external contributors read), tell the worker explicitly that commit messages in \`oss/\` MUST be English. Root-level commits (proprietary packages) may use any language.
45713
- - **Scoped test runs.** For a validation or code-change task, instruct the worker to run only the tests covering the changed files (\`vitest run <path>\` or \`-t <name>\`), not the whole suite. Run the full suite only when the task is explicitly a full-suite gate \u2014 a broad daemon-core run is minutes of wall-clock and the biggest source of worker slowness.
45714
45909
  - **Branch convergence state.** For a worktree task, require the completion report to classify the touched branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. A task that ends on a non-main branch is not complete unless the report names that state and the next step.`;
45715
45910
  }
45716
45911
  var fs42;
@@ -45792,7 +45987,8 @@ When you compose the task message you dispatch to a node, include these requirem
45792
45987
  | \`mesh_magi_kind_panel_set\` | Bind a task_kind \u2192 MAGI kind-panel slots (the SOLE MAGI panel-resolution surface; machine-local, wholesale replacement \u2014 approve current-vs-new first) |
45793
45988
  | \`mesh_magi_kind_panel_list\` | List configured task_kind \u2192 MAGI kind-panel slot bindings (machine-local, read-only) |
45794
45989
  | \`mesh_node_slots_list\` | List a node's capability slots (its AI-tool profile: provider/model/thinking + difficulty range + capability tags), read-only |
45795
- | \`mesh_node_slots_set\` | PROPOSE (dry-run) or APPLY a node's capability slots \u2014 how you autonomously retune a node's tool profile; WHOLESALE replacement, present current-vs-proposed and get user approval before write=true |`;
45990
+ | \`mesh_node_slots_set\` | PROPOSE (dry-run) or APPLY a node's capability slots \u2014 how you autonomously retune a node's tool profile; WHOLESALE replacement, present current-vs-proposed and get user approval before write=true |
45991
+ | \`mesh_node_slots_propose\` | AUTO-DETECT a node's installed CLI agents and DRAFT a slot profile from them (read-only, never writes) \u2014 use when a node has no slots yet or new CLIs were installed; reports droppedSlots (what a wholesale write would destroy), then apply via mesh_node_slots_set write=true after approval |`;
45796
45992
  TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
45797
45993
 
45798
45994
  Before doing any coordinator work, confirm that the actual callable tool list includes \`mesh_status\` and the other \`mesh_*\` tools from the table above. If this Repo Mesh coordinator prompt is present but the callable \`mesh_*\` tools are missing, the MCP server/tool manifest is stale or not injected yet. Do not substitute terminal/file/git tools, do not inspect or edit the repository directly, and do not continue as a non-mesh local coding agent. Stop immediately and tell the user to run \`/reload-mcp\` or start a fresh coordinator session so ADHDev can reconnect \`adhdev-mesh\`.`;
@@ -51552,10 +51748,15 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
51552
51748
  }
51553
51749
  function findRecentTerminalLedgerEvidence(args) {
51554
51750
  if (!args.sessionId && !args.nodeId) return null;
51751
+ const notBefore = typeof args.notBeforeMs === "number" && Number.isFinite(args.notBeforeMs) ? args.notBeforeMs : null;
51555
51752
  const entries = readLedgerEntries(args.meshId, { tail: 200 });
51556
51753
  for (let i = entries.length - 1; i >= 0; i--) {
51557
51754
  const entry = entries[i];
51558
51755
  if (entry.kind !== "task_completed" && entry.kind !== "task_failed" && entry.kind !== "task_stalled") continue;
51756
+ if (notBefore !== null) {
51757
+ const entryTime = new Date(entry.timestamp).getTime();
51758
+ if (!Number.isFinite(entryTime) || entryTime < notBefore) continue;
51759
+ }
51559
51760
  if (args.sessionId && sessionIdsEquivalent(entry.sessionId, args.sessionId)) {
51560
51761
  return { id: entry.id, kind: entry.kind, payload: entry.payload || {}, timestamp: entry.timestamp };
51561
51762
  }
@@ -51819,19 +52020,44 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
51819
52020
  }
51820
52021
  };
51821
52022
  }
51822
- const terminal = findRecentTerminalLedgerEvidence({
52023
+ const taskId = readNonEmptyString(args.metadataEvent.taskId);
52024
+ const terminal = taskId ? findTerminalLedgerEvidenceForTask({
51823
52025
  meshId: args.meshId,
52026
+ taskId,
51824
52027
  sessionId: sessionId || void 0,
51825
52028
  nodeId: nodeId || void 0
51826
- });
52029
+ }) : (() => {
52030
+ const notBeforeMs = resolveNoProgressEvidenceFloor(args.metadataEvent);
52031
+ if (notBeforeMs === null) return null;
52032
+ return findRecentTerminalLedgerEvidence({
52033
+ meshId: args.meshId,
52034
+ sessionId: sessionId || void 0,
52035
+ nodeId: nodeId || void 0,
52036
+ notBeforeMs
52037
+ });
52038
+ })();
51827
52039
  if (!terminal) return null;
51828
52040
  return {
51829
52041
  ...args.metadataEvent,
51830
52042
  source: "no_progress_terminal_ledger_suppression",
51831
52043
  terminalLedgerKind: terminal.kind,
51832
- terminalLedgerAt: terminal.timestamp
52044
+ terminalLedgerAt: terminal.timestamp,
52045
+ terminalLedgerScope: taskId ? "task" : "session_recency_bounded"
51833
52046
  };
51834
52047
  }
52048
+ function resolveNoProgressEvidenceFloor(metadataEvent) {
52049
+ const lastOutputAt = readFiniteNumber(metadataEvent.lastOutputAt);
52050
+ if (lastOutputAt !== null && lastOutputAt > 0) return lastOutputAt;
52051
+ const stalledMs = readFiniteNumber(metadataEvent.stalledMs);
52052
+ const timestamp2 = readFiniteNumber(metadataEvent.timestamp);
52053
+ if (stalledMs !== null && stalledMs >= 0 && timestamp2 !== null && timestamp2 > 0) {
52054
+ return timestamp2 - stalledMs;
52055
+ }
52056
+ return null;
52057
+ }
52058
+ function readFiniteNumber(value) {
52059
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
52060
+ }
51835
52061
  var DIRECT_DISPATCH_RECONCILE_GRACE_MS;
51836
52062
  var DIRECT_DISPATCH_IDLE_SESSION_RECONCILE_GRACE_MS;
51837
52063
  var init_mesh_events_stale = __esm2({
@@ -58161,7 +58387,7 @@ ${cleanBody}`;
58161
58387
  function readRecord6(value) {
58162
58388
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
58163
58389
  }
58164
- function readFiniteNumber(value) {
58390
+ function readFiniteNumber2(value) {
58165
58391
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
58166
58392
  }
58167
58393
  function identitiesMatch(metadataEvent, evidence, attempt, eventSessionId) {
@@ -58170,8 +58396,8 @@ ${cleanBody}`;
58170
58396
  const evidenceTaskId = readNonEmptyString(evidence.taskId);
58171
58397
  const evidenceAttemptId = readNonEmptyString(evidence.attemptId);
58172
58398
  const evidenceSessionId = readNonEmptyString(evidence.sessionId);
58173
- const eventNonce = readFiniteNumber(metadataEvent.dispatchNonce);
58174
- const evidenceNonce = readFiniteNumber(evidence.dispatchNonce);
58399
+ const eventNonce = readFiniteNumber2(metadataEvent.dispatchNonce);
58400
+ const evidenceNonce = readFiniteNumber2(evidence.dispatchNonce);
58175
58401
  if (!taskId || !attemptId || !eventSessionId) return false;
58176
58402
  if (taskId !== attempt.taskId || attemptId !== attempt.attemptId) return false;
58177
58403
  if (evidenceTaskId !== taskId || evidenceAttemptId !== attemptId) return false;
@@ -58207,16 +58433,16 @@ ${cleanBody}`;
58207
58433
  return { authoritative: false, reason: "evidence_source_profile_mismatch" };
58208
58434
  }
58209
58435
  const finalSummary = readNonEmptyString(metadataEvent.finalSummary);
58210
- const finalContentLength = readFiniteNumber(evidence.finalContentLength) ?? 0;
58436
+ const finalContentLength = readFiniteNumber2(evidence.finalContentLength) ?? 0;
58211
58437
  if (!finalSummary || finalContentLength <= 0) {
58212
58438
  return { authoritative: false, reason: "empty_final_content" };
58213
58439
  }
58214
58440
  if (!identitiesMatch(metadataEvent, evidence, attempt, eventSessionId)) {
58215
58441
  return { authoritative: false, reason: "causal_identity_mismatch" };
58216
58442
  }
58217
- const observedAt = readFiniteNumber(evidence.observedAt);
58218
- const eventTimestamp = readFiniteNumber(metadataEvent.timestamp);
58219
- const turnStartedAt = readFiniteNumber(evidence.turnStartedAt);
58443
+ const observedAt = readFiniteNumber2(evidence.observedAt);
58444
+ const eventTimestamp = readFiniteNumber2(metadataEvent.timestamp);
58445
+ const turnStartedAt = readFiniteNumber2(evidence.turnStartedAt);
58220
58446
  const acceptedAt = Date.parse(attempt.acceptedAt || attempt.createdAt);
58221
58447
  if (!observedAt || !eventTimestamp || !turnStartedAt) {
58222
58448
  return { authoritative: false, reason: "missing_evidence_timestamp" };
@@ -58258,11 +58484,11 @@ ${cleanBody}`;
58258
58484
  if (!attempt || attempt.terminalOutcome || isTerminalTurnStage(attempt.stage)) return false;
58259
58485
  const taskId = readNonEmptyString(metadataEvent.taskId);
58260
58486
  const attemptId = readNonEmptyString(metadataEvent.attemptId);
58261
- const nonce = readFiniteNumber(metadataEvent.dispatchNonce);
58487
+ const nonce = readFiniteNumber2(metadataEvent.dispatchNonce);
58262
58488
  if (!taskId || taskId !== attempt.taskId || attemptId !== attempt.attemptId) return false;
58263
58489
  if (!attempt.sessionId || !sessionIdsEquivalent(attempt.sessionId, eventSessionId)) return false;
58264
58490
  if (typeof attempt.dispatchNonce !== "number" || nonce !== attempt.dispatchNonce) return false;
58265
- const eventTimestamp = readFiniteNumber(metadataEvent.timestamp);
58491
+ const eventTimestamp = readFiniteNumber2(metadataEvent.timestamp);
58266
58492
  const acceptedAt = Date.parse(attempt.acceptedAt || attempt.createdAt);
58267
58493
  if (!eventTimestamp || eventTimestamp > nowMs + 2e3 || nowMs - eventTimestamp > AUTHORITATIVE_COMPLETION_MAX_AGE_MS) return false;
58268
58494
  if (Number.isFinite(acceptedAt) && eventTimestamp < acceptedAt - 2e3) return false;
@@ -71432,6 +71658,7 @@ ${lastSnapshot}`;
71432
71658
  DEFAULT_ACTIVE_CHAT_POLL_STATUSES: () => DEFAULT_ACTIVE_CHAT_POLL_STATUSES,
71433
71659
  DEFAULT_CDP_DISCOVERY_INTERVAL_MS: () => DEFAULT_CDP_DISCOVERY_INTERVAL_MS,
71434
71660
  DEFAULT_CDP_SCAN_INTERVAL_MS: () => DEFAULT_CDP_SCAN_INTERVAL_MS,
71661
+ DEFAULT_CHAT_TAIL_MISSING_SESSION_POLICY: () => DEFAULT_CHAT_TAIL_MISSING_SESSION_POLICY,
71435
71662
  DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS: () => DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS2,
71436
71663
  DEFAULT_DAEMON_PORT: () => DEFAULT_DAEMON_PORT2,
71437
71664
  DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS: () => DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS,
@@ -71598,6 +71825,7 @@ ${lastSnapshot}`;
71598
71825
  createSessionDelivery: () => createSessionDelivery,
71599
71826
  createWorktree: () => createWorktree,
71600
71827
  daemonIdsEquivalent: () => daemonIdsEquivalent,
71828
+ decideMissingSessionAttempt: () => decideMissingSessionAttempt2,
71601
71829
  delegatedWorkerAutoApproveSettings: () => delegatedWorkerAutoApproveSettings,
71602
71830
  deleteDirectDispatchesByTaskId: () => deleteDirectDispatchesByTaskId,
71603
71831
  deleteMesh: () => deleteMesh,
@@ -71695,6 +71923,7 @@ ${lastSnapshot}`;
71695
71923
  isManagedStatusWorking: () => isManagedStatusWorking,
71696
71924
  isMeshHostOwner: () => isMeshHostOwner,
71697
71925
  isMeshNodeHealthLaunchable: () => isMeshNodeHealthLaunchable,
71926
+ isMissingLiveSessionResult: () => isMissingLiveSessionResult2,
71698
71927
  isOperatingNoteTombstoned: () => isOperatingNoteTombstoned,
71699
71928
  isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
71700
71929
  isPathInside: () => isPathInside,
@@ -71797,6 +72026,7 @@ ${lastSnapshot}`;
71797
72026
  recordDebugTrace: () => recordDebugTrace,
71798
72027
  recordDirectDispatchTask: () => recordDirectDispatchTask,
71799
72028
  recordMeshToolCall: () => recordMeshToolCall,
72029
+ recordMissingSessionAttempt: () => recordMissingSessionAttempt2,
71800
72030
  registerExtensionProviders: () => registerExtensionProviders,
71801
72031
  registerMeshCoordinator: () => registerMeshCoordinator,
71802
72032
  removeMagiKindPanel: () => removeMagiKindPanel,
@@ -71811,6 +72041,7 @@ ${lastSnapshot}`;
71811
72041
  resetState: () => resetState,
71812
72042
  resolveAllowSendKeysDestructive: () => resolveAllowSendKeysDestructive,
71813
72043
  resolveAutoConvergeCodeChange: () => resolveAutoConvergeCodeChange,
72044
+ resolveBackoffMs: () => resolveBackoffMs,
71814
72045
  resolveChatMessageKind: () => resolveChatMessageKind,
71815
72046
  resolveConvergeRequiredTags: () => resolveConvergeRequiredTags,
71816
72047
  resolveCurrentGlobalInstallSurface: () => resolveCurrentGlobalInstallSurface,
@@ -71856,6 +72087,7 @@ ${lastSnapshot}`;
71856
72087
  shouldAutoRestoreHostedSessionsOnStartup: () => shouldAutoRestoreHostedSessionsOnStartup2,
71857
72088
  shouldCollectTraceCategory: () => shouldCollectTraceCategory,
71858
72089
  shouldFireIdleReminder: () => shouldFireIdleReminder,
72090
+ shouldWarnForMissingSession: () => shouldWarnForMissingSession2,
71859
72091
  shutdownDaemonComponents: () => shutdownDaemonComponents2,
71860
72092
  spawnDetachedDaemonUpgradeHelper: () => spawnDetachedDaemonUpgradeHelper,
71861
72093
  startDaemonDevSupport: () => startDaemonDevSupport2,
@@ -73532,6 +73764,12 @@ ${lastSnapshot}`;
73532
73764
  ambiguous: identities.length > 1 || !!remotes.find((remote) => remote.identities.length > 1)
73533
73765
  };
73534
73766
  }
73767
+ async function deriveLocalRepoIdentity(repoRoot) {
73768
+ const raw = await git(repoRoot, ["rev-list", "--max-parents=0", "HEAD"], true);
73769
+ const roots = raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line)).sort();
73770
+ const root = roots[0];
73771
+ return root ? `local/${root.toLowerCase()}` : "";
73772
+ }
73535
73773
  async function resolveDefaultBranch(repoRoot, remoteName, currentBranch) {
73536
73774
  const symbolic = await git(repoRoot, ["symbolic-ref", "--quiet", "--short", `refs/remotes/${remoteName}/HEAD`], true);
73537
73775
  if (symbolic.startsWith(`${remoteName}/`)) return symbolic.slice(remoteName.length + 1);
@@ -73619,12 +73857,34 @@ ${lastSnapshot}`;
73619
73857
  );
73620
73858
  }
73621
73859
  const remoteDiscovery = await discoverRemotes(repoRoot, currentBranch);
73622
- if (!remoteDiscovery.remotes.length || !remoteDiscovery.selectedRemote) {
73860
+ if (!remoteDiscovery.remotes.length) {
73861
+ const localIdentity = await deriveLocalRepoIdentity(repoRoot);
73862
+ if (!localIdentity) {
73863
+ return failure2(
73864
+ "no_commits_for_local_identity",
73865
+ "This repository has no remote and no commits, so a stable repository identity cannot be derived.",
73866
+ "Create at least one commit (the root commit becomes the local identity), or pass an explicit repo identity when creating the mesh.",
73867
+ { discovery: partial2 }
73868
+ );
73869
+ }
73870
+ const localDefaultBranch = await resolveDefaultBranch(repoRoot, "", currentBranch);
73871
+ return {
73872
+ ...partial2,
73873
+ remotes: [],
73874
+ origin: void 0,
73875
+ upstream: void 0,
73876
+ selectedRemote: "",
73877
+ repoIdentity: localIdentity,
73878
+ defaultBranch: localDefaultBranch
73879
+ };
73880
+ }
73881
+ if (!remoteDiscovery.selectedRemote) {
73882
+ const detail = remoteDiscovery.remotes.map((remote) => remote.name).join(", ");
73623
73883
  return failure2(
73624
- "remote_not_found",
73625
- "No Git remote is configured, so a stable repository identity cannot be inferred.",
73626
- "Add a canonical remote (normally origin), or keep using the explicit mesh_create API with a reviewed repo identity.",
73627
- { discovery: partial2 }
73884
+ "remote_not_selected",
73885
+ `No canonical remote could be selected among multiple remotes (${detail}).`,
73886
+ "Name one of them origin or upstream, set the current branch to track one of them, or pass an explicit repo identity when creating the mesh.",
73887
+ { discovery: { ...partial2, remotes: remoteDiscovery.remotes, origin: remoteDiscovery.origin, upstream: remoteDiscovery.upstream } }
73628
73888
  );
73629
73889
  }
73630
73890
  if (remoteDiscovery.ambiguous) {
@@ -83789,8 +84049,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
83789
84049
  if (sinceTs > 0) {
83790
84050
  return { success: true, logs: [], totalBuffered: 0 };
83791
84051
  }
83792
- if (fs14.existsSync(LOG_PATH)) {
83793
- const content = fs14.readFileSync(LOG_PATH, "utf-8");
84052
+ const logPath = getCurrentDaemonLogPath();
84053
+ if (fs14.existsSync(logPath)) {
84054
+ const content = fs14.readFileSync(logPath, "utf-8");
83794
84055
  const allLines = content.split("\n");
83795
84056
  const recent = allLines.slice(-count).join("\n");
83796
84057
  return { success: true, logs: recent, totalLines: allLines.length };
@@ -84099,11 +84360,17 @@ ${formatManifestValidationIssues2(validation.issues)}`,
84099
84360
  var http2 = __toESM2(require("http"));
84100
84361
  var path21 = __toESM2(require("path"));
84101
84362
  var import_child_process6 = require("child_process");
84363
+ init_logger();
84364
+ function errorText(error48) {
84365
+ return error48 instanceof Error ? error48.message : String(error48);
84366
+ }
84367
+ var warnedCommandLineFailures = /* @__PURE__ */ new Set();
84102
84368
  function defaultExecFileSync() {
84103
84369
  return import_child_process6.execFileSync;
84104
84370
  }
84105
84371
  function getWindowsProcessCommandLine(pid, exec7) {
84106
84372
  const pidFilter = `ProcessId=${pid}`;
84373
+ const failures = [];
84107
84374
  try {
84108
84375
  const psOut = exec7("powershell.exe", [
84109
84376
  "-NoProfile",
@@ -84115,7 +84382,9 @@ ${formatManifestValidationIssues2(validation.issues)}`,
84115
84382
  ], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
84116
84383
  const text = String(psOut).trim();
84117
84384
  if (text) return text;
84118
- } catch {
84385
+ failures.push("powershell Get-CimInstance returned no CommandLine");
84386
+ } catch (error48) {
84387
+ failures.push(`powershell Get-CimInstance failed: ${errorText(error48)}`);
84119
84388
  }
84120
84389
  try {
84121
84390
  const wmicOut = exec7("wmic", [
@@ -84127,7 +84396,19 @@ ${formatManifestValidationIssues2(validation.issues)}`,
84127
84396
  ], { encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
84128
84397
  const text = String(wmicOut).trim();
84129
84398
  if (text) return text;
84130
- } catch {
84399
+ failures.push("wmic returned no CommandLine");
84400
+ } catch (error48) {
84401
+ failures.push(`wmic failed: ${errorText(error48)}`);
84402
+ }
84403
+ const signature = failures.join("; ");
84404
+ if (!warnedCommandLineFailures.has(signature)) {
84405
+ warnedCommandLineFailures.add(signature);
84406
+ LOG2.warn(
84407
+ "ProcessLifecycle",
84408
+ `Could not read a process command line (pid ${pid}): ${signature}. Process-identity checks that depend on it will fail safe. This is logged once per distinct cause.`
84409
+ );
84410
+ } else {
84411
+ LOG2.debug("ProcessLifecycle", `Could not read the command line of pid ${pid}: ${signature}`);
84131
84412
  }
84132
84413
  return null;
84133
84414
  }
@@ -84145,7 +84426,8 @@ ${formatManifestValidationIssues2(validation.issues)}`,
84145
84426
  stdio: ["ignore", "pipe", "ignore"]
84146
84427
  })).trim();
84147
84428
  return text || null;
84148
- } catch {
84429
+ } catch (error48) {
84430
+ LOG2.debug("ProcessLifecycle", `ps lookup failed for pid ${pid}: ${errorText(error48)}`);
84149
84431
  return null;
84150
84432
  }
84151
84433
  }
@@ -84370,7 +84652,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
84370
84652
  function packageRootForPrefix(prefix, packageName) {
84371
84653
  return path21.join(prefix, "node_modules", ...packageName.split("/"));
84372
84654
  }
84373
- var CONPTY_PREBUILD_RELATIVE_PATH = path21.join(
84655
+ var CONPTY_PREBUILD_NESTED_RELATIVE_PATH = path21.join(
84374
84656
  "node_modules",
84375
84657
  "adhdev",
84376
84658
  "node_modules",
@@ -84379,16 +84661,28 @@ ${formatManifestValidationIssues2(validation.issues)}`,
84379
84661
  "win32-x64",
84380
84662
  "conpty.node"
84381
84663
  );
84382
- function resolveStagedConptyPrebuildPath(stagedPrefix) {
84383
- return path21.join(stagedPrefix, CONPTY_PREBUILD_RELATIVE_PATH);
84664
+ var CONPTY_PREBUILD_HOISTED_RELATIVE_PATH = path21.join(
84665
+ "node_modules",
84666
+ "node-pty",
84667
+ "prebuilds",
84668
+ "win32-x64",
84669
+ "conpty.node"
84670
+ );
84671
+ function resolveConptyPrebuildCandidates(prefix) {
84672
+ return [
84673
+ path21.join(prefix, CONPTY_PREBUILD_NESTED_RELATIVE_PATH),
84674
+ path21.join(prefix, CONPTY_PREBUILD_HOISTED_RELATIVE_PATH)
84675
+ ];
84384
84676
  }
84385
- function verifyStagedConptyPrebuild(stagedPrefix) {
84386
- const conptyPath = resolveStagedConptyPrebuildPath(stagedPrefix);
84387
- if (!fs15.existsSync(conptyPath)) {
84677
+ function verifyStagedConptyPrebuild(stagedPrefix, log) {
84678
+ const candidates = resolveConptyPrebuildCandidates(stagedPrefix);
84679
+ const found = candidates.find((candidate) => fs15.existsSync(candidate));
84680
+ if (!found) {
84388
84681
  throw new Error(
84389
- `Staged install is missing required native addon: ${conptyPath}. Aborting activation to prevent a daemon boot crash.`
84682
+ `Staged install is missing required native addon: node-pty's conpty.node prebuild (checked: ${candidates.join(", ")}). Aborting activation to prevent a daemon boot crash.`
84390
84683
  );
84391
84684
  }
84685
+ log?.(`conpty prebuild verified at ${found}`);
84392
84686
  }
84393
84687
  function readPackageCliEntry(prefix, packageName, targetVersion) {
84394
84688
  const packageRoot = packageRootForPrefix(prefix, packageName);
@@ -84544,6 +84838,29 @@ exec "${portableNode}" "${cliEntry}" "$@"
84544
84838
  }
84545
84839
  atomicWrite(layout.pointerPath, versionName, "ascii");
84546
84840
  }
84841
+ function removeFailedStagedPrefix(stagedPrefix, layout, log) {
84842
+ try {
84843
+ const staged = normalizeForCompare(stagedPrefix);
84844
+ const protectedPrefixes = [layout.activePrefix, layout.stablePrefix, layout.installRoot];
84845
+ if (protectedPrefixes.some((candidate) => normalizeForCompare(candidate) === staged)) {
84846
+ log(`Refusing to clean up ${stagedPrefix}: it is the active/stable install, not a staged prefix`);
84847
+ return;
84848
+ }
84849
+ if (normalizeForCompare(path21.dirname(stagedPrefix)) !== normalizeForCompare(layout.installRoot) || !path21.basename(stagedPrefix).startsWith("version-")) {
84850
+ log(`Refusing to clean up ${stagedPrefix}: not a version-* prefix under ${layout.installRoot}`);
84851
+ return;
84852
+ }
84853
+ if (!fs15.existsSync(stagedPrefix)) return;
84854
+ removeInactivePrefix(stagedPrefix, log);
84855
+ if (fs15.existsSync(stagedPrefix)) {
84856
+ log(`Failed staged prefix ${stagedPrefix} could not be fully removed; a future update will retry`);
84857
+ } else {
84858
+ log(`Cleaned up failed staged prefix ${stagedPrefix}`);
84859
+ }
84860
+ } catch (error48) {
84861
+ log(`Failed staged-prefix cleanup errored for ${stagedPrefix}: ${error48?.message || String(error48)}`);
84862
+ }
84863
+ }
84547
84864
  async function performWindowsAtomicUpgrade(options) {
84548
84865
  const { layout, packageName, targetVersion, portableNode, hooks } = options;
84549
84866
  const versionName = `version-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}`;
@@ -84555,7 +84872,7 @@ exec "${portableNode}" "${cliEntry}" "$@"
84555
84872
  try {
84556
84873
  hooks.log(`Installing ${packageName}@${targetVersion} into inactive prefix ${stagedPrefix}`);
84557
84874
  await hooks.install(stagedPrefix, portableNode);
84558
- verifyStagedConptyPrebuild(stagedPrefix);
84875
+ verifyStagedConptyPrebuild(stagedPrefix, hooks.log);
84559
84876
  const stagedCliEntry = readPackageCliEntry(stagedPrefix, packageName, targetVersion);
84560
84877
  pinStagedShims(stagedPrefix, portableNode, stagedCliEntry);
84561
84878
  validateStagedCli(portableNode, stagedCliEntry, targetVersion);
@@ -84599,6 +84916,7 @@ exec "${portableNode}" "${cliEntry}" "$@"
84599
84916
  } catch {
84600
84917
  hooks.log("Failed to restart the previous daemon during rollback");
84601
84918
  }
84919
+ removeFailedStagedPrefix(stagedPrefix, layout, hooks.log);
84602
84920
  try {
84603
84921
  await hooks.cleanup(layout, layout.activePrefix);
84604
84922
  } catch {
@@ -84882,6 +85200,8 @@ exec "${portableNode}" "${cliEntry}" "$@"
84882
85200
  const current = env2[pathKey] || "";
84883
85201
  env2[pathKey] = current ? `${nodeBinDir};${current}` : nodeBinDir;
84884
85202
  env2.ADHDEV_BOOTSTRAP = "1";
85203
+ env2.npm_config_build_from_source = "false";
85204
+ env2["npm_config_build-from-source"] = "false";
84885
85205
  return env2;
84886
85206
  }
84887
85207
  function getNpmExecOptions(platform10 = process.platform) {
@@ -85233,6 +85553,21 @@ ${body}
85233
85553
  if (installOutput.trim()) {
85234
85554
  appendUpgradeLog(installOutput.trim());
85235
85555
  }
85556
+ if (process.platform === "win32" && installCommand.surface.installPrefix) {
85557
+ try {
85558
+ verifyStagedConptyPrebuild(installCommand.surface.installPrefix, appendUpgradeLog);
85559
+ } catch (error48) {
85560
+ appendUpgradeLog(`Post-install conpty verification failed: ${error48?.message || String(error48)}`);
85561
+ emitUpgradeFailureNotice([
85562
+ `adhdev ${spec} installed but is missing node-pty's native addon (conpty.node).`,
85563
+ 'Starting it would break every session with "Failed to load native module: conpty.node",',
85564
+ "so the running daemon was left on its previous version and was NOT restarted.",
85565
+ "To recover, reinstall (this forces the shipped prebuild instead of a source rebuild):",
85566
+ ` ${buildManualRecoveryCommand(installCommand)}`
85567
+ ]);
85568
+ throw error48;
85569
+ }
85570
+ }
85236
85571
  if (process.platform === "win32") {
85237
85572
  await new Promise((resolve29) => setTimeout(resolve29, 500));
85238
85573
  cleanupStaleGlobalInstallDirs(payload.packageName, installCommand.surface);
@@ -86321,6 +86656,7 @@ ${body}
86321
86656
  return n;
86322
86657
  }
86323
86658
  var SUBMIT_DELAY_FLOOR_MS = 200;
86659
+ var fsmDriverSeq = 0;
86324
86660
  var WIN32_SUBMIT_RESEND_GAP_MS = 350;
86325
86661
  var WIN32_SUBMIT_MAX_RESENDS = 14;
86326
86662
  var WIN32_SUBMIT_SETTLE_MS = 500;
@@ -86353,6 +86689,8 @@ ${body}
86353
86689
  var FsmDriver = class {
86354
86690
  constructor(opts) {
86355
86691
  this.opts = opts;
86692
+ const sessionId = typeof opts.sessionId === "string" ? opts.sessionId.trim() : "";
86693
+ this.sessionTag = sessionId ? sessionId.slice(0, 8) : `d${++fsmDriverSeq}`;
86356
86694
  this.loadSpecOrThrow();
86357
86695
  this.adapter = new TerminalAdapter(
86358
86696
  this.buildAdapterOpts(),
@@ -86442,6 +86780,8 @@ ${body}
86442
86780
  * the shadow verdict currently disagrees with the real one), so the
86443
86781
  * shadow log emits on FLIP only, not every frame. */
86444
86782
  shadowDivergenceLast = /* @__PURE__ */ new Map();
86783
+ /** FSMLOG-SESSION-ATTRIBUTION (D3): session segment of every log line's prefix. */
86784
+ sessionTag;
86445
86785
  subscribe(listener) {
86446
86786
  this.listeners.add(listener);
86447
86787
  return () => {
@@ -87405,8 +87745,16 @@ ${body}
87405
87745
  });
87406
87746
  if (this.stateHistory.length > 50) this.stateHistory.shift();
87407
87747
  }
87748
+ /**
87749
+ * FSMLOG-SESSION-ATTRIBUTION (D3): log prefix identifying BOTH the spec being driven and the
87750
+ * session driving it. Previously spec-only, which made every concurrent session of the same
87751
+ * provider log under an identical tag. The session segment is the owning instance's session id
87752
+ * (short form — the leading 8 chars are what mesh ledger/trace lines carry, so logs grep-join
87753
+ * against them), or a per-driver `d<n>` fallback when no session id was supplied.
87754
+ */
87408
87755
  specTag() {
87409
- return this.opts.specPath.split("/").slice(-3).join("/");
87756
+ const spec = this.opts.specPath.split(/[/\\]/).slice(-3).join("/");
87757
+ return `${spec}|${this.sessionTag}`;
87410
87758
  }
87411
87759
  emit(ev) {
87412
87760
  for (const l of this.listeners) {
@@ -87583,7 +87931,7 @@ ${body}
87583
87931
  * cli-state-engine's lastApprovalResolvedAt for the spec-driven adapter path
87584
87932
  * (claude-cli specs/4.0.json), which previously stubbed the method to false. */
87585
87933
  lastApprovalResolvedAt = 0;
87586
- constructor(specPath, workingDir, cliArgs, extraEnv, transportFactory) {
87934
+ constructor(specPath, workingDir, cliArgs, extraEnv, transportFactory, sessionId) {
87587
87935
  const raw = JSON.parse(fs222.readFileSync(specPath, "utf8"));
87588
87936
  this.spec = {
87589
87937
  id: raw.id,
@@ -87602,7 +87950,8 @@ ${body}
87602
87950
  hotReload: true,
87603
87951
  emitTrace: false,
87604
87952
  transportFactory,
87605
- extraCliArgs: cliArgs
87953
+ extraCliArgs: cliArgs,
87954
+ sessionId
87606
87955
  });
87607
87956
  this.driver.subscribe((ev) => this.handleEvent(ev));
87608
87957
  }
@@ -88592,7 +88941,7 @@ ${body}
88592
88941
  }
88593
88942
  };
88594
88943
  init_logger();
88595
- function createCliAdapter(provider, workingDir, cliArgs, extraEnv, transportFactory) {
88944
+ function createCliAdapter(provider, workingDir, cliArgs, extraEnv, transportFactory, sessionId) {
88596
88945
  const resolvedSpecPath = provider._resolvedSpecPath;
88597
88946
  const dir = provider._resolvedProviderDir;
88598
88947
  let specPath = resolvedSpecPath && fs23.existsSync(resolvedSpecPath) ? resolvedSpecPath : void 0;
@@ -88603,7 +88952,7 @@ ${body}
88603
88952
  if (specPath) {
88604
88953
  try {
88605
88954
  LOG2.info("spec-route", `[${provider.type}] routing through SpecCliAdapter (${path27.relative(dir || "", specPath) || specPath})`);
88606
- return new SpecCliAdapter(specPath, workingDir, cliArgs, extraEnv, transportFactory);
88955
+ return new SpecCliAdapter(specPath, workingDir, cliArgs, extraEnv, transportFactory, sessionId);
88607
88956
  } catch (err) {
88608
88957
  LOG2.warn("spec-route", `[${provider.type}] spec invalid, falling back to ProviderCliAdapter: ${err.message}`);
88609
88958
  }
@@ -89005,7 +89354,7 @@ ${body}
89005
89354
  this.launchMode = options?.launchMode || "new";
89006
89355
  this.initialThinkingLevel = options?.initialThinkingLevel;
89007
89356
  this.onProviderSessionResolved = options?.onProviderSessionResolved;
89008
- this.adapter = createCliAdapter(provider, workingDir, cliArgs, options?.extraEnv || {}, transportFactory);
89357
+ this.adapter = createCliAdapter(provider, workingDir, cliArgs, options?.extraEnv || {}, transportFactory, this.instanceId);
89009
89358
  if (this.providerSessionId) {
89010
89359
  this.adapter.updateRuntimeMeta({ providerSessionId: this.providerSessionId });
89011
89360
  }
@@ -99126,19 +99475,37 @@ Run 'adhdev doctor' for detailed diagnostics.`
99126
99475
  * Resolve provider type by alias
99127
99476
  * 'claude' → 'claude-cli', 'codex' → 'codex-cli' etc
99128
99477
  * Returns input as-is if no match found.
99478
+ *
99479
+ * `categories` narrows resolution to the given provider categories. Without it
99480
+ * the resolution order is unchanged (direct type match first, then alias scan)
99481
+ * — every existing caller keeps its exact behaviour.
99482
+ *
99483
+ * The hint exists because provider types and aliases share one namespace across
99484
+ * categories, so a direct match can shadow an alias that a category-scoped
99485
+ * caller actually wants. Concretely: `extension/codex` declares `type: 'codex'`
99486
+ * while `cli/codex-cli` declares `aliases: ['codex']`, so unscoped
99487
+ * `resolveAlias('codex')` returns the IDE-webview provider. `adhdev launch`
99488
+ * only ever starts a cli/acp session, so it passes `['cli', 'acp']` and gets
99489
+ * `codex-cli`. Within a scope the direct-match-first order still holds.
99129
99490
  */
99130
- resolveAlias(input) {
99131
- if (this.providers.has(input)) return input;
99491
+ resolveAlias(input, categories) {
99492
+ const inScope = (p) => !!p && (!categories || categories.includes(p.category));
99493
+ const direct = this.providers.get(input);
99494
+ if (inScope(direct)) return input;
99132
99495
  for (const p of this.providers.values()) {
99133
- if (p.aliases?.includes(input)) return p.type;
99496
+ if (p.aliases?.includes(input) && inScope(p)) return p.type;
99134
99497
  }
99135
99498
  return input;
99136
99499
  }
99137
99500
  /**
99138
99501
  * Get provider with alias resolution (get + alias fallback)
99502
+ * `categories` narrows resolution the same way as `resolveAlias`, and also
99503
+ * filters the returned module so an out-of-scope provider is never handed back.
99139
99504
  */
99140
- getByAlias(input) {
99141
- return this.providers.get(this.resolveAlias(input));
99505
+ getByAlias(input, categories) {
99506
+ const resolved = this.providers.get(this.resolveAlias(input, categories));
99507
+ if (resolved && categories && !categories.includes(resolved.category)) return void 0;
99508
+ return resolved;
99142
99509
  }
99143
99510
  /**
99144
99511
  * Build CLI/ACP detection list (replaces cli-detector)
@@ -104744,12 +105111,12 @@ ${ptyResult.output.slice(-2e3)}`);
104744
105111
  var fs37 = __toESM2(require("fs"));
104745
105112
  var path43 = __toESM2(require("path"));
104746
105113
  var os26 = __toESM2(require("os"));
104747
- var ADHDEV_HOME2 = process.env.ADHDEV_CONFIG_DIR && process.env.ADHDEV_CONFIG_DIR.trim() ? process.env.ADHDEV_CONFIG_DIR.trim() : path43.join(os26.homedir(), ".adhdev");
104748
- var LOG_DIR2 = path43.join(ADHDEV_HOME2, "logs");
105114
+ var ADHDEV_HOME = process.env.ADHDEV_CONFIG_DIR && process.env.ADHDEV_CONFIG_DIR.trim() ? process.env.ADHDEV_CONFIG_DIR.trim() : path43.join(os26.homedir(), ".adhdev");
105115
+ var LOG_DIR = path43.join(ADHDEV_HOME, "logs");
104749
105116
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
104750
105117
  var MAX_DAYS = 7;
104751
105118
  try {
104752
- fs37.mkdirSync(LOG_DIR2, { recursive: true });
105119
+ fs37.mkdirSync(LOG_DIR, { recursive: true });
104753
105120
  } catch {
104754
105121
  }
104755
105122
  var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
@@ -104783,19 +105150,19 @@ ${ptyResult.output.slice(-2e3)}`);
104783
105150
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
104784
105151
  }
104785
105152
  var currentDate2 = getDateStr2();
104786
- var currentFile = path43.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
105153
+ var currentFile = path43.join(LOG_DIR, `commands-${currentDate2}.jsonl`);
104787
105154
  var writeCount2 = 0;
104788
105155
  function checkRotation() {
104789
105156
  const today = getDateStr2();
104790
105157
  if (today !== currentDate2) {
104791
105158
  currentDate2 = today;
104792
- currentFile = path43.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
105159
+ currentFile = path43.join(LOG_DIR, `commands-${currentDate2}.jsonl`);
104793
105160
  cleanOldFiles();
104794
105161
  }
104795
105162
  }
104796
105163
  function cleanOldFiles() {
104797
105164
  try {
104798
- const files = fs37.readdirSync(LOG_DIR2).filter((f) => f.startsWith("commands-") && f.endsWith(".jsonl"));
105165
+ const files = fs37.readdirSync(LOG_DIR).filter((f) => f.startsWith("commands-") && f.endsWith(".jsonl"));
104799
105166
  const cutoff = /* @__PURE__ */ new Date();
104800
105167
  cutoff.setDate(cutoff.getDate() - MAX_DAYS);
104801
105168
  const cutoffStr = cutoff.toISOString().slice(0, 10);
@@ -104803,7 +105170,7 @@ ${ptyResult.output.slice(-2e3)}`);
104803
105170
  const dateMatch = file2.match(/commands-(\d{4}-\d{2}-\d{2})/);
104804
105171
  if (dateMatch && dateMatch[1] < cutoffStr) {
104805
105172
  try {
104806
- fs37.unlinkSync(path43.join(LOG_DIR2, file2));
105173
+ fs37.unlinkSync(path43.join(LOG_DIR, file2));
104807
105174
  } catch {
104808
105175
  }
104809
105176
  }
@@ -110323,6 +110690,53 @@ ${e?.stderr || ""}`;
110323
110690
  });
110324
110691
  await Promise.all(runners);
110325
110692
  }
110693
+ var DEFAULT_CHAT_TAIL_MISSING_SESSION_POLICY = {
110694
+ // 10s of normal-cadence retries: comfortably longer than a session-registry
110695
+ // attach, short enough that a genuinely dead session starts backing off well
110696
+ // before it can produce a meaningful number of warns.
110697
+ graceMs: 1e4,
110698
+ initialBackoffMs: 1e3,
110699
+ // 30s ceiling — matches the idle status heartbeat, so a lingering
110700
+ // subscription costs at most one read per heartbeat instead of ~20/sec.
110701
+ maxBackoffMs: 3e4,
110702
+ // 5 min. Past this the session is not coming back under this subscription;
110703
+ // the dashboard re-subscribes on reopen if it ever does.
110704
+ giveUpAfterMs: 5 * 6e4
110705
+ };
110706
+ function decideMissingSessionAttempt2(state, now, policy = DEFAULT_CHAT_TAIL_MISSING_SESSION_POLICY) {
110707
+ if (!state) return { action: "attempt" };
110708
+ const missingFor = now - state.firstMissingAt;
110709
+ if (missingFor >= policy.giveUpAfterMs) return { action: "drop" };
110710
+ if (missingFor < policy.graceMs) return { action: "attempt" };
110711
+ const sinceLastAttempt = now - state.lastAttemptAt;
110712
+ return sinceLastAttempt >= resolveBackoffMs(state, policy) ? { action: "attempt" } : { action: "skip" };
110713
+ }
110714
+ function resolveBackoffMs(state, policy = DEFAULT_CHAT_TAIL_MISSING_SESSION_POLICY) {
110715
+ const stepsIntoBackoff = Math.max(0, state.consecutiveMisses - 1);
110716
+ const boundedExponent = Math.min(stepsIntoBackoff, 20);
110717
+ const scaled = policy.initialBackoffMs * Math.pow(2, boundedExponent);
110718
+ return Math.min(scaled, policy.maxBackoffMs);
110719
+ }
110720
+ function recordMissingSessionAttempt2(state, now) {
110721
+ if (!state) {
110722
+ return { firstMissingAt: now, lastAttemptAt: now, consecutiveMisses: 1, warned: false };
110723
+ }
110724
+ return {
110725
+ firstMissingAt: state.firstMissingAt,
110726
+ lastAttemptAt: now,
110727
+ consecutiveMisses: state.consecutiveMisses + 1,
110728
+ warned: state.warned
110729
+ };
110730
+ }
110731
+ function shouldWarnForMissingSession2(state) {
110732
+ return !state.warned;
110733
+ }
110734
+ function isMissingLiveSessionResult2(result) {
110735
+ if (!result || typeof result !== "object") return false;
110736
+ const { success: success2, error: error48 } = result;
110737
+ if (success2 !== false || typeof error48 !== "string") return false;
110738
+ return error48.startsWith("Live session not found for targetSessionId:");
110739
+ }
110326
110740
  init_control_effects();
110327
110741
  init_provider_patch_state();
110328
110742
  init_chat_message_normalization();
@@ -117151,6 +117565,7 @@ data: ${JSON.stringify(msg.data)}
117151
117565
  const appName = options.appName;
117152
117566
  const timeoutMs = options.timeoutMs ?? DEFAULT_SESSION_HOST_READY_TIMEOUT_MS2;
117153
117567
  const isManagedPid = options.isManagedPid ?? (() => true);
117568
+ let unverifiedStopPid = null;
117154
117569
  const instance = () => getProcessInstanceContext();
117155
117570
  const getEndpoint = () => (0, import_session_host_core15.getDefaultSessionHostEndpoint)(appName, { ipcKey: instance().ipcKey });
117156
117571
  function buildEnv(baseEnv) {
@@ -117228,8 +117643,25 @@ data: ${JSON.stringify(msg.data)}
117228
117643
  );
117229
117644
  return process.execPath;
117230
117645
  }
117646
+ function verifyConptyPrebuildBeforeSpawn(entry) {
117647
+ if (process.platform !== "win32") return;
117648
+ const normalized = entry.replace(/\\/g, "/");
117649
+ const marker = "/node_modules/adhdev/vendor/session-host-daemon/";
117650
+ const markerIndex = normalized.lastIndexOf(marker);
117651
+ if (markerIndex === -1) return;
117652
+ const activePrefix = entry.slice(0, markerIndex);
117653
+ const candidates = resolveConptyPrebuildCandidates(activePrefix);
117654
+ const found = candidates.find((candidate) => fs47.existsSync(candidate));
117655
+ if (!found) {
117656
+ throw new Error(
117657
+ `conpty.node missing at boot despite passing the install-time gate \u2014 likely deleted post-install (checked: ${candidates.join(", ")}). Every session-host spawn would crash requiring node-pty; refusing to spawn.`
117658
+ );
117659
+ }
117660
+ LOG2.info("SessionHost", `conpty prebuild verified before spawn at ${found}`);
117661
+ }
117231
117662
  function spawnHost() {
117232
117663
  const entry = resolveEntry();
117664
+ verifyConptyPrebuildBeforeSpawn(entry);
117233
117665
  const nodeExecutable = resolveSessionHostNode();
117234
117666
  let stdio = "ignore";
117235
117667
  let logFd = null;
@@ -117282,7 +117714,21 @@ data: ${JSON.stringify(msg.data)}
117282
117714
  if (existingPid !== null) {
117283
117715
  const runningPath = getRunningSessionHostScriptPath(existingPid);
117284
117716
  const currentEntry = resolveEntry();
117285
- if (runningPath && !pathsEquivalent(runningPath, currentEntry)) {
117717
+ if (runningPath === null) {
117718
+ if (unverifiedStopPid !== existingPid) {
117719
+ unverifiedStopPid = existingPid;
117720
+ LOG2.warn(
117721
+ "SessionHost",
117722
+ `Could not read the command line of host pid ${existingPid}; cannot prove it runs from ${currentEntry}. Stopping it once and respawning rather than risking reuse of a stale-prefix host.`
117723
+ );
117724
+ stopManagedSessionHostProcess();
117725
+ } else {
117726
+ LOG2.warn(
117727
+ "SessionHost",
117728
+ `Host pid ${existingPid} is still unverifiable; leaving it alone (already restarted once this process) to avoid a kill/respawn loop.`
117729
+ );
117730
+ }
117731
+ } else if (!pathsEquivalent(runningPath, currentEntry)) {
117286
117732
  LOG2.warn(
117287
117733
  "SessionHost",
117288
117734
  `Detected stale host pid ${existingPid} running from ${runningPath}; restarting from ${currentEntry}`
@@ -120524,6 +120970,20 @@ var StandaloneServer = class {
120524
120970
  ...request.historySessionId ? { historySessionId: request.historySessionId } : {},
120525
120971
  ...state.cursor.tailLimit > 0 ? { tailLimit: state.cursor.tailLimit } : {}
120526
120972
  });
120973
+ if ((0, import_daemon_core2.isMissingLiveSessionResult)(result)) {
120974
+ const now = Date.now();
120975
+ const missing = (0, import_daemon_core2.recordMissingSessionAttempt)(state.missingSession, now);
120976
+ state.missingSession = missing;
120977
+ const message = `[chat_tail] session ${request.targetSessionId} is not in the live registry`;
120978
+ if ((0, import_daemon_core2.shouldWarnForMissingSession)(missing)) {
120979
+ missing.warned = true;
120980
+ import_daemon_core2.LOG.warn("Standalone", `${message} \u2014 backing off, and dropping the subscription if it stays absent`);
120981
+ } else {
120982
+ import_daemon_core2.LOG.debug("Standalone", `${message} (miss #${missing.consecutiveMisses})`);
120983
+ }
120984
+ return null;
120985
+ }
120986
+ if (state.missingSession) state.missingSession = void 0;
120527
120987
  const prepared = (0, import_daemon_core2.prepareSessionChatTailUpdate)({
120528
120988
  key,
120529
120989
  sessionId: request.targetSessionId,
@@ -120687,6 +121147,7 @@ var StandaloneServer = class {
120687
121147
  const targets = targetWs ? [targetWs] : Array.from(this.clients);
120688
121148
  const hotSessionIds = options.onlyActive ? this.getHotChatSessionIdsForWsFlush() : null;
120689
121149
  const forceSessionIds = options.forceSessionIds ?? null;
121150
+ const now = Date.now();
120690
121151
  const tasks = [];
120691
121152
  for (const ws of targets) {
120692
121153
  if (ws.readyState !== import_ws.WebSocket.OPEN) continue;
@@ -120694,6 +121155,13 @@ var StandaloneServer = class {
120694
121155
  if (!subs || subs.size === 0) continue;
120695
121156
  for (const [key, sub] of subs.entries()) {
120696
121157
  const targetSessionId = sub.request.params.targetSessionId;
121158
+ const decision = (0, import_daemon_core2.decideMissingSessionAttempt)(sub.missingSession, now);
121159
+ if (decision.action === "skip") continue;
121160
+ if (decision.action === "drop") {
121161
+ subs.delete(key);
121162
+ import_daemon_core2.LOG.info("Standalone", `[chat_tail] subscription dropped: session=${targetSessionId} key=${key} \u2014 live session absent for ${Math.round((now - (sub.missingSession?.firstMissingAt ?? now)) / 1e3)}s`);
121163
+ continue;
121164
+ }
120697
121165
  const isForced = forceSessionIds?.has(targetSessionId) === true;
120698
121166
  if (!isForced && hotSessionIds && !hotSessionIds.active.has(targetSessionId) && !hotSessionIds.finalizing.has(targetSessionId)) {
120699
121167
  continue;