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

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.mjs CHANGED
@@ -1187,6 +1187,7 @@ function buildRulesSection(coordinatorCliType) {
1187
1187
  - **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
1188
1188
  - **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.
1189
1189
  - **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.
1190
+ - **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\`.
1190
1191
  - **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
1191
1192
  }
1192
1193
  var TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION;
@@ -1233,7 +1234,7 @@ Before doing any coordinator work, confirm that the actual callable tool list in
1233
1234
  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\`.
1234
1235
  5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
1235
1236
  6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
1236
- 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.
1237
+ 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.
1237
1238
  8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
1238
1239
  9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
1239
1240
 
@@ -15560,6 +15561,7 @@ function buildSessionModalDeliverySignature(payload) {
15560
15561
  var RECENT_SEND_WINDOW_MS = 1200;
15561
15562
  var READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25e3;
15562
15563
  var HERMES_CLI_STARTING_SEND_SETTLE_MS = 2e3;
15564
+ var CODEX_NATIVE_HISTORY_FRESH_MS = 5 * 6e4;
15563
15565
  var recentSendByTarget = /* @__PURE__ */ new Map();
15564
15566
  function getCurrentProviderType(h, fallback = "") {
15565
15567
  return h.currentSession?.providerType || h.currentProviderType || fallback;
@@ -15693,6 +15695,77 @@ function normalizeReadChatMessages(payload) {
15693
15695
  const messages = Array.isArray(payload.messages) ? payload.messages : [];
15694
15696
  return normalizeChatMessages(messages);
15695
15697
  }
15698
+ function getMessageNewestReceivedAt(messages) {
15699
+ let newest = 0;
15700
+ for (const message of messages) {
15701
+ const receivedAt = Number(message?.receivedAt ?? message?.timestamp ?? 0);
15702
+ if (Number.isFinite(receivedAt) && receivedAt > newest) newest = receivedAt;
15703
+ }
15704
+ return newest;
15705
+ }
15706
+ function buildCliMessageSourceProvenance(args) {
15707
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
15708
+ const sourceMtimeAgeMs = sourceMtimeMs > 0 ? Math.max(0, Date.now() - sourceMtimeMs) : void 0;
15709
+ const nativeMessages = args.nativeMessages || [];
15710
+ const ptyMessages = args.ptyMessages || [];
15711
+ const returnedMessages = args.returnedMessages || [];
15712
+ return {
15713
+ selected: args.selected,
15714
+ provider: args.provider,
15715
+ ...args.nativeHandle ? { nativeHandle: args.nativeHandle } : {},
15716
+ ...args.fallbackReason ? { fallbackReason: args.fallbackReason } : {},
15717
+ ...args.nativeSource ? { nativeSource: args.nativeSource } : {},
15718
+ ...args.sourcePath ? { sourcePath: args.sourcePath } : {},
15719
+ ptyStatusApprovalOnly: args.ptyStatusApprovalOnly === true,
15720
+ staleness: {
15721
+ sourceMtimeMs: sourceMtimeMs || void 0,
15722
+ sourceMtimeAgeMs,
15723
+ nativeNewestMessageAt: getMessageNewestReceivedAt(nativeMessages),
15724
+ ptyNewestMessageAt: getMessageNewestReceivedAt(ptyMessages),
15725
+ freshEnough: args.freshEnough === true
15726
+ },
15727
+ coverage: {
15728
+ nativeMessageCount: nativeMessages.length,
15729
+ ptyMessageCount: ptyMessages.length,
15730
+ returnedMessageCount: returnedMessages.length,
15731
+ safeMapping: args.safeMapping === true
15732
+ }
15733
+ };
15734
+ }
15735
+ function buildNativeHistoryFallbackReason(args) {
15736
+ if (args.providerType !== "codex-cli") return "provider_not_codex_cli";
15737
+ if (args.nativeSource === "native-unavailable") return "native_history_unavailable";
15738
+ if (args.nativeSource && args.nativeSource !== "provider-native") return `native_history_source_${args.nativeSource}`;
15739
+ if (args.nativeMessageCount <= 0) return "native_history_empty";
15740
+ if (!args.safeMapping) return "native_history_not_safely_mapped";
15741
+ if (!args.freshEnough) return "native_history_stale";
15742
+ return "native_history_not_selected";
15743
+ }
15744
+ function isCodexCliProvider(providerType) {
15745
+ return providerType === "codex-cli";
15746
+ }
15747
+ function hasSafeNativeHistoryMapping(args) {
15748
+ const explicitSessionId = String(args.historySessionId || args.providerSessionId || "").trim();
15749
+ if (explicitSessionId) {
15750
+ const messageSessionIds = args.nativeMessages.map((message) => typeof message?.historySessionId === "string" ? message.historySessionId.trim() : "").filter(Boolean);
15751
+ if (messageSessionIds.length === 0) return true;
15752
+ return messageSessionIds.some((id) => id === explicitSessionId);
15753
+ }
15754
+ const workspace = String(args.workspace || "").trim();
15755
+ if (!workspace) return false;
15756
+ return args.nativeMessages.some((message) => String(message?.workspace || "").trim() === workspace);
15757
+ }
15758
+ function isNativeHistoryFreshEnough(args) {
15759
+ const nativeNewest = getMessageNewestReceivedAt(args.nativeMessages);
15760
+ const ptyNewest = getMessageNewestReceivedAt(args.ptyMessages);
15761
+ if (nativeNewest > 0 && nativeNewest >= ptyNewest) return true;
15762
+ const sourceMtimeMs = Number(args.sourceMtimeMs || 0);
15763
+ if (sourceMtimeMs > 0 && Date.now() - sourceMtimeMs <= CODEX_NATIVE_HISTORY_FRESH_MS) return true;
15764
+ return ptyNewest === 0 && nativeNewest > 0;
15765
+ }
15766
+ function shouldPreserveReadChatPayloadField(key) {
15767
+ return key === "messageSource" || key === "transcriptProvenance";
15768
+ }
15696
15769
  function deriveHistoryDedupKey(message) {
15697
15770
  const unitKey = typeof message._unitKey === "string" ? message._unitKey.trim() : "";
15698
15771
  if (unitKey) return `read_chat:${unitKey}`;
@@ -15798,6 +15871,7 @@ function buildReadChatCommandResult(payload, args) {
15798
15871
  return {
15799
15872
  success: true,
15800
15873
  ...validatedPayload,
15874
+ ...Object.fromEntries(Object.entries(payload).filter(([key]) => shouldPreserveReadChatPayloadField(key))),
15801
15875
  messages: sync.messages,
15802
15876
  totalMessages: sync.totalMessages,
15803
15877
  ...returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}
@@ -15974,6 +16048,8 @@ function buildChatDebugBundleSummary(bundle) {
15974
16048
  adapterStatus: debugReadChat.adapterStatus,
15975
16049
  parsedStatus: debugReadChat.parsedStatus,
15976
16050
  returnedStatus: debugReadChat.returnedStatus,
16051
+ selectedMessageSource: debugReadChat.selectedMessageSource,
16052
+ messageSource: debugReadChat.messageSource,
15977
16053
  parsedMsgCount: debugReadChat.parsedMsgCount,
15978
16054
  returnedMsgCount: debugReadChat.returnedMsgCount,
15979
16055
  shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages
@@ -16049,6 +16125,8 @@ async function handleGetChatDebugBundle(h, args) {
16049
16125
  providerSessionId: readResult.providerSessionId,
16050
16126
  transcriptAuthority: readResult.transcriptAuthority,
16051
16127
  coverage: readResult.coverage,
16128
+ messageSource: readResult.messageSource,
16129
+ transcriptProvenance: readResult.transcriptProvenance,
16052
16130
  activeModal: readResult.activeModal,
16053
16131
  messagesTail: Array.isArray(readResult.messages) ? readResult.messages.slice(-20) : [],
16054
16132
  debugReadChat: readResult.debugReadChat
@@ -16246,25 +16324,134 @@ async function handleReadChat(h, args) {
16246
16324
  const runtimeMessageMerger = getTargetInstance(h, args);
16247
16325
  const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages, returnedStatus);
16248
16326
  const returnedMessages = runtimeMessageMerger?.category === "cli" && runtimeMessageMerger.type === adapter.cliType && typeof runtimeMessageMerger.mergeRuntimeChatMessages === "function" ? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages) : parsedMessages;
16327
+ const providerType = provider?.type || adapter.cliType;
16328
+ let selectedMessages = returnedMessages;
16329
+ let selectedTitle = title;
16330
+ let selectedProviderSessionId = providerSessionId;
16331
+ let selectedTranscriptAuthority = transcriptAuthority;
16332
+ let selectedCoverage = coverage;
16333
+ let messageSource = buildCliMessageSourceProvenance({
16334
+ selected: "pty-parser",
16335
+ provider: adapter.cliType,
16336
+ fallbackReason: isCodexCliProvider(providerType) ? "native_history_not_checked" : "provider_not_codex_cli",
16337
+ ptyMessages: returnedMessages,
16338
+ returnedMessages,
16339
+ ptyStatusApprovalOnly: false
16340
+ });
16341
+ if (isCodexCliProvider(providerType)) {
16342
+ const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
16343
+ const workspace = typeof args?.workspace === "string" ? args.workspace : typeof h.currentSession?.workspace === "string" ? h.currentSession.workspace : typeof adapter.workingDir === "string" ? adapter.workingDir : void 0;
16344
+ const nativeHistoryLimit = Math.max(
16345
+ normalizeReadChatTailLimit(args) || 0,
16346
+ returnedMessages.length,
16347
+ 200
16348
+ );
16349
+ let nativeHistory = null;
16350
+ try {
16351
+ nativeHistory = readProviderChatHistory(agentStr, {
16352
+ canonicalHistory: provider?.canonicalHistory,
16353
+ historySessionId,
16354
+ workspace,
16355
+ offset: 0,
16356
+ limit: nativeHistoryLimit,
16357
+ excludeRecentCount: 0,
16358
+ historyBehavior: provider?.historyBehavior,
16359
+ scripts: provider?.scripts
16360
+ });
16361
+ } catch (error) {
16362
+ const fallbackReason = `native_history_error:${error?.message || String(error)}`;
16363
+ messageSource = buildCliMessageSourceProvenance({
16364
+ selected: "pty-parser",
16365
+ provider: adapter.cliType,
16366
+ fallbackReason,
16367
+ ptyMessages: returnedMessages,
16368
+ returnedMessages,
16369
+ ptyStatusApprovalOnly: false
16370
+ });
16371
+ nativeHistory = null;
16372
+ }
16373
+ if (nativeHistory) {
16374
+ const nativeMessages = Array.isArray(nativeHistory.messages) ? normalizeChatMessages(nativeHistory.messages) : [];
16375
+ const historyProviderSessionId = typeof nativeHistory?.providerSessionId === "string" ? nativeHistory.providerSessionId : historySessionId;
16376
+ const safeMapping = hasSafeNativeHistoryMapping({
16377
+ historySessionId,
16378
+ providerSessionId,
16379
+ workspace,
16380
+ nativeMessages
16381
+ });
16382
+ const freshEnough = isNativeHistoryFreshEnough({
16383
+ sourceMtimeMs: nativeHistory.sourceMtimeMs,
16384
+ nativeMessages,
16385
+ ptyMessages: returnedMessages
16386
+ });
16387
+ if (nativeHistory.source === "provider-native" && nativeMessages.length > 0 && safeMapping && freshEnough) {
16388
+ selectedMessages = finalizeStreamingMessagesWhenIdle(nativeMessages, returnedStatus);
16389
+ selectedProviderSessionId = historyProviderSessionId || providerSessionId;
16390
+ selectedTranscriptAuthority = "provider";
16391
+ selectedCoverage = nativeHistory.hasMore ? "tail" : "full";
16392
+ messageSource = buildCliMessageSourceProvenance({
16393
+ selected: "native-history",
16394
+ provider: adapter.cliType,
16395
+ nativeHandle: selectedProviderSessionId || historySessionId,
16396
+ nativeSource: nativeHistory.source,
16397
+ sourcePath: nativeHistory.sourcePath,
16398
+ sourceMtimeMs: nativeHistory.sourceMtimeMs,
16399
+ nativeMessages,
16400
+ ptyMessages: returnedMessages,
16401
+ returnedMessages: selectedMessages,
16402
+ safeMapping,
16403
+ freshEnough,
16404
+ ptyStatusApprovalOnly: true
16405
+ });
16406
+ } else {
16407
+ const fallbackReason = buildNativeHistoryFallbackReason({
16408
+ providerType,
16409
+ nativeSource: nativeHistory.source,
16410
+ nativeMessageCount: nativeMessages.length,
16411
+ safeMapping,
16412
+ freshEnough
16413
+ });
16414
+ messageSource = buildCliMessageSourceProvenance({
16415
+ selected: "pty-parser",
16416
+ provider: adapter.cliType,
16417
+ nativeHandle: historyProviderSessionId || historySessionId,
16418
+ fallbackReason,
16419
+ nativeSource: nativeHistory.source,
16420
+ sourcePath: nativeHistory.sourcePath,
16421
+ sourceMtimeMs: nativeHistory.sourceMtimeMs,
16422
+ nativeMessages,
16423
+ ptyMessages: returnedMessages,
16424
+ returnedMessages,
16425
+ safeMapping,
16426
+ freshEnough,
16427
+ ptyStatusApprovalOnly: false
16428
+ });
16429
+ }
16430
+ }
16431
+ }
16249
16432
  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}`);
