@adhdev/daemon-core 0.9.82-rc.70 → 0.9.82-rc.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1192,6 +1192,7 @@ function buildRulesSection(coordinatorCliType) {
1192
1192
  - **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
1193
1193
  - **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, fast-forward obvious clean behind-only branches with \`mesh_fast_forward_node\`, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
1194
1194
  - **Keep Refinery validation project-configurable.** \`mesh_refine_node\` must execute validation from repo mesh/refine config (for example \`.adhdev/refine.{json,yaml,yml}\`, \`.adhdev/repo-mesh-refine.*\`, or \`repo-mesh.refine.*\`). Heuristics are suggestions/scaffolding only, not the execution path.
1195
+ - **Treat submodule reachability as publish-needed.** A \`submodule_reachability_failed\` refine result means the root gitlink points at a submodule commit that is not reachable from the configured submodule remote. Do not retry validation blindly or start code review first. Classify it as \`blocked_review\`, request user approval to push/publish the submodule commit, then rerun \`mesh_refine_node\`.
1195
1196
  - **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
1196
1197
  }
1197
1198
  var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
@@ -1238,7 +1239,7 @@ Before doing any coordinator work, confirm that the actual callable tool list in
1238
1239
  4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
1239
1240
  5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
1240
1241
  6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
1241
- 7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use \`mesh_fast_forward_node\` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
1242
+ 7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use \`mesh_fast_forward_node\` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use \`mesh_refine_node\` for clean worktree branches when safe. Before/refine merging root commits that contain submodule gitlink changes, require each submodule commit to be reachable from its configured remote. If \`mesh_refine_node\` returns \`submodule_reachability_failed\` or publish-required evidence, keep the public convergence bucket as \`blocked_review\`, ask the user for explicit approval to push/publish the unreachable submodule commit(s), then rerun \`mesh_refine_node\`; do not merge the root branch until the submodule commit(s) are reachable. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
1242
1243
  8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
1243
1244
  9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
1244
1245
 
@@ -15819,6 +15820,7 @@ function buildSessionModalDeliverySignature(payload) {
15819
15820
  var RECENT_SEND_WINDOW_MS = 1200;
15820
15821
  var READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25e3;
15821
15822
  var HERMES_CLI_STARTING_SEND_SETTLE_MS = 2e3;
15823
+ var CODEX_NATIVE_HISTORY_FRESH_MS = 5 * 6e4;
15822
15824
  var recentSendByTarget = /* @__PURE__ */ new Map();
15823
15825
  function getCurrentProviderType(h, fallback = "") {
15824
15826
  return h.currentSession?.providerType || h.currentProviderType || fallback;
@@ -15952,6 +15954,77 @@ function normalizeReadChatMessages(payload) {
15952
15954
  const messages = Array.isArray(payload.messages) ? payload.messages : [];
15953
15955
  return normalizeChatMessages(messages);
15954
15956
  }
15957
+ function getMessageNewestReceivedAt(messages) {
15958
+ let newest = 0;
15959
+ for (const message of messages) {
15960
+ const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
15961
+ if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
15962
+ }
15963
+ return newest;
15964
+ }
15965
+ function buildCliMessageSourceProvenance(args) {
15966
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
15967
+ const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : void 0;
15968
+ const nativeMessages = args.nativeMessages || [];
15969
+ const ptyMessages = args.ptyMessages || [];
15970
+ const returnedMessages = args.returnedMessages || [];
15971
+ return {
15972
+ selected: args.selected,
15973
+ provider: args.provider,
15974
+ ...args.nativeHandle ? { nativeHandle: args.nativeHandle } : {},
15975
+ ...args.fallbackReason ? { fallbackReason: args.fallbackReason } : {},
15976
+ ...args.nativeSource ? { nativeSource: args.nativeSource } : {},
15977
+ ...args.sourcePath ? { sourcePath: args.sourcePath } : {},
15978
+ ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
15979
+ staleness: {
15980
+ sourceMtimeMs: sourceMtimeMs || void 0,
15981
+ sourceMtimeAgeMs,
15982
+ nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
15983
+ ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
15984
+ freshEnough: args.freshEnough === true
15985
+ },
15986
+ coverage: {
15987
+ nativeMessageCount: nativeMessages.length,
15988
+ ptyMessageCount: ptyMessages.length,
15989
+ returnedMessageCount: returnedMessages.length,
15990
+ safeMapping: args.safeMapping === true
15991
+ }
15992
+ };
15993
+ }
15994
+ function buildNativeHistoryFallbackReason(args) {
15995
+ if (args.providerType !== "codex-cli") return "provider_not_codex_cli";
15996
+ if (args.nativeSource === "native-unavailable") return "native_history_unavailable";
15997
+ if (args.nativeSource && args.nativeSource !== "provider-native") return `native_history_source_${args.nativeSource}`;
15998
+ if (args.nativeMessageCount <= 0) return "native_history_empty";
15999
+ if (!args.safeMapping) return "native_history_not_safely_mapped";
16000
+ if (!args.freshEnough) return "native_history_stale";
16001
+ return "native_history_not_selected";
16002
+ }
16003
+ function isCodexCliProvider(providerType) {
16004
+ return providerType === "codex-cli";
16005
+ }
16006
+ function hasSafeNativeHistoryMapping(args) {
16007
+ const explicitSessionId = String(args.historySessionId || args.providerSessionId || "").trim();
16008
+ if (explicitSessionId) {
16009
+ const messageSessionIds = args.nativeMessages.map((message) => typeof message?.historySessionId === "string" ? message.historySessionId.trim() : "").filter(Boolean);
16010
+ if (messageSessionIds.length === 0) return true;
16011
+ return messageSessionIds.some((id) => id === explicitSessionId);
16012
+ }
16013
+ const workspace = String(args.workspace || "").trim();
16014
+ if (!workspace) return false;
16015
+ return args.nativeMessages.some((message) => String(message?.workspace || "").trim() === workspace);
16016
+ }
16017
+ function isNativeHistoryFreshEnough(args) {
16018
+ const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
16019
+ const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
16020
+ if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
16021
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
16022
+ if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CODEX_NATIVE_HISTORY_FRESH_MS) return true;
16023
+ return ptyNewest === 0 && nativeNewest > 0;
16024
+ }
16025
+ function shouldPreserveReadChatPayloadField(key) {
16026
+ return key === "messageSource" || key === "transcriptProvenance";
16027
+ }
15955
16028
  function deriveHistoryDedupKey(message) {
15956
16029
  const unitKey = typeof message._unitKey === "string" ? message._unitKey.trim() : "";
15957
16030
  if (unitKey) return `read_chat:${unitKey}`;
@@ -16057,6 +16130,7 @@ function buildReadChatCommandResult(payload, args) {
16057
16130
  return {
16058
16131
  success: true,
16059
16132
  ...validatedPayload,
16133
+ ...Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key))),
16060
16134
  messages: sync.messages,
16061
16135
  totalMessages: sync.totalMessages,
16062
16136
  ...returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}
@@ -16233,6 +16307,8 @@ function buildChatDebugBundleSummary(bundle) {
16233
16307
  adapterStatus: debugReadChat.adapterStatus,
16234
16308
  parsedStatus: debugReadChat.parsedStatus,
16235
16309
  returnedStatus: debugReadChat.returnedStatus,
16310
+ selectedMessageSource: debugReadChat.selectedMessageSource,
16311
+ messageSource: debugReadChat.messageSource,
16236
16312
  parsedMsgCount: debugReadChat.parsedMsgCount,
16237
16313
  returnedMsgCount: debugReadChat.returnedMsgCount,
16238
16314
  shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages
@@ -16308,6 +16384,8 @@ async function handleGetChatDebugBundle(h, args) {
16308
16384
  providerSessionId: readResult.providerSessionId,
16309
16385
  transcriptAuthority: readResult.transcriptAuthority,
16310
16386
  coverage: readResult.coverage,
16387
+ messageSource: readResult.messageSource,
16388
+ transcriptProvenance: readResult.transcriptProvenance,
16311
16389
  activeModal: readResult.activeModal,
16312
16390
  messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
16313
16391
  debugReadChat: readResult.debugReadChat
@@ -16505,25 +16583,134 @@ async function handleReadChat(h, args) {
16505
16583
  const runtimeMessageMerger = getTargetInstance(h, args);
16506
16584
  const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages, returnedStatus);
16507
16585
  const returnedMessages = runtimeMessageMerger?.category === "cli" && runtimeMessageMerger.type === adapter.cliType && typeof runtimeMessageMerger.mergeRuntimeChatMessages === "function" ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages) : parsedMessages;
16586
+ const providerType = provider?.type || adapter.cliType;
16587
+ let selectedMessages = returnedMessages;
16588
+ let selectedTitle = title;
16589
+ let selectedProviderSessionId = providerSessionId;
16590
+ let selectedTranscriptAuthority = transcriptAuthority;
16591
+ let selectedCoverage = coverage;
16592
+ let messageSource = buildCliMessageSourceProvenance({
16593
+ selected: "pty-parser",
16594
+ provider: adapter.cliType,
16595
+ fallbackReason: isCodexCliProvider(providerType) ? "native_history_not_checked" : "provider_not_codex_cli",
16596
+ ptyMessages: returnedMessages,
16597
+ returnedMessages,
16598
+ ptyStatusApprovalOnly: false
16599
+ });
16600
+ if (isCodexCliProvider(providerType)) {
16601
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
16602
+ const workspace = typeof args?.workspace === "string" ? args.workspace : typeof h.currentSession?.workspace === "string" ? h.currentSession.workspace : typeof adapter.workingDir === "string" ? adapter.workingDir : void 0;
16603
+ const nativeHistoryLimit = Math.max(
16604
+ normalizeReadChatTailLimit(args) || 0,
16605
+ returnedMessages.length,
16606
+ 200
16607
+ );
16608
+ let nativeHistory = null;
16609
+ try {
16610
+ nativeHistory = readProviderChatHistory(agentStr, {
16611
+ canonicalHistory: provider?.canonicalHistory,
16612
+ historySessionId,
16613
+ workspace,
16614
+ offset: 0,
16615
+ limit: nativeHistoryLimit,
16616
+ excludeRecentCount: 0,
16617
+ historyBehavior: provider?.historyBehavior,
16618
+ scripts: provider?.scripts
16619
+ });
16620
+ } catch (error) {
16621
+ const fallbackReason = `native_history_error:${error?.message || String(error)}`;
16622
+ messageSource = buildCliMessageSourceProvenance({
16623
+ selected: "pty-parser",
16624
+ provider: adapter.cliType,
16625
+ fallbackReason,
16626
+ ptyMessages: returnedMessages,
16627
+ returnedMessages,
16628
+ ptyStatusApprovalOnly: false
16629
+ });
16630
+ nativeHistory = null;
16631
+ }
16632
+ if (nativeHistory) {
16633
+ const nativeMessages = Array.isArray(nativeHistory.messages) ? normalizeChatMessages(nativeHistory.messages) : [];
16634
+ const historyProviderSessionId = typeof nativeHistory?.providerSessionId === "string" ? nativeHistory.providerSessionId : historySessionId;
16635
+ const safeMapping = hasSafeNativeHistoryMapping({
16636
+ historySessionId,
16637
+ providerSessionId,
16638
+ workspace,
16639
+ nativeMessages
16640
+ });
16641
+ const freshEnough = isNativeHistoryFreshEnough({
16642
+ sourceMtimeMs: nativeHistory.sourceMtimeMs,
16643
+ nativeMessages,
16644
+ ptyMessages: returnedMessages
16645
+ });
16646
+ if (nativeHistory.source === "provider-native" && nativeMessages.length > 0 && safeMapping && freshEnough) {
16647
+ selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
16648
+ selectedProviderSessionId = historyProviderSessionId || providerSessionId;
16649
+ selectedTranscriptAuthority = "provider";
16650
+ selectedCoverage = nativeHistory.hasMore ? "tail" : "full";
16651
+ messageSource = buildCliMessageSourceProvenance({
16652
+ selected: "native-history",
16653
+ provider: adapter.cliType,
16654
+ nativeHandle: selectedProviderSessionId || historySessionId,
16655
+ nativeSource: nativeHistory.source,
16656
+ sourcePath: nativeHistory.sourcePath,
16657
+ sourceMtimeMs: nativeHistory.sourceMtimeMs,
16658
+ nativeMessages,
16659
+ ptyMessages: returnedMessages,
16660
+ returnedMessages: selectedMessages,
16661
+ safeMapping,
16662
+ freshEnough,
16663
+ ptyStatusApprovalOnly: true
16664
+ });
16665
+ } else {
16666
+ const fallbackReason = buildNativeHistoryFallbackReason({
16667
+ providerType,
16668
+ nativeSource: nativeHistory.source,
16669
+ nativeMessageCount: nativeMessages.length,
16670
+ safeMapping,
16671
+ freshEnough
16672
+ });
16673
+ messageSource = buildCliMessageSourceProvenance({
16674
+ selected: "pty-parser",
16675
+ provider: adapter.cliType,
16676
+ nativeHandle: historyProviderSessionId || historySessionId,
16677
+ fallbackReason,
16678
+ nativeSource: nativeHistory.source,
16679
+ sourcePath: nativeHistory.sourcePath,
16680
+ sourceMtimeMs: nativeHistory.sourceMtimeMs,
16681
+ nativeMessages,
16682
+ ptyMessages: returnedMessages,
16683
+ returnedMessages,
16684
+ safeMapping,
16685
+ freshEnough,
16686
+ ptyStatusApprovalOnly: false
16687
+ });
16688
+ }
16689
+ }
16690
+ }
16508
16691
  LOG.debug("Command", `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || "")} adapterStatus=${String(adapterStatus.status || "")} parsedStatus=${String(parsedRecord.status || "")} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
