@adhdev/daemon-core 0.9.82-rc.66 → 0.9.82-rc.67
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.d.ts +1 -1
- package/dist/index.js +747 -591
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +748 -593
- 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-work-queue.d.ts +4 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/cli-adapters/provider-cli-shared.ts +18 -10
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +5 -3
- package/src/detection/ide-detector.ts +26 -16
- package/src/index.ts +1 -1
- 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 +163 -0
- package/src/mesh/mesh-ledger.ts +1 -1
- package/src/mesh/mesh-work-queue.ts +23 -41
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/provider-loader.ts +10 -4
- package/src/providers/version-archive.ts +38 -20
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
package/dist/installer.d.ts
CHANGED
|
@@ -28,10 +28,7 @@ export interface InstallResult {
|
|
|
28
28
|
alreadyInstalled: boolean;
|
|
29
29
|
error?: string;
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
* Check if an extension is already installed
|
|
33
|
-
*/
|
|
34
|
-
export declare function isExtensionInstalled(ide: IDEInfo, marketplaceId: string): boolean;
|
|
31
|
+
export declare function isExtensionInstalled(ide: IDEInfo, marketplaceId: string): Promise<boolean>;
|
|
35
32
|
/**
|
|
36
33
|
* Install a single extension
|
|
37
34
|
*/
|
package/dist/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;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class AsyncBatchWriter {
|
|
2
|
+
private static buffers;
|
|
3
|
+
private static writePromises;
|
|
4
|
+
private static flushTimer;
|
|
5
|
+
/**
|
|
6
|
+
* Queues data to be written to a file asynchronously in a batch.
|
|
7
|
+
*/
|
|
8
|
+
static write(filePath: string, data: string): void;
|
|
9
|
+
private static flushAll;
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
2
|
+
export declare class BeadsDB {
|
|
3
|
+
private static instance;
|
|
4
|
+
private readonly db;
|
|
5
|
+
private readonly migratedMeshIds;
|
|
6
|
+
private constructor();
|
|
7
|
+
static getInstance(): BeadsDB;
|
|
8
|
+
static resetForTests(): void;
|
|
9
|
+
close(): void;
|
|
10
|
+
transaction<T>(fn: () => T): T;
|
|
11
|
+
private migrate;
|
|
12
|
+
private ensureLegacyQueueMigrated;
|
|
13
|
+
getQueueEntries(meshId: string, statuses?: MeshTaskStatus[]): MeshWorkQueueEntry[];
|
|
14
|
+
getQueueRevision(meshId: string): string;
|
|
15
|
+
replaceQueue(meshId: string, queue: MeshWorkQueueEntry[]): void;
|
|
16
|
+
deleteQueue(meshId: string): void;
|
|
17
|
+
private toRow;
|
|
18
|
+
}
|
|
@@ -66,6 +66,7 @@ export declare function enqueueTask(meshId: string, message: string, opts?: {
|
|
|
66
66
|
export declare function getQueue(meshId: string, opts?: {
|
|
67
67
|
status?: MeshTaskStatus[];
|
|
68
68
|
}): MeshWorkQueueEntry[];
|
|
69
|
+
export declare function getMeshQueueRevision(meshId: string): string;
|
|
69
70
|
/**
|
|
70
71
|
* Find the next pending task that this node is allowed to claim, and mark it as assigned.
|
|
71
72
|
*/
|
|
@@ -123,3 +124,6 @@ export interface MeshWorkQueueStats {
|
|
|
123
124
|
* Return aggregate queue statistics for the given mesh.
|
|
124
125
|
*/
|
|
125
126
|
export declare function getMeshQueueStats(meshId: string): MeshWorkQueueStats;
|
|
127
|
+
export declare function __replaceMeshQueueForTests(meshId: string, queue: MeshWorkQueueEntry[]): void;
|
|
128
|
+
export declare function __clearMeshQueueForTests(meshId: string): void;
|
|
129
|
+
export declare function __resetBeadsDBForTests(): void;
|
|
@@ -46,6 +46,8 @@ export declare class DaemonStatusReporter {
|
|
|
46
46
|
private lastStatusSentAt;
|
|
47
47
|
private statusPendingThrottle;
|
|
48
48
|
private lastP2PStatusHash;
|
|
49
|
+
private lastP2PStatusSentAt;
|
|
50
|
+
private p2pDebounceTimer;
|
|
49
51
|
private lastServerStatusHash;
|
|
50
52
|
private lastStatusSummary;
|
|
51
53
|
private statusTimer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.9.82-rc.
|
|
3
|
+
"version": "0.9.82-rc.67",
|
|
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",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"@adhdev/session-host-core": "*",
|
|
50
50
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
51
51
|
"@xterm/xterm": "^6.0.0",
|
|
52
|
+
"better-sqlite3": "^12.10.0",
|
|
52
53
|
"chalk": "^5.3.0",
|
|
53
54
|
"chokidar": "^4.0.3",
|
|
54
55
|
"conf": "^13.0.0",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"@adhdev/ghostty-vt-node": "*"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
64
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
63
65
|
"@types/js-yaml": "^4.0.9",
|
|
64
66
|
"@types/node": "^22.0.0",
|
|
65
67
|
"@types/ws": "^8.18.1",
|
|
@@ -484,17 +484,25 @@ export function findBinary(name: string): string {
|
|
|
484
484
|
return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);
|
|
485
485
|
}
|
|
486
486
|
const isWin = os.platform() === 'win32';
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
487
|
+
const paths = (process.env.PATH || '').split(path.delimiter);
|
|
488
|
+
const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
|
|
489
|
+
|
|
490
|
+
for (const p of paths) {
|
|
491
|
+
if (!p) continue;
|
|
492
|
+
for (const ext of exes) {
|
|
493
|
+
const fullPath = path.join(p, trimmed + ext);
|
|
494
|
+
try {
|
|
495
|
+
const fs = require('fs');
|
|
496
|
+
if (fs.existsSync(fullPath)) {
|
|
497
|
+
const stat = fs.statSync(fullPath);
|
|
498
|
+
if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
|
|
499
|
+
return fullPath;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} catch { }
|
|
503
|
+
}
|
|
497
504
|
}
|
|
505
|
+
return isWin ? `${trimmed}.cmd` : trimmed;
|
|
498
506
|
}
|
|
499
507
|
|
|
500
508
|
export function isScriptBinary(binaryPath: string): boolean {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process'
|
|
2
1
|
import { createHash } from 'node:crypto'
|
|
3
|
-
import { existsSync, readdirSync, realpathSync } from 'node:fs'
|
|
4
|
-
import { createRequire } from 'node:module'
|
|
5
2
|
import * as os from 'node:os'
|
|
6
|
-
import {
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path'
|
|
7
4
|
import type { ProviderModule, MeshCoordinatorMcpConfigFormat } from '../providers/contracts.js'
|
|
8
5
|
|
|
9
6
|
export interface MeshCoordinatorMcpServerLaunch {
|
|
@@ -55,7 +52,7 @@ export interface ResolveMeshCoordinatorSetupOptions {
|
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
const DEFAULT_SERVER_NAME = 'adhdev-mesh'
|
|
58
|
-
const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev
|
|
55
|
+
const DEFAULT_ADHDEV_MCP_COMMAND = 'adhdev'
|
|
59
56
|
const HERMES_CLI_TYPE = 'hermes-cli'
|
|
60
57
|
const HERMES_MCP_CONFIG_PATH = '~/.hermes/config.yaml'
|
|
61
58
|
|
|
@@ -67,8 +64,7 @@ function isHermesProvider(provider: ProviderModule | null | undefined, cliType?:
|
|
|
67
64
|
function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupOptions): MeshCoordinatorSetup {
|
|
68
65
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
69
66
|
meshId: options.meshId,
|
|
70
|
-
|
|
71
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
67
|
+
adhdevMcpCommand: options.adhdevMcpCommand,
|
|
72
68
|
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
73
69
|
adhdevMcpPort: options.adhdevMcpPort,
|
|
74
70
|
})
|
|
@@ -100,7 +96,7 @@ export function createHermesManualMeshCoordinatorSetup(meshId: string, workspace
|
|
|
100
96
|
requiresRestart: true,
|
|
101
97
|
instructions: 'Hermes CLI does not auto-import repo-local .mcp.json. Add this MCP server to Hermes config under mcp_servers, then start a fresh Hermes session.',
|
|
102
98
|
template: renderMeshCoordinatorTemplate(
|
|
103
|
-
'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
|
|
99
|
+
'mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - mcp\n - --mode\n - ipc\n - --repo-mesh\n - {{meshId}}\n enabled: true\n',
|
|
104
100
|
{
|
|
105
101
|
meshId,
|
|
106
102
|
workspace,
|
|
@@ -141,8 +137,7 @@ export function resolveMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetup
|
|
|
141
137
|
}
|
|
142
138
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
143
139
|
meshId,
|
|
144
|
-
|
|
145
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
140
|
+
adhdevMcpCommand: options.adhdevMcpCommand,
|
|
146
141
|
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
147
142
|
adhdevMcpPort: options.adhdevMcpPort,
|
|
148
143
|
})
|
|
@@ -222,25 +217,25 @@ function resolveMcpConfigPath(configPath: string, workspace: string): string {
|
|
|
222
217
|
|
|
223
218
|
function resolveAdhdevMcpServerLaunch(options: {
|
|
224
219
|
meshId: string
|
|
225
|
-
|
|
226
|
-
adhdevMcpEntryPath?: string
|
|
220
|
+
adhdevMcpCommand?: string
|
|
227
221
|
adhdevMcpTransport?: 'local' | 'ipc'
|
|
228
222
|
adhdevMcpPort?: number
|
|
229
223
|
}): MeshCoordinatorMcpServerLaunch | null {
|
|
230
|
-
const
|
|
231
|
-
if (!entryPath) return null
|
|
232
|
-
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable)
|
|
233
|
-
if (!nodeExecutable) return null
|
|
224
|
+
const command = resolveAdhdevCommand(options.adhdevMcpCommand)
|
|
234
225
|
const transport = resolveMcpTransport(options.adhdevMcpTransport)
|
|
235
|
-
const args = [
|
|
226
|
+
const args = ['mcp', '--mode', transport, '--repo-mesh', options.meshId]
|
|
236
227
|
const port = resolveMcpPort(options.adhdevMcpPort)
|
|
237
228
|
if (port !== undefined) args.push('--port', String(port))
|
|
238
229
|
return {
|
|
239
|
-
command
|
|
230
|
+
command,
|
|
240
231
|
args,
|
|
241
232
|
}
|
|
242
233
|
}
|
|
243
234
|
|
|
235
|
+
function resolveAdhdevCommand(explicitCommand?: string): string {
|
|
236
|
+
return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND
|
|
237
|
+
}
|
|
238
|
+
|
|
244
239
|
function resolveMcpTransport(explicitTransport?: 'local' | 'ipc'): 'local' | 'ipc' {
|
|
245
240
|
if (explicitTransport === 'local' || explicitTransport === 'ipc') return explicitTransport
|
|
246
241
|
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim()
|
|
@@ -254,128 +249,3 @@ function resolveMcpPort(explicitPort?: number): number | undefined {
|
|
|
254
249
|
const parsed = Number(raw)
|
|
255
250
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined
|
|
256
251
|
}
|
|
257
|
-
|
|
258
|
-
function resolveMcpNodeExecutable(explicitExecutable?: string): string | null {
|
|
259
|
-
const explicit = explicitExecutable?.trim()
|
|
260
|
-
if (explicit) return explicit
|
|
261
|
-
|
|
262
|
-
const candidates: string[] = []
|
|
263
|
-
const addCandidate = (candidate?: string | null) => {
|
|
264
|
-
const trimmed = candidate?.trim()
|
|
265
|
-
if (!trimmed) return
|
|
266
|
-
const normalized = normalizeExistingPath(trimmed) || trimmed
|
|
267
|
-
if (!candidates.includes(normalized)) candidates.push(normalized)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
addCandidate(process.env.ADHDEV_MCP_NODE_EXECUTABLE)
|
|
271
|
-
addCandidate(process.env.ADHDEV_NODE_EXECUTABLE)
|
|
272
|
-
addCandidate(process.env.npm_node_execpath)
|
|
273
|
-
addNodeCandidatesFromPath(process.env.PATH, addCandidate)
|
|
274
|
-
addNodeCandidatesFromNvm(os.homedir(), addCandidate)
|
|
275
|
-
addCandidate('/opt/homebrew/bin/node')
|
|
276
|
-
addCandidate('/usr/local/bin/node')
|
|
277
|
-
addCandidate('/usr/bin/node')
|
|
278
|
-
addCandidate(process.execPath)
|
|
279
|
-
|
|
280
|
-
for (const candidate of candidates) {
|
|
281
|
-
if (nodeRuntimeSupportsWebSocket(candidate)) return candidate
|
|
282
|
-
}
|
|
283
|
-
return null
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function addNodeCandidatesFromPath(pathValue: string | undefined, addCandidate: (candidate?: string | null) => void) {
|
|
287
|
-
for (const entry of (pathValue || '').split(':')) {
|
|
288
|
-
const dir = entry.trim()
|
|
289
|
-
if (!dir) continue
|
|
290
|
-
addCandidate(join(dir, 'node'))
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function addNodeCandidatesFromNvm(homeDir: string, addCandidate: (candidate?: string | null) => void) {
|
|
295
|
-
const versionsDir = join(homeDir, '.nvm', 'versions', 'node')
|
|
296
|
-
try {
|
|
297
|
-
const versionDirs = readdirSync(versionsDir, { withFileTypes: true })
|
|
298
|
-
.filter((entry) => entry.isDirectory())
|
|
299
|
-
.map((entry) => entry.name)
|
|
300
|
-
.sort(compareNodeVersionNamesDescending)
|
|
301
|
-
for (const versionDir of versionDirs) {
|
|
302
|
-
addCandidate(join(versionsDir, versionDir, 'bin', 'node'))
|
|
303
|
-
}
|
|
304
|
-
} catch {
|
|
305
|
-
// nvm is optional; PATH and process.execPath candidates still cover normal installs.
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function compareNodeVersionNamesDescending(a: string, b: string): number {
|
|
310
|
-
const parse = (value: string) => value.replace(/^v/, '').split('.').map((part) => Number.parseInt(part, 10) || 0)
|
|
311
|
-
const left = parse(a)
|
|
312
|
-
const right = parse(b)
|
|
313
|
-
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
314
|
-
const diff = (right[i] || 0) - (left[i] || 0)
|
|
315
|
-
if (diff !== 0) return diff
|
|
316
|
-
}
|
|
317
|
-
return b.localeCompare(a)
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function nodeRuntimeSupportsWebSocket(nodeExecutable: string): boolean {
|
|
321
|
-
try {
|
|
322
|
-
execFileSync(nodeExecutable, ['-e', "process.exit(typeof WebSocket === 'function' ? 0 : 42)"], {
|
|
323
|
-
stdio: 'ignore',
|
|
324
|
-
timeout: 3000,
|
|
325
|
-
})
|
|
326
|
-
return true
|
|
327
|
-
} catch {
|
|
328
|
-
return false
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function resolveAdhdevMcpEntryPath(explicitPath?: string): string | null {
|
|
333
|
-
const explicit = explicitPath?.trim()
|
|
334
|
-
if (explicit) return normalizeExistingPath(explicit) || explicit
|
|
335
|
-
|
|
336
|
-
const envPath = process.env.ADHDEV_MCP_SERVER_PATH?.trim()
|
|
337
|
-
if (envPath) return normalizeExistingPath(envPath) || envPath
|
|
338
|
-
|
|
339
|
-
const candidates: string[] = []
|
|
340
|
-
const addCandidate = (candidate: string) => {
|
|
341
|
-
if (!candidates.includes(candidate)) candidates.push(candidate)
|
|
342
|
-
}
|
|
343
|
-
const addPackagedCandidates = (baseFile?: string) => {
|
|
344
|
-
if (!baseFile) return
|
|
345
|
-
const realBase = normalizeExistingPath(baseFile) || baseFile
|
|
346
|
-
const dir = dirname(realBase)
|
|
347
|
-
addCandidate(resolve(dir, '../vendor/mcp-server/index.js'))
|
|
348
|
-
addCandidate(resolve(dir, '../../vendor/mcp-server/index.js'))
|
|
349
|
-
addCandidate(resolve(dir, '../../../vendor/mcp-server/index.js'))
|
|
350
|
-
// Source checkout/dev mode does not vendor the MCP server into daemon-standalone.
|
|
351
|
-
// Resolve the sibling workspace build directly so Repo Mesh auto-import still
|
|
352
|
-
// writes an absolute Node entrypoint instead of falling back to a PATH bin shim.
|
|
353
|
-
addCandidate(resolve(dir, '../../mcp-server/dist/index.js'))
|
|
354
|
-
addCandidate(resolve(dir, '../../../mcp-server/dist/index.js'))
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
addPackagedCandidates(process.argv[1])
|
|
358
|
-
|
|
359
|
-
for (const candidate of candidates) {
|
|
360
|
-
const normalized = normalizeExistingPath(candidate)
|
|
361
|
-
if (normalized) return normalized
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
try {
|
|
365
|
-
const requireBase = process.argv[1] ? (normalizeExistingPath(process.argv[1]) || process.argv[1]) : join(process.cwd(), 'adhdev-daemon.js')
|
|
366
|
-
const req = createRequire(requireBase)
|
|
367
|
-
const resolvedModule = req.resolve('@adhdev/mcp-server')
|
|
368
|
-
return normalizeExistingPath(resolvedModule) || resolvedModule
|
|
369
|
-
} catch {
|
|
370
|
-
return null
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function normalizeExistingPath(filePath: string): string | null {
|
|
375
|
-
try {
|
|
376
|
-
if (!existsSync(filePath)) return null
|
|
377
|
-
return realpathSync.native(filePath)
|
|
378
|
-
} catch {
|
|
379
|
-
return null
|
|
380
|
-
}
|
|
381
|
-
}
|
package/src/commands/router.ts
CHANGED
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
import { buildMachineInfo, buildStatusSnapshot } from '../status/snapshot.js';
|
|
54
54
|
import { getSessionCompletionMarker } from '../status/snapshot.js';
|
|
55
55
|
import { execNpmCommandSync, resolveCurrentGlobalInstallSurface, spawnDetachedDaemonUpgradeHelper } from './upgrade-helper.js';
|
|
56
|
+
import { getMeshQueueRevision } from '../mesh/mesh-work-queue.js';
|
|
56
57
|
import type { RepoMeshSessionCleanupMode } from '../repo-mesh-types.js';
|
|
57
58
|
import { homedir } from 'os';
|
|
58
59
|
import { join as pathJoin, resolve as pathResolve } from 'path';
|
|
@@ -1706,7 +1707,7 @@ export class DaemonCommandRouter {
|
|
|
1706
1707
|
* the mesh doesn't exist in the local meshes.json file. */
|
|
1707
1708
|
private inlineMeshCache = new Map<string, any>();
|
|
1708
1709
|
/** Coordinator-owned whole-mesh aggregate status snapshots. Browser callers read this by default. */
|
|
1709
|
-
private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any }>();
|
|
1710
|
+
private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any; queueRevision: string }>();
|
|
1710
1711
|
/** In-memory async Refinery jobs keyed by meshId:nodeId to reject/return duplicate in-flight requests. */
|
|
1711
1712
|
private runningRefineJobs = new Map<string, MeshRefineJobHandle>();
|
|
1712
1713
|
/** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
|
|
@@ -1796,6 +1797,7 @@ export class DaemonCommandRouter {
|
|
|
1796
1797
|
private getCachedAggregateMeshStatus(meshId: string, mesh?: any, options?: { requireDirectPeerTruth?: boolean }): any | null {
|
|
1797
1798
|
const cached = this.aggregateMeshStatusCache.get(meshId);
|
|
1798
1799
|
if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
|
|
1800
|
+
if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
|
|
1799
1801
|
let snapshot = this.cloneJsonValue(cached.snapshot);
|
|
1800
1802
|
snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
|
|
1801
1803
|
if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
|
|
@@ -1840,7 +1842,7 @@ export class DaemonCommandRouter {
|
|
|
1840
1842
|
returnedAt: new Date(builtAt).toISOString(),
|
|
1841
1843
|
},
|
|
1842
1844
|
};
|
|
1843
|
-
this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next) });
|
|
1845
|
+
this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next), queueRevision: getMeshQueueRevision(meshId) });
|
|
1844
1846
|
return next;
|
|
1845
1847
|
}
|
|
1846
1848
|
|
|
@@ -5101,7 +5103,7 @@ export class DaemonCommandRouter {
|
|
|
5101
5103
|
|
|
5102
5104
|
// 3. Kill OS process if requested
|
|
5103
5105
|
if (killProcess) {
|
|
5104
|
-
const running = isIdeRunning(ideType);
|
|
5106
|
+
const running = await isIdeRunning(ideType);
|
|
5105
5107
|
if (running) {
|
|
5106
5108
|
LOG.info('StopIDE', `Killing IDE process: ${ideType}`);
|
|
5107
5109
|
const killed = await killIdeProcess(ideType);
|
|
@@ -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
|
@@ -186,7 +186,7 @@ export { buildMeshLedgerReconciliationEvidence, buildMeshLedgerReplicaEvidence }
|
|
|
186
186
|
export type { MeshLedgerReconciliationEvidence, MeshLedgerReplicaEvidence, MeshLedgerReplicaStatus } from './mesh/mesh-ledger-reconciliation.js';
|
|
187
187
|
|
|
188
188
|
// ── Mesh Work Queue (GUPP) ──
|
|
189
|
-
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, normalizeMeshTaskMode, validateMeshTaskModeRequest } 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
190
|
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshTaskMode, MeshWorkQueueStats, MeshQueueMutationOptions, MeshTaskModeValidationResult } from './mesh/mesh-work-queue.js';
|
|
191
191
|
export { buildMeshActiveWork, buildMeshActiveWorkSummary } from './mesh/mesh-active-work.js';
|
|
192
192
|
export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource } from './mesh/mesh-active-work.js';
|
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;
|