16250
16433
  return buildReadChatCommandResult({
16251
- messages: returnedMessages,
16434
+ messages: selectedMessages,
16252
16435
  status: returnedStatus,
16253
16436
  activeModal,
16437
+ messageSource,
16438
+ transcriptProvenance: messageSource,
16254
16439
  debugReadChat: {
16255
16440
  provider: adapter.cliType,
16256
16441
  targetSessionId: String(args?.targetSessionId || ""),
16257
16442
  adapterStatus: String(adapterStatus.status || ""),
16258
16443
  parsedStatus: String(parsedRecord.status || ""),
16259
16444
  returnedStatus: String(returnedStatus || ""),
16260
- shouldPreferAdapterMessages: false,
16445
+ selectedMessageSource: messageSource.selected,
16446
+ messageSource,
16447
+ shouldPreferAdapterMessages: messageSource.selected !== "native-history",
16261
16448
  parsedMsgCount: parsedRecord.messages.length,
16262
- returnedMsgCount: returnedMessages.length
16449
+ returnedMsgCount: selectedMessages.length
16263
16450
  },
16264
- ...title ? { title } : {},
16265
- ...providerSessionId ? { providerSessionId } : {},
16266
- ...transcriptAuthority ? { transcriptAuthority } : {},
16267
- ...coverage ? { coverage } : {}
16451
+ ...selectedTitle ? { title: selectedTitle } : {},
16452
+ ...selectedProviderSessionId ? { providerSessionId: selectedProviderSessionId } : {},
16453
+ ...selectedTranscriptAuthority ? { transcriptAuthority: selectedTranscriptAuthority } : {},
16454
+ ...selectedCoverage ? { coverage: selectedCoverage } : {}
16268
16455
  }, args);
