openclaw-hybrid-memory 2026.5.310 → 2026.5.311
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/api/plugin-runtime.ts +2 -0
- package/dist/api/plugin-runtime.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/setup/hybrid-memory-reload-coordinator.js +12 -10
- package/dist/setup/hybrid-memory-reload-coordinator.js.map +1 -1
- package/dist/setup/register-plugin.js +25 -21
- package/dist/setup/register-plugin.js.map +1 -1
- package/dist/setup/reregister-policy.js +2 -2
- package/dist/setup/reregister-policy.js.map +1 -1
- package/index.ts +2 -2
- package/npm-shrinkwrap.json +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/setup/hybrid-memory-reload-coordinator.ts +26 -0
- package/setup/register-plugin.ts +62 -32
- package/setup/reregister-policy.ts +6 -4
package/api/plugin-runtime.ts
CHANGED
|
@@ -49,6 +49,8 @@ import type { ToolRegistrationHandle } from "../setup/register-tools.js";
|
|
|
49
49
|
export interface PluginRuntime {
|
|
50
50
|
// --- Config & resolved paths ---
|
|
51
51
|
cfg: HybridMemoryConfig;
|
|
52
|
+
/** Parse-time config snapshot for hot-reload reuse checks (bootstrap may mutate `cfg`). */
|
|
53
|
+
parsedCfgSnapshot: HybridMemoryConfig;
|
|
52
54
|
resolvedLancePath: string;
|
|
53
55
|
resolvedSqlitePath: string;
|
|
54
56
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-runtime.js","names":[],"sources":["../../api/plugin-runtime.ts"],"sourcesContent":["/**\n * PluginRuntime: instance-scoped container for all mutable plugin state.\n *\n * Replaces module-level `let` variables in index.ts to enable:\n * - Independent plugin instances in the same process (testability)\n * - Clearer ownership of runtime state\n * - Explicit context passing to tools and lifecycle hooks\n *\n * A single module-level `const runtimeRef: { value: PluginRuntime | null }` holds the\n * active instance. Closures (tools, timers, event handlers) capture `runtimeRef` by\n * reference; when register() creates a fresh PluginRuntime after a SIGUSR1 reload the\n * closures automatically see the new instance through `runtimeRef.value`.\n */\n\nimport type OpenAI from \"openai\";\nimport type { AgentHealthStore } from \"../backends/agent-health-store.js\";\nimport type { ApitapStore } from \"../backends/apitap-store.js\";\nimport type { AuditStore } from \"../backends/audit-store.js\";\nimport type { CostTracker } from \"../backends/cost-tracker.js\";\nimport type { CredentialsDB } from \"../backends/credentials-db.js\";\nimport type { CrystallizationStore } from \"../backends/crystallization-store.js\";\nimport type { EdictStore } from \"../backends/edict-store.js\";\nimport type { EventBus } from \"../backends/event-bus.js\";\nimport type { EventLog } from \"../backends/event-log.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { IdentityReflectionStore } from \"../backends/identity-reflection-store.js\";\nimport type { IssueStore } from \"../backends/issue-store.js\";\nimport type { LearningsDB } from \"../backends/learnings-db.js\";\nimport type { NarrativesDB } from \"../backends/narratives-db.js\";\nimport type { PersonaStateStore } from \"../backends/persona-state-store.js\";\nimport type { ProposalsDB } from \"../backends/proposals-db.js\";\nimport type { ToolProposalStore } from \"../backends/tool-proposal-store.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { WriteAheadLog } from \"../backends/wal.js\";\nimport type { WorkflowStore } from \"../backends/workflow-store.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport type { PendingLLMWarnings } from \"../services/chat.js\";\nimport type { VariantGenerationQueue } from \"../services/contextual-variants.js\";\nimport type { EmbeddingRegistry } from \"../services/embedding-registry.js\";\nimport type { EmbeddingProvider } from \"../services/embeddings.js\";\nimport type { ProvenanceService } from \"../services/provenance.js\";\nimport type { PythonBridge } from \"../services/python-bridge.js\";\nimport type { AliasDB } from \"../services/retrieval-aliases.js\";\nimport type { VerificationStore } from \"../services/verification-store.js\";\nimport type { LifecycleHooksHandle } from \"../setup/register-hooks.js\";\nimport type { ToolRegistrationHandle } from \"../setup/register-tools.js\";\n\n/** All mutable per-instance state for the memory-hybrid plugin. */\nexport interface PluginRuntime {\n // --- Config & resolved paths ---\n cfg: HybridMemoryConfig;\n resolvedLancePath: string;\n resolvedSqlitePath: string;\n\n // --- Core backends (always present after init) ---\n factsDb: FactsDB;\n edictStore: EdictStore;\n vectorDb: VectorDB;\n embeddings: EmbeddingProvider;\n embeddingRegistry: EmbeddingRegistry;\n openai: OpenAI;\n\n // --- Optional backends (null when feature disabled) ---\n credentialsDb: CredentialsDB | null;\n wal: WriteAheadLog | null;\n proposalsDb: ProposalsDB | null;\n identityReflectionStore: IdentityReflectionStore | null;\n personaStateStore: PersonaStateStore | null;\n eventLog: EventLog | null;\n narrativesDb: NarrativesDB | null;\n aliasDb: AliasDB | null;\n eventBus: EventBus | null;\n costTracker: CostTracker | null;\n issueStore: IssueStore | null;\n workflowStore: WorkflowStore | null;\n crystallizationStore: CrystallizationStore | null;\n toolProposalStore: ToolProposalStore | null;\n provenanceService: ProvenanceService | null;\n verificationStore: VerificationStore | null;\n apitapStore: ApitapStore | null;\n pythonBridge: PythonBridge | null;\n variantQueue: VariantGenerationQueue | null;\n /** Staged intake buffer for errors, lessons, and feature requests (Issue #617). */\n learningsDb: LearningsDB | null;\n /** Cross-agent audit log (Issue #790). */\n auditStore: AuditStore | null;\n agentHealthStore: AgentHealthStore | null;\n\n // --- Lifecycle state ---\n /** Handle returned by registerLifecycleHooks; set after hooks are registered, null until then. */\n lifecycleHooksHandle: LifecycleHooksHandle | null;\n /** Handle returned by registerTools; set after tool registration, null until then. */\n toolRegistrationHandle: ToolRegistrationHandle | null;\n /**\n * Resolves when async bootstrap work from `initializeDatabases` finishes (embedding/vault checks, etc.).\n * Used to sequence CLI teardown so we do not close DBs while init I/O is still running (Issue #1039).\n */\n bootstrapAsyncInit: Promise<void>;\n /** Tracks whether bootstrapAsyncInit has settled (used by re-register reuse policy). */\n bootstrapSettledRef?: { value: boolean };\n /** Last bootstrap health snapshot from initializeDatabases() for reuse-databases policy. */\n bootstrapHealth?: {\n embeddingsOk: boolean;\n credentialsVaultOk: boolean;\n lastCheckTime: number;\n };\n pendingLLMWarnings: PendingLLMWarnings;\n\n // --- Mutable refs (objects so that closures can share mutations) ---\n /** Detected agent for current session; updated on before_agent_start. */\n currentAgentIdRef: { value: string | null };\n /** Set to true once the restart-pending flag has been cleared this session. */\n restartPendingClearedRef: { value: boolean };\n /** Count of in-flight recall operations (degradation / back-pressure). */\n recallInFlightRef: { value: number };\n /** Last user prompt used for interactive auto-recall (issue #957 post-compaction reinjection). */\n lastAutoRecallPromptRef: { value: string | null };\n /** Last progressive index fact IDs (1-based position → fact id). */\n lastProgressiveIndexIds: string[];\n\n // --- Timer refs (objects so they can be passed by reference to plugin-service) ---\n timers: {\n pruneTimer: { value: ReturnType<typeof setInterval> | null };\n classifyTimer: { value: ReturnType<typeof setInterval> | null };\n classifyStartupTimeout: { value: ReturnType<typeof setTimeout> | null };\n proposalsPruneTimer: { value: ReturnType<typeof setInterval> | null };\n languageKeywordsTimer: { value: ReturnType<typeof setInterval> | null };\n languageKeywordsStartupTimeout: { value: ReturnType<typeof setTimeout> | null };\n postUpgradeTimeout: { value: ReturnType<typeof setTimeout> | null };\n passiveObserverTimer: { value: ReturnType<typeof setInterval> | null };\n /** Issue #631: Stale-run watchdog timer for autonomous task queue self-healing. */\n watchdogTimer: { value: ReturnType<typeof setInterval> | null };\n };\n}\n\n/** Create a fresh, empty timers bag for a new PluginRuntime instance. */\nexport function createTimers(): PluginRuntime[\"timers\"] {\n return {\n pruneTimer: { value: null },\n classifyTimer: { value: null },\n classifyStartupTimeout: { value: null },\n proposalsPruneTimer: { value: null },\n languageKeywordsTimer: { value: null },\n languageKeywordsStartupTimeout: { value: null },\n postUpgradeTimeout: { value: null },\n passiveObserverTimer: { value: null },\n watchdogTimer: { value: null },\n };\n}\n\n/**\n * Clear all runtime timer handles and reset refs.\n * Shared by CLI teardown and hot-reload cleanup paths.\n */\nexport function clearRuntimeTimers(timers: PluginRuntime[\"timers\"]): void {\n if (timers.pruneTimer.value) {\n clearInterval(timers.pruneTimer.value);\n timers.pruneTimer.value = null;\n }\n if (timers.classifyTimer.value) {\n clearInterval(timers.classifyTimer.value);\n timers.classifyTimer.value = null;\n }\n if (timers.classifyStartupTimeout.value) {\n clearTimeout(timers.classifyStartupTimeout.value);\n timers.classifyStartupTimeout.value = null;\n }\n if (timers.proposalsPruneTimer.value) {\n clearInterval(timers.proposalsPruneTimer.value);\n timers.proposalsPruneTimer.value = null;\n }\n if (timers.languageKeywordsTimer.value) {\n clearInterval(timers.languageKeywordsTimer.value);\n timers.languageKeywordsTimer.value = null;\n }\n if (timers.languageKeywordsStartupTimeout.value) {\n clearTimeout(timers.languageKeywordsStartupTimeout.value);\n timers.languageKeywordsStartupTimeout.value = null;\n }\n if (timers.postUpgradeTimeout.value) {\n clearTimeout(timers.postUpgradeTimeout.value);\n timers.postUpgradeTimeout.value = null;\n }\n if (timers.passiveObserverTimer.value) {\n clearInterval(timers.passiveObserverTimer.value);\n timers.passiveObserverTimer.value = null;\n }\n if (timers.watchdogTimer.value) {\n clearInterval(timers.watchdogTimer.value);\n timers.watchdogTimer.value = null;\n }\n}\n"],"mappings":";;AAwIA,SAAgB,eAAwC;CACtD,OAAO;EACL,YAAY,EAAE,OAAO,MAAM;EAC3B,eAAe,EAAE,OAAO,MAAM;EAC9B,wBAAwB,EAAE,OAAO,MAAM;EACvC,qBAAqB,EAAE,OAAO,MAAM;EACpC,uBAAuB,EAAE,OAAO,MAAM;EACtC,gCAAgC,EAAE,OAAO,MAAM;EAC/C,oBAAoB,EAAE,OAAO,MAAM;EACnC,sBAAsB,EAAE,OAAO,MAAM;EACrC,eAAe,EAAE,OAAO,MAAM;EAC/B;;;;;;AAOH,SAAgB,mBAAmB,QAAuC;CACxE,IAAI,OAAO,WAAW,OAAO;EAC3B,cAAc,OAAO,WAAW,MAAM;EACtC,OAAO,WAAW,QAAQ;;CAE5B,IAAI,OAAO,cAAc,OAAO;EAC9B,cAAc,OAAO,cAAc,MAAM;EACzC,OAAO,cAAc,QAAQ;;CAE/B,IAAI,OAAO,uBAAuB,OAAO;EACvC,aAAa,OAAO,uBAAuB,MAAM;EACjD,OAAO,uBAAuB,QAAQ;;CAExC,IAAI,OAAO,oBAAoB,OAAO;EACpC,cAAc,OAAO,oBAAoB,MAAM;EAC/C,OAAO,oBAAoB,QAAQ;;CAErC,IAAI,OAAO,sBAAsB,OAAO;EACtC,cAAc,OAAO,sBAAsB,MAAM;EACjD,OAAO,sBAAsB,QAAQ;;CAEvC,IAAI,OAAO,+BAA+B,OAAO;EAC/C,aAAa,OAAO,+BAA+B,MAAM;EACzD,OAAO,+BAA+B,QAAQ;;CAEhD,IAAI,OAAO,mBAAmB,OAAO;EACnC,aAAa,OAAO,mBAAmB,MAAM;EAC7C,OAAO,mBAAmB,QAAQ;;CAEpC,IAAI,OAAO,qBAAqB,OAAO;EACrC,cAAc,OAAO,qBAAqB,MAAM;EAChD,OAAO,qBAAqB,QAAQ;;CAEtC,IAAI,OAAO,cAAc,OAAO;EAC9B,cAAc,OAAO,cAAc,MAAM;EACzC,OAAO,cAAc,QAAQ"}
|
|
1
|
+
{"version":3,"file":"plugin-runtime.js","names":[],"sources":["../../api/plugin-runtime.ts"],"sourcesContent":["/**\n * PluginRuntime: instance-scoped container for all mutable plugin state.\n *\n * Replaces module-level `let` variables in index.ts to enable:\n * - Independent plugin instances in the same process (testability)\n * - Clearer ownership of runtime state\n * - Explicit context passing to tools and lifecycle hooks\n *\n * A single module-level `const runtimeRef: { value: PluginRuntime | null }` holds the\n * active instance. Closures (tools, timers, event handlers) capture `runtimeRef` by\n * reference; when register() creates a fresh PluginRuntime after a SIGUSR1 reload the\n * closures automatically see the new instance through `runtimeRef.value`.\n */\n\nimport type OpenAI from \"openai\";\nimport type { AgentHealthStore } from \"../backends/agent-health-store.js\";\nimport type { ApitapStore } from \"../backends/apitap-store.js\";\nimport type { AuditStore } from \"../backends/audit-store.js\";\nimport type { CostTracker } from \"../backends/cost-tracker.js\";\nimport type { CredentialsDB } from \"../backends/credentials-db.js\";\nimport type { CrystallizationStore } from \"../backends/crystallization-store.js\";\nimport type { EdictStore } from \"../backends/edict-store.js\";\nimport type { EventBus } from \"../backends/event-bus.js\";\nimport type { EventLog } from \"../backends/event-log.js\";\nimport type { FactsDB } from \"../backends/facts-db.js\";\nimport type { IdentityReflectionStore } from \"../backends/identity-reflection-store.js\";\nimport type { IssueStore } from \"../backends/issue-store.js\";\nimport type { LearningsDB } from \"../backends/learnings-db.js\";\nimport type { NarrativesDB } from \"../backends/narratives-db.js\";\nimport type { PersonaStateStore } from \"../backends/persona-state-store.js\";\nimport type { ProposalsDB } from \"../backends/proposals-db.js\";\nimport type { ToolProposalStore } from \"../backends/tool-proposal-store.js\";\nimport type { VectorDB } from \"../backends/vector-db.js\";\nimport type { WriteAheadLog } from \"../backends/wal.js\";\nimport type { WorkflowStore } from \"../backends/workflow-store.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport type { PendingLLMWarnings } from \"../services/chat.js\";\nimport type { VariantGenerationQueue } from \"../services/contextual-variants.js\";\nimport type { EmbeddingRegistry } from \"../services/embedding-registry.js\";\nimport type { EmbeddingProvider } from \"../services/embeddings.js\";\nimport type { ProvenanceService } from \"../services/provenance.js\";\nimport type { PythonBridge } from \"../services/python-bridge.js\";\nimport type { AliasDB } from \"../services/retrieval-aliases.js\";\nimport type { VerificationStore } from \"../services/verification-store.js\";\nimport type { LifecycleHooksHandle } from \"../setup/register-hooks.js\";\nimport type { ToolRegistrationHandle } from \"../setup/register-tools.js\";\n\n/** All mutable per-instance state for the memory-hybrid plugin. */\nexport interface PluginRuntime {\n // --- Config & resolved paths ---\n cfg: HybridMemoryConfig;\n /** Parse-time config snapshot for hot-reload reuse checks (bootstrap may mutate `cfg`). */\n parsedCfgSnapshot: HybridMemoryConfig;\n resolvedLancePath: string;\n resolvedSqlitePath: string;\n\n // --- Core backends (always present after init) ---\n factsDb: FactsDB;\n edictStore: EdictStore;\n vectorDb: VectorDB;\n embeddings: EmbeddingProvider;\n embeddingRegistry: EmbeddingRegistry;\n openai: OpenAI;\n\n // --- Optional backends (null when feature disabled) ---\n credentialsDb: CredentialsDB | null;\n wal: WriteAheadLog | null;\n proposalsDb: ProposalsDB | null;\n identityReflectionStore: IdentityReflectionStore | null;\n personaStateStore: PersonaStateStore | null;\n eventLog: EventLog | null;\n narrativesDb: NarrativesDB | null;\n aliasDb: AliasDB | null;\n eventBus: EventBus | null;\n costTracker: CostTracker | null;\n issueStore: IssueStore | null;\n workflowStore: WorkflowStore | null;\n crystallizationStore: CrystallizationStore | null;\n toolProposalStore: ToolProposalStore | null;\n provenanceService: ProvenanceService | null;\n verificationStore: VerificationStore | null;\n apitapStore: ApitapStore | null;\n pythonBridge: PythonBridge | null;\n variantQueue: VariantGenerationQueue | null;\n /** Staged intake buffer for errors, lessons, and feature requests (Issue #617). */\n learningsDb: LearningsDB | null;\n /** Cross-agent audit log (Issue #790). */\n auditStore: AuditStore | null;\n agentHealthStore: AgentHealthStore | null;\n\n // --- Lifecycle state ---\n /** Handle returned by registerLifecycleHooks; set after hooks are registered, null until then. */\n lifecycleHooksHandle: LifecycleHooksHandle | null;\n /** Handle returned by registerTools; set after tool registration, null until then. */\n toolRegistrationHandle: ToolRegistrationHandle | null;\n /**\n * Resolves when async bootstrap work from `initializeDatabases` finishes (embedding/vault checks, etc.).\n * Used to sequence CLI teardown so we do not close DBs while init I/O is still running (Issue #1039).\n */\n bootstrapAsyncInit: Promise<void>;\n /** Tracks whether bootstrapAsyncInit has settled (used by re-register reuse policy). */\n bootstrapSettledRef?: { value: boolean };\n /** Last bootstrap health snapshot from initializeDatabases() for reuse-databases policy. */\n bootstrapHealth?: {\n embeddingsOk: boolean;\n credentialsVaultOk: boolean;\n lastCheckTime: number;\n };\n pendingLLMWarnings: PendingLLMWarnings;\n\n // --- Mutable refs (objects so that closures can share mutations) ---\n /** Detected agent for current session; updated on before_agent_start. */\n currentAgentIdRef: { value: string | null };\n /** Set to true once the restart-pending flag has been cleared this session. */\n restartPendingClearedRef: { value: boolean };\n /** Count of in-flight recall operations (degradation / back-pressure). */\n recallInFlightRef: { value: number };\n /** Last user prompt used for interactive auto-recall (issue #957 post-compaction reinjection). */\n lastAutoRecallPromptRef: { value: string | null };\n /** Last progressive index fact IDs (1-based position → fact id). */\n lastProgressiveIndexIds: string[];\n\n // --- Timer refs (objects so they can be passed by reference to plugin-service) ---\n timers: {\n pruneTimer: { value: ReturnType<typeof setInterval> | null };\n classifyTimer: { value: ReturnType<typeof setInterval> | null };\n classifyStartupTimeout: { value: ReturnType<typeof setTimeout> | null };\n proposalsPruneTimer: { value: ReturnType<typeof setInterval> | null };\n languageKeywordsTimer: { value: ReturnType<typeof setInterval> | null };\n languageKeywordsStartupTimeout: { value: ReturnType<typeof setTimeout> | null };\n postUpgradeTimeout: { value: ReturnType<typeof setTimeout> | null };\n passiveObserverTimer: { value: ReturnType<typeof setInterval> | null };\n /** Issue #631: Stale-run watchdog timer for autonomous task queue self-healing. */\n watchdogTimer: { value: ReturnType<typeof setInterval> | null };\n };\n}\n\n/** Create a fresh, empty timers bag for a new PluginRuntime instance. */\nexport function createTimers(): PluginRuntime[\"timers\"] {\n return {\n pruneTimer: { value: null },\n classifyTimer: { value: null },\n classifyStartupTimeout: { value: null },\n proposalsPruneTimer: { value: null },\n languageKeywordsTimer: { value: null },\n languageKeywordsStartupTimeout: { value: null },\n postUpgradeTimeout: { value: null },\n passiveObserverTimer: { value: null },\n watchdogTimer: { value: null },\n };\n}\n\n/**\n * Clear all runtime timer handles and reset refs.\n * Shared by CLI teardown and hot-reload cleanup paths.\n */\nexport function clearRuntimeTimers(timers: PluginRuntime[\"timers\"]): void {\n if (timers.pruneTimer.value) {\n clearInterval(timers.pruneTimer.value);\n timers.pruneTimer.value = null;\n }\n if (timers.classifyTimer.value) {\n clearInterval(timers.classifyTimer.value);\n timers.classifyTimer.value = null;\n }\n if (timers.classifyStartupTimeout.value) {\n clearTimeout(timers.classifyStartupTimeout.value);\n timers.classifyStartupTimeout.value = null;\n }\n if (timers.proposalsPruneTimer.value) {\n clearInterval(timers.proposalsPruneTimer.value);\n timers.proposalsPruneTimer.value = null;\n }\n if (timers.languageKeywordsTimer.value) {\n clearInterval(timers.languageKeywordsTimer.value);\n timers.languageKeywordsTimer.value = null;\n }\n if (timers.languageKeywordsStartupTimeout.value) {\n clearTimeout(timers.languageKeywordsStartupTimeout.value);\n timers.languageKeywordsStartupTimeout.value = null;\n }\n if (timers.postUpgradeTimeout.value) {\n clearTimeout(timers.postUpgradeTimeout.value);\n timers.postUpgradeTimeout.value = null;\n }\n if (timers.passiveObserverTimer.value) {\n clearInterval(timers.passiveObserverTimer.value);\n timers.passiveObserverTimer.value = null;\n }\n if (timers.watchdogTimer.value) {\n clearInterval(timers.watchdogTimer.value);\n timers.watchdogTimer.value = null;\n }\n}\n"],"mappings":";;AA0IA,SAAgB,eAAwC;CACtD,OAAO;EACL,YAAY,EAAE,OAAO,MAAM;EAC3B,eAAe,EAAE,OAAO,MAAM;EAC9B,wBAAwB,EAAE,OAAO,MAAM;EACvC,qBAAqB,EAAE,OAAO,MAAM;EACpC,uBAAuB,EAAE,OAAO,MAAM;EACtC,gCAAgC,EAAE,OAAO,MAAM;EAC/C,oBAAoB,EAAE,OAAO,MAAM;EACnC,sBAAsB,EAAE,OAAO,MAAM;EACrC,eAAe,EAAE,OAAO,MAAM;EAC/B;;;;;;AAOH,SAAgB,mBAAmB,QAAuC;CACxE,IAAI,OAAO,WAAW,OAAO;EAC3B,cAAc,OAAO,WAAW,MAAM;EACtC,OAAO,WAAW,QAAQ;;CAE5B,IAAI,OAAO,cAAc,OAAO;EAC9B,cAAc,OAAO,cAAc,MAAM;EACzC,OAAO,cAAc,QAAQ;;CAE/B,IAAI,OAAO,uBAAuB,OAAO;EACvC,aAAa,OAAO,uBAAuB,MAAM;EACjD,OAAO,uBAAuB,QAAQ;;CAExC,IAAI,OAAO,oBAAoB,OAAO;EACpC,cAAc,OAAO,oBAAoB,MAAM;EAC/C,OAAO,oBAAoB,QAAQ;;CAErC,IAAI,OAAO,sBAAsB,OAAO;EACtC,cAAc,OAAO,sBAAsB,MAAM;EACjD,OAAO,sBAAsB,QAAQ;;CAEvC,IAAI,OAAO,+BAA+B,OAAO;EAC/C,aAAa,OAAO,+BAA+B,MAAM;EACzD,OAAO,+BAA+B,QAAQ;;CAEhD,IAAI,OAAO,mBAAmB,OAAO;EACnC,aAAa,OAAO,mBAAmB,MAAM;EAC7C,OAAO,mBAAmB,QAAQ;;CAEpC,IAAI,OAAO,qBAAqB,OAAO;EACrC,cAAc,OAAO,qBAAqB,MAAM;EAChD,OAAO,qBAAqB,QAAQ;;CAEtC,IAAI,OAAO,cAAc,OAAO;EAC9B,cAAc,OAAO,cAAc,MAAM;EACzC,OAAO,cAAc,QAAQ"}
|
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ declare const memoryHybridPlugin: {
|
|
|
32
32
|
readonly memoryManagerVersion: "3.0";
|
|
33
33
|
readonly schemaVersion: 3;
|
|
34
34
|
};
|
|
35
|
-
register(api: ClawdbotPluginApi):
|
|
35
|
+
register(api: ClawdbotPluginApi): void;
|
|
36
36
|
};
|
|
37
37
|
//#endregion
|
|
38
38
|
export { AUTOPILOT_ACTIONS, AUTOPILOT_ACTION_CLASSES, AUTOPILOT_CAPABILITY_CLASSES, AUTOPILOT_MODES, AUTOPILOT_REASON_CODES, AutopilotAction, AutopilotActionClass, AutopilotCapabilityClass, AutopilotMode, AutopilotReasonCode, type ClusterDetectionOptions, type ClusterDetectionResult, type ClusterFactLookup, type ContradictionRecord, CreatePendingRunInput, DEFAULT_GRAPH_HUB_DEGREE_CAP, type GapEmbeddings, type GapFact, type GapFactsDB, type GapMode, type GapVectorDB, type GraphExpandedResult, type GraphExpansionStats, type GraphFactLookup, type GraphRetrievalOptions, type KnowledgeGapReport, type LinkPathStep, type MessageLike, PENDING_AUTOPILOT_SCHEMA_VERSION, PENDING_QUEUES, PERSONA_APPLY_CONFIDENCE_THRESHOLD, PERSONA_PROPOSAL_TRIAGE_POLICY_VERSION, PERSONA_REJECT_CONFIDENCE_THRESHOLD, type PathStep, PendingAutopilotCursor, PendingAutopilotLock, PendingAutopilotRunSummary, PendingAutopilotStore, PendingDecision, PendingDecisionActorContext, PendingDecisionContext, PendingDecisionEvidence, PendingItem, PendingQueue, PendingQueueAdapter, type PersonaProposalDecisionView, type PersonaProposalPendingItem, type PersonaProposalReviewBundle, type PersonaProposalRisk, type PersonaProposalTriageOptions, type PersonaProposalTriagePolicy, type PersonaProposalTriageResult, RedactedAutopilotAudit, RedactedAutopilotSummary, RedactedAutopilotText, type RetrievalPipelineOptions, type ShortestPathLookup, type ShortestPathResult, type SuggestedLink, type TopicCluster, _testing, assertKnownEnum, canonicalJson, computePendingInputHash, createPendingAutopilotRunId, createPersonaProposalTriageAdapter, createStableRunSummary, decidePersonaProposal, memoryHybridPlugin as default, getHybridMemoryContextBudgetHint, isAutopilotAction, isAutopilotActionClass, isAutopilotCapabilityClass, isAutopilotMode, isAutopilotReasonCode, isHybridMemHelpInvocation, isHybridMemJsonInvocation, isPendingQueue, migratePendingAutopilotTables, redactAutopilotText, redactAutopilotValue, renderPersonaProposalTriageHumanSummary, resolveGraphHubDegreeCap, runPersonaProposalTriage, sanitizeMessagesForClaude, sanitizeMessagesForOpenAIResponses, sanitizePendingDecision, shouldAdvancePendingCursor, stablePersonaProposalTriageJson, stableRunSummaryJson, validatePersonaPolicy, versionInfo };
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../index.ts"],"sourcesContent":["import type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\n\nimport { hybridConfigSchema } from \"./config/hybrid-schema.js\";\nimport { versionInfo } from \"./versionInfo.js\";\nimport { PLUGIN_ID } from \"./utils/constants.js\";\nimport { runMemoryHybridRegister } from \"./setup/register-plugin.js\";\nimport { isHybridMemHelpInvocation } from \"./index-help.js\";\n\nexport { isHybridMemHelpInvocation };\nexport { isHybridMemJsonInvocation } from \"./utils/hybrid-mem-json-cli.js\";\nexport type {\n GraphExpandedResult,\n GraphExpansionStats,\n GraphFactLookup,\n GraphRetrievalOptions,\n LinkPathStep,\n} from \"./services/graph-retrieval.js\";\nexport { DEFAULT_GRAPH_HUB_DEGREE_CAP, resolveGraphHubDegreeCap } from \"./services/graph-retrieval.js\";\nexport * from \"./services/pending-autopilot/index.js\";\nexport {\n PERSONA_APPLY_CONFIDENCE_THRESHOLD,\n PERSONA_PROPOSAL_TRIAGE_POLICY_VERSION,\n PERSONA_REJECT_CONFIDENCE_THRESHOLD,\n createPersonaProposalTriageAdapter,\n decidePersonaProposal,\n renderPersonaProposalTriageHumanSummary,\n runPersonaProposalTriage,\n stablePersonaProposalTriageJson,\n validatePersonaPolicy,\n} from \"./services/persona-proposal-triage.js\";\nexport type {\n PersonaProposalDecisionView,\n PersonaProposalPendingItem,\n PersonaProposalReviewBundle,\n PersonaProposalRisk,\n PersonaProposalTriageOptions,\n PersonaProposalTriagePolicy,\n PersonaProposalTriageResult,\n} from \"./services/persona-proposal-triage.js\";\nexport type { ShortestPathResult, PathStep, ShortestPathLookup } from \"./services/shortest-path.js\";\nexport type {\n GapFact,\n SuggestedLink,\n KnowledgeGapReport,\n GapMode,\n GapFactsDB,\n GapVectorDB,\n GapEmbeddings,\n} from \"./services/knowledge-gaps.js\";\nexport type {\n TopicCluster,\n ClusterDetectionResult,\n ClusterDetectionOptions,\n ClusterFactLookup,\n} from \"./services/topic-clusters.js\";\n\nexport { _testing } from \"./index-testing-exports.js\";\n\n// Plugin Definition\n\nconst memoryHybridPlugin = {\n id: PLUGIN_ID,\n name: \"Memory (Hybrid: SQLite + LanceDB)\",\n description: \"Two-tier memory: SQLite+FTS5 for structured facts, LanceDB for semantic search\",\n kind: \"memory\" as const,\n configSchema: hybridConfigSchema,\n versionInfo,\n\n
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../index.ts"],"sourcesContent":["import type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\n\nimport { hybridConfigSchema } from \"./config/hybrid-schema.js\";\nimport { versionInfo } from \"./versionInfo.js\";\nimport { PLUGIN_ID } from \"./utils/constants.js\";\nimport { runMemoryHybridRegister } from \"./setup/register-plugin.js\";\nimport { isHybridMemHelpInvocation } from \"./index-help.js\";\n\nexport { isHybridMemHelpInvocation };\nexport { isHybridMemJsonInvocation } from \"./utils/hybrid-mem-json-cli.js\";\nexport type {\n GraphExpandedResult,\n GraphExpansionStats,\n GraphFactLookup,\n GraphRetrievalOptions,\n LinkPathStep,\n} from \"./services/graph-retrieval.js\";\nexport { DEFAULT_GRAPH_HUB_DEGREE_CAP, resolveGraphHubDegreeCap } from \"./services/graph-retrieval.js\";\nexport * from \"./services/pending-autopilot/index.js\";\nexport {\n PERSONA_APPLY_CONFIDENCE_THRESHOLD,\n PERSONA_PROPOSAL_TRIAGE_POLICY_VERSION,\n PERSONA_REJECT_CONFIDENCE_THRESHOLD,\n createPersonaProposalTriageAdapter,\n decidePersonaProposal,\n renderPersonaProposalTriageHumanSummary,\n runPersonaProposalTriage,\n stablePersonaProposalTriageJson,\n validatePersonaPolicy,\n} from \"./services/persona-proposal-triage.js\";\nexport type {\n PersonaProposalDecisionView,\n PersonaProposalPendingItem,\n PersonaProposalReviewBundle,\n PersonaProposalRisk,\n PersonaProposalTriageOptions,\n PersonaProposalTriagePolicy,\n PersonaProposalTriageResult,\n} from \"./services/persona-proposal-triage.js\";\nexport type { ShortestPathResult, PathStep, ShortestPathLookup } from \"./services/shortest-path.js\";\nexport type {\n GapFact,\n SuggestedLink,\n KnowledgeGapReport,\n GapMode,\n GapFactsDB,\n GapVectorDB,\n GapEmbeddings,\n} from \"./services/knowledge-gaps.js\";\nexport type {\n TopicCluster,\n ClusterDetectionResult,\n ClusterDetectionOptions,\n ClusterFactLookup,\n} from \"./services/topic-clusters.js\";\n\nexport { _testing } from \"./index-testing-exports.js\";\n\n// Plugin Definition\n\nconst memoryHybridPlugin = {\n id: PLUGIN_ID,\n name: \"Memory (Hybrid: SQLite + LanceDB)\",\n description: \"Two-tier memory: SQLite+FTS5 for structured facts, LanceDB for semantic search\",\n kind: \"memory\" as const,\n configSchema: hybridConfigSchema,\n versionInfo,\n\n register(api: ClawdbotPluginApi) {\n runMemoryHybridRegister(api);\n },\n};\n\nexport { versionInfo } from \"./versionInfo.js\";\nexport {\n sanitizeMessagesForClaude,\n sanitizeMessagesForOpenAIResponses,\n type MessageLike,\n} from \"./utils/sanitize-messages.js\";\nexport { getHybridMemoryContextBudgetHint } from \"./services/context-budget.js\";\nexport type { ContradictionRecord } from \"./backends/facts-db.js\";\nexport type { RetrievalPipelineOptions } from \"./services/retrieval-orchestrator.js\";\nexport default memoryHybridPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;AA4DA,MAAM,qBAAqB;CACzB,IAAI;CACJ,MAAM;CACN,aAAa;CACb,MAAM;CACN,cAAc;CACd;CAEA,SAAS,KAAwB;EAC/B,wBAAwB,IAAI;;CAE/B"}
|
|
@@ -36,26 +36,28 @@ async function drainOldRecall(recallInFlightRef) {
|
|
|
36
36
|
const deadline = Date.now() + RECALL_DRAIN_MS;
|
|
37
37
|
while (recallInFlightRef.value > 0 && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 50));
|
|
38
38
|
}
|
|
39
|
+
/** Shared buffer for Atomics.wait while synchronously blocking register(). */
|
|
40
|
+
const syncWaitBuffer = new SharedArrayBuffer(4);
|
|
41
|
+
const syncWaitArray = new Int32Array(syncWaitBuffer);
|
|
39
42
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
+
* Synchronous variant of `awaitReloadTeardownBeforeOpen` for OpenClaw's sync `register()` contract.
|
|
44
|
+
* Polls teardown queue depth while pumping the event loop via Atomics.wait (50ms ticks).
|
|
45
|
+
*
|
|
46
|
+
* Prefer a bounded `timeoutMs` (default {@link TEARDOWN_WAIT_MS}). Avoid `timeoutMs === 0`
|
|
47
|
+
* in tests: vitest cannot interrupt synchronous Atomics.wait loops, so unbounded waits hang CI.
|
|
43
48
|
*/
|
|
44
|
-
|
|
49
|
+
function blockReloadTeardownBeforeOpen(timeoutMs = TEARDOWN_WAIT_MS) {
|
|
45
50
|
if (timeoutMs < 0) return false;
|
|
46
51
|
const deadline = timeoutMs === 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
|
|
47
52
|
while (Date.now() <= deadline) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (Number.isFinite(deadline)) await Promise.race([chainSnapshot.catch(() => {}), new Promise((resolve) => setTimeout(resolve, remainingMs))]);
|
|
51
|
-
else await chainSnapshot.catch(() => {});
|
|
52
|
-
if (reloadTeardownQueueDepth === 0 && chainSnapshot === reloadTeardownChain) return true;
|
|
53
|
+
if (reloadTeardownQueueDepth === 0 && reloadTeardownChain === reloadTeardownChain) return true;
|
|
54
|
+
Atomics.wait(syncWaitArray, 0, 0, 50);
|
|
53
55
|
if (!Number.isFinite(deadline)) continue;
|
|
54
56
|
if (Date.now() >= deadline) break;
|
|
55
57
|
}
|
|
56
58
|
return reloadTeardownQueueDepth === 0;
|
|
57
59
|
}
|
|
58
60
|
//#endregion
|
|
59
|
-
export {
|
|
61
|
+
export { TEARDOWN_WAIT_MS, blockReloadTeardownBeforeOpen, drainOldBootstrap, drainOldRecall, schedulePluginTeardown };
|
|
60
62
|
|
|
61
63
|
//# sourceMappingURL=hybrid-memory-reload-coordinator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hybrid-memory-reload-coordinator.js","names":[],"sources":["../../setup/hybrid-memory-reload-coordinator.ts"],"sourcesContent":["import { capturePluginError } from \"../services/error-reporter.js\";\n\n/** Max time to wait for superseded instance bootstrap before permanentClose (embedding verify can take minutes). */\nexport const BOOTSTRAP_DRAIN_MS = 3_000;\n\n/** Brief wait for in-flight auto-recall before permanentClose (directives can run 20s+). */\nexport const RECALL_DRAIN_MS = 2_000;\n\n/** Max time register() blocks waiting for scheduled teardown before opening new DB handles. */\nconst TEARDOWN_MIN_WAIT_MS = BOOTSTRAP_DRAIN_MS + RECALL_DRAIN_MS + 1_000;\nexport const TEARDOWN_WAIT_MS = Math.max(6_000, TEARDOWN_MIN_WAIT_MS);\n\n/** Serializes plugin teardown (bootstrap settle → close DBs) across hot reloads (#1550 / reload race). */\nlet reloadTeardownChain: Promise<void> = Promise.resolve();\nlet reloadTeardownQueueDepth = 0;\n\nexport function schedulePluginTeardown(teardown: () => Promise<void>): void {\n reloadTeardownQueueDepth += 1;\n reloadTeardownChain = reloadTeardownChain\n .catch(() => {\n /* keep chain alive; next teardown still must run */\n })\n .then(async () => {\n try {\n await teardown();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"reload-teardown\",\n severity: \"warning\",\n });\n } finally {\n reloadTeardownQueueDepth = Math.max(0, reloadTeardownQueueDepth - 1);\n }\n });\n}\n\n/** Wait for superseded bootstrap work with a short cap; then close old handles regardless. */\nexport async function drainOldBootstrap(bootstrap: Promise<void>): Promise<void> {\n await Promise.race([\n bootstrap.catch(() => {\n /* embedding/vault init may fail; still close handles */\n }),\n new Promise<void>((resolve) => setTimeout(resolve, BOOTSTRAP_DRAIN_MS)),\n ]);\n}\n\n/** Let superseded auto-recall/directive probes finish before closing SQLite handles. */\nexport async function drainOldRecall(recallInFlightRef: { value: number } | undefined): Promise<void> {\n if (!recallInFlightRef || recallInFlightRef.value <= 0) return;\n const deadline = Date.now() + RECALL_DRAIN_MS;\n while (recallInFlightRef.value > 0 && Date.now() < deadline) {\n await new Promise<void>((resolve) => setTimeout(resolve, 50));\n }\n}\n\n/**\n * Block until scheduled teardowns finish before opening new DB handles (issue #802).\n * If timeoutMs is 0, waits indefinitely until teardown completes.\n * Returns a promise that resolves to false if teardown is still in flight after timeout.\n */\nexport async function awaitReloadTeardownBeforeOpen(timeoutMs = TEARDOWN_WAIT_MS): Promise<boolean> {\n if (timeoutMs < 0) return false;\n const deadline = timeoutMs === 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;\n while (Date.now() <= deadline) {\n const chainSnapshot = reloadTeardownChain;\n const remainingMs = Number.isFinite(deadline) ? Math.max(0, deadline - Date.now()) : 0;\n if (Number.isFinite(deadline)) {\n await Promise.race([\n chainSnapshot.catch(() => {\n /* teardown error already logged */\n }),\n new Promise<void>((resolve) => setTimeout(resolve, remainingMs)),\n ]);\n } else {\n await chainSnapshot.catch(() => {\n /* teardown error already logged */\n });\n }\n\n // Consider teardown drained only when both queue depth and chain identity are stable.\n if (reloadTeardownQueueDepth === 0 && chainSnapshot === reloadTeardownChain) {\n return true;\n }\n if (!Number.isFinite(deadline)) continue;\n if (Date.now() >= deadline) break;\n }\n return reloadTeardownQueueDepth === 0;\n}\n\n/** Reset chain for unit tests only. */\nexport function resetReloadTeardownChainForTests(): void {\n reloadTeardownChain = Promise.resolve();\n reloadTeardownQueueDepth = 0;\n}\n"],"mappings":";;;AAGA,MAAa,qBAAqB;;AAGlC,MAAa,kBAAkB;;AAG/B,MAAM,uBAAuB,qBAAqB,kBAAkB;AACpE,MAAa,mBAAmB,KAAK,IAAI,KAAO,qBAAqB;;AAGrE,IAAI,sBAAqC,QAAQ,SAAS;AAC1D,IAAI,2BAA2B;AAE/B,SAAgB,uBAAuB,UAAqC;CAC1E,4BAA4B;CAC5B,sBAAsB,oBACnB,YAAY,GAEX,CACD,KAAK,YAAY;EAChB,IAAI;GACF,MAAM,UAAU;WACT,KAAK;GACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;IACtE,WAAW;IACX,WAAW;IACX,UAAU;IACX,CAAC;YACM;GACR,2BAA2B,KAAK,IAAI,GAAG,2BAA2B,EAAE;;GAEtE;;;AAIN,eAAsB,kBAAkB,WAAyC;CAC/E,MAAM,QAAQ,KAAK,CACjB,UAAU,YAAY,GAEpB,EACF,IAAI,SAAe,YAAY,WAAW,SAAS,mBAAmB,CAAC,CACxE,CAAC;;;AAIJ,eAAsB,eAAe,mBAAiE;CACpG,IAAI,CAAC,qBAAqB,kBAAkB,SAAS,GAAG;CACxD,MAAM,WAAW,KAAK,KAAK,GAAG;CAC9B,OAAO,kBAAkB,QAAQ,KAAK,KAAK,KAAK,GAAG,UACjD,MAAM,IAAI,SAAe,YAAY,WAAW,SAAS,GAAG,CAAC
|
|
1
|
+
{"version":3,"file":"hybrid-memory-reload-coordinator.js","names":["chainSnapshot"],"sources":["../../setup/hybrid-memory-reload-coordinator.ts"],"sourcesContent":["import { capturePluginError } from \"../services/error-reporter.js\";\n\n/** Max time to wait for superseded instance bootstrap before permanentClose (embedding verify can take minutes). */\nexport const BOOTSTRAP_DRAIN_MS = 3_000;\n\n/** Brief wait for in-flight auto-recall before permanentClose (directives can run 20s+). */\nexport const RECALL_DRAIN_MS = 2_000;\n\n/** Max time register() blocks waiting for scheduled teardown before opening new DB handles. */\nconst TEARDOWN_MIN_WAIT_MS = BOOTSTRAP_DRAIN_MS + RECALL_DRAIN_MS + 1_000;\nexport const TEARDOWN_WAIT_MS = Math.max(6_000, TEARDOWN_MIN_WAIT_MS);\n\n/** Serializes plugin teardown (bootstrap settle → close DBs) across hot reloads (#1550 / reload race). */\nlet reloadTeardownChain: Promise<void> = Promise.resolve();\nlet reloadTeardownQueueDepth = 0;\n\nexport function schedulePluginTeardown(teardown: () => Promise<void>): void {\n reloadTeardownQueueDepth += 1;\n reloadTeardownChain = reloadTeardownChain\n .catch(() => {\n /* keep chain alive; next teardown still must run */\n })\n .then(async () => {\n try {\n await teardown();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"reload-teardown\",\n severity: \"warning\",\n });\n } finally {\n reloadTeardownQueueDepth = Math.max(0, reloadTeardownQueueDepth - 1);\n }\n });\n}\n\n/** Wait for superseded bootstrap work with a short cap; then close old handles regardless. */\nexport async function drainOldBootstrap(bootstrap: Promise<void>): Promise<void> {\n await Promise.race([\n bootstrap.catch(() => {\n /* embedding/vault init may fail; still close handles */\n }),\n new Promise<void>((resolve) => setTimeout(resolve, BOOTSTRAP_DRAIN_MS)),\n ]);\n}\n\n/** Let superseded auto-recall/directive probes finish before closing SQLite handles. */\nexport async function drainOldRecall(recallInFlightRef: { value: number } | undefined): Promise<void> {\n if (!recallInFlightRef || recallInFlightRef.value <= 0) return;\n const deadline = Date.now() + RECALL_DRAIN_MS;\n while (recallInFlightRef.value > 0 && Date.now() < deadline) {\n await new Promise<void>((resolve) => setTimeout(resolve, 50));\n }\n}\n\n/**\n * Block until scheduled teardowns finish before opening new DB handles (issue #802).\n * If timeoutMs is 0, waits indefinitely until teardown completes.\n * Returns a promise that resolves to false if teardown is still in flight after timeout.\n */\nexport async function awaitReloadTeardownBeforeOpen(timeoutMs = TEARDOWN_WAIT_MS): Promise<boolean> {\n if (timeoutMs < 0) return false;\n const deadline = timeoutMs === 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;\n while (Date.now() <= deadline) {\n const chainSnapshot = reloadTeardownChain;\n const remainingMs = Number.isFinite(deadline) ? Math.max(0, deadline - Date.now()) : 0;\n if (Number.isFinite(deadline)) {\n await Promise.race([\n chainSnapshot.catch(() => {\n /* teardown error already logged */\n }),\n new Promise<void>((resolve) => setTimeout(resolve, remainingMs)),\n ]);\n } else {\n await chainSnapshot.catch(() => {\n /* teardown error already logged */\n });\n }\n\n // Consider teardown drained only when both queue depth and chain identity are stable.\n if (reloadTeardownQueueDepth === 0 && chainSnapshot === reloadTeardownChain) {\n return true;\n }\n if (!Number.isFinite(deadline)) continue;\n if (Date.now() >= deadline) break;\n }\n return reloadTeardownQueueDepth === 0;\n}\n\n/** Shared buffer for Atomics.wait while synchronously blocking register(). */\nconst syncWaitBuffer = new SharedArrayBuffer(4);\nconst syncWaitArray = new Int32Array(syncWaitBuffer);\n\n/**\n * Synchronous variant of `awaitReloadTeardownBeforeOpen` for OpenClaw's sync `register()` contract.\n * Polls teardown queue depth while pumping the event loop via Atomics.wait (50ms ticks).\n *\n * Prefer a bounded `timeoutMs` (default {@link TEARDOWN_WAIT_MS}). Avoid `timeoutMs === 0`\n * in tests: vitest cannot interrupt synchronous Atomics.wait loops, so unbounded waits hang CI.\n */\nexport function blockReloadTeardownBeforeOpen(timeoutMs = TEARDOWN_WAIT_MS): boolean {\n if (timeoutMs < 0) return false;\n const deadline = timeoutMs === 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;\n while (Date.now() <= deadline) {\n const chainSnapshot = reloadTeardownChain;\n if (reloadTeardownQueueDepth === 0 && chainSnapshot === reloadTeardownChain) {\n return true;\n }\n Atomics.wait(syncWaitArray, 0, 0, 50);\n if (!Number.isFinite(deadline)) continue;\n if (Date.now() >= deadline) break;\n }\n return reloadTeardownQueueDepth === 0;\n}\n\n/** Reset chain for unit tests only. */\nexport function resetReloadTeardownChainForTests(): void {\n reloadTeardownChain = Promise.resolve();\n reloadTeardownQueueDepth = 0;\n}\n"],"mappings":";;;AAGA,MAAa,qBAAqB;;AAGlC,MAAa,kBAAkB;;AAG/B,MAAM,uBAAuB,qBAAqB,kBAAkB;AACpE,MAAa,mBAAmB,KAAK,IAAI,KAAO,qBAAqB;;AAGrE,IAAI,sBAAqC,QAAQ,SAAS;AAC1D,IAAI,2BAA2B;AAE/B,SAAgB,uBAAuB,UAAqC;CAC1E,4BAA4B;CAC5B,sBAAsB,oBACnB,YAAY,GAEX,CACD,KAAK,YAAY;EAChB,IAAI;GACF,MAAM,UAAU;WACT,KAAK;GACZ,mBAAmB,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,EAAE;IACtE,WAAW;IACX,WAAW;IACX,UAAU;IACX,CAAC;YACM;GACR,2BAA2B,KAAK,IAAI,GAAG,2BAA2B,EAAE;;GAEtE;;;AAIN,eAAsB,kBAAkB,WAAyC;CAC/E,MAAM,QAAQ,KAAK,CACjB,UAAU,YAAY,GAEpB,EACF,IAAI,SAAe,YAAY,WAAW,SAAS,mBAAmB,CAAC,CACxE,CAAC;;;AAIJ,eAAsB,eAAe,mBAAiE;CACpG,IAAI,CAAC,qBAAqB,kBAAkB,SAAS,GAAG;CACxD,MAAM,WAAW,KAAK,KAAK,GAAG;CAC9B,OAAO,kBAAkB,QAAQ,KAAK,KAAK,KAAK,GAAG,UACjD,MAAM,IAAI,SAAe,YAAY,WAAW,SAAS,GAAG,CAAC;;;AAuCjE,MAAM,iBAAiB,IAAI,kBAAkB,EAAE;AAC/C,MAAM,gBAAgB,IAAI,WAAW,eAAe;;;;;;;;AASpD,SAAgB,8BAA8B,YAAY,kBAA2B;CACnF,IAAI,YAAY,GAAG,OAAO;CAC1B,MAAM,WAAW,cAAc,IAAI,OAAO,oBAAoB,KAAK,KAAK,GAAG;CAC3E,OAAO,KAAK,KAAK,IAAI,UAAU;EAE7B,IAAI,6BAA6B,KAAKA,wBAAkB,qBACtD,OAAO;EAET,QAAQ,KAAK,eAAe,GAAG,GAAG,GAAG;EACrC,IAAI,CAAC,OAAO,SAAS,SAAS,EAAE;EAChC,IAAI,KAAK,KAAK,IAAI,UAAU;;CAE9B,OAAO,6BAA6B"}
|
|
@@ -28,7 +28,7 @@ import { closeOldDatabases, createReusedDatabaseBootstrap, initializeDatabases }
|
|
|
28
28
|
import "./init-databases.js";
|
|
29
29
|
import { createPluginService } from "./plugin-service.js";
|
|
30
30
|
import { canReuseDatabasesOnReregister, databaseContextFromRuntime, recordReregisterDatabaseReuse, recordReregisterFullTeardown, recordReregisterRegistration, resolveReregisterPolicy } from "./reregister-policy.js";
|
|
31
|
-
import {
|
|
31
|
+
import { TEARDOWN_WAIT_MS, blockReloadTeardownBeforeOpen, drainOldBootstrap, drainOldRecall, schedulePluginTeardown } from "./hybrid-memory-reload-coordinator.js";
|
|
32
32
|
import { registerContextEngineBestEffort } from "./register-context-engine.js";
|
|
33
33
|
import { registerLifecycleHooks } from "./register-hooks.js";
|
|
34
34
|
import { registerTools } from "./register-tools.js";
|
|
@@ -47,7 +47,10 @@ function detectCategory(text) {
|
|
|
47
47
|
const runtimeRef = { value: null };
|
|
48
48
|
const registrationGenerationRef = getHybridMemoryRegistrationState().registrationGenerationRef;
|
|
49
49
|
/** Guard to prevent concurrent registrations from interleaving (Issue #802 re-entrancy). */
|
|
50
|
-
let registrationInProgress =
|
|
50
|
+
let registrationInProgress = false;
|
|
51
|
+
/** Shared buffer for registration gate wait/notify (separate from reload coordinator buffer). */
|
|
52
|
+
const registrationGateWaitBuffer = new SharedArrayBuffer(4);
|
|
53
|
+
const registrationGateWaitArray = new Int32Array(registrationGateWaitBuffer);
|
|
51
54
|
/** Release DBs and timers after a `hybrid-mem` CLI command so the Node process can exit (Issue #1039). */
|
|
52
55
|
async function performHybridMemCliTeardown() {
|
|
53
56
|
restoreStdoutAfterJsonCli();
|
|
@@ -113,7 +116,7 @@ async function performHybridMemCliTeardown() {
|
|
|
113
116
|
});
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
|
|
119
|
+
function runMemoryHybridRegister(api) {
|
|
117
120
|
if (api.registrationMode === "cli-metadata") {
|
|
118
121
|
registerHybridMemCliMetadataOnly(api);
|
|
119
122
|
return;
|
|
@@ -122,33 +125,33 @@ async function runMemoryHybridRegister(api) {
|
|
|
122
125
|
registerHybridMemCliHelpOnlyWithApi(api);
|
|
123
126
|
return;
|
|
124
127
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const registrationPromise = new Promise((resolve) => {
|
|
128
|
-
releaseRegistrationGate = resolve;
|
|
129
|
-
});
|
|
130
|
-
registrationInProgress = registrationPromise;
|
|
128
|
+
while (registrationInProgress) Atomics.wait(registrationGateWaitArray, 0, 0, 50);
|
|
129
|
+
registrationInProgress = true;
|
|
131
130
|
try {
|
|
132
|
-
|
|
133
|
-
await runMemoryHybridRegisterImpl(api);
|
|
131
|
+
runMemoryHybridRegisterImpl(api);
|
|
134
132
|
} finally {
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
registrationInProgress = false;
|
|
134
|
+
Atomics.notify(registrationGateWaitArray, 0, 1);
|
|
137
135
|
}
|
|
138
136
|
}
|
|
139
|
-
|
|
137
|
+
function runMemoryHybridRegisterImpl(api) {
|
|
140
138
|
const logApi = wrapApiLoggerStderrForJsonCli(api);
|
|
141
139
|
initPluginLogger(logApi.logger, false);
|
|
142
140
|
const old = runtimeRef.value;
|
|
143
141
|
let cfg;
|
|
142
|
+
let parsedCfgSnapshot;
|
|
144
143
|
try {
|
|
145
144
|
const rawPc = api.pluginConfig;
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
const buildConfigToParse = () => {
|
|
146
|
+
if (rawPc && typeof rawPc === "object" && !Array.isArray(rawPc)) {
|
|
147
|
+
const clone = shallowClonePluginConfigForGatewayMerge(rawPc);
|
|
148
|
+
applyGatewayEmbeddingInheritanceBeforeParse(clone, api);
|
|
149
|
+
return clone;
|
|
150
|
+
}
|
|
151
|
+
return rawPc;
|
|
152
|
+
};
|
|
153
|
+
cfg = hybridConfigSchema.parse(buildConfigToParse());
|
|
154
|
+
parsedCfgSnapshot = hybridConfigSchema.parse(buildConfigToParse());
|
|
152
155
|
} catch (err) {
|
|
153
156
|
capturePluginError(err instanceof Error ? err : new Error(String(err)), {
|
|
154
157
|
subsystem: "registration",
|
|
@@ -206,7 +209,7 @@ async function runMemoryHybridRegisterImpl(api) {
|
|
|
206
209
|
runtimeRef.value = null;
|
|
207
210
|
}
|
|
208
211
|
if (old && !reuseDatabases) {
|
|
209
|
-
if (!
|
|
212
|
+
if (!blockReloadTeardownBeforeOpen(TEARDOWN_WAIT_MS)) throw new Error("memory-hybrid: reload teardown did not drain before opening new databases");
|
|
210
213
|
}
|
|
211
214
|
let dbContext;
|
|
212
215
|
try {
|
|
@@ -306,6 +309,7 @@ async function runMemoryHybridRegisterImpl(api) {
|
|
|
306
309
|
});
|
|
307
310
|
const newRuntime = {
|
|
308
311
|
cfg,
|
|
312
|
+
parsedCfgSnapshot,
|
|
309
313
|
resolvedLancePath,
|
|
310
314
|
resolvedSqlitePath,
|
|
311
315
|
factsDb: dbContext.factsDb,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-plugin.js","names":["shouldCaptureUtil","detectCategoryUtil"],"sources":["../../setup/register-plugin.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { AgentHealthStore, agentHealthDbPathForMemorySqlite } from \"../backends/agent-health-store.js\";\nimport { AuditStore, auditDbPathForMemorySqlite } from \"../backends/audit-store.js\";\nimport { EventBus } from \"../backends/event-bus.js\";\nimport { LearningsDB } from \"../backends/learnings-db.js\";\nimport type { MemoryPluginAPI } from \"../api/memory-plugin-api.js\";\nimport { type PluginRuntime, clearRuntimeTimers, createTimers } from \"../api/plugin-runtime.js\";\nimport type { HybridMemoryConfig, MemoryCategory } from \"../config.js\";\nimport { hybridConfigSchema } from \"../config/hybrid-schema.js\";\nimport { createPendingLLMWarnings } from \"../services/chat.js\";\nimport { getMemoryTriggers } from \"../services/auto-capture.js\";\nimport { detectCategory as detectCategoryUtil, shouldCapture as shouldCaptureUtil } from \"../services/capture-utils.js\";\nimport { ContextualVariantGenerator, VariantGenerationQueue } from \"../services/contextual-variants.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { runReflection, runReflectionMeta, runReflectionRules } from \"../services/reflection.js\";\nimport { PythonBridge } from \"../services/python-bridge.js\";\nimport { findSimilarByEmbedding } from \"../services/vector-search.js\";\nimport { resetStartupMemoryAttribution } from \"../services/startup-memory-attribution.js\";\nimport { walRemove, walWrite } from \"../services/wal-helpers.js\";\nimport { registerHybridMemCliMetadataOnly } from \"./cli-context/metadata.js\";\nimport { registerHybridMemCliHelpOnlyWithApi } from \"./cli-context/register-help.js\";\nimport { registerHybridMemCliWithApi } from \"./cli-context/register-full.js\";\nimport \"./cli-context.js\";\nimport { closeOldDatabases, createReusedDatabaseBootstrap, initializeDatabases } from \"./bootstrap-databases.js\";\nimport \"./init-databases.js\";\nimport { createPluginService } from \"./plugin-service.js\";\nimport {\n canReuseDatabasesOnReregister,\n databaseContextFromRuntime,\n recordReregisterDatabaseReuse,\n recordReregisterFullTeardown,\n recordReregisterRegistration,\n resolveReregisterPolicy,\n} from \"./reregister-policy.js\";\nimport {\n applyGatewayEmbeddingInheritanceBeforeParse,\n shallowClonePluginConfigForGatewayMerge,\n} from \"./provider-router.js\";\nimport { getHybridMemoryRegistrationState } from \"./hybrid-memory-generation-state.js\";\nimport {\n awaitReloadTeardownBeforeOpen,\n drainOldBootstrap,\n drainOldRecall,\n schedulePluginTeardown,\n} from \"./hybrid-memory-reload-coordinator.js\";\nimport { registerContextEngineBestEffort } from \"./register-context-engine.js\";\nimport { registerLifecycleHooks } from \"./register-hooks.js\";\nimport { registerTools } from \"./register-tools.js\";\nimport { PLUGIN_ID } from \"../utils/constants.js\";\nimport { isHybridMemHelpInvocation } from \"../index-help.js\";\nimport { wrapApiLoggerStderrForJsonCli, restoreStdoutAfterJsonCli } from \"../utils/hybrid-mem-json-cli.js\";\nimport {\n getCategoryDecisionRegex,\n getCategoryEntityRegex,\n getCategoryFactRegex,\n getCategoryPreferenceRegex,\n} from \"../utils/language-keywords.js\";\nimport { initPluginLogger } from \"../utils/logger.js\";\nimport { buildToolScopeFilter } from \"../utils/scope-filter.js\";\nimport { versionInfo } from \"../versionInfo.js\";\n\n/** Wrappers for extracted helper functions that need access to per-instance config via runtimeRef. */\nfunction shouldCapture(text: string): boolean {\n return shouldCaptureUtil(text, runtimeRef.value?.cfg.captureMaxChars ?? 5000, getMemoryTriggers());\n}\n\nfunction detectCategory(text: string): MemoryCategory {\n return detectCategoryUtil(\n text,\n getCategoryDecisionRegex(),\n getCategoryPreferenceRegex(),\n getCategoryEntityRegex(),\n getCategoryFactRegex(),\n );\n}\n\nconst runtimeRef: { value: PluginRuntime | null } = { value: null };\nconst registrationGenerationRef = getHybridMemoryRegistrationState().registrationGenerationRef;\n\n/** Guard to prevent concurrent registrations from interleaving (Issue #802 re-entrancy). */\nlet registrationInProgress: Promise<void> | null = null;\n\n/** Release DBs and timers after a `hybrid-mem` CLI command so the Node process can exit (Issue #1039). */\nasync function performHybridMemCliTeardown(): Promise<void> {\n // Restore stdout before checking runtime ref, so teardown without runtime still cleans up (issue #1618).\n restoreStdoutAfterJsonCli();\n\n const r = runtimeRef.value;\n if (!r) return;\n // Stop long-lived service timers first so one-shot CLI commands can exit promptly.\n clearRuntimeTimers(r.timers);\n try {\n await r.bootstrapAsyncInit;\n } catch {\n /* embedding/vault init may fail; still close handles */\n }\n // Startup can race with command completion; clear again after async init settles.\n clearRuntimeTimers(r.timers);\n try {\n r.lifecycleHooksHandle?.dispose();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:dispose-hooks\",\n });\n }\n try {\n r.toolRegistrationHandle?.dispose();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:dispose-tools\",\n });\n }\n try {\n closeOldDatabases({\n factsDb: r.factsDb,\n edictStore: r.edictStore,\n narrativesDb: r.narrativesDb,\n vectorDb: r.vectorDb,\n credentialsDb: r.credentialsDb,\n proposalsDb: r.proposalsDb,\n identityReflectionStore: r.identityReflectionStore,\n personaStateStore: r.personaStateStore,\n eventLog: r.eventLog,\n aliasDb: r.aliasDb,\n eventBus: r.eventBus,\n issueStore: r.issueStore,\n workflowStore: r.workflowStore,\n crystallizationStore: r.crystallizationStore,\n toolProposalStore: r.toolProposalStore,\n verificationStore: r.verificationStore,\n provenanceService: r.provenanceService,\n learningsDb: r.learningsDb,\n apitapStore: r.apitapStore,\n auditStore: r.auditStore,\n agentHealthStore: r.agentHealthStore,\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:close-databases\",\n });\n }\n try {\n await r.pythonBridge?.shutdown();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:python-bridge\",\n });\n }\n}\n\nexport async function runMemoryHybridRegister(api: ClawdbotPluginApi): Promise<void> {\n // OpenClaw `loadOpenClawPluginCliRegistry` — metadata only; no DBs or native deps (issue #1111).\n // Check this FIRST, before any logger init or config parsing, so an incomplete config\n // cannot block lightweight metadata registration.\n //\n // Issue #1209/#XXXX: Always register CLI metadata even when service markers are present in the environment.\n // Service markers (OPENCLAW_SERVICE_KIND, OPENCLAW_SERVICE_MARKER) should only prevent full plugin\n // initialization (databases, timers), not CLI metadata registration. Without this, `openclaw hybrid-mem`\n // commands become unavailable in cron/service environments where these markers leak.\n if (api.registrationMode === \"cli-metadata\") {\n registerHybridMemCliMetadataOnly(api);\n return;\n }\n\n // Help invocations should be cheap and deterministic: register the command tree but do not\n // bootstrap DBs or start background checks/timers that can keep Node alive after help prints.\n // Examples: `openclaw hybrid-mem --help`, `openclaw hybrid-mem verify --help`.\n if (isHybridMemHelpInvocation(process.argv)) {\n registerHybridMemCliHelpOnlyWithApi(api);\n return;\n }\n\n // Issue #802 re-entrancy: If another registration is already in flight, wait for it to complete\n // before starting a new one. Install our own gate before awaiting prior work to avoid\n // TOCTOU races when multiple callers enter concurrently.\n const previousRegistration = registrationInProgress ?? Promise.resolve();\n let releaseRegistrationGate = (): void => {};\n const registrationPromise = new Promise<void>((resolve) => {\n releaseRegistrationGate = resolve;\n });\n registrationInProgress = registrationPromise;\n\n try {\n await previousRegistration;\n await runMemoryHybridRegisterImpl(api);\n } finally {\n releaseRegistrationGate();\n if (registrationInProgress === registrationPromise) {\n registrationInProgress = null;\n }\n }\n}\n\nasync function runMemoryHybridRegisterImpl(api: ClawdbotPluginApi): Promise<void> {\n // Issue #1230 / #1234: JSON CLI must not write plugin telemetry to stdout (cron harnesses, jq).\n // Wrap api.logger to stderr before bootstrap; keep pluginLogger on that same logger delegate.\n const logApi = wrapApiLoggerStderrForJsonCli(api);\n initPluginLogger(logApi.logger, false);\n\n // Reopen guard: ensure any previous instance is closed before creating new one (avoids duplicate\n // DB instances if host calls register() before stop(), e.g. on SIGUSR1 or rapid reload).\n const old = runtimeRef.value;\n\n let cfg: HybridMemoryConfig;\n try {\n const rawPc = api.pluginConfig;\n const toParse =\n rawPc && typeof rawPc === \"object\" && !Array.isArray(rawPc)\n ? (() => {\n const clone = shallowClonePluginConfigForGatewayMerge(rawPc as Record<string, unknown>);\n applyGatewayEmbeddingInheritanceBeforeParse(clone, api);\n return clone;\n })()\n : rawPc;\n cfg = hybridConfigSchema.parse(toParse);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:config-parse\",\n });\n throw err;\n }\n\n const registrationGeneration = registrationGenerationRef.value + 1;\n registrationGenerationRef.value = registrationGeneration;\n recordReregisterRegistration();\n\n const reusePolicy = resolveReregisterPolicy();\n const reuseDatabases = canReuseDatabasesOnReregister(old, cfg, logApi);\n if (old && reusePolicy === \"reuse-databases\" && !reuseDatabases) {\n logApi.logger.debug?.(\n \"memory-hybrid: re-register falling back to full teardown (reuse policy requested but donor bootstrap not reusable)\",\n );\n }\n const donorRuntime = reuseDatabases && old ? old : null;\n\n if (old) {\n // Clear old timer handles to prevent leaks.\n clearRuntimeTimers(old.timers);\n // Issue #463: Dispose lifecycle hooks (stale session sweep timer, per-session state)\n old.lifecycleHooksHandle?.dispose();\n // Issue #1630: Reset startup memory attribution so the new plugin generation can record fresh checkpoints.\n resetStartupMemoryAttribution();\n // Dispose tool registrations when API exposes unregister/dispose handles.\n old.toolRegistrationHandle?.dispose();\n if (reuseDatabases) {\n recordReregisterDatabaseReuse();\n logApi.logger.debug?.(\n `memory-hybrid: re-register reusing database handles (policy=${resolveReregisterPolicy()})`,\n );\n } else {\n recordReregisterFullTeardown();\n const oldRuntime = old;\n old.pythonBridge?.shutdown().catch(() => {});\n // Let in-flight bootstrap (vault check, embedding verify) finish before permanentClose (#1550 reload race).\n schedulePluginTeardown(async () => {\n await drainOldBootstrap(oldRuntime.bootstrapAsyncInit);\n await drainOldRecall(oldRuntime.recallInFlightRef);\n closeOldDatabases({\n factsDb: oldRuntime.factsDb,\n edictStore: oldRuntime.edictStore,\n vectorDb: oldRuntime.vectorDb,\n credentialsDb: oldRuntime.credentialsDb,\n proposalsDb: oldRuntime.proposalsDb,\n identityReflectionStore: oldRuntime.identityReflectionStore,\n personaStateStore: oldRuntime.personaStateStore,\n eventLog: oldRuntime.eventLog,\n narrativesDb: oldRuntime.narrativesDb,\n aliasDb: oldRuntime.aliasDb,\n eventBus: oldRuntime.eventBus,\n issueStore: oldRuntime.issueStore,\n workflowStore: oldRuntime.workflowStore,\n crystallizationStore: oldRuntime.crystallizationStore,\n toolProposalStore: oldRuntime.toolProposalStore,\n verificationStore: oldRuntime.verificationStore,\n provenanceService: oldRuntime.provenanceService,\n learningsDb: oldRuntime.learningsDb,\n apitapStore: oldRuntime.apitapStore,\n auditStore: oldRuntime.auditStore,\n agentHealthStore: oldRuntime.agentHealthStore,\n });\n });\n }\n runtimeRef.value = null;\n }\n\n if (old && !reuseDatabases) {\n // Wait for teardown to complete before opening new DB handles (#802).\n // Do NOT timeout — proceed only after prior generation fully closes to prevent double-opens.\n const drained = await awaitReloadTeardownBeforeOpen(0);\n if (!drained) {\n throw new Error(\"memory-hybrid: reload teardown did not drain before opening new databases\");\n }\n }\n\n let dbContext: ReturnType<typeof initializeDatabases>;\n try {\n if (donorRuntime) {\n const health = { embeddingsOk: false, credentialsVaultOk: false, lastCheckTime: Date.now() };\n const newBootstrapPromise = createReusedDatabaseBootstrap(\n cfg,\n logApi,\n {\n embeddings: donorRuntime.embeddings,\n wal: donorRuntime.wal,\n credentialsDb: donorRuntime.credentialsDb,\n factsDb: donorRuntime.factsDb,\n vectorDb: donorRuntime.vectorDb,\n aliasDb: donorRuntime.aliasDb,\n resolvedSqlitePath: donorRuntime.resolvedSqlitePath,\n health,\n },\n { bootRegistrationGeneration: registrationGeneration },\n );\n dbContext = databaseContextFromRuntime(donorRuntime, { newBootstrapPromise, health });\n } else {\n dbContext = initializeDatabases(cfg, logApi, { bootRegistrationGeneration: registrationGeneration });\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:init-databases\",\n });\n throw err;\n }\n\n const { resolvedSqlitePath, resolvedLancePath } = dbContext;\n\n logApi.logger.info(\n `memory-hybrid: registered (v${versionInfo.pluginVersion}, memory-manager ${versionInfo.memoryManagerVersion}) sqlite: ${resolvedSqlitePath}, lance: ${resolvedLancePath}`,\n );\n\n // ========================================================================\n // Event Bus for Sensor Sweep (Issue #236)\n // ========================================================================\n\n let eventBus: EventBus | null = donorRuntime?.eventBus ?? null;\n if (cfg.sensorSweep.enabled && !eventBus) {\n try {\n const eventBusPath = join(dirname(resolvedSqlitePath), \"event-bus.db\");\n eventBus = new EventBus(eventBusPath);\n logApi.logger.info(`memory-hybrid: event bus initialized at ${eventBusPath}`);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:event-bus-init\",\n severity: \"warning\",\n });\n eventBus = null;\n }\n }\n\n // ========================================================================\n // Python Bridge (lazy -- only when documents.enabled, spawns on first use)\n // ========================================================================\n\n // Initialized lazily -- PythonBridge only spawns the subprocess on first convert() call.\n // Dependency check runs from plugin service start() so `register()` stays lighter (issue #1111).\n const pythonBridge =\n donorRuntime?.pythonBridge ?? (cfg.documents.enabled ? new PythonBridge(cfg.documents.pythonPath) : null);\n\n // ========================================================================\n // Contextual Variant Generator (Issue #159)\n // ========================================================================\n\n let variantQueue: VariantGenerationQueue | null = null;\n if (cfg.contextualVariants.enabled) {\n const variantGenerator = new ContextualVariantGenerator(cfg.contextualVariants, dbContext.openai);\n variantQueue = new VariantGenerationQueue(variantGenerator, async (factId, variantType, variants) => {\n for (const v of variants) {\n dbContext.factsDb.storeVariant(factId, variantType, v);\n }\n });\n }\n\n // ========================================================================\n // Learnings Intake Buffer (Issue #617)\n // ========================================================================\n\n let learningsDb: LearningsDB | null = donorRuntime?.learningsDb ?? null;\n if (!learningsDb)\n try {\n const learningsDbPath = join(dirname(resolvedSqlitePath), \"learnings.db\");\n learningsDb = new LearningsDB(learningsDbPath);\n logApi.logger.info(`memory-hybrid: learnings DB initialized at ${learningsDbPath}`);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:learnings-db-init\",\n severity: \"warning\",\n });\n learningsDb = null;\n }\n\n // ========================================================================\n // Audit log (Issue #790)\n // ========================================================================\n\n let auditStore: AuditStore | null = donorRuntime?.auditStore ?? null;\n if (!auditStore)\n try {\n const auditPath = auditDbPathForMemorySqlite(resolvedSqlitePath);\n if (auditPath) {\n auditStore = new AuditStore(auditPath);\n logApi.logger.info(`memory-hybrid: audit store initialized at ${auditPath}`);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:audit-store-init\",\n severity: \"warning\",\n });\n auditStore = null;\n }\n\n let agentHealthStore: AgentHealthStore | null = donorRuntime?.agentHealthStore ?? null;\n if (!agentHealthStore)\n try {\n const ahPath = agentHealthDbPathForMemorySqlite(resolvedSqlitePath);\n if (ahPath) {\n agentHealthStore = new AgentHealthStore(ahPath);\n logApi.logger.info(`memory-hybrid: agent health store initialized at ${ahPath}`);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:agent-health-store-init\",\n severity: \"warning\",\n });\n agentHealthStore = null;\n }\n\n // ========================================================================\n // Build PluginRuntime -- single instance-scoped container for all state\n // ========================================================================\n\n const bootstrapSettledRef = { value: false };\n const bootstrapAsyncInit = dbContext.initialized.finally(() => {\n bootstrapSettledRef.value = true;\n });\n\n const newRuntime: PluginRuntime = {\n cfg,\n resolvedLancePath,\n resolvedSqlitePath,\n factsDb: dbContext.factsDb,\n edictStore: dbContext.edictStore,\n vectorDb: dbContext.vectorDb,\n embeddings: dbContext.embeddings,\n embeddingRegistry: dbContext.embeddingRegistry,\n openai: dbContext.openai,\n credentialsDb: dbContext.credentialsDb,\n wal: dbContext.wal,\n proposalsDb: dbContext.proposalsDb,\n identityReflectionStore: dbContext.identityReflectionStore,\n personaStateStore: dbContext.personaStateStore,\n eventLog: dbContext.eventLog,\n narrativesDb: dbContext.narrativesDb,\n aliasDb: dbContext.aliasDb,\n eventBus,\n costTracker: dbContext.costTracker,\n issueStore: dbContext.issueStore,\n workflowStore: dbContext.workflowStore,\n crystallizationStore: dbContext.crystallizationStore,\n toolProposalStore: dbContext.toolProposalStore,\n provenanceService: dbContext.provenanceService,\n verificationStore: dbContext.verificationStore,\n apitapStore: dbContext.apitapStore,\n pythonBridge,\n variantQueue,\n learningsDb,\n auditStore,\n agentHealthStore,\n lifecycleHooksHandle: null, // set after registerLifecycleHooks below\n toolRegistrationHandle: null, // set after registerTools below\n bootstrapAsyncInit,\n bootstrapSettledRef,\n bootstrapHealth: dbContext.health,\n pendingLLMWarnings: createPendingLLMWarnings(),\n currentAgentIdRef: { value: null },\n restartPendingClearedRef: { value: false },\n recallInFlightRef: { value: 0 },\n lastAutoRecallPromptRef: { value: null },\n lastProgressiveIndexIds: [],\n timers: createTimers(),\n };\n\n runtimeRef.value = newRuntime;\n\n const runtime = newRuntime;\n\n // Phase 2.6 / Phase 3: Single plugin context satisfying MemoryPluginAPI (stable internal API).\n const pluginContext: MemoryPluginAPI = {\n factsDb: runtime.factsDb,\n edictStore: runtime.edictStore,\n vectorDb: runtime.vectorDb,\n cfg: runtime.cfg,\n embeddings: runtime.embeddings,\n embeddingRegistry: runtime.embeddingRegistry,\n openai: runtime.openai,\n wal: runtime.wal,\n credentialsDb: runtime.credentialsDb,\n aliasDb: runtime.aliasDb,\n proposalsDb: runtime.proposalsDb,\n eventLog: runtime.eventLog,\n narrativesDb: runtime.narrativesDb,\n provenanceService: runtime.provenanceService,\n issueStore: runtime.issueStore ?? null,\n workflowStore: runtime.workflowStore,\n crystallizationStore: runtime.crystallizationStore,\n toolProposalStore: runtime.toolProposalStore,\n verificationStore: runtime.verificationStore,\n variantQueue: runtime.variantQueue,\n lastProgressiveIndexIds: runtime.lastProgressiveIndexIds,\n currentAgentIdRef: runtime.currentAgentIdRef,\n restartPendingClearedRef: runtime.restartPendingClearedRef,\n recallInFlightRef: runtime.recallInFlightRef,\n lastAutoRecallPromptRef: runtime.lastAutoRecallPromptRef,\n registrationGeneration,\n currentRegistrationGenerationRef: registrationGenerationRef,\n pendingLLMWarnings: runtime.pendingLLMWarnings,\n resolvedSqlitePath: runtime.resolvedSqlitePath,\n timers: { proposalsPruneTimer: runtime.timers.proposalsPruneTimer },\n buildToolScopeFilter,\n walWrite,\n walRemove,\n findSimilarByEmbedding,\n shouldCapture,\n detectCategory,\n runReflection,\n runReflectionRules,\n runReflectionMeta,\n pythonBridge: runtime.pythonBridge,\n apitapStore: runtime.apitapStore,\n auditStore: runtime.auditStore,\n agentHealthStore: runtime.agentHealthStore,\n };\n\n // ========================================================================\n // Tools\n\n try {\n runtime.toolRegistrationHandle = registerTools(pluginContext, logApi);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:tools\",\n });\n throw err;\n }\n\n // CLI Commands — after a hybrid-mem subcommand finishes, tear down DBs and timers so the\n // one-shot `openclaw hybrid-mem …` process can exit (Issue #1039; persistent LanceDB + sweep timer).\n try {\n registerHybridMemCliWithApi(\n logApi,\n {\n factsDb: runtime.factsDb,\n vectorDb: runtime.vectorDb,\n embeddings: runtime.embeddings,\n openai: runtime.openai,\n cfg: runtime.cfg,\n credentialsDb: runtime.credentialsDb,\n aliasDb: runtime.aliasDb,\n wal: runtime.wal,\n proposalsDb: runtime.proposalsDb,\n identityReflectionStore: runtime.identityReflectionStore,\n personaStateStore: runtime.personaStateStore,\n crystallizationStore: runtime.crystallizationStore ?? null,\n eventLog: runtime.eventLog,\n verificationStore: runtime.verificationStore,\n provenanceService: runtime.provenanceService,\n costTracker: runtime.costTracker,\n eventBus: runtime.eventBus,\n resolvedSqlitePath: runtime.resolvedSqlitePath,\n resolvedLancePath: runtime.resolvedLancePath,\n pluginId: PLUGIN_ID,\n detectCategory,\n auditStore: runtime.auditStore,\n agentHealthStore: runtime.agentHealthStore ?? null,\n },\n { onHybridMemCliComplete: () => performHybridMemCliTeardown() },\n );\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:cli\",\n });\n throw err;\n }\n\n // ContextEngine Plugin Slot (Issue #273) -- feature-detected, non-fatal if unavailable\n\n registerContextEngineBestEffort({\n runtime,\n logger: logApi.logger,\n pluginVersion: versionInfo.pluginVersion,\n });\n\n // Lifecycle Hooks (issueStore may be null; issue-related behavior is gated inside hooks)\n try {\n runtime.lifecycleHooksHandle = registerLifecycleHooks(pluginContext, logApi);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:hooks\",\n });\n throw err;\n }\n\n // Service\n\n try {\n api.registerService(\n createPluginService({\n PLUGIN_ID,\n factsDb: runtime.factsDb,\n edictStore: runtime.edictStore,\n vectorDb: runtime.vectorDb,\n embeddings: runtime.embeddings,\n embeddingRegistry: runtime.embeddingRegistry,\n credentialsDb: runtime.credentialsDb,\n proposalsDb: runtime.proposalsDb,\n wal: runtime.wal,\n eventLog: runtime.eventLog,\n cfg: runtime.cfg,\n openai: runtime.openai,\n resolvedLancePath: runtime.resolvedLancePath,\n resolvedSqlitePath: runtime.resolvedSqlitePath,\n api: logApi,\n timers: runtime.timers,\n pythonBridge: runtime.pythonBridge,\n provenanceService: runtime.provenanceService,\n costTracker: runtime.costTracker,\n auditStore: runtime.auditStore ?? null,\n agentHealthStore: runtime.agentHealthStore ?? null,\n }),\n );\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:service\",\n });\n throw err;\n }\n\n // Issue #281 -- Verify cron health on boot\n //\n // When `maintenance.cronReliability.verifyOnBoot` is true (the default), check\n // whether a backup cron entry exists and log a warning if missing. This does NOT\n // auto-install the cron entry -- users must explicitly run `hybrid-mem backup schedule`\n // to install it.\n //\n // This runs asynchronously and is entirely non-fatal: cron check failures\n // (e.g. no `crontab` binary, read-only environment) are logged as debug and do not\n // block the plugin from starting.\n if (cfg.maintenance?.cronReliability?.verifyOnBoot !== false) {\n setImmediate(() => {\n void (async () => {\n try {\n const { execSync } = await import(\"node:child_process\");\n\n // Check if a backup cron is already registered\n let currentCrontab = \"\";\n try {\n currentCrontab = execSync(\"crontab -l 2>/dev/null\", { encoding: \"utf-8\" });\n } catch {\n // No existing crontab\n }\n\n if (currentCrontab.includes(\"hybrid-mem backup\")) {\n // Already scheduled -- nothing to do\n logApi.logger.debug?.(\"memory-hybrid: boot-check -- weekly backup cron already present\");\n return;\n }\n\n // Cron not found -- log warning\n const weeklyExpr = cfg.maintenance?.cronReliability?.weeklyBackupCron ?? \"0 4 * * 0\";\n logApi.logger.warn?.(\n `memory-hybrid: boot-check -- weekly backup cron not found. Run 'hybrid-mem backup schedule' to install (${weeklyExpr}).`,\n );\n } catch (err) {\n // Non-fatal -- crontab may not be available (containers, read-only envs)\n logApi.logger.debug?.(`memory-hybrid: boot-check -- could not verify backup cron (non-fatal): ${err}`);\n }\n })();\n });\n }\n}\n\nexport { runtimeRef, shouldCapture, detectCategory, performHybridMemCliTeardown };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"register-plugin.js","names":["shouldCaptureUtil","detectCategoryUtil"],"sources":["../../setup/register-plugin.ts"],"sourcesContent":["import { dirname, join } from \"node:path\";\nimport type { ClawdbotPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { AgentHealthStore, agentHealthDbPathForMemorySqlite } from \"../backends/agent-health-store.js\";\nimport { AuditStore, auditDbPathForMemorySqlite } from \"../backends/audit-store.js\";\nimport { EventBus } from \"../backends/event-bus.js\";\nimport { LearningsDB } from \"../backends/learnings-db.js\";\nimport type { MemoryPluginAPI } from \"../api/memory-plugin-api.js\";\nimport { type PluginRuntime, clearRuntimeTimers, createTimers } from \"../api/plugin-runtime.js\";\nimport type { HybridMemoryConfig, MemoryCategory } from \"../config.js\";\nimport { hybridConfigSchema } from \"../config/hybrid-schema.js\";\nimport { createPendingLLMWarnings } from \"../services/chat.js\";\nimport { getMemoryTriggers } from \"../services/auto-capture.js\";\nimport { detectCategory as detectCategoryUtil, shouldCapture as shouldCaptureUtil } from \"../services/capture-utils.js\";\nimport { ContextualVariantGenerator, VariantGenerationQueue } from \"../services/contextual-variants.js\";\nimport { capturePluginError } from \"../services/error-reporter.js\";\nimport { runReflection, runReflectionMeta, runReflectionRules } from \"../services/reflection.js\";\nimport { PythonBridge } from \"../services/python-bridge.js\";\nimport { findSimilarByEmbedding } from \"../services/vector-search.js\";\nimport { resetStartupMemoryAttribution } from \"../services/startup-memory-attribution.js\";\nimport { walRemove, walWrite } from \"../services/wal-helpers.js\";\nimport { registerHybridMemCliMetadataOnly } from \"./cli-context/metadata.js\";\nimport { registerHybridMemCliHelpOnlyWithApi } from \"./cli-context/register-help.js\";\nimport { registerHybridMemCliWithApi } from \"./cli-context/register-full.js\";\nimport \"./cli-context.js\";\nimport { closeOldDatabases, createReusedDatabaseBootstrap, initializeDatabases } from \"./bootstrap-databases.js\";\nimport \"./init-databases.js\";\nimport { createPluginService } from \"./plugin-service.js\";\nimport {\n canReuseDatabasesOnReregister,\n databaseContextFromRuntime,\n recordReregisterDatabaseReuse,\n recordReregisterFullTeardown,\n recordReregisterRegistration,\n resetReregisterPolicyForTests,\n resolveReregisterPolicy,\n} from \"./reregister-policy.js\";\nimport {\n applyGatewayEmbeddingInheritanceBeforeParse,\n shallowClonePluginConfigForGatewayMerge,\n} from \"./provider-router.js\";\nimport {\n getHybridMemoryRegistrationState,\n resetHybridMemoryRegistrationStateForTests,\n} from \"./hybrid-memory-generation-state.js\";\nimport {\n blockReloadTeardownBeforeOpen,\n drainOldBootstrap,\n drainOldRecall,\n resetReloadTeardownChainForTests,\n schedulePluginTeardown,\n TEARDOWN_WAIT_MS,\n} from \"./hybrid-memory-reload-coordinator.js\";\nimport { registerContextEngineBestEffort } from \"./register-context-engine.js\";\nimport { registerLifecycleHooks } from \"./register-hooks.js\";\nimport { registerTools } from \"./register-tools.js\";\nimport { PLUGIN_ID } from \"../utils/constants.js\";\nimport { isHybridMemHelpInvocation } from \"../index-help.js\";\nimport { wrapApiLoggerStderrForJsonCli, restoreStdoutAfterJsonCli } from \"../utils/hybrid-mem-json-cli.js\";\nimport {\n getCategoryDecisionRegex,\n getCategoryEntityRegex,\n getCategoryFactRegex,\n getCategoryPreferenceRegex,\n} from \"../utils/language-keywords.js\";\nimport { initPluginLogger } from \"../utils/logger.js\";\nimport { buildToolScopeFilter } from \"../utils/scope-filter.js\";\nimport { versionInfo } from \"../versionInfo.js\";\n\n/** Wrappers for extracted helper functions that need access to per-instance config via runtimeRef. */\nfunction shouldCapture(text: string): boolean {\n return shouldCaptureUtil(text, runtimeRef.value?.cfg.captureMaxChars ?? 5000, getMemoryTriggers());\n}\n\nfunction detectCategory(text: string): MemoryCategory {\n return detectCategoryUtil(\n text,\n getCategoryDecisionRegex(),\n getCategoryPreferenceRegex(),\n getCategoryEntityRegex(),\n getCategoryFactRegex(),\n );\n}\n\nconst runtimeRef: { value: PluginRuntime | null } = { value: null };\nconst registrationGenerationRef = getHybridMemoryRegistrationState().registrationGenerationRef;\n\n/** Guard to prevent concurrent registrations from interleaving (Issue #802 re-entrancy). */\nlet registrationInProgress = false;\n\n/** Shared buffer for registration gate wait/notify (separate from reload coordinator buffer). */\nconst registrationGateWaitBuffer = new SharedArrayBuffer(4);\nconst registrationGateWaitArray = new Int32Array(registrationGateWaitBuffer);\n\n/** Release DBs and timers after a `hybrid-mem` CLI command so the Node process can exit (Issue #1039). */\nasync function performHybridMemCliTeardown(): Promise<void> {\n // Restore stdout before checking runtime ref, so teardown without runtime still cleans up (issue #1618).\n restoreStdoutAfterJsonCli();\n\n const r = runtimeRef.value;\n if (!r) return;\n // Stop long-lived service timers first so one-shot CLI commands can exit promptly.\n clearRuntimeTimers(r.timers);\n try {\n await r.bootstrapAsyncInit;\n } catch {\n /* embedding/vault init may fail; still close handles */\n }\n // Startup can race with command completion; clear again after async init settles.\n clearRuntimeTimers(r.timers);\n try {\n r.lifecycleHooksHandle?.dispose();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:dispose-hooks\",\n });\n }\n try {\n r.toolRegistrationHandle?.dispose();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:dispose-tools\",\n });\n }\n try {\n closeOldDatabases({\n factsDb: r.factsDb,\n edictStore: r.edictStore,\n narrativesDb: r.narrativesDb,\n vectorDb: r.vectorDb,\n credentialsDb: r.credentialsDb,\n proposalsDb: r.proposalsDb,\n identityReflectionStore: r.identityReflectionStore,\n personaStateStore: r.personaStateStore,\n eventLog: r.eventLog,\n aliasDb: r.aliasDb,\n eventBus: r.eventBus,\n issueStore: r.issueStore,\n workflowStore: r.workflowStore,\n crystallizationStore: r.crystallizationStore,\n toolProposalStore: r.toolProposalStore,\n verificationStore: r.verificationStore,\n provenanceService: r.provenanceService,\n learningsDb: r.learningsDb,\n apitapStore: r.apitapStore,\n auditStore: r.auditStore,\n agentHealthStore: r.agentHealthStore,\n });\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:close-databases\",\n });\n }\n try {\n await r.pythonBridge?.shutdown();\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"cli\",\n operation: \"hybrid-mem-teardown:python-bridge\",\n });\n }\n}\n\nexport function runMemoryHybridRegister(api: ClawdbotPluginApi): void {\n // OpenClaw `loadOpenClawPluginCliRegistry` — metadata only; no DBs or native deps (issue #1111).\n // Check this FIRST, before any logger init or config parsing, so an incomplete config\n // cannot block lightweight metadata registration.\n //\n // Issue #1209/#XXXX: Always register CLI metadata even when service markers are present in the environment.\n // Service markers (OPENCLAW_SERVICE_KIND, OPENCLAW_SERVICE_MARKER) should only prevent full plugin\n // initialization (databases, timers), not CLI metadata registration. Without this, `openclaw hybrid-mem`\n // commands become unavailable in cron/service environments where these markers leak.\n if (api.registrationMode === \"cli-metadata\") {\n registerHybridMemCliMetadataOnly(api);\n return;\n }\n\n // Help invocations should be cheap and deterministic: register the command tree but do not\n // bootstrap DBs or start background checks/timers that can keep Node alive after help prints.\n // Examples: `openclaw hybrid-mem --help`, `openclaw hybrid-mem verify --help`.\n if (isHybridMemHelpInvocation(process.argv)) {\n registerHybridMemCliHelpOnlyWithApi(api);\n return;\n }\n\n // Issue #802 re-entrancy: serialize concurrent register() calls. OpenClaw requires register()\n // to be synchronous (no Promise return), so we block with Atomics.wait instead of async/await.\n while (registrationInProgress) {\n Atomics.wait(registrationGateWaitArray, 0, 0, 50);\n }\n registrationInProgress = true;\n try {\n runMemoryHybridRegisterImpl(api);\n } finally {\n registrationInProgress = false;\n Atomics.notify(registrationGateWaitArray, 0, 1);\n }\n}\n\nfunction runMemoryHybridRegisterImpl(api: ClawdbotPluginApi): void {\n // Issue #1230 / #1234: JSON CLI must not write plugin telemetry to stdout (cron harnesses, jq).\n // Wrap api.logger to stderr before bootstrap; keep pluginLogger on that same logger delegate.\n const logApi = wrapApiLoggerStderrForJsonCli(api);\n initPluginLogger(logApi.logger, false);\n\n // Reopen guard: ensure any previous instance is closed before creating new one (avoids duplicate\n // DB instances if host calls register() before stop(), e.g. on SIGUSR1 or rapid reload).\n const old = runtimeRef.value;\n\n let cfg: HybridMemoryConfig;\n let parsedCfgSnapshot: HybridMemoryConfig;\n try {\n const rawPc = api.pluginConfig;\n const buildConfigToParse = (): unknown => {\n if (rawPc && typeof rawPc === \"object\" && !Array.isArray(rawPc)) {\n const clone = shallowClonePluginConfigForGatewayMerge(rawPc as Record<string, unknown>);\n applyGatewayEmbeddingInheritanceBeforeParse(clone, api);\n return clone;\n }\n return rawPc;\n };\n cfg = hybridConfigSchema.parse(buildConfigToParse());\n // Re-parse from raw plugin config so snapshot matches a fresh register() parse (structuredClone\n // drops non-enumerable fields such as credentials.encryptionKey).\n parsedCfgSnapshot = hybridConfigSchema.parse(buildConfigToParse());\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:config-parse\",\n });\n throw err;\n }\n\n const registrationGeneration = registrationGenerationRef.value + 1;\n registrationGenerationRef.value = registrationGeneration;\n recordReregisterRegistration();\n\n const reusePolicy = resolveReregisterPolicy();\n const reuseDatabases = canReuseDatabasesOnReregister(old, cfg, logApi);\n if (old && reusePolicy === \"reuse-databases\" && !reuseDatabases) {\n logApi.logger.debug?.(\n \"memory-hybrid: re-register falling back to full teardown (reuse policy requested but donor bootstrap not reusable)\",\n );\n }\n const donorRuntime = reuseDatabases && old ? old : null;\n\n if (old) {\n // Clear old timer handles to prevent leaks.\n clearRuntimeTimers(old.timers);\n // Issue #463: Dispose lifecycle hooks (stale session sweep timer, per-session state)\n old.lifecycleHooksHandle?.dispose();\n // Issue #1630: Reset startup memory attribution so the new plugin generation can record fresh checkpoints.\n resetStartupMemoryAttribution();\n // Dispose tool registrations when API exposes unregister/dispose handles.\n old.toolRegistrationHandle?.dispose();\n if (reuseDatabases) {\n recordReregisterDatabaseReuse();\n logApi.logger.debug?.(\n `memory-hybrid: re-register reusing database handles (policy=${resolveReregisterPolicy()})`,\n );\n } else {\n recordReregisterFullTeardown();\n const oldRuntime = old;\n old.pythonBridge?.shutdown().catch(() => {});\n // Let in-flight bootstrap (vault check, embedding verify) finish before permanentClose (#1550 reload race).\n schedulePluginTeardown(async () => {\n await drainOldBootstrap(oldRuntime.bootstrapAsyncInit);\n await drainOldRecall(oldRuntime.recallInFlightRef);\n closeOldDatabases({\n factsDb: oldRuntime.factsDb,\n edictStore: oldRuntime.edictStore,\n vectorDb: oldRuntime.vectorDb,\n credentialsDb: oldRuntime.credentialsDb,\n proposalsDb: oldRuntime.proposalsDb,\n identityReflectionStore: oldRuntime.identityReflectionStore,\n personaStateStore: oldRuntime.personaStateStore,\n eventLog: oldRuntime.eventLog,\n narrativesDb: oldRuntime.narrativesDb,\n aliasDb: oldRuntime.aliasDb,\n eventBus: oldRuntime.eventBus,\n issueStore: oldRuntime.issueStore,\n workflowStore: oldRuntime.workflowStore,\n crystallizationStore: oldRuntime.crystallizationStore,\n toolProposalStore: oldRuntime.toolProposalStore,\n verificationStore: oldRuntime.verificationStore,\n provenanceService: oldRuntime.provenanceService,\n learningsDb: oldRuntime.learningsDb,\n apitapStore: oldRuntime.apitapStore,\n auditStore: oldRuntime.auditStore,\n agentHealthStore: oldRuntime.agentHealthStore,\n });\n });\n }\n runtimeRef.value = null;\n }\n\n if (old && !reuseDatabases) {\n // Wait for teardown to complete before opening new DB handles (#802).\n // Bounded wait: drains (3s + 2s) + buffer fit within TEARDOWN_WAIT_MS.\n const drained = blockReloadTeardownBeforeOpen(TEARDOWN_WAIT_MS);\n if (!drained) {\n throw new Error(\"memory-hybrid: reload teardown did not drain before opening new databases\");\n }\n }\n\n let dbContext: ReturnType<typeof initializeDatabases>;\n try {\n if (donorRuntime) {\n const health = { embeddingsOk: false, credentialsVaultOk: false, lastCheckTime: Date.now() };\n const newBootstrapPromise = createReusedDatabaseBootstrap(\n cfg,\n logApi,\n {\n embeddings: donorRuntime.embeddings,\n wal: donorRuntime.wal,\n credentialsDb: donorRuntime.credentialsDb,\n factsDb: donorRuntime.factsDb,\n vectorDb: donorRuntime.vectorDb,\n aliasDb: donorRuntime.aliasDb,\n resolvedSqlitePath: donorRuntime.resolvedSqlitePath,\n health,\n },\n { bootRegistrationGeneration: registrationGeneration },\n );\n dbContext = databaseContextFromRuntime(donorRuntime, { newBootstrapPromise, health });\n } else {\n dbContext = initializeDatabases(cfg, logApi, { bootRegistrationGeneration: registrationGeneration });\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:init-databases\",\n });\n throw err;\n }\n\n const { resolvedSqlitePath, resolvedLancePath } = dbContext;\n\n logApi.logger.info(\n `memory-hybrid: registered (v${versionInfo.pluginVersion}, memory-manager ${versionInfo.memoryManagerVersion}) sqlite: ${resolvedSqlitePath}, lance: ${resolvedLancePath}`,\n );\n\n // ========================================================================\n // Event Bus for Sensor Sweep (Issue #236)\n // ========================================================================\n\n let eventBus: EventBus | null = donorRuntime?.eventBus ?? null;\n if (cfg.sensorSweep.enabled && !eventBus) {\n try {\n const eventBusPath = join(dirname(resolvedSqlitePath), \"event-bus.db\");\n eventBus = new EventBus(eventBusPath);\n logApi.logger.info(`memory-hybrid: event bus initialized at ${eventBusPath}`);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:event-bus-init\",\n severity: \"warning\",\n });\n eventBus = null;\n }\n }\n\n // ========================================================================\n // Python Bridge (lazy -- only when documents.enabled, spawns on first use)\n // ========================================================================\n\n // Initialized lazily -- PythonBridge only spawns the subprocess on first convert() call.\n // Dependency check runs from plugin service start() so `register()` stays lighter (issue #1111).\n const pythonBridge =\n donorRuntime?.pythonBridge ?? (cfg.documents.enabled ? new PythonBridge(cfg.documents.pythonPath) : null);\n\n // ========================================================================\n // Contextual Variant Generator (Issue #159)\n // ========================================================================\n\n let variantQueue: VariantGenerationQueue | null = null;\n if (cfg.contextualVariants.enabled) {\n const variantGenerator = new ContextualVariantGenerator(cfg.contextualVariants, dbContext.openai);\n variantQueue = new VariantGenerationQueue(variantGenerator, async (factId, variantType, variants) => {\n for (const v of variants) {\n dbContext.factsDb.storeVariant(factId, variantType, v);\n }\n });\n }\n\n // ========================================================================\n // Learnings Intake Buffer (Issue #617)\n // ========================================================================\n\n let learningsDb: LearningsDB | null = donorRuntime?.learningsDb ?? null;\n if (!learningsDb)\n try {\n const learningsDbPath = join(dirname(resolvedSqlitePath), \"learnings.db\");\n learningsDb = new LearningsDB(learningsDbPath);\n logApi.logger.info(`memory-hybrid: learnings DB initialized at ${learningsDbPath}`);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:learnings-db-init\",\n severity: \"warning\",\n });\n learningsDb = null;\n }\n\n // ========================================================================\n // Audit log (Issue #790)\n // ========================================================================\n\n let auditStore: AuditStore | null = donorRuntime?.auditStore ?? null;\n if (!auditStore)\n try {\n const auditPath = auditDbPathForMemorySqlite(resolvedSqlitePath);\n if (auditPath) {\n auditStore = new AuditStore(auditPath);\n logApi.logger.info(`memory-hybrid: audit store initialized at ${auditPath}`);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:audit-store-init\",\n severity: \"warning\",\n });\n auditStore = null;\n }\n\n let agentHealthStore: AgentHealthStore | null = donorRuntime?.agentHealthStore ?? null;\n if (!agentHealthStore)\n try {\n const ahPath = agentHealthDbPathForMemorySqlite(resolvedSqlitePath);\n if (ahPath) {\n agentHealthStore = new AgentHealthStore(ahPath);\n logApi.logger.info(`memory-hybrid: agent health store initialized at ${ahPath}`);\n }\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:agent-health-store-init\",\n severity: \"warning\",\n });\n agentHealthStore = null;\n }\n\n // ========================================================================\n // Build PluginRuntime -- single instance-scoped container for all state\n // ========================================================================\n\n const bootstrapSettledRef = { value: false };\n const bootstrapAsyncInit = dbContext.initialized.finally(() => {\n bootstrapSettledRef.value = true;\n });\n\n const newRuntime: PluginRuntime = {\n cfg,\n parsedCfgSnapshot,\n resolvedLancePath,\n resolvedSqlitePath,\n factsDb: dbContext.factsDb,\n edictStore: dbContext.edictStore,\n vectorDb: dbContext.vectorDb,\n embeddings: dbContext.embeddings,\n embeddingRegistry: dbContext.embeddingRegistry,\n openai: dbContext.openai,\n credentialsDb: dbContext.credentialsDb,\n wal: dbContext.wal,\n proposalsDb: dbContext.proposalsDb,\n identityReflectionStore: dbContext.identityReflectionStore,\n personaStateStore: dbContext.personaStateStore,\n eventLog: dbContext.eventLog,\n narrativesDb: dbContext.narrativesDb,\n aliasDb: dbContext.aliasDb,\n eventBus,\n costTracker: dbContext.costTracker,\n issueStore: dbContext.issueStore,\n workflowStore: dbContext.workflowStore,\n crystallizationStore: dbContext.crystallizationStore,\n toolProposalStore: dbContext.toolProposalStore,\n provenanceService: dbContext.provenanceService,\n verificationStore: dbContext.verificationStore,\n apitapStore: dbContext.apitapStore,\n pythonBridge,\n variantQueue,\n learningsDb,\n auditStore,\n agentHealthStore,\n lifecycleHooksHandle: null, // set after registerLifecycleHooks below\n toolRegistrationHandle: null, // set after registerTools below\n bootstrapAsyncInit,\n bootstrapSettledRef,\n bootstrapHealth: dbContext.health,\n pendingLLMWarnings: createPendingLLMWarnings(),\n currentAgentIdRef: { value: null },\n restartPendingClearedRef: { value: false },\n recallInFlightRef: { value: 0 },\n lastAutoRecallPromptRef: { value: null },\n lastProgressiveIndexIds: [],\n timers: createTimers(),\n };\n\n runtimeRef.value = newRuntime;\n\n const runtime = newRuntime;\n\n // Phase 2.6 / Phase 3: Single plugin context satisfying MemoryPluginAPI (stable internal API).\n const pluginContext: MemoryPluginAPI = {\n factsDb: runtime.factsDb,\n edictStore: runtime.edictStore,\n vectorDb: runtime.vectorDb,\n cfg: runtime.cfg,\n embeddings: runtime.embeddings,\n embeddingRegistry: runtime.embeddingRegistry,\n openai: runtime.openai,\n wal: runtime.wal,\n credentialsDb: runtime.credentialsDb,\n aliasDb: runtime.aliasDb,\n proposalsDb: runtime.proposalsDb,\n eventLog: runtime.eventLog,\n narrativesDb: runtime.narrativesDb,\n provenanceService: runtime.provenanceService,\n issueStore: runtime.issueStore ?? null,\n workflowStore: runtime.workflowStore,\n crystallizationStore: runtime.crystallizationStore,\n toolProposalStore: runtime.toolProposalStore,\n verificationStore: runtime.verificationStore,\n variantQueue: runtime.variantQueue,\n lastProgressiveIndexIds: runtime.lastProgressiveIndexIds,\n currentAgentIdRef: runtime.currentAgentIdRef,\n restartPendingClearedRef: runtime.restartPendingClearedRef,\n recallInFlightRef: runtime.recallInFlightRef,\n lastAutoRecallPromptRef: runtime.lastAutoRecallPromptRef,\n registrationGeneration,\n currentRegistrationGenerationRef: registrationGenerationRef,\n pendingLLMWarnings: runtime.pendingLLMWarnings,\n resolvedSqlitePath: runtime.resolvedSqlitePath,\n timers: { proposalsPruneTimer: runtime.timers.proposalsPruneTimer },\n buildToolScopeFilter,\n walWrite,\n walRemove,\n findSimilarByEmbedding,\n shouldCapture,\n detectCategory,\n runReflection,\n runReflectionRules,\n runReflectionMeta,\n pythonBridge: runtime.pythonBridge,\n apitapStore: runtime.apitapStore,\n auditStore: runtime.auditStore,\n agentHealthStore: runtime.agentHealthStore,\n };\n\n // ========================================================================\n // Tools\n\n try {\n runtime.toolRegistrationHandle = registerTools(pluginContext, logApi);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:tools\",\n });\n throw err;\n }\n\n // CLI Commands — after a hybrid-mem subcommand finishes, tear down DBs and timers so the\n // one-shot `openclaw hybrid-mem …` process can exit (Issue #1039; persistent LanceDB + sweep timer).\n try {\n registerHybridMemCliWithApi(\n logApi,\n {\n factsDb: runtime.factsDb,\n vectorDb: runtime.vectorDb,\n embeddings: runtime.embeddings,\n openai: runtime.openai,\n cfg: runtime.cfg,\n credentialsDb: runtime.credentialsDb,\n aliasDb: runtime.aliasDb,\n wal: runtime.wal,\n proposalsDb: runtime.proposalsDb,\n identityReflectionStore: runtime.identityReflectionStore,\n personaStateStore: runtime.personaStateStore,\n crystallizationStore: runtime.crystallizationStore ?? null,\n eventLog: runtime.eventLog,\n verificationStore: runtime.verificationStore,\n provenanceService: runtime.provenanceService,\n costTracker: runtime.costTracker,\n eventBus: runtime.eventBus,\n resolvedSqlitePath: runtime.resolvedSqlitePath,\n resolvedLancePath: runtime.resolvedLancePath,\n pluginId: PLUGIN_ID,\n detectCategory,\n auditStore: runtime.auditStore,\n agentHealthStore: runtime.agentHealthStore ?? null,\n },\n { onHybridMemCliComplete: () => performHybridMemCliTeardown() },\n );\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:cli\",\n });\n throw err;\n }\n\n // ContextEngine Plugin Slot (Issue #273) -- feature-detected, non-fatal if unavailable\n\n registerContextEngineBestEffort({\n runtime,\n logger: logApi.logger,\n pluginVersion: versionInfo.pluginVersion,\n });\n\n // Lifecycle Hooks (issueStore may be null; issue-related behavior is gated inside hooks)\n try {\n runtime.lifecycleHooksHandle = registerLifecycleHooks(pluginContext, logApi);\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:hooks\",\n });\n throw err;\n }\n\n // Service\n\n try {\n api.registerService(\n createPluginService({\n PLUGIN_ID,\n factsDb: runtime.factsDb,\n edictStore: runtime.edictStore,\n vectorDb: runtime.vectorDb,\n embeddings: runtime.embeddings,\n embeddingRegistry: runtime.embeddingRegistry,\n credentialsDb: runtime.credentialsDb,\n proposalsDb: runtime.proposalsDb,\n wal: runtime.wal,\n eventLog: runtime.eventLog,\n cfg: runtime.cfg,\n openai: runtime.openai,\n resolvedLancePath: runtime.resolvedLancePath,\n resolvedSqlitePath: runtime.resolvedSqlitePath,\n api: logApi,\n timers: runtime.timers,\n pythonBridge: runtime.pythonBridge,\n provenanceService: runtime.provenanceService,\n costTracker: runtime.costTracker,\n auditStore: runtime.auditStore ?? null,\n agentHealthStore: runtime.agentHealthStore ?? null,\n }),\n );\n } catch (err) {\n capturePluginError(err instanceof Error ? err : new Error(String(err)), {\n subsystem: \"registration\",\n operation: \"plugin-register:service\",\n });\n throw err;\n }\n\n // Issue #281 -- Verify cron health on boot\n //\n // When `maintenance.cronReliability.verifyOnBoot` is true (the default), check\n // whether a backup cron entry exists and log a warning if missing. This does NOT\n // auto-install the cron entry -- users must explicitly run `hybrid-mem backup schedule`\n // to install it.\n //\n // This runs asynchronously and is entirely non-fatal: cron check failures\n // (e.g. no `crontab` binary, read-only environment) are logged as debug and do not\n // block the plugin from starting.\n if (cfg.maintenance?.cronReliability?.verifyOnBoot !== false) {\n setImmediate(() => {\n void (async () => {\n try {\n const { execSync } = await import(\"node:child_process\");\n\n // Check if a backup cron is already registered\n let currentCrontab = \"\";\n try {\n currentCrontab = execSync(\"crontab -l 2>/dev/null\", { encoding: \"utf-8\" });\n } catch {\n // No existing crontab\n }\n\n if (currentCrontab.includes(\"hybrid-mem backup\")) {\n // Already scheduled -- nothing to do\n logApi.logger.debug?.(\"memory-hybrid: boot-check -- weekly backup cron already present\");\n return;\n }\n\n // Cron not found -- log warning\n const weeklyExpr = cfg.maintenance?.cronReliability?.weeklyBackupCron ?? \"0 4 * * 0\";\n logApi.logger.warn?.(\n `memory-hybrid: boot-check -- weekly backup cron not found. Run 'hybrid-mem backup schedule' to install (${weeklyExpr}).`,\n );\n } catch (err) {\n // Non-fatal -- crontab may not be available (containers, read-only envs)\n logApi.logger.debug?.(`memory-hybrid: boot-check -- could not verify backup cron (non-fatal): ${err}`);\n }\n })();\n });\n }\n}\n\nexport { runtimeRef, shouldCapture, detectCategory, performHybridMemCliTeardown };\n\n/** Reset plugin singleton state between vitest cases (runtime, reload chain, generation). */\nexport function resetPluginRegistrationStateForTests(): void {\n const old = runtimeRef.value;\n if (old) {\n clearRuntimeTimers(old.timers);\n try {\n old.lifecycleHooksHandle?.dispose();\n } catch {\n /* non-fatal */\n }\n try {\n old.toolRegistrationHandle?.dispose();\n } catch {\n /* non-fatal */\n }\n runtimeRef.value = null;\n }\n resetReloadTeardownChainForTests();\n resetReregisterPolicyForTests();\n resetHybridMemoryRegistrationStateForTests();\n}\n"],"mappings":"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"}
|
|
@@ -35,7 +35,7 @@ function canReuseDatabasesOnReregister(old, cfg, api) {
|
|
|
35
35
|
const nextSqlite = api.resolvePath(cfg.sqlitePath);
|
|
36
36
|
const nextLance = api.resolvePath(cfg.lanceDbPath);
|
|
37
37
|
if (old.resolvedSqlitePath !== nextSqlite || old.resolvedLancePath !== nextLance) return false;
|
|
38
|
-
const oldCfg = old.cfg;
|
|
38
|
+
const oldCfg = old.parsedCfgSnapshot ?? old.cfg;
|
|
39
39
|
if (!oldCfg?.embedding || !cfg.embedding) return false;
|
|
40
40
|
if (oldCfg.embedding.provider !== cfg.embedding.provider || oldCfg.embedding.model !== cfg.embedding.model || oldCfg.embedding.endpoint !== cfg.embedding.endpoint || oldCfg.embedding.apiKey !== cfg.embedding.apiKey || oldCfg.embedding.deployment !== cfg.embedding.deployment) return false;
|
|
41
41
|
const oldLlm = oldCfg.llm;
|
|
@@ -45,7 +45,7 @@ function canReuseDatabasesOnReregister(old, cfg, api) {
|
|
|
45
45
|
if (JSON.stringify(oldLlm?.nano) !== JSON.stringify(newLlm?.nano)) return false;
|
|
46
46
|
if (JSON.stringify(oldLlm?.providers) !== JSON.stringify(newLlm?.providers)) return false;
|
|
47
47
|
if (oldCfg.credentials?.enabled !== cfg.credentials?.enabled) return false;
|
|
48
|
-
if (oldCfg.credentials?.encryptionKey !== cfg.credentials?.encryptionKey) return false;
|
|
48
|
+
if ((oldCfg.credentials?.encryptionKey ?? "") !== (cfg.credentials?.encryptionKey ?? "")) return false;
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
51
|
/** Snapshot of initializeDatabases() return shape for register-plugin when reusing handles. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reregister-policy.js","names":[],"sources":["../../setup/reregister-policy.ts"],"sourcesContent":["/** @module reregister-policy — Hot-reload DB teardown vs reuse (OPENCLAW_HYBRID_MEM_REREGISTER_POLICY). */\nimport type { PluginRuntime } from \"../api/plugin-runtime.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport { getEnv } from \"../utils/env-manager.js\";\n\nexport type ReregisterPolicy = \"default\" | \"full\" | \"reuse-databases\";\n\nexport type ReregisterMetrics = {\n policy: string;\n registrations: number;\n fullTeardowns: number;\n databaseReuses: number;\n};\n\nexport const reregisterMetrics: ReregisterMetrics = {\n policy: \"(unset)\",\n registrations: 0,\n fullTeardowns: 0,\n databaseReuses: 0,\n};\n\n/** Resolved once per process from OPENCLAW_HYBRID_MEM_REREGISTER_POLICY. */\nlet cachedPolicy: ReregisterPolicy | null = null;\n\nexport function resolveReregisterPolicy(): ReregisterPolicy {\n if (cachedPolicy) return cachedPolicy;\n const raw = getEnv(\"OPENCLAW_HYBRID_MEM_REREGISTER_POLICY\")?.trim().toLowerCase();\n if (raw === \"reuse-databases\") cachedPolicy = \"reuse-databases\";\n else if (raw === \"full\") cachedPolicy = \"full\";\n else cachedPolicy = \"default\";\n reregisterMetrics.policy = cachedPolicy;\n return cachedPolicy;\n}\n\n/** Default and explicit `full` always close and reopen database handles on re-register. */\nexport function shouldFullTeardownOnReregister(): boolean {\n return resolveReregisterPolicy() !== \"reuse-databases\";\n}\n\nexport function resetReregisterPolicyForTests(): void {\n cachedPolicy = null;\n reregisterMetrics.policy = \"(unset)\";\n reregisterMetrics.registrations = 0;\n reregisterMetrics.fullTeardowns = 0;\n reregisterMetrics.databaseReuses = 0;\n}\n\nexport function recordReregisterRegistration(): void {\n resolveReregisterPolicy();\n reregisterMetrics.registrations += 1;\n}\n\nexport function recordReregisterFullTeardown(): void {\n reregisterMetrics.fullTeardowns += 1;\n}\n\nexport function recordReregisterDatabaseReuse(): void {\n reregisterMetrics.databaseReuses += 1;\n}\n\ntype PathResolvingApi = { resolvePath: (p: string) => string };\n\n/** True when re-register may keep existing SQLite/Lance handles (paths unchanged). */\nexport function canReuseDatabasesOnReregister(\n old: PluginRuntime | null,\n cfg: HybridMemoryConfig,\n api: PathResolvingApi,\n): boolean {\n if (!old) return false;\n if (resolveReregisterPolicy() !== \"reuse-databases\") return false;\n // Reuse only after donor bootstrap has fully settled. If bootstrap is still in flight when\n // generation bumps, supersession can skip one-shot init work (vault/migration checks).\n if (!old.bootstrapSettledRef || old.bootstrapSettledRef.value !== true) return false;\n const nextSqlite = api.resolvePath(cfg.sqlitePath);\n const nextLance = api.resolvePath(cfg.lanceDbPath);\n if (old.resolvedSqlitePath !== nextSqlite || old.resolvedLancePath !== nextLance) return false;\n\n // Compare
|
|
1
|
+
{"version":3,"file":"reregister-policy.js","names":[],"sources":["../../setup/reregister-policy.ts"],"sourcesContent":["/** @module reregister-policy — Hot-reload DB teardown vs reuse (OPENCLAW_HYBRID_MEM_REREGISTER_POLICY). */\nimport type { PluginRuntime } from \"../api/plugin-runtime.js\";\nimport type { HybridMemoryConfig } from \"../config.js\";\nimport { getEnv } from \"../utils/env-manager.js\";\n\nexport type ReregisterPolicy = \"default\" | \"full\" | \"reuse-databases\";\n\nexport type ReregisterMetrics = {\n policy: string;\n registrations: number;\n fullTeardowns: number;\n databaseReuses: number;\n};\n\nexport const reregisterMetrics: ReregisterMetrics = {\n policy: \"(unset)\",\n registrations: 0,\n fullTeardowns: 0,\n databaseReuses: 0,\n};\n\n/** Resolved once per process from OPENCLAW_HYBRID_MEM_REREGISTER_POLICY. */\nlet cachedPolicy: ReregisterPolicy | null = null;\n\nexport function resolveReregisterPolicy(): ReregisterPolicy {\n if (cachedPolicy) return cachedPolicy;\n const raw = getEnv(\"OPENCLAW_HYBRID_MEM_REREGISTER_POLICY\")?.trim().toLowerCase();\n if (raw === \"reuse-databases\") cachedPolicy = \"reuse-databases\";\n else if (raw === \"full\") cachedPolicy = \"full\";\n else cachedPolicy = \"default\";\n reregisterMetrics.policy = cachedPolicy;\n return cachedPolicy;\n}\n\n/** Default and explicit `full` always close and reopen database handles on re-register. */\nexport function shouldFullTeardownOnReregister(): boolean {\n return resolveReregisterPolicy() !== \"reuse-databases\";\n}\n\nexport function resetReregisterPolicyForTests(): void {\n cachedPolicy = null;\n reregisterMetrics.policy = \"(unset)\";\n reregisterMetrics.registrations = 0;\n reregisterMetrics.fullTeardowns = 0;\n reregisterMetrics.databaseReuses = 0;\n}\n\nexport function recordReregisterRegistration(): void {\n resolveReregisterPolicy();\n reregisterMetrics.registrations += 1;\n}\n\nexport function recordReregisterFullTeardown(): void {\n reregisterMetrics.fullTeardowns += 1;\n}\n\nexport function recordReregisterDatabaseReuse(): void {\n reregisterMetrics.databaseReuses += 1;\n}\n\ntype PathResolvingApi = { resolvePath: (p: string) => string };\n\n/** True when re-register may keep existing SQLite/Lance handles (paths unchanged). */\nexport function canReuseDatabasesOnReregister(\n old: PluginRuntime | null,\n cfg: HybridMemoryConfig,\n api: PathResolvingApi,\n): boolean {\n if (!old) return false;\n if (resolveReregisterPolicy() !== \"reuse-databases\") return false;\n // Reuse only after donor bootstrap has fully settled. If bootstrap is still in flight when\n // generation bumps, supersession can skip one-shot init work (vault/migration checks).\n if (!old.bootstrapSettledRef || old.bootstrapSettledRef.value !== true) return false;\n const nextSqlite = api.resolvePath(cfg.sqlitePath);\n const nextLance = api.resolvePath(cfg.lanceDbPath);\n if (old.resolvedSqlitePath !== nextSqlite || old.resolvedLancePath !== nextLance) return false;\n\n // Compare parse-time config, not bootstrap-mutated runtime cfg (initializeDatabases may inject llm tiers).\n const oldCfg = old.parsedCfgSnapshot ?? old.cfg;\n if (!oldCfg?.embedding || !cfg.embedding) return false;\n if (\n oldCfg.embedding.provider !== cfg.embedding.provider ||\n oldCfg.embedding.model !== cfg.embedding.model ||\n oldCfg.embedding.endpoint !== cfg.embedding.endpoint ||\n oldCfg.embedding.apiKey !== cfg.embedding.apiKey ||\n oldCfg.embedding.deployment !== cfg.embedding.deployment\n ) {\n return false;\n }\n\n // Compare LLM config to detect model/provider changes that require rebuilding openai client\n const oldLlm = oldCfg.llm;\n const newLlm = cfg.llm;\n if (JSON.stringify(oldLlm?.default) !== JSON.stringify(newLlm?.default)) return false;\n if (JSON.stringify(oldLlm?.heavy) !== JSON.stringify(newLlm?.heavy)) return false;\n if (JSON.stringify(oldLlm?.nano) !== JSON.stringify(newLlm?.nano)) return false;\n if (JSON.stringify(oldLlm?.providers) !== JSON.stringify(newLlm?.providers)) return false;\n\n // Compare credentials.enabled to detect when vault should be opened or closed\n if (oldCfg.credentials?.enabled !== cfg.credentials?.enabled) return false;\n // encryptionKey is non-enumerable on parsed config; treat missing as empty (structuredClone drops it).\n const oldEncKey = oldCfg.credentials?.encryptionKey ?? \"\";\n const newEncKey = cfg.credentials?.encryptionKey ?? \"\";\n if (oldEncKey !== newEncKey) return false;\n\n return true;\n}\n\n/** Snapshot of initializeDatabases() return shape for register-plugin when reusing handles. */\nexport function databaseContextFromRuntime(\n old: PluginRuntime,\n opts?: {\n newBootstrapPromise?: Promise<void>;\n health?: { embeddingsOk: boolean; credentialsVaultOk: boolean; lastCheckTime: number };\n },\n) {\n const health = opts?.health ??\n old.bootstrapHealth ?? { embeddingsOk: false, credentialsVaultOk: false, lastCheckTime: Date.now() };\n return {\n factsDb: old.factsDb,\n edictStore: old.edictStore,\n vectorDb: old.vectorDb,\n embeddings: old.embeddings,\n embeddingRegistry: old.embeddingRegistry,\n openai: old.openai,\n credentialsDb: old.credentialsDb,\n wal: old.wal,\n proposalsDb: old.proposalsDb,\n identityReflectionStore: old.identityReflectionStore,\n personaStateStore: old.personaStateStore,\n eventLog: old.eventLog,\n narrativesDb: old.narrativesDb!,\n aliasDb: old.aliasDb,\n issueStore: old.issueStore!,\n workflowStore: old.workflowStore!,\n crystallizationStore: old.crystallizationStore!,\n toolProposalStore: old.toolProposalStore!,\n verificationStore: old.verificationStore,\n provenanceService: old.provenanceService,\n costTracker: old.costTracker,\n resolvedLancePath: old.resolvedLancePath,\n resolvedSqlitePath: old.resolvedSqlitePath,\n apitapStore: old.apitapStore!,\n initialized: opts?.newBootstrapPromise ?? old.bootstrapAsyncInit,\n health,\n };\n}\n"],"mappings":";;AAcA,MAAa,oBAAuC;CAClD,QAAQ;CACR,eAAe;CACf,eAAe;CACf,gBAAgB;CACjB;;AAGD,IAAI,eAAwC;AAE5C,SAAgB,0BAA4C;CAC1D,IAAI,cAAc,OAAO;CACzB,MAAM,MAAM,OAAO,wCAAwC,EAAE,MAAM,CAAC,aAAa;CACjF,IAAI,QAAQ,mBAAmB,eAAe;MACzC,IAAI,QAAQ,QAAQ,eAAe;MACnC,eAAe;CACpB,kBAAkB,SAAS;CAC3B,OAAO;;AAgBT,SAAgB,+BAAqC;CACnD,yBAAyB;CACzB,kBAAkB,iBAAiB;;AAGrC,SAAgB,+BAAqC;CACnD,kBAAkB,iBAAiB;;AAGrC,SAAgB,gCAAsC;CACpD,kBAAkB,kBAAkB;;;AAMtC,SAAgB,8BACd,KACA,KACA,KACS;CACT,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,yBAAyB,KAAK,mBAAmB,OAAO;CAG5D,IAAI,CAAC,IAAI,uBAAuB,IAAI,oBAAoB,UAAU,MAAM,OAAO;CAC/E,MAAM,aAAa,IAAI,YAAY,IAAI,WAAW;CAClD,MAAM,YAAY,IAAI,YAAY,IAAI,YAAY;CAClD,IAAI,IAAI,uBAAuB,cAAc,IAAI,sBAAsB,WAAW,OAAO;CAGzF,MAAM,SAAS,IAAI,qBAAqB,IAAI;CAC5C,IAAI,CAAC,QAAQ,aAAa,CAAC,IAAI,WAAW,OAAO;CACjD,IACE,OAAO,UAAU,aAAa,IAAI,UAAU,YAC5C,OAAO,UAAU,UAAU,IAAI,UAAU,SACzC,OAAO,UAAU,aAAa,IAAI,UAAU,YAC5C,OAAO,UAAU,WAAW,IAAI,UAAU,UAC1C,OAAO,UAAU,eAAe,IAAI,UAAU,YAE9C,OAAO;CAIT,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,IAAI;CACnB,IAAI,KAAK,UAAU,QAAQ,QAAQ,KAAK,KAAK,UAAU,QAAQ,QAAQ,EAAE,OAAO;CAChF,IAAI,KAAK,UAAU,QAAQ,MAAM,KAAK,KAAK,UAAU,QAAQ,MAAM,EAAE,OAAO;CAC5E,IAAI,KAAK,UAAU,QAAQ,KAAK,KAAK,KAAK,UAAU,QAAQ,KAAK,EAAE,OAAO;CAC1E,IAAI,KAAK,UAAU,QAAQ,UAAU,KAAK,KAAK,UAAU,QAAQ,UAAU,EAAE,OAAO;CAGpF,IAAI,OAAO,aAAa,YAAY,IAAI,aAAa,SAAS,OAAO;CAIrE,KAFkB,OAAO,aAAa,iBAAiB,SACrC,IAAI,aAAa,iBAAiB,KACvB,OAAO;CAEpC,OAAO;;;AAIT,SAAgB,2BACd,KACA,MAIA;CACA,MAAM,SAAS,MAAM,UACnB,IAAI,mBAAmB;EAAE,cAAc;EAAO,oBAAoB;EAAO,eAAe,KAAK,KAAK;EAAE;CACtG,OAAO;EACL,SAAS,IAAI;EACb,YAAY,IAAI;EAChB,UAAU,IAAI;EACd,YAAY,IAAI;EAChB,mBAAmB,IAAI;EACvB,QAAQ,IAAI;EACZ,eAAe,IAAI;EACnB,KAAK,IAAI;EACT,aAAa,IAAI;EACjB,yBAAyB,IAAI;EAC7B,mBAAmB,IAAI;EACvB,UAAU,IAAI;EACd,cAAc,IAAI;EAClB,SAAS,IAAI;EACb,YAAY,IAAI;EAChB,eAAe,IAAI;EACnB,sBAAsB,IAAI;EAC1B,mBAAmB,IAAI;EACvB,mBAAmB,IAAI;EACvB,mBAAmB,IAAI;EACvB,aAAa,IAAI;EACjB,mBAAmB,IAAI;EACvB,oBAAoB,IAAI;EACxB,aAAa,IAAI;EACjB,aAAa,MAAM,uBAAuB,IAAI;EAC9C;EACD"}
|
package/index.ts
CHANGED
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-hybrid-memory",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.311",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "openclaw-hybrid-memory",
|
|
9
|
-
"version": "2026.5.
|
|
9
|
+
"version": "2026.5.311",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@lancedb/lancedb": "^0.29.0",
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-hybrid-memory",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.311",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Give your OpenClaw agent lasting memory: structured facts, semantic search, auto-capture & recall, decay, optional credential vault. Part of Hybrid Memory v3.",
|
|
6
6
|
"files": [
|
|
@@ -88,6 +88,32 @@ export async function awaitReloadTeardownBeforeOpen(timeoutMs = TEARDOWN_WAIT_MS
|
|
|
88
88
|
return reloadTeardownQueueDepth === 0;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** Shared buffer for Atomics.wait while synchronously blocking register(). */
|
|
92
|
+
const syncWaitBuffer = new SharedArrayBuffer(4);
|
|
93
|
+
const syncWaitArray = new Int32Array(syncWaitBuffer);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Synchronous variant of `awaitReloadTeardownBeforeOpen` for OpenClaw's sync `register()` contract.
|
|
97
|
+
* Polls teardown queue depth while pumping the event loop via Atomics.wait (50ms ticks).
|
|
98
|
+
*
|
|
99
|
+
* Prefer a bounded `timeoutMs` (default {@link TEARDOWN_WAIT_MS}). Avoid `timeoutMs === 0`
|
|
100
|
+
* in tests: vitest cannot interrupt synchronous Atomics.wait loops, so unbounded waits hang CI.
|
|
101
|
+
*/
|
|
102
|
+
export function blockReloadTeardownBeforeOpen(timeoutMs = TEARDOWN_WAIT_MS): boolean {
|
|
103
|
+
if (timeoutMs < 0) return false;
|
|
104
|
+
const deadline = timeoutMs === 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutMs;
|
|
105
|
+
while (Date.now() <= deadline) {
|
|
106
|
+
const chainSnapshot = reloadTeardownChain;
|
|
107
|
+
if (reloadTeardownQueueDepth === 0 && chainSnapshot === reloadTeardownChain) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
Atomics.wait(syncWaitArray, 0, 0, 50);
|
|
111
|
+
if (!Number.isFinite(deadline)) continue;
|
|
112
|
+
if (Date.now() >= deadline) break;
|
|
113
|
+
}
|
|
114
|
+
return reloadTeardownQueueDepth === 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
/** Reset chain for unit tests only. */
|
|
92
118
|
export function resetReloadTeardownChainForTests(): void {
|
|
93
119
|
reloadTeardownChain = Promise.resolve();
|
package/setup/register-plugin.ts
CHANGED
|
@@ -31,18 +31,24 @@ import {
|
|
|
31
31
|
recordReregisterDatabaseReuse,
|
|
32
32
|
recordReregisterFullTeardown,
|
|
33
33
|
recordReregisterRegistration,
|
|
34
|
+
resetReregisterPolicyForTests,
|
|
34
35
|
resolveReregisterPolicy,
|
|
35
36
|
} from "./reregister-policy.js";
|
|
36
37
|
import {
|
|
37
38
|
applyGatewayEmbeddingInheritanceBeforeParse,
|
|
38
39
|
shallowClonePluginConfigForGatewayMerge,
|
|
39
40
|
} from "./provider-router.js";
|
|
40
|
-
import { getHybridMemoryRegistrationState } from "./hybrid-memory-generation-state.js";
|
|
41
41
|
import {
|
|
42
|
-
|
|
42
|
+
getHybridMemoryRegistrationState,
|
|
43
|
+
resetHybridMemoryRegistrationStateForTests,
|
|
44
|
+
} from "./hybrid-memory-generation-state.js";
|
|
45
|
+
import {
|
|
46
|
+
blockReloadTeardownBeforeOpen,
|
|
43
47
|
drainOldBootstrap,
|
|
44
48
|
drainOldRecall,
|
|
49
|
+
resetReloadTeardownChainForTests,
|
|
45
50
|
schedulePluginTeardown,
|
|
51
|
+
TEARDOWN_WAIT_MS,
|
|
46
52
|
} from "./hybrid-memory-reload-coordinator.js";
|
|
47
53
|
import { registerContextEngineBestEffort } from "./register-context-engine.js";
|
|
48
54
|
import { registerLifecycleHooks } from "./register-hooks.js";
|
|
@@ -79,7 +85,11 @@ const runtimeRef: { value: PluginRuntime | null } = { value: null };
|
|
|
79
85
|
const registrationGenerationRef = getHybridMemoryRegistrationState().registrationGenerationRef;
|
|
80
86
|
|
|
81
87
|
/** Guard to prevent concurrent registrations from interleaving (Issue #802 re-entrancy). */
|
|
82
|
-
let registrationInProgress
|
|
88
|
+
let registrationInProgress = false;
|
|
89
|
+
|
|
90
|
+
/** Shared buffer for registration gate wait/notify (separate from reload coordinator buffer). */
|
|
91
|
+
const registrationGateWaitBuffer = new SharedArrayBuffer(4);
|
|
92
|
+
const registrationGateWaitArray = new Int32Array(registrationGateWaitBuffer);
|
|
83
93
|
|
|
84
94
|
/** Release DBs and timers after a `hybrid-mem` CLI command so the Node process can exit (Issue #1039). */
|
|
85
95
|
async function performHybridMemCliTeardown(): Promise<void> {
|
|
@@ -153,7 +163,7 @@ async function performHybridMemCliTeardown(): Promise<void> {
|
|
|
153
163
|
}
|
|
154
164
|
}
|
|
155
165
|
|
|
156
|
-
export
|
|
166
|
+
export function runMemoryHybridRegister(api: ClawdbotPluginApi): void {
|
|
157
167
|
// OpenClaw `loadOpenClawPluginCliRegistry` — metadata only; no DBs or native deps (issue #1111).
|
|
158
168
|
// Check this FIRST, before any logger init or config parsing, so an incomplete config
|
|
159
169
|
// cannot block lightweight metadata registration.
|
|
@@ -175,28 +185,21 @@ export async function runMemoryHybridRegister(api: ClawdbotPluginApi): Promise<v
|
|
|
175
185
|
return;
|
|
176
186
|
}
|
|
177
187
|
|
|
178
|
-
// Issue #802 re-entrancy:
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
releaseRegistrationGate = resolve;
|
|
185
|
-
});
|
|
186
|
-
registrationInProgress = registrationPromise;
|
|
187
|
-
|
|
188
|
+
// Issue #802 re-entrancy: serialize concurrent register() calls. OpenClaw requires register()
|
|
189
|
+
// to be synchronous (no Promise return), so we block with Atomics.wait instead of async/await.
|
|
190
|
+
while (registrationInProgress) {
|
|
191
|
+
Atomics.wait(registrationGateWaitArray, 0, 0, 50);
|
|
192
|
+
}
|
|
193
|
+
registrationInProgress = true;
|
|
188
194
|
try {
|
|
189
|
-
|
|
190
|
-
await runMemoryHybridRegisterImpl(api);
|
|
195
|
+
runMemoryHybridRegisterImpl(api);
|
|
191
196
|
} finally {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
registrationInProgress = null;
|
|
195
|
-
}
|
|
197
|
+
registrationInProgress = false;
|
|
198
|
+
Atomics.notify(registrationGateWaitArray, 0, 1);
|
|
196
199
|
}
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
|
|
202
|
+
function runMemoryHybridRegisterImpl(api: ClawdbotPluginApi): void {
|
|
200
203
|
// Issue #1230 / #1234: JSON CLI must not write plugin telemetry to stdout (cron harnesses, jq).
|
|
201
204
|
// Wrap api.logger to stderr before bootstrap; keep pluginLogger on that same logger delegate.
|
|
202
205
|
const logApi = wrapApiLoggerStderrForJsonCli(api);
|
|
@@ -207,17 +210,21 @@ async function runMemoryHybridRegisterImpl(api: ClawdbotPluginApi): Promise<void
|
|
|
207
210
|
const old = runtimeRef.value;
|
|
208
211
|
|
|
209
212
|
let cfg: HybridMemoryConfig;
|
|
213
|
+
let parsedCfgSnapshot: HybridMemoryConfig;
|
|
210
214
|
try {
|
|
211
215
|
const rawPc = api.pluginConfig;
|
|
212
|
-
const
|
|
213
|
-
rawPc && typeof rawPc === "object" && !Array.isArray(rawPc)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
cfg = hybridConfigSchema.parse(
|
|
216
|
+
const buildConfigToParse = (): unknown => {
|
|
217
|
+
if (rawPc && typeof rawPc === "object" && !Array.isArray(rawPc)) {
|
|
218
|
+
const clone = shallowClonePluginConfigForGatewayMerge(rawPc as Record<string, unknown>);
|
|
219
|
+
applyGatewayEmbeddingInheritanceBeforeParse(clone, api);
|
|
220
|
+
return clone;
|
|
221
|
+
}
|
|
222
|
+
return rawPc;
|
|
223
|
+
};
|
|
224
|
+
cfg = hybridConfigSchema.parse(buildConfigToParse());
|
|
225
|
+
// Re-parse from raw plugin config so snapshot matches a fresh register() parse (structuredClone
|
|
226
|
+
// drops non-enumerable fields such as credentials.encryptionKey).
|
|
227
|
+
parsedCfgSnapshot = hybridConfigSchema.parse(buildConfigToParse());
|
|
221
228
|
} catch (err) {
|
|
222
229
|
capturePluginError(err instanceof Error ? err : new Error(String(err)), {
|
|
223
230
|
subsystem: "registration",
|
|
@@ -291,8 +298,8 @@ async function runMemoryHybridRegisterImpl(api: ClawdbotPluginApi): Promise<void
|
|
|
291
298
|
|
|
292
299
|
if (old && !reuseDatabases) {
|
|
293
300
|
// Wait for teardown to complete before opening new DB handles (#802).
|
|
294
|
-
//
|
|
295
|
-
const drained =
|
|
301
|
+
// Bounded wait: drains (3s + 2s) + buffer fit within TEARDOWN_WAIT_MS.
|
|
302
|
+
const drained = blockReloadTeardownBeforeOpen(TEARDOWN_WAIT_MS);
|
|
296
303
|
if (!drained) {
|
|
297
304
|
throw new Error("memory-hybrid: reload teardown did not drain before opening new databases");
|
|
298
305
|
}
|
|
@@ -446,6 +453,7 @@ async function runMemoryHybridRegisterImpl(api: ClawdbotPluginApi): Promise<void
|
|
|
446
453
|
|
|
447
454
|
const newRuntime: PluginRuntime = {
|
|
448
455
|
cfg,
|
|
456
|
+
parsedCfgSnapshot,
|
|
449
457
|
resolvedLancePath,
|
|
450
458
|
resolvedSqlitePath,
|
|
451
459
|
factsDb: dbContext.factsDb,
|
|
@@ -694,3 +702,25 @@ async function runMemoryHybridRegisterImpl(api: ClawdbotPluginApi): Promise<void
|
|
|
694
702
|
}
|
|
695
703
|
|
|
696
704
|
export { runtimeRef, shouldCapture, detectCategory, performHybridMemCliTeardown };
|
|
705
|
+
|
|
706
|
+
/** Reset plugin singleton state between vitest cases (runtime, reload chain, generation). */
|
|
707
|
+
export function resetPluginRegistrationStateForTests(): void {
|
|
708
|
+
const old = runtimeRef.value;
|
|
709
|
+
if (old) {
|
|
710
|
+
clearRuntimeTimers(old.timers);
|
|
711
|
+
try {
|
|
712
|
+
old.lifecycleHooksHandle?.dispose();
|
|
713
|
+
} catch {
|
|
714
|
+
/* non-fatal */
|
|
715
|
+
}
|
|
716
|
+
try {
|
|
717
|
+
old.toolRegistrationHandle?.dispose();
|
|
718
|
+
} catch {
|
|
719
|
+
/* non-fatal */
|
|
720
|
+
}
|
|
721
|
+
runtimeRef.value = null;
|
|
722
|
+
}
|
|
723
|
+
resetReloadTeardownChainForTests();
|
|
724
|
+
resetReregisterPolicyForTests();
|
|
725
|
+
resetHybridMemoryRegistrationStateForTests();
|
|
726
|
+
}
|
|
@@ -75,8 +75,8 @@ export function canReuseDatabasesOnReregister(
|
|
|
75
75
|
const nextLance = api.resolvePath(cfg.lanceDbPath);
|
|
76
76
|
if (old.resolvedSqlitePath !== nextSqlite || old.resolvedLancePath !== nextLance) return false;
|
|
77
77
|
|
|
78
|
-
// Compare
|
|
79
|
-
const oldCfg = old.cfg;
|
|
78
|
+
// Compare parse-time config, not bootstrap-mutated runtime cfg (initializeDatabases may inject llm tiers).
|
|
79
|
+
const oldCfg = old.parsedCfgSnapshot ?? old.cfg;
|
|
80
80
|
if (!oldCfg?.embedding || !cfg.embedding) return false;
|
|
81
81
|
if (
|
|
82
82
|
oldCfg.embedding.provider !== cfg.embedding.provider ||
|
|
@@ -98,8 +98,10 @@ export function canReuseDatabasesOnReregister(
|
|
|
98
98
|
|
|
99
99
|
// Compare credentials.enabled to detect when vault should be opened or closed
|
|
100
100
|
if (oldCfg.credentials?.enabled !== cfg.credentials?.enabled) return false;
|
|
101
|
-
//
|
|
102
|
-
|
|
101
|
+
// encryptionKey is non-enumerable on parsed config; treat missing as empty (structuredClone drops it).
|
|
102
|
+
const oldEncKey = oldCfg.credentials?.encryptionKey ?? "";
|
|
103
|
+
const newEncKey = cfg.credentials?.encryptionKey ?? "";
|
|
104
|
+
if (oldEncKey !== newEncKey) return false;
|
|
103
105
|
|
|
104
106
|
return true;
|
|
105
107
|
}
|