@adhdev/daemon-core 0.9.82-rc.65 → 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 +850 -588
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +851 -590
- 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 +144 -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';
|
|
@@ -1084,6 +1085,24 @@ type MeshRefinePatchEquivalenceSummary = {
|
|
|
1084
1085
|
stderr?: string;
|
|
1085
1086
|
};
|
|
1086
1087
|
|
|
1088
|
+
type MeshRefineSubmoduleReachabilityEntry = {
|
|
1089
|
+
path: string;
|
|
1090
|
+
commit: string;
|
|
1091
|
+
reachable: boolean;
|
|
1092
|
+
checkedLocal?: boolean;
|
|
1093
|
+
fetchedFromOrigin?: boolean;
|
|
1094
|
+
error?: string;
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
type MeshRefineSubmoduleReachabilitySummary = {
|
|
1098
|
+
status: MeshRefineStageStatus;
|
|
1099
|
+
checked: number;
|
|
1100
|
+
unreachable: MeshRefineSubmoduleReachabilityEntry[];
|
|
1101
|
+
entries: MeshRefineSubmoduleReachabilityEntry[];
|
|
1102
|
+
durationMs: number;
|
|
1103
|
+
error?: string;
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1087
1106
|
type MeshRefineAsyncJobStatus = 'accepted' | 'completed' | 'failed';
|
|
1088
1107
|
|
|
1089
1108
|
type MeshRefineJobHandle = {
|
|
@@ -1216,6 +1235,95 @@ async function runMeshRefinePatchEquivalenceGate(
|
|
|
1216
1235
|
}
|
|
1217
1236
|
}
|
|
1218
1237
|
|
|
1238
|
+
async function runMeshRefineSubmoduleReachabilityGate(
|
|
1239
|
+
repoRoot: string,
|
|
1240
|
+
mergedTree: string,
|
|
1241
|
+
): Promise<MeshRefineSubmoduleReachabilitySummary> {
|
|
1242
|
+
const startedAt = Date.now();
|
|
1243
|
+
const entries: MeshRefineSubmoduleReachabilityEntry[] = [];
|
|
1244
|
+
try {
|
|
1245
|
+
const { execFile } = await import('node:child_process');
|
|
1246
|
+
const { promisify } = await import('node:util');
|
|
1247
|
+
const execFileAsync = promisify(execFile);
|
|
1248
|
+
const runGit = async (cwd: string, args: string[]): Promise<string> => {
|
|
1249
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
1250
|
+
cwd,
|
|
1251
|
+
encoding: 'utf8',
|
|
1252
|
+
timeout: 30_000,
|
|
1253
|
+
maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
|
|
1254
|
+
windowsHide: true,
|
|
1255
|
+
});
|
|
1256
|
+
return String(stdout || '');
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
const treeOutput = await runGit(repoRoot, ['ls-tree', '-r', '-z', mergedTree]);
|
|
1260
|
+
const gitlinks = treeOutput
|
|
1261
|
+
.split('\0')
|
|
1262
|
+
.filter(Boolean)
|
|
1263
|
+
.map(record => {
|
|
1264
|
+
const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record);
|
|
1265
|
+
return match ? { commit: match[1], path: match[2] } : null;
|
|
1266
|
+
})
|
|
1267
|
+
.filter((entry): entry is { commit: string; path: string } => !!entry);
|
|
1268
|
+
|
|
1269
|
+
for (const gitlink of gitlinks) {
|
|
1270
|
+
const submodulePath = pathResolve(repoRoot, gitlink.path);
|
|
1271
|
+
const entry: MeshRefineSubmoduleReachabilityEntry = {
|
|
1272
|
+
path: gitlink.path,
|
|
1273
|
+
commit: gitlink.commit,
|
|
1274
|
+
reachable: false,
|
|
1275
|
+
};
|
|
1276
|
+
try {
|
|
1277
|
+
if (!fs.existsSync(submodulePath)) {
|
|
1278
|
+
entry.error = `Submodule checkout missing at ${gitlink.path}`;
|
|
1279
|
+
entries.push(entry);
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
entry.checkedLocal = true;
|
|
1284
|
+
try {
|
|
1285
|
+
await runGit(submodulePath, ['cat-file', '-e', `${gitlink.commit}^{commit}`]);
|
|
1286
|
+
entry.reachable = true;
|
|
1287
|
+
entries.push(entry);
|
|
1288
|
+
continue;
|
|
1289
|
+
} catch {
|
|
1290
|
+
// Probe the submodule remote before allowing cleanup/completion.
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
try {
|
|
1294
|
+
await runGit(submodulePath, ['fetch', 'origin', gitlink.commit]);
|
|
1295
|
+
entry.fetchedFromOrigin = true;
|
|
1296
|
+
await runGit(submodulePath, ['cat-file', '-e', `${gitlink.commit}^{commit}`]);
|
|
1297
|
+
entry.reachable = true;
|
|
1298
|
+
} catch (e: any) {
|
|
1299
|
+
entry.error = truncateValidationOutput(e?.stderr || e?.message || String(e));
|
|
1300
|
+
}
|
|
1301
|
+
} catch (e: any) {
|
|
1302
|
+
entry.error = truncateValidationOutput(e?.message || String(e));
|
|
1303
|
+
}
|
|
1304
|
+
entries.push(entry);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const unreachable = entries.filter(entry => !entry.reachable);
|
|
1308
|
+
return {
|
|
1309
|
+
status: unreachable.length ? 'failed' : 'passed',
|
|
1310
|
+
checked: entries.length,
|
|
1311
|
+
unreachable,
|
|
1312
|
+
entries,
|
|
1313
|
+
durationMs: Date.now() - startedAt,
|
|
1314
|
+
};
|
|
1315
|
+
} catch (e: any) {
|
|
1316
|
+
return {
|
|
1317
|
+
status: 'failed',
|
|
1318
|
+
checked: entries.length,
|
|
1319
|
+
unreachable: entries.filter(entry => !entry.reachable),
|
|
1320
|
+
entries,
|
|
1321
|
+
durationMs: Date.now() - startedAt,
|
|
1322
|
+
error: truncateValidationOutput(e?.message || String(e)),
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1219
1327
|
function buildMeshRefineValidationPlan(mesh: any, workspace: string): Record<string, unknown> {
|
|
1220
1328
|
const plan = resolveMeshRefineValidationPlan(mesh, workspace);
|
|
1221
1329
|
return {
|
|
@@ -1599,7 +1707,7 @@ export class DaemonCommandRouter {
|
|
|
1599
1707
|
* the mesh doesn't exist in the local meshes.json file. */
|
|
1600
1708
|
private inlineMeshCache = new Map<string, any>();
|
|
1601
1709
|
/** Coordinator-owned whole-mesh aggregate status snapshots. Browser callers read this by default. */
|
|
1602
|
-
private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any }>();
|
|
1710
|
+
private aggregateMeshStatusCache = new Map<string, { builtAt: number; snapshot: any; queueRevision: string }>();
|
|
1603
1711
|
/** In-memory async Refinery jobs keyed by meshId:nodeId to reject/return duplicate in-flight requests. */
|
|
1604
1712
|
private runningRefineJobs = new Map<string, MeshRefineJobHandle>();
|
|
1605
1713
|
/** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
|
|
@@ -1689,6 +1797,7 @@ export class DaemonCommandRouter {
|
|
|
1689
1797
|
private getCachedAggregateMeshStatus(meshId: string, mesh?: any, options?: { requireDirectPeerTruth?: boolean }): any | null {
|
|
1690
1798
|
const cached = this.aggregateMeshStatusCache.get(meshId);
|
|
1691
1799
|
if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
|
|
1800
|
+
if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
|
|
1692
1801
|
let snapshot = this.cloneJsonValue(cached.snapshot);
|
|
1693
1802
|
snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
|
|
1694
1803
|
if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
|
|
@@ -1733,7 +1842,7 @@ export class DaemonCommandRouter {
|
|
|
1733
1842
|
returnedAt: new Date(builtAt).toISOString(),
|
|
1734
1843
|
},
|
|
1735
1844
|
};
|
|
1736
|
-
this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next) });
|
|
1845
|
+
this.aggregateMeshStatusCache.set(meshId, { builtAt, snapshot: this.cloneJsonValue(next), queueRevision: getMeshQueueRevision(meshId) });
|
|
1737
1846
|
return next;
|
|
1738
1847
|
}
|
|
1739
1848
|
|
|
@@ -2566,6 +2675,38 @@ export class DaemonCommandRouter {
|
|
|
2566
2675
|
};
|
|
2567
2676
|
}
|
|
2568
2677
|
|
|
2678
|
+
const submoduleReachabilityStarted = Date.now();
|
|
2679
|
+
const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead);
|
|
2680
|
+
recordMeshRefineStage(refineStages, 'submodule_reachability', submoduleReachability.status, submoduleReachabilityStarted, {
|
|
2681
|
+
checked: submoduleReachability.checked,
|
|
2682
|
+
unreachable: submoduleReachability.unreachable.map(entry => ({ path: entry.path, commit: entry.commit, error: entry.error })),
|
|
2683
|
+
error: submoduleReachability.error,
|
|
2684
|
+
});
|
|
2685
|
+
if (submoduleReachability.status === 'failed') {
|
|
2686
|
+
return {
|
|
2687
|
+
success: false,
|
|
2688
|
+
code: 'submodule_reachability_failed',
|
|
2689
|
+
convergenceStatus: 'blocked_review',
|
|
2690
|
+
error: 'Refinery submodule reachability preflight failed; merge/refine cleanup was not attempted.',
|
|
2691
|
+
branch,
|
|
2692
|
+
into: baseBranch,
|
|
2693
|
+
validationSummary,
|
|
2694
|
+
patchEquivalence,
|
|
2695
|
+
submoduleReachability,
|
|
2696
|
+
refineStages,
|
|
2697
|
+
finalBranchConvergenceState: {
|
|
2698
|
+
branch,
|
|
2699
|
+
baseBranch,
|
|
2700
|
+
merged: false,
|
|
2701
|
+
removed: false,
|
|
2702
|
+
validation: 'passed',
|
|
2703
|
+
patchEquivalence: 'passed',
|
|
2704
|
+
submoduleReachability: 'failed',
|
|
2705
|
+
status: 'blocked_review',
|
|
2706
|
+
},
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2569
2710
|
let mergeResult: Record<string, unknown> | undefined;
|
|
2570
2711
|
const mergeStarted = Date.now();
|
|
2571
2712
|
try {
|
|
@@ -4962,7 +5103,7 @@ export class DaemonCommandRouter {
|
|
|
4962
5103
|
|
|
4963
5104
|
// 3. Kill OS process if requested
|
|
4964
5105
|
if (killProcess) {
|
|
4965
|
-
const running = isIdeRunning(ideType);
|
|
5106
|
+
const running = await isIdeRunning(ideType);
|
|
4966
5107
|
if (running) {
|
|
4967
5108
|
LOG.info('StopIDE', `Killing IDE process: ${ideType}`);
|
|
4968
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
|
*/
|