16269
16456
  }
16270
16457
  const historyLimit = normalizeReadChatTailLimit(args);
@@ -19308,7 +19495,7 @@ var CliProviderInstance = class {
19308
19495
  chatTitle: pending.chatTitle,
19309
19496
  duration: pending.duration,
19310
19497
  timestamp: pending.timestamp,
19311
- finalSummary: extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
19498
+ finalSummary: blockReason.startsWith("parsed_status:") ? "" : extractFinalSummaryFromMessages(this.adapter?.getScriptParsedStatus()?.messages),
19312
19499
  completionDiagnostic
19313
19500
  });
19314
19501
  this.completedDebouncePending = null;
@@ -21151,6 +21338,10 @@ function commandExists(command) {
21151
21338
  return false;
21152
21339
  }
21153
21340
  }
21341
+ var BUSY_AGENT_STATUSES = /* @__PURE__ */ new Set(["generating", "running", "streaming", "starting", "busy", "waiting", "waiting_approval", "long_generating"]);
21342
+ function normalizeAgentStatus(value) {
21343
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
21344
+ }
21154
21345
  var chalkModule = chalk;
21155
21346
  var chalkApi = typeof chalkModule.yellow === "function" ? chalkModule : chalkModule.default || null;
21156
21347
  function colorize(color, text) {
@@ -21925,6 +22116,19 @@ Run 'adhdev doctor' for detailed diagnostics.`
21925
22116
  if (!found) throw new Error(`CLI agent not running: ${agentType}`);
21926
22117
  const { adapter, key } = found;
21927
22118
  if (action === "send_chat") {
22119
+ const currentStatus = normalizeAgentStatus(adapter.getStatus?.()?.status);
22120
+ if (BUSY_AGENT_STATUSES.has(currentStatus)) {
22121
+ return {
22122
+ success: false,
22123
+ code: "agent_runtime_busy",
22124
+ reason: "agent_runtime_busy",
22125
+ retryable: true,
22126
+ retryRecommended: true,
22127
+ status: currentStatus,
22128
+ targetSessionId: args?.targetSessionId,
22129
+ error: `CLI agent '${agentType}' is currently ${currentStatus}; retry after the current turn finishes.`
22130
+ };
22131
+ }
21928
22132
  const input = normalizeInputEnvelope(args?.input ? { input: args.input } : args);
21929
22133
  const provider = this.providerLoader.resolve(agentType) || this.providerLoader.getMeta(agentType);
21930
22134
  if (provider?.category === "acp") {
@@ -25257,7 +25461,7 @@ async function maybeRunDaemonUpgradeHelperFromEnv() {
25257
25461
 
25258
25462
  // src/commands/router.ts
25259
25463
  init_mesh_work_queue();
25260
- import { homedir as homedir19 } from "os";
25464
+ import { homedir as homedir19, tmpdir as tmpdir5 } from "os";
25261
25465
  import { join as pathJoin, resolve as pathResolve } from "path";
25262
25466
  import * as fs11 from "fs";
25263
25467
  var CHANNEL_NPM_TAG = { stable: "latest", preview: "next" };
@@ -26069,6 +26273,10 @@ function recordMeshRefineStage(stages, stage, status, startedAt, details) {
26069
26273
  ...details || {}
26070
26274
  });
26071
26275
  }
26276
+ function buildSubmodulePublishRequiredNextStep(entries) {
26277
+ const refs = entries.map((entry) => `${entry.path}@${entry.commit}`).join(", ");
26278
+ 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.`;
26279
+ }
26072
26280
  async function computeGitPatchId(cwd, fromRef, toRef) {
26073
26281
  const { execFileSync: execFileSync3 } = await import("child_process");
26074
26282
  const diff = execFileSync3("git", ["diff", "--patch", "--full-index", fromRef, toRef], {
@@ -26154,6 +26362,16 @@ async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree) {
26154
26362
  });
26155
26363
  return String(stdout || "");
26156
26364
  };
