@adhdev/daemon-core 0.9.82-rc.9 → 0.9.82-rc.91
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-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/mesh-config.d.ts +66 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.js +5417 -1207
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5381 -1193
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +18 -0
- package/dist/mesh/mesh-active-work.d.ts +60 -0
- package/dist/mesh/mesh-events.d.ts +29 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +38 -1
- package/dist/mesh/mesh-work-queue.d.ts +27 -5
- package/dist/mesh/refine-config.d.ts +176 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +2 -1
- package/dist/repo-mesh-types.d.ts +46 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +1 -0
- package/src/cli-adapters/provider-cli-adapter.ts +91 -3
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +4 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +20 -10
- package/src/commands/chat-commands.ts +472 -15
- package/src/commands/cli-manager.ts +126 -0
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +2687 -435
- package/src/config/chat-history.ts +9 -7
- package/src/config/mesh-config.ts +245 -1
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/index.ts +31 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +176 -0
- package/src/mesh/coordinator-prompt.ts +30 -7
- package/src/mesh/mesh-active-work.ts +255 -0
- package/src/mesh/mesh-events.ts +400 -47
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +138 -1
- package/src/mesh/mesh-work-queue.ts +199 -137
- package/src/mesh/refine-config.ts +356 -0
- package/src/providers/chat-message-normalization.ts +7 -12
- package/src/providers/cli-provider-instance.ts +93 -14
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +10 -4
- package/src/providers/read-chat-contract.ts +1 -1
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +51 -0
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
|
@@ -1387,22 +1387,23 @@ function callProviderNativeHistoryRead(
|
|
|
1387
1387
|
agentType: string,
|
|
1388
1388
|
canonicalHistory: ProviderCanonicalHistoryConfig | undefined,
|
|
1389
1389
|
scripts: ProviderNativeHistoryScripts | undefined,
|
|
1390
|
-
historySessionId: string,
|
|
1390
|
+
historySessionId: string | undefined,
|
|
1391
1391
|
workspace?: string,
|
|
1392
1392
|
): ProviderNativeHistoryReadResult | null {
|
|
1393
1393
|
const fn = getProviderNativeHistoryScript(scripts, canonicalHistory, 'readSession');
|
|
1394
1394
|
if (!fn) return null;
|
|
1395
|
+
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
|
|
1395
1396
|
const result = fn({
|
|
1396
1397
|
agentType,
|
|
1397
|
-
sessionId:
|
|
1398
|
-
historySessionId,
|
|
1398
|
+
sessionId: normalizedSessionId,
|
|
1399
|
+
historySessionId: normalizedSessionId,
|
|
1399
1400
|
workspace,
|
|
1400
1401
|
format: canonicalHistory?.format,
|
|
1401
1402
|
watchPath: canonicalHistory?.watchPath,
|
|
1402
|
-
args: { sessionId:
|
|
1403
|
+
args: { sessionId: normalizedSessionId, historySessionId: normalizedSessionId, workspace },
|
|
1403
1404
|
});
|
|
1404
1405
|
if (!result || typeof result !== 'object') return null;
|
|
1405
|
-
const records = normalizeProviderNativeHistoryRecords(agentType,
|
|
1406
|
+
const records = normalizeProviderNativeHistoryRecords(agentType, normalizedSessionId, (result as any).messages || (result as any).records);
|
|
1406
1407
|
if (records.length === 0) return null;
|
|
1407
1408
|
return {
|
|
1408
1409
|
records,
|
|
@@ -1419,7 +1420,8 @@ function buildNativeHistoryReadResult(
|
|
|
1419
1420
|
workspace?: string,
|
|
1420
1421
|
): ProviderNativeHistoryReadResult | null {
|
|
1421
1422
|
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
|
|
1422
|
-
|
|
1423
|
+
const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
|
|
1424
|
+
if (!canonicalHistory || (!normalizedSessionId && !normalizedWorkspace) || !isNativeSourceCanonicalHistory(canonicalHistory)) return null;
|
|
1423
1425
|
return callProviderNativeHistoryRead(agentType, canonicalHistory, scripts, normalizedSessionId, workspace);
|
|
1424
1426
|
}
|
|
1425
1427
|
|
|
@@ -1478,7 +1480,7 @@ export function readProviderChatHistory(
|
|
|
1478
1480
|
scripts?: ProviderNativeHistoryScripts;
|
|
1479
1481
|
} = {},
|
|
1480
1482
|
): { messages: HistoryMessage[]; hasMore: boolean; source: 'provider-native' | 'adhdev-mirror' | 'native-unavailable'; sourcePath?: string; sourceMtimeMs?: number } {
|
|
1481
|
-
if (isNativeSourceCanonicalHistory(options.canonicalHistory) && options.historySessionId) {
|
|
1483
|
+
if (isNativeSourceCanonicalHistory(options.canonicalHistory) && (options.historySessionId || options.workspace)) {
|
|
1482
1484
|
const nativeResult = buildNativeHistoryReadResult(agentType, options.canonicalHistory, options.scripts, options.historySessionId, options.workspace);
|
|
1483
1485
|
if (!nativeResult) return { messages: [], hasMore: false, source: 'native-unavailable' };
|
|
1484
1486
|
return {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
-
import { randomUUID } from 'crypto';
|
|
11
|
+
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
12
12
|
import { getConfigDir } from './config.js';
|
|
13
13
|
import type {
|
|
14
14
|
LocalMeshConfig,
|
|
@@ -18,8 +18,11 @@ import type {
|
|
|
18
18
|
RepoMeshNodePolicy,
|
|
19
19
|
RepoMeshNodeCapabilities,
|
|
20
20
|
RepoMeshCoordinatorConfig,
|
|
21
|
+
RepoMeshHostMetadata,
|
|
22
|
+
RepoMeshDaemonRole,
|
|
21
23
|
} from '../repo-mesh-types.js';
|
|
22
24
|
import { DEFAULT_MESH_POLICY } from '../repo-mesh-types.js';
|
|
25
|
+
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
23
26
|
|
|
24
27
|
// ─── Persistence ────────────────────────────────
|
|
25
28
|
|
|
@@ -84,6 +87,7 @@ function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMe
|
|
|
84
87
|
}
|
|
85
88
|
const maxParallelTasks = Number(policy.maxParallelTasks);
|
|
86
89
|
policy.maxParallelTasks = Number.isFinite(maxParallelTasks) ? Math.max(1, Math.min(8, Math.floor(maxParallelTasks))) : 2;
|
|
90
|
+
policy.allowAutoPublishSubmoduleMainCommits = policy.allowAutoPublishSubmoduleMainCommits === true;
|
|
87
91
|
if (!SESSION_CLEANUP_MODES.has(String(policy.sessionCleanupOnNodeRemove))) {
|
|
88
92
|
policy.sessionCleanupOnNodeRemove = 'preserve';
|
|
89
93
|
}
|
|
@@ -112,6 +116,7 @@ export interface CreateMeshOptions {
|
|
|
112
116
|
defaultBranch?: string;
|
|
113
117
|
policy?: Partial<RepoMeshPolicy>;
|
|
114
118
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
119
|
+
meshHost?: RepoMeshHostMetadata;
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
@@ -133,6 +138,7 @@ export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
|
133
138
|
defaultBranch: opts.defaultBranch,
|
|
134
139
|
policy: mergeMeshPolicy(undefined, opts.policy),
|
|
135
140
|
coordinator: opts.coordinator || {},
|
|
141
|
+
meshHost: opts.meshHost || createDefaultMeshHostMetadata(),
|
|
136
142
|
nodes: [],
|
|
137
143
|
createdAt: now,
|
|
138
144
|
updatedAt: now,
|
|
@@ -148,6 +154,7 @@ export interface UpdateMeshOptions {
|
|
|
148
154
|
defaultBranch?: string;
|
|
149
155
|
policy?: Partial<RepoMeshPolicy>;
|
|
150
156
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
157
|
+
meshHost?: RepoMeshHostMetadata;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEntry | undefined {
|
|
@@ -159,6 +166,7 @@ export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEn
|
|
|
159
166
|
if (opts.defaultBranch !== undefined) mesh.defaultBranch = opts.defaultBranch;
|
|
160
167
|
if (opts.policy) mesh.policy = mergeMeshPolicy(mesh.policy, opts.policy);
|
|
161
168
|
if (opts.coordinator) mesh.coordinator = opts.coordinator;
|
|
169
|
+
if (opts.meshHost) mesh.meshHost = opts.meshHost;
|
|
162
170
|
mesh.updatedAt = new Date().toISOString();
|
|
163
171
|
|
|
164
172
|
saveMeshConfig(config);
|
|
@@ -174,6 +182,240 @@ export function deleteMesh(meshId: string): boolean {
|
|
|
174
182
|
return true;
|
|
175
183
|
}
|
|
176
184
|
|
|
185
|
+
function normalizeManualHostAddress(hostAddress: string): string {
|
|
186
|
+
const normalized = hostAddress.trim().replace(/\/+$/, '');
|
|
187
|
+
if (!normalized) throw new Error('hostAddress required');
|
|
188
|
+
let parsed: URL;
|
|
189
|
+
try {
|
|
190
|
+
parsed = new URL(normalized);
|
|
191
|
+
} catch {
|
|
192
|
+
throw new Error('hostAddress must be a valid http(s) or ws(s) URL');
|
|
193
|
+
}
|
|
194
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
|
|
195
|
+
throw new Error('hostAddress must use http, https, ws, or wss');
|
|
196
|
+
}
|
|
197
|
+
return normalized;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function tokenIdForManualPairing(token: string): string {
|
|
201
|
+
return `tok_${createHash('sha256').update(token).digest('hex').slice(0, 16)}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function normalizeTokenExpiry(value: unknown): string | undefined {
|
|
205
|
+
if (typeof value !== 'string' || !value.trim()) return undefined;
|
|
206
|
+
const date = new Date(value);
|
|
207
|
+
if (Number.isNaN(date.getTime())) throw new Error('expiresAt must be a valid ISO date');
|
|
208
|
+
return date.toISOString();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function assertPairingTokenValid(pairing: RepoMeshHostMetadata['pairing'], rawToken: string, nowIso: string): { ok: true; tokenId: string } | { ok: false; reason: string; expectedTokenId?: string; presentedTokenId?: string } {
|
|
212
|
+
const token = rawToken.trim();
|
|
213
|
+
if (!token) return { ok: false, reason: 'token required' };
|
|
214
|
+
const presentedTokenId = tokenIdForManualPairing(token);
|
|
215
|
+
const expectedTokenId = pairing?.tokenId;
|
|
216
|
+
if (!expectedTokenId || pairing?.status === 'not_configured' || pairing?.status === 'revoked') {
|
|
217
|
+
return { ok: false, reason: 'host pairing token is not configured', presentedTokenId };
|
|
218
|
+
}
|
|
219
|
+
if (pairing.expiresAt && new Date(pairing.expiresAt).getTime() <= new Date(nowIso).getTime()) {
|
|
220
|
+
return { ok: false, reason: 'host pairing token expired', expectedTokenId, presentedTokenId };
|
|
221
|
+
}
|
|
222
|
+
if (presentedTokenId !== expectedTokenId) {
|
|
223
|
+
return { ok: false, reason: 'invalid pairing token', expectedTokenId, presentedTokenId };
|
|
224
|
+
}
|
|
225
|
+
return { ok: true, tokenId: presentedTokenId };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface ConfigureMeshHostPairingOptions {
|
|
229
|
+
hostAddress: string;
|
|
230
|
+
token: string;
|
|
231
|
+
now?: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function configureMeshHostPairing(
|
|
235
|
+
meshId: string,
|
|
236
|
+
opts: ConfigureMeshHostPairingOptions,
|
|
237
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; hostAddress: string } | undefined {
|
|
238
|
+
const hostAddress = normalizeManualHostAddress(opts.hostAddress);
|
|
239
|
+
const token = opts.token.trim();
|
|
240
|
+
if (!token) throw new Error('token required');
|
|
241
|
+
|
|
242
|
+
const config = loadMeshConfig();
|
|
243
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
244
|
+
if (!mesh) return undefined;
|
|
245
|
+
|
|
246
|
+
const now = opts.now || new Date().toISOString();
|
|
247
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
248
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
249
|
+
...previous,
|
|
250
|
+
role: 'member',
|
|
251
|
+
hostAddress,
|
|
252
|
+
pairing: {
|
|
253
|
+
status: 'pairing',
|
|
254
|
+
tokenId: tokenIdForManualPairing(token),
|
|
255
|
+
lastPairedAt: now,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
mesh.meshHost = meshHost;
|
|
260
|
+
mesh.updatedAt = now;
|
|
261
|
+
saveMeshConfig(config);
|
|
262
|
+
return { mesh, meshHost, hostAddress };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface CreateMeshHostPairingTokenOptions {
|
|
266
|
+
token?: string;
|
|
267
|
+
expiresAt?: string;
|
|
268
|
+
now?: string;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function createMeshHostPairingToken(
|
|
272
|
+
meshId: string,
|
|
273
|
+
opts: CreateMeshHostPairingTokenOptions = {},
|
|
274
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; token: string; tokenId: string; expiresAt?: string } | undefined {
|
|
275
|
+
const config = loadMeshConfig();
|
|
276
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
277
|
+
if (!mesh) return undefined;
|
|
278
|
+
const now = opts.now || new Date().toISOString();
|
|
279
|
+
const token = (opts.token || `mhj_${randomBytes(24).toString('base64url')}`).trim();
|
|
280
|
+
if (!token) throw new Error('token required');
|
|
281
|
+
const tokenId = tokenIdForManualPairing(token);
|
|
282
|
+
const expiresAt = normalizeTokenExpiry(opts.expiresAt);
|
|
283
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
284
|
+
if (previous.role === 'member') {
|
|
285
|
+
throw new Error('Mesh Host daemon required to create host pairing tokens; member daemons cannot mint host join tokens.');
|
|
286
|
+
}
|
|
287
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
288
|
+
...previous,
|
|
289
|
+
role: 'host',
|
|
290
|
+
pairing: {
|
|
291
|
+
status: 'pairing',
|
|
292
|
+
tokenId,
|
|
293
|
+
lastPairedAt: now,
|
|
294
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
mesh.meshHost = meshHost;
|
|
298
|
+
mesh.updatedAt = now;
|
|
299
|
+
saveMeshConfig(config);
|
|
300
|
+
return { mesh, meshHost, token, tokenId, ...(expiresAt ? { expiresAt } : {}) };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface MeshHostJoinMemberNodeInput {
|
|
304
|
+
id?: string;
|
|
305
|
+
workspace: string;
|
|
306
|
+
repoRoot?: string;
|
|
307
|
+
daemonId?: string;
|
|
308
|
+
machineId?: string;
|
|
309
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
310
|
+
policy?: RepoMeshNodePolicy;
|
|
311
|
+
role?: RepoMeshDaemonRole;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface ApplyMeshHostJoinOptions {
|
|
315
|
+
token: string;
|
|
316
|
+
memberNode: MeshHostJoinMemberNodeInput;
|
|
317
|
+
memberMeshId?: string;
|
|
318
|
+
now?: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function applyMeshHostJoinRequest(
|
|
322
|
+
meshId: string,
|
|
323
|
+
opts: ApplyMeshHostJoinOptions,
|
|
324
|
+
): { accepted: true; mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; node: LocalMeshNodeEntry; tokenId: string } | { accepted: false; mesh?: LocalMeshEntry; meshHost?: RepoMeshHostMetadata; tokenId?: string; reason: string } | undefined {
|
|
325
|
+
const config = loadMeshConfig();
|
|
326
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
327
|
+
if (!mesh) return undefined;
|
|
328
|
+
const now = opts.now || new Date().toISOString();
|
|
329
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
330
|
+
if (previous.role === 'member') {
|
|
331
|
+
return { accepted: false, mesh, meshHost: previous, reason: 'Mesh Host daemon required to accept join requests' };
|
|
332
|
+
}
|
|
333
|
+
const meshHost: RepoMeshHostMetadata = { ...previous, role: 'host' };
|
|
334
|
+
const validation = assertPairingTokenValid(meshHost.pairing, opts.token, now);
|
|
335
|
+
if (!validation.ok) {
|
|
336
|
+
mesh.meshHost = {
|
|
337
|
+
...meshHost,
|
|
338
|
+
pairing: {
|
|
339
|
+
...(meshHost.pairing || { status: 'not_configured' as const }),
|
|
340
|
+
status: 'rejected',
|
|
341
|
+
lastRejectedAt: now,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
mesh.updatedAt = now;
|
|
345
|
+
saveMeshConfig(config);
|
|
346
|
+
return { accepted: false, mesh, meshHost: mesh.meshHost, tokenId: validation.presentedTokenId, reason: validation.reason };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const workspace = opts.memberNode.workspace.trim();
|
|
350
|
+
if (!workspace) throw new Error('memberNode.workspace required');
|
|
351
|
+
const memberId = opts.memberNode.id?.trim();
|
|
352
|
+
let node = mesh.nodes.find(n => (memberId && n.id === memberId) || n.workspace === workspace);
|
|
353
|
+
if (node) {
|
|
354
|
+
node.workspace = workspace;
|
|
355
|
+
node.repoRoot = opts.memberNode.repoRoot;
|
|
356
|
+
node.daemonId = opts.memberNode.daemonId;
|
|
357
|
+
node.machineId = opts.memberNode.machineId;
|
|
358
|
+
node.userOverrides = opts.memberNode.userOverrides || node.userOverrides || {};
|
|
359
|
+
node.policy = { ...(node.policy || {}), ...(opts.memberNode.policy || {}) };
|
|
360
|
+
node.role = 'member';
|
|
361
|
+
} else {
|
|
362
|
+
if (mesh.nodes.length >= 10) throw new Error('Maximum 10 nodes per mesh');
|
|
363
|
+
node = {
|
|
364
|
+
id: memberId || `node_${randomUUID().replace(/-/g, '')}`,
|
|
365
|
+
workspace,
|
|
366
|
+
repoRoot: opts.memberNode.repoRoot,
|
|
367
|
+
daemonId: opts.memberNode.daemonId,
|
|
368
|
+
machineId: opts.memberNode.machineId,
|
|
369
|
+
userOverrides: opts.memberNode.userOverrides || {},
|
|
370
|
+
policy: opts.memberNode.policy || {},
|
|
371
|
+
role: 'member',
|
|
372
|
+
};
|
|
373
|
+
mesh.nodes.push(node);
|
|
374
|
+
}
|
|
375
|
+
mesh.meshHost = {
|
|
376
|
+
...meshHost,
|
|
377
|
+
pairing: {
|
|
378
|
+
...(meshHost.pairing || {}),
|
|
379
|
+
status: 'paired',
|
|
380
|
+
tokenId: validation.tokenId,
|
|
381
|
+
joinedAt: now,
|
|
382
|
+
lastPairedAt: meshHost.pairing?.lastPairedAt || now,
|
|
383
|
+
...(meshHost.pairing?.expiresAt ? { expiresAt: meshHost.pairing.expiresAt } : {}),
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
mesh.updatedAt = now;
|
|
387
|
+
saveMeshConfig(config);
|
|
388
|
+
return { accepted: true, mesh, meshHost: mesh.meshHost, node, tokenId: validation.tokenId };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function markMeshHostPairingJoined(
|
|
392
|
+
meshId: string,
|
|
393
|
+
opts: { hostDaemonId?: string; hostNodeId?: string; joinedAt?: string; token?: string; tokenId?: string },
|
|
394
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata } | undefined {
|
|
395
|
+
const config = loadMeshConfig();
|
|
396
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
397
|
+
if (!mesh) return undefined;
|
|
398
|
+
const now = opts.joinedAt || new Date().toISOString();
|
|
399
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
400
|
+
const tokenId = opts.tokenId || (opts.token ? tokenIdForManualPairing(opts.token) : previous.pairing?.tokenId);
|
|
401
|
+
mesh.meshHost = {
|
|
402
|
+
...previous,
|
|
403
|
+
role: 'member',
|
|
404
|
+
...(opts.hostDaemonId ? { hostDaemonId: opts.hostDaemonId } : {}),
|
|
405
|
+
...(opts.hostNodeId ? { hostNodeId: opts.hostNodeId } : {}),
|
|
406
|
+
pairing: {
|
|
407
|
+
...(previous.pairing || {}),
|
|
408
|
+
status: 'paired',
|
|
409
|
+
...(tokenId ? { tokenId } : {}),
|
|
410
|
+
joinedAt: now,
|
|
411
|
+
lastPairedAt: previous.pairing?.lastPairedAt || now,
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
mesh.updatedAt = now;
|
|
415
|
+
saveMeshConfig(config);
|
|
416
|
+
return { mesh, meshHost: mesh.meshHost };
|
|
417
|
+
}
|
|
418
|
+
|
|
177
419
|
// ─── Node Operations ────────────────────────────
|
|
178
420
|
|
|
179
421
|
export interface AddNodeOptions {
|
|
@@ -186,6 +428,7 @@ export interface AddNodeOptions {
|
|
|
186
428
|
isLocalWorktree?: boolean;
|
|
187
429
|
worktreeBranch?: string;
|
|
188
430
|
clonedFromNodeId?: string;
|
|
431
|
+
role?: RepoMeshDaemonRole;
|
|
189
432
|
}
|
|
190
433
|
|
|
191
434
|
export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntry | undefined {
|
|
@@ -213,6 +456,7 @@ export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntr
|
|
|
213
456
|
isLocalWorktree: opts.isLocalWorktree,
|
|
214
457
|
worktreeBranch: opts.worktreeBranch,
|
|
215
458
|
clonedFromNodeId: opts.clonedFromNodeId,
|
|
459
|
+
role: opts.role,
|
|
216
460
|
};
|
|
217
461
|
|
|
218
462
|
mesh.nodes.push(node);
|
|
@@ -802,7 +802,16 @@ export async function handleCliSend(ctx: DevServerContext, req: http.IncomingMes
|
|
|
802
802
|
}
|
|
803
803
|
|
|
804
804
|
try {
|
|
805
|
-
|
|
805
|
+
if (target.category === 'cli') {
|
|
806
|
+
const bundle = getCliTargetBundle(ctx, type, instanceId);
|
|
807
|
+
if (!bundle) {
|
|
808
|
+
ctx.json(res, 404, { error: `No running CLI adapter found for: ${type || instanceId}` });
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
await bundle.adapter.sendMessage(text);
|
|
812
|
+
} else {
|
|
813
|
+
ctx.instanceManager!.sendEvent(target.instanceId, 'send_message', { text });
|
|
814
|
+
}
|
|
806
815
|
ctx.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId });
|
|
807
816
|
} catch (e: any) {
|
|
808
817
|
ctx.json(res, 500, { error: `Send failed: ${e.message}` });
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* Migrated from @adhdev/core — this is now the single source of truth.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
import { existsSync, statSync } from 'fs';
|
|
12
14
|
import { platform, homedir } from 'os';
|
|
13
15
|
import * as path from 'path';
|
|
14
16
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
@@ -73,25 +75,33 @@ function findCliCommand(command: string): string | null {
|
|
|
73
75
|
const resolved = path.isAbsolute(candidate) ? candidate : path.resolve(candidate);
|
|
74
76
|
return existsSync(resolved) ? resolved : null;
|
|
75
77
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const isWin = platform() === 'win32';
|
|
79
|
+
const paths = (process.env.PATH || '').split(isWin ? ';' : ':');
|
|
80
|
+
const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
|
|
81
|
+
for (const p of paths) {
|
|
82
|
+
if (!p) continue;
|
|
83
|
+
for (const ext of exes) {
|
|
84
|
+
const fullPath = path.join(p, trimmed + ext);
|
|
85
|
+
try {
|
|
86
|
+
if (existsSync(fullPath)) {
|
|
87
|
+
const stat = statSync(fullPath);
|
|
88
|
+
if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
|
|
89
|
+
return fullPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch { }
|
|
93
|
+
}
|
|
84
94
|
}
|
|
95
|
+
return null;
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
function getIdeVersion(cliCommand: string): string | null {
|
|
98
|
+
async function getIdeVersion(cliCommand: string): Promise<string | null> {
|
|
88
99
|
try {
|
|
89
|
-
const
|
|
100
|
+
const { stdout } = await execAsync(`"${cliCommand}" --version`, {
|
|
90
101
|
encoding: 'utf-8',
|
|
91
102
|
timeout: 10000,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return result.split('\n')[0] || null;
|
|
103
|
+
});
|
|
104
|
+
return stdout.trim().split('\n')[0] || null;
|
|
95
105
|
} catch {
|
|
96
106
|
return null;
|
|
97
107
|
}
|
|
@@ -152,7 +162,7 @@ export async function detectIDEs(providerLoader?: ProviderLoader): Promise<IDEIn
|
|
|
152
162
|
const installed = os === 'darwin'
|
|
153
163
|
? !!(resolvedCli || appPath)
|
|
154
164
|
: !!resolvedCli;
|
|
155
|
-
const version = resolvedCli ? getIdeVersion(resolvedCli) : null;
|
|
165
|
+
const version = resolvedCli ? await getIdeVersion(resolvedCli) : null;
|
|
156
166
|
|
|
157
167
|
results.push({
|
|
158
168
|
id: def.id,
|
package/src/index.ts
CHANGED
|
@@ -88,6 +88,10 @@ export type {
|
|
|
88
88
|
// ── Repo Mesh Types (cross-package) ──
|
|
89
89
|
export type {
|
|
90
90
|
RepoMesh,
|
|
91
|
+
RepoMeshDaemonRole,
|
|
92
|
+
RepoMeshHostMetadata,
|
|
93
|
+
RepoMeshHostPairingMetadata,
|
|
94
|
+
RepoMeshHostStatus,
|
|
91
95
|
RepoMeshNode,
|
|
92
96
|
RepoMeshNodeHealth,
|
|
93
97
|
RepoMeshPolicy,
|
|
@@ -155,18 +159,40 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
155
159
|
// ── Mesh Coordinator ──
|
|
156
160
|
export { buildCoordinatorSystemPrompt } from './mesh/coordinator-prompt.js';
|
|
157
161
|
export type { CoordinatorPromptContext } from './mesh/coordinator-prompt.js';
|
|
162
|
+
export {
|
|
163
|
+
MESH_REFINE_CONFIG_LOCATIONS,
|
|
164
|
+
MESH_REFINE_CONFIG_SCHEMA,
|
|
165
|
+
loadMeshRefineConfig,
|
|
166
|
+
resolveMeshRefineValidationPlan,
|
|
167
|
+
suggestMeshRefineConfig,
|
|
168
|
+
validateMeshRefineConfig,
|
|
169
|
+
} from './mesh/refine-config.js';
|
|
170
|
+
export type {
|
|
171
|
+
MeshRefineValidationCategory,
|
|
172
|
+
MeshRefineValidationCommandPlan,
|
|
173
|
+
MeshRefineValidationPlan,
|
|
174
|
+
RepoMeshRefineConfig,
|
|
175
|
+
RepoMeshRefineValidationCommandConfig,
|
|
176
|
+
} from './mesh/refine-config.js';
|
|
158
177
|
export { syncMeshes } from './mesh/mesh-sync.js';
|
|
159
178
|
export type { MeshSyncTransport, MeshSyncResult, RemoteMeshRecord } from './mesh/mesh-sync.js';
|
|
160
179
|
|
|
161
180
|
// ── Mesh Task Ledger ──
|
|
162
|
-
export { appendLedgerEntry, appendRemoteLedgerEntries, readLedgerEntries, readLedgerSlice, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
|
163
|
-
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext } from './mesh/mesh-ledger.js';
|
|
181
|
+
export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
|
182
|
+
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext, MeshTaskCompletionEvidence, MeshWorkerResultArtifact, MeshProcessArtifact, MeshValidationResultArtifact } from './mesh/mesh-ledger.js';
|
|
183
|
+
export { fastForwardMeshNode } from './mesh/mesh-fast-forward.js';
|
|
184
|
+
export type { MeshFastForwardNodeArgs, MeshFastForwardPlannedStep, MeshFastForwardResult } from './mesh/mesh-fast-forward.js';
|
|
164
185
|
export { buildMeshLedgerReconciliationEvidence, buildMeshLedgerReplicaEvidence } from './mesh/mesh-ledger-reconciliation.js';
|
|
165
186
|
export type { MeshLedgerReconciliationEvidence, MeshLedgerReplicaEvidence, MeshLedgerReplicaStatus } from './mesh/mesh-ledger-reconciliation.js';
|
|
166
187
|
|
|
167
188
|
// ── Mesh Work Queue (GUPP) ──
|
|
168
|
-
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats } from './mesh/mesh-work-queue.js';
|
|
169
|
-
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshWorkQueueStats } from './mesh/mesh-work-queue.js';
|
|
189
|
+
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, getMeshQueueRevision, normalizeMeshTaskMode, validateMeshTaskModeRequest } from './mesh/mesh-work-queue.js';
|
|
190
|
+
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshTaskMode, MeshWorkQueueStats, MeshQueueMutationOptions, MeshTaskModeValidationResult } from './mesh/mesh-work-queue.js';
|
|
191
|
+
export { buildMeshActiveWork, buildMeshActiveWorkSummary } from './mesh/mesh-active-work.js';
|
|
192
|
+
export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource } from './mesh/mesh-active-work.js';
|
|
193
|
+
|
|
194
|
+
// ── Mesh Host Ownership ──
|
|
195
|
+
export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHostOwner, normalizeMeshDaemonRole, requireMeshHostQueueOwner, resolveMeshHostStatus } from './mesh/mesh-host-ownership.js';
|
|
170
196
|
|
|
171
197
|
// ── Mesh Visualization ──
|
|
172
198
|
// buildMeshGraph and MeshGraph types moved to @adhdev/web-core to avoid
|
|
@@ -176,7 +202,7 @@ export type { MeshWorkQueueEntry, MeshTaskStatus, MeshWorkQueueStats } from './m
|
|
|
176
202
|
// export type { MeshGraph, MeshGraphNode, MeshGraphEdge, MeshGraphNodeType, MeshGraphEdgeType } from './mesh/mesh-visualization.js';
|
|
177
203
|
|
|
178
204
|
// ── Mesh Events ──
|
|
179
|
-
export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents } from './mesh/mesh-events.js';
|
|
205
|
+
export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
|
|
180
206
|
export type { PendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
|
|
181
207
|
|
|
182
208
|
// ── Mesh P2P Relay Failure Classification ──
|
package/src/installer.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface InstallResult {
|
|
|
31
31
|
/**
|
|
32
32
|
* Check if an extension is already installed
|
|
33
33
|
*/
|
|
34
|
-
export declare function isExtensionInstalled(ide: IDEInfo, marketplaceId: string): boolean
|
|
34
|
+
export declare function isExtensionInstalled(ide: IDEInfo, marketplaceId: string): Promise<boolean>;
|
|
35
35
|
/**
|
|
36
36
|
* Install a single extension
|
|
37
37
|
*/
|
package/src/installer.ts
CHANGED
|
@@ -122,20 +122,22 @@ export interface InstallResult {
|
|
|
122
122
|
/**
|
|
123
123
|
* Check if an extension is already installed
|
|
124
124
|
*/
|
|
125
|
-
|
|
125
|
+
import { promisify } from 'util';
|
|
126
|
+
const execAsync = promisify(exec);
|
|
127
|
+
|
|
128
|
+
export async function isExtensionInstalled(
|
|
126
129
|
ide: IDEInfo,
|
|
127
130
|
marketplaceId: string
|
|
128
|
-
): boolean {
|
|
131
|
+
): Promise<boolean> {
|
|
129
132
|
if (!ide.cliCommand) return false;
|
|
130
133
|
|
|
131
134
|
try {
|
|
132
|
-
const
|
|
135
|
+
const { stdout } = await execAsync(`"${ide.cliCommand}" --list-extensions`, {
|
|
133
136
|
encoding: 'utf-8',
|
|
134
137
|
timeout: 15000,
|
|
135
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
136
138
|
});
|
|
137
139
|
|
|
138
|
-
const installed =
|
|
140
|
+
const installed = stdout
|
|
139
141
|
.trim()
|
|
140
142
|
.split('\n')
|
|
141
143
|
.map((e) => e.trim().toLowerCase());
|
|
@@ -163,7 +165,7 @@ export async function installExtension(
|
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
// Check if already installed
|
|
166
|
-
const alreadyInstalled = isExtensionInstalled(ide, extension.marketplaceId);
|
|
168
|
+
const alreadyInstalled = await isExtensionInstalled(ide, extension.marketplaceId);
|
|
167
169
|
if (alreadyInstalled) {
|
|
168
170
|
return {
|
|
169
171
|
extensionId: extension.id,
|
package/src/launch.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
/** Kill IDE process (graceful → force) */
|
|
19
19
|
export declare function killIdeProcess(ideId: string): Promise<boolean>;
|
|
20
20
|
/** Check if IDE process is running */
|
|
21
|
-
export declare function isIdeRunning(ideId: string): boolean
|
|
21
|
+
export declare function isIdeRunning(ideId: string): Promise<boolean>;
|
|
22
22
|
export interface LaunchOptions {
|
|
23
23
|
ideId?: string;
|
|
24
24
|
workspace?: string;
|