@adhdev/daemon-core 0.9.82-rc.455 → 0.9.82-rc.457

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.
Files changed (32) hide show
  1. package/dist/commands/med-family/mesh-crud.d.ts +19 -0
  2. package/dist/config/config.d.ts +14 -0
  3. package/dist/config/registry-resolver.d.ts +54 -0
  4. package/dist/index.js +369 -67
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +369 -67
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/mesh/preview-freshness.d.ts +11 -1
  9. package/dist/mesh/worktree-bootstrap-config.d.ts +9 -0
  10. package/dist/providers/approval-utils.d.ts +25 -0
  11. package/dist/providers/cli-provider-instance.d.ts +30 -1
  12. package/dist/providers/manual-attendance.d.ts +16 -0
  13. package/dist/providers/provider-instance.d.ts +8 -1
  14. package/dist/providers/provider-loader.d.ts +17 -2
  15. package/dist/providers/spec/fsm-driver.d.ts +5 -0
  16. package/package.json +3 -3
  17. package/src/boot/daemon-lifecycle.ts +2 -0
  18. package/src/commands/handler.ts +9 -5
  19. package/src/commands/low-family/daemon-lifecycle.ts +14 -1
  20. package/src/commands/med-family/mesh-crud.ts +83 -49
  21. package/src/config/config.ts +18 -0
  22. package/src/config/registry-resolver.ts +100 -0
  23. package/src/mesh/preview-freshness.ts +46 -1
  24. package/src/mesh/worktree-bootstrap-config.ts +1 -1
  25. package/src/providers/approval-utils.ts +42 -0
  26. package/src/providers/cli-provider-instance.ts +246 -13
  27. package/src/providers/manual-attendance.ts +20 -0
  28. package/src/providers/provider-instance.ts +6 -1
  29. package/src/providers/provider-loader.ts +36 -9
  30. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +13 -2
  31. package/src/providers/spec/fsm-driver.ts +49 -2
  32. package/src/commands/WINDOWS-UPGRADE-LOCK-FAILURE.md +0 -198
package/dist/index.mjs CHANGED
@@ -404,10 +404,10 @@ function readInjected(value) {
404
404
  }
