@adhdev/daemon-core 0.9.82-rc.7 → 0.9.82-rc.70
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/daemon-lifecycle.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +2 -0
- package/dist/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/dist/commands/router.d.ts +24 -0
- package/dist/config/mesh-config.d.ts +66 -1
- package/dist/git/git-commands.d.ts +1 -0
- package/dist/git/git-status.d.ts +5 -0
- package/dist/git/git-types.d.ts +10 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +4619 -1143
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4582 -1128
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +18 -0
- package/dist/mesh/mesh-active-work.d.ts +48 -0
- package/dist/mesh/mesh-events.d.ts +28 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +38 -1
- package/dist/mesh/mesh-work-queue.d.ts +27 -5
- package/dist/mesh/refine-config.d.ts +119 -0
- package/dist/providers/chat-message-normalization.d.ts +1 -0
- package/dist/providers/cli-provider-instance.d.ts +1 -0
- package/dist/repo-mesh-types.d.ts +160 -0
- package/dist/status/reporter.d.ts +2 -0
- package/package.json +3 -1
- package/src/boot/daemon-lifecycle.ts +4 -0
- package/src/cli-adapters/provider-cli-adapter.ts +91 -3
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +4 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +20 -10
- package/src/commands/handler.ts +8 -1
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +2452 -409
- package/src/config/chat-history.ts +9 -7
- package/src/config/mesh-config.ts +244 -1
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +3 -3
- package/src/git/git-status.ts +97 -6
- package/src/git/git-summary.ts +3 -0
- package/src/git/git-types.ts +11 -0
- package/src/index.ts +39 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +176 -0
- package/src/mesh/coordinator-prompt.ts +4 -2
- package/src/mesh/mesh-active-work.ts +205 -0
- package/src/mesh/mesh-events.ts +291 -38
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +138 -1
- package/src/mesh/mesh-work-queue.ts +199 -137
- package/src/mesh/refine-config.ts +306 -0
- package/src/providers/chat-message-normalization.ts +3 -1
- package/src/providers/cli-provider-instance.ts +66 -1
- 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/repo-mesh-types.ts +174 -0
- package/src/status/reporter.ts +15 -0
- package/src/system/host-memory.ts +29 -12
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,
|
|
@@ -103,6 +107,14 @@ export type {
|
|
|
103
107
|
LocalMeshNodeEntry,
|
|
104
108
|
RepoMeshStatus,
|
|
105
109
|
RepoMeshNodeStatus,
|
|
110
|
+
RepoMeshSessionStatus,
|
|
111
|
+
RepoMeshQueueTask,
|
|
112
|
+
RepoMeshQueueTaskStatus,
|
|
113
|
+
RepoMeshQueueSummary,
|
|
114
|
+
RepoMeshQueueStatus,
|
|
115
|
+
RepoMeshLedgerEntryStatus,
|
|
116
|
+
RepoMeshLedgerSummaryStatus,
|
|
117
|
+
RepoMeshLedgerStatus,
|
|
106
118
|
} from './repo-mesh-types.js';
|
|
107
119
|
export { DEFAULT_MESH_POLICY } from './repo-mesh-types.js';
|
|
108
120
|
|
|
@@ -147,18 +159,40 @@ export type { CreateMeshOptions, UpdateMeshOptions, AddNodeOptions } from './con
|
|
|
147
159
|
// ── Mesh Coordinator ──
|
|
148
160
|
export { buildCoordinatorSystemPrompt } from './mesh/coordinator-prompt.js';
|
|
149
161
|
export type { CoordinatorPromptContext } from './mesh/coordinator-prompt.js';
|
|
162
|
+
export {
|
|
163
|
+
MESH_REFINE_CONFIG_LOCATIONS,
|
|
164
|
+
MESH_REFINE_CONFIG_SCHEMA,
|
|
165
|
+
loadMeshRefineConfig,
|
|
166
|
+
resolveMeshRefineValidationPlan,
|
|
167
|
+
suggestMeshRefineConfig,
|
|
168
|
+
validateMeshRefineConfig,
|
|
169
|
+
} from './mesh/refine-config.js';
|
|
170
|
+
export type {
|
|
171
|
+
MeshRefineValidationCategory,
|
|
172
|
+
MeshRefineValidationCommandPlan,
|
|
173
|
+
MeshRefineValidationPlan,
|
|
174
|
+
RepoMeshRefineConfig,
|
|
175
|
+
RepoMeshRefineValidationCommandConfig,
|
|
176
|
+
} from './mesh/refine-config.js';
|
|
150
177
|
export { syncMeshes } from './mesh/mesh-sync.js';
|
|
151
178
|
export type { MeshSyncTransport, MeshSyncResult, RemoteMeshRecord } from './mesh/mesh-sync.js';
|
|
152
179
|
|
|
153
180
|
// ── Mesh Task Ledger ──
|
|
154
|
-
export { appendLedgerEntry, appendRemoteLedgerEntries, readLedgerEntries, readLedgerSlice, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
|
155
|
-
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext } from './mesh/mesh-ledger.js';
|
|
181
|
+
export { appendLedgerEntry, appendRemoteLedgerEntries, buildTaskCompletionEvidence, normalizeMeshWorkerResult, readLedgerEntries, readLedgerSlice, getLedgerSummary, getLedgerDir, getSessionRecoveryContext, MAX_LEDGER_SLICE_LIMIT } from './mesh/mesh-ledger.js';
|
|
182
|
+
export type { AppendRemoteLedgerResult, MeshLedgerEntry, MeshLedgerKind, MeshLedgerSlice, MeshLedgerSummary, ReadLedgerOptions, ReadLedgerSliceOptions, SessionRecoveryContext, MeshTaskCompletionEvidence, MeshWorkerResultArtifact, MeshProcessArtifact, MeshValidationResultArtifact } from './mesh/mesh-ledger.js';
|
|
183
|
+
export { fastForwardMeshNode } from './mesh/mesh-fast-forward.js';
|
|
184
|
+
export type { MeshFastForwardNodeArgs, MeshFastForwardPlannedStep, MeshFastForwardResult } from './mesh/mesh-fast-forward.js';
|
|
156
185
|
export { buildMeshLedgerReconciliationEvidence, buildMeshLedgerReplicaEvidence } from './mesh/mesh-ledger-reconciliation.js';
|
|
157
186
|
export type { MeshLedgerReconciliationEvidence, MeshLedgerReplicaEvidence, MeshLedgerReplicaStatus } from './mesh/mesh-ledger-reconciliation.js';
|
|
158
187
|
|
|
159
188
|
// ── Mesh Work Queue (GUPP) ──
|
|
160
|
-
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats } from './mesh/mesh-work-queue.js';
|
|
161
|
-
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshWorkQueueStats } from './mesh/mesh-work-queue.js';
|
|
189
|
+
export { enqueueTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, getMeshQueueRevision, normalizeMeshTaskMode, validateMeshTaskModeRequest } from './mesh/mesh-work-queue.js';
|
|
190
|
+
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshTaskMode, MeshWorkQueueStats, MeshQueueMutationOptions, MeshTaskModeValidationResult } from './mesh/mesh-work-queue.js';
|
|
191
|
+
export { buildMeshActiveWork, buildMeshActiveWorkSummary } from './mesh/mesh-active-work.js';
|
|
192
|
+
export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource } from './mesh/mesh-active-work.js';
|
|
193
|
+
|
|
194
|
+
// ── Mesh Host Ownership ──
|
|
195
|
+
export { buildMeshHostRequiredFailure, createDefaultMeshHostMetadata, isMeshHostOwner, normalizeMeshDaemonRole, requireMeshHostQueueOwner, resolveMeshHostStatus } from './mesh/mesh-host-ownership.js';
|
|
162
196
|
|
|
163
197
|
// ── Mesh Visualization ──
|
|
164
198
|
// buildMeshGraph and MeshGraph types moved to @adhdev/web-core to avoid
|
|
@@ -168,7 +202,7 @@ export type { MeshWorkQueueEntry, MeshTaskStatus, MeshWorkQueueStats } from './m
|
|
|
168
202
|
// export type { MeshGraph, MeshGraphNode, MeshGraphEdge, MeshGraphNodeType, MeshGraphEdgeType } from './mesh/mesh-visualization.js';
|
|
169
203
|
|
|
170
204
|
// ── Mesh Events ──
|
|
171
|
-
export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents } from './mesh/mesh-events.js';
|
|
205
|
+
export { triggerMeshQueue, drainPendingMeshCoordinatorEvents, getPendingMeshCoordinatorEvents, clearPendingMeshCoordinatorEvents, queuePendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
|
|
172
206
|
export type { PendingMeshCoordinatorEvent } from './mesh/mesh-events.js';
|
|
173
207
|
|
|
174
208
|
// ── Mesh P2P Relay Failure Classification ──
|
package/src/installer.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface InstallResult {
|
|
|
31
31
|
/**
|
|
32
32
|
* Check if an extension is already installed
|
|
33
33
|
*/
|
|
34
|
-
export declare function isExtensionInstalled(ide: IDEInfo, marketplaceId: string): boolean
|
|
34
|
+
export declare function isExtensionInstalled(ide: IDEInfo, marketplaceId: string): Promise<boolean>;
|
|
35
35
|
/**
|
|
36
36
|
* Install a single extension
|
|
37
37
|
*/
|
package/src/installer.ts
CHANGED
|
@@ -122,20 +122,22 @@ export interface InstallResult {
|
|
|
122
122
|
/**
|
|
123
123
|
* Check if an extension is already installed
|
|
124
124
|
*/
|
|
125
|
-
|
|
125
|
+
import { promisify } from 'util';
|
|
126
|
+
const execAsync = promisify(exec);
|
|
127
|
+
|
|
128
|
+
export async function isExtensionInstalled(
|
|
126
129
|
ide: IDEInfo,
|
|
127
130
|
marketplaceId: string
|
|
128
|
-
): boolean {
|
|
131
|
+
): Promise<boolean> {
|
|
129
132
|
if (!ide.cliCommand) return false;
|
|
130
133
|
|
|
131
134
|
try {
|
|
132
|
-
const
|
|
135
|
+
const { stdout } = await execAsync(`"${ide.cliCommand}" --list-extensions`, {
|
|
133
136
|
encoding: 'utf-8',
|
|
134
137
|
timeout: 15000,
|
|
135
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
136
138
|
});
|
|
137
139
|
|
|
138
|
-
const installed =
|
|
140
|
+
const installed = stdout
|
|
139
141
|
.trim()
|
|
140
142
|
.split('\n')
|
|
141
143
|
.map((e) => e.trim().toLowerCase());
|
|
@@ -163,7 +165,7 @@ export async function installExtension(
|
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
// Check if already installed
|
|
166
|
-
const alreadyInstalled = isExtensionInstalled(ide, extension.marketplaceId);
|
|
168
|
+
const alreadyInstalled = await isExtensionInstalled(ide, extension.marketplaceId);
|
|
167
169
|
if (alreadyInstalled) {
|
|
168
170
|
return {
|
|
169
171
|
extensionId: extension.id,
|
package/src/launch.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
/** Kill IDE process (graceful → force) */
|
|
19
19
|
export declare function killIdeProcess(ideId: string): Promise<boolean>;
|
|
20
20
|
/** Check if IDE process is running */
|
|
21
|
-
export declare function isIdeRunning(ideId: string): boolean
|
|
21
|
+
export declare function isIdeRunning(ideId: string): Promise<boolean>;
|
|
22
22
|
export interface LaunchOptions {
|
|
23
23
|
ideId?: string;
|
|
24
24
|
workspace?: string;
|
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) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
|
|
3
|
+
export class AsyncBatchWriter {
|
|
4
|
+
// Maps filePath -> string buffer
|
|
5
|
+
private static buffers: Map<string, string[]> = new Map();
|
|
6
|
+
private static writePromises: Map<string, Promise<void>> = new Map();
|
|
7
|
+
private static flushTimer: NodeJS.Timeout | null = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Queues data to be written to a file asynchronously in a batch.
|
|
11
|
+
*/
|
|
12
|
+
public static write(filePath: string, data: string) {
|
|
13
|
+
let buf = this.buffers.get(filePath);
|
|
14
|
+
if (!buf) {
|
|
15
|
+
buf = [];
|
|
16
|
+
this.buffers.set(filePath, buf);
|
|
17
|
+
}
|
|
18
|
+
buf.push(data);
|
|
19
|
+
|
|
20
|
+
if (!this.flushTimer) {
|
|
21
|
+
this.flushTimer = setTimeout(() => {
|
|
22
|
+
this.flushTimer = null;
|
|
23
|
+
this.flushAll();
|
|
24
|
+
}, 50);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static async flushAll() {
|
|
29
|
+
const entries = Array.from(this.buffers.entries());
|
|
30
|
+
this.buffers.clear();
|
|
31
|
+
|
|
32
|
+
for (const [filePath, buffer] of entries) {
|
|
33
|
+
const dataToWrite = buffer.join('');
|
|
34
|
+
|
|
35
|
+
const doWrite = async () => {
|
|
36
|
+
try {
|
|
37
|
+
const prevPromise = this.writePromises.get(filePath);
|
|
38
|
+
if (prevPromise) await prevPromise;
|
|
39
|
+
await fs.promises.appendFile(filePath, dataToWrite, { encoding: 'utf-8', mode: 0o600 });
|
|
40
|
+
} catch {
|
|
41
|
+
// Logging must never create secondary failures or late console noise.
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const writePromise = doWrite();
|
|
46
|
+
this.writePromises.set(filePath, writePromise);
|
|
47
|
+
|
|
48
|
+
writePromise.finally(() => {
|
|
49
|
+
if (this.writePromises.get(filePath) === writePromise) {
|
|
50
|
+
this.writePromises.delete(filePath);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/logging/logger.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import * as fs from 'fs';
|
|
21
21
|
import * as path from 'path';
|
|
22
22
|
import * as os from 'os';
|
|
23
|
+
import { AsyncBatchWriter } from './async-batch-writer.js';
|
|
23
24
|
|
|
24
25
|
// ─── Log Level ──────────────────────────────
|
|
25
26
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
@@ -123,7 +124,7 @@ function writeToFile(line: string): void {
|
|
|
123
124
|
checkDateRotation();
|
|
124
125
|
rotateSizeIfNeeded();
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
+
AsyncBatchWriter.write(currentLogFile, line + '\n');
|
|
127
128
|
} catch { }
|
|
128
129
|
}
|
|
129
130
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import { getLedgerDir } from './mesh-ledger.js';
|
|
5
|
+
import type { MeshTaskStatus, MeshWorkQueueEntry } from './mesh-work-queue.js';
|
|
6
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
7
|
+
import type { Database as DatabaseHandle } from 'better-sqlite3';
|
|
8
|
+
|
|
9
|
+
let DatabaseCtor: typeof BetterSqlite3 | undefined;
|
|
10
|
+
|
|
11
|
+
function loadDatabaseCtor(): typeof BetterSqlite3 {
|
|
12
|
+
if (DatabaseCtor) return DatabaseCtor;
|
|
13
|
+
const runtimeRequire = typeof require === 'function'
|
|
14
|
+
? require
|
|
15
|
+
: createRequire(import.meta.url);
|
|
16
|
+
DatabaseCtor = runtimeRequire('better-sqlite3') as typeof BetterSqlite3;
|
|
17
|
+
return DatabaseCtor;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function safeMeshId(meshId: string): string {
|
|
21
|
+
return meshId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function legacyQueuePath(meshId: string): string {
|
|
25
|
+
return join(getLedgerDir(), `${safeMeshId(meshId)}.queue.json`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class BeadsDB {
|
|
29
|
+
private static instance: BeadsDB | undefined;
|
|
30
|
+
private readonly db: DatabaseHandle;
|
|
31
|
+
private readonly migratedMeshIds = new Set<string>();
|
|
32
|
+
|
|
33
|
+
private constructor(dbPath: string) {
|
|
34
|
+
const dir = dirname(dbPath);
|
|
35
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
this.db = new (loadDatabaseCtor())(dbPath);
|
|
38
|
+
this.db.pragma('journal_mode = WAL');
|
|
39
|
+
this.db.pragma('synchronous = NORMAL');
|
|
40
|
+
this.db.pragma('foreign_keys = ON');
|
|
41
|
+
this.db.pragma('busy_timeout = 5000');
|
|
42
|
+
this.migrate();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static getInstance(): BeadsDB {
|
|
46
|
+
if (!this.instance) {
|
|
47
|
+
this.instance = new BeadsDB(join(getLedgerDir(), 'beads.db'));
|
|
48
|
+
}
|
|
49
|
+
return this.instance;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static resetForTests(): void {
|
|
53
|
+
this.instance?.close();
|
|
54
|
+
this.instance = undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
close(): void {
|
|
58
|
+
this.db.close();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
transaction<T>(fn: () => T): T {
|
|
62
|
+
return this.db.transaction(fn).immediate();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private migrate(): void {
|
|
66
|
+
this.db.exec(`
|
|
67
|
+
CREATE TABLE IF NOT EXISTS mesh_queue (
|
|
68
|
+
id TEXT PRIMARY KEY,
|
|
69
|
+
mesh_id TEXT NOT NULL,
|
|
70
|
+
status TEXT NOT NULL,
|
|
71
|
+
target_node_id TEXT,
|
|
72
|
+
target_session_id TEXT,
|
|
73
|
+
assigned_node_id TEXT,
|
|
74
|
+
assigned_session_id TEXT,
|
|
75
|
+
created_at TEXT NOT NULL,
|
|
76
|
+
updated_at TEXT NOT NULL,
|
|
77
|
+
payload TEXT NOT NULL
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_queue_mesh_status_created
|
|
81
|
+
ON mesh_queue(mesh_id, status, created_at);
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_queue_assignment
|
|
83
|
+
ON mesh_queue(mesh_id, assigned_node_id, assigned_session_id, status);
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private ensureLegacyQueueMigrated(meshId: string): void {
|
|
88
|
+
if (this.migratedMeshIds.has(meshId)) return;
|
|
89
|
+
this.migratedMeshIds.add(meshId);
|
|
90
|
+
|
|
91
|
+
const count = this.db
|
|
92
|
+
.prepare('SELECT COUNT(*) AS count FROM mesh_queue WHERE mesh_id = ?')
|
|
93
|
+
.get(meshId) as { count: number };
|
|
94
|
+
if (count.count > 0) return;
|
|
95
|
+
|
|
96
|
+
const path = legacyQueuePath(meshId);
|
|
97
|
+
if (!existsSync(path)) return;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const entries = JSON.parse(readFileSync(path, 'utf-8')) as MeshWorkQueueEntry[];
|
|
101
|
+
if (!Array.isArray(entries)) return;
|
|
102
|
+
const insert = this.db.prepare(`
|
|
103
|
+
INSERT OR REPLACE INTO mesh_queue (
|
|
104
|
+
id, mesh_id, status, target_node_id, target_session_id,
|
|
105
|
+
assigned_node_id, assigned_session_id, created_at, updated_at, payload
|
|
106
|
+
) VALUES (
|
|
107
|
+
@id, @meshId, @status, @targetNodeId, @targetSessionId,
|
|
108
|
+
@assignedNodeId, @assignedSessionId, @createdAt, @updatedAt, @payload
|
|
109
|
+
)
|
|
110
|
+
`);
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
insert.run(this.toRow(entry));
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getQueueEntries(meshId: string, statuses?: MeshTaskStatus[]): MeshWorkQueueEntry[] {
|
|
120
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
121
|
+
if (statuses?.length) {
|
|
122
|
+
const placeholders = statuses.map(() => '?').join(', ');
|
|
123
|
+
const rows = this.db
|
|
124
|
+
.prepare(`SELECT payload FROM mesh_queue WHERE mesh_id = ? AND status IN (${placeholders}) ORDER BY created_at ASC`)
|
|
125
|
+
.all(meshId, ...statuses) as Array<{ payload: string }>;
|
|
126
|
+
return rows.map(row => JSON.parse(row.payload) as MeshWorkQueueEntry);
|
|
127
|
+
}
|
|
128
|
+
const rows = this.db
|
|
129
|
+
.prepare('SELECT payload FROM mesh_queue WHERE mesh_id = ? ORDER BY created_at ASC')
|
|
130
|
+
.all(meshId) as Array<{ payload: string }>;
|
|
131
|
+
return rows.map(row => JSON.parse(row.payload) as MeshWorkQueueEntry);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
getQueueRevision(meshId: string): string {
|
|
135
|
+
this.ensureLegacyQueueMigrated(meshId);
|
|
136
|
+
const rows = this.db
|
|
137
|
+
.prepare('SELECT id, status, updated_at FROM mesh_queue WHERE mesh_id = ? ORDER BY id ASC')
|
|
138
|
+
.all(meshId) as Array<{ id: string; status: string; updated_at: string }>;
|
|
139
|
+
return rows.map(row => `${row.id}:${row.status}:${row.updated_at}`).join('|');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
replaceQueue(meshId: string, queue: MeshWorkQueueEntry[]): void {
|
|
143
|
+
const deleteStmt = this.db.prepare('DELETE FROM mesh_queue WHERE mesh_id = ?');
|
|
144
|
+
const insert = this.db.prepare(`
|
|
145
|
+
INSERT INTO mesh_queue (
|
|
146
|
+
id, mesh_id, status, target_node_id, target_session_id,
|
|
147
|
+
assigned_node_id, assigned_session_id, created_at, updated_at, payload
|
|
148
|
+
) VALUES (
|
|
149
|
+
@id, @meshId, @status, @targetNodeId, @targetSessionId,
|
|
150
|
+
@assignedNodeId, @assignedSessionId, @createdAt, @updatedAt, @payload
|
|
151
|
+
)
|
|
152
|
+
`);
|
|
153
|
+
deleteStmt.run(meshId);
|
|
154
|
+
for (const entry of queue) insert.run(this.toRow(entry));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
deleteQueue(meshId: string): void {
|
|
158
|
+
this.db.prepare('DELETE FROM mesh_queue WHERE mesh_id = ?').run(meshId);
|
|
159
|
+
this.migratedMeshIds.delete(meshId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private toRow(entry: MeshWorkQueueEntry): Record<string, unknown> {
|
|
163
|
+
return {
|
|
164
|
+
id: entry.id,
|
|
165
|
+
meshId: entry.meshId,
|
|
166
|
+
status: entry.status,
|
|
167
|
+
targetNodeId: entry.targetNodeId ?? null,
|
|
168
|
+
targetSessionId: entry.targetSessionId ?? null,
|
|
169
|
+
assignedNodeId: entry.assignedNodeId ?? null,
|
|
170
|
+
assignedSessionId: entry.assignedSessionId ?? null,
|
|
171
|
+
createdAt: entry.createdAt,
|
|
172
|
+
updatedAt: entry.updatedAt,
|
|
173
|
+
payload: JSON.stringify(entry),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -140,6 +140,7 @@ const TOOLS_SECTION = `## Available Tools
|
|
|
140
140
|
| \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
|
|
141
141
|
| \`mesh_task_history\` | Read the task ledger — dispatches, completions, failures. Use to understand what has been done before deciding next steps |
|
|
142
142
|
| \`mesh_git_status\` | Check git status on a specific node |
|
|
143
|
+
| \`mesh_fast_forward_node\` | Safely dry-run or explicitly execute an obvious clean fast-forward without launching an agent session |
|
|
143
144
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
144
145
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
145
146
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
@@ -164,7 +165,7 @@ const WORKFLOW_SECTION = `## Orchestration Workflow
|
|
|
164
165
|
4. **Monitor** — Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
|
|
165
166
|
5. **Verify** — When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
166
167
|
6. **Checkpoint** — Call \`mesh_checkpoint\` to save the work.
|
|
167
|
-
7. **Converge branches** — Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary and \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
|
|
168
|
+
7. **Converge branches** — Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use \`mesh_fast_forward_node\` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
|
|
168
169
|
8. **Clean up** — Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
169
170
|
9. **Report** — Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
170
171
|
|
|
@@ -201,6 +202,7 @@ function buildRulesSection(coordinatorCliType?: string): string {
|
|
|
201
202
|
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
202
203
|
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.
|
|
203
204
|
- **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
|
|
204
|
-
- **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
|
|
205
|
+
- **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, fast-forward obvious clean behind-only branches with \`mesh_fast_forward_node\`, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
|
|
206
|
+
- **Keep Refinery validation project-configurable.** \`mesh_refine_node\` must execute validation from repo mesh/refine config (for example \`.adhdev/refine.{json,yaml,yml}\`, \`.adhdev/repo-mesh-refine.*\`, or \`repo-mesh.refine.*\`). Heuristics are suggestions/scaffolding only, not the execution path.
|
|
205
207
|
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
206
208
|
}
|