26365
+ const verifyRemoteCommitReachable = async (remoteUrl, commit) => {
26366
+ const probeDir = fs11.mkdtempSync(pathJoin(tmpdir5(), "adhdev-submodule-reachability-"));
26367
+ try {
26368
+ await runGit2(probeDir, ["init", "-q"]);
26369
+ await runGit2(probeDir, ["-c", "protocol.file.allow=always", "fetch", "--depth=1", remoteUrl, commit]);
26370
+ await runGit2(probeDir, ["cat-file", "-e", `${commit}^{commit}`]);
26371
+ } finally {
26372
+ fs11.rmSync(probeDir, { recursive: true, force: true });
26373
+ }
26374
+ };
26157
26375
  const treeOutput = await runGit2(repoRoot, ["ls-tree", "-r", "-z", mergedTree]);
26158
26376
  const gitlinks = treeOutput.split("\0").filter(Boolean).map((record) => {
26159
26377
  const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record);
@@ -26169,27 +26387,43 @@ async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree) {
26169
26387
  try {
26170
26388
  if (!fs11.existsSync(submodulePath)) {
26171
26389
  entry.error = `Submodule checkout missing at ${gitlink.path}`;
26390
+ entry.publishRequired = true;
26172
26391
  entries.push(entry);
26173
26392
  continue;
26174
26393
  }
26175
26394
  entry.checkedLocal = true;
26176
26395
  try {
26177
26396
  await runGit2(submodulePath, ["cat-file", "-e", `${gitlink.commit}^{commit}`]);
26178
- entry.reachable = true;
26179
- entries.push(entry);
26180
- continue;
26397
+ entry.localReachable = true;
26181
26398
  } catch {
26399
+ entry.localReachable = false;
26182
26400
  }
26183
26401
  try {
26184
- await runGit2(submodulePath, ["fetch", "origin", gitlink.commit]);
26402
+ entry.remote = "origin";
26403
+ let remoteUrl = "";
26404
+ try {
26405
+ remoteUrl = (await runGit2(submodulePath, ["remote", "get-url", "origin"])).trim();
26406
+ if (!remoteUrl) throw new Error("origin remote has no URL");
26407
+ entry.remoteUrl = remoteUrl;
26408
+ } catch {
26409
+ entry.error = "Submodule remote reachability check failed: no configured origin remote";
26410
+ entry.publishRequired = true;
26411
+ entries.push(entry);
26412
+ continue;
26413
+ }
26414
+ await verifyRemoteCommitReachable(remoteUrl, gitlink.commit);
26185
26415
  entry.fetchedFromOrigin = true;
26186
- await runGit2(submodulePath, ["cat-file", "-e", `${gitlink.commit}^{commit}`]);
26416
+ entry.remoteReachable = true;
26187
26417
  entry.reachable = true;
26188
26418
  } catch (e) {
26189
- entry.error = truncateValidationOutput(e?.stderr || e?.message || String(e));
26419
+ entry.remoteReachable = false;
26420
+ entry.publishRequired = true;
26421
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
26422
+ entry.error = `Submodule remote reachability check failed for origin: ${details}`;
26190
26423
  }
26191
26424
  } catch (e) {
26192
26425
  entry.error = truncateValidationOutput(e?.message || String(e));
26426
+ entry.publishRequired = true;
26193
26427
  }
26194
26428
  entries.push(entry);
26195
26429
  }