16509
16692
  return buildReadChatCommandResult({
16510
- messages: returnedMessages,
16693
+ messages: selectedMessages,
16511
16694
  status: returnedStatus,
16512
16695
  activeModal,
16696
+ messageSource,
16697
+ transcriptProvenance: messageSource,
16513
16698
  debugReadChat: {
16514
16699
  provider: adapter.cliType,
16515
16700
  targetSessionId: String(args?.targetSessionId || ""),
16516
16701
  adapterStatus: String(adapterStatus.status || ""),
16517
16702
  parsedStatus: String(parsedRecord.status || ""),
16518
16703
  returnedStatus: String(returnedStatus || ""),
16519
- shouldPreferAdapterMessages: false,
16704
+ selectedMessageSource: messageSource.selected,
16705
+ messageSource,
16706
+ shouldPreferAdapterMessages: messageSource.selected !== "native-history",
16520
16707
  parsedMsgCount: parsedRecord.messages.length,
16521
- returnedMsgCount: returnedMessages.length
16708
+ returnedMsgCount: selectedMessages.length
16522
16709
  },
16523
- ...title ? { title } : {},
16524
- ...providerSessionId ? { providerSessionId } : {},
16525
- ...transcriptAuthority ? { transcriptAuthority } : {},
16526
- ...coverage ? { coverage } : {}
16710
+ ...selectedTitle ? { title: selectedTitle } : {},
16711
+ ...selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {},
16712
+ ...selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {},
16713
+ ...selectedCoverage ? { coverage: selectedCoverage } : {}
16527
16714
  }, args);
