@adhdev/daemon-core 0.9.82-rc.66 → 0.9.82-rc.68

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.
@@ -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.66",
3
+ "version": "0.9.82-rc.68",
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
- try {
488
- const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
489
- return execSync(cmd, {
490
- encoding: 'utf-8',
491
- timeout: 5000,
492
- stdio: ['pipe', 'pipe', 'pipe'],
493
- ...(isWin ? { windowsHide: true } : {}),
494
- }).trim().split('\n')[0].trim();
495
- } catch {
496
- return isWin ? `${trimmed}.cmd` : trimmed;
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 { dirname, isAbsolute, join, resolve } from 'node:path'
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-mcp'
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
- nodeExecutable: options.nodeExecutable,
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
- nodeExecutable: options.nodeExecutable,
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
- nodeExecutable?: string
226
- adhdevMcpEntryPath?: string
220
+ adhdevMcpCommand?: string
227
221
  adhdevMcpTransport?: 'local' | 'ipc'
228
222
  adhdevMcpPort?: number
229
223
  }): MeshCoordinatorMcpServerLaunch | null {
230
- const entryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath)
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 = [entryPath, '--mode', transport, '--repo-mesh', options.meshId]
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: nodeExecutable,
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
- }
@@ -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 { execSync } from 'child_process';
11
- import { existsSync } from 'fs';
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
- try {
77
- const result = execSync(
78
- platform() === 'win32' ? `where ${trimmed}` : `which ${trimmed}`,
79
- { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
80
- ).trim();
81
- return result.split('\n')[0] || null;
82
- } catch {
83
- return null;
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 result = execSync(`"${cliCommand}" --version`, {
100
+ const { stdout } = await execAsync(`"${cliCommand}" --version`, {
90
101
  encoding: 'utf-8',
91
102
  timeout: 10000,
92
- stdio: ['pipe', 'pipe', 'pipe'],
93
- }).trim();
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';
@@ -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
- export function isExtensionInstalled(
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 result = execSync(`"${ide.cliCommand}" --list-extensions`, {
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 = result
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;