@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.js CHANGED
@@ -409,10 +409,10 @@ function readInjected(value) {
409
409
  }
410
410
  function getDaemonBuildInfo() {
411
411
  if (cached) return cached;
412
- const commit = readInjected(true ? "dba171accb2a47e4b76a157f2d6a556869ef6cdf" : void 0) ?? "unknown";
413
- const commitShort = readInjected(true ? "dba171ac" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
414
- const version = readInjected(true ? "0.9.82-rc.455" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
415
- const builtAt = readInjected(true ? "2026-07-04T05:46:41.792Z" : void 0);
412
+ const commit = readInjected(true ? "29441f596e5efe1972b874e0fb5b3d380c9d9bda" : void 0) ?? "unknown";
413
+ const commitShort = readInjected(true ? "29441f59" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
414
+ const version = readInjected(true ? "0.9.82-rc.457" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
415
+ const builtAt = readInjected(true ? "2026-07-04T08:51:27.287Z" : void 0);
416
416
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
417
417
  return cached;
418
418
  }
@@ -1586,6 +1586,8 @@ function normalizeConfig(raw) {
1586
1586
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
1587
1587
  providerSourceMode: resolveProviderSourceMode(parsed.providerSourceMode, parsed.disableUpstream),
1588
1588
  providerDir: asOptionalString(parsed.providerDir),
1589
+ registryUrl: asOptionalString(parsed.registryUrl),
1590
+ providerTarballUrl: asOptionalString(parsed.providerTarballUrl),
1589
1591
  updateChannel: parsed.updateChannel === "preview" ? "preview" : "stable",
1590
1592
  terminalSizingMode: parsed.terminalSizingMode === "fit" ? "fit" : "measured"
1591
1593
  };
@@ -8932,6 +8934,7 @@ __export(worktree_bootstrap_config_exports, {
8932
8934
  WORKTREE_BOOTSTRAP_STALE_RUNNING_MS: () => WORKTREE_BOOTSTRAP_STALE_RUNNING_MS,
8933
8935
  computeStaleInputsDigest: () => computeStaleInputsDigest,
8934
8936
  evaluateWorktreeBootstrapState: () => evaluateWorktreeBootstrapState,
8937
+ getRegisteredSubmodulePaths: () => getRegisteredSubmodulePaths,
8935
8938
  isWorktreeBootstrapStaleRunning: () => isWorktreeBootstrapStaleRunning,
8936
8939
  loadMeshWorktreeBootstrapConfig: () => loadMeshWorktreeBootstrapConfig,
8937
8940
  runMeshWorktreeBootstrap: () => runMeshWorktreeBootstrap,
@@ -18418,6 +18421,20 @@ function isNegativeApprovalLabel(value) {
18418
18421
  function hasNegativeApprovalOption(buttons) {
18419
18422
  return (buttons || []).some((button) => isNegativeApprovalLabel(String(button || "")));
18420
18423
  }
18424
+ function hasReliableApprovalAffirmative(buttons) {
18425
+ return (buttons || []).some((button) => {
18426
+ const label = normalizeApprovalLabel(String(button || ""));
18427
+ if (!label) return false;
18428
+ if (/^always allow\b/.test(label)) return true;
18429
+ if (/^yes\b/.test(label)) {
18430
+ if (/\ballow\b/.test(label)) return true;
18431
+ if (/\bask again\b/.test(label)) return true;
18432
+ if (/\bduring this session\b/.test(label)) return true;
18433
+ if (/\bfrom this project\b/.test(label)) return true;
18434
+ }
18435
+ return false;
18436
+ });
18437
+ }
18421
18438
  function getApprovalPositiveHints(provider) {
18422
18439
  const customHints = Array.isArray(provider?.approvalPositiveHints) ? provider.approvalPositiveHints.map((hint) => normalizeApprovalLabel(String(hint || ""))).filter(Boolean) : [];
18423
18440
  return customHints.length > 0 ? customHints : DEFAULT_APPROVAL_POSITIVE_HINTS;
@@ -20240,7 +20257,7 @@ function scopeLines(spec, lines, questionIndex) {
20240
20257
  }
20241
20258
  }
20242
20259
  }
20243
- if (lastSep >= 0 && prevSep >= 0) {
20260
+ if (lastSep >= 0 && prevSep >= 0 && questionIndex >= prevSep && questionIndex < lastSep + 1) {
20244
20261
  return { start: prevSep, end: lastSep + 1 };
20245
20262
  }
20246
20263
  return {
@@ -20333,7 +20350,7 @@ var init_parse_approval = __esm({
20333
20350
  "src/providers/sdk/v1/builders/cli/parse-approval.ts"() {
20334
20351
  "use strict";
20335
20352
  init_visible_region();
20336
- SEPARATOR_RE = /^(?:─|━|═|━){10,}\s*$/;
20353
+ SEPARATOR_RE = /^[─━═╌╍┄┅┈┉]{10,}\s*$/;
20337
20354
  }
20338
20355
  });
20339
20356
 
@@ -30675,6 +30692,35 @@ var DaemonCdpInitializer = class {
30675
30692
 
30676
30693
  // src/commands/handler.ts
30677
30694
  init_builders();
30695
+ init_config();
30696
+
30697
+ // src/config/registry-resolver.ts
30698
+ var DEFAULT_REGISTRY_BASE_URL = "https://api.adhf.dev/api/v1/registry";
30699
+ var DEFAULT_PROVIDER_TARBALL_URL = "https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz";
30700
+ var REGISTRY_URL_ENV_VAR = "ADHDEV_REGISTRY_URL";
30701
+ var PROVIDER_TARBALL_URL_ENV_VAR = "ADHDEV_PROVIDER_TARBALL_URL";
30702
+ function cleanString(value) {
30703
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
30704
+ }
30705
+ function stripTrailingSlashes(url) {
30706
+ return url.replace(/\/+$/, "");
30707
+ }
30708
+ function resolveRegistryBaseUrl(configuredUrl, env = process.env) {
30709
+ const resolved = cleanString(configuredUrl) ?? cleanString(env[REGISTRY_URL_ENV_VAR]) ?? DEFAULT_REGISTRY_BASE_URL;
30710
+ return stripTrailingSlashes(resolved);
30711
+ }
30712
+ function resolveProviderTarballUrl(configuredUrl, env = process.env) {
30713
+ return cleanString(configuredUrl) ?? cleanString(env[PROVIDER_TARBALL_URL_ENV_VAR]) ?? DEFAULT_PROVIDER_TARBALL_URL;
30714
+ }
30715
+ function resolveProviderTarballTarget(configuredUrl, env = process.env) {
30716
+ const url = resolveProviderTarballUrl(configuredUrl, env);
30717
+ const parsed = new URL(url);
30718
+ return {
30719
+ url,
30720
+ hostname: parsed.hostname,
30721
+ path: parsed.pathname + (parsed.search || "")
30722
+ };
30723
+ }
30678
30724
 
30679
30725
  // src/sessions/reconcile.ts
30680
30726
  function upsertSessionTarget(sessionRegistry, target) {
@@ -30788,6 +30834,10 @@ var MANUAL_ATTENDANCE_COMMANDS = /* @__PURE__ */ new Set([
30788
30834
  "resolve_action",
30789
30835
  "pty_input"
30790
30836
  ]);
30837
+ var MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS = /* @__PURE__ */ new Set([
30838
+ "select_session",
30839
+ "open_panel"
30840
+ ]);
30791
30841
 
30792
30842
  // src/chat/chat-signatures.ts
30793
30843
  function hashSignatureParts(parts) {
@@ -35270,13 +35320,14 @@ var DaemonCommandHandler = class {
35270
35320
  */
35271
35321
  noteManualAttendanceIfApplicable(cmd, args) {
35272
35322
  if (!MANUAL_ATTENDANCE_COMMANDS.has(cmd)) return;
35323
+ const passive = MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS.has(cmd);
35273
35324
  const sessionId = this._currentRoute.session?.sessionId || (typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "");
35274
35325
  if (!sessionId) return;
35275
35326
  const session = this._ctx.sessionRegistry?.get(sessionId);
35276
35327
  const instanceKey = session?.adapterKey || session?.instanceKey || sessionId;
35277
35328
  const instance = this._ctx.instanceManager?.getInstance(instanceKey);
35278
35329
  try {
35279
- instance?.noteManualInteraction?.();
35330
+ instance?.noteManualInteraction?.(void 0, { passive });
35280
35331
  } catch {
35281
35332
  }
35282
35333
  }
@@ -35567,7 +35618,7 @@ var DaemonCommandHandler = class {
35567
35618
  const https = require("https");
35568
35619
  const fs40 = require("fs");
35569
35620
  const path44 = require("path");
35570
- const REGISTRY = "https://api.adhf.dev/api/v1/registry";
35621
+ const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
35571
35622
  function fetchText(url, timeoutMs) {
35572
35623
  return new Promise((resolve25, reject) => {
35573
35624
  const req = https.get(url, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: timeoutMs }, (res) => {
@@ -35929,7 +35980,7 @@ ${formatManifestValidationIssues2(validation.issues)}`,
35929
35980
  const installed = this.handleListInstalledProviders({});
35930
35981
  if (!installed.success) return installed;
35931
35982
  const https = require("https");
35932
- const REGISTRY = "https://api.adhf.dev/api/v1/registry";
35983
+ const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
35933
35984
  function fetchJson(url) {
35934
35985
  return new Promise((resolve25, reject) => {
35935
35986
  const req = https.get(url, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: 1e4 }, (res) => {
@@ -37532,6 +37583,7 @@ var CHANNEL_SERVER_URL = {
37532
37583
  stable: "https://api.adhf.dev",
37533
37584
  preview: "https://api-preview.adhf.dev"
37534
37585
  };
37586
+ var VENDOR_SERVER_URLS = new Set(Object.values(CHANNEL_SERVER_URL));
37535
37587
  function normalizeReleaseChannel(value) {
37536
37588
  if (typeof value !== "string") return null;
37537
37589
  const normalized = value.trim().toLowerCase();
@@ -37553,7 +37605,11 @@ var daemonLifecycleHandlers = {
37553
37605
  const npmTag = CHANNEL_NPM_TAG[channel];
37554
37606
  const latest = String(execNpmCommandSync(["view", `${pkgName}@${npmTag}`, "version"], { encoding: "utf-8", timeout: 1e4 }, npmSurface)).trim();
37555
37607
  LOG.info("Upgrade", `Latest ${pkgName}@${npmTag}: v${latest}`);
37556
- updateConfig({ updateChannel: channel, serverUrl: CHANNEL_SERVER_URL[channel] });
37608
+ const currentServerUrl = typeof loadConfig().serverUrl === "string" ? loadConfig().serverUrl.trim() : "";
37609
+ const useVendorServerUrl = currentServerUrl === "" || VENDOR_SERVER_URLS.has(currentServerUrl);
37610
+ updateConfig(
37611
+ useVendorServerUrl ? { updateChannel: channel, serverUrl: CHANNEL_SERVER_URL[channel] } : { updateChannel: channel }
37612
+ );
37557
37613
  let currentInstalled = null;
37558
37614
  try {
37559
37615
  const currentJson = String(execNpmCommandSync(["ls", "-g", pkgName, "--depth=0", "--json"], {
@@ -38261,6 +38317,7 @@ function applyPreLaunchTrust(trust, workingDir) {
38261
38317
 
38262
38318
  // src/providers/spec/fsm-driver.ts
38263
38319
  init_logger();
38320
+ init_debug_config();
38264
38321
  init_pty_write_chunking();
38265
38322
  function countNewlines(s2) {
38266
38323
  let n = 0;
@@ -38329,6 +38386,11 @@ var FsmDriver = class {
38329
38386
  * (−1 = whole screen), or a `section:<id>` / `<region>#ignore:<pat>` string
38330
38387
  * when the clause scopes to a section or declares an ignore_lines filter. */
38331
38388
  regionLastChangedAt = /* @__PURE__ */ new Map();
38389
+ /** COMPLETION-EARLYNOTIFY stable-eval trace: last stable/not-stable verdict
38390
+ * recorded per stable region, so the trace fires only when the verdict FLIPS
38391
+ * (not every quiet frame). Cleared on every transition alongside
38392
+ * regionLastChangedAt. Diagnostic-only — never consulted by the FSM. */
38393
+ stableVerdictCache = /* @__PURE__ */ new Map();
38332
38394
  /** Timer that re-runs evaluate() when a time-condition would flip true
38333
38395
  * with no PTY frame to trigger it. */
38334
38396
  wakeTimer = null;
@@ -38664,6 +38726,7 @@ var FsmDriver = class {
38664
38726
  this.currentStateId = fired.to;
38665
38727
  this.stateEnteredAt = now;
38666
38728
  this.regionLastChangedAt.clear();
38729
+ this.stableVerdictCache.clear();
38667
38730
  this.pushHistory(fired.to, stateById(this.spec, fired.to)?.label ?? fired.to, {
38668
38731
  reason: "transition",
38669
38732
  via: `${from}\u2192${fired.to}`,
@@ -38768,6 +38831,7 @@ var FsmDriver = class {
38768
38831
  trackRegionChanges(currentLines, cursor, now) {
38769
38832
  if (this.prevScreenLines.length === 0) return;
38770
38833
  const descs = this.stableRegionDescriptors();
38834
+ const stableTraceOn = shouldCollectTraceCategory("fsm-transition");
38771
38835
  const needsSections = descs.some((d) => !!d.section);
38772
38836
  const curSections = needsSections ? resolveSections(this.spec.sections ?? {}, currentLines) : [];
38773
38837
  const prevSections = needsSections ? resolveSections(this.spec.sections ?? {}, this.prevScreenLines) : [];
@@ -38788,6 +38852,28 @@ var FsmDriver = class {
38788
38852
  const cur = filterIgnoredLines(curLines, d.ignoreRe).join("\n");
38789
38853
  const prev = filterIgnoredLines(prevLines, d.ignoreRe).join("\n");
38790
38854
  if (cur !== prev) this.regionLastChangedAt.set(d.key, now);
38855
+ if (stableTraceOn && typeof d.holdMs === "number") {
38856
+ const lastChanged = this.regionLastChangedAt.get(d.key) ?? this.stateEnteredAt;
38857
+ const ageMs = now - lastChanged;
38858
+ const verdict = ageMs >= d.holdMs;
38859
+ if (this.stableVerdictCache.get(d.key) !== verdict) {
38860
+ this.stableVerdictCache.set(d.key, verdict);
38861
+ recordDebugTrace({
38862
+ category: "fsm-transition",
38863
+ stage: "stable-eval",
38864
+ level: "debug",
38865
+ payload: {
38866
+ state: this.currentStateId,
38867
+ regionKey: String(d.key),
38868
+ ignorePattern: d.ignoreRe?.source ?? null,
38869
+ fingerprintLen: cur.length,
38870
+ ageMs,
38871
+ holdMs: d.holdMs,
38872
+ verdict
38873
+ }
38874
+ });
38875
+ }
38876
+ }
38791
38877
  }
38792
38878
  }
38793
38879
  /** Every distinct stable-region descriptor referenced by stable_ms
@@ -39303,7 +39389,8 @@ function collectStableDescriptors(when, byKey) {
39303
39389
  const w = when;
39304
39390
  if ("stable_ms" in w) {
39305
39391
  const key2 = stableRegionKey(w);
39306
- if (!byKey.has(key2)) {
39392
+ const existing = byKey.get(key2);
39393
+ if (!existing) {
39307
39394
  let ignoreRe;
39308
39395
  if (w.ignore_lines) {
39309
39396
  try {
@@ -39311,7 +39398,9 @@ function collectStableDescriptors(when, byKey) {
39311
39398
  } catch {
39312
39399
  }
39313
39400
  }
39314
- byKey.set(key2, { key: key2, section: w.section, cursor_above: w.cursor_above, ignoreRe });
39401
+ 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 });
39402
+ } else if (existing.holdMs === void 0 && typeof w.stable_ms === "number") {
39403
+ existing.holdMs = w.stable_ms;
39315
39404
  }
39316
39405
  return;
39317
39406
  }
@@ -41083,6 +41172,7 @@ function createCliAdapter(provider, workingDir, cliArgs, extraEnv, transportFact
41083
41172
 
41084
41173
  // src/providers/cli-provider-instance.ts
41085
41174
  init_logger();
41175
+ init_debug_config();
41086
41176
  init_mesh_event_trace();
41087
41177
  init_control_effects();
41088
41178
  init_approval_utils();
@@ -41455,6 +41545,13 @@ var CliProviderInstance = class _CliProviderInstance {
41455
41545
  // mask is dropped so the real waiting_approval surfaces. Cleared when the episode ends
41456
41546
  // (modal genuinely gone, manual attendance takes over, or auto-approve fires).
41457
41547
  autoApproveMaskSince = 0;
41548
+ // NOTIF-APPROVAL-MASKED (Q1b): the autoApproveMaskSince episode value for which a
41549
+ // stalled-approval coordinator nudge has already been emitted, so the nudge fires
41550
+ // exactly once per stalled auto-approve episode (0 = none emitted). Reusing the
41551
+ // per-episode mask-clock value as the key makes it provider-agnostic (no reliance on
41552
+ // approvalEntrySeq) and self-resetting: each new episode gets a fresh
41553
+ // autoApproveMaskSince timestamp, and the episode-end reset zeroes it.
41554
+ stalledApprovalNudgeEpisode = 0;
41458
41555
  // Provider-common manual-attendance signal: while a human is actively driving
41459
41556
  // this session from the dashboard, auto-approve holds so they can take manual
41460
41557
  // control. Background mesh workers are never attended → delegated auto-approve
@@ -42149,9 +42246,10 @@ var CliProviderInstance = class _CliProviderInstance {
42149
42246
  return restoredHistory.messages;
42150
42247
  }
42151
42248
  completionFinalAssistantEvidence(parsedMessages, turnStartedAt) {
42249
+ const turnClosed = !this.hasAdapterPendingResponse();
42152
42250
  if (this.completionHasFinalAssistantMessage(parsedMessages, turnStartedAt)) {
42153
42251
  return {
42154
- present: true,
42252
+ present: turnClosed,
42155
42253
  messages: Array.isArray(parsedMessages) ? parsedMessages : [],
42156
42254
  source: "parsed"
42157
42255
  };
@@ -42159,7 +42257,7 @@ var CliProviderInstance = class _CliProviderInstance {
42159
42257
  const externalMessages = this.readExternalCompletionMessages();
42160
42258
  if (externalMessages) {
42161
42259
  return {
42162
- present: this.completionHasFinalAssistantMessage(externalMessages, turnStartedAt),
42260
+ present: turnClosed && this.completionHasFinalAssistantMessage(externalMessages, turnStartedAt),
42163
42261
  messages: externalMessages,
42164
42262
  source: "external-native"
42165
42263
  };
@@ -42258,11 +42356,9 @@ var CliProviderInstance = class _CliProviderInstance {
42258
42356
  if (latestVisibleStatus !== "idle") return { reason: `status:${latestVisibleStatus}`, terminal: true };
42259
42357
  const adapterAny = this.adapter;
42260
42358
  const approvalResolvedIdle = pending.previousStatus === "waiting_approval";
42261
- if (!approvalResolvedIdle) {
42262
- if (adapterAny?.isWaitingForResponse === true) return { reason: "adapter_waiting_for_response", terminal: true };
42263
- if (adapterAny?.currentTurnScope) return { reason: "adapter_turn_scope_active", terminal: true };
42264
- if (this.hasAdapterPendingResponse()) return { reason: "adapter_pending_response", terminal: true };
42265
- }
42359
+ if (adapterAny?.isWaitingForResponse === true) return { reason: "adapter_waiting_for_response", terminal: !approvalResolvedIdle };
42360
+ if (adapterAny?.currentTurnScope) return { reason: "adapter_turn_scope_active", terminal: !approvalResolvedIdle };
42361
+ if (this.hasAdapterPendingResponse()) return { reason: "adapter_pending_response", terminal: !approvalResolvedIdle };
42266
42362
  const partial = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
42267
42363
  if (typeof partial === "string" && partial.trim()) return { reason: "partial_response_pending", terminal: true };
42268
42364
  let parsed;
@@ -42417,6 +42513,39 @@ var CliProviderInstance = class _CliProviderInstance {
42417
42513
  event
42418
42514
  };
42419
42515
  }
42516
+ // COMPLETION-EARLYNOTIFY instrumentation. A session-keyed FSM-transition +
42517
+ // completion-gate snapshot recorded into the shared debug-trace ring buffer
42518
+ // (secret-safe, length/role/pattern-name only — never screen or bubble text).
42519
+ // Retrieved via getRecentDebugTrace (chat_debug_bundle). Both categories are a
42520
+ // no-op unless collectDebugTrace is on AND the category is selected, so the
42521
+ // hot-path guards below (completionTraceOn / fsmTraceOn) keep production cost
42522
+ // at a single boolean check.
42523
+ completionTraceOn() {
42524
+ return shouldCollectTraceCategory("completion-gate");
42525
+ }
42526
+ fsmTraceOn() {
42527
+ return shouldCollectTraceCategory("fsm-transition");
42528
+ }
42529
+ recordCompletionGateTrace(stage, payload) {
42530
+ recordDebugTrace({
42531
+ category: "completion-gate",
42532
+ stage,
42533
+ level: "debug",
42534
+ sessionId: this.instanceId,
42535
+ providerType: this.type,
42536
+ payload
42537
+ });
42538
+ }
42539
+ recordFsmTransitionTrace(payload) {
42540
+ recordDebugTrace({
42541
+ category: "fsm-transition",
42542
+ stage: "transition",
42543
+ level: "debug",
42544
+ sessionId: this.instanceId,
42545
+ providerType: this.type,
42546
+ payload
42547
+ });
42548
+ }
42420
42549
  flushCompletedDebounceIfFinalized() {
42421
42550
  const pending = this.completedDebouncePending;
42422
42551
  if (!pending) {
@@ -42429,12 +42558,27 @@ var CliProviderInstance = class _CliProviderInstance {
42429
42558
  LOG.debug("CLI", `[${this.type}] flush attempt: adapterStatus=${latestStatus.status} latestVisible=${latestVisibleStatus} generatingStartedAt=${this.generatingStartedAt} isWaitingForResponse=${!!this.adapter?.isWaitingForResponse} hasPartial=${!!this.adapter.getPartialResponse?.()}`);
42430
42559
  if (latestVisibleStatus !== "idle") {
42431
42560
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
42561
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("cancel", {
42562
+ blockReason: "resumed_status",
42563
+ latestVisibleStatus,
42564
+ previousStatus: pending.previousStatus,
42565
+ busyEpochAtArm: pending.busyEpochAtArm,
42566
+ busyEpoch: this.busyEpoch
42567
+ });
42432
42568
  this.completedDebouncePending = null;
42433
42569
  this.completedDebounceTimer = null;
42434
42570
  return;
42435
42571
  }
42436
42572
  if (typeof pending.busyEpochAtArm === "number" && this.busyEpoch !== pending.busyEpochAtArm) {
42437
42573
  LOG.info("CLI", `[${this.type}] cancelled pending completed (busy re-entry during settle: epoch ${pending.busyEpochAtArm}\u2192${this.busyEpoch})`);
42574
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("cancel", {
42575
+ blockReason: "busy_reentry",
42576
+ latestVisibleStatus,
42577
+ previousStatus: pending.previousStatus,
42578
+ busyEpochAtArm: pending.busyEpochAtArm,
42579
+ busyEpoch: this.busyEpoch,
42580
+ busyEpochDelta: this.busyEpoch - pending.busyEpochAtArm
42581
+ });
42438
42582
  this.completedDebouncePending = null;
42439
42583
  this.completedDebounceTimer = null;
42440
42584
  return;
@@ -42442,6 +42586,14 @@ var CliProviderInstance = class _CliProviderInstance {
42442
42586
  const latestOutputAt = typeof latestStatus?.lastOutputAt === "number" ? latestStatus.lastOutputAt : void 0;
42443
42587
  if (typeof pending.lastOutputAtArm === "number" && typeof latestOutputAt === "number" && latestOutputAt > pending.lastOutputAtArm) {
42444
42588
  LOG.info("CLI", `[${this.type}] cancelled pending completed (new PTY output during settle: ${pending.lastOutputAtArm}\u2192${latestOutputAt})`);
42589
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("cancel", {
42590
+ blockReason: "new_pty_output",
42591
+ latestVisibleStatus,
42592
+ previousStatus: pending.previousStatus,
42593
+ lastOutputAtArm: pending.lastOutputAtArm,
42594
+ lastOutputAt: latestOutputAt,
42595
+ lastOutputAtDelta: latestOutputAt - pending.lastOutputAtArm
42596
+ });
42445
42597
  this.completedDebouncePending = null;
42446
42598
  this.completedDebounceTimer = null;
42447
42599
  return;
@@ -42458,6 +42610,14 @@ var CliProviderInstance = class _CliProviderInstance {
42458
42610
  if (this.isMeshWorkerSession()) {
42459
42611
  traceMeshEventDrop("completion_gate_hold", this.meshTraceCtx(), `${blockReason} waited=${waitedMs}ms`);
42460
42612
  }
42613
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("hold", {
42614
+ blockReason,
42615
+ latestVisibleStatus,
42616
+ terminal: block2.terminal === true,
42617
+ holdForTranscript: block2.holdForTranscript === true,
42618
+ approvalResolvedIdle: pending.previousStatus === "waiting_approval",
42619
+ waitedMs
42620
+ });
42461
42621
  pending.loggedBlockReason = blockReason;
42462
42622
  }
42463
42623
  this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
@@ -42477,6 +42637,19 @@ var CliProviderInstance = class _CliProviderInstance {
42477
42637
  if (this.isMeshWorkerSession()) {
42478
42638
  traceMeshEventStage("fired", this.meshTraceCtx(), `forced after ${waitedMs}ms (${blockReason})`);
42479
42639
  }
42640
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("fire", {
42641
+ path: isTranscriptEvidenceGate && !emittedAfterFinalizationTimeout ? "canon_c_decoupled" : "forced_timeout",
42642
+ blockReason,
42643
+ latestVisibleStatus,
42644
+ approvalResolvedIdle: pending.previousStatus === "waiting_approval",
42645
+ finalAssistantPresent: completionDiagnostic.finalAssistantPresent === true,
42646
+ evidenceSource: completionDiagnostic.finalAssistantEvidenceSource ?? null,
42647
+ lastVisibleRole: completionDiagnostic.lastVisibleRole ?? null,
42648
+ lastVisibleContentLen: completionDiagnostic.lastVisibleContentLength ?? null,
42649
+ emittedAfterFinalizationTimeout,
42650
+ waitedMs,
42651
+ busyEpoch: this.busyEpoch
42652
+ });
42480
42653
  this.pushEvent({
42481
42654
  event: "agent:generating_completed",
42482
42655
  chatTitle: pending.chatTitle,
@@ -42504,6 +42677,14 @@ var CliProviderInstance = class _CliProviderInstance {
42504
42677
  if (this.isMeshWorkerSession()) {
42505
42678
  traceMeshEventStage("fired", this.meshTraceCtx(), `duration=${pending.duration}s`);
42506
42679
  }
42680
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("fire", {
42681
+ path: "clean",
42682
+ latestVisibleStatus,
42683
+ approvalResolvedIdle: pending.previousStatus === "waiting_approval",
42684
+ finalAssistantPresent: true,
42685
+ duration: pending.duration,
42686
+ busyEpoch: this.busyEpoch
42687
+ });
42507
42688
  this.pushEvent({
42508
42689
  event: "agent:generating_completed",
42509
42690
  chatTitle: pending.chatTitle,
@@ -42525,6 +42706,7 @@ var CliProviderInstance = class _CliProviderInstance {
42525
42706
  this.pendingAutoApprovalSince = 0;
42526
42707
  this.autoApproveInactiveSince = 0;
42527
42708
  this.autoApproveMaskSince = 0;
42709
+ this.stalledApprovalNudgeEpisode = 0;
42528
42710
  if (this.autoApproveSettleTimer) clearTimeout(this.autoApproveSettleTimer);
42529
42711
  this.autoApproveSettleTimer = setTimeout(() => {
42530
42712
  this.autoApproveSettleTimer = null;
@@ -42551,6 +42733,7 @@ var CliProviderInstance = class _CliProviderInstance {
42551
42733
  this.pendingAutoApprovalSince = 0;
42552
42734
  this.autoApproveInactiveSince = 0;
42553
42735
  this.autoApproveMaskSince = 0;
42736
+ this.stalledApprovalNudgeEpisode = 0;
42554
42737
  if (this.autoApproveSettleTimer) {
42555
42738
  clearTimeout(this.autoApproveSettleTimer);
42556
42739
  this.autoApproveSettleTimer = null;
@@ -42559,6 +42742,7 @@ var CliProviderInstance = class _CliProviderInstance {
42559
42742
  }
42560
42743
  this.autoApproveInactiveSince = 0;
42561
42744
  if (!this.autoApproveMaskSince) this.autoApproveMaskSince = now;
42745
+ this.maybeEmitStalledApprovalNudge(adapterStatus, now);
42562
42746
  const modal = adapterStatus.activeModal;
42563
42747
  const buttons = Array.isArray(modal?.buttons) ? modal.buttons.map((b) => String(b || "").trim()).filter(Boolean) : [];
42564
42748
  if (!modal || buttons.length === 0) {
@@ -42569,7 +42753,8 @@ var CliProviderInstance = class _CliProviderInstance {
42569
42753
  return autoApproveActive;
42570
42754
  }
42571
42755
  const { index: buttonIndex, label: buttonLabel } = pickApprovalButton(buttons, this.provider);
42572
- if (buttonIndex < 0 || !hasNegativeApprovalOption(buttons)) {
42756
+ const hasReliableConsentAnchor = hasNegativeApprovalOption(buttons) || hasReliableApprovalAffirmative(buttons);
42757
+ if (buttonIndex < 0 || !hasReliableConsentAnchor) {
42573
42758
  return autoApproveActive;
42574
42759
  }
42575
42760
  const modalSignature = [
@@ -42605,6 +42790,7 @@ var CliProviderInstance = class _CliProviderInstance {
42605
42790
  this.pendingAutoApprovalSince = 0;
42606
42791
  this.autoApproveInactiveSince = 0;
42607
42792
  this.autoApproveMaskSince = 0;
42793
+ this.stalledApprovalNudgeEpisode = 0;
42608
42794
  if (this.autoApproveBusyTimer) clearTimeout(this.autoApproveBusyTimer);
42609
42795
  this.autoApproveBusyTimer = setTimeout(() => {
42610
42796
  this.autoApproveBusy = false;
@@ -42732,6 +42918,18 @@ var CliProviderInstance = class _CliProviderInstance {
42732
42918
  const previousStatus = this.lastStatus;
42733
42919
  if (newStatus !== this.lastStatus) {
42734
42920
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
42921
+ if (this.fsmTraceOn()) this.recordFsmTransitionTrace({
42922
+ from: this.lastStatus,
42923
+ to: newStatus,
42924
+ rawStatus,
42925
+ autoApproveActive,
42926
+ autoApproveHoldIdle,
42927
+ autoApproveBusy: this.autoApproveBusy,
42928
+ hasPending: this.hasAdapterPendingResponse(),
42929
+ busyEpoch: this.busyEpoch,
42930
+ lastOutputAt: typeof adapterStatus?.lastOutputAt === "number" ? adapterStatus.lastOutputAt : null,
42931
+ lastScreenChangeAt: typeof adapterStatus?.lastScreenChangeAt === "number" ? adapterStatus.lastScreenChangeAt : null
42932
+ });
42735
42933
  const startingToGeneratingWithActiveTurn = this.lastStatus === "starting" && newStatus === "generating" && this.hasAdapterPendingResponse();
42736
42934
  if (this.lastStatus === "idle" && newStatus === "generating" || startingToGeneratingWithActiveTurn) {
42737
42935
  if (this.completedDebouncePending && this.generatingStartedAt === 0) {
@@ -42858,6 +43056,17 @@ var CliProviderInstance = class _CliProviderInstance {
42858
43056
  if (this.isMeshWorkerSession()) {
42859
43057
  traceMeshEventStage("arm", this.meshTraceCtx(), `short-generating settle-arm (source=${shortEvidenceSource}, missingEvidence=${missingEvidence})`);
42860
43058
  }
43059
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("arm", {
43060
+ branch: "short_generating",
43061
+ previousStatus: this.lastStatus,
43062
+ turnStartedAt: shortTurnStartedAt || null,
43063
+ busyEpochAtArm: this.busyEpoch,
43064
+ lastOutputAtArm: typeof adapterStatus?.lastOutputAt === "number" ? adapterStatus.lastOutputAt : null,
43065
+ flushDelay: NATIVE_HISTORY_MESH_IDLE_SETTLE_MS,
43066
+ evidenceSource: shortEvidenceSource,
43067
+ missingEvidence,
43068
+ hasFinalSummary: !!shortFinalSummary
43069
+ });
42861
43070
  this.scheduleCompletedDebounceFlush(NATIVE_HISTORY_MESH_IDLE_SETTLE_MS);
42862
43071
  } else if (missingEvidence) {
42863
43072
  LOG.info("CLI", `[${this.type}] short completion suppressed: missing final assistant evidence, non-mesh session (source=${shortEvidenceSource})`);
@@ -42906,6 +43115,16 @@ var CliProviderInstance = class _CliProviderInstance {
42906
43115
  const meshSettleSession = this.isAutonomousMeshSession();
42907
43116
  const flushDelay = ownsExternalHistory ? meshSettleSession ? NATIVE_HISTORY_MESH_IDLE_SETTLE_MS : 0 : 3e3;
42908
43117
  LOG.debug("CLI", `[${this.type}] set completedDebouncePending duration=${duration}s ownsExternalHistory=${ownsExternalHistory} meshSettle=${meshSettleSession} flushDelay=${flushDelay}ms generatingStartedAt=${this.generatingStartedAt}`);
43118
+ if (this.completionTraceOn()) this.recordCompletionGateTrace("arm", {
43119
+ branch: "normal",
43120
+ previousStatus: this.completedDebouncePending.previousStatus,
43121
+ turnStartedAt: this.completedDebouncePending.turnStartedAt ?? null,
43122
+ busyEpochAtArm: this.completedDebouncePending.busyEpochAtArm ?? null,
43123
+ lastOutputAtArm: this.completedDebouncePending.lastOutputAtArm ?? null,
43124
+ flushDelay,
43125
+ ownsExternalHistory,
43126
+ meshSettle: meshSettleSession
43127
+ });
42909
43128
  this.scheduleCompletedDebounceFlush(flushDelay);
42910
43129
  }
42911
43130
  } else if (newStatus === "idle" && this.lastStatus === "starting") {
@@ -43158,7 +43377,8 @@ ${effect.notification.body || ""}`.trim();
43158
43377
  return false;
43159
43378
  }
43160
43379
  /** @see ProviderInstance.noteManualInteraction */
43161
- noteManualInteraction(now = Date.now()) {
43380
+ noteManualInteraction(now = Date.now(), opts) {
43381
+ if (opts?.passive && this.isMeshWorkerSession()) return;
43162
43382
  this.manualAttendance.note(now);
43163
43383
  }
43164
43384
  /**
@@ -43182,6 +43402,50 @@ ${effect.notification.body || ""}`.trim();
43182
43402
  autoApproveMaskStalled(now = Date.now()) {
43183
43403
  return this.autoApproveMaskSince > 0 && now - this.autoApproveMaskSince > _CliProviderInstance.AUTO_APPROVE_MASK_STALL_MS;
43184
43404
  }
43405
+ /**
43406
+ * NOTIF-APPROVAL-MASKED (Q1b): surface a delegated worker's STALLED auto-approve modal
43407
+ * to the mesh COORDINATOR, decoupled from the dashboard visible-status mask.
43408
+ *
43409
+ * When auto-approve is configured but the episode never settles (modal parse miss / the
43410
+ * settle gate never satisfied), getState()/detectStatusTransition() fold the raw
43411
+ * `waiting_approval` into `generating` to suppress dashboard flicker — so
43412
+ * detectStatusTransition()'s `waiting_approval` arm never runs and NO agent:waiting_approval
43413
+ * event is emitted. The coordinator's real-time approval-nudge delivery then has no input and
43414
+ * the worker's stuck modal is never surfaced (the live ~25s stall). The dashboard mask is
43415
+ * intentional and stays; this emits the coordinator nudge exactly ONCE, gated on the SAME
43416
+ * raw-waiting_approval + mask-stalled signal resolveModalParkStatus() distinguishes, the
43417
+ * instant the mask-stall threshold trips (the same moment getState un-folds the mask).
43418
+ *
43419
+ * Only delegated worker sessions qualify: a foreground session has no coordinator to notify,
43420
+ * and its own dashboard mask already reveals the modal on stall. A normally-resolving
43421
+ * auto-approve never reaches AUTO_APPROVE_MASK_STALL_MS, so it emits nothing here; and if a
43422
+ * masked approval clears just as this fires, rc.455's isApprovalNudgeResolved stale-drop
43423
+ * discards the nudge coordinator-side without noise. Dedup is per-episode (keyed on the
43424
+ * mask-clock value) so a modal that flaps between parsed/unparsed states is announced once.
43425
+ */
43426
+ maybeEmitStalledApprovalNudge(adapterStatus, now) {
43427
+ if (!this.isMeshWorkerSession()) return;
43428
+ if (adapterStatus?.status !== "waiting_approval") return;
43429
+ if (!this.autoApproveMaskStalled(now)) return;
43430
+ if (this.stalledApprovalNudgeEpisode === this.autoApproveMaskSince) return;
43431
+ this.stalledApprovalNudgeEpisode = this.autoApproveMaskSince;
43432
+ const modal = adapterStatus.activeModal;
43433
+ const dirName = workingDirBasename(this.workingDir);
43434
+ const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
43435
+ this.appendRuntimeSystemMessage(
43436
+ this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
43437
+ `approval_request:${now}`,
43438
+ now
43439
+ );
43440
+ this.pushEvent({
43441
+ event: "agent:waiting_approval",
43442
+ chatTitle,
43443
+ timestamp: now,
43444
+ modalMessage: modal?.message,
43445
+ modalButtons: modal?.buttons
43446
+ });
43447
+ LOG.info("CLI", `[${this.type}] stalled auto-approve nudge \u2192 coordinator (masked ${Math.round((now - this.autoApproveMaskSince) / 1e3)}s)`);
43448
+ }
43185
43449
  recordAutoApproval(modalMessage, buttonLabel, now = Date.now()) {
43186
43450
  this.appendRuntimeSystemMessage(
43187
43451
  formatAutoApprovalMessage(modalMessage, buttonLabel),
@@ -47860,12 +48124,17 @@ var ProviderLoader = class _ProviderLoader {
47860
48124
  logFn;
47861
48125
  versionArchive = null;
47862
48126
  scriptsCache = /* @__PURE__ */ new Map();
48127
+ /**
48128
+ * Resolved registry base URL and provider tarball URL. Resolution order:
48129
+ * explicit config field (constructor option) → env var → vendor default.
48130
+ * See `config/registry-resolver.ts`.
48131
+ */
48132
+ registryBaseUrl;
48133
+ providerTarballUrl;
47863
48134
  /** Inject VersionArchive so resolve() can auto-detect installed versions */
47864
48135
  setVersionArchive(archive) {
47865
48136
  this.versionArchive = archive;
47866
48137
  }
47867
- static GITHUB_TARBALL_URL = "https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz";
47868
- static REGISTRY_BASE_URL = "https://api.adhf.dev/api/v1/registry";
47869
48138
  static META_FILE = ".meta.json";
47870
48139
  static REGISTRY_META_FILE = ".registry-meta.json";
47871
48140
  static REPO_PROVIDER_DIRNAME = "adhdev-providers";
@@ -47933,6 +48202,8 @@ var ProviderLoader = class _ProviderLoader {
47933
48202
  constructor(options) {
47934
48203
  this.logFn = options?.logFn || LOG.forComponent("Provider").asLogFn();
47935
48204
  this.probeStarts = options?.probeStarts ?? [process.cwd(), __dirname];
48205
+ this.registryBaseUrl = resolveRegistryBaseUrl(options?.registryUrl);
48206
+ this.providerTarballUrl = resolveProviderTarballUrl(options?.providerTarballUrl);
47936
48207
  this.defaultProvidersDir = path35.join(os25.homedir(), ".adhdev", "providers");
47937
48208
  const detected = this.detectDefaultUserDir();
47938
48209
  this.userDir = detected.path;
@@ -48965,7 +49236,7 @@ var ProviderLoader = class _ProviderLoader {
48965
49236
  this.log("Registry sync skipped (sourceMode=no-upstream)");
48966
49237
  return { updated: false };
48967
49238
  }
48968
- this.log(`Registry sync starting (${_ProviderLoader.REGISTRY_BASE_URL})...`);
49239
+ this.log(`Registry sync starting (${this.registryBaseUrl})...`);
48969
49240
  const https = require("https");
48970
49241
  const regMetaPath = path35.join(this.upstreamDir, _ProviderLoader.REGISTRY_META_FILE);
48971
49242
  let cachedChecksums = {};
@@ -48976,7 +49247,7 @@ var ProviderLoader = class _ProviderLoader {
48976
49247
  } catch {
48977
49248
  }
48978
49249
  try {
48979
- const listUrl = `${_ProviderLoader.REGISTRY_BASE_URL}/providers`;
49250
+ const listUrl = `${this.registryBaseUrl}/providers`;
48980
49251
  const listBody = await new Promise((resolve25, reject) => {
48981
49252
  const req = https.get(listUrl, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: 1e4 }, (res) => {
48982
49253
  if (res.statusCode !== 200) {
@@ -49000,7 +49271,7 @@ var ProviderLoader = class _ProviderLoader {
49000
49271
  const { type, category, checksum, version } = entry;
49001
49272
  const cacheKey = `${category}/${type}`;
49002
49273
  if (cachedChecksums[cacheKey] === checksum) continue;
49003
- const dlUrl = `${_ProviderLoader.REGISTRY_BASE_URL}/providers/${type}/${version}/download`;
49274
+ const dlUrl = `${this.registryBaseUrl}/providers/${type}/${version}/download`;
49004
49275
  const manifestBody = await new Promise((resolve25, reject) => {
49005
49276
  const req = https.get(dlUrl, { headers: { "User-Agent": "adhdev-daemon", "Accept": "application/json" }, timeout: 3e4 }, (res) => {
49006
49277
  if (res.statusCode !== 200) {
@@ -49067,12 +49338,13 @@ var ProviderLoader = class _ProviderLoader {
49067
49338
  this.log("Upstream check skipped (last check < 30min ago)");
49068
49339
  return { updated: false };
49069
49340
  }
49341
+ const tarballTarget = resolveProviderTarballTarget(this.providerTarballUrl);
49070
49342
  try {
49071
49343
  const etag = await new Promise((resolve25, reject) => {
49072
49344
  const options = {
49073
49345
  method: "HEAD",
49074
- hostname: "github.com",
49075
- path: "/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz",
49346
+ hostname: tarballTarget.hostname,
49347
+ path: tarballTarget.path,
49076
49348
  headers: { "User-Agent": "adhdev-launcher" },
49077
49349
  timeout: 1e4
49078
49350
  };
@@ -49113,7 +49385,7 @@ var ProviderLoader = class _ProviderLoader {
49113
49385
  this.log("Downloading latest providers from GitHub...");
49114
49386
  const tmpTar = path35.join(os25.tmpdir(), `adhdev-providers-${Date.now()}.tar.gz`);
49115
49387
  const tmpExtract = path35.join(os25.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
49116
- await this.downloadFile(_ProviderLoader.GITHUB_TARBALL_URL, tmpTar);
49388
+ await this.downloadFile(tarballTarget.url, tmpTar);
49117
49389
  fs25.mkdirSync(tmpExtract, { recursive: true });
49118
49390
  await execAsync5(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 3e4 });
49119
49391
  const extracted = fs25.readdirSync(tmpExtract);
@@ -49213,7 +49485,7 @@ var ProviderLoader = class _ProviderLoader {
49213
49485
  etag,
49214
49486
  timestamp,
49215
49487
  lastCheck: new Date(timestamp).toISOString(),
49216
- source: _ProviderLoader.GITHUB_TARBALL_URL
49488
+ source: this.providerTarballUrl
49217
49489
  }, null, 2));
49218
49490
  } catch {
49219
49491
  }
@@ -50325,6 +50597,47 @@ async function decideOssCloneSync(ossCtx, worktreeOssSha, sourceSha, rg) {
50325
50597
  if (await isAncestor(worktreeOssSha, sourceSha)) return "advance";
50326
50598
  return "skip_diverged";
50327
50599
  }
50600
+ async function syncClonedWorktreeSubmodules(worktreePath, sourceWorkspace, rg) {
50601
+ const submodulePaths = getRegisteredSubmodulePaths(worktreePath);
50602
+ if (submodulePaths.size === 0) return;
50603
+ const sourceCtx = { workspace: sourceWorkspace, repoRoot: sourceWorkspace, isGitRepo: true };
50604
+ const worktreeCtx = { workspace: worktreePath, repoRoot: worktreePath, isGitRepo: true };
50605
+ const readStdout = (out) => (typeof out === "string" ? out : out?.stdout ?? "").trim();
50606
+ for (const submodulePath of submodulePaths) {
50607
+ try {
50608
+ const sourceStatusOut = await rg(sourceCtx, ["submodule", "status", submodulePath], { timeoutMs: 1e4 });
50609
+ const sourceSha = readStdout(sourceStatusOut).match(/^[+\- ]?([0-9a-f]{40})/)?.[1];
50610
+ if (!sourceSha) continue;
50611
+ const subCtx = {
50612
+ workspace: `${worktreePath}/${submodulePath}`,
50613
+ repoRoot: `${worktreePath}/${submodulePath}`,
50614
+ isGitRepo: true
50615
+ };
50616
+ const worktreeSubSha = readStdout(await rg(subCtx, ["rev-parse", "HEAD"], { timeoutMs: 1e4 }));
50617
+ if (!worktreeSubSha || worktreeSubSha === sourceSha) continue;
50618
+ await rg(subCtx, ["fetch", `${sourceWorkspace}/${submodulePath}`, "HEAD"], { timeoutMs: 6e4 });
50619
+ let action;
50620
+ try {
50621
+ action = await decideOssCloneSync(subCtx, worktreeSubSha, sourceSha, rg);
50622
+ } catch (decideErr) {
50623
+ action = "skip_diverged";
50624
+ console.warn(`[mesh] ${submodulePath} submodule sync guard could not resolve ancestry (kept fresh worktree HEAD): ${decideErr?.message ?? decideErr}`);
50625
+ }
50626
+ if (action === "advance") {
50627
+ await rg(subCtx, ["checkout", sourceSha], { timeoutMs: 1e4 });
50628
+ await rg(worktreeCtx, ["add", submodulePath], { timeoutMs: 1e4 });
50629
+ await rg(worktreeCtx, ["commit", "-m", `chore: sync ${submodulePath} to source node HEAD on clone`], { timeoutMs: 1e4 });
50630
+ console.log(`[mesh] Advanced ${submodulePath} submodule to newer source HEAD ${sourceSha.slice(0, 8)} in worktree`);
50631
+ } else if (action === "skip_rewind") {
50632
+ 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`);
50633
+ } else if (action === "skip_diverged") {
50634
+ 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)`);
50635
+ }
50636
+ } catch (subErr) {
50637
+ console.warn(`[mesh] ${submodulePath} submodule sync to source HEAD failed (best-effort):`, subErr?.message ?? subErr);
50638
+ }
50639
+ }
50640
+ }
50328
50641
  var meshCrudHandlers = {
50329
50642
  list_meshes: async (ctx, _args) => {
50330
50643
  try {
@@ -51029,42 +51342,8 @@ var meshCrudHandlers = {
51029
51342
  submodulesInitialized2 = true;
51030
51343
  const sourceWorkspace = sourceNode.repoRoot || sourceNode.workspace;
51031
51344
  if (sourceWorkspace) {
51032
- try {
51033
- const { runGit: rg } = await Promise.resolve().then(() => (init_git_executor(), git_executor_exports));
51034
- const sourceCtx = { workspace: sourceWorkspace, repoRoot: sourceWorkspace, isGitRepo: true };
51035
- const worktreeCtx = { workspace: result.worktreePath, repoRoot: result.worktreePath, isGitRepo: true };
51036
- const sourceStatusOut = await rg(sourceCtx, ["submodule", "status", "oss"], { timeoutMs: 1e4 });
51037
- const sourceStatusLine = (typeof sourceStatusOut === "string" ? sourceStatusOut : sourceStatusOut?.stdout ?? "").trim();
51038
- const sourceShaMatch = sourceStatusLine.match(/^[+\- ]?([0-9a-f]{40})/);
51039
- const sourceSha = sourceShaMatch?.[1];
51040
- if (sourceSha) {
51041
- const ossCtx = { workspace: `${result.worktreePath}/oss`, repoRoot: `${result.worktreePath}/oss`, isGitRepo: true };
51042
- const worktreeOssHeadOut = await rg(ossCtx, ["rev-parse", "HEAD"], { timeoutMs: 1e4 });
51043
- const worktreeOssSha = (typeof worktreeOssHeadOut === "string" ? worktreeOssHeadOut : worktreeOssHeadOut?.stdout ?? "").trim();
51044
- if (worktreeOssSha && worktreeOssSha !== sourceSha) {
51045
- await rg(ossCtx, ["fetch", `${sourceWorkspace}/oss`, "HEAD"], { timeoutMs: 6e4 });
51046
- let ossAction;
51047
- try {
51048
- ossAction = await decideOssCloneSync(ossCtx, worktreeOssSha, sourceSha, rg);
51049
- } catch (decideErr) {
51050
- ossAction = "skip_diverged";
51051
- console.warn(`[mesh] oss submodule sync guard could not resolve ancestry (kept fresh worktree HEAD): ${decideErr?.message ?? decideErr}`);
51052
- }
51053
- if (ossAction === "advance") {
51054
- await rg(ossCtx, ["checkout", sourceSha], { timeoutMs: 1e4 });
51055
- await rg(worktreeCtx, ["add", "oss"], { timeoutMs: 1e4 });
51056
- await rg(worktreeCtx, ["commit", "-m", "chore: sync oss to source node HEAD on clone"], { timeoutMs: 1e4 });
51057
- console.log(`[mesh] Advanced oss submodule to newer source HEAD ${sourceSha.slice(0, 8)} in worktree`);
51058
- } else if (ossAction === "skip_rewind") {
51059
- 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`);
51060
- } else if (ossAction === "skip_diverged") {
51061
- 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)`);
51062
- }
51063
- }
51064
- }
51065
- } catch (ossErr) {
51066
- console.warn("[mesh] oss submodule sync to source HEAD failed (best-effort):", ossErr.message);
51067
- }
51345
+ const { runGit: rg } = await Promise.resolve().then(() => (init_git_executor(), git_executor_exports));
51346
+ await syncClonedWorktreeSubmodules(result.worktreePath, sourceWorkspace, rg);
51068
51347
  }
51069
51348
  } catch (subErr) {
51070
51349
  console.warn("[mesh] Submodule init failed for worktree:", subErr.message);
@@ -52498,6 +52777,26 @@ var import_node_child_process4 = require("child_process");
52498
52777
  var import_node_fs4 = require("fs");
52499
52778
  var import_node_path2 = require("path");
52500
52779
  var PREVIEW_DEPLOY_RECORD = ".adhdev/preview-deploy.json";
52780
+ var PREVIEW_PIPELINE_SCRIPTS = [
52781
+ "scripts/preview-freshness.mjs",
52782
+ "scripts/smoke-preview-web.mjs",
52783
+ "scripts/deploy-preview-local.mjs"
52784
+ ];
52785
+ function hasDeployPreviewNpmScript(repoRoot) {
52786
+ const pkgPath = (0, import_node_path2.resolve)(repoRoot, "package.json");
52787
+ if (!(0, import_node_fs4.existsSync)(pkgPath)) return false;
52788
+ try {
52789
+ const pkg = JSON.parse((0, import_node_fs4.readFileSync)(pkgPath, "utf8"));
52790
+ return typeof pkg?.scripts?.["deploy:preview"] === "string";
52791
+ } catch {
52792
+ return false;
52793
+ }
52794
+ }
52795
+ function isPreviewPipelineConfigured(repoRoot) {
52796
+ if ((0, import_node_fs4.existsSync)((0, import_node_path2.resolve)(repoRoot, PREVIEW_DEPLOY_RECORD))) return true;
52797
+ if (PREVIEW_PIPELINE_SCRIPTS.some((rel) => (0, import_node_fs4.existsSync)((0, import_node_path2.resolve)(repoRoot, rel)))) return true;
52798
+ return hasDeployPreviewNpmScript(repoRoot);
52799
+ }
52501
52800
  function runGit2(repoRoot, args) {
52502
52801
  try {
52503
52802
  return (0, import_node_child_process4.execFileSync)("git", args, {
@@ -52549,6 +52848,7 @@ function readCurrentMainCommit(repoRoot) {
52549
52848
  return { currentMainCommit: null, currentMainCommitSource: "unknown" };
52550
52849
  }
52551
52850
  function buildPreviewFreshness(repoRoot) {
52851
+ if (!isPreviewPipelineConfigured(repoRoot)) return null;
52552
52852
  const current = readCurrentMainCommit(repoRoot);
52553
52853
  const record = readRecord6(repoRoot);
52554
52854
  const lastPreviewCommit = normalizeCommit(record?.lastPreviewCommit);
@@ -66259,7 +66559,9 @@ async function initDaemonComponents(config) {
66259
66559
  const providerLoader = new ProviderLoader({
66260
66560
  logFn: config.providerLogFn,
66261
66561
  sourceMode: providerSourceMode,
66262
- userDir: appConfig.providerDir
66562
+ userDir: appConfig.providerDir,
66563
+ registryUrl: appConfig.registryUrl,
66564
+ providerTarballUrl: appConfig.providerTarballUrl
66263
66565
  });
66264
66566
  providerLoader.loadAll();
66265
66567
  providerLoader.registerToDetector();