@remnic/core 9.3.691 → 9.3.693
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/access-boundary.d.ts +6 -5
- package/dist/access-boundary.js +12 -12
- package/dist/access-cli.js +71 -18
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -4
- package/dist/access-http.js +16 -16
- package/dist/access-mcp.d.ts +7 -4
- package/dist/access-mcp.js +15 -15
- package/dist/access-operations.d.ts +12 -6
- package/dist/access-operations.js +16 -14
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-CKnFdxH-.d.ts → access-service-DxCpgyVW.d.ts} +96 -2
- package/dist/access-service.d.ts +5 -4
- package/dist/access-service.js +11 -11
- package/dist/access-surface-catalog.d.ts +5 -4
- package/dist/access-surface-catalog.js +2 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/bootstrap.d.ts +4 -3
- package/dist/{chunk-QNG35H27.js → chunk-4IQNAOP3.js} +5 -141
- package/dist/chunk-4IQNAOP3.js.map +1 -0
- package/dist/{chunk-VH2DHBHG.js → chunk-5HGPDRQZ.js} +2 -2
- package/dist/chunk-5HGPDRQZ.js.map +1 -0
- package/dist/chunk-6C2BMUZA.js +444 -0
- package/dist/chunk-6C2BMUZA.js.map +1 -0
- package/dist/{chunk-UUMYB3FR.js → chunk-6ZNIUGXB.js} +10 -10
- package/dist/{chunk-IPJD7WJH.js → chunk-E6BOD3UJ.js} +2 -2
- package/dist/{chunk-TJ5IIQIR.js → chunk-F7BVJ3IE.js} +40 -369
- package/dist/chunk-F7BVJ3IE.js.map +1 -0
- package/dist/{chunk-D3DNGQNQ.js → chunk-FM7KLRKN.js} +2 -2
- package/dist/{chunk-7MOTEVAA.js → chunk-HRUULBBV.js} +3 -1
- package/dist/chunk-HRUULBBV.js.map +1 -0
- package/dist/chunk-HXNMDPHN.js +149 -0
- package/dist/chunk-HXNMDPHN.js.map +1 -0
- package/dist/{chunk-7UVDIWLL.js → chunk-JNKH2XWQ.js} +36 -6
- package/dist/chunk-JNKH2XWQ.js.map +1 -0
- package/dist/{chunk-IPLYGWQF.js → chunk-KQAFEZQX.js} +5 -5
- package/dist/{chunk-OOO7ZKOD.js → chunk-KYH53EFD.js} +3 -3
- package/dist/{chunk-LQ6JI4VH.js → chunk-LD53WPMU.js} +4 -4
- package/dist/{chunk-KO6OL4I7.js → chunk-MQTPUPLP.js} +297 -17
- package/dist/chunk-MQTPUPLP.js.map +1 -0
- package/dist/{chunk-TSGREOYY.js → chunk-RHWFMBO5.js} +33 -6
- package/dist/chunk-RHWFMBO5.js.map +1 -0
- package/dist/{chunk-5CEJH5ZN.js → chunk-TAPSNIMA.js} +4 -4
- package/dist/{chunk-K7I24ZO2.js → chunk-THKNO6OX.js} +22 -21
- package/dist/{chunk-K7I24ZO2.js.map → chunk-THKNO6OX.js.map} +1 -1
- package/dist/{chunk-QLRYXOAD.js → chunk-UDJLF3BO.js} +2 -2
- package/dist/{chunk-NCRDCTEJ.js → chunk-WYB44HHV.js} +21 -7
- package/dist/chunk-WYB44HHV.js.map +1 -0
- package/dist/{chunk-WRFKZEO6.js → chunk-YQGHW5ML.js} +4 -4
- package/dist/{cli-CrSW_KRq.d.ts → cli-DoQsv4ut.d.ts} +2 -2
- package/dist/cli.d.ts +6 -5
- package/dist/cli.js +26 -25
- package/dist/contradiction/index.js +4 -4
- package/dist/explicit-capture.d.ts +4 -3
- package/dist/index.d.ts +6 -5
- package/dist/index.js +33 -31
- package/dist/index.js.map +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +5 -4
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/search.js +7 -7
- package/dist/operator-toolkit.js +11 -10
- package/dist/orchestration/maintenance.d.ts +112 -0
- package/dist/orchestration/maintenance.js +39 -0
- package/dist/orchestration/maintenance.js.map +1 -0
- package/dist/{orchestrator-DvswGK76.d.ts → orchestrator-CNJCYjZ1.d.ts} +9 -33
- package/dist/orchestrator.d.ts +2 -1
- package/dist/orchestrator.js +13 -11
- package/dist/schemas.d.ts +28 -28
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +11 -11
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +1 -1
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +12 -2
- package/src/access-boundary.ts +2 -1
- package/src/access-cli.ts +65 -2
- package/src/access-http.ts +20 -0
- package/src/access-mcp.ts +29 -0
- package/src/access-operations.ts +46 -0
- package/src/access-service.ts +55 -0
- package/src/access-surface-catalog.test.ts +1 -1
- package/src/access-surface-catalog.ts +2 -0
- package/src/cli.ts +1 -0
- package/src/coding/coding-namespace.ts +1 -1
- package/src/coding/session-delta-surfaces.test.ts +383 -0
- package/src/coding/session-delta-surfaces.ts +325 -0
- package/src/coding/session-delta.test.ts +411 -0
- package/src/coding/session-delta.ts +442 -0
- package/src/orchestration/maintenance.test.ts +738 -0
- package/src/orchestration/maintenance.ts +557 -0
- package/src/orchestrator-flush.test.ts +0 -599
- package/src/orchestrator.ts +33 -419
- package/dist/chunk-7MOTEVAA.js.map +0 -1
- package/dist/chunk-7UVDIWLL.js.map +0 -1
- package/dist/chunk-KO6OL4I7.js.map +0 -1
- package/dist/chunk-NCRDCTEJ.js.map +0 -1
- package/dist/chunk-QNG35H27.js.map +0 -1
- package/dist/chunk-TJ5IIQIR.js.map +0 -1
- package/dist/chunk-TSGREOYY.js.map +0 -1
- package/dist/chunk-VH2DHBHG.js.map +0 -1
- /package/dist/{chunk-UUMYB3FR.js.map → chunk-6ZNIUGXB.js.map} +0 -0
- /package/dist/{chunk-IPJD7WJH.js.map → chunk-E6BOD3UJ.js.map} +0 -0
- /package/dist/{chunk-D3DNGQNQ.js.map → chunk-FM7KLRKN.js.map} +0 -0
- /package/dist/{chunk-IPLYGWQF.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-OOO7ZKOD.js.map → chunk-KYH53EFD.js.map} +0 -0
- /package/dist/{chunk-LQ6JI4VH.js.map → chunk-LD53WPMU.js.map} +0 -0
- /package/dist/{chunk-5CEJH5ZN.js.map → chunk-TAPSNIMA.js.map} +0 -0
- /package/dist/{chunk-QLRYXOAD.js.map → chunk-UDJLF3BO.js.map} +0 -0
- /package/dist/{chunk-WRFKZEO6.js.map → chunk-YQGHW5ML.js.map} +0 -0
package/src/orchestrator.ts
CHANGED
|
@@ -82,15 +82,7 @@ import {
|
|
|
82
82
|
fallbackLlmRuntimeContextFromConfig,
|
|
83
83
|
gatewayTaskChainOptions,
|
|
84
84
|
} from "./fallback-llm.js";
|
|
85
|
-
import {
|
|
86
|
-
ensureDaySummaryCron,
|
|
87
|
-
ensureNightlyGovernanceCron,
|
|
88
|
-
ensureProceduralMiningCron,
|
|
89
|
-
ensureContradictionScanCron,
|
|
90
|
-
ensurePatternReinforcementCron,
|
|
91
|
-
ensureGraphEdgeDecayCron,
|
|
92
|
-
graphEdgeDecayCadenceToCronExpr,
|
|
93
|
-
} from "./maintenance/memory-governance-cron.js";
|
|
85
|
+
import { MaintenanceScheduler } from "./orchestration/maintenance.js";
|
|
94
86
|
import {
|
|
95
87
|
runLiveConnectorsOnce,
|
|
96
88
|
type LiveConnectorsRunSummary,
|
|
@@ -315,9 +307,6 @@ import {
|
|
|
315
307
|
} from "./namespaces/catalog.js";
|
|
316
308
|
import {
|
|
317
309
|
planNamespaceMaintenance,
|
|
318
|
-
runNamespaceMaintenanceBatchPlan,
|
|
319
|
-
type NamespaceMaintenancePlan,
|
|
320
|
-
type NamespaceMaintenanceSkipReason,
|
|
321
310
|
type NamespaceMaintenanceSummary,
|
|
322
311
|
} from "./maintenance/namespace-planner.js";
|
|
323
312
|
import {
|
|
@@ -1804,13 +1793,6 @@ export function resolvePersistedMemoryRelativePath(options: {
|
|
|
1804
1793
|
return path.join(subtree, `${options.memoryId}.md`);
|
|
1805
1794
|
}
|
|
1806
1795
|
|
|
1807
|
-
function qmdMaintenanceSkipReasonForError(error: unknown): NamespaceMaintenanceSkipReason | null {
|
|
1808
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1809
|
-
return /^QMD (?:update|embed) skipped by .*min-interval gate$/.test(message)
|
|
1810
|
-
? "throttled"
|
|
1811
|
-
: null;
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
1796
|
export class Orchestrator {
|
|
1815
1797
|
readonly storage: StorageManager;
|
|
1816
1798
|
private readonly storageRouter: NamespaceStorageRouter;
|
|
@@ -1820,6 +1802,12 @@ export class Orchestrator {
|
|
|
1820
1802
|
private namespaceStorageDirHintsLoaded = false;
|
|
1821
1803
|
private readonly namespaceSearchRouter: NamespaceSearchRouter;
|
|
1822
1804
|
qmd: SearchBackend;
|
|
1805
|
+
/**
|
|
1806
|
+
* Maintenance scheduler (issue #1526 PR1). Owns cron auto-registration,
|
|
1807
|
+
* debounced QMD index maintenance, and consolidation scheduling. Job
|
|
1808
|
+
* runners (consolidation/pattern-reinforcement/governance) stay here.
|
|
1809
|
+
*/
|
|
1810
|
+
readonly maintenanceScheduler: MaintenanceScheduler;
|
|
1823
1811
|
private readonly conversationQmd?: ConversationQmdRuntime;
|
|
1824
1812
|
private readonly conversationFaiss?: ReturnType<
|
|
1825
1813
|
typeof createConversationIndexRuntime
|
|
@@ -1947,19 +1935,11 @@ export class Orchestrator {
|
|
|
1947
1935
|
private queueProcessing = false;
|
|
1948
1936
|
private heartbeatObserverChains = new Map<string, Promise<void>>();
|
|
1949
1937
|
private recentExtractionFingerprints = new Map<string, number>();
|
|
1950
|
-
private nonZeroExtractionsSinceConsolidation = 0;
|
|
1951
|
-
private lastConsolidationRunAtMs = 0;
|
|
1952
|
-
private consolidationInFlight = false;
|
|
1953
1938
|
private readonly consolidationObservers = new Set<
|
|
1954
1939
|
(observation: ConsolidationObservation) => Promise<void> | void
|
|
1955
1940
|
>();
|
|
1956
|
-
private qmdMaintenanceTimer: NodeJS.Timeout | null = null;
|
|
1957
1941
|
private wearablesServiceInstance: WearablesService | null = null;
|
|
1958
1942
|
private wearablesAutoSyncHandle: { stop(): Promise<void> } | null = null;
|
|
1959
|
-
private qmdMaintenancePending = false;
|
|
1960
|
-
private qmdMaintenanceInFlight = false;
|
|
1961
|
-
private lastQmdEmbedAtMs = 0;
|
|
1962
|
-
private lastQmdEmbedAtMsByNamespace = new Map<string, number>();
|
|
1963
1943
|
private lastQmdReprobeAtMs = 0;
|
|
1964
1944
|
private tierMigrationInFlight = false;
|
|
1965
1945
|
private lastTierMigrationRunAtMs = 0;
|
|
@@ -2044,11 +2024,7 @@ export class Orchestrator {
|
|
|
2044
2024
|
await this.wearablesAutoSyncHandle.stop();
|
|
2045
2025
|
this.wearablesAutoSyncHandle = null;
|
|
2046
2026
|
}
|
|
2047
|
-
|
|
2048
|
-
clearTimeout(this.qmdMaintenanceTimer);
|
|
2049
|
-
this.qmdMaintenanceTimer = null;
|
|
2050
|
-
}
|
|
2051
|
-
this.qmdMaintenancePending = false;
|
|
2027
|
+
this.maintenanceScheduler.dispose();
|
|
2052
2028
|
await this.namespaceSearchRouter.dispose();
|
|
2053
2029
|
await this.disposeSearchBackendIfNeeded();
|
|
2054
2030
|
if (this.conversationQmd && this.conversationQmd !== this.qmd) {
|
|
@@ -2462,22 +2438,6 @@ export class Orchestrator {
|
|
|
2462
2438
|
}
|
|
2463
2439
|
}
|
|
2464
2440
|
|
|
2465
|
-
/**
|
|
2466
|
-
* Shared namespace maintenance planner (issue #1500). This extends the
|
|
2467
|
-
* #1499 catalog-union QMD helper into a reusable contract: configured
|
|
2468
|
-
* namespaces are always considered, dynamic catalog namespaces are admitted
|
|
2469
|
-
* only when their live router root still matches real memory data, and branch
|
|
2470
|
-
* namespaces are opt-in. Recurring jobs use the per-cycle budget; startup and
|
|
2471
|
-
* recovery discovery paths use the same safety filters without that cycle
|
|
2472
|
-
* budget so every live namespace is ensured/synced.
|
|
2473
|
-
*/
|
|
2474
|
-
private async namespaceMaintenancePlan(jobName: string): Promise<NamespaceMaintenancePlan> {
|
|
2475
|
-
return planNamespaceMaintenance(this.config, {
|
|
2476
|
-
jobName,
|
|
2477
|
-
catalog: this.namespaceCatalog,
|
|
2478
|
-
});
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
2441
|
private async maintenanceNamespaces(
|
|
2482
2442
|
jobName = "qmd",
|
|
2483
2443
|
budgetMode: "cycle" | "unbounded" = "unbounded",
|
|
@@ -2760,6 +2720,16 @@ export class Orchestrator {
|
|
|
2760
2720
|
// and writes will throw SecureStoreLockedError via resolveWriteKey().
|
|
2761
2721
|
}
|
|
2762
2722
|
this.qmd = createSearchBackend(config);
|
|
2723
|
+
this.maintenanceScheduler = new MaintenanceScheduler({
|
|
2724
|
+
config,
|
|
2725
|
+
// Live accessor: the orchestrator reassigns this.qmd to NoopSearchBackend
|
|
2726
|
+
// after construction when the collection is missing (initialize /
|
|
2727
|
+
// startupSearchSync); the scheduler must read the current backend so
|
|
2728
|
+
// debounced maintenance never runs against a stale/disposed one.
|
|
2729
|
+
getQmd: () => this.qmd,
|
|
2730
|
+
namespaceSearchRouter: this.namespaceSearchRouter,
|
|
2731
|
+
namespaceCatalog: this.namespaceCatalog,
|
|
2732
|
+
});
|
|
2763
2733
|
const conversationIndexRuntime = createConversationIndexRuntime(config, {
|
|
2764
2734
|
getQmd: () => this.conversationQmd,
|
|
2765
2735
|
getFaiss: () => this.conversationFaiss,
|
|
@@ -3540,56 +3510,10 @@ export class Orchestrator {
|
|
|
3540
3510
|
// rely on cron jobs being registered when it resolves. Without this, the
|
|
3541
3511
|
// fire-and-forget pattern lets deferredReady settle while cron writes are
|
|
3542
3512
|
// still in flight. Errors are non-fatal — catch individually.
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
log.debug(`day-summary cron auto-register failed (non-fatal): ${err}`);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
if (this.config.nightlyGovernanceCronAutoRegister) {
|
|
3551
|
-
try {
|
|
3552
|
-
await this.autoRegisterNightlyGovernanceCron();
|
|
3553
|
-
} catch (err) {
|
|
3554
|
-
log.debug(`nightly governance cron auto-register failed (non-fatal): ${err}`);
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
if (this.config.procedural?.proceduralMiningCronAutoRegister) {
|
|
3558
|
-
try {
|
|
3559
|
-
await this.autoRegisterProceduralMiningCron();
|
|
3560
|
-
} catch (err) {
|
|
3561
|
-
log.debug(`procedural mining cron auto-register failed (non-fatal): ${err}`);
|
|
3562
|
-
}
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
// Auto-register contradiction scan cron (gated by config)
|
|
3566
|
-
if (this.config.contradictionScan?.enabled) {
|
|
3567
|
-
try {
|
|
3568
|
-
await this.autoRegisterContradictionScanCron();
|
|
3569
|
-
} catch (err) {
|
|
3570
|
-
log.debug(`contradiction scan cron auto-register failed (non-fatal): ${err}`);
|
|
3571
|
-
}
|
|
3572
|
-
}
|
|
3573
|
-
|
|
3574
|
-
// Auto-register pattern-reinforcement cron (issue #687 PR 2/4).
|
|
3575
|
-
// Gated on the feature flag so memory-only users without the
|
|
3576
|
-
// cron daemon installed never see a stray jobs.json mutation.
|
|
3577
|
-
if (this.config.patternReinforcementEnabled) {
|
|
3578
|
-
try {
|
|
3579
|
-
await this.autoRegisterPatternReinforcementCron();
|
|
3580
|
-
} catch (err) {
|
|
3581
|
-
log.debug(`pattern reinforcement cron auto-register failed (non-fatal): ${err}`);
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
|
|
3585
|
-
// Auto-register graph-edge decay cron (gated by config — issue #681 PR 2/3).
|
|
3586
|
-
if (this.config.graphEdgeDecayEnabled) {
|
|
3587
|
-
try {
|
|
3588
|
-
await this.autoRegisterGraphEdgeDecayCron();
|
|
3589
|
-
} catch (err) {
|
|
3590
|
-
log.debug(`graph edge decay cron auto-register failed (non-fatal): ${err}`);
|
|
3591
|
-
}
|
|
3592
|
-
}
|
|
3513
|
+
// Auto-register every cron job in one pass. Each registration is gated
|
|
3514
|
+
// by its config flag inside the scheduler and individually non-fatal
|
|
3515
|
+
// (issue #1526 PR1 — moved to MaintenanceScheduler).
|
|
3516
|
+
await this.maintenanceScheduler.autoRegisterCrons(signal);
|
|
3593
3517
|
|
|
3594
3518
|
// First-start lifecycle migration (issue #686 retention-completion).
|
|
3595
3519
|
// When lifecyclePolicyEnabled is true and the memoryDir has never been
|
|
@@ -3819,168 +3743,6 @@ export class Orchestrator {
|
|
|
3819
3743
|
return true;
|
|
3820
3744
|
}
|
|
3821
3745
|
|
|
3822
|
-
/**
|
|
3823
|
-
* Auto-register the engram-day-summary cron job in OpenClaw.
|
|
3824
|
-
* Reconciles model and timezone on every startup so config changes propagate.
|
|
3825
|
-
* Fire-and-forget — never blocks init or crashes on failure.
|
|
3826
|
-
* Issues #1474, #1475.
|
|
3827
|
-
*/
|
|
3828
|
-
private async autoRegisterDaySummaryCron(): Promise<void> {
|
|
3829
|
-
const home = resolveHomeDir();
|
|
3830
|
-
const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
|
|
3831
|
-
|
|
3832
|
-
try {
|
|
3833
|
-
if (!existsSync(jobsPath)) {
|
|
3834
|
-
log.debug(
|
|
3835
|
-
"day-summary cron: jobs.json not found, skipping auto-register",
|
|
3836
|
-
);
|
|
3837
|
-
return;
|
|
3838
|
-
}
|
|
3839
|
-
|
|
3840
|
-
// Resolve an OpenClaw cron-routing model only in gateway mode. In plugin
|
|
3841
|
-
// mode, summaryModel is a direct-client model id for Remnic's own LLM
|
|
3842
|
-
// calls and may be unroutable as an OpenClaw agentTurn model.
|
|
3843
|
-
const rawSummaryModel = this.config.summaryModel;
|
|
3844
|
-
const taskPrimary = this.config.taskModelChain?.primary;
|
|
3845
|
-
const isGateway = this.config.modelSource === "gateway";
|
|
3846
|
-
const model = isGateway ? (rawSummaryModel || taskPrimary || undefined) : undefined;
|
|
3847
|
-
// Attach task-chain fallbacks only when the model matches the task-chain
|
|
3848
|
-
// primary. If summaryModel is a distinct override, its fallbacks would
|
|
3849
|
-
// be unrelated to the task chain. Also append gateway default models as
|
|
3850
|
-
// tail fallbacks (de-duped) so a task-chain outage doesn't stop the cron
|
|
3851
|
-
// before reaching the gateway default chain. Mirrors hourly cron pattern.
|
|
3852
|
-
const fallbacks: string[] = [];
|
|
3853
|
-
if (model && taskPrimary && model === taskPrimary) {
|
|
3854
|
-
const seen = new Set<string>(model ? [model] : []);
|
|
3855
|
-
const addUnique = (value: string | undefined) => {
|
|
3856
|
-
if (typeof value !== "string") return;
|
|
3857
|
-
const trimmed = value.trim();
|
|
3858
|
-
if (trimmed.length > 0 && !seen.has(trimmed)) {
|
|
3859
|
-
seen.add(trimmed);
|
|
3860
|
-
fallbacks.push(trimmed);
|
|
3861
|
-
}
|
|
3862
|
-
};
|
|
3863
|
-
for (const fb of this.config.taskModelChain?.fallbacks ?? []) addUnique(fb);
|
|
3864
|
-
const gwDefaults = this.config.gatewayConfig?.agents?.defaults?.model;
|
|
3865
|
-
addUnique(gwDefaults?.primary);
|
|
3866
|
-
if (Array.isArray(gwDefaults?.fallbacks)) {
|
|
3867
|
-
for (const fb of gwDefaults.fallbacks) addUnique(fb);
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
|
-
|
|
3871
|
-
// Resolve timezone: configurable override, then server default
|
|
3872
|
-
const timezone = this.config.daySummaryTimezone
|
|
3873
|
-
|| Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
3874
|
-
|
|
3875
|
-
const result = await ensureDaySummaryCron(jobsPath, {
|
|
3876
|
-
timezone,
|
|
3877
|
-
...(model ? { model } : {}),
|
|
3878
|
-
...(fallbacks.length > 0 ? { fallbacks } : {}),
|
|
3879
|
-
});
|
|
3880
|
-
if (result.created) {
|
|
3881
|
-
log.info(
|
|
3882
|
-
`day-summary cron auto-registered (${result.jobId}, 23:47 ${timezone}${model ? `, model: ${model}` : ""})`,
|
|
3883
|
-
);
|
|
3884
|
-
} else if (result.updated) {
|
|
3885
|
-
log.info(
|
|
3886
|
-
`day-summary cron reconciled (${result.jobId}, timezone: ${timezone}${model ? `, model: ${model}` : ""})`,
|
|
3887
|
-
);
|
|
3888
|
-
} else {
|
|
3889
|
-
log.debug("day-summary cron already up to date");
|
|
3890
|
-
}
|
|
3891
|
-
} catch (err) {
|
|
3892
|
-
log.debug(`day-summary cron auto-register error: ${err}`);
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
|
|
3896
|
-
private async autoRegisterNightlyGovernanceCron(): Promise<void> {
|
|
3897
|
-
const home = resolveHomeDir();
|
|
3898
|
-
const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
|
|
3899
|
-
|
|
3900
|
-
try {
|
|
3901
|
-
if (!existsSync(jobsPath)) {
|
|
3902
|
-
log.debug("nightly governance cron: jobs.json not found, skipping auto-register");
|
|
3903
|
-
return;
|
|
3904
|
-
}
|
|
3905
|
-
|
|
3906
|
-
const created = await ensureNightlyGovernanceCron(jobsPath, {
|
|
3907
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
3908
|
-
});
|
|
3909
|
-
if (created.created) {
|
|
3910
|
-
log.info(
|
|
3911
|
-
`nightly governance cron auto-registered (${created.jobId}, 02:23 ${Intl.DateTimeFormat().resolvedOptions().timeZone})`,
|
|
3912
|
-
);
|
|
3913
|
-
} else {
|
|
3914
|
-
log.debug("nightly governance cron already exists, skipping auto-register");
|
|
3915
|
-
}
|
|
3916
|
-
} catch (err) {
|
|
3917
|
-
log.debug(`nightly governance cron auto-register error: ${err}`);
|
|
3918
|
-
}
|
|
3919
|
-
}
|
|
3920
|
-
|
|
3921
|
-
private async autoRegisterProceduralMiningCron(): Promise<void> {
|
|
3922
|
-
const home = resolveHomeDir();
|
|
3923
|
-
const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
|
|
3924
|
-
try {
|
|
3925
|
-
if (!existsSync(jobsPath)) {
|
|
3926
|
-
log.debug("procedural mining cron: jobs.json not found, skipping auto-register");
|
|
3927
|
-
return;
|
|
3928
|
-
}
|
|
3929
|
-
const created = await ensureProceduralMiningCron(jobsPath, {
|
|
3930
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
3931
|
-
});
|
|
3932
|
-
if (created.created) {
|
|
3933
|
-
log.info(`procedural mining cron auto-registered (${created.jobId})`);
|
|
3934
|
-
} else {
|
|
3935
|
-
log.debug("procedural mining cron already exists, skipping auto-register");
|
|
3936
|
-
}
|
|
3937
|
-
} catch (err) {
|
|
3938
|
-
log.debug(`procedural mining cron auto-register error: ${err}`);
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
|
|
3942
|
-
private async autoRegisterContradictionScanCron(): Promise<void> {
|
|
3943
|
-
const home = resolveHomeDir();
|
|
3944
|
-
const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
|
|
3945
|
-
try {
|
|
3946
|
-
if (!existsSync(jobsPath)) {
|
|
3947
|
-
log.debug("contradiction scan cron: jobs.json not found, skipping auto-register");
|
|
3948
|
-
return;
|
|
3949
|
-
}
|
|
3950
|
-
const created = await ensureContradictionScanCron(jobsPath, {
|
|
3951
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
3952
|
-
});
|
|
3953
|
-
if (created.created) {
|
|
3954
|
-
log.info(`contradiction scan cron auto-registered (${created.jobId})`);
|
|
3955
|
-
} else {
|
|
3956
|
-
log.debug("contradiction scan cron already exists, skipping auto-register");
|
|
3957
|
-
}
|
|
3958
|
-
} catch (err) {
|
|
3959
|
-
log.debug(`contradiction scan cron auto-register error: ${err}`);
|
|
3960
|
-
}
|
|
3961
|
-
}
|
|
3962
|
-
|
|
3963
|
-
private async autoRegisterPatternReinforcementCron(): Promise<void> {
|
|
3964
|
-
const home = resolveHomeDir();
|
|
3965
|
-
const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
|
|
3966
|
-
try {
|
|
3967
|
-
if (!existsSync(jobsPath)) {
|
|
3968
|
-
log.debug("pattern reinforcement cron: jobs.json not found, skipping auto-register");
|
|
3969
|
-
return;
|
|
3970
|
-
}
|
|
3971
|
-
const created = await ensurePatternReinforcementCron(jobsPath, {
|
|
3972
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
3973
|
-
});
|
|
3974
|
-
if (created.created) {
|
|
3975
|
-
log.info(`pattern reinforcement cron auto-registered (${created.jobId})`);
|
|
3976
|
-
} else {
|
|
3977
|
-
log.debug("pattern reinforcement cron already exists, skipping auto-register");
|
|
3978
|
-
}
|
|
3979
|
-
} catch (err) {
|
|
3980
|
-
log.debug(`pattern reinforcement cron auto-register error: ${err}`);
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
3983
|
-
|
|
3984
3746
|
/**
|
|
3985
3747
|
* Run the pattern-reinforcement maintenance job (issue #687 PR 2/4).
|
|
3986
3748
|
*
|
|
@@ -4142,29 +3904,6 @@ export class Orchestrator {
|
|
|
4142
3904
|
);
|
|
4143
3905
|
}
|
|
4144
3906
|
|
|
4145
|
-
private async autoRegisterGraphEdgeDecayCron(): Promise<void> {
|
|
4146
|
-
const home = resolveHomeDir();
|
|
4147
|
-
const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
|
|
4148
|
-
try {
|
|
4149
|
-
if (!existsSync(jobsPath)) {
|
|
4150
|
-
log.debug("graph edge decay cron: jobs.json not found, skipping auto-register");
|
|
4151
|
-
return;
|
|
4152
|
-
}
|
|
4153
|
-
const scheduleExpr = graphEdgeDecayCadenceToCronExpr(this.config.graphEdgeDecayCadenceMs);
|
|
4154
|
-
const created = await ensureGraphEdgeDecayCron(jobsPath, {
|
|
4155
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
4156
|
-
scheduleExpr,
|
|
4157
|
-
});
|
|
4158
|
-
if (created.created) {
|
|
4159
|
-
log.info(`graph edge decay cron auto-registered (${created.jobId}, ${scheduleExpr})`);
|
|
4160
|
-
} else {
|
|
4161
|
-
log.debug("graph edge decay cron already exists, skipping auto-register");
|
|
4162
|
-
}
|
|
4163
|
-
} catch (err) {
|
|
4164
|
-
log.debug(`graph edge decay cron auto-register error: ${err}`);
|
|
4165
|
-
}
|
|
4166
|
-
}
|
|
4167
|
-
|
|
4168
3907
|
async runLiveConnectors(options: {
|
|
4169
3908
|
force?: boolean;
|
|
4170
3909
|
abortSignal?: AbortSignal;
|
|
@@ -4718,7 +4457,7 @@ export class Orchestrator {
|
|
|
4718
4457
|
|
|
4719
4458
|
async waitForConsolidationIdle(timeoutMs: number = 60_000): Promise<boolean> {
|
|
4720
4459
|
const started = Date.now();
|
|
4721
|
-
while (this.
|
|
4460
|
+
while (this.maintenanceScheduler.isConsolidationInFlight()) {
|
|
4722
4461
|
if (Date.now() - started > timeoutMs) {
|
|
4723
4462
|
log.warn(`waitForConsolidationIdle timed out after ${timeoutMs}ms`);
|
|
4724
4463
|
return false;
|
|
@@ -13657,7 +13396,8 @@ export class Orchestrator {
|
|
|
13657
13396
|
// Check if consolidation is needed (debounced + non-zero gated).
|
|
13658
13397
|
const nonZeroExtraction = durableOutputCount > 0;
|
|
13659
13398
|
try {
|
|
13660
|
-
|
|
13399
|
+
// The increment of nonZeroExtractionsSinceConsolidation moved into
|
|
13400
|
+
// the scheduler with the rest of the cadence state (issue #1526 PR1).
|
|
13661
13401
|
this.maybeScheduleConsolidation(nonZeroExtraction);
|
|
13662
13402
|
} catch (err) {
|
|
13663
13403
|
log.warn(
|
|
@@ -13928,148 +13668,22 @@ export class Orchestrator {
|
|
|
13928
13668
|
}
|
|
13929
13669
|
|
|
13930
13670
|
private maybeScheduleConsolidation(nonZeroExtraction: boolean): void {
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
)
|
|
13936
|
-
return;
|
|
13937
|
-
|
|
13938
|
-
const now = Date.now();
|
|
13939
|
-
if (
|
|
13940
|
-
now - this.lastConsolidationRunAtMs <
|
|
13941
|
-
this.config.consolidationMinIntervalMs
|
|
13942
|
-
)
|
|
13943
|
-
return;
|
|
13944
|
-
if (this.consolidationInFlight) return;
|
|
13945
|
-
|
|
13946
|
-
this.consolidationInFlight = true;
|
|
13947
|
-
this.lastConsolidationRunAtMs = now;
|
|
13948
|
-
this.nonZeroExtractionsSinceConsolidation = 0;
|
|
13949
|
-
this.runConsolidation()
|
|
13950
|
-
.catch((err) => log.error("background consolidation failed", err))
|
|
13951
|
-
.finally(() => {
|
|
13952
|
-
this.consolidationInFlight = false;
|
|
13953
|
-
});
|
|
13671
|
+
this.maintenanceScheduler.maybeScheduleConsolidation(
|
|
13672
|
+
nonZeroExtraction,
|
|
13673
|
+
() => this.runConsolidation(),
|
|
13674
|
+
);
|
|
13954
13675
|
}
|
|
13955
13676
|
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
if (!this.config.qmdMaintenanceEnabled) return;
|
|
13959
|
-
|
|
13960
|
-
this.qmdMaintenancePending = true;
|
|
13961
|
-
if (this.qmdMaintenanceTimer) return;
|
|
13962
|
-
|
|
13963
|
-
this.qmdMaintenanceTimer = setTimeout(() => {
|
|
13964
|
-
this.qmdMaintenanceTimer = null;
|
|
13965
|
-
this.runQmdMaintenance().catch((err) =>
|
|
13966
|
-
log.debug(`background qmd maintenance failed: ${err}`),
|
|
13967
|
-
);
|
|
13968
|
-
}, this.config.qmdMaintenanceDebounceMs);
|
|
13677
|
+
requestQmdMaintenance(): void {
|
|
13678
|
+
this.maintenanceScheduler.requestQmdMaintenanceForTool("__internal__");
|
|
13969
13679
|
}
|
|
13970
13680
|
|
|
13971
13681
|
/**
|
|
13972
13682
|
* Public entrypoint for tool-driven QMD maintenance requests.
|
|
13973
|
-
*
|
|
13683
|
+
* Delegates to the maintenance scheduler (issue #1526 PR1).
|
|
13974
13684
|
*/
|
|
13975
13685
|
requestQmdMaintenanceForTool(reason: string): void {
|
|
13976
|
-
|
|
13977
|
-
this.requestQmdMaintenance();
|
|
13978
|
-
} catch (err) {
|
|
13979
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
13980
|
-
log.warn(`qmd maintenance request failed (${reason}): ${msg}`);
|
|
13981
|
-
}
|
|
13982
|
-
}
|
|
13983
|
-
|
|
13984
|
-
private async runQmdMaintenance(): Promise<void> {
|
|
13985
|
-
if (this.qmdMaintenanceInFlight) return;
|
|
13986
|
-
if (!this.qmdMaintenancePending) return;
|
|
13987
|
-
this.qmdMaintenanceInFlight = true;
|
|
13988
|
-
this.qmdMaintenancePending = false;
|
|
13989
|
-
|
|
13990
|
-
try {
|
|
13991
|
-
if (this.config.namespacesEnabled) {
|
|
13992
|
-
// Include cataloged dynamic namespaces, not just the configured set
|
|
13993
|
-
// (NGnei), but run through the namespace-aware maintenance planner so
|
|
13994
|
-
// each namespace is budgeted, lock-protected, and status-recorded
|
|
13995
|
-
// independently (issue #1500).
|
|
13996
|
-
const plan = await this.namespaceMaintenancePlan("qmd");
|
|
13997
|
-
const now = Date.now();
|
|
13998
|
-
const lastEmbedAtByNamespace =
|
|
13999
|
-
this.lastQmdEmbedAtMsByNamespace ?? (this.lastQmdEmbedAtMsByNamespace = new Map());
|
|
14000
|
-
const dueEmbedNamespaces = (namespaces: string[]): string[] => {
|
|
14001
|
-
if (!this.config.qmdAutoEmbedEnabled) return [];
|
|
14002
|
-
return namespaces.filter(
|
|
14003
|
-
(namespace) =>
|
|
14004
|
-
now - (lastEmbedAtByNamespace.get(namespace) ?? 0) >= this.config.qmdEmbedMinIntervalMs,
|
|
14005
|
-
);
|
|
14006
|
-
};
|
|
14007
|
-
const markEmbedded = (namespaces: string[]): void => {
|
|
14008
|
-
if (namespaces.length === 0) return;
|
|
14009
|
-
for (const namespace of namespaces) {
|
|
14010
|
-
lastEmbedAtByNamespace.set(namespace, now);
|
|
14011
|
-
}
|
|
14012
|
-
this.lastQmdEmbedAtMs = now;
|
|
14013
|
-
};
|
|
14014
|
-
await runNamespaceMaintenanceBatchPlan(
|
|
14015
|
-
this.config,
|
|
14016
|
-
plan,
|
|
14017
|
-
async (candidates) => {
|
|
14018
|
-
const namespaces = candidates.map((candidate) => candidate.namespace);
|
|
14019
|
-
const embedNamespaces = dueEmbedNamespaces(namespaces);
|
|
14020
|
-
let result: Awaited<ReturnType<NamespaceSearchRouter["updateNamespacesDetailed"]>>;
|
|
14021
|
-
try {
|
|
14022
|
-
result = await this.namespaceSearchRouter.updateNamespacesDetailed(
|
|
14023
|
-
namespaces,
|
|
14024
|
-
undefined,
|
|
14025
|
-
{ strict: true },
|
|
14026
|
-
);
|
|
14027
|
-
} catch (error) {
|
|
14028
|
-
if (
|
|
14029
|
-
embedNamespaces.length > 0 &&
|
|
14030
|
-
qmdMaintenanceSkipReasonForError(error) === "throttled"
|
|
14031
|
-
) {
|
|
14032
|
-
await this.namespaceSearchRouter.embedNamespaces(embedNamespaces, { strict: true });
|
|
14033
|
-
markEmbedded(embedNamespaces);
|
|
14034
|
-
}
|
|
14035
|
-
throw error;
|
|
14036
|
-
}
|
|
14037
|
-
if (result.backendCount <= 0) {
|
|
14038
|
-
throw new Error("no eligible QMD backend for selected namespaces");
|
|
14039
|
-
}
|
|
14040
|
-
if (result.eligibleNamespaces.length !== namespaces.length) {
|
|
14041
|
-
const eligible = new Set(result.eligibleNamespaces);
|
|
14042
|
-
const missing = namespaces.filter((namespace) => !eligible.has(namespace));
|
|
14043
|
-
throw new Error(`QMD backend ineligible for selected namespaces (${missing.length})`);
|
|
14044
|
-
}
|
|
14045
|
-
if (embedNamespaces.length > 0) {
|
|
14046
|
-
await this.namespaceSearchRouter.embedNamespaces(embedNamespaces, { strict: true });
|
|
14047
|
-
markEmbedded(embedNamespaces);
|
|
14048
|
-
}
|
|
14049
|
-
return { itemCount: result.backendCount };
|
|
14050
|
-
},
|
|
14051
|
-
this.namespaceCatalog,
|
|
14052
|
-
{
|
|
14053
|
-
skipReasonForError: qmdMaintenanceSkipReasonForError,
|
|
14054
|
-
},
|
|
14055
|
-
);
|
|
14056
|
-
} else {
|
|
14057
|
-
await this.qmd.update();
|
|
14058
|
-
const now = Date.now();
|
|
14059
|
-
if (
|
|
14060
|
-
this.config.qmdAutoEmbedEnabled &&
|
|
14061
|
-
now - this.lastQmdEmbedAtMs >= this.config.qmdEmbedMinIntervalMs
|
|
14062
|
-
) {
|
|
14063
|
-
await this.qmd.embed();
|
|
14064
|
-
this.lastQmdEmbedAtMs = now;
|
|
14065
|
-
}
|
|
14066
|
-
}
|
|
14067
|
-
} finally {
|
|
14068
|
-
this.qmdMaintenanceInFlight = false;
|
|
14069
|
-
if (this.qmdMaintenancePending) {
|
|
14070
|
-
this.requestQmdMaintenance();
|
|
14071
|
-
}
|
|
14072
|
-
}
|
|
13686
|
+
this.maintenanceScheduler.requestQmdMaintenanceForTool(reason);
|
|
14073
13687
|
}
|
|
14074
13688
|
|
|
14075
13689
|
private async persistExtraction(
|