405
405
  function getDaemonBuildInfo() {
406
406
  if (cached) return cached;
407
- const commit = readInjected(true ? "dba171accb2a47e4b76a157f2d6a556869ef6cdf" : void 0) ?? "unknown";
408
- const commitShort = readInjected(true ? "dba171ac" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
409
- const version = readInjected(true ? "0.9.82-rc.455" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
410
- const builtAt = readInjected(true ? "2026-07-04T05:46:41.792Z" : void 0);
407
+ const commit = readInjected(true ? "29441f596e5efe1972b874e0fb5b3d380c9d9bda" : void 0) ?? "unknown";
408
+ const commitShort = readInjected(true ? "29441f59" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
409
+ const version = readInjected(true ? "0.9.82-rc.457" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
410
+ const builtAt = readInjected(true ? "2026-07-04T08:51:27.287Z" : void 0);
411
411
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
412
412
  return cached;
413
413
  }
@@ -1585,6 +1585,8 @@ function normalizeConfig(raw) {
1585
1585
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
1586
1586
  providerSourceMode: resolveProviderSourceMode(parsed.providerSourceMode, parsed.disableUpstream),
1587
1587
  providerDir: asOptionalString(parsed.providerDir),
1588
+ registryUrl: asOptionalString(parsed.registryUrl),
1589
+ providerTarballUrl: asOptionalString(parsed.providerTarballUrl),
1588
1590
  updateChannel: parsed.updateChannel === "preview" ? "preview" : "stable",
1589
1591
  terminalSizingMode: parsed.terminalSizingMode === "fit" ? "fit" : "measured"
1590
1592
  };
@@ -8925,6 +8927,7 @@ __export(worktree_bootstrap_config_exports, {
8925
8927
  WORKTREE_BOOTSTRAP_STALE_RUNNING_MS: () => WORKTREE_BOOTSTRAP_STALE_RUNNING_MS,
8926
8928
  computeStaleInputsDigest: () => computeStaleInputsDigest,
8927
8929
  evaluateWorktreeBootstrapState: () => evaluateWorktreeBootstrapState,
8930
+ getRegisteredSubmodulePaths: () => getRegisteredSubmodulePaths,
8928
8931
  isWorktreeBootstrapStaleRunning: () => isWorktreeBootstrapStaleRunning,
8929
8932
  loadMeshWorktreeBootstrapConfig: () => loadMeshWorktreeBootstrapConfig,
8930
8933
  runMeshWorktreeBootstrap: () => runMeshWorktreeBootstrap,
@@ -18413,6 +18416,20 @@ function isNegativeApprovalLabel(value) {
18413
18416
  function hasNegativeApprovalOption(buttons) {
18414
18417
  return (buttons || []).some((button) => isNegativeApprovalLabel(String(button || "")));
18415
18418
  }
18419
+ function hasReliableApprovalAffirmative(buttons) {
18420
+ return (buttons || []).some((button) => {
18421
+ const label = normalizeApprovalLabel(String(button || ""));
18422
+ if (!label) return false;
18423
+ if (/^always allow\b/.test(label)) return true;
18424
+ if (/^yes\b/.test(label)) {
18425
+ if (/\ballow\b/.test(label)) return true;
18426
+ if (/\bask again\b/.test(label)) return true;
18427
+ if (/\bduring this session\b/.test(label)) return true;
18428
+ if (/\bfrom this project\b/.test(label)) return true;
18429
+ }
18430
+ return false;
18431
+ });
18432
+ }
18416
18433
  function getApprovalPositiveHints(provider) {
18417
18434
  const customHints = Array.isArray(provider?.approvalPositiveHints) ? provider.approvalPositiveHints.map((hint) => normalizeApprovalLabel(String(hint || ""))).filter(Boolean) : [];
18418
18435
  return customHints.length > 0 ? customHints : DEFAULT_APPROVAL_POSITIVE_HINTS;
@@ -20234,7 +20251,7 @@ function scopeLines(spec, lines, questionIndex) {
20234
20251
  }
20235
20252
  }
20236
20253
  }
20237
- if (lastSep >= 0 && prevSep >= 0) {
20254
+ if (lastSep >= 0 && prevSep >= 0 && questionIndex >= prevSep && questionIndex < lastSep + 1) {
20238
20255
  return { start: prevSep, end: lastSep + 1 };
20239
20256
  }
20240
20257
  return {
@@ -20327,7 +20344,7 @@ var init_parse_approval = __esm({
20327
20344
  "src/providers/sdk/v1/builders/cli/parse-approval.ts"() {
20328
20345
  "use strict";
20329
20346
  init_visible_region();
20330
- SEPARATOR_RE = /^(?:─|━|═|━){10,}\s*$/;
20347
+ SEPARATOR_RE = /^[─━═╌╍┄┅┈┉]{10,}\s*$/;
20331
20348
  }
20332
20349
  });
20333
20350
 
@@ -30268,6 +30285,35 @@ var DaemonCdpInitializer = class {
30268
30285
 
30269
30286
  // src/commands/handler.ts
30270
30287
  init_builders();
30288
+ init_config();
30289
+
30290
+ // src/config/registry-resolver.ts
30291
+ var DEFAULT_REGISTRY_BASE_URL = "https://api.adhf.dev/api/v1/registry";
30292
+ var DEFAULT_PROVIDER_TARBALL_URL = "https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz";
30293
+ var REGISTRY_URL_ENV_VAR = "ADHDEV_REGISTRY_URL";
30294
+ var PROVIDER_TARBALL_URL_ENV_VAR = "ADHDEV_PROVIDER_TARBALL_URL";
30295
+ function cleanString(value) {
30296
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
30297
+ }
30298
+ function stripTrailingSlashes(url) {
30299
+ return url.replace(/\/+$/, "");
30300
+ }
30301
+ function resolveRegistryBaseUrl(configuredUrl, env = process.env) {
30302
+ const resolved = cleanString(configuredUrl) ?? cleanString(env[REGISTRY_URL_ENV_VAR]) ?? DEFAULT_REGISTRY_BASE_URL;
30303
+ return stripTrailingSlashes(resolved);
30304
+ }
30305
+ function resolveProviderTarballUrl(configuredUrl, env = process.env) {
30306
+ return cleanString(configuredUrl) ?? cleanString(env[PROVIDER_TARBALL_URL_ENV_VAR]) ?? DEFAULT_PROVIDER_TARBALL_URL;
30307
+ }
30308
+ function resolveProviderTarballTarget(configuredUrl, env = process.env) {
30309
+ const url = resolveProviderTarballUrl(configuredUrl, env);
30310
+ const parsed = new URL(url);
30311
+ return {
30312
+ url,
30313
+ hostname: parsed.hostname,
30314
+ path: parsed.pathname + (parsed.search || "")
30315
+ };
30316
+ }
30271
30317
 
30272
30318
  // src/sessions/reconcile.ts
30273
30319
  function upsertSessionTarget(sessionRegistry, target) {
@@ -30381,6 +30427,10 @@ var MANUAL_ATTENDANCE_COMMANDS = /* @__PURE__ */ new Set([
30381
30427
  "resolve_action",
30382
30428
  "pty_input"
30383
30429
  ]);
30430
+ var MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS = /* @__PURE__ */ new Set([
30431
+ "select_session",
30432
+ "open_panel"
30433
+ ]);
30384
30434
 
30385
30435
  // src/chat/chat-signatures.ts
30386
30436
  function hashSignatureParts(parts) {
@@ -34863,13 +34913,14 @@ var DaemonCommandHandler = class {
34863
34913
  */
34864
34914
  noteManualAttendanceIfApplicable(cmd, args) {
34865
34915
  if (!MANUAL_ATTENDANCE_COMMANDS.has(cmd)) return;
34916
+ const passive = MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS.has(cmd);
34866
34917
  const sessionId = this._currentRoute.session?.sessionId || (typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "");
34867
34918
  if (!sessionId) return;
34868
34919
  const session = this._ctx.sessionRegistry?.get(sessionId);
34869
34920
  const instanceKey = session?.adapterKey || session?.instanceKey || sessionId;
34870
34921
  const instance = this._ctx.instanceManager?.getInstance(instanceKey);
34871
34922
  try {
34872
- instance?.noteManualInteraction?.();
34923
+ instance?.noteManualInteraction?.(void 0, { passive });
34873
34924
  } catch {
34874
34925
  }
34875
34926
  }
@@ -35160,7 +35211,7 @@ var DaemonCommandHandler = class {
35160
35211
  const https = __require("https");
35161
35212
  const fs40 = __require("fs");
35162
35213
  const path44 = __require("path");
35163
- const REGISTRY = "https://api.adhf.dev/api/v1/registry";
35214
+ const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
35164
35215
  function fetchText(url, timeoutMs) {
35165
35216
  return new Promise((resolve25, reject) => {
35166
35217
  const req = https.get(url, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: timeoutMs }, (res) => {
@@ -35522,7 +35573,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
35522
35573
  const installed = this.handleListInstalledProviders({});
35523
35574
  if (!installed.success) return installed;
35524
35575
  const https = __require("https");
35525
- const REGISTRY = "https://api.adhf.dev/api/v1/registry";
35576
+ const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
35526
35577
  function fetchJson(url) {
35527
35578
  return new Promise((resolve25, reject) => {
35528
35579
  const req = https.get(url, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: 1e4 }, (res) => {
@@ -37125,6 +37176,7 @@ var CHANNEL_SERVER_URL = {
37125
37176
  stable: "https://api.adhf.dev",
37126
37177
  preview: "https://api-preview.adhf.dev"
37127
37178
  };
37179
+ var VENDOR_SERVER_URLS = new Set(Object.values(CHANNEL_SERVER_URL));
37128
37180
  function normalizeReleaseChannel(value) {
37129
37181
  if (typeof value !== "string") return null;
37130
37182
  const normalized = value.trim().toLowerCase();
@@ -37146,7 +37198,11 @@ var daemonLifecycleHandlers = {
37146
37198
  const npmTag = CHANNEL_NPM_TAG[channel];
37147
37199
  const latest = String(execNpmCommandSync(["view", `${pkgName}@${npmTag}`, "version"], { encoding: "utf-8", timeout: 1e4 }, npmSurface)).trim();
37148
37200
  LOG.info("Upgrade", `Latest ${pkgName}@${npmTag}: v${latest}`);
37149
- updateConfig({ updateChannel: channel, serverUrl: CHANNEL_SERVER_URL[channel] });
37201
+ const currentServerUrl = typeof loadConfig().serverUrl === "string" ? loadConfig().serverUrl.trim() : "";
37202
+ const useVendorServerUrl = currentServerUrl === "" || VENDOR_SERVER_URLS.has(currentServerUrl);
37203
+ updateConfig(
37204
+ useVendorServerUrl ? { updateChannel: channel, serverUrl: CHANNEL_SERVER_URL[channel] } : { updateChannel: channel }
37205
+ );
37150
37206
  let currentInstalled = null;
37151
37207
  try {
37152
37208
  const currentJson = String(execNpmCommandSync(["ls", "-g", pkgName, "--depth=0", "--json"], {
@@ -37854,6 +37910,7 @@ function applyPreLaunchTrust(trust, workingDir) {
37854
37910
 
37855
37911
  // src/providers/spec/fsm-driver.ts
37856
37912
  init_logger();
37913
+ init_debug_config();
37857
37914
  init_pty_write_chunking();
37858
37915
  function countNewlines(s2) {
37859
37916
  let n = 0;
@@ -37922,6 +37979,11 @@ var FsmDriver = class {
37922
37979
  * (−1 = whole screen), or a `section:<id>` / `<region>#ignore:<pat>` string
37923
37980
  * when the clause scopes to a section or declares an ignore_lines filter. */
37924
37981
  regionLastChangedAt = /* @__PURE__ */ new Map();
37982
+ /** COMPLETION-EARLYNOTIFY stable-eval trace: last stable/not-stable verdict
37983
+ * recorded per stable region, so the trace fires only when the verdict FLIPS
37984
+ * (not every quiet frame). Cleared on every transition alongside
37985
+ * regionLastChangedAt. Diagnostic-only — never consulted by the FSM. */
37986
+ stableVerdictCache = /* @__PURE__ */ new Map();
37925
37987
  /** Timer that re-runs evaluate() when a time-condition would flip true
37926
37988
  * with no PTY frame to trigger it. */
37927
37989
  wakeTimer = null;
@@ -38257,6 +38319,7 @@ var FsmDriver = class {
38257
38319
  this.currentStateId = fired.to;
38258
38320
  this.stateEnteredAt = now;
38259
38321
  this.regionLastChangedAt.clear();
38322
+ this.stableVerdictCache.clear();
38260
38323
  this.pushHistory(fired.to, stateById(this.spec, fired.to)?.label ?? fired.to, {
38261
38324
  reason: "transition",
38262
38325
  via: `${from}\u2192${fired.to}`,
@@ -38361,6 +38424,7 @@ var FsmDriver = class {
38361
38424
  trackRegionChanges(currentLines, cursor, now) {
38362
38425
  if (this.prevScreenLines.length === 0) return;
38363
38426
  const descs = this.stableRegionDescriptors();
38427
+ const stableTraceOn = shouldCollectTraceCategory("fsm-transition");
38364
38428
  const needsSections = descs.some((d) => !!d.section);
38365
38429
  const curSections = needsSections ? resolveSections(this.spec.sections ?? {}, currentLines) : [];
38366
38430
  const prevSections = needsSections ? resolveSections(this.spec.sections ?? {}, this.prevScreenLines) : [];
@@ -38381,6 +38445,28 @@ var FsmDriver = class {
38381
38445
  const cur = filterIgnoredLines(curLines, d.ignoreRe).join("\n");
38382
38446
  const prev = filterIgnoredLines(prevLines, d.ignoreRe).join("\n");
38383
38447
  if (cur !== prev) this.regionLastChangedAt.set(d.key, now);
38448
+ if (stableTraceOn && typeof d.holdMs === "number") {
38449
+ const lastChanged = this.regionLastChangedAt.get(d.key) ?? this.stateEnteredAt;
38450
+ const ageMs = now - lastChanged;
38451
+ const verdict = ageMs >= d.holdMs;
38452
+ if (this.stableVerdictCache.get(d.key) !== verdict) {
38453
+ this.stableVerdictCache.set(d.key, verdict);
38454
+ recordDebugTrace({
38455
+ category: "fsm-transition",
38456
+ stage: "stable-eval",
38457
+ level: "debug",
38458
+ payload: {
38459
+ state: this.currentStateId,
38460
+ regionKey: String(d.key),
38461
+ ignorePattern: d.ignoreRe?.source ?? null,
38462
+ fingerprintLen: cur.length,
38463
+ ageMs,
38464
+ holdMs: d.holdMs,
38465
+ verdict
38466
+ }
38467
+ });
38468
+ }
38469
+ }
38384
38470
  }
38385
38471
  }
38386
38472
  /** Every distinct stable-region descriptor referenced by stable_ms
@@ -38896,7 +38982,8 @@ function collectStableDescriptors(when, byKey) {
38896
38982
  const w = when;
38897
38983
  if ("stable_ms" in w) {
38898
38984
  const key2 = stableRegionKey(w);
38899
- if (!byKey.has(key2)) {
38985
+ const existing = byKey.get(key2);
38986
+ if (!existing) {
38900
38987
  let ignoreRe;
38901
38988
  if (w.ignore_lines) {
38902
38989
  try {
@@ -38904,7 +38991,9 @@ function collectStableDescriptors(when, byKey) {
38904
38991
  } catch {
38905
38992
  }
38906
38993
  }
38907
- byKey.set(key2, { key: key2, section: w.section, cursor_above: w.cursor_above, ignoreRe });
38994
+ byKey.set(key2, { key: key2, section: w.section, cursor_above: w.cursor_above, ignoreRe, holdMs: typeof w.stable_ms === "number" ? w.stable_ms : void 0 });
38995
+ } else if (existing.holdMs === void 0 && typeof w.stable_ms === "number") {
38996
+ existing.holdMs = w.stable_ms;
38908
38997
  }
38909
38998
  return;
38910
38999
  }
@@ -40676,6 +40765,7 @@ function createCliAdapter(provider, workingDir, cliArgs, extraEnv, transportFact
40676
40765
 
40677
40766
  // src/providers/cli-provider-instance.ts
40678
40767
  init_logger();
40768
+ init_debug_config();
40679
40769
  init_mesh_event_trace();
40680
40770
  init_control_effects();
40681
40771
  init_approval_utils();
@@ -41048,6 +41138,13 @@ var CliProviderInstance = class _CliProviderInstance {
41048
41138
  // mask is dropped so the real waiting_approval surfaces. Cleared when the episode ends
41049
41139
  // (modal genuinely gone, manual attendance takes over, or auto-approve fires).
41050
41140
  autoApproveMaskSince = 0;
41141
+ // NOTIF-APPROVAL-MASKED (Q1b): the autoApproveMaskSince episode value for which a
41142
+ // stalled-approval coordinator nudge has already been emitted, so the nudge fires
41143
+ // exactly once per stalled auto-approve episode (0 = none emitted). Reusing the
41144
+ // per-episode mask-clock value as the key makes it provider-agnostic (no reliance on
41145
+ // approvalEntrySeq) and self-resetting: each new episode gets a fresh
41146
+ // autoApproveMaskSince timestamp, and the episode-end reset zeroes it.
41147
+ stalledApprovalNudgeEpisode = 0;
41051
41148
  // Provider-common manual-attendance signal: while a human is actively driving
41052
41149
  // this session from the dashboard, auto-approve holds so they can take manual
41053
41150
  // control. Background mesh workers are never attended → delegated auto-approve
@@ -41742,9 +41839,10 @@ var CliProviderInstance = class _CliProviderInstance {
41742
41839
  return restoredHistory.messages;
41743
41840
  }
41744
41841
  completionFinalAssistantEvidence(parsedMessages, turnStartedAt) {
41842
+ const turnClosed = !this.hasAdapterPendingResponse();
41745
41843
  if (this.completionHasFinalAssistantMessage(parsedMessages, turnStartedAt)) {
41746
41844
  return {
41747
- present: true,
41845
+ present: turnClosed,
41748
41846
  messages: Array.isArray(parsedMessages) ? parsedMessages : [],
41749
41847
  source: "parsed"
41750
41848
  };
@@ -41752,7 +41850,7 @@ var CliProviderInstance = class _CliProviderInstance {
41752
41850
  const externalMessages = this.readExternalCompletionMessages();
41753
41851
  if (externalMessages) {
41754
41852
  return {
41755
- present: this.completionHasFinalAssistantMessage(externalMessages, turnStartedAt),
41853
+ present: turnClosed && this.completionHasFinalAssistantMessage(externalMessages, turnStartedAt),
41756
41854
  messages: externalMessages,
41757
41855
  source: "external-native"
41758
41856
  };
@@ -41851,11 +41949,9 @@ var CliProviderInstance = class _CliProviderInstance {
41851
41949
  if (latestVisibleStatus !== "idle") return { reason: `status:${latestVisibleStatus}`, terminal: true };
41852
41950
  const adapterAny = this.adapter;
41853
41951
  const approvalResolvedIdle = pending.previousStatus === "waiting_approval";
41854
- if (!approvalResolvedIdle) {
41855
- if (adapterAny?.isWaitingForResponse === true) return { reason: "adapter_waiting_for_response", terminal: true };
41856
- if (adapterAny?.currentTurnScope) return { reason: "adapter_turn_scope_active", terminal: true };
41857
- if (this.hasAdapterPendingResponse()) return { reason: "adapter_pending_response", terminal: true };
41858
- }
41952
+ if (adapterAny?.isWaitingForResponse === true) return { reason: "adapter_waiting_for_response", terminal: !approvalResolvedIdle };
41953
+ if (adapterAny?.currentTurnScope) return { reason: "adapter_turn_scope_active", terminal: !approvalResolvedIdle };
41954
+ if (this.hasAdapterPendingResponse()) return { reason: "adapter_pending_response", terminal: !approvalResolvedIdle };
41859
41955
  const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
41860
41956
  if (typeof partial === "string" && partial.trim()) return { reason: "partial_response_pending", terminal: true };
41861
41957
  let parsed;
@@ -42010,6 +42106,39 @@ var CliProviderInstance = class _CliProviderInstance {
42010
42106
  event
42011
42107
  };
42012
42108
  }
42109
+ // COMPLETION-EARLYNOTIFY instrumentation. A session-keyed FSM-transition +
42110
+ // completion-gate snapshot recorded into the shared debug-trace ring buffer
42111
+ // (secret-safe, length/role/pattern-name only — never screen or bubble text).
42112
+ // Retrieved via getRecentDebugTrace (chat_debug_bundle). Both categories are a
42113
+ // no-op unless collectDebugTrace is on AND the category is selected, so the
42114
+ // hot-path guards below (completionTraceOn / fsmTraceOn) keep production cost
42115
+ // at a single boolean check.
42116
+ completionTraceOn() {
42117
+ return shouldCollectTraceCategory("completion-gate");
42118
+ }
42119
+ fsmTraceOn() {
42120
+ return shouldCollectTraceCategory("fsm-transition");
42121
+ }
42122
+ recordCompletionGateTrace(stage, payload) {
42123
+ recordDebugTrace({
42124
+ category: "completion-gate",
42125
+ stage,
42126
+ level: "debug",
42127
+ sessionId: this.instanceId,
42128
+ providerType: this.type,
42129
+ payload
42130
+ });
42131
+ }
42132
+ recordFsmTransitionTrace(payload) {
42133
+ recordDebugTrace({
42134
+ category: "fsm-transition",
42135
+ stage: "transition",
42136
+ level: "debug",
42137
+ sessionId: this.instanceId,
42138
+ providerType: this.type,
42139
+ payload
42140
+ });
42141
+ }
42013
42142
  flushCompletedDebounceIfFinalized() {
42014
42143
  const pending = this.completedDebouncePending;
42015
42144
  if (!pending) {
@@ -42022,12 +42151,27 @@ var CliProviderInstance = class _CliProviderInstance {
42022
42151
  LOG.debug("CLI", `[${this.type}] flush attempt: adapterStatus=${latestStatus.status} latestVisible=${latestVisibleStatus} generatingStartedAt=${this.generatingStartedAt} isWaitingForResponse=${!!this.adapter?.isWaitingForResponse} hasPartial=${!!this.adapter.getPartialResponse?.()}`);
42023
42152
  if (latestVisibleStatus !== "idle") {
42024
42153
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
42154
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("cancel", {
42155
+ blockReason: "resumed_status",
42156
+ latestVisibleStatus,
42157
+ previousStatus: pending.previousStatus,
42158
+ busyEpochAtArm: pending.busyEpochAtArm,
42159
+ busyEpoch: this.busyEpoch
42160
+ });
42025
42161
  this.completedDebouncePending = null;
42026
42162
  this.completedDebounceTimer = null;
42027
42163
  return;
42028
42164
  }
42029
42165
  if (typeof pending.busyEpochAtArm === "number" && this.busyEpoch !== pending.busyEpochAtArm) {
42030
42166
  LOG.info("CLI", `[${this.type}] cancelled pending completed (busy re-entry during settle: epoch ${pending.busyEpochAtArm}\u2192${this.busyEpoch})`);
42167
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("cancel", {
42168
+ blockReason: "busy_reentry",
42169
+ latestVisibleStatus,
42170
+ previousStatus: pending.previousStatus,
42171
+ busyEpochAtArm: pending.busyEpochAtArm,
42172
+ busyEpoch: this.busyEpoch,
42173
+ busyEpochDelta: this.busyEpoch - pending.busyEpochAtArm
42174
+ });
42031
42175
  this.completedDebouncePending = null;
42032
42176
  this.completedDebounceTimer = null;
42033
42177
  return;
@@ -42035,6 +42179,14 @@ var CliProviderInstance = class _CliProviderInstance {
42035
42179
  const latestOutputAt = typeof latestStatus?.lastOutputAt === "number" ? latestStatus.lastOutputAt : void 0;
42036
42180
  if (typeof pending.lastOutputAtArm === "number" && typeof latestOutputAt === "number" && latestOutputAt > pending.lastOutputAtArm) {
42037
42181
  LOG.info("CLI", `[${this.type}] cancelled pending completed (new PTY output during settle: ${pending.lastOutputAtArm}\u2192${latestOutputAt})`);
42182
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("cancel", {
42183
+ blockReason: "new_pty_output",
42184
+ latestVisibleStatus,
42185
+ previousStatus: pending.previousStatus,
42186
+ lastOutputAtArm: pending.lastOutputAtArm,
42187
+ lastOutputAt: latestOutputAt,
42188
+ lastOutputAtDelta: latestOutputAt - pending.lastOutputAtArm
42189
+ });
42038
42190
  this.completedDebouncePending = null;
42039
42191
  this.completedDebounceTimer = null;
42040
42192
  return;
@@ -42051,6 +42203,14 @@ var CliProviderInstance = class _CliProviderInstance {
42051
42203
  if (this.isMeshWorkerSession()) {
42052
42204
  traceMeshEventDrop("completion_gate_hold", this.meshTraceCtx(), `${blockReason} waited=${waitedMs}ms`);
42053
42205
  }
42206
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("hold", {
42207
+ blockReason,
42208
+ latestVisibleStatus,
42209
+ terminal: block2.terminal === true,
42210
+ holdForTranscript: block2.holdForTranscript === true,
42211
+ approvalResolvedIdle: pending.previousStatus === "waiting_approval",
42212
+ waitedMs
42213
+ });
42054
42214
  pending.loggedBlockReason = blockReason;
42055
42215
  }
42056
42216
  this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
@@ -42070,6 +42230,19 @@ var CliProviderInstance = class _CliProviderInstance {
42070
42230
  if (this.isMeshWorkerSession()) {
42071
42231
  traceMeshEventStage("fired", this.meshTraceCtx(), `forced after ${waitedMs}ms (${blockReason})`);
42072
42232
  }
42233
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("fire", {
42234
+ path: isTranscriptEvidenceGate && !emittedAfterFinalizationTimeout ? "canon_c_decoupled" : "forced_timeout",
42235
+ blockReason,
42236
+ latestVisibleStatus,
42237
+ approvalResolvedIdle: pending.previousStatus === "waiting_approval",
42238
+ finalAssistantPresent: completionDiagnostic.finalAssistantPresent === true,
42239
+ evidenceSource: completionDiagnostic.finalAssistantEvidenceSource ?? null,
42240
+ lastVisibleRole: completionDiagnostic.lastVisibleRole ?? null,
42241
+ lastVisibleContentLen: completionDiagnostic.lastVisibleContentLength ?? null,
42242
+ emittedAfterFinalizationTimeout,
42243
+ waitedMs,
42244
+ busyEpoch: this.busyEpoch
42245
+ });
42073
42246
  this.pushEvent({
42074
42247
  event: "agent:generating_completed",
42075
42248
  chatTitle: pending.chatTitle,
@@ -42097,6 +42270,14 @@ var CliProviderInstance = class _CliProviderInstance {
42097
42270
  if (this.isMeshWorkerSession()) {
42098
42271
  traceMeshEventStage("fired", this.meshTraceCtx(), `duration=${pending.duration}s`);
42099
42272
  }
42273
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("fire", {
42274
+ path: "clean",
42275
+ latestVisibleStatus,
42276
+ approvalResolvedIdle: pending.previousStatus === "waiting_approval",
42277
+ finalAssistantPresent: true,
42278
+ duration: pending.duration,
42279
+ busyEpoch: this.busyEpoch
42280
+ });
42100
42281
  this.pushEvent({
42101
42282
  event: "agent:generating_completed",
42102
42283
  chatTitle: pending.chatTitle,
@@ -42118,6 +42299,7 @@ var CliProviderInstance = class _CliProviderInstance {
42118
42299
  this.pendingAutoApprovalSince = 0;
42119
42300
  this.autoApproveInactiveSince = 0;
42120
42301
  this.autoApproveMaskSince = 0;
42302
+ this.stalledApprovalNudgeEpisode = 0;
42121
42303
  if (this.autoApproveSettleTimer) clearTimeout(this.autoApproveSettleTimer);
42122
42304
  this.autoApproveSettleTimer = setTimeout(() => {
42123
42305
  this.autoApproveSettleTimer = null;
@@ -42144,6 +42326,7 @@ var CliProviderInstance = class _CliProviderInstance {
42144
42326
  this.pendingAutoApprovalSince = 0;
42145
42327
  this.autoApproveInactiveSince = 0;
42146
42328
  this.autoApproveMaskSince = 0;
42329
+ this.stalledApprovalNudgeEpisode = 0;
42147
42330
  if (this.autoApproveSettleTimer) {
42148
42331
  clearTimeout(this.autoApproveSettleTimer);
42149
42332
  this.autoApproveSettleTimer = null;
@@ -42152,6 +42335,7 @@ var CliProviderInstance = class _CliProviderInstance {
42152
42335
  }
42153
42336
  this.autoApproveInactiveSince = 0;
42154
42337
  if (!this.autoApproveMaskSince) this.autoApproveMaskSince = now;
42338
+ this.maybeEmitStalledApprovalNudge(adapterStatus, now);
42155
42339
  const modal = adapterStatus.activeModal;
42156
42340
  const buttons = Array.isArray(modal?.buttons) ? modal.buttons.map((b) => String(b || "").trim()).filter(Boolean) : [];
42157
42341
  if (!modal || buttons.length === 0) {
@@ -42162,7 +42346,8 @@ var CliProviderInstance = class _CliProviderInstance {
42162
42346
  return autoApproveActive;
42163
42347
  }
42164
42348
  const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(buttons, this.provider);
42165
- if (buttonIndex < 0 || !hasNegativeApprovalOption(buttons)) {
42349
+ const hasReliableConsentAnchor = hasNegativeApprovalOption(buttons) || hasReliableApprovalAffirmative(buttons);
42350
+ if (buttonIndex < 0 || !hasReliableConsentAnchor) {
42166
42351
  return autoApproveActive;
42167
42352
  }
42168
42353
  const modalSignature = [
@@ -42198,6 +42383,7 @@ var CliProviderInstance = class _CliProviderInstance {
42198
42383
  this.pendingAutoApprovalSince = 0;
42199
42384
  this.autoApproveInactiveSince = 0;
42200
42385
  this.autoApproveMaskSince = 0;
42386
+ this.stalledApprovalNudgeEpisode = 0;
42201
42387
  if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
42202
42388
  this.autoApproveBusyTimer = setTimeout(() => {
42203
42389
  this.autoApproveBusy = false;
@@ -42325,6 +42511,18 @@ var CliProviderInstance = class _CliProviderInstance {
42325
42511
  const previousStatus = this.lastStatus;
42326
42512
  if (newStatus !== this.lastStatus) {
42327
42513
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
42514
+ if (this.fsmTraceOn()) this.recordFsmTransitionTrace({
42515
+ from: this.lastStatus,
42516
+ to: newStatus,
42517
+ rawStatus,
42518
+ autoApproveActive,
42519
+ autoApproveHoldIdle,
42520
+ autoApproveBusy: this.autoApproveBusy,
42521
+ hasPending: this.hasAdapterPendingResponse(),
42522
+ busyEpoch: this.busyEpoch,
42523
+ lastOutputAt: typeof adapterStatus?.lastOutputAt === "number" ? adapterStatus.lastOutputAt : null,
42524
+ lastScreenChangeAt: typeof adapterStatus?.lastScreenChangeAt === "number" ? adapterStatus.lastScreenChangeAt : null
42525
+ });
42328
42526
  const startingToGeneratingWithActiveTurn = this.lastStatus === "starting" && newStatus === "generating" && this.hasAdapterPendingResponse();
42329
42527
  if (this.lastStatus === "idle" && newStatus === "generating" || startingToGeneratingWithActiveTurn) {
42330
42528
  if (this.completedDebouncePending && this.generatingStartedAt === 0) {
@@ -42451,6 +42649,17 @@ var CliProviderInstance = class _CliProviderInstance {
42451
42649
  if (this.isMeshWorkerSession()) {
42452
42650
  traceMeshEventStage("arm", this.meshTraceCtx(), `short-generating settle-arm (source=${shortEvidenceSource}, missingEvidence=${missingEvidence})`);
42453
42651
  }
42652
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("arm", {
42653
+ branch: "short_generating",
42654
+ previousStatus: this.lastStatus,
42655
+ turnStartedAt: shortTurnStartedAt || null,
42656
+ busyEpochAtArm: this.busyEpoch,
42657
+ lastOutputAtArm: typeof adapterStatus?.lastOutputAt === "number" ? adapterStatus.lastOutputAt : null,
42658
+ flushDelay: NATIVE_HISTORY_MESH_IDLE_SETTLE_MS,
42659
+ evidenceSource: shortEvidenceSource,
42660
+ missingEvidence,
42661
+ hasFinalSummary: !!shortFinalSummary
42662
+ });
42454
42663
  this.scheduleCompletedDebounceFlush(NATIVE_HISTORY_MESH_IDLE_SETTLE_MS);
42455
42664
  } else if (missingEvidence) {
42456
42665
  LOG.info("CLI", `[${this.type}] short completion suppressed: missing final assistant evidence, non-mesh session (source=${shortEvidenceSource})`);
@@ -42499,6 +42708,16 @@ var CliProviderInstance = class _CliProviderInstance {
42499
42708
  const meshSettleSession = this.isAutonomousMeshSession();
42500
42709
  const flushDelay = ownsExternalHistory ? meshSettleSession ? NATIVE_HISTORY_MESH_IDLE_SETTLE_MS : 0 : 3e3;
42501
42710
  LOG.debug("CLI", `[${this.type}] set completedDebouncePending duration=${duration}s ownsExternalHistory=${ownsExternalHistory} meshSettle=${meshSettleSession} flushDelay=${flushDelay}ms generatingStartedAt=${this.generatingStartedAt}`);
42711
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("arm", {
42712
+ branch: "normal",
42713
+ previousStatus: this.completedDebouncePending.previousStatus,
42714
+ turnStartedAt: this.completedDebouncePending.turnStartedAt ?? null,
42715
+ busyEpochAtArm: this.completedDebouncePending.busyEpochAtArm ?? null,
42716
+ lastOutputAtArm: this.completedDebouncePending.lastOutputAtArm ?? null,
42717
+ flushDelay,
42718
+ ownsExternalHistory,
42719
+ meshSettle: meshSettleSession
42720
+ });
42502
42721
  this.scheduleCompletedDebounceFlush(flushDelay);
42503
42722
  }
42504
42723
  } else if (newStatus === "idle" && this.lastStatus === "starting") {
@@ -42751,7 +42970,8 @@ ${effect.notification.body || ""}`.trim();
42751
42970
  return false;
42752
42971
  }
42753
42972
  /** @see ProviderInstance.noteManualInteraction */
42754
- noteManualInteraction(now = Date.now()) {
42973
+ noteManualInteraction(now = Date.now(), opts) {
42974
+ if (opts?.passive && this.isMeshWorkerSession()) return;
42755
42975
  this.manualAttendance.note(now);
42756
42976
  }
42757
42977
  /**
@@ -42775,6 +42995,50 @@ ${effect.notification.body || ""}`.trim();
42775
42995
  autoApproveMaskStalled(now = Date.now()) {
42776
42996
  return this.autoApproveMaskSince > 0 && now - this.autoApproveMaskSince > _CliProviderInstance.AUTO_APPROVE_MASK_STALL_MS;
42777
42997
  }
42998
+ /**
42999
+ * NOTIF-APPROVAL-MASKED (Q1b): surface a delegated worker's STALLED auto-approve modal
43000
+ * to the mesh COORDINATOR, decoupled from the dashboard visible-status mask.
43001
+ *
43002
+ * When auto-approve is configured but the episode never settles (modal parse miss / the
43003
+ * settle gate never satisfied), getState()/detectStatusTransition() fold the raw
43004
+ * `waiting_approval` into `generating` to suppress dashboard flicker — so
43005
+ * detectStatusTransition()'s `waiting_approval` arm never runs and NO agent:waiting_approval
43006
+ * event is emitted. The coordinator's real-time approval-nudge delivery then has no input and
43007
+ * the worker's stuck modal is never surfaced (the live ~25s stall). The dashboard mask is
43008
+ * intentional and stays; this emits the coordinator nudge exactly ONCE, gated on the SAME
43009
+ * raw-waiting_approval + mask-stalled signal resolveModalParkStatus() distinguishes, the
43010
+ * instant the mask-stall threshold trips (the same moment getState un-folds the mask).
43011
+ *
43012
+ * Only delegated worker sessions qualify: a foreground session has no coordinator to notify,
43013
+ * and its own dashboard mask already reveals the modal on stall. A normally-resolving
43014
+ * auto-approve never reaches AUTO_APPROVE_MASK_STALL_MS, so it emits nothing here; and if a
43015
+ * masked approval clears just as this fires, rc.455's isApprovalNudgeResolved stale-drop
43016
+ * discards the nudge coordinator-side without noise. Dedup is per-episode (keyed on the
43017
+ * mask-clock value) so a modal that flaps between parsed/unparsed states is announced once.
43018
+ */
43019
+ maybeEmitStalledApprovalNudge(adapterStatus, now) {
43020
+ if (!this.isMeshWorkerSession()) return;
43021
+ if (adapterStatus?.status !== "waiting_approval") return;
43022
+ if (!this.autoApproveMaskStalled(now)) return;
43023
+ if (this.stalledApprovalNudgeEpisode === this.autoApproveMaskSince) return;
43024
+ this.stalledApprovalNudgeEpisode = this.autoApproveMaskSince;
43025
+ const modal = adapterStatus.activeModal;
43026
+ const dirName = workingDirBasename(this.workingDir);
43027
+ const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
43028
+ this.appendRuntimeSystemMessage(
43029
+ this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
43030
+ `approval_request:${now}`,
43031
+ now
43032
+ );
43033
+ this.pushEvent({
43034
+ event: "agent:waiting_approval",
43035
+ chatTitle,
43036
+ timestamp: now,
43037
+ modalMessage: modal?.message,
43038
+ modalButtons: modal?.buttons
43039
+ });
43040
+ LOG.info("CLI", `[${this.type}] stalled auto-approve nudge \u2192 coordinator (masked ${Math.round((now - this.autoApproveMaskSince) / 1e3)}s)`);
43041
+ }
42778
43042
  recordAutoApproval(modalMessage, buttonLabel, now = Date.now()) {
42779
43043
  this.appendRuntimeSystemMessage(
42780
43044
  formatAutoApprovalMessage(modalMessage, buttonLabel),
@@ -47458,12 +47722,17 @@ var ProviderLoader = class _ProviderLoader {
47458
47722
  logFn;
47459
47723
  versionArchive = null;
47460
47724
  scriptsCache = /* @__PURE__ */ new Map();
47725
+ /**
47726
+ * Resolved registry base URL and provider tarball URL. Resolution order:
47727
+ * explicit config field (constructor option) → env var → vendor default.
47728
+ * See `config/registry-resolver.ts`.
47729
+ */
47730
+ registryBaseUrl;
47731
+ providerTarballUrl;
47461
47732
  /** Inject VersionArchive so resolve() can auto-detect installed versions */
47462
47733
  setVersionArchive(archive) {
47463
47734
  this.versionArchive = archive;
47464
47735
  }
47465
- static GITHUB_TARBALL_URL = "https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz";
47466
- static REGISTRY_BASE_URL = "https://api.adhf.dev/api/v1/registry";
47467
47736
  static META_FILE = ".meta.json";
47468
47737
  static REGISTRY_META_FILE = ".registry-meta.json";
47469
47738
  static REPO_PROVIDER_DIRNAME = "adhdev-providers";
@@ -47531,6 +47800,8 @@ var ProviderLoader = class _ProviderLoader {
47531
47800
  constructor(options) {
47532
47801
  this.logFn = options?.logFn || LOG.forComponent("Provider").asLogFn();
47533
47802
  this.probeStarts = options?.probeStarts ?? [process.cwd(), __dirname];
47803
+ this.registryBaseUrl = resolveRegistryBaseUrl(options?.registryUrl);
47804
+ this.providerTarballUrl = resolveProviderTarballUrl(options?.providerTarballUrl);
47534
47805
  this.defaultProvidersDir = path35.join(os25.homedir(), ".adhdev", "providers");
47535
47806
  const detected = this.detectDefaultUserDir();
47536
47807
  this.userDir = detected.path;
@@ -48563,7 +48834,7 @@ var ProviderLoader = class _ProviderLoader {
48563
48834
  this.log("Registry sync skipped (sourceMode=no-upstream)");
48564
48835
  return { updated: false };
48565
48836
  }
48566
- this.log(`Registry sync starting (${_ProviderLoader.REGISTRY_BASE_URL})...`);
48837
+ this.log(`Registry sync starting (${this.registryBaseUrl})...`);
48567
48838
  const https = __require("https");
48568
48839
  const regMetaPath = path35.join(this.upstreamDir, _ProviderLoader.REGISTRY_META_FILE);
48569
48840
  let cachedChecksums = {};
@@ -48574,7 +48845,7 @@ var ProviderLoader = class _ProviderLoader {
48574
48845
  } catch {
48575
48846
  }
48576
48847
  try {
48577
- const listUrl = `${_ProviderLoader.REGISTRY_BASE_URL}/providers`;
48848
+ const listUrl = `${this.registryBaseUrl}/providers`;
48578
48849
  const listBody = await new Promise((resolve25, reject) => {
48579
48850
  const req = https.get(listUrl, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: 1e4 }, (res) => {
48580
48851
  if (res.statusCode !== 200) {
@@ -48598,7 +48869,7 @@ var ProviderLoader = class _ProviderLoader {
48598
48869
  const { type, category, checksum, version } = entry;
48599
48870
  const cacheKey = `${category}/${type}`;
48600
48871
  if (cachedChecksums[cacheKey] === checksum) continue;
48601
- const dlUrl = `${_ProviderLoader.REGISTRY_BASE_URL}/providers/${type}/${version}/download`;
48872
+ const dlUrl = `${this.registryBaseUrl}/providers/${type}/${version}/download`;
48602
48873
  const manifestBody = await new Promise((resolve25, reject) => {
48603
48874
  const req = https.get(dlUrl, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: 3e4 }, (res) => {
48604
48875
  if (res.statusCode !== 200) {
@@ -48665,12 +48936,13 @@ var ProviderLoader = class _ProviderLoader {
48665
48936
  this.log("Upstream check skipped (last check < 30min ago)");
48666
48937
  return { updated: false };
48667
48938
  }
48939
+ const tarballTarget = resolveProviderTarballTarget(this.providerTarballUrl);
48668
48940
  try {
48669
48941
  const etag = await new Promise((resolve25, reject) => {
48670
48942
  const options = {
48671
48943
  method: "HEAD",
48672
- hostname: "github.com",
48673
- path: "/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz",
48944
+ hostname: tarballTarget.hostname,
48945
+ path: tarballTarget.path,
48674
48946
  headers: { "User-Agent": "adhdev-launcher" },
48675
48947
  timeout: 1e4
48676
48948
  };
@@ -48711,7 +48983,7 @@ var ProviderLoader = class _ProviderLoader {
48711
48983
  this.log("Downloading latest providers from GitHub...");
48712
48984
  const tmpTar = path35.join(os25.tmpdir(), `adhdev-providers-${Date.now()}.tar.gz`);
48713
48985
  const tmpExtract = path35.join(os25.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
48714
- await this.downloadFile(_ProviderLoader.GITHUB_TARBALL_URL, tmpTar);
48986
+ await this.downloadFile(tarballTarget.url, tmpTar);
48715
48987
  fs25.mkdirSync(tmpExtract, { recursive: true });
48716
48988
  await execAsync5(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 3e4 });
48717
48989
  const extracted = fs25.readdirSync(tmpExtract);
@@ -48811,7 +49083,7 @@ var ProviderLoader = class _ProviderLoader {
48811
49083
  etag,
48812
49084
  timestamp,
48813
49085
  lastCheck: new Date(timestamp).toISOString(),
48814
- source: _ProviderLoader.GITHUB_TARBALL_URL
49086
+ source: this.providerTarballUrl
48815
49087
  }, null, 2));
48816
49088
  } catch {
48817
49089
  }
@@ -49923,6 +50195,47 @@ async function decideOssCloneSync(ossCtx, worktreeOssSha, sourceSha, rg) {
49923
50195
  if (await isAncestor(worktreeOssSha, sourceSha)) return "advance";
49924
50196
  return "skip_diverged";
49925
50197
  }
50198
+ async function syncClonedWorktreeSubmodules(worktreePath, sourceWorkspace, rg) {
50199
+ const submodulePaths = getRegisteredSubmodulePaths(worktreePath);
50200
+ if (submodulePaths.size === 0) return;
50201
+ const sourceCtx = { workspace: sourceWorkspace, repoRoot: sourceWorkspace, isGitRepo: true };
50202
+ const worktreeCtx = { workspace: worktreePath, repoRoot: worktreePath, isGitRepo: true };
50203
+ const readStdout = (out) => (typeof out === "string" ? out : out?.stdout ?? "").trim();
50204
+ for (const submodulePath of submodulePaths) {
50205
+ try {
50206
+ const sourceStatusOut = await rg(sourceCtx, ["submodule", "status", submodulePath], { timeoutMs: 1e4 });
50207
+ const sourceSha = readStdout(sourceStatusOut).match(/^[+\- ]?([0-9a-f]{40})/)?.[1];
50208
+ if (!sourceSha) continue;
50209
+ const subCtx = {
50210
+ workspace: `${worktreePath}/${submodulePath}`,
50211
+ repoRoot: `${worktreePath}/${submodulePath}`,
50212
+ isGitRepo: true
50213
+ };
50214
+ const worktreeSubSha = readStdout(await rg(subCtx, ["rev-parse", "HEAD"], { timeoutMs: 1e4 }));
50215
+ if (!worktreeSubSha || worktreeSubSha === sourceSha) continue;
50216
+ await rg(subCtx, ["fetch", `${sourceWorkspace}/${submodulePath}`, "HEAD"], { timeoutMs: 6e4 });
50217
+ let action;
50218
+ try {
50219
+ action = await decideOssCloneSync(subCtx, worktreeSubSha, sourceSha, rg);
50220
+ } catch (decideErr) {
50221
+ action = "skip_diverged";
50222
+ console.warn(`[mesh] ${submodulePath} submodule sync guard could not resolve ancestry (kept fresh worktree HEAD): ${decideErr?.message ?? decideErr}`);
50223
+ }
50224
+ if (action === "advance") {
50225
+ await rg(subCtx, ["checkout", sourceSha], { timeoutMs: 1e4 });
50226
+ await rg(worktreeCtx, ["add", submodulePath], { timeoutMs: 1e4 });
50227
+ await rg(worktreeCtx, ["commit", "-m", `chore: sync ${submodulePath} to source node HEAD on clone`], { timeoutMs: 1e4 });
50228
+ console.log(`[mesh] Advanced ${submodulePath} submodule to newer source HEAD ${sourceSha.slice(0, 8)} in worktree`);
50229
+ } else if (action === "skip_rewind") {
50230
+ console.warn(`[mesh] Skipped ${submodulePath} submodule rewind on clone: source node ${submodulePath} ${sourceSha.slice(0, 8)} is an ancestor of the fresh worktree ${submodulePath} ${worktreeSubSha.slice(0, 8)} \u2014 kept fresher worktree HEAD`);
50231
+ } else if (action === "skip_diverged") {
50232
+ console.warn(`[mesh] Skipped ${submodulePath} submodule sync on clone: source node ${submodulePath} ${sourceSha.slice(0, 8)} diverged from the fresh worktree ${submodulePath} ${worktreeSubSha.slice(0, 8)} \u2014 kept worktree HEAD (coordinator reconciles)`);
50233
+ }
50234
+ } catch (subErr) {
50235
+ console.warn(`[mesh] ${submodulePath} submodule sync to source HEAD failed (best-effort):`, subErr?.message ?? subErr);
50236
+ }
50237
+ }
50238
+ }
49926
50239
  var meshCrudHandlers = {
49927
50240
  list_meshes: async (ctx, _args) => {
49928
50241
  try {
@@ -50627,42 +50940,8 @@ var meshCrudHandlers = {
50627
50940
  submodulesInitialized2 = true;
50628
50941
  const sourceWorkspace = sourceNode.repoRoot || sourceNode.workspace;
50629
50942
  if (sourceWorkspace) {
50630
- try {
50631
- const { runGit: rg } = await Promise.resolve().then(() => (init_git_executor(), git_executor_exports));
50632
- const sourceCtx = { workspace: sourceWorkspace, repoRoot: sourceWorkspace, isGitRepo: true };
50633
- const worktreeCtx = { workspace: result.worktreePath, repoRoot: result.worktreePath, isGitRepo: true };
50634
- const sourceStatusOut = await rg(sourceCtx, ["submodule", "status", "oss"], { timeoutMs: 1e4 });
50635
- const sourceStatusLine = (typeof sourceStatusOut === "string" ? sourceStatusOut : sourceStatusOut?.stdout ?? "").trim();
50636
- const sourceShaMatch = sourceStatusLine.match(/^[+\- ]?([0-9a-f]{40})/);
50637
- const sourceSha = sourceShaMatch?.[1];
50638
- if (sourceSha) {
50639
- const ossCtx = { workspace: `${result.worktreePath}/oss`, repoRoot: `${result.worktreePath}/oss`, isGitRepo: true };
50640
- const worktreeOssHeadOut = await rg(ossCtx, ["rev-parse", "HEAD"], { timeoutMs: 1e4 });
50641
- const worktreeOssSha = (typeof worktreeOssHeadOut === "string" ? worktreeOssHeadOut : worktreeOssHeadOut?.stdout ?? "").trim();
50642
- if (worktreeOssSha && worktreeOssSha !== sourceSha) {
50643
- await rg(ossCtx, ["fetch", `${sourceWorkspace}/oss`, "HEAD"], { timeoutMs: 6e4 });
50644
- let ossAction;
50645
- try {
50646
- ossAction = await decideOssCloneSync(ossCtx, worktreeOssSha, sourceSha, rg);
50647
- } catch (decideErr) {
50648
- ossAction = "skip_diverged";
50649
- console.warn(`[mesh] oss submodule sync guard could not resolve ancestry (kept fresh worktree HEAD): ${decideErr?.message ?? decideErr}`);
50650
- }
50651
- if (ossAction === "advance") {
50652
- await rg(ossCtx, ["checkout", sourceSha], { timeoutMs: 1e4 });
50653
- await rg(worktreeCtx, ["add", "oss"], { timeoutMs: 1e4 });
50654
- await rg(worktreeCtx, ["commit", "-m", "chore: sync oss to source node HEAD on clone"], { timeoutMs: 1e4 });
50655
- console.log(`[mesh] Advanced oss submodule to newer source HEAD ${sourceSha.slice(0, 8)} in worktree`);
50656
- } else if (ossAction === "skip_rewind") {
50657
- console.warn(`[mesh] Skipped oss submodule rewind on clone: source node oss ${sourceSha.slice(0, 8)} is an ancestor of the fresh worktree oss ${worktreeOssSha.slice(0, 8)} \u2014 kept fresher worktree HEAD`);
50658
- } else if (ossAction === "skip_diverged") {
50659
- console.warn(`[mesh] Skipped oss submodule sync on clone: source node oss ${sourceSha.slice(0, 8)} diverged from the fresh worktree oss ${worktreeOssSha.slice(0, 8)} \u2014 kept worktree HEAD (coordinator reconciles)`);
50660
- }
50661
- }
50662
- }
50663
- } catch (ossErr) {
50664
- console.warn("[mesh] oss submodule sync to source HEAD failed (best-effort):", ossErr.message);
50665
- }
50943
+ const { runGit: rg } = await Promise.resolve().then(() => (init_git_executor(), git_executor_exports));
50944
+ await syncClonedWorktreeSubmodules(result.worktreePath, sourceWorkspace, rg);
50666
50945
  }
50667
50946
  } catch (subErr) {
50668
50947
  console.warn("[mesh] Submodule init failed for worktree:", subErr.message);
@@ -52096,6 +52375,26 @@ import { execFileSync as execFileSync6 } from "child_process";
52096
52375
  import { existsSync as existsSync40, readFileSync as readFileSync31 } from "fs";
52097
52376
  import { resolve as resolve19 } from "path";
52098
52377
  var PREVIEW_DEPLOY_RECORD = ".adhdev/preview-deploy.json";
52378
+ var PREVIEW_PIPELINE_SCRIPTS = [
52379
+ "scripts/preview-freshness.mjs",
52380
+ "scripts/smoke-preview-web.mjs",
52381
+ "scripts/deploy-preview-local.mjs"
52382
+ ];
52383
+ function hasDeployPreviewNpmScript(repoRoot) {
52384
+ const pkgPath = resolve19(repoRoot, "package.json");
52385
+ if (!existsSync40(pkgPath)) return false;
52386
+ try {
52387
+ const pkg = JSON.parse(readFileSync31(pkgPath, "utf8"));
52388
+ return typeof pkg?.scripts?.["deploy:preview"] === "string";
52389
+ } catch {
52390
+ return false;
52391
+ }
52392
+ }
52393
+ function isPreviewPipelineConfigured(repoRoot) {
52394
+ if (existsSync40(resolve19(repoRoot, PREVIEW_DEPLOY_RECORD))) return true;
52395
+ if (PREVIEW_PIPELINE_SCRIPTS.some((rel) => existsSync40(resolve19(repoRoot, rel)))) return true;
52396
+ return hasDeployPreviewNpmScript(repoRoot);
52397
+ }
52099
52398
  function runGit2(repoRoot, args) {
52100
52399
  try {
52101
52400
  return execFileSync6("git", args, {
@@ -52147,6 +52446,7 @@ function readCurrentMainCommit(repoRoot) {
52147
52446
  return { currentMainCommit: null, currentMainCommitSource: "unknown" };
52148
52447
  }
52149
52448
  function buildPreviewFreshness(repoRoot) {
52449
+ if (!isPreviewPipelineConfigured(repoRoot)) return null;
52150
52450
  const current = readCurrentMainCommit(repoRoot);
52151
52451
  const record = readRecord6(repoRoot);
52152
52452
  const lastPreviewCommit = normalizeCommit(record?.lastPreviewCommit);
@@ -65867,7 +66167,9 @@ async function initDaemonComponents(config) {
65867
66167
  const providerLoader = new ProviderLoader({
65868
66168
  logFn: config.providerLogFn,
65869
66169
  sourceMode: providerSourceMode,
65870
- userDir: appConfig.providerDir
66170
+ userDir: appConfig.providerDir,
66171
+ registryUrl: appConfig.registryUrl,
66172
+ providerTarballUrl: appConfig.providerTarballUrl
65871
66173
  });
65872
66174
  providerLoader.loadAll();
65873
66175
  providerLoader.registerToDetector();