@@ -26197,16 +26431,17 @@ async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree) {
26197
26431
  return {
26198
26432
  status: unreachable.length ? "failed" : "passed",
26199
26433
  checked: entries.length,
26200
- unreachable,
26201
- entries,
26434
+ unreachable: unreachable.map((entry) => ({ ...entry, publishRequired: entry.publishRequired !== false })),
26435
+ entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
26202
26436
  durationMs: Date.now() - startedAt
26203
26437
  };
26204
26438
  } catch (e) {
26439
+ const unreachable = entries.filter((entry) => !entry.reachable).map((entry) => ({ ...entry, publishRequired: true }));
26205
26440
  return {
26206
26441
  status: "failed",
26207
26442
  checked: entries.length,
26208
- unreachable: entries.filter((entry) => !entry.reachable),
26209
- entries,
26443
+ unreachable,
26444
+ entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: true }),
26210
26445
  durationMs: Date.now() - startedAt,
26211
26446
  error: truncateValidationOutput(e?.message || String(e))
26212
26447
  };
@@ -27325,15 +27560,41 @@ var DaemonCommandRouter = class {
27325
27560
  const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead);
27326
27561
  recordMeshRefineStage(refineStages, "submodule_reachability", submoduleReachability.status, submoduleReachabilityStarted, {
27327
27562
  checked: submoduleReachability.checked,
27328
- unreachable: submoduleReachability.unreachable.map((entry) => ({ path: entry.path, commit: entry.commit, error: entry.error })),
27563
+ unreachable: submoduleReachability.unreachable.map((entry) => ({
27564
+ path: entry.path,
27565
+ commit: entry.commit,
27566
+ publishRequired: entry.publishRequired === true,
27567
+ remote: entry.remote,
27568
+ remoteUrl: entry.remoteUrl,
27569
+ remoteReachable: entry.remoteReachable,
27570
+ error: entry.error
27571
+ })),
27329
27572
  error: submoduleReachability.error
27330
27573
  });
