@adhdev/daemon-core 0.9.82-rc.116 → 0.9.82-rc.117

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.116",
3
+ "version": "0.9.82-rc.117",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -55,6 +55,7 @@ import {
55
55
  import {
56
56
  MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS,
57
57
  MESH_WORKTREE_BOOTSTRAP_CONFIG_SCHEMA,
58
+ loadMeshWorktreeBootstrapConfig,
58
59
  runMeshWorktreeBootstrap,
59
60
  type WorktreeBootstrapState,
60
61
  } from '../mesh/worktree-bootstrap-config.js';
@@ -934,6 +935,12 @@ function finalizeMeshNodeStatus(args: {
934
935
  || 'Required worktree bootstrap failed; resolve it before launching an agent into this node.';
935
936
  return;
936
937
  }
938
+ if (bootstrap.status === 'running' && bootstrap.required !== false) {
939
+ status.launchReady = false;
940
+ status.launchBlockedReason = 'worktree_bootstrap_running';
941
+ status.launchBlockedMessage = 'Required worktree bootstrap is still running; wait for it to finish before launching an agent into this node.';
942
+ return;
943
+ }
937
944
  }
938
945
  const connectionState = readStringValue(readObjectRecord(status.connection).state);
939
946
  status.launchReady = !!daemonId && (
@@ -4971,60 +4978,120 @@ export class DaemonCommandRouter {
4971
4978
  this.invalidateAggregateMeshStatus(meshId);
4972
4979
  }
4973
4980
 
4974
- // Initialize submodules if policy allows (default: true)
4975
- const initSubmodules = (sourceNode.policy as any)?.initSubmodulesOnClone !== false;
4976
- if (initSubmodules) {
4977
- try {
4978
- const { runGit } = await import('../git/git-executor.js');
4979
- await runGit(
4980
- { workspace: result.worktreePath, repoRoot: result.worktreePath, isGitRepo: true },
4981
- ['submodule', 'update', '--init', '--recursive'],
4982
- { timeoutMs: 120000 },
4983
- );
4984
- } catch (subErr: any) {
4985
- // Submodule init is best-effort; don't fail the clone
4986
- console.warn('[mesh] Submodule init failed for worktree:', subErr.message);
4981
+ const persistWorktreeSetupState = async (bootstrapState: WorktreeBootstrapState): Promise<void> => {
4982
+ node.worktreeBootstrap = bootstrapState;
4983
+ if (meshRecord.inline) {
4984
+ this.updateInlineMeshNode(meshId, mesh, node);
4985
+ return;
4987
4986
  }
4988
- }
4989
-
4990
- const bootstrapState: WorktreeBootstrapState = await runMeshWorktreeBootstrap(mesh, result.worktreePath);
4991
- node.worktreeBootstrap = bootstrapState;
4992
- if (!meshRecord.inline) {
4993
4987
  try {
4994
4988
  const { updateNode } = await import('../config/mesh-config.js');
4995
4989
  updateNode(meshId, node.id, { worktreeBootstrap: bootstrapState });
4996
4990
  this.invalidateAggregateMeshStatus(meshId);
4997
4991
  } catch { /* bootstrap status persistence is best-effort */ }
4998
- }
4992
+ };
4999
4993
 
5000
- // Record in task ledger
5001
- try {
5002
- const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
5003
- appendLedgerEntry(meshId, {
5004
- kind: 'node_cloned',
5005
- nodeId: node.id,
5006
- payload: {
5007
- sourceNodeId,
5008
- branch: result.branch,
5009
- worktreePath: result.worktreePath,
5010
- submodulesInitialized: initSubmodules,
5011
- worktreeBootstrap: {
5012
- status: bootstrapState.status,
5013
- required: bootstrapState.required,
5014
- configSource: bootstrapState.configSource,
5015
- configSourceType: bootstrapState.configSourceType,
5016
- lastCommand: bootstrapState.lastCommand,
5017
- exitCode: bootstrapState.exitCode,
4994
+ const appendCloneLedger = async (initSubmodules: boolean, bootstrapState: WorktreeBootstrapState): Promise<void> => {
4995
+ try {
4996
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
4997
+ appendLedgerEntry(meshId, {
4998
+ kind: 'node_cloned',
4999
+ nodeId: node.id,
5000
+ payload: {
5001
+ sourceNodeId,
5002
+ branch: result.branch,
5003
+ worktreePath: result.worktreePath,
5004
+ submodulesInitialized: initSubmodules,
5005
+ worktreeBootstrap: {
5006
+ status: bootstrapState.status,
5007
+ required: bootstrapState.required,
5008
+ configSource: bootstrapState.configSource,
5009
+ configSourceType: bootstrapState.configSourceType,
5010
+ lastCommand: bootstrapState.lastCommand,
5011
+ exitCode: bootstrapState.exitCode,
5012
+ },
5018
5013
  },
5019
- },
5014
+ });
5015
+ } catch { /* ledger append is best-effort */ }
5016
+ };
5017
+
5018
+ const initSubmodules = (sourceNode.policy as any)?.initSubmodulesOnClone !== false;
5019
+ const loadedBootstrap = loadMeshWorktreeBootstrapConfig(mesh, result.worktreePath);
5020
+ const runningBootstrapState: WorktreeBootstrapState = {
5021
+ status: 'running',
5022
+ required: loadedBootstrap.config?.required !== false,
5023
+ configSource: loadedBootstrap.path || loadedBootstrap.source,
5024
+ configSourceType: loadedBootstrap.sourceType,
5025
+ startedAt: new Date().toISOString(),
5026
+ };
5027
+ await persistWorktreeSetupState(runningBootstrapState);
5028
+
5029
+ const finishWorktreeSetup = async (): Promise<{ submodulesInitialized: boolean; bootstrapState: WorktreeBootstrapState }> => {
5030
+ let submodulesInitialized = false;
5031
+ if (initSubmodules) {
5032
+ try {
5033
+ const { runGit } = await import('../git/git-executor.js');
5034
+ await runGit(
5035
+ { workspace: result.worktreePath, repoRoot: result.worktreePath, isGitRepo: true },
5036
+ ['submodule', 'update', '--init', '--recursive'],
5037
+ { timeoutMs: 120000 },
5038
+ );
5039
+ submodulesInitialized = true;
5040
+ } catch (subErr: any) {
5041
+ // Submodule init is best-effort; don't fail the clone
5042
+ console.warn('[mesh] Submodule init failed for worktree:', subErr.message);
5043
+ }
5044
+ }
5045
+ const bootstrapState: WorktreeBootstrapState = await runMeshWorktreeBootstrap(mesh, result.worktreePath);
5046
+ await persistWorktreeSetupState(bootstrapState);
5047
+ await appendCloneLedger(submodulesInitialized, bootstrapState);
5048
+ return { submodulesInitialized, bootstrapState };
5049
+ };
5050
+
5051
+ const requestedSetupWaitMs = Number(args?.setupWaitMs ?? args?.bootstrapWaitMs ?? 8000);
5052
+ const setupWaitMs = Number.isFinite(requestedSetupWaitMs)
5053
+ ? Math.min(Math.max(requestedSetupWaitMs, 0), 14000)
5054
+ : 8000;
5055
+ const setupPromise = finishWorktreeSetup();
5056
+ const setupResult = await Promise.race([
5057
+ setupPromise.then((value) => ({ completed: true as const, value })),
5058
+ new Promise<{ completed: false }>((resolve) => setTimeout(() => resolve({ completed: false }), setupWaitMs)),
5059
+ ]);
5060
+
5061
+ if (!setupResult.completed) {
5062
+ setupPromise.catch((error: any) => {
5063
+ const failedState: WorktreeBootstrapState = {
5064
+ ...runningBootstrapState,
5065
+ status: 'failed',
5066
+ completedAt: new Date().toISOString(),
5067
+ error: error?.message || String(error),
5068
+ };
5069
+ void persistWorktreeSetupState(failedState);
5070
+ void appendCloneLedger(false, failedState);
5020
5071
  });
5021
- } catch { /* ledger append is best-effort */ }
5072
+ return {
5073
+ success: true,
5074
+ async: true,
5075
+ status: 'accepted',
5076
+ node,
5077
+ worktreePath: result.worktreePath,
5078
+ branch: result.branch,
5079
+ worktreeBootstrap: runningBootstrapState,
5080
+ worktreeSetup: {
5081
+ status: 'running',
5082
+ setupWaitMs,
5083
+ message: 'Worktree node is registered; submodule/bootstrap setup is continuing in the background.',
5084
+ },
5085
+ };
5086
+ }
5022
5087
 
5088
+ const { submodulesInitialized, bootstrapState } = setupResult.value;
5023
5089
  return {
5024
5090
  success: true,
5025
5091
  node,
5026
5092
  worktreePath: result.worktreePath,
5027
5093
  branch: result.branch,
5094
+ submodulesInitialized,
5028
5095
  worktreeBootstrap: bootstrapState,
5029
5096
  };
5030
5097
  } catch (e: any) {
@@ -24,6 +24,13 @@ function normalizeApprovalLabel(value: string): string {
24
24
  .trim();
25
25
  }
26
26
 
27
+ function isNegativeApprovalLabel(value: string): boolean {
28
+ const label = normalizeApprovalLabel(value);
29
+ return /^(no|deny|reject|cancel|skip|exit|stop)\b/.test(label)
30
+ || /\bwithout\b/.test(label)
31
+ || /\bdo not\b/.test(label);
32
+ }
33
+
27
34
  export function getApprovalPositiveHints(provider?: Pick<ProviderModule, 'approvalPositiveHints'> | null): string[] {
28
35
  const customHints = Array.isArray(provider?.approvalPositiveHints)
29
36
  ? provider.approvalPositiveHints
@@ -39,24 +46,24 @@ export function pickApprovalButton(
39
46
  ): { index: number; label: string } {
40
47
  const labels = (buttons || []).map((button) => String(button || '').trim()).filter(Boolean);
41
48
  if (labels.length === 0) {
42
- return { index: 0, label: 'Approve' };
49
+ return { index: -1, label: '' };
43
50
  }
44
51
 
45
52
  const normalizedButtons = labels.map((label) => normalizeApprovalLabel(label));
46
53
  const hints = getApprovalPositiveHints(provider);
47
54
 
48
55
  for (const hint of hints) {
49
- const exactIndex = normalizedButtons.findIndex((label) => label === hint);
56
+ const exactIndex = normalizedButtons.findIndex((label, index) => label === hint && !isNegativeApprovalLabel(labels[index]));
50
57
  if (exactIndex >= 0) return { index: exactIndex, label: labels[exactIndex] };
51
58
 
52
- const prefixIndex = normalizedButtons.findIndex((label) => label.startsWith(hint));
59
+ const prefixIndex = normalizedButtons.findIndex((label, index) => label.startsWith(hint) && !isNegativeApprovalLabel(labels[index]));
53
60
  if (prefixIndex >= 0) return { index: prefixIndex, label: labels[prefixIndex] };
54
61
 
55
- const includeIndex = normalizedButtons.findIndex((label) => label.includes(hint));
62
+ const includeIndex = normalizedButtons.findIndex((label, index) => label.includes(hint) && !isNegativeApprovalLabel(labels[index]));
56
63
  if (includeIndex >= 0) return { index: includeIndex, label: labels[includeIndex] };
57
64
  }
58
65
 
59
- return { index: 0, label: labels[0] };
66
+ return { index: -1, label: '' };
60
67
  }
61
68
 
62
69
  export function formatAutoApprovalMessage(modalMessage?: string, buttonLabel?: string): string {