@remnic/core 9.3.688 → 9.3.690
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 +5 -4
- package/dist/access-boundary.js +12 -11
- package/dist/access-cli.js +90 -26
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +4 -3
- package/dist/access-http.js +15 -14
- package/dist/access-mcp.d.ts +11 -3
- package/dist/access-mcp.js +14 -13
- package/dist/access-operations.d.ts +11 -5
- package/dist/access-operations.js +15 -12
- package/dist/{access-service-DmCHJ4cH.d.ts → access-service-Dujr3MTm.d.ts} +62 -2
- package/dist/access-service.d.ts +4 -3
- package/dist/access-service.js +11 -10
- package/dist/access-surface-catalog.d.ts +4 -3
- package/dist/access-surface-catalog.js +2 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/bootstrap.d.ts +3 -2
- package/dist/briefing.d.ts +1 -0
- package/dist/briefing.js +3 -2
- package/dist/buffer.d.ts +1 -0
- package/dist/{catalog-COqWZlZ6.d.ts → catalog-CKPtJ114.d.ts} +1 -1
- package/dist/causal-consolidation.js +4 -3
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-B4XVLHJA.js → chunk-2N6UNJSX.js} +2 -2
- package/dist/{chunk-NN7QYW5W.js → chunk-46URPRE6.js} +2 -2
- package/dist/chunk-46URPRE6.js.map +1 -0
- package/dist/{chunk-4N3TFFPH.js → chunk-54PVJDO5.js} +2 -2
- package/dist/{chunk-PH3HOKYW.js → chunk-67MMWC74.js} +2 -2
- package/dist/{chunk-STOEE37X.js → chunk-BKAMHZYR.js} +2 -2
- package/dist/{chunk-B43NZNMG.js → chunk-CE6CBRCV.js} +432 -2240
- package/dist/chunk-CE6CBRCV.js.map +1 -0
- package/dist/{chunk-RVYD6LR3.js → chunk-CP2NZQLT.js} +15 -4
- package/dist/chunk-CP2NZQLT.js.map +1 -0
- package/dist/{chunk-BLIWOONZ.js → chunk-CUNTLEJP.js} +4 -12
- package/dist/chunk-CUNTLEJP.js.map +1 -0
- package/dist/{chunk-2SJCWLQD.js → chunk-CWE74HRG.js} +3 -3
- package/dist/{chunk-VX6OBUDW.js → chunk-GR77Z2BM.js} +2 -2
- package/dist/{chunk-CTOQEZSN.js → chunk-LXIEXSHU.js} +2 -2
- package/dist/{chunk-KFBOZYME.js → chunk-NSVXK7M5.js} +33 -4
- package/dist/chunk-NSVXK7M5.js.map +1 -0
- package/dist/{chunk-473JIN2U.js → chunk-OMKUJTVJ.js} +37 -5
- package/dist/chunk-OMKUJTVJ.js.map +1 -0
- package/dist/{chunk-GS55WYRL.js → chunk-PHZKALOE.js} +2 -2
- package/dist/{chunk-6O6A6YUO.js → chunk-RGNEARXW.js} +3 -3
- package/dist/{chunk-4FE2K57M.js → chunk-RTFAZOIR.js} +2 -2
- package/dist/{chunk-UTYBJR7M.js → chunk-SB6CQTKP.js} +2 -2
- package/dist/chunk-SVOZFLIQ.js +16 -0
- package/dist/chunk-SVOZFLIQ.js.map +1 -0
- package/dist/{chunk-2KAYTPPT.js → chunk-TYF3D4MS.js} +5 -5
- package/dist/{chunk-WIHPNY65.js → chunk-UD5OKH4J.js} +2 -2
- package/dist/{chunk-QANVLERJ.js → chunk-UPIBE2DK.js} +2 -2
- package/dist/{chunk-CCWHPGT4.js → chunk-WROKD3XC.js} +45 -30
- package/dist/chunk-WROKD3XC.js.map +1 -0
- package/dist/{chunk-ZPTISBQU.js → chunk-X5J3JZR3.js} +7 -7
- package/dist/{chunk-OV4D5T7V.js → chunk-X7RLU5CR.js} +2 -2
- package/dist/{chunk-S2OU5DZY.js → chunk-XKUKJIOY.js} +30 -7
- package/dist/chunk-XKUKJIOY.js.map +1 -0
- package/dist/{chunk-FUCUR2OZ.js → chunk-Z7KILAOU.js} +603 -9
- package/dist/chunk-Z7KILAOU.js.map +1 -0
- package/dist/chunk-ZU7N3S6V.js +2190 -0
- package/dist/chunk-ZU7N3S6V.js.map +1 -0
- package/dist/{chunk-DCWIQFNA.js → chunk-ZUDM75KG.js} +4 -4
- package/dist/{cli-D8nZ2MPH.d.ts → cli-BkDp6WNi.d.ts} +2 -2
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +32 -30
- package/dist/compounding/engine.d.ts +1 -0
- package/dist/compounding/engine.js +3 -2
- package/dist/connectors/codex-materialize-runner.js +3 -2
- package/dist/connectors/index.js +3 -2
- package/dist/consolidation-provenance-check.d.ts +1 -0
- package/dist/consolidation-undo.d.ts +1 -0
- package/dist/contradiction/index.d.ts +1 -0
- package/dist/conversation-index/backend.js +2 -2
- package/dist/entity-retrieval.d.ts +1 -0
- package/dist/entity-retrieval.js +3 -2
- package/dist/explicit-capture.d.ts +3 -2
- package/dist/index.d.ts +6 -5
- package/dist/index.js +47 -44
- package/dist/index.js.map +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +5 -5
- package/dist/maintenance/memory-governance.js +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +4 -3
- package/dist/mcp-memory-inspector-app.d.ts +4 -3
- package/dist/memory-worth-outcomes.d.ts +1 -0
- package/dist/namespaces/migrate.d.ts +2 -1
- package/dist/namespaces/migrate.js +10 -9
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.d.ts +2 -1
- package/dist/namespaces/storage.js +3 -2
- package/dist/operator-toolkit.d.ts +1 -0
- package/dist/operator-toolkit.js +13 -11
- package/dist/{orchestrator-CA6ouzBn.d.ts → orchestrator-B7ixmUkP.d.ts} +145 -1
- package/dist/orchestrator.d.ts +3 -2
- package/dist/orchestrator.js +22 -20
- package/dist/recall-pipeline-stages.d.ts +167 -0
- package/dist/recall-pipeline-stages.js +61 -0
- package/dist/recall-pipeline-stages.js.map +1 -0
- package/dist/schemas.d.ts +10 -10
- package/dist/search/factory.js +5 -5
- package/dist/search/index.js +9 -9
- package/dist/semantic-consolidation.js +4 -3
- package/dist/semantic-rule-promotion.js +3 -2
- package/dist/semantic-rule-verifier.js +3 -2
- package/dist/storage.d.ts +3 -27
- package/dist/storage.js +5 -3
- package/dist/structured-attributes.d.ts +29 -0
- package/dist/structured-attributes.js +8 -0
- package/dist/structured-attributes.js.map +1 -0
- package/dist/temporal-supersession.d.ts +1 -0
- package/dist/tier-migration.d.ts +1 -0
- package/dist/verified-recall.js +3 -2
- package/package.json +2 -2
- package/src/access-boundary.ts +2 -1
- package/src/access-cli.test.ts +40 -0
- package/src/access-cli.ts +90 -2
- package/src/access-http.ts +35 -6
- package/src/access-mcp.ts +34 -0
- package/src/access-operations.ts +45 -0
- package/src/access-service.ts +60 -0
- package/src/access-surface-catalog.test.ts +1 -1
- package/src/access-surface-catalog.ts +2 -0
- package/src/cli.ts +18 -0
- package/src/coding/architecture-card.test.ts +544 -0
- package/src/coding/architecture-card.ts +687 -0
- package/src/coding/architecture-surfaces.test.ts +579 -0
- package/src/coding/architecture-surfaces.ts +457 -0
- package/src/maintenance/namespace-maintenance-fanout.test.ts +595 -0
- package/src/maintenance/namespace-maintenance-fanout.ts +318 -0
- package/src/maintenance/namespace-planner.ts +74 -16
- package/src/operator-toolkit.ts +25 -0
- package/src/orchestrator.ts +144 -0
- package/src/recall-pipeline-stages.test.ts +269 -0
- package/src/recall-pipeline-stages.ts +294 -0
- package/src/storage.ts +6 -20
- package/src/structured-attributes.ts +39 -0
- package/dist/chunk-473JIN2U.js.map +0 -1
- package/dist/chunk-B43NZNMG.js.map +0 -1
- package/dist/chunk-BLIWOONZ.js.map +0 -1
- package/dist/chunk-CCWHPGT4.js.map +0 -1
- package/dist/chunk-FUCUR2OZ.js.map +0 -1
- package/dist/chunk-KFBOZYME.js.map +0 -1
- package/dist/chunk-NN7QYW5W.js.map +0 -1
- package/dist/chunk-RVYD6LR3.js.map +0 -1
- package/dist/chunk-S2OU5DZY.js.map +0 -1
- /package/dist/{chunk-B4XVLHJA.js.map → chunk-2N6UNJSX.js.map} +0 -0
- /package/dist/{chunk-4N3TFFPH.js.map → chunk-54PVJDO5.js.map} +0 -0
- /package/dist/{chunk-PH3HOKYW.js.map → chunk-67MMWC74.js.map} +0 -0
- /package/dist/{chunk-STOEE37X.js.map → chunk-BKAMHZYR.js.map} +0 -0
- /package/dist/{chunk-2SJCWLQD.js.map → chunk-CWE74HRG.js.map} +0 -0
- /package/dist/{chunk-VX6OBUDW.js.map → chunk-GR77Z2BM.js.map} +0 -0
- /package/dist/{chunk-CTOQEZSN.js.map → chunk-LXIEXSHU.js.map} +0 -0
- /package/dist/{chunk-GS55WYRL.js.map → chunk-PHZKALOE.js.map} +0 -0
- /package/dist/{chunk-6O6A6YUO.js.map → chunk-RGNEARXW.js.map} +0 -0
- /package/dist/{chunk-4FE2K57M.js.map → chunk-RTFAZOIR.js.map} +0 -0
- /package/dist/{chunk-UTYBJR7M.js.map → chunk-SB6CQTKP.js.map} +0 -0
- /package/dist/{chunk-2KAYTPPT.js.map → chunk-TYF3D4MS.js.map} +0 -0
- /package/dist/{chunk-WIHPNY65.js.map → chunk-UD5OKH4J.js.map} +0 -0
- /package/dist/{chunk-QANVLERJ.js.map → chunk-UPIBE2DK.js.map} +0 -0
- /package/dist/{chunk-ZPTISBQU.js.map → chunk-X5J3JZR3.js.map} +0 -0
- /package/dist/{chunk-OV4D5T7V.js.map → chunk-X7RLU5CR.js.map} +0 -0
- /package/dist/{chunk-DCWIQFNA.js.map → chunk-ZUDM75KG.js.map} +0 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespace maintenance fanout coordinator (issue #1500).
|
|
3
|
+
*
|
|
4
|
+
* Builds on the namespace-aware maintenance planner (#1499 / #1517) to fan out
|
|
5
|
+
* the REMAINING background maintenance jobs — dreams, pattern reinforcement,
|
|
6
|
+
* governance/lifecycle, contradiction scans, semantic/causal consolidation,
|
|
7
|
+
* graph decay, fact archival, tier migration — across all maintained
|
|
8
|
+
* namespaces.
|
|
9
|
+
*
|
|
10
|
+
* The planner (`namespace-planner.ts`) already handles namespace discovery
|
|
11
|
+
* (configured + catalog), per-kind gating, cycle budgeting, per-job+namespace
|
|
12
|
+
* locking, status recording, and catalog maintenance timestamps. QMD
|
|
13
|
+
* maintenance was wired through it in #1517. This module standardizes the
|
|
14
|
+
* per-job adapter pattern so every other maintenance job can be fanned out
|
|
15
|
+
* with one call, and aggregates per-namespace health for doctor/dashboard.
|
|
16
|
+
*
|
|
17
|
+
* Design contract (issue #1500 compatibility requirements):
|
|
18
|
+
* - Existing direct `runJob({ namespace })` calls continue to work unchanged.
|
|
19
|
+
* - Namespaces disabled: fanout is a no-op (the planner returns only the
|
|
20
|
+
* default namespace; jobs run exactly as before).
|
|
21
|
+
* - `maintenanceNamespaceFanoutEnabled: false`: fanout is a no-op.
|
|
22
|
+
* - A failure in one namespace must not abort other namespaces (the planner
|
|
23
|
+
* already isolates per-namespace failures).
|
|
24
|
+
* - Per-namespace locks prevent duplicate concurrent runs (planner-provided).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { NamespaceCatalog } from "../namespaces/catalog.js";
|
|
28
|
+
import type { PluginConfig } from "../types.js";
|
|
29
|
+
import {
|
|
30
|
+
planNamespaceMaintenance,
|
|
31
|
+
readNamespaceMaintenanceLastRanStatuses,
|
|
32
|
+
readNamespaceMaintenanceStatuses,
|
|
33
|
+
runNamespaceMaintenancePlan,
|
|
34
|
+
type NamespaceMaintenanceCandidate,
|
|
35
|
+
type NamespaceMaintenanceRunStatus,
|
|
36
|
+
type NamespaceMaintenanceSummary,
|
|
37
|
+
} from "./namespace-planner.js";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Standard maintenance job names fanned out across namespaces.
|
|
41
|
+
*
|
|
42
|
+
* These strings are used as the `jobName` key in:
|
|
43
|
+
* - per-namespace status files (`state/namespace-maintenance-status/<job>/...`)
|
|
44
|
+
* - catalog `lastMaintenanceAt[jobName]` timestamps
|
|
45
|
+
* - per-job+namespace lock files (`state/maintenance-locks/<job>/...`)
|
|
46
|
+
*
|
|
47
|
+
* Keeping them centralized ensures the doctor, CLI, and dashboard all report
|
|
48
|
+
* against the same keys.
|
|
49
|
+
*/
|
|
50
|
+
export const NAMESPACE_MAINTENANCE_JOBS = [
|
|
51
|
+
"qmd",
|
|
52
|
+
"pattern-reinforcement",
|
|
53
|
+
"contradiction-scan",
|
|
54
|
+
"semantic-consolidation",
|
|
55
|
+
"governance",
|
|
56
|
+
"lifecycle",
|
|
57
|
+
"graph-decay",
|
|
58
|
+
"fact-archival",
|
|
59
|
+
"tier-migration",
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
export type NamespaceMaintenanceStandardJob =
|
|
63
|
+
(typeof NAMESPACE_MAINTENANCE_JOBS)[number];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Context passed to a fanout job runner. The runner receives the per-namespace
|
|
67
|
+
* candidate (namespace name, kind, storage dir) and a storage resolver that
|
|
68
|
+
* the orchestrator wires to `storageRouter.storageFor(namespace)`.
|
|
69
|
+
*
|
|
70
|
+
* The storage resolver is intentionally typed as `unknown` here so this module
|
|
71
|
+
* does not depend on the `StorageManager` class — the orchestrator provides the
|
|
72
|
+
* typed resolver. This keeps the fanout module free of orchestrator/storage
|
|
73
|
+
* imports and unit-testable with a stub.
|
|
74
|
+
*/
|
|
75
|
+
export interface NamespaceMaintenanceFanoutRunnerContext {
|
|
76
|
+
config: PluginConfig;
|
|
77
|
+
candidate: NamespaceMaintenanceCandidate;
|
|
78
|
+
resolveStorage: (namespace: string) => Promise<unknown>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Result returned by a fanout job runner. `itemCount` is optional and
|
|
83
|
+
* domain-specific (e.g. memories scanned, edges decayed, embeddings updated).
|
|
84
|
+
*
|
|
85
|
+
* `skipped`/`skipReason`: when a runner performs no work for a namespace
|
|
86
|
+
* (e.g. the job's own cadence gate throttled it), set `skipped: true`. The
|
|
87
|
+
* planner records the namespace as `state: "skipped"` and does NOT touch
|
|
88
|
+
* the catalog's `lastMaintenanceAt`, so a throttled namespace is not
|
|
89
|
+
* falsely reported as maintained.
|
|
90
|
+
*/
|
|
91
|
+
export interface NamespaceMaintenanceFanoutRunnerResult {
|
|
92
|
+
itemCount?: number;
|
|
93
|
+
skipped?: boolean;
|
|
94
|
+
skipReason?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type NamespaceMaintenanceFanoutRunner = (
|
|
98
|
+
ctx: NamespaceMaintenanceFanoutRunnerContext,
|
|
99
|
+
) => Promise<NamespaceMaintenanceFanoutRunnerResult | undefined>;
|
|
100
|
+
|
|
101
|
+
export interface RunNamespaceMaintenanceFanoutOptions {
|
|
102
|
+
config: PluginConfig;
|
|
103
|
+
catalog?: NamespaceCatalog;
|
|
104
|
+
jobName: string;
|
|
105
|
+
runner: NamespaceMaintenanceFanoutRunner;
|
|
106
|
+
resolveStorage: (namespace: string) => Promise<unknown>;
|
|
107
|
+
/**
|
|
108
|
+
* When `false`, skip fanout entirely and return a zero-summary without
|
|
109
|
+
* touching the planner or locks. This lets callers gate fanout on
|
|
110
|
+
* per-job config (e.g. `semanticConsolidationEnabled`) without repeating
|
|
111
|
+
* the namespace-discovery logic.
|
|
112
|
+
*/
|
|
113
|
+
enabled?: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Fan out a single maintenance job across all maintained namespaces.
|
|
118
|
+
*
|
|
119
|
+
* This is the primary entry point for wiring a maintenance job through the
|
|
120
|
+
* namespace-aware planner. It:
|
|
121
|
+
* 1. Plans which namespaces should be maintained (configured + catalog,
|
|
122
|
+
* budgeted, kind-gated).
|
|
123
|
+
* 2. Runs the job per-namespace through `runNamespaceMaintenancePlan`, which
|
|
124
|
+
* acquires per-job+namespace locks, records status files, and touches the
|
|
125
|
+
* catalog's `lastMaintenanceAt`.
|
|
126
|
+
*
|
|
127
|
+
* When namespaces are disabled or fanout is off, the planner returns only the
|
|
128
|
+
* default namespace, so the job runs exactly once against default storage —
|
|
129
|
+
* preserving single-user behavior.
|
|
130
|
+
*/
|
|
131
|
+
export async function runNamespaceMaintenanceFanout(
|
|
132
|
+
options: RunNamespaceMaintenanceFanoutOptions,
|
|
133
|
+
): Promise<NamespaceMaintenanceSummary> {
|
|
134
|
+
if (options.enabled === false) {
|
|
135
|
+
return {
|
|
136
|
+
jobName: options.jobName,
|
|
137
|
+
generatedAt: new Date().toISOString(),
|
|
138
|
+
ran: 0,
|
|
139
|
+
skipped: 0,
|
|
140
|
+
failed: 0,
|
|
141
|
+
statuses: [],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const plan = await planNamespaceMaintenance(options.config, {
|
|
146
|
+
jobName: options.jobName,
|
|
147
|
+
catalog: options.catalog,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return runNamespaceMaintenancePlan(
|
|
151
|
+
options.config,
|
|
152
|
+
plan,
|
|
153
|
+
async (candidate) => {
|
|
154
|
+
return options.runner({
|
|
155
|
+
config: options.config,
|
|
156
|
+
candidate,
|
|
157
|
+
resolveStorage: options.resolveStorage,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
options.catalog,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Health summary for doctor / dashboard / CLI
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
export interface NamespaceMaintenanceJobHealth {
|
|
169
|
+
jobName: string;
|
|
170
|
+
ran: number;
|
|
171
|
+
skipped: number;
|
|
172
|
+
failed: number;
|
|
173
|
+
lastRunAt: string | null;
|
|
174
|
+
namespaces: NamespaceMaintenanceRunStatus[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface NamespaceMaintenanceHealthSummary {
|
|
178
|
+
generatedAt: string;
|
|
179
|
+
fanoutEnabled: boolean;
|
|
180
|
+
namespacesEnabled: boolean;
|
|
181
|
+
maxNamespacesPerCycle: number;
|
|
182
|
+
jobs: NamespaceMaintenanceJobHealth[];
|
|
183
|
+
totalRan: number;
|
|
184
|
+
totalSkipped: number;
|
|
185
|
+
totalFailed: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Read all per-namespace maintenance status files and aggregate them into a
|
|
190
|
+
* health summary suitable for `remnic doctor` and the admin dashboard.
|
|
191
|
+
*
|
|
192
|
+
* This is a pure read operation — it never runs maintenance or acquires locks.
|
|
193
|
+
*/
|
|
194
|
+
export async function summarizeNamespaceMaintenanceHealth(
|
|
195
|
+
config: PluginConfig,
|
|
196
|
+
): Promise<NamespaceMaintenanceHealthSummary> {
|
|
197
|
+
const generatedAt = new Date().toISOString();
|
|
198
|
+
const statuses = await readNamespaceMaintenanceStatuses(config);
|
|
199
|
+
// Merge last-successful-run records so lastRunAt reflects the most recent
|
|
200
|
+
// successful maintenance, not just the latest (possibly skipped/failed)
|
|
201
|
+
// outcome. The latest status file is overwritten on every run; without
|
|
202
|
+
// this merge a namespace that ran then got budget-skipped shows ran=0 /
|
|
203
|
+
// lastRunAt=null (review #1622: preserve last successful run).
|
|
204
|
+
const lastRanStatuses = await readNamespaceMaintenanceLastRanStatuses(config);
|
|
205
|
+
const lastRanByJobNs = new Map<string, NamespaceMaintenanceRunStatus>();
|
|
206
|
+
for (const lr of lastRanStatuses) {
|
|
207
|
+
lastRanByJobNs.set(`${lr.jobName}\u0000${lr.namespace}`, lr);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const byJob = new Map<string, NamespaceMaintenanceRunStatus[]>();
|
|
211
|
+
for (const status of statuses) {
|
|
212
|
+
const bucket = byJob.get(status.jobName);
|
|
213
|
+
if (bucket) {
|
|
214
|
+
bucket.push(status);
|
|
215
|
+
} else {
|
|
216
|
+
byJob.set(status.jobName, [status]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const knownJobs = new Set<string>(NAMESPACE_MAINTENANCE_JOBS);
|
|
221
|
+
// Include any job names seen in status files that are not in the standard
|
|
222
|
+
// set (e.g. custom jobs registered by the orchestrator) so the doctor does
|
|
223
|
+
// not silently hide them.
|
|
224
|
+
for (const jobName of byJob.keys()) {
|
|
225
|
+
knownJobs.add(jobName);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const jobs: NamespaceMaintenanceJobHealth[] = [];
|
|
229
|
+
let totalRan = 0;
|
|
230
|
+
let totalSkipped = 0;
|
|
231
|
+
let totalFailed = 0;
|
|
232
|
+
|
|
233
|
+
for (const jobName of [...knownJobs].sort()) {
|
|
234
|
+
const jobStatuses = byJob.get(jobName) ?? [];
|
|
235
|
+
const ran = jobStatuses.filter((s) => s.state === "ran").length;
|
|
236
|
+
const skipped = jobStatuses.filter((s) => s.state === "skipped").length;
|
|
237
|
+
const failed = jobStatuses.filter((s) => s.state === "failed").length;
|
|
238
|
+
// Consider BOTH the latest outcome and the last successful run so a
|
|
239
|
+
// namespace that ran then was later skipped still reports lastRunAt.
|
|
240
|
+
const candidates: string[] = [];
|
|
241
|
+
for (const st of jobStatuses) {
|
|
242
|
+
if (typeof st.completedAt === "string") candidates.push(st.completedAt);
|
|
243
|
+
const lr = lastRanByJobNs.get(`${jobName}\u0000${st.namespace}`);
|
|
244
|
+
if (lr && typeof lr.completedAt === "string") candidates.push(lr.completedAt);
|
|
245
|
+
}
|
|
246
|
+
const lastRunAt = candidates.sort().at(-1) ?? null;
|
|
247
|
+
|
|
248
|
+
totalRan += ran;
|
|
249
|
+
totalSkipped += skipped;
|
|
250
|
+
totalFailed += failed;
|
|
251
|
+
|
|
252
|
+
jobs.push({
|
|
253
|
+
jobName,
|
|
254
|
+
ran,
|
|
255
|
+
skipped,
|
|
256
|
+
failed,
|
|
257
|
+
lastRunAt,
|
|
258
|
+
namespaces: jobStatuses,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
generatedAt,
|
|
264
|
+
fanoutEnabled: config.maintenanceNamespaceFanoutEnabled !== false,
|
|
265
|
+
namespacesEnabled: config.namespacesEnabled,
|
|
266
|
+
maxNamespacesPerCycle: config.maintenanceMaxNamespacesPerCycle,
|
|
267
|
+
jobs,
|
|
268
|
+
totalRan,
|
|
269
|
+
totalSkipped,
|
|
270
|
+
totalFailed,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Format the health summary as human-readable text for the CLI.
|
|
276
|
+
*/
|
|
277
|
+
export function formatNamespaceMaintenanceHealthText(
|
|
278
|
+
summary: NamespaceMaintenanceHealthSummary,
|
|
279
|
+
): string {
|
|
280
|
+
const lines: string[] = [
|
|
281
|
+
"=== Namespace Maintenance ===",
|
|
282
|
+
"",
|
|
283
|
+
` fanout: ${summary.fanoutEnabled ? "enabled" : "disabled"}`,
|
|
284
|
+
` namespaces: ${summary.namespacesEnabled ? "enabled" : "disabled"}`,
|
|
285
|
+
` max/cycle: ${summary.maxNamespacesPerCycle}`,
|
|
286
|
+
` total ran: ${summary.totalRan}`,
|
|
287
|
+
` total skipped: ${summary.totalSkipped}`,
|
|
288
|
+
` total failed: ${summary.totalFailed}`,
|
|
289
|
+
"",
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
if (summary.totalRan === 0 && summary.totalSkipped === 0 && summary.totalFailed === 0) {
|
|
293
|
+
lines.push(" (no maintenance status recorded yet)");
|
|
294
|
+
return lines.join("\n");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
lines.push(" Per-job breakdown:");
|
|
298
|
+
for (const job of summary.jobs) {
|
|
299
|
+
const parts = [
|
|
300
|
+
`ran=${job.ran}`,
|
|
301
|
+
`skipped=${job.skipped}`,
|
|
302
|
+
`failed=${job.failed}`,
|
|
303
|
+
];
|
|
304
|
+
if (job.lastRunAt) {
|
|
305
|
+
parts.push(`last=${job.lastRunAt}`);
|
|
306
|
+
}
|
|
307
|
+
lines.push(` ${job.jobName}: ${parts.join(", ")}`);
|
|
308
|
+
// Surface failed namespaces with their reason/error so operators know
|
|
309
|
+
// WHAT to fix without rerunning with --json (review #1622).
|
|
310
|
+
const failedNs = job.namespaces.filter((n) => n.state === "failed");
|
|
311
|
+
for (const ns of failedNs) {
|
|
312
|
+
const detail = ns.error ? `: ${ns.error}` : "";
|
|
313
|
+
lines.push(` ! ${ns.namespace} failed (reason=${ns.reason ?? "unknown"})${detail}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return lines.join("\n");
|
|
318
|
+
}
|
|
@@ -213,7 +213,14 @@ export async function planNamespaceMaintenance(
|
|
|
213
213
|
options: NamespaceMaintenancePlannerOptions
|
|
214
214
|
): Promise<NamespaceMaintenancePlan> {
|
|
215
215
|
const generatedAt = (options.now ?? new Date()).toISOString();
|
|
216
|
-
|
|
216
|
+
// When namespaces are disabled, storageFor() collapses every namespace name
|
|
217
|
+
// to config.memoryDir. Seeding all configured namespaces (default + shared +
|
|
218
|
+
// policies) would make a mutating maintenance job process the SAME corpus
|
|
219
|
+
// once per configured name. The #1500 contract is "namespaces disabled:
|
|
220
|
+
// maintain the current default storage only," so collapse to the default.
|
|
221
|
+
const configured = config.namespacesEnabled
|
|
222
|
+
? configuredNamespaces(config)
|
|
223
|
+
: [config.defaultNamespace.trim()].filter(Boolean);
|
|
217
224
|
const byNamespace = new Map<string, NamespaceMaintenanceCandidate>();
|
|
218
225
|
const skipped: NamespaceMaintenanceSkippedNamespace[] = [];
|
|
219
226
|
|
|
@@ -640,10 +647,44 @@ export async function readNamespaceMaintenanceStatuses(config: PluginConfig): Pr
|
|
|
640
647
|
});
|
|
641
648
|
}
|
|
642
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Read the last SUCCESSFUL run status per job+namespace (the
|
|
652
|
+
* `<namespace>.last-ran.json` files written only when state === "ran").
|
|
653
|
+
*
|
|
654
|
+
* The latest status file (`<namespace>.json`) is overwritten on every run,
|
|
655
|
+
* so after a successful run followed by a skip (budget/lock/cadence) the
|
|
656
|
+
* latest file shows "skipped" and the prior success is invisible. Merging
|
|
657
|
+
* these last-ran records into the health summary lets `lastRunAt` and run
|
|
658
|
+
* history reflect the most recent successful maintenance (review #1622).
|
|
659
|
+
*/
|
|
660
|
+
export async function readNamespaceMaintenanceLastRanStatuses(
|
|
661
|
+
config: PluginConfig,
|
|
662
|
+
): Promise<NamespaceMaintenanceRunStatus[]> {
|
|
663
|
+
return (await readLastRanStatusFiles(config)).sort((a, b) => {
|
|
664
|
+
const byJob = a.jobName.localeCompare(b.jobName);
|
|
665
|
+
if (byJob !== 0) return byJob;
|
|
666
|
+
return a.namespace.localeCompare(b.namespace);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export type NamespaceMaintenancePlanRunnerResult = {
|
|
671
|
+
itemCount?: number;
|
|
672
|
+
/**
|
|
673
|
+
* When `true`, the runner performed NO work for this namespace (e.g. the
|
|
674
|
+
* job's own cadence gate throttled it). The planner records the namespace
|
|
675
|
+
* as `state: "skipped"` with the given reason and does NOT touch the
|
|
676
|
+
* catalog's `lastMaintenanceAt`, so a throttled namespace is not falsely
|
|
677
|
+
* reported as maintained. Without this signal a runner that resolves
|
|
678
|
+
* without throwing is always recorded as `state: "ran"`.
|
|
679
|
+
*/
|
|
680
|
+
skipped?: boolean;
|
|
681
|
+
skipReason?: string;
|
|
682
|
+
} | undefined;
|
|
683
|
+
|
|
643
684
|
export async function runNamespaceMaintenancePlan(
|
|
644
685
|
config: PluginConfig,
|
|
645
686
|
plan: NamespaceMaintenancePlan,
|
|
646
|
-
runner: (candidate: NamespaceMaintenanceCandidate) => Promise<
|
|
687
|
+
runner: (candidate: NamespaceMaintenanceCandidate) => Promise<NamespaceMaintenancePlanRunnerResult>,
|
|
647
688
|
catalog?: NamespaceCatalog
|
|
648
689
|
): Promise<NamespaceMaintenanceSummary> {
|
|
649
690
|
const statuses: NamespaceMaintenanceRunStatus[] = [];
|
|
@@ -683,20 +724,37 @@ export async function runNamespaceMaintenancePlan(
|
|
|
683
724
|
try {
|
|
684
725
|
const result = await withNamespaceMaintenanceLockHeartbeat(config, lock, () => runner(candidate));
|
|
685
726
|
const completedAt = new Date().toISOString();
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
727
|
+
if (result?.skipped) {
|
|
728
|
+
// The runner performed no work (e.g. the job's own cadence gate
|
|
729
|
+
// throttled this namespace). Record skipped WITHOUT touching the
|
|
730
|
+
// catalog's lastMaintenanceAt so a throttled namespace is not
|
|
731
|
+
// falsely reported as maintained.
|
|
732
|
+
const status: NamespaceMaintenanceRunStatus = {
|
|
733
|
+
namespace: candidate.namespace,
|
|
734
|
+
jobName: plan.jobName,
|
|
735
|
+
state: "skipped",
|
|
736
|
+
reason: (result.skipReason ?? "throttled") as NamespaceMaintenanceSkipReason,
|
|
737
|
+
startedAt,
|
|
738
|
+
completedAt,
|
|
739
|
+
};
|
|
740
|
+
statuses.push(status);
|
|
741
|
+
await recordNamespaceMaintenanceStatusSafely(config, status);
|
|
742
|
+
} else {
|
|
743
|
+
const status: NamespaceMaintenanceRunStatus = {
|
|
744
|
+
namespace: candidate.namespace,
|
|
745
|
+
jobName: plan.jobName,
|
|
746
|
+
state: "ran",
|
|
747
|
+
startedAt,
|
|
748
|
+
completedAt,
|
|
749
|
+
itemCount: result?.itemCount,
|
|
750
|
+
};
|
|
751
|
+
statuses.push(status);
|
|
752
|
+
await recordNamespaceMaintenanceStatusSafely(config, status);
|
|
753
|
+
try {
|
|
754
|
+
await catalog?.markMaintenance(candidate.namespace, plan.jobName, new Date(completedAt));
|
|
755
|
+
} catch {
|
|
756
|
+
// Catalog maintenance touches are best-effort status metadata.
|
|
757
|
+
}
|
|
700
758
|
}
|
|
701
759
|
} catch (error) {
|
|
702
760
|
const completedAt = new Date().toISOString();
|
package/src/operator-toolkit.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "./native-knowledge.js";
|
|
13
13
|
import { StorageManager } from "./storage.js";
|
|
14
14
|
import { listNamespaces } from "./namespaces/migrate.js";
|
|
15
|
+
import { summarizeNamespaceMaintenanceHealth } from "./maintenance/namespace-maintenance-fanout.js";
|
|
15
16
|
import {
|
|
16
17
|
createEvalBaselineSnapshot,
|
|
17
18
|
getEvalHarnessStatus,
|
|
@@ -1301,6 +1302,30 @@ export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise
|
|
|
1301
1302
|
// state and is never an error.
|
|
1302
1303
|
checks.push(await summarizeObservationThroughput(config.memoryDir));
|
|
1303
1304
|
|
|
1305
|
+
// Namespace maintenance fanout status (issue #1500).
|
|
1306
|
+
// Reports per-job+namespace maintenance outcomes (ran/skipped/failed) so
|
|
1307
|
+
// operators can verify that dynamic project/team namespaces are being
|
|
1308
|
+
// maintained. Informational: never errors on its own — a namespace that has
|
|
1309
|
+
// never been maintained is the expected cold-install state.
|
|
1310
|
+
const namespaceMaintenanceHealth = await summarizeNamespaceMaintenanceHealth(config);
|
|
1311
|
+
checks.push({
|
|
1312
|
+
key: "namespace_maintenance",
|
|
1313
|
+
status: namespaceMaintenanceHealth.totalFailed > 0 ? "warn" : "ok",
|
|
1314
|
+
summary:
|
|
1315
|
+
namespaceMaintenanceHealth.totalRan === 0 && namespaceMaintenanceHealth.totalFailed === 0
|
|
1316
|
+
? "No namespace maintenance recorded yet (expected for a fresh install)."
|
|
1317
|
+
: `${namespaceMaintenanceHealth.totalRan} namespace maintenance run(s)` +
|
|
1318
|
+
(namespaceMaintenanceHealth.totalFailed > 0
|
|
1319
|
+
? `, ${namespaceMaintenanceHealth.totalFailed} failure(s) across ${namespaceMaintenanceHealth.jobs.filter((j) => j.failed > 0).length} job(s)`
|
|
1320
|
+
: "") +
|
|
1321
|
+
".",
|
|
1322
|
+
remediation:
|
|
1323
|
+
namespaceMaintenanceHealth.totalFailed > 0
|
|
1324
|
+
? "Run `remnic namespaces maintenance` for per-namespace details."
|
|
1325
|
+
: undefined,
|
|
1326
|
+
details: namespaceMaintenanceHealth,
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1304
1329
|
const summary = checks.reduce(
|
|
1305
1330
|
(acc, check) => {
|
|
1306
1331
|
acc[check.status] += 1;
|
package/src/orchestrator.ts
CHANGED
|
@@ -318,7 +318,14 @@ import {
|
|
|
318
318
|
runNamespaceMaintenanceBatchPlan,
|
|
319
319
|
type NamespaceMaintenancePlan,
|
|
320
320
|
type NamespaceMaintenanceSkipReason,
|
|
321
|
+
type NamespaceMaintenanceSummary,
|
|
321
322
|
} from "./maintenance/namespace-planner.js";
|
|
323
|
+
import {
|
|
324
|
+
runNamespaceMaintenanceFanout,
|
|
325
|
+
summarizeNamespaceMaintenanceHealth,
|
|
326
|
+
type NamespaceMaintenanceFanoutRunnerContext,
|
|
327
|
+
type NamespaceMaintenanceHealthSummary,
|
|
328
|
+
} from "./maintenance/namespace-maintenance-fanout.js";
|
|
322
329
|
import {
|
|
323
330
|
namespaceIdentityFromToken,
|
|
324
331
|
namespaceIdentityToken,
|
|
@@ -2483,6 +2490,42 @@ export class Orchestrator {
|
|
|
2483
2490
|
return plan.namespaces.map((candidate) => candidate.namespace);
|
|
2484
2491
|
}
|
|
2485
2492
|
|
|
2493
|
+
/**
|
|
2494
|
+
* Fan out a maintenance job across all maintained namespaces (issue #1500).
|
|
2495
|
+
*
|
|
2496
|
+
* Delegates to the namespace-maintenance-fanout coordinator, which plans
|
|
2497
|
+
* namespace discovery (configured + catalog), applies the cycle budget,
|
|
2498
|
+
* acquires per-job+namespace locks, records status files, and touches the
|
|
2499
|
+
* catalog's `lastMaintenanceAt`. The runner receives a per-namespace
|
|
2500
|
+
* candidate and a storage resolver wired to `this.getStorage(namespace)`.
|
|
2501
|
+
*
|
|
2502
|
+
* When namespaces are disabled the planner returns only the default
|
|
2503
|
+
* namespace, so the job runs exactly once — preserving single-user behavior.
|
|
2504
|
+
*/
|
|
2505
|
+
async runNamespaceMaintenanceFanoutForJob(
|
|
2506
|
+
jobName: string,
|
|
2507
|
+
runner: (ctx: NamespaceMaintenanceFanoutRunnerContext) => Promise<{ itemCount?: number } | undefined>,
|
|
2508
|
+
options: { enabled?: boolean } = {},
|
|
2509
|
+
): Promise<NamespaceMaintenanceSummary> {
|
|
2510
|
+
return runNamespaceMaintenanceFanout({
|
|
2511
|
+
config: this.config,
|
|
2512
|
+
catalog: this.namespaceCatalog,
|
|
2513
|
+
jobName,
|
|
2514
|
+
runner,
|
|
2515
|
+
resolveStorage: (namespace) => this.getStorage(namespace),
|
|
2516
|
+
enabled: options.enabled,
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
/**
|
|
2521
|
+
* Read-only namespace maintenance health summary for doctor / dashboard /
|
|
2522
|
+
* CLI (issue #1500). Aggregates all per-namespace maintenance status files
|
|
2523
|
+
* into one report without running any maintenance.
|
|
2524
|
+
*/
|
|
2525
|
+
async readNamespaceMaintenanceHealth(): Promise<NamespaceMaintenanceHealthSummary> {
|
|
2526
|
+
return summarizeNamespaceMaintenanceHealth(this.config);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2486
2529
|
private buildConfiguredQmdSearchOptions(
|
|
2487
2530
|
queryText: string,
|
|
2488
2531
|
): SearchQueryOptions | undefined {
|
|
@@ -3998,6 +4041,107 @@ export class Orchestrator {
|
|
|
3998
4041
|
return { ran: true, result, namespace: cadenceKey };
|
|
3999
4042
|
}
|
|
4000
4043
|
|
|
4044
|
+
/**
|
|
4045
|
+
* Fan out pattern reinforcement across all maintained namespaces (issue #1500).
|
|
4046
|
+
* Delegates per-namespace execution to {@link runPatternReinforcement} while
|
|
4047
|
+
* the planner handles discovery, budgeting, locking, and status recording.
|
|
4048
|
+
* When namespaces are disabled, runs once against default storage.
|
|
4049
|
+
*/
|
|
4050
|
+
async runPatternReinforcementFanout(options: {
|
|
4051
|
+
force?: boolean;
|
|
4052
|
+
} = {}): Promise<NamespaceMaintenanceSummary> {
|
|
4053
|
+
return this.runNamespaceMaintenanceFanoutForJob(
|
|
4054
|
+
"pattern-reinforcement",
|
|
4055
|
+
async (ctx) => {
|
|
4056
|
+
const result = await this.runPatternReinforcement({
|
|
4057
|
+
namespace: ctx.candidate.namespace,
|
|
4058
|
+
force: options.force,
|
|
4059
|
+
});
|
|
4060
|
+
// runPatternReinforcement has its own per-namespace cadence gate
|
|
4061
|
+
// (lastPatternReinforcementAtByNs). When it throttles (ran:false),
|
|
4062
|
+
// signal skip so the planner records state:"skipped" and does NOT
|
|
4063
|
+
// touch lastMaintenanceAt — otherwise a throttled namespace would
|
|
4064
|
+
// look maintained while pattern reinforcement never ran (#1500
|
|
4065
|
+
// review: cadence-skip accuracy).
|
|
4066
|
+
if (!result.ran) {
|
|
4067
|
+
return {
|
|
4068
|
+
skipped: true,
|
|
4069
|
+
skipReason: result.skippedReason ?? "throttled",
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
return result.result
|
|
4073
|
+
? { itemCount: result.result.clustersFound }
|
|
4074
|
+
: { itemCount: 0 };
|
|
4075
|
+
},
|
|
4076
|
+
{ enabled: this.config.patternReinforcementEnabled },
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
/**
|
|
4081
|
+
* Fan out lifecycle/governance policy across all maintained namespaces
|
|
4082
|
+
* (issue #1500). Each namespace gets its own lifecycle pass against its
|
|
4083
|
+
* namespace-scoped storage. When namespaces are disabled, runs once against
|
|
4084
|
+
* default storage.
|
|
4085
|
+
*/
|
|
4086
|
+
async runLifecyclePolicyFanout(): Promise<NamespaceMaintenanceSummary> {
|
|
4087
|
+
return this.runNamespaceMaintenanceFanoutForJob(
|
|
4088
|
+
"lifecycle",
|
|
4089
|
+
async (ctx) => {
|
|
4090
|
+
const storage = await this.getStorage(ctx.candidate.namespace);
|
|
4091
|
+
const corpus = await storage.readAllMemories();
|
|
4092
|
+
const assessed = await this.runLifecyclePolicyPass(corpus, storage);
|
|
4093
|
+
return { itemCount: assessed };
|
|
4094
|
+
},
|
|
4095
|
+
{ enabled: this.config.lifecyclePolicyEnabled },
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
/**
|
|
4100
|
+
* Fan out semantic consolidation across all maintained namespaces (issue #1500).
|
|
4101
|
+
* Each namespace gets its own consolidation pass against its namespace-scoped
|
|
4102
|
+
* storage. When namespaces are disabled, runs once against default storage.
|
|
4103
|
+
*/
|
|
4104
|
+
async runSemanticConsolidationFanout(options: {
|
|
4105
|
+
dryRun?: boolean;
|
|
4106
|
+
} = {}): Promise<NamespaceMaintenanceSummary> {
|
|
4107
|
+
return this.runNamespaceMaintenanceFanoutForJob(
|
|
4108
|
+
"semantic-consolidation",
|
|
4109
|
+
async (ctx) => {
|
|
4110
|
+
const storage = await this.getStorage(ctx.candidate.namespace);
|
|
4111
|
+
const result = await this.runSemanticConsolidation({
|
|
4112
|
+
dryRun: options.dryRun,
|
|
4113
|
+
thresholdOverride: undefined,
|
|
4114
|
+
force: true,
|
|
4115
|
+
storage,
|
|
4116
|
+
});
|
|
4117
|
+
return { itemCount: result.clustersFound };
|
|
4118
|
+
},
|
|
4119
|
+
{ enabled: this.config.semanticConsolidationEnabled },
|
|
4120
|
+
);
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
/**
|
|
4124
|
+
* Fan out deep-sleep governance across all maintained namespaces (issue #1500).
|
|
4125
|
+
* Each namespace gets its own governance scan against its namespace-scoped
|
|
4126
|
+
* storage. When namespaces are disabled, runs once against default storage.
|
|
4127
|
+
*/
|
|
4128
|
+
async runDeepSleepGovernanceFanout(options: {
|
|
4129
|
+
dryRun?: boolean;
|
|
4130
|
+
} = {}): Promise<NamespaceMaintenanceSummary> {
|
|
4131
|
+
return this.runNamespaceMaintenanceFanoutForJob(
|
|
4132
|
+
"governance",
|
|
4133
|
+
async (ctx) => {
|
|
4134
|
+
const storage = await this.getStorage(ctx.candidate.namespace);
|
|
4135
|
+
const result = await this.runDeepSleepGovernanceNow({
|
|
4136
|
+
dryRun: options.dryRun,
|
|
4137
|
+
storage,
|
|
4138
|
+
});
|
|
4139
|
+
return { itemCount: result.scannedMemories };
|
|
4140
|
+
},
|
|
4141
|
+
{ enabled: this.config.dreamsPhases.deepSleep.enabled },
|
|
4142
|
+
);
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4001
4145
|
private async autoRegisterGraphEdgeDecayCron(): Promise<void> {
|
|
4002
4146
|
const home = resolveHomeDir();
|
|
4003
4147
|
const jobsPath = path.join(home, ".openclaw", "cron", "jobs.json");
|