27331
27574
  if (submoduleReachability.status === "failed") {
27575
+ const nextStep = buildSubmodulePublishRequiredNextStep(submoduleReachability.unreachable);
27332
27576
  return {
27333
27577
  success: false,
27334
27578
  code: "submodule_reachability_failed",
27335
27579
  convergenceStatus: "blocked_review",
27336
- error: "Refinery submodule reachability preflight failed; merge/refine cleanup was not attempted.",
27580
+ publishRequired: true,
27581
+ blockedReason: "submodule_publish_required",
27582
+ 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.",
27583
+ nextStep,
27584
+ nextSteps: [
27585
+ "Ask the user for explicit approval before pushing or publishing any submodule commit.",
27586
+ "Push/publish each unreachable submodule commit to the configured submodule remote shown in the evidence.",
27587
+ "Rerun mesh_refine_node after remote reachability is confirmed.",
27588
+ "Do not merge the root branch until every submodule gitlink commit is reachable from its configured remote."
27589
+ ],
27590
+ unreachableSubmoduleCommits: submoduleReachability.unreachable.map((entry) => ({
27591
+ path: entry.path,
27592
+ commit: entry.commit,
27593
+ remote: entry.remote,
27594
+ remoteUrl: entry.remoteUrl,
27595
+ remoteReachable: entry.remoteReachable,
27596
+ error: entry.error
27597
+ })),
27337
27598
  branch,
27338
27599
  into: baseBranch,
27339
27600
  validationSummary,
@@ -27348,7 +27609,9 @@ var DaemonCommandRouter = class {
27348
27609
  validation: "passed",
27349
27610
  patchEquivalence: "passed",
27350
27611
  submoduleReachability: "failed",
27351
- status: "blocked_review"
27612
+ status: "blocked_review",
27613
+ reason: "submodule_publish_required",
27614
+ nextStep
27352
27615
  }
27353
27616
  };
27354
27617
  }