adhdev 0.9.81 → 0.9.82-rc.10
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/cli/index.js +528 -83
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +499 -79
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/vendor/mcp-server/index.js +111 -14
- package/vendor/mcp-server/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adhdev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.82-rc.10",
|
|
4
4
|
"description": "ADHDev — Agent Dashboard Hub for Dev. Remote-control AI coding agents from anywhere.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"node": ">=18"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/daemon-core": "
|
|
50
|
+
"@adhdev/daemon-core": "0.9.82-rc.10",
|
|
51
51
|
"@adhdev/ghostty-vt-node": "*",
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
53
53
|
"@xterm/addon-serialize": "^0.14.0",
|
|
@@ -583,10 +583,18 @@ function isIdleSessionRecord(session) {
|
|
|
583
583
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
584
584
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
585
585
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
586
|
+
const isMeshOwnedDelegateSession = (session) => {
|
|
587
|
+
const settings = session?.settings;
|
|
588
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
589
|
+
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
590
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
591
|
+
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
592
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
593
|
+
};
|
|
586
594
|
const meshSessions = live.filter(
|
|
587
|
-
(session) => session
|
|
595
|
+
(session) => isMeshOwnedDelegateSession(session)
|
|
588
596
|
);
|
|
589
|
-
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) ||
|
|
597
|
+
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
590
598
|
}
|
|
591
599
|
function findNestedPayload(value, predicate) {
|
|
592
600
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -872,6 +880,10 @@ function summarizeRelatedRepoStatus(repo, status) {
|
|
|
872
880
|
workspace: repo.workspace,
|
|
873
881
|
isGitRepo: status?.isGitRepo === true,
|
|
874
882
|
branch: status?.branch ?? null,
|
|
883
|
+
upstream: status?.upstream ?? null,
|
|
884
|
+
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
885
|
+
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
886
|
+
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
875
887
|
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
876
888
|
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
877
889
|
dirty,
|
|
@@ -888,7 +900,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
|
|
|
888
900
|
const results = [];
|
|
889
901
|
for (const repo of relatedRepos) {
|
|
890
902
|
try {
|
|
891
|
-
const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace });
|
|
903
|
+
const statusResult = !isLocalTransport(ctx.transport) && node.daemonId ? await ctx.transport.gitStatus(node.daemonId, repo.workspace, false, true) : await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
|
|
892
904
|
const status = extractGitStatus(statusResult);
|
|
893
905
|
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
894
906
|
} catch (e) {
|
|
@@ -936,11 +948,13 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
936
948
|
const ahead = readNumeric(status?.ahead);
|
|
937
949
|
const behind = readNumeric(status?.behind);
|
|
938
950
|
const upstream = readString(status?.upstream) ?? null;
|
|
951
|
+
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
939
952
|
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
940
953
|
const base = {
|
|
941
954
|
defaultBranch,
|
|
942
955
|
branch,
|
|
943
956
|
upstream,
|
|
957
|
+
upstreamStatus,
|
|
944
958
|
ahead,
|
|
945
959
|
behind,
|
|
946
960
|
isWorktree: node.isLocalWorktree === true,
|
|
@@ -974,6 +988,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
974
988
|
};
|
|
975
989
|
}
|
|
976
990
|
if (branch === defaultBranch) {
|
|
991
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
992
|
+
return {
|
|
993
|
+
...base,
|
|
994
|
+
status: "blocked_review",
|
|
995
|
+
needsConvergence: true,
|
|
996
|
+
reason: "default_branch_upstream_unverified",
|
|
997
|
+
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
998
|
+
};
|
|
999
|
+
}
|
|
977
1000
|
if (ahead > 0 || behind > 0) {
|
|
978
1001
|
return {
|
|
979
1002
|
...base,
|
|
@@ -1000,6 +1023,15 @@ function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
|
1000
1023
|
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
1001
1024
|
};
|
|
1002
1025
|
}
|
|
1026
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
1027
|
+
return {
|
|
1028
|
+
...base,
|
|
1029
|
+
status: "blocked_review",
|
|
1030
|
+
needsConvergence: true,
|
|
1031
|
+
reason: "feature_branch_upstream_unverified",
|
|
1032
|
+
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1003
1035
|
if (!upstream || ahead > 0 || behind > 0) {
|
|
1004
1036
|
return {
|
|
1005
1037
|
...base,
|
|
@@ -1043,6 +1075,71 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1043
1075
|
}
|
|
1044
1076
|
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
1045
1077
|
}
|
|
1078
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1079
|
+
const payload = unwrapCommandPayload(value);
|
|
1080
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
1081
|
+
return events.filter((event) => event && typeof event === "object");
|
|
1082
|
+
}
|
|
1083
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
1084
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
1085
|
+
return {
|
|
1086
|
+
event: readString(event?.event),
|
|
1087
|
+
meshId: readString(event?.meshId),
|
|
1088
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
1089
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
1090
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
1091
|
+
providerType: readString(metadataEvent.providerType),
|
|
1092
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1093
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1094
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1095
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1096
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
1097
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
1098
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
1099
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
1100
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
1104
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
1105
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1106
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
1107
|
+
const surfacedEvents = [];
|
|
1108
|
+
try {
|
|
1109
|
+
surfacedEvents.push(
|
|
1110
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1111
|
+
);
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
for (const node of ctx.mesh.nodes) {
|
|
1115
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
1116
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1117
|
+
try {
|
|
1118
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1119
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", {})
|
|
1120
|
+
).filter(matchesCurrentMesh);
|
|
1121
|
+
if (remoteEvents.length === 0) continue;
|
|
1122
|
+
for (const event of remoteEvents) {
|
|
1123
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1124
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1125
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1126
|
+
}
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
try {
|
|
1131
|
+
surfacedEvents.push(
|
|
1132
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1133
|
+
);
|
|
1134
|
+
} catch {
|
|
1135
|
+
}
|
|
1136
|
+
return surfacedEvents;
|
|
1137
|
+
}
|
|
1138
|
+
if (isLocalTransport(ctx.transport)) {
|
|
1139
|
+
return (0, import_daemon_core.drainPendingMeshCoordinatorEvents)().filter(matchesCurrentMesh);
|
|
1140
|
+
}
|
|
1141
|
+
return [];
|
|
1142
|
+
}
|
|
1046
1143
|
function isP2pTransportUnavailableError(error) {
|
|
1047
1144
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
1048
1145
|
}
|
|
@@ -1344,7 +1441,7 @@ async function meshStatus(ctx) {
|
|
|
1344
1441
|
};
|
|
1345
1442
|
try {
|
|
1346
1443
|
if (!isLocalTransport(transport) && node.daemonId) {
|
|
1347
|
-
const result = await transport.gitStatus(node.daemonId, node.workspace, false);
|
|
1444
|
+
const result = await transport.gitStatus(node.daemonId, node.workspace, false, true);
|
|
1348
1445
|
const status = extractGitStatus(result);
|
|
1349
1446
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1350
1447
|
const dirty = isGitStatusDirty(status);
|
|
@@ -1362,6 +1459,7 @@ async function meshStatus(ctx) {
|
|
|
1362
1459
|
const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
|
|
1363
1460
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
1364
1461
|
workspace: node.workspace,
|
|
1462
|
+
refreshUpstream: true,
|
|
1365
1463
|
includeSubmodules: autoDiscover,
|
|
1366
1464
|
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
1367
1465
|
});
|
|
@@ -1460,13 +1558,7 @@ async function meshStatus(ctx) {
|
|
|
1460
1558
|
} catch {
|
|
1461
1559
|
}
|
|
1462
1560
|
try {
|
|
1463
|
-
|
|
1464
|
-
if (ctx.transport instanceof IpcTransport) {
|
|
1465
|
-
const eventsResult = await ctx.transport.command("get_pending_mesh_events", {});
|
|
1466
|
-
pendingEvents = Array.isArray(eventsResult?.events) ? eventsResult.events : [];
|
|
1467
|
-
} else if (isLocalTransport(ctx.transport)) {
|
|
1468
|
-
pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)();
|
|
1469
|
-
}
|
|
1561
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1470
1562
|
if (pendingEvents.length > 0) {
|
|
1471
1563
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1472
1564
|
}
|
|
@@ -1476,6 +1568,7 @@ async function meshStatus(ctx) {
|
|
|
1476
1568
|
}
|
|
1477
1569
|
async function meshTaskHistory(ctx, args) {
|
|
1478
1570
|
const { mesh } = ctx;
|
|
1571
|
+
await drainCoordinatorPendingEvents(ctx);
|
|
1479
1572
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1480
1573
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1481
1574
|
const entries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
@@ -1823,6 +1916,9 @@ async function meshReadChat(ctx, args) {
|
|
|
1823
1916
|
if (!node) {
|
|
1824
1917
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
1825
1918
|
}
|
|
1919
|
+
if (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport)) {
|
|
1920
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
1921
|
+
}
|
|
1826
1922
|
if (isLocalTransport(ctx.transport)) {
|
|
1827
1923
|
const cached = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1828
1924
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
@@ -2028,7 +2124,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2028
2124
|
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2029
2125
|
try {
|
|
2030
2126
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2031
|
-
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
2127
|
+
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true, true);
|
|
2032
2128
|
return JSON.stringify({
|
|
2033
2129
|
nodeId: args.node_id,
|
|
2034
2130
|
workspace: node.workspace,
|
|
@@ -2040,6 +2136,7 @@ async function meshGitStatus(ctx, args) {
|
|
|
2040
2136
|
} else if (isLocalTransport(ctx.transport)) {
|
|
2041
2137
|
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
2042
2138
|
workspace: node.workspace,
|
|
2139
|
+
refreshUpstream: true,
|
|
2043
2140
|
includeSubmodules: autoDiscoverSubmodules,
|
|
2044
2141
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
2045
2142
|
});
|
|
@@ -2511,8 +2608,8 @@ var CloudTransport = class {
|
|
|
2511
2608
|
if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
|
|
2512
2609
|
return res.json();
|
|
2513
2610
|
}
|
|
2514
|
-
async gitStatus(daemonId, workspace, includeDiff = true) {
|
|
2515
|
-
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff) });
|
|
2611
|
+
async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
|
|
2612
|
+
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
|
|
2516
2613
|
const res = await fetch(
|
|
2517
2614
|
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
|
|
2518
2615
|
{ headers: this.headers() }
|