16528
16715
  }
16529
16716
  const historyLimit = normalizeReadChatTailLimit(args);
@@ -19567,7 +19754,7 @@ var CliProviderInstance = class {
19567
19754
  chatTitle: pending.chatTitle,
19568
19755
  duration: pending.duration,
19569
19756
  timestamp: pending.timestamp,
19570
- finalSummary: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
19757
+ finalSummary: blockReason.startsWith("parsed_status:") ? "" : extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
19571
19758
  completionDiagnostic
19572
19759
  });
19573
19760
  this.completedDebouncePending = null;
@@ -21405,6 +21592,10 @@ function commandExists(command) {
21405
21592
  return false;
21406
21593
  }
21407
21594
  }
21595
+ var BUSY_AGENT_STATUSES = /* @__PURE__ */ new Set(["generating", "running", "streaming", "starting", "busy", "waiting", "waiting_approval", "long_generating"]);
21596
+ function normalizeAgentStatus(value) {
21597
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
21598
+ }
21408
21599
  var chalkModule = import_chalk.default;
21409
21600
  var chalkApi = typeof chalkModule.yellow === "function" ? chalkModule : chalkModule.default || null;
21410
21601
  function colorize(color, text) {
@@ -22179,6 +22370,19 @@ Run 'adhdev doctor' for detailed diagnostics.`
22179
22370
  if (!found) throw new Error(`CLI agent not running: ${agentType}`);
22180
22371
  const { adapter, key } = found;
22181
22372
  if (action === "send_chat") {
22373
+ const currentStatus = normalizeAgentStatus(adapter.getStatus?.()?.status);
22374
+ if (BUSY_AGENT_STATUSES.has(currentStatus)) {
22375
+ return {
22376
+ success: false,
22377
+ code: "agent_runtime_busy",
22378
+ reason: "agent_runtime_busy",
22379
+ retryable: true,
22380
+ retryRecommended: true,
22381
+ status: currentStatus,
22382
+ targetSessionId: args?.targetSessionId,
22383
+ error: `CLI agent '${agentType}' is currently ${currentStatus}; retry after the current turn finishes.`
22384
+ };
22385
+ }
22182
22386
  const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
22183
22387
  const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
22184
22388
  if (provider?.category === "acp") {
@@ -26323,6 +26527,10 @@ function recordMeshRefineStage(stages, stage, status, startedAt, details) {
26323
26527
  ...details || {}
26324
26528
  });
26325
26529
  }
26530
+ function buildSubmodulePublishRequiredNextStep(entries) {
26531
+ const refs = entries.map((entry) => `${entry.path}@${entry.commit}`).join(", ");
26532
+ return `Ask the user for explicit approval to push/publish the unreachable submodule commit(s) (${refs}) to their configured submodule remote(s), then rerun mesh_refine_node. Do not merge the root branch until every submodule gitlink commit is reachable from its configured remote.`;
26533
+ }
26326
26534
  async function computeGitPatchId(cwd, fromRef, toRef) {
26327
26535
  const { execFileSync: execFileSync3 } = await import("child_process");
26328
26536
  const diff = execFileSync3("git", ["diff", "--patch", "--full-index", fromRef, toRef], {
@@ -26408,6 +26616,16 @@ async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree) {
26408
26616
  });
26409
26617
  return String(stdout || "");
26410
26618
  };
26619
+ const verifyRemoteCommitReachable = async (remoteUrl, commit) => {
26620
+ const probeDir = fs11.mkdtempSync((0, import_path8.join)((0, import_os3.tmpdir)(), "adhdev-submodule-reachability-"));
26621
+ try {
26622
+ await runGit2(probeDir, ["init", "-q"]);
26623
+ await runGit2(probeDir, ["-c", "protocol.file.allow=always", "fetch", "--depth=1", remoteUrl, commit]);
26624
+ await runGit2(probeDir, ["cat-file", "-e", `${commit}^{commit}`]);
26625
+ } finally {
26626
+ fs11.rmSync(probeDir, { recursive: true, force: true });
26627
+ }
26628
+ };
26411
26629
  const treeOutput = await runGit2(repoRoot, ["ls-tree", "-r", "-z", mergedTree]);
26412
26630
  const gitlinks = treeOutput.split("\0").filter(Boolean).map((record) => {
26413
26631
  const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record);
@@ -26423,27 +26641,43 @@ async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree) {
26423
26641
  try {
26424
26642
  if (!fs11.existsSync(submodulePath)) {
26425
26643
  entry.error = `Submodule checkout missing at ${gitlink.path}`;
26644
+ entry.publishRequired = true;
26426
26645
  entries.push(entry);
26427
26646
  continue;
26428
26647
  }
26429
26648
  entry.checkedLocal = true;
26430
26649
  try {
26431
26650
  await runGit2(submodulePath, ["cat-file", "-e", `${gitlink.commit}^{commit}`]);
26432
- entry.reachable = true;
26433
- entries.push(entry);
26434
- continue;
26651
+ entry.localReachable = true;
26435
26652
  } catch {
26653
+ entry.localReachable = false;
26436
26654
  }
26437
26655
  try {
26438
- await runGit2(submodulePath, ["fetch", "origin", gitlink.commit]);
26656
+ entry.remote = "origin";
26657
+ let remoteUrl = "";
26658
+ try {
26659
+ remoteUrl = (await runGit2(submodulePath, ["remote", "get-url", "origin"])).trim();
26660
+ if (!remoteUrl) throw new Error("origin remote has no URL");
26661
+ entry.remoteUrl = remoteUrl;
26662
+ } catch {
26663
+ entry.error = "Submodule remote reachability check failed: no configured origin remote";
26664
+ entry.publishRequired = true;
26665
+ entries.push(entry);
26666
+ continue;
26667
+ }
26668
+ await verifyRemoteCommitReachable(remoteUrl, gitlink.commit);
26439
26669
  entry.fetchedFromOrigin = true;
26440
- await runGit2(submodulePath, ["cat-file", "-e", `${gitlink.commit}^{commit}`]);
26670
+ entry.remoteReachable = true;
26441
26671
  entry.reachable = true;
26442
26672
  } catch (e) {
26443
- entry.error = truncateValidationOutput(e?.stderr || e?.message || String(e));
26673
+ entry.remoteReachable = false;
26674
+ entry.publishRequired = true;
26675
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
26676
+ entry.error = `Submodule remote reachability check failed for origin: ${details}`;
26444
26677
  }
26445
26678
  } catch (e) {
26446
26679
  entry.error = truncateValidationOutput(e?.message || String(e));
26680
+ entry.publishRequired = true;
26447
26681
  }
26448
26682
  entries.push(entry);
26449
26683
  }
@@ -26451,16 +26685,17 @@ async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree) {
26451
26685
  return {
26452
26686
  status: unreachable.length ? "failed" : "passed",
26453
26687
  checked: entries.length,
26454
- unreachable,
26455
- entries,
26688
+ unreachable: unreachable.map((entry) => ({ ...entry, publishRequired: entry.publishRequired !== false })),
26689
+ entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
26456
26690
  durationMs: Date.now() - startedAt
26457
26691
  };
26458
26692
  } catch (e) {
26693
+ const unreachable = entries.filter((entry) => !entry.reachable).map((entry) => ({ ...entry, publishRequired: true }));
26459
26694
  return {
26460
26695
  status: "failed",
26461
26696
  checked: entries.length,
26462
- unreachable: entries.filter((entry) => !entry.reachable),
26463
- entries,
26697
+ unreachable,
26698
+ entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: true }),
26464
26699
  durationMs: Date.now() - startedAt,
26465
26700
  error: truncateValidationOutput(e?.message || String(e))
26466
26701
  };
@@ -27579,15 +27814,41 @@ var DaemonCommandRouter = class {
27579
27814
  const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead);
27580
27815
  recordMeshRefineStage(refineStages, "submodule_reachability", submoduleReachability.status, submoduleReachabilityStarted, {
27581
27816
  checked: submoduleReachability.checked,
27582
- unreachable: submoduleReachability.unreachable.map((entry) => ({ path: entry.path, commit: entry.commit, error: entry.error })),
27817
+ unreachable: submoduleReachability.unreachable.map((entry) => ({
27818
+ path: entry.path,
27819
+ commit: entry.commit,
27820
+ publishRequired: entry.publishRequired === true,
27821
+ remote: entry.remote,
27822
+ remoteUrl: entry.remoteUrl,
27823
+ remoteReachable: entry.remoteReachable,
27824
+ error: entry.error
27825
+ })),
27583
27826
  error: submoduleReachability.error
27584
27827
  });
27585
27828
  if (submoduleReachability.status === "failed") {
27829
+ const nextStep = buildSubmodulePublishRequiredNextStep(submoduleReachability.unreachable);
27586
27830
  return {
27587
27831
  success: false,
27588
27832
  code: "submodule_reachability_failed",
27589
27833
  convergenceStatus: "blocked_review",
27590
- error: "Refinery submodule reachability preflight failed; merge/refine cleanup was not attempted.",
27834
+ publishRequired: true,
27835
+ blockedReason: "submodule_publish_required",
27836
+ error: "Refinery submodule reachability preflight failed because one or more submodule gitlink commits are not reachable from their configured remote; merge/refine cleanup was not attempted.",
27837
+ nextStep,
27838
+ nextSteps: [
27839
+ "Ask the user for explicit approval before pushing or publishing any submodule commit.",
27840
+ "Push/publish each unreachable submodule commit to the configured submodule remote shown in the evidence.",
27841
+ "Rerun mesh_refine_node after remote reachability is confirmed.",
27842
+ "Do not merge the root branch until every submodule gitlink commit is reachable from its configured remote."
27843
+ ],
27844
+ unreachableSubmoduleCommits: submoduleReachability.unreachable.map((entry) => ({
27845
+ path: entry.path,
27846
+ commit: entry.commit,
27847
+ remote: entry.remote,
27848
+ remoteUrl: entry.remoteUrl,
27849
+ remoteReachable: entry.remoteReachable,
27850
+ error: entry.error
27851
+ })),
27591
27852
  branch,
27592
27853
  into: baseBranch,
27593
27854
  validationSummary,
@@ -27602,7 +27863,9 @@ var DaemonCommandRouter = class {
27602
27863
  validation: "passed",
27603
27864
  patchEquivalence: "passed",
27604
27865
  submoduleReachability: "failed",
27605
- status: "blocked_review"
27866
+ status: "blocked_review",
27867
+ reason: "submodule_publish_required",
27868
+ nextStep
27606
27869
  }
27607
27870
  };
27608
27871
  }