agenr 0.9.0 → 0.9.2
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/CHANGELOG.md +19 -0
- package/dist/cli-main.js +167 -81
- package/package.json +13 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.2 (2026-02-26)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- fix(consolidate): fragmented clustering produced duplicate canonical entries instead of a single winner (#249)
|
|
7
|
+
- Phase 1 now over-fetches neighbors (3x) when type-filtered to preserve same-type neighborhood coverage
|
|
8
|
+
- Added a new Phase 3 post-merge dedup pass to merge near-duplicate canonical entries created in the same run
|
|
9
|
+
- Phase 3 disables idempotency and only processes clusters that include entries created during the current run
|
|
10
|
+
|
|
11
|
+
## 0.9.1 (2026-02-26)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Renamed `agenr daemon` CLI command to `agenr watcher` - "watcher" better describes what it does
|
|
15
|
+
- `agenr daemon` still works as a hidden compatibility command
|
|
16
|
+
- Updated user-facing command output to say "watcher" instead of "daemon"
|
|
17
|
+
|
|
18
|
+
### Internal
|
|
19
|
+
- Renamed `src/commands/daemon.ts` to `src/commands/watcher.ts`
|
|
20
|
+
- Renamed daemon command interfaces and exports from `Daemon*`/`runDaemon*` to `Watcher*`/`runWatcher*`
|
|
21
|
+
|
|
3
22
|
## 0.9.0 (2026-02-25)
|
|
4
23
|
|
|
5
24
|
### Features
|
package/dist/cli-main.js
CHANGED
|
@@ -1040,7 +1040,7 @@ async function runResetCommand(options, deps) {
|
|
|
1040
1040
|
return { exitCode: 0 };
|
|
1041
1041
|
}
|
|
1042
1042
|
resolvedDeps.stdoutLine(
|
|
1043
|
-
"WARNING: If the agenr watcher
|
|
1043
|
+
"WARNING: If the agenr watcher is running, stop it before proceeding. Reset will not abort if the watcher is running."
|
|
1044
1044
|
);
|
|
1045
1045
|
let backupPath;
|
|
1046
1046
|
try {
|
|
@@ -1690,7 +1690,7 @@ async function runDbCheckCommand(options, deps) {
|
|
|
1690
1690
|
}
|
|
1691
1691
|
}
|
|
1692
1692
|
|
|
1693
|
-
// src/commands/
|
|
1693
|
+
// src/commands/watcher.ts
|
|
1694
1694
|
import { execFile, spawn } from "child_process";
|
|
1695
1695
|
import fs9 from "fs/promises";
|
|
1696
1696
|
import os6 from "os";
|
|
@@ -2024,7 +2024,7 @@ function getDefaultPlatformDir(platform, homeDir = os5.homedir()) {
|
|
|
2024
2024
|
throw new Error(`No default directory for platform: ${platform}`);
|
|
2025
2025
|
}
|
|
2026
2026
|
|
|
2027
|
-
// src/commands/
|
|
2027
|
+
// src/commands/watcher.ts
|
|
2028
2028
|
var LAUNCH_LABEL = "com.agenr.watch";
|
|
2029
2029
|
var DEFAULT_INTERVAL_SECONDS = 120;
|
|
2030
2030
|
var DEFAULT_STATUS_LOG_LINES = 20;
|
|
@@ -2131,7 +2131,7 @@ function resolveDeps2(deps) {
|
|
|
2131
2131
|
}
|
|
2132
2132
|
function ensureSupportedPlatform(platform) {
|
|
2133
2133
|
if (platform !== "darwin") {
|
|
2134
|
-
throw new Error("
|
|
2134
|
+
throw new Error("Watcher commands are currently supported on macOS only.");
|
|
2135
2135
|
}
|
|
2136
2136
|
}
|
|
2137
2137
|
async function runLaunchctl(deps, args, strict) {
|
|
@@ -2266,7 +2266,7 @@ function printLines(lines) {
|
|
|
2266
2266
|
`);
|
|
2267
2267
|
}
|
|
2268
2268
|
}
|
|
2269
|
-
async function
|
|
2269
|
+
async function runWatcherInstallCommand(options, deps) {
|
|
2270
2270
|
const resolvedDeps = resolveDeps2(deps);
|
|
2271
2271
|
ensureSupportedPlatform(resolvedDeps.platformFn());
|
|
2272
2272
|
const intervalSeconds = parsePositiveInt(options.interval, DEFAULT_INTERVAL_SECONDS, "--interval");
|
|
@@ -2278,7 +2278,7 @@ async function runDaemonInstallCommand(options, deps) {
|
|
|
2278
2278
|
const { launchAgentsDir, plistPath, logDir, logPath } = getPaths(homeDir);
|
|
2279
2279
|
const plistExists = await fileExists(resolvedDeps.statFn, plistPath);
|
|
2280
2280
|
if (plistExists && options.force !== true) {
|
|
2281
|
-
throw new Error(`
|
|
2281
|
+
throw new Error(`Watcher plist already exists: ${plistPath}. Re-run with --force to overwrite.`);
|
|
2282
2282
|
}
|
|
2283
2283
|
await resolvedDeps.mkdirFn(launchAgentsDir, { recursive: true });
|
|
2284
2284
|
await resolvedDeps.mkdirFn(logDir, { recursive: true });
|
|
@@ -2307,7 +2307,7 @@ async function runDaemonInstallCommand(options, deps) {
|
|
|
2307
2307
|
await resolvedDeps.writeFileFn(plistPath, plist, "utf8");
|
|
2308
2308
|
await runLaunchctl(resolvedDeps, ["bootout", `gui/${uid}/${LAUNCH_LABEL}`], false);
|
|
2309
2309
|
await runLaunchctl(resolvedDeps, ["bootstrap", `gui/${uid}`, plistPath], true);
|
|
2310
|
-
clack2.log.success(`Installed
|
|
2310
|
+
clack2.log.success(`Installed watcher plist: ${plistPath}`);
|
|
2311
2311
|
clack2.log.info(`Log file: ${logPath}`);
|
|
2312
2312
|
return { exitCode: 0 };
|
|
2313
2313
|
}
|
|
@@ -2327,7 +2327,7 @@ async function requireInstalledPlist(deps, homeDir, message) {
|
|
|
2327
2327
|
}
|
|
2328
2328
|
return plistPath;
|
|
2329
2329
|
}
|
|
2330
|
-
async function
|
|
2330
|
+
async function runWatcherStartCommand(_options = {}, deps) {
|
|
2331
2331
|
const resolvedDeps = resolveDeps2(deps);
|
|
2332
2332
|
ensureSupportedPlatform(resolvedDeps.platformFn());
|
|
2333
2333
|
const homeDir = resolvedDeps.homedirFn();
|
|
@@ -2338,21 +2338,21 @@ async function runDaemonStartCommand(_options = {}, deps) {
|
|
|
2338
2338
|
const plistPath = await requireInstalledPlist(
|
|
2339
2339
|
resolvedDeps,
|
|
2340
2340
|
homeDir,
|
|
2341
|
-
"
|
|
2341
|
+
"Watcher not installed. Run `agenr watcher install` first."
|
|
2342
2342
|
);
|
|
2343
2343
|
const state = await getLaunchctlState(resolvedDeps, uid);
|
|
2344
2344
|
if (state.loaded && state.running) {
|
|
2345
|
-
clack2.log.info("
|
|
2345
|
+
clack2.log.info("Watcher is already running.");
|
|
2346
2346
|
return { exitCode: 0 };
|
|
2347
2347
|
}
|
|
2348
2348
|
if (state.loaded && !state.running) {
|
|
2349
2349
|
await runLaunchctl(resolvedDeps, ["bootout", `gui/${uid}/${LAUNCH_LABEL}`], false);
|
|
2350
2350
|
}
|
|
2351
2351
|
await runLaunchctl(resolvedDeps, ["bootstrap", `gui/${uid}`, plistPath], true);
|
|
2352
|
-
clack2.log.success("
|
|
2352
|
+
clack2.log.success("Watcher started.");
|
|
2353
2353
|
return { exitCode: 0 };
|
|
2354
2354
|
}
|
|
2355
|
-
async function
|
|
2355
|
+
async function runWatcherStopCommand(_options = {}, deps) {
|
|
2356
2356
|
const resolvedDeps = resolveDeps2(deps);
|
|
2357
2357
|
ensureSupportedPlatform(resolvedDeps.platformFn());
|
|
2358
2358
|
const homeDir = resolvedDeps.homedirFn();
|
|
@@ -2363,18 +2363,18 @@ async function runDaemonStopCommand(_options = {}, deps) {
|
|
|
2363
2363
|
await requireInstalledPlist(
|
|
2364
2364
|
resolvedDeps,
|
|
2365
2365
|
homeDir,
|
|
2366
|
-
"
|
|
2366
|
+
"Watcher not installed. Run `agenr watcher install` first."
|
|
2367
2367
|
);
|
|
2368
2368
|
const state = await getLaunchctlState(resolvedDeps, uid);
|
|
2369
2369
|
if (!state.loaded) {
|
|
2370
|
-
clack2.log.info("
|
|
2370
|
+
clack2.log.info("Watcher is not loaded.");
|
|
2371
2371
|
return { exitCode: 0 };
|
|
2372
2372
|
}
|
|
2373
2373
|
await runLaunchctl(resolvedDeps, ["bootout", `gui/${uid}/${LAUNCH_LABEL}`], true);
|
|
2374
|
-
clack2.log.success("
|
|
2374
|
+
clack2.log.success("Watcher stopped.");
|
|
2375
2375
|
return { exitCode: 0 };
|
|
2376
2376
|
}
|
|
2377
|
-
async function
|
|
2377
|
+
async function runWatcherRestartCommand(_options = {}, deps) {
|
|
2378
2378
|
const resolvedDeps = resolveDeps2(deps);
|
|
2379
2379
|
ensureSupportedPlatform(resolvedDeps.platformFn());
|
|
2380
2380
|
const homeDir = resolvedDeps.homedirFn();
|
|
@@ -2382,7 +2382,7 @@ async function runDaemonRestartCommand(_options = {}, deps) {
|
|
|
2382
2382
|
if (uid < 0) {
|
|
2383
2383
|
throw new Error("Unable to resolve current user ID for launchctl.");
|
|
2384
2384
|
}
|
|
2385
|
-
const plistPath = await requireInstalledPlist(resolvedDeps, homeDir, "
|
|
2385
|
+
const plistPath = await requireInstalledPlist(resolvedDeps, homeDir, "Watcher not installed.");
|
|
2386
2386
|
await runLaunchctl(resolvedDeps, ["bootout", `gui/${uid}/${LAUNCH_LABEL}`], false);
|
|
2387
2387
|
const maxWaitMs = 1e4;
|
|
2388
2388
|
const pollMs = 500;
|
|
@@ -2394,13 +2394,13 @@ async function runDaemonRestartCommand(_options = {}, deps) {
|
|
|
2394
2394
|
}
|
|
2395
2395
|
const finalState = await getLaunchctlState(resolvedDeps, uid);
|
|
2396
2396
|
if (finalState.loaded) {
|
|
2397
|
-
clack2.log.warn("
|
|
2397
|
+
clack2.log.warn("Watcher did not unload within 10s - attempting bootstrap anyway.");
|
|
2398
2398
|
}
|
|
2399
2399
|
await runLaunchctl(resolvedDeps, ["bootstrap", `gui/${uid}`, plistPath], true);
|
|
2400
|
-
clack2.log.success("
|
|
2400
|
+
clack2.log.success("Watcher restarted.");
|
|
2401
2401
|
return { exitCode: 0 };
|
|
2402
2402
|
}
|
|
2403
|
-
async function
|
|
2403
|
+
async function runWatcherUninstallCommand(options, deps) {
|
|
2404
2404
|
const resolvedDeps = resolveDeps2(deps);
|
|
2405
2405
|
ensureSupportedPlatform(resolvedDeps.platformFn());
|
|
2406
2406
|
const homeDir = resolvedDeps.homedirFn();
|
|
@@ -2411,11 +2411,11 @@ async function runDaemonUninstallCommand(options, deps) {
|
|
|
2411
2411
|
const { plistPath } = getPaths(homeDir);
|
|
2412
2412
|
const exists = await fileExists(resolvedDeps.statFn, plistPath);
|
|
2413
2413
|
if (!exists) {
|
|
2414
|
-
clack2.log.info(`
|
|
2414
|
+
clack2.log.info(`Watcher plist not found: ${plistPath}`);
|
|
2415
2415
|
return { exitCode: 0 };
|
|
2416
2416
|
}
|
|
2417
2417
|
if (options.yes !== true) {
|
|
2418
|
-
const confirmed = await resolvedDeps.confirmFn("Remove agenr
|
|
2418
|
+
const confirmed = await resolvedDeps.confirmFn("Remove agenr watcher?");
|
|
2419
2419
|
if (!confirmed) {
|
|
2420
2420
|
clack2.log.warn("Uninstall cancelled.");
|
|
2421
2421
|
return { exitCode: 1 };
|
|
@@ -2423,10 +2423,10 @@ async function runDaemonUninstallCommand(options, deps) {
|
|
|
2423
2423
|
}
|
|
2424
2424
|
await runLaunchctl(resolvedDeps, ["bootout", `gui/${uid}/${LAUNCH_LABEL}`], false);
|
|
2425
2425
|
await resolvedDeps.rmFn(plistPath, { force: true });
|
|
2426
|
-
clack2.log.success("
|
|
2426
|
+
clack2.log.success("Watcher uninstalled.");
|
|
2427
2427
|
return { exitCode: 0 };
|
|
2428
2428
|
}
|
|
2429
|
-
async function
|
|
2429
|
+
async function runWatcherStatusCommand(options, deps) {
|
|
2430
2430
|
const resolvedDeps = resolveDeps2(deps);
|
|
2431
2431
|
ensureSupportedPlatform(resolvedDeps.platformFn());
|
|
2432
2432
|
const homeDir = resolvedDeps.homedirFn();
|
|
@@ -2454,8 +2454,8 @@ async function runDaemonStatusCommand(options, deps) {
|
|
|
2454
2454
|
const lineCount = parsePositiveInt(options.lines, DEFAULT_STATUS_LOG_LINES, "--lines");
|
|
2455
2455
|
const logTail = await readLastLines(resolvedDeps.readFileFn, logPath, lineCount);
|
|
2456
2456
|
const nowMs = resolvedDeps.nowFn();
|
|
2457
|
-
const
|
|
2458
|
-
"--
|
|
2457
|
+
const serviceLines = [
|
|
2458
|
+
"-- Service --",
|
|
2459
2459
|
`Loaded: ${loaded ? "yes" : "no"}`,
|
|
2460
2460
|
`Running: ${running ? "yes" : "no"}`,
|
|
2461
2461
|
`Current file: ${currentFile ?? "(none)"}`,
|
|
@@ -2471,7 +2471,7 @@ async function runDaemonStatusCommand(options, deps) {
|
|
|
2471
2471
|
`Sessions watched: ${health.sessionsWatched}`,
|
|
2472
2472
|
`Entries stored: ${health.entriesStored}`
|
|
2473
2473
|
] : ["-- Watcher --", "Heartbeat: no data"];
|
|
2474
|
-
resolvedDeps.noteFn([...
|
|
2474
|
+
resolvedDeps.noteFn([...serviceLines, "", ...watcherLines].join("\n"), "Status");
|
|
2475
2475
|
if (logTail.length > 0) {
|
|
2476
2476
|
clack2.log.info(`Last ${logTail.length} log lines:`);
|
|
2477
2477
|
printLines(logTail);
|
|
@@ -2500,14 +2500,14 @@ async function followLog(deps, logPath, lines) {
|
|
|
2500
2500
|
});
|
|
2501
2501
|
});
|
|
2502
2502
|
}
|
|
2503
|
-
async function
|
|
2503
|
+
async function runWatcherLogsCommand(options, deps) {
|
|
2504
2504
|
const resolvedDeps = resolveDeps2(deps);
|
|
2505
2505
|
ensureSupportedPlatform(resolvedDeps.platformFn());
|
|
2506
2506
|
const homeDir = resolvedDeps.homedirFn();
|
|
2507
2507
|
const { logPath } = getPaths(homeDir);
|
|
2508
2508
|
const logExists = await fileExists(resolvedDeps.statFn, logPath);
|
|
2509
2509
|
if (!logExists) {
|
|
2510
|
-
throw new Error(`
|
|
2510
|
+
throw new Error(`Watcher log file not found: ${logPath}`);
|
|
2511
2511
|
}
|
|
2512
2512
|
const lineCount = parsePositiveInt(options.lines, DEFAULT_LOG_LINES, "--lines");
|
|
2513
2513
|
const follow = options.follow === true || options.lines === void 0;
|
|
@@ -6400,7 +6400,8 @@ async function buildClusters(db, options = {}) {
|
|
|
6400
6400
|
unionFind.add(entry.id);
|
|
6401
6401
|
}
|
|
6402
6402
|
for (const entry of candidates) {
|
|
6403
|
-
const
|
|
6403
|
+
const fetchLimit = typeFilter ? neighborLimit * 3 : neighborLimit;
|
|
6404
|
+
const neighbors = await findSimilar(db, entry.embedding, fetchLimit);
|
|
6404
6405
|
for (const neighbor of neighbors) {
|
|
6405
6406
|
const candidate = entryById.get(neighbor.entry.id);
|
|
6406
6407
|
if (!candidate || candidate.id === entry.id) {
|
|
@@ -7006,17 +7007,19 @@ function checkpointToProcessedMaps(checkpoint) {
|
|
|
7006
7007
|
}
|
|
7007
7008
|
return {
|
|
7008
7009
|
phase1,
|
|
7009
|
-
phase2: new Set(checkpoint.processed.phase2 ?? [])
|
|
7010
|
+
phase2: new Set(checkpoint.processed.phase2 ?? []),
|
|
7011
|
+
phase3: new Set(checkpoint.processed.phase3 ?? [])
|
|
7010
7012
|
};
|
|
7011
7013
|
}
|
|
7012
|
-
function processedMapsToCheckpoint(phase1, phase2) {
|
|
7014
|
+
function processedMapsToCheckpoint(phase1, phase2, phase3) {
|
|
7013
7015
|
const serializedPhase1 = {};
|
|
7014
7016
|
for (const [type, fingerprints] of phase1.entries()) {
|
|
7015
7017
|
serializedPhase1[type] = [...fingerprints];
|
|
7016
7018
|
}
|
|
7017
7019
|
return {
|
|
7018
7020
|
phase1: serializedPhase1,
|
|
7019
|
-
phase2: [...phase2]
|
|
7021
|
+
phase2: [...phase2],
|
|
7022
|
+
phase3: [...phase3]
|
|
7020
7023
|
};
|
|
7021
7024
|
}
|
|
7022
7025
|
function createEmptyPlan(types) {
|
|
@@ -7044,8 +7047,10 @@ function createDefaultCheckpoint(dbPathSignature, optionsSignature, types) {
|
|
|
7044
7047
|
optionsSignature,
|
|
7045
7048
|
processed: {
|
|
7046
7049
|
phase1: {},
|
|
7047
|
-
phase2: []
|
|
7050
|
+
phase2: [],
|
|
7051
|
+
phase3: []
|
|
7048
7052
|
},
|
|
7053
|
+
createdEntryIds: [],
|
|
7049
7054
|
plan: createEmptyPlan(types)
|
|
7050
7055
|
};
|
|
7051
7056
|
}
|
|
@@ -7119,12 +7124,15 @@ async function processPhaseClusters(params, deps) {
|
|
|
7119
7124
|
stats.clustersMerged += 1;
|
|
7120
7125
|
stats.entriesConsolidatedFrom += item.cluster.entries.length;
|
|
7121
7126
|
stats.canonicalEntriesCreated += 1;
|
|
7127
|
+
if (outcome.mergedEntryId) {
|
|
7128
|
+
params.context.createdEntryIds.add(outcome.mergedEntryId);
|
|
7129
|
+
}
|
|
7122
7130
|
}
|
|
7123
7131
|
params.checkpoint.phase = params.phase;
|
|
7124
7132
|
params.checkpoint.projectIndex = params.projectIndex;
|
|
7125
7133
|
params.checkpoint.typeIndex = params.typeIndex;
|
|
7126
7134
|
params.checkpoint.clusterIndex = item.index + 1;
|
|
7127
|
-
params.checkpoint.processed = processedMapsToCheckpoint(params.context.processedPhase1, params.context.processedPhase2);
|
|
7135
|
+
params.checkpoint.processed = processedMapsToCheckpoint(params.context.processedPhase1, params.context.processedPhase2, params.context.processedPhase3);
|
|
7128
7136
|
await saveCheckpoint(params.checkpoint);
|
|
7129
7137
|
if (isShutdownRequested()) {
|
|
7130
7138
|
params.context.batchReached = true;
|
|
@@ -7229,6 +7237,8 @@ async function runConsolidationOrchestrator(db, dbPath, llmClient, embeddingApiK
|
|
|
7229
7237
|
checkpoint,
|
|
7230
7238
|
processedPhase1: processedMaps.phase1,
|
|
7231
7239
|
processedPhase2: processedMaps.phase2,
|
|
7240
|
+
processedPhase3: processedMaps.phase3,
|
|
7241
|
+
createdEntryIds: new Set(checkpoint.createdEntryIds ?? []),
|
|
7232
7242
|
minCluster,
|
|
7233
7243
|
phase1Threshold,
|
|
7234
7244
|
phase2Threshold,
|
|
@@ -7493,23 +7503,85 @@ async function runConsolidationOrchestrator(db, dbPath, llmClient, embeddingApiK
|
|
|
7493
7503
|
}
|
|
7494
7504
|
}
|
|
7495
7505
|
}
|
|
7506
|
+
if (!context.batchReached && context.batchLimit && context.processedClustersInRun >= context.batchLimit) {
|
|
7507
|
+
context.batchReached = true;
|
|
7508
|
+
}
|
|
7509
|
+
if (!context.batchReached && context.createdEntryIds.size >= 2) {
|
|
7510
|
+
onLog(`Phase 3: Post-merge dedup (${context.createdEntryIds.size} new entries)...`);
|
|
7511
|
+
for (let projectIndex = 0; projectIndex < phase1PlanByProject.length; projectIndex += 1) {
|
|
7512
|
+
if (isShutdownRequested()) {
|
|
7513
|
+
context.batchReached = true;
|
|
7514
|
+
break;
|
|
7515
|
+
}
|
|
7516
|
+
if (context.batchReached) {
|
|
7517
|
+
break;
|
|
7518
|
+
}
|
|
7519
|
+
const projectPlan = phase1PlanByProject[projectIndex];
|
|
7520
|
+
const project = projectPlan.project;
|
|
7521
|
+
const projectLabel = project ?? "(untagged)";
|
|
7522
|
+
const dedupClusters = await resolvedDeps.buildClustersFn(db, {
|
|
7523
|
+
simThreshold: phase1Threshold,
|
|
7524
|
+
minCluster: 2,
|
|
7525
|
+
maxClusterSize: phase1MaxClusterSize,
|
|
7526
|
+
platform,
|
|
7527
|
+
project,
|
|
7528
|
+
idempotencyDays: 0,
|
|
7529
|
+
verbose: options.verbose,
|
|
7530
|
+
onLog: options.verbose ? onLog : void 0
|
|
7531
|
+
});
|
|
7532
|
+
const relevantClusters = dedupClusters.filter(
|
|
7533
|
+
(cluster) => cluster.entries.some((entry) => context.createdEntryIds.has(entry.id))
|
|
7534
|
+
);
|
|
7535
|
+
if (relevantClusters.length === 0) {
|
|
7536
|
+
continue;
|
|
7537
|
+
}
|
|
7538
|
+
onLog(`Phase 3: Found ${relevantClusters.length} dedup clusters for project=${projectLabel}`);
|
|
7539
|
+
const phase3Stats = await processPhaseClusters(
|
|
7540
|
+
{
|
|
7541
|
+
db,
|
|
7542
|
+
clusters: relevantClusters,
|
|
7543
|
+
phase: 3,
|
|
7544
|
+
projectIndex,
|
|
7545
|
+
type: "post-merge-dedup",
|
|
7546
|
+
typeIndex: 0,
|
|
7547
|
+
llmClient,
|
|
7548
|
+
embeddingApiKey,
|
|
7549
|
+
options,
|
|
7550
|
+
checkpoint: context.checkpoint,
|
|
7551
|
+
processedSet: context.processedPhase3,
|
|
7552
|
+
context
|
|
7553
|
+
},
|
|
7554
|
+
resolvedDeps
|
|
7555
|
+
);
|
|
7556
|
+
if (context.report.phase3) {
|
|
7557
|
+
updateAggregateStats(context.report.phase3, phase3Stats);
|
|
7558
|
+
} else {
|
|
7559
|
+
context.report.phase3 = phase3Stats;
|
|
7560
|
+
}
|
|
7561
|
+
if (context.batchReached) {
|
|
7562
|
+
break;
|
|
7563
|
+
}
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7496
7566
|
context.report.phase1.types = phase1Types.map((type) => phase1StatsByType.get(type)).filter((item) => Boolean(item));
|
|
7497
7567
|
}
|
|
7498
7568
|
await runFinalization(db, dryRun, onWarn, resolvedDeps);
|
|
7499
7569
|
context.report.entriesAfter = (await Promise.all(projectGroups.map((project) => resolvedDeps.countActiveEntriesFn(db, platform, project)))).reduce((sum, count) => sum + count, 0);
|
|
7500
7570
|
context.report.progress.partial = context.batchReached;
|
|
7501
|
-
|
|
7571
|
+
const estimatedPhasesProcessed = context.report.phase1.totals.clustersProcessed + (context.report.phase2?.clustersProcessed ?? 0);
|
|
7572
|
+
context.report.progress.processedClusters = estimatedPhasesProcessed + (context.report.phase3?.clustersProcessed ?? 0);
|
|
7502
7573
|
context.report.progress.remainingClusters = Math.max(
|
|
7503
|
-
context.report.estimate.totalClusters - context.report.phase1.totals.skippedByResume - (context.report.phase2?.skippedByResume ?? 0) -
|
|
7574
|
+
context.report.estimate.totalClusters - context.report.phase1.totals.skippedByResume - (context.report.phase2?.skippedByResume ?? 0) - estimatedPhasesProcessed,
|
|
7504
7575
|
0
|
|
7505
7576
|
);
|
|
7506
|
-
context.report.summary.totalLlmCalls = context.report.phase1.totals.llmCalls + (context.report.phase2?.llmCalls ?? 0);
|
|
7507
|
-
context.report.summary.totalFlagged = context.report.phase1.totals.mergesFlagged + (context.report.phase2?.mergesFlagged ?? 0);
|
|
7508
|
-
context.report.summary.totalCanonicalEntriesCreated = context.report.phase1.totals.canonicalEntriesCreated + (context.report.phase2?.canonicalEntriesCreated ?? 0);
|
|
7509
|
-
context.report.summary.totalEntriesConsolidatedFrom = context.report.phase1.totals.entriesConsolidatedFrom + (context.report.phase2?.entriesConsolidatedFrom ?? 0);
|
|
7577
|
+
context.report.summary.totalLlmCalls = context.report.phase1.totals.llmCalls + (context.report.phase2?.llmCalls ?? 0) + (context.report.phase3?.llmCalls ?? 0);
|
|
7578
|
+
context.report.summary.totalFlagged = context.report.phase1.totals.mergesFlagged + (context.report.phase2?.mergesFlagged ?? 0) + (context.report.phase3?.mergesFlagged ?? 0);
|
|
7579
|
+
context.report.summary.totalCanonicalEntriesCreated = context.report.phase1.totals.canonicalEntriesCreated + (context.report.phase2?.canonicalEntriesCreated ?? 0) + (context.report.phase3?.canonicalEntriesCreated ?? 0);
|
|
7580
|
+
context.report.summary.totalEntriesConsolidatedFrom = context.report.phase1.totals.entriesConsolidatedFrom + (context.report.phase2?.entriesConsolidatedFrom ?? 0) + (context.report.phase3?.entriesConsolidatedFrom ?? 0);
|
|
7510
7581
|
if (context.batchReached) {
|
|
7511
|
-
context.checkpoint.phase = context.report.phase2 ? 2 : 1;
|
|
7512
|
-
context.checkpoint.processed = processedMapsToCheckpoint(context.processedPhase1, context.processedPhase2);
|
|
7582
|
+
context.checkpoint.phase = context.report.phase3 ? 3 : context.report.phase2 ? 2 : 1;
|
|
7583
|
+
context.checkpoint.processed = processedMapsToCheckpoint(context.processedPhase1, context.processedPhase2, context.processedPhase3);
|
|
7584
|
+
context.checkpoint.createdEntryIds = [...context.createdEntryIds];
|
|
7513
7585
|
await saveCheckpoint(context.checkpoint);
|
|
7514
7586
|
} else {
|
|
7515
7587
|
await clearCheckpoint();
|
|
@@ -7777,6 +7849,15 @@ function renderTextReport(stats, dryRun) {
|
|
|
7777
7849
|
`| +- LLM calls: ${formatNumber(stats.phase2.llmCalls)}`
|
|
7778
7850
|
);
|
|
7779
7851
|
}
|
|
7852
|
+
if (stats.phase3) {
|
|
7853
|
+
lines.push(
|
|
7854
|
+
"|",
|
|
7855
|
+
"| Phase 3: Post-Merge Dedup",
|
|
7856
|
+
`| +- Clusters processed: ${formatNumber(stats.phase3.clustersProcessed)} / ${formatNumber(stats.phase3.clustersFound)}`,
|
|
7857
|
+
`| +- Clusters merged: ${formatNumber(stats.phase3.clustersMerged)}`,
|
|
7858
|
+
`| +- LLM calls: ${formatNumber(stats.phase3.llmCalls)}`
|
|
7859
|
+
);
|
|
7860
|
+
}
|
|
7780
7861
|
lines.push(
|
|
7781
7862
|
"|",
|
|
7782
7863
|
"| Summary",
|
|
@@ -17044,8 +17125,8 @@ var initWizardRuntime = {
|
|
|
17044
17125
|
scanSessionFiles,
|
|
17045
17126
|
runIngestCommand,
|
|
17046
17127
|
runConsolidateCommand,
|
|
17047
|
-
|
|
17048
|
-
|
|
17128
|
+
runWatcherInstallCommand,
|
|
17129
|
+
runWatcherStopCommand,
|
|
17049
17130
|
runDbResetCommand
|
|
17050
17131
|
};
|
|
17051
17132
|
async function runInitWizard(options) {
|
|
@@ -17496,7 +17577,7 @@ but requires clearing your existing knowledge database first.`
|
|
|
17496
17577
|
if (process.platform === "darwin") {
|
|
17497
17578
|
spinner4.start("Stopping watcher...");
|
|
17498
17579
|
try {
|
|
17499
|
-
await initWizardRuntime.
|
|
17580
|
+
await initWizardRuntime.runWatcherStopCommand({});
|
|
17500
17581
|
} catch {
|
|
17501
17582
|
}
|
|
17502
17583
|
spinner4.stop("Watcher stopped");
|
|
@@ -17633,27 +17714,27 @@ Estimated cost with ${model}:
|
|
|
17633
17714
|
}
|
|
17634
17715
|
if (setupWatcher) {
|
|
17635
17716
|
const spinner4 = clack7.spinner();
|
|
17636
|
-
spinner4.start("Installing watcher
|
|
17717
|
+
spinner4.start("Installing watcher...");
|
|
17637
17718
|
try {
|
|
17638
|
-
const
|
|
17719
|
+
const watcherResult = await initWizardRuntime.runWatcherInstallCommand({
|
|
17639
17720
|
force: true,
|
|
17640
17721
|
interval: 120,
|
|
17641
17722
|
dir: selectedPlatform.sessionsDir,
|
|
17642
17723
|
platform: selectedPlatform.id
|
|
17643
17724
|
});
|
|
17644
|
-
if (
|
|
17645
|
-
spinner4.stop("Watcher
|
|
17725
|
+
if (watcherResult.exitCode === 0) {
|
|
17726
|
+
spinner4.stop("Watcher installed and running (120s interval)");
|
|
17646
17727
|
watcherStatus = "Running (120s interval)";
|
|
17647
17728
|
} else {
|
|
17648
17729
|
spinner4.stop(
|
|
17649
|
-
`
|
|
17730
|
+
`Watcher install failed. Run manually:
|
|
17650
17731
|
agenr watch --dir ${selectedPlatform.sessionsDir} --platform ${selectedPlatform.id}`
|
|
17651
17732
|
);
|
|
17652
17733
|
watcherStatus = "Failed";
|
|
17653
17734
|
}
|
|
17654
17735
|
} catch (error) {
|
|
17655
17736
|
const message = error instanceof Error ? error.message : String(error);
|
|
17656
|
-
spinner4.stop(`
|
|
17737
|
+
spinner4.stop(`Watcher install failed: ${message}`);
|
|
17657
17738
|
watcherStatus = "Failed";
|
|
17658
17739
|
}
|
|
17659
17740
|
} else {
|
|
@@ -21448,35 +21529,40 @@ function createProgram() {
|
|
|
21448
21529
|
program.command("mcp").description("Start MCP server for cross-tool AI memory").option("--db <path>", "Database path override").option("--verbose", "Log requests to stderr", false).action(async (opts) => {
|
|
21449
21530
|
await runMcpCommand(opts);
|
|
21450
21531
|
});
|
|
21451
|
-
const
|
|
21452
|
-
|
|
21453
|
-
|
|
21454
|
-
|
|
21455
|
-
|
|
21456
|
-
|
|
21457
|
-
|
|
21458
|
-
|
|
21459
|
-
|
|
21460
|
-
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
|
|
21467
|
-
|
|
21468
|
-
|
|
21469
|
-
|
|
21470
|
-
|
|
21471
|
-
|
|
21472
|
-
|
|
21473
|
-
|
|
21474
|
-
|
|
21475
|
-
|
|
21476
|
-
|
|
21477
|
-
|
|
21478
|
-
|
|
21479
|
-
|
|
21532
|
+
const registerWatcherSubcommands = (command) => {
|
|
21533
|
+
command.command("install").description("Install and start the watcher (macOS launchd)").option("--force", "Overwrite existing launchd plist", false).option("--interval <seconds>", "Watch interval for watcher mode", parseIntOption, 120).option("--dir <path>", "Sessions directory to watch (overrides auto-detection)").option("--platform <name>", "Platform name (openclaw, claude-code, codex, plaud)").option("--node-path <path>", "Node binary path override for launchd").option("--context <path>", "Regenerate context file after each cycle").action(async (opts) => {
|
|
21534
|
+
const result = await runWatcherInstallCommand(opts);
|
|
21535
|
+
process.exitCode = result.exitCode;
|
|
21536
|
+
});
|
|
21537
|
+
command.command("start").description("Start the watcher if installed").action(async () => {
|
|
21538
|
+
const result = await runWatcherStartCommand({});
|
|
21539
|
+
process.exitCode = result.exitCode;
|
|
21540
|
+
});
|
|
21541
|
+
command.command("stop").description("Stop the watcher without uninstalling").action(async () => {
|
|
21542
|
+
const result = await runWatcherStopCommand({});
|
|
21543
|
+
process.exitCode = result.exitCode;
|
|
21544
|
+
});
|
|
21545
|
+
command.command("restart").description("Restart the watcher").action(async () => {
|
|
21546
|
+
const result = await runWatcherRestartCommand({});
|
|
21547
|
+
process.exitCode = result.exitCode;
|
|
21548
|
+
});
|
|
21549
|
+
command.command("uninstall").description("Stop and remove the watcher").option("--yes", "Skip confirmation prompt", false).action(async (opts) => {
|
|
21550
|
+
const result = await runWatcherUninstallCommand(opts);
|
|
21551
|
+
process.exitCode = result.exitCode;
|
|
21552
|
+
});
|
|
21553
|
+
command.command("status").description("Show watcher status and recent logs").option("--lines <n>", "Number of log lines to include", parseIntOption, 20).action(async (opts) => {
|
|
21554
|
+
const result = await runWatcherStatusCommand(opts);
|
|
21555
|
+
process.exitCode = result.exitCode;
|
|
21556
|
+
});
|
|
21557
|
+
command.command("logs").description("Show or follow watcher logs").option("--lines <n>", "Number of log lines", parseIntOption, 100).option("--follow", "Follow logs continuously", false).action(async (opts) => {
|
|
21558
|
+
const result = await runWatcherLogsCommand(opts);
|
|
21559
|
+
process.exitCode = result.exitCode;
|
|
21560
|
+
});
|
|
21561
|
+
};
|
|
21562
|
+
const watcherCommand = program.command("watcher").description("Manage the agenr watcher");
|
|
21563
|
+
registerWatcherSubcommands(watcherCommand);
|
|
21564
|
+
const daemonAliasCommand = program.command("daemon", { hidden: true }).description("Alias for watcher (deprecated)");
|
|
21565
|
+
registerWatcherSubcommands(daemonAliasCommand);
|
|
21480
21566
|
const dbCommand = program.command("db").description("Manage the local knowledge database");
|
|
21481
21567
|
dbCommand.command("stats").description("Show database statistics").option("--db <path>", "Database path override").option("--platform <name>", "Filter stats by platform").option("--project <name>", "Filter by project (repeatable)", (val, prev) => [...prev, val], []).option("--exclude-project <name>", "Exclude entries from project (repeatable)", (val, prev) => [...prev, val], []).action(async (opts) => {
|
|
21482
21568
|
await runDbStatsCommand({ db: opts.db, platform: opts.platform, project: opts.project, excludeProject: opts.excludeProject });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenr",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"openclaw": {
|
|
5
5
|
"extensions": [
|
|
6
6
|
"dist/openclaw-plugin/index.js"
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
"bin": {
|
|
12
12
|
"agenr": "dist/cli.js"
|
|
13
13
|
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
|
|
16
|
+
"dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
14
21
|
"dependencies": {
|
|
15
22
|
"@clack/prompts": "^1.0.1",
|
|
16
23
|
"@libsql/client": "^0.17.0",
|
|
@@ -54,11 +61,9 @@
|
|
|
54
61
|
"README.md"
|
|
55
62
|
],
|
|
56
63
|
"author": "agenr-ai",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"test:watch": "vitest",
|
|
62
|
-
"typecheck": "tsc --noEmit"
|
|
64
|
+
"pnpm": {
|
|
65
|
+
"overrides": {
|
|
66
|
+
"fast-xml-parser": "^5.3.6"
|
|
67
|
+
}
|
|
63
68
|
}
|
|
64
|
-
}
|
|
69
|
+
}
|