@adhdev/daemon-core 0.9.82-rc.15 → 0.9.82-rc.152
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/boot/process-hardening.d.ts +50 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +5 -1
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +104 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +27 -0
- package/dist/commands/handler.d.ts +66 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +5 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +20 -6
- package/dist/index.js +13153 -3031
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13099 -3023
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-registry.d.ts +25 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +77 -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 +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- package/dist/providers/contracts.d.ts +50 -5
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/index.d.ts +11 -0
- package/dist/providers/provider-loader.d.ts +19 -1
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +64 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +169 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +67 -0
- package/dist/shared-types.d.ts +12 -0
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +6 -2
- package/src/boot/daemon-lifecycle.ts +17 -10
- package/src/boot/process-hardening.ts +89 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +3 -1
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1086 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +664 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -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 +57 -11
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1451 -52
- package/src/commands/cli-manager.ts +184 -3
- package/src/commands/handler.ts +547 -2
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3273 -428
- package/src/config/chat-history.ts +79 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +249 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +72 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- 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 +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +40 -22
- package/src/mesh/coordinator-registry.ts +75 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +820 -61
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.ts +39 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +380 -44
- package/src/providers/contracts.ts +52 -5
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +419 -0
- package/src/providers/native-history/index.ts +23 -0
- package/src/providers/provider-loader.ts +294 -28
- package/src/providers/provider-schema.ts +31 -13
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +245 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +444 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +119 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +210 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +77 -0
- package/src/shared-types.ts +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
package/src/git/git-worktree.ts
CHANGED
|
@@ -116,8 +116,11 @@ export async function createWorktree(opts: WorktreeCreateOptions): Promise<Workt
|
|
|
116
116
|
});
|
|
117
117
|
} catch (error: any) {
|
|
118
118
|
const stderr = typeof error.stderr === 'string' ? error.stderr : '';
|
|
119
|
-
// Clean error messages for common failures
|
|
120
119
|
if (/already exists/i.test(stderr)) {
|
|
120
|
+
// Distinguish directory-collision (TOCTOU race) from branch-already-exists
|
|
121
|
+
if (existsSync(targetDir)) {
|
|
122
|
+
throw new Error(`Worktree target directory was created concurrently: ${targetDir}`);
|
|
123
|
+
}
|
|
121
124
|
throw new Error(`Branch '${branch}' already exists or is checked out in another worktree`);
|
|
122
125
|
}
|
|
123
126
|
throw new Error(`git worktree add failed: ${stderr.trim() || error.message}`);
|
|
@@ -170,6 +173,10 @@ export async function removeWorktree(repoRoot: string, worktreePath: string, opt
|
|
|
170
173
|
const stdout = typeof error.stdout === 'string' ? error.stdout : '';
|
|
171
174
|
const detail = `${stderr}\n${stdout}\n${error.message || ''}`;
|
|
172
175
|
if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
|
|
176
|
+
process.stderr.write(
|
|
177
|
+
`[adhdev-mesh] WARNING: git worktree remove --force fallback for submodule worktree '${worktreePath}'. `
|
|
178
|
+
+ `Any uncommitted changes inside submodules will be lost.\n`,
|
|
179
|
+
);
|
|
173
180
|
try {
|
|
174
181
|
await execFileAsync('git', ['worktree', 'remove', '--force', worktreePath], {
|
|
175
182
|
cwd: repoRoot,
|
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,
|
|
@@ -111,6 +115,7 @@ export type {
|
|
|
111
115
|
RepoMeshLedgerEntryStatus,
|
|
112
116
|
RepoMeshLedgerSummaryStatus,
|
|
113
117
|
RepoMeshLedgerStatus,
|
|
118
|
+
MeshAsyncJobLifecycle,
|
|
114
119
|
} from './repo-mesh-types.js';
|
|
115
120
|
export { DEFAULT_MESH_POLICY } from './repo-mesh-types.js';
|
|
116
121
|
|
|
@@ -138,7 +143,7 @@ export type RecentSessionBucket = 'needs_attention' | 'working' | 'task_complete
|
|
|
138
143
|
export type { IDaemonCore, DaemonCoreOptions } from './daemon-core.js';
|
|
139
144
|
|
|
140
145
|
// ── Config ──
|
|
141
|
-
export { loadConfig, saveConfig, resetConfig, isSetupComplete, markSetupComplete, updateConfig } from './config/config.js';
|
|
146
|
+
export { loadConfig, saveConfig, resetConfig, isSetupComplete, markSetupComplete, updateConfig, getDaemonDataDir } from './config/config.js';
|
|
142
147
|
export { getWorkspaceState } from './config/workspaces.js';
|
|
143
148
|
export { appendRecentActivity, getRecentActivity } from './config/recent-activity.js';
|
|
144
149
|
export type { RecentActivityEntry } from './config/recent-activity.js';
|
|
@@ -155,18 +160,53 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
155
160
|
// ── Mesh Coordinator ──
|
|
156
161
|
export { buildCoordinatorSystemPrompt } from './mesh/coordinator-prompt.js';
|
|
157
162
|
export type { CoordinatorPromptContext } from './mesh/coordinator-prompt.js';
|
|
163
|
+
export { loadMeshCoordinatorRegistry, registerMeshCoordinator, unregisterMeshCoordinator, getCoordinatorForSession, listCoordinatorsForWorkspace } from './mesh/coordinator-registry.js';
|
|
164
|
+
export type { CoordinatorRegistryEntry } from './mesh/coordinator-registry.js';
|
|
165
|
+
export {
|
|
166
|
+
MESH_REFINE_CONFIG_LOCATIONS,
|
|
167
|
+
MESH_REFINE_CONFIG_SCHEMA,
|
|
168
|
+
loadMeshRefineConfig,
|
|
169
|
+
resolveMeshRefineValidationPlan,
|
|
170
|
+
suggestMeshRefineConfig,
|
|
171
|
+
validateMeshRefineConfig,
|
|
172
|
+
} from './mesh/refine-config.js';
|
|
173
|
+
export {
|
|
174
|
+
MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS,
|
|
175
|
+
MESH_WORKTREE_BOOTSTRAP_CONFIG_SCHEMA,
|
|
176
|
+
loadMeshWorktreeBootstrapConfig,
|
|
177
|
+
runMeshWorktreeBootstrap,
|
|
178
|
+
validateMeshWorktreeBootstrapConfig,
|
|
179
|
+
type RepoMeshWorktreeBootstrapConfig,
|
|
180
|
+
type WorktreeBootstrapState,
|
|
181
|
+
} from './mesh/worktree-bootstrap-config.js';
|
|
182
|
+
export type {
|
|
183
|
+
MeshRefineValidationCategory,
|
|
184
|
+
MeshRefineValidationCommandPlan,
|
|
185
|
+
MeshRefineValidationPlan,
|
|
186
|
+
RepoMeshRefineConfig,
|
|
187
|
+
RepoMeshRefineValidationCommandConfig,
|
|
188
|
+
} from './mesh/refine-config.js';
|
|
158
189
|
export { syncMeshes } from './mesh/mesh-sync.js';
|
|
159
190
|
export type { MeshSyncTransport, MeshSyncResult, RemoteMeshRecord } from './mesh/mesh-sync.js';
|
|
160
191
|
|
|
161
192
|
// ── 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';
|
|
193
|
+
export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
|
194
|
+
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext, MeshTaskCompletionEvidence, MeshWorkerResultArtifact, MeshProcessArtifact, MeshValidationResultArtifact } from './mesh/mesh-ledger.js';
|
|
195
|
+
export { fastForwardMeshNode } from './mesh/mesh-fast-forward.js';
|
|
196
|
+
export type { MeshFastForwardNodeArgs, MeshFastForwardPlannedStep, MeshFastForwardResult } from './mesh/mesh-fast-forward.js';
|
|
164
197
|
export { buildMeshLedgerReconciliationEvidence, buildMeshLedgerReplicaEvidence } from './mesh/mesh-ledger-reconciliation.js';
|
|
165
198
|
export type { MeshLedgerReconciliationEvidence, MeshLedgerReplicaEvidence, MeshLedgerReplicaStatus } from './mesh/mesh-ledger-reconciliation.js';
|
|
166
199
|
|
|
167
200
|
// ── 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';
|
|
201
|
+
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, getMeshQueueRevision, normalizeMeshTaskMode, validateMeshTaskModeRequest, insertDirectDispatch, getActiveDirectDispatches, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, markStaleDirectDispatches } from './mesh/mesh-work-queue.js';
|
|
202
|
+
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshTaskMode, MeshWorkQueueStats, MeshQueueMutationOptions, MeshTaskModeValidationResult, DirectDispatchRecord } from './mesh/mesh-work-queue.js';
|
|
203
|
+
export { buildCompactStaleDirectWorkSummary, buildMeshActiveWork, buildMeshActiveWorkSummary } from './mesh/mesh-active-work.js';
|
|
204
|
+
export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource, MeshStaleDirectWorkSummary } from './mesh/mesh-active-work.js';
|
|
205
|
+
export { buildMeshAsyncRefineJobs } from './mesh/mesh-refine-status.js';
|
|
206
|
+
export type { MeshAsyncRefineJobStatus, MeshAsyncRefineJobSummary } from './mesh/mesh-refine-status.js';
|
|
207
|
+
|
|
208
|
+
// ── Mesh Host Ownership ──
|
|
209
|
+
export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHostOwner, normalizeMeshDaemonRole, requireMeshHostQueueOwner, resolveMeshHostStatus } from './mesh/mesh-host-ownership.js';
|
|
170
210
|
|
|
171
211
|
// ── Mesh Visualization ──
|
|
172
212
|
// buildMeshGraph and MeshGraph types moved to @adhdev/web-core to avoid
|
|
@@ -415,3 +455,30 @@ export type { ExtensionInfo as InstallerExtensionInfo } from './installer.js';
|
|
|
415
455
|
// ── Boot / Lifecycle ──
|
|
416
456
|
export { initDaemonComponents, startDaemonDevSupport, shutdownDaemonComponents } from './boot/daemon-lifecycle.js';
|
|
417
457
|
export type { DaemonInitConfig, DaemonComponents, DaemonDevSupportOptions } from './boot/daemon-lifecycle.js';
|
|
458
|
+
|
|
459
|
+
// ── Local IPC server (shared between cloud + standalone daemons) ──
|
|
460
|
+
export {
|
|
461
|
+
startLocalIpcServer,
|
|
462
|
+
buildIpcStatusHttpResponse,
|
|
463
|
+
type LocalIpcServerOptions,
|
|
464
|
+
type LocalIpcServerHandle,
|
|
465
|
+
type IpcCommandContext,
|
|
466
|
+
type IpcCommandResult,
|
|
467
|
+
type IpcStatusPayload,
|
|
468
|
+
} from './ipc/local-ipc-server.js';
|
|
469
|
+
|
|
470
|
+
// ── Provider SDK (v1) — selective re-exports for external tooling ──
|
|
471
|
+
// Tooling (registry publish, dashboard validators, the e2e harness) needs
|
|
472
|
+
// the manifest validator, the builder catalog, and the contract version.
|
|
473
|
+
// We don't re-export *everything* from the SDK to keep the public surface
|
|
474
|
+
// stable; consumers that need internal SDK types still import from the
|
|
475
|
+
// sdk/v1 subpath.
|
|
476
|
+
export {
|
|
477
|
+
validateCliProviderManifest,
|
|
478
|
+
formatManifestValidationIssues,
|
|
479
|
+
type ManifestValidationIssue,
|
|
480
|
+
type ManifestValidationResult,
|
|
481
|
+
V1_CONTRACT_VERSION,
|
|
482
|
+
V1_PRIMITIVE_CATALOG,
|
|
483
|
+
V1_ALL_PRIMITIVES,
|
|
484
|
+
} from './providers/sdk/v1/index.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,
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalIpcServer — generic HTTP + WebSocket IPC endpoint for daemon processes.
|
|
3
|
+
*
|
|
4
|
+
* Both daemon-cloud and daemon-standalone need a local IPC surface so
|
|
5
|
+
* external tools — the `adhdev mcp --mode ipc` MCP server and anything
|
|
6
|
+
* else that wants to issue commands without going through the cloud
|
|
7
|
+
* Worker — can connect over `ws://127.0.0.1:<port>/ipc`.
|
|
8
|
+
*
|
|
9
|
+
* Until now the implementation lived only in daemon-cloud. Standalone
|
|
10
|
+
* sessions therefore couldn't host MCP mesh tools — codex/claude config
|
|
11
|
+
* pointing at `--mode ipc` got `Cannot reach ipc daemon`. This module
|
|
12
|
+
* moves the transport into daemon-core so both daemons can mount it.
|
|
13
|
+
*
|
|
14
|
+
* The transport itself is daemon-agnostic. Cloud-specific behaviors
|
|
15
|
+
* (mesh relay, mandatory update gates) are injected via the caller's
|
|
16
|
+
* `handleCommand` hook; the IPC layer just frames/unframes JSON and
|
|
17
|
+
* routes messages.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createServer, type IncomingMessage, type Server as HttpServer } from 'http';
|
|
21
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
22
|
+
import { LOG } from '../logging/logger.js';
|
|
23
|
+
import { DAEMON_WS_PATH } from '../ipc-protocol.js';
|
|
24
|
+
|
|
25
|
+
/** Parameters passed to `handleCommand` for each incoming ext:command frame. */
|
|
26
|
+
export interface IpcCommandContext {
|
|
27
|
+
/** The raw `command` field from the message — caller decides routing. */
|
|
28
|
+
command: string;
|
|
29
|
+
/** Args provided by the client; may have been normalized by the caller. */
|
|
30
|
+
args: Record<string, unknown>;
|
|
31
|
+
/** Stable id correlating the request and the eventual ext:command_result. */
|
|
32
|
+
requestId: string;
|
|
33
|
+
/** The connected WebSocket — only needed if the caller wants to send extra events. */
|
|
34
|
+
ws: WebSocket;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Result reported back to the client. `result` is optional context for success. */
|
|
38
|
+
export interface IpcCommandResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
result?: unknown;
|
|
41
|
+
error?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Additional fields the caller wants merged into the response payload
|
|
44
|
+
* (e.g. the cloud daemon's mandatory-update-block fields). Reserved.
|
|
45
|
+
*/
|
|
46
|
+
extra?: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Status payload returned from GET / on the IPC HTTP port. */
|
|
50
|
+
export interface IpcStatusPayload {
|
|
51
|
+
ok: true;
|
|
52
|
+
pid: number;
|
|
53
|
+
wsPath: string;
|
|
54
|
+
port: number;
|
|
55
|
+
/** Arbitrary daemon-specific summary. Cloud daemon includes mesh state, etc. */
|
|
56
|
+
status: Record<string, unknown> | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface LocalIpcServerOptions {
|
|
60
|
+
/** TCP port to listen on. 19222 is the conventional default. */
|
|
61
|
+
port: number;
|
|
62
|
+
/** Build the GET / status payload. Called on every health probe. */
|
|
63
|
+
buildStatusPayload: () => Record<string, unknown> | null;
|
|
64
|
+
/** Build the welcome message sent to a freshly-connected client. */
|
|
65
|
+
buildWelcomePayload: () => Record<string, unknown>;
|
|
66
|
+
/** Handle a single ext:command frame. Caller decides routing and returns the result. */
|
|
67
|
+
handleCommand: (ctx: IpcCommandContext) => Promise<IpcCommandResult>;
|
|
68
|
+
/** Optional notification when a new client connects (lets daemon track them for broadcasts). */
|
|
69
|
+
onClientConnected?: (ws: WebSocket) => void;
|
|
70
|
+
/** Optional notification when a client disconnects. */
|
|
71
|
+
onClientDisconnected?: (ws: WebSocket) => void;
|
|
72
|
+
/** Optional logger label (defaults to "IPC"). */
|
|
73
|
+
logCategory?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returned controller for stopping the server and broadcasting to clients.
|
|
78
|
+
* Stored by the daemon and torn down at shutdown.
|
|
79
|
+
*/
|
|
80
|
+
export interface LocalIpcServerHandle {
|
|
81
|
+
/** True once `listen()` succeeded. */
|
|
82
|
+
isListening(): boolean;
|
|
83
|
+
/** Push a message to every connected client. */
|
|
84
|
+
broadcast(type: string, payload: unknown): void;
|
|
85
|
+
/** Close all client sockets + the HTTP server. */
|
|
86
|
+
close(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build a JSON HTTP response describing the IPC endpoint. Exposed so caller
|
|
91
|
+
* code (and tests) can build the same shape without spinning a real server.
|
|
92
|
+
*/
|
|
93
|
+
export function buildIpcStatusHttpResponse(
|
|
94
|
+
method: string | undefined,
|
|
95
|
+
url: string | undefined,
|
|
96
|
+
payload: IpcStatusPayload,
|
|
97
|
+
): { statusCode: number; body: Record<string, unknown> } {
|
|
98
|
+
if (method && method.toUpperCase() !== 'GET') {
|
|
99
|
+
return { statusCode: 405, body: { ok: false, error: 'method not allowed' } };
|
|
100
|
+
}
|
|
101
|
+
// `/health` is the probe path used by the MCP IpcTransport ping —
|
|
102
|
+
// mirror cloud daemon behavior so `adhdev mcp --mode ipc` recognizes
|
|
103
|
+
// us as a live IPC host. `/` and `/status` return the same shape.
|
|
104
|
+
if (url && url !== '/' && url !== '/status' && url !== '/health') {
|
|
105
|
+
return { statusCode: 404, body: { ok: false, error: 'not found' } };
|
|
106
|
+
}
|
|
107
|
+
return { statusCode: 200, body: payload as unknown as Record<string, unknown> };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Start the local IPC server. Returns a handle the caller can use to broadcast
|
|
112
|
+
* events and tear down at shutdown.
|
|
113
|
+
*/
|
|
114
|
+
export async function startLocalIpcServer(opts: LocalIpcServerOptions): Promise<LocalIpcServerHandle> {
|
|
115
|
+
const logCategory = opts.logCategory || 'IPC';
|
|
116
|
+
const clients = new Set<WebSocket>();
|
|
117
|
+
let httpServer: HttpServer | null = null;
|
|
118
|
+
let wss: WebSocketServer | null = null;
|
|
119
|
+
let listening = false;
|
|
120
|
+
|
|
121
|
+
httpServer = createServer((req, res) => {
|
|
122
|
+
const payload: IpcStatusPayload = {
|
|
123
|
+
ok: true,
|
|
124
|
+
pid: process.pid,
|
|
125
|
+
wsPath: DAEMON_WS_PATH,
|
|
126
|
+
port: opts.port,
|
|
127
|
+
status: opts.buildStatusPayload(),
|
|
128
|
+
};
|
|
129
|
+
const response = buildIpcStatusHttpResponse(req.method, req.url, payload);
|
|
130
|
+
const body = JSON.stringify(response.body);
|
|
131
|
+
res.writeHead(response.statusCode, {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
'Content-Length': Buffer.byteLength(body),
|
|
134
|
+
'Connection': 'close',
|
|
135
|
+
});
|
|
136
|
+
res.end(body);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
wss = new WebSocketServer({ noServer: true });
|
|
140
|
+
wss.on('connection', (ws) => handleConnection(ws));
|
|
141
|
+
|
|
142
|
+
httpServer.on('upgrade', (req: IncomingMessage, socket, head) => {
|
|
143
|
+
const wsUrl = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
|
|
144
|
+
if (wsUrl.pathname !== DAEMON_WS_PATH) {
|
|
145
|
+
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
|
146
|
+
socket.destroy();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
wss!.handleUpgrade(req, socket, head, (ws) => {
|
|
150
|
+
wss!.emit('connection', ws, req);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
function handleConnection(ws: WebSocket): void {
|
|
155
|
+
clients.add(ws);
|
|
156
|
+
sendWelcome(ws);
|
|
157
|
+
opts.onClientConnected?.(ws);
|
|
158
|
+
|
|
159
|
+
ws.on('message', (raw) => {
|
|
160
|
+
void handleMessage(ws, raw.toString());
|
|
161
|
+
});
|
|
162
|
+
ws.on('close', () => {
|
|
163
|
+
clients.delete(ws);
|
|
164
|
+
opts.onClientDisconnected?.(ws);
|
|
165
|
+
});
|
|
166
|
+
ws.on('error', () => {
|
|
167
|
+
clients.delete(ws);
|
|
168
|
+
opts.onClientDisconnected?.(ws);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function sendWelcome(ws: WebSocket): void {
|
|
173
|
+
try {
|
|
174
|
+
ws.send(JSON.stringify({
|
|
175
|
+
type: 'daemon:welcome',
|
|
176
|
+
payload: opts.buildWelcomePayload(),
|
|
177
|
+
}));
|
|
178
|
+
} catch (error: any) {
|
|
179
|
+
LOG.warn(logCategory, `Failed to send welcome: ${error?.message || error}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleMessage(ws: WebSocket, raw: string): Promise<void> {
|
|
184
|
+
let msg: any;
|
|
185
|
+
try { msg = JSON.parse(raw); } catch { return; }
|
|
186
|
+
if (!msg || typeof msg !== 'object') return;
|
|
187
|
+
|
|
188
|
+
// Re-issue welcome on client request (used by reconnecting MCP servers).
|
|
189
|
+
if (msg.type === 'ext:register') {
|
|
190
|
+
sendWelcome(ws);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (msg.type !== 'ext:command') return;
|
|
194
|
+
|
|
195
|
+
const payload = msg.payload && typeof msg.payload === 'object' ? msg.payload : {};
|
|
196
|
+
const command = typeof payload.command === 'string' ? payload.command : '';
|
|
197
|
+
const args = payload.args && typeof payload.args === 'object'
|
|
198
|
+
? payload.args as Record<string, unknown>
|
|
199
|
+
: {};
|
|
200
|
+
const requestId = typeof payload.requestId === 'string'
|
|
201
|
+
? payload.requestId
|
|
202
|
+
: typeof payload.messageId === 'string'
|
|
203
|
+
? payload.messageId
|
|
204
|
+
: `ipc-${Date.now()}`;
|
|
205
|
+
|
|
206
|
+
if (!command) {
|
|
207
|
+
ws.send(JSON.stringify({
|
|
208
|
+
type: 'ext:command_result',
|
|
209
|
+
payload: { requestId, success: false, error: 'command required' },
|
|
210
|
+
}));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await opts.handleCommand({ command, args, requestId, ws });
|
|
216
|
+
ws.send(JSON.stringify({
|
|
217
|
+
type: 'ext:command_result',
|
|
218
|
+
payload: {
|
|
219
|
+
requestId,
|
|
220
|
+
success: result.success,
|
|
221
|
+
result: result.result,
|
|
222
|
+
error: result.error,
|
|
223
|
+
...(result.extra || {}),
|
|
224
|
+
},
|
|
225
|
+
}));
|
|
226
|
+
} catch (error: any) {
|
|
227
|
+
ws.send(JSON.stringify({
|
|
228
|
+
type: 'ext:command_result',
|
|
229
|
+
payload: {
|
|
230
|
+
requestId,
|
|
231
|
+
success: false,
|
|
232
|
+
error: error?.message || String(error),
|
|
233
|
+
},
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
await new Promise<void>((resolve, reject) => {
|
|
239
|
+
const onError = (error: Error) => {
|
|
240
|
+
httpServer?.off('listening', onListening);
|
|
241
|
+
reject(error);
|
|
242
|
+
};
|
|
243
|
+
const onListening = () => {
|
|
244
|
+
httpServer?.off('error', onError);
|
|
245
|
+
listening = true;
|
|
246
|
+
resolve();
|
|
247
|
+
};
|
|
248
|
+
httpServer!.once('error', onError);
|
|
249
|
+
httpServer!.once('listening', onListening);
|
|
250
|
+
httpServer!.listen(opts.port, '127.0.0.1');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
LOG.info(logCategory, `Local IPC listening on ws://127.0.0.1:${opts.port}${DAEMON_WS_PATH}`);
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
isListening: () => listening,
|
|
257
|
+
broadcast(type, payload) {
|
|
258
|
+
const message = JSON.stringify({ type, payload });
|
|
259
|
+
for (const client of clients) {
|
|
260
|
+
if (client.readyState !== WebSocket.OPEN) continue;
|
|
261
|
+
try { client.send(message); } catch { /* drop silently */ }
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
async close() {
|
|
265
|
+
for (const client of clients) {
|
|
266
|
+
try { client.close(); } catch { /* ignore */ }
|
|
267
|
+
}
|
|
268
|
+
clients.clear();
|
|
269
|
+
await new Promise<void>((resolve) => {
|
|
270
|
+
if (!httpServer) { resolve(); return; }
|
|
271
|
+
httpServer.close(() => resolve());
|
|
272
|
+
});
|
|
273
|
+
httpServer = null;
|
|
274
|
+
wss = null;
|
|
275
|
+
listening = false;
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
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;
|
package/src/launch.ts
CHANGED
|
@@ -16,7 +16,16 @@
|
|
|
16
16
|
* adhdev launch --workspace /path — Open specific workspace
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { exec, spawn, spawnSync } from 'child_process';
|
|
20
|
+
|
|
21
|
+
async function execQuiet(command: string, options: any = {}): Promise<string> {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
exec(command, options, (error, stdout) => {
|
|
24
|
+
if (error) return resolve('');
|
|
25
|
+
resolve(stdout.toString());
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
20
29
|
import * as net from 'net';
|
|
21
30
|
import * as os from 'os';
|
|
22
31
|
import * as path from 'path';
|
|
@@ -76,11 +85,11 @@ function getIdePathCandidates(ideId: string): string[] {
|
|
|
76
85
|
return getProviderLoader().getIdePathCandidates(ideId);
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
function getMacAppProcessPids(ideId: string): number[] {
|
|
88
|
+
async function getMacAppProcessPids(ideId: string): Promise<number[]> {
|
|
80
89
|
const appPaths = getIdePathCandidates(ideId);
|
|
81
90
|
if (appPaths.length === 0) return [];
|
|
82
91
|
try {
|
|
83
|
-
const output =
|
|
92
|
+
const output = await execQuiet('ps axww -o pid=,args=', {
|
|
84
93
|
encoding: 'utf-8',
|
|
85
94
|
timeout: 3000,
|
|
86
95
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -91,8 +100,8 @@ function getMacAppProcessPids(ideId: string): number[] {
|
|
|
91
100
|
}
|
|
92
101
|
}
|
|
93
102
|
|
|
94
|
-
function killMacAppPathProcesses(ideId: string, signal: NodeJS.Signals): boolean {
|
|
95
|
-
const pids = getMacAppProcessPids(ideId);
|
|
103
|
+
async function killMacAppPathProcesses(ideId: string, signal: NodeJS.Signals): Promise<boolean> {
|
|
104
|
+
const pids = (await getMacAppProcessPids(ideId));
|
|
96
105
|
let signalled = false;
|
|
97
106
|
for (const pid of pids) {
|
|
98
107
|
try {
|
|
@@ -163,49 +172,49 @@ export async function killIdeProcess(ideId: string): Promise<boolean> {
|
|
|
163
172
|
if (plat === 'darwin' && appName) {
|
|
164
173
|
// macOS: graceful quit via osascript
|
|
165
174
|
try {
|
|
166
|
-
|
|
175
|
+
await execQuiet(`osascript -e 'tell application "${escapeForAppleScript(appName)}" to quit' 2>/dev/null`, {
|
|
167
176
|
timeout: 5000,
|
|
168
177
|
});
|
|
169
178
|
} catch {
|
|
170
|
-
try {
|
|
179
|
+
try { await execQuiet(`pkill -x "${appName}" 2>/dev/null`, { timeout: 5000 }); } catch { }
|
|
171
180
|
}
|
|
172
|
-
killMacAppPathProcesses(ideId, 'SIGTERM');
|
|
181
|
+
await killMacAppPathProcesses(ideId, 'SIGTERM');
|
|
173
182
|
} else if (plat === 'win32' && winProcesses) {
|
|
174
183
|
// Windows: taskkill for each process name
|
|
175
184
|
for (const proc of winProcesses) {
|
|
176
185
|
try {
|
|
177
|
-
|
|
186
|
+
await execQuiet(`taskkill /IM "${proc}" /F 2>nul`, { timeout: 5000 });
|
|
178
187
|
} catch { }
|
|
179
188
|
}
|
|
180
189
|
// Process name may differ, so also try via WMIC
|
|
181
190
|
try {
|
|
182
191
|
const exeName = winProcesses[0].replace('.exe', '');
|
|
183
|
-
|
|
192
|
+
await execQuiet(`powershell -Command "Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue | Stop-Process -Force"`, {
|
|
184
193
|
timeout: 10000,
|
|
185
194
|
});
|
|
186
195
|
} catch { }
|
|
187
196
|
} else {
|
|
188
|
-
try {
|
|
197
|
+
try { await execQuiet(`pkill -f "${ideId}" 2>/dev/null`); } catch { }
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
// Wait for process kill (max 15 seconds)
|
|
192
201
|
for (let i = 0; i < 30; i++) {
|
|
193
202
|
await new Promise(r => setTimeout(r, 500));
|
|
194
|
-
if (!isIdeRunning(ideId)) return true;
|
|
203
|
+
if (!(await isIdeRunning(ideId))) return true;
|
|
195
204
|
}
|
|
196
205
|
|
|
197
206
|
// Force terminate retry
|
|
198
207
|
if (plat === 'darwin' && appName) {
|
|
199
|
-
try {
|
|
200
|
-
killMacAppPathProcesses(ideId, 'SIGKILL');
|
|
208
|
+
try { await execQuiet(`pkill -9 -x "${appName}" 2>/dev/null`, { timeout: 5000 }); } catch { }
|
|
209
|
+
await killMacAppPathProcesses(ideId, 'SIGKILL');
|
|
201
210
|
} else if (plat === 'win32' && winProcesses) {
|
|
202
211
|
for (const proc of winProcesses) {
|
|
203
|
-
try {
|
|
212
|
+
try { await execQuiet(`taskkill /IM "${proc}" /F 2>nul`); } catch { }
|
|
204
213
|
}
|
|
205
214
|
}
|
|
206
215
|
|
|
207
216
|
await new Promise(r => setTimeout(r, 2000));
|
|
208
|
-
return !isIdeRunning(ideId);
|
|
217
|
+
return !(await isIdeRunning(ideId));
|
|
209
218
|
|
|
210
219
|
} catch {
|
|
211
220
|
return false;
|
|
@@ -213,15 +222,15 @@ export async function killIdeProcess(ideId: string): Promise<boolean> {
|
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
/** Check if IDE process is running */
|
|
216
|
-
export function isIdeRunning(ideId: string): boolean {
|
|
225
|
+
export async function isIdeRunning(ideId: string): Promise<boolean> {
|
|
217
226
|
const plat = os.platform();
|
|
218
227
|
|
|
219
228
|
try {
|
|
220
229
|
if (plat === 'darwin') {
|
|
221
230
|
const appName = getMacAppIdentifiers()[ideId];
|
|
222
|
-
if (!appName) return getMacAppProcessPids(ideId).length > 0;
|
|
231
|
+
if (!appName) return (await getMacAppProcessPids(ideId)).length > 0;
|
|
223
232
|
try {
|
|
224
|
-
const result =
|
|
233
|
+
const result = await execQuiet(`pgrep -x "${appName}" 2>/dev/null`, {
|
|
225
234
|
encoding: 'utf-8',
|
|
226
235
|
timeout: 3000,
|
|
227
236
|
});
|
|
@@ -229,7 +238,7 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
229
238
|
} catch { }
|
|
230
239
|
|
|
231
240
|
try {
|
|
232
|
-
const result =
|
|
241
|
+
const result = await execQuiet(
|
|
233
242
|
`osascript -e 'tell application "System Events" to count (every process whose name is "${escapeForAppleScript(appName)}")'`,
|
|
234
243
|
{
|
|
235
244
|
encoding: 'utf-8',
|
|
@@ -240,21 +249,21 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
240
249
|
if (Number.parseInt(result.trim() || '0', 10) > 0) return true;
|
|
241
250
|
} catch { }
|
|
242
251
|
|
|
243
|
-
return getMacAppProcessPids(ideId).length > 0;
|
|
252
|
+
return (await getMacAppProcessPids(ideId)).length > 0;
|
|
244
253
|
} else if (plat === 'win32') {
|
|
245
254
|
const winProcesses = getWinProcessNames()[ideId];
|
|
246
255
|
if (!winProcesses) return false;
|
|
247
256
|
// Check each process name
|
|
248
257
|
for (const proc of winProcesses) {
|
|
249
258
|
try {
|
|
250
|
-
const result =
|
|
259
|
+
const result = await execQuiet(`tasklist /FI "IMAGENAME eq ${proc}" /NH 2>nul`, { encoding: 'utf-8' });
|
|
251
260
|
if (result.includes(proc)) return true;
|
|
252
261
|
} catch { }
|
|
253
262
|
}
|
|
254
263
|
// Also check via PowerShell (when tasklist cannot find)
|
|
255
264
|
try {
|
|
256
265
|
const exeName = winProcesses[0].replace('.exe', '');
|
|
257
|
-
const result =
|
|
266
|
+
const result = await execQuiet(
|
|
258
267
|
`powershell -Command "(Get-Process -Name '${exeName}' -ErrorAction SilentlyContinue).Count"`,
|
|
259
268
|
{ encoding: 'utf-8', timeout: 5000 }
|
|
260
269
|
);
|
|
@@ -262,7 +271,7 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
262
271
|
} catch { }
|
|
263
272
|
return false;
|
|
264
273
|
} else {
|
|
265
|
-
const result =
|
|
274
|
+
const result = await execQuiet(`pgrep -f "${ideId}" 2>/dev/null`, { encoding: 'utf-8' });
|
|
266
275
|
return result.trim().length > 0;
|
|
267
276
|
}
|
|
268
277
|
} catch {
|
|
@@ -271,14 +280,14 @@ export function isIdeRunning(ideId: string): boolean {
|
|
|
271
280
|
}
|
|
272
281
|
|
|
273
282
|
/** Detect currently open workspace path */
|
|
274
|
-
function detectCurrentWorkspace(ideId: string): string | undefined {
|
|
283
|
+
async function detectCurrentWorkspace(ideId: string): Promise<string | undefined> {
|
|
275
284
|
const plat = os.platform();
|
|
276
285
|
|
|
277
286
|
if (plat === 'darwin') {
|
|
278
287
|
try {
|
|
279
288
|
const appName = getMacAppIdentifiers()[ideId];
|
|
280
289
|
if (!appName) return undefined;
|
|
281
|
-
const result =
|
|
290
|
+
const result = await execQuiet(
|
|
282
291
|
`lsof -c "${appName}" 2>/dev/null | grep cwd | head -1 | awk '{print $NF}'`,
|
|
283
292
|
{ encoding: 'utf-8', timeout: 3000 }
|
|
284
293
|
);
|
|
@@ -392,8 +401,8 @@ export async function launchWithCdp(options: LaunchOptions = {}): Promise<Launch
|
|
|
392
401
|
}
|
|
393
402
|
|
|
394
403
|
// 4. Check if IDE is currently running
|
|
395
|
-
const alreadyRunning = isIdeRunning(targetIde.id);
|
|
396
|
-
const workspace = options.workspace || (alreadyRunning ? detectCurrentWorkspace(targetIde.id) : undefined);
|
|
404
|
+
const alreadyRunning = await isIdeRunning(targetIde.id);
|
|
405
|
+
const workspace = options.workspace || (alreadyRunning ? await detectCurrentWorkspace(targetIde.id) : undefined);
|
|
397
406
|
|
|
398
407
|
// 5. If IDE is running, terminate it
|
|
399
408
|
if (alreadyRunning) {
|