@adhdev/daemon-core 0.9.82-rc.115 → 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/dist/index.js +132 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +132 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/commands/chat-commands.ts +29 -2
- package/src/commands/router.ts +106 -39
- package/src/providers/approval-utils.ts +12 -5
package/package.json
CHANGED
|
@@ -455,18 +455,45 @@ function hasSafeNativeHistoryMapping(args: {
|
|
|
455
455
|
ptyMessages?: ChatMessage[];
|
|
456
456
|
requireWorkspaceContentOverlap?: boolean;
|
|
457
457
|
}): boolean {
|
|
458
|
+
const isCoordinatorTranscript = args.nativeMessages.some((m: any) => {
|
|
459
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
460
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat') || text.includes('mesh_launch_session');
|
|
461
|
+
});
|
|
462
|
+
|
|
458
463
|
const explicitSessionId = String(args.historySessionId || args.providerSessionId || '').trim();
|
|
459
464
|
if (explicitSessionId) {
|
|
460
465
|
const messageSessionIds = args.nativeMessages
|
|
461
466
|
.map((message: any) => typeof message?.historySessionId === 'string' ? message.historySessionId.trim() : '')
|
|
462
467
|
.filter(Boolean);
|
|
463
|
-
if (messageSessionIds.length
|
|
464
|
-
|
|
468
|
+
if (messageSessionIds.length > 0) {
|
|
469
|
+
return messageSessionIds.some((id) => id === explicitSessionId);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
|
|
473
|
+
const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
|
|
474
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
475
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
|
|
476
|
+
});
|
|
477
|
+
if (!ptyHasCoordinator) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
465
481
|
}
|
|
466
482
|
const workspace = String(args.workspace || '').trim();
|
|
467
483
|
if (!workspace) return false;
|
|
468
484
|
const workspaceMatches = args.nativeMessages.some((message: any) => String(message?.workspace || '').trim() === workspace);
|
|
469
485
|
if (!workspaceMatches) return false;
|
|
486
|
+
|
|
487
|
+
if (isCoordinatorTranscript && args.ptyMessages && args.ptyMessages.length > 0) {
|
|
488
|
+
const ptyHasCoordinator = args.ptyMessages.some((m: any) => {
|
|
489
|
+
const text = typeof m?.content === 'string' ? m.content : JSON.stringify(m?.content || '');
|
|
490
|
+
return text.includes('mesh_send_task') || text.includes('mesh_status') || text.includes('mesh_read_chat');
|
|
491
|
+
});
|
|
492
|
+
if (!ptyHasCoordinator) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
470
497
|
if (!args.requireWorkspaceContentOverlap) return true;
|
|
471
498
|
return hasOverlappingVisibleConversationText(args.nativeMessages, args.ptyMessages || []);
|
|
472
499
|
}
|
package/src/commands/router.ts
CHANGED
|
@@ -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
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
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
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
66
|
+
return { index: -1, label: '' };
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
export function formatAutoApprovalMessage(modalMessage?: string, buttonLabel?: string): string {
|