@remnic/core 9.3.629 → 9.3.631
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-cli.js +15 -13
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -4
- package/dist/access-http.js +7 -6
- package/dist/access-mcp.d.ts +5 -4
- package/dist/access-mcp.js +6 -5
- package/dist/{access-service-BdThkfIE.d.ts → access-service-C9_EpVHd.d.ts} +2 -2
- package/dist/access-service.d.ts +5 -4
- package/dist/access-service.js +5 -4
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/auto-sync-RFADEHIQ.js +75 -0
- package/dist/auto-sync-RFADEHIQ.js.map +1 -0
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -3
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -2
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -3
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-532VCWYW.js → chunk-242XFZ36.js} +2 -2
- package/dist/{chunk-XXO5TI3B.js → chunk-32U3N7H5.js} +3 -3
- package/dist/{chunk-KB4MFBF5.js → chunk-3RDYU3JS.js} +3 -3
- package/dist/{chunk-57QXN2CS.js → chunk-4S3N6HFG.js} +2 -2
- package/dist/{chunk-OLNNOHBC.js → chunk-5PT5I6JQ.js} +20 -14
- package/dist/{chunk-OLNNOHBC.js.map → chunk-5PT5I6JQ.js.map} +1 -1
- package/dist/{chunk-GE7Q7KXP.js → chunk-7A2QKUUA.js} +2 -2
- package/dist/{chunk-KKTXCFD7.js → chunk-7H5WCPBS.js} +95 -11
- package/dist/{chunk-KKTXCFD7.js.map → chunk-7H5WCPBS.js.map} +1 -1
- package/dist/{chunk-3MNBW7R7.js → chunk-C4KKM62E.js} +2 -2
- package/dist/{chunk-NKCW223V.js → chunk-CMN5AWAZ.js} +2 -2
- package/dist/{chunk-JXHMAQYT.js → chunk-DOBJH4I6.js} +4 -4
- package/dist/{chunk-TZDSNIRO.js → chunk-IFVFQRZ2.js} +5 -5
- package/dist/{chunk-LQYTQCXM.js → chunk-JCLECECB.js} +2 -2
- package/dist/chunk-KVDUDYEN.js +1164 -0
- package/dist/chunk-KVDUDYEN.js.map +1 -0
- package/dist/{chunk-QDV6VAD4.js → chunk-LEG7XWS2.js} +2 -2
- package/dist/chunk-M7XQSUBB.js +280 -0
- package/dist/chunk-M7XQSUBB.js.map +1 -0
- package/dist/{chunk-N5RGXWLQ.js → chunk-PUEAEQSN.js} +2 -2
- package/dist/{chunk-UGHUNQ74.js → chunk-QYGIQ5NM.js} +212 -417
- package/dist/chunk-QYGIQ5NM.js.map +1 -0
- package/dist/{chunk-JKCDQBDW.js → chunk-UXFOGILU.js} +2 -2
- package/dist/{chunk-MVQN73GT.js → chunk-VTR3MNYF.js} +2 -2
- package/dist/{chunk-KVFYTRMV.js → chunk-W25I7G6U.js} +2 -2
- package/dist/{chunk-3GLCUPXP.js → chunk-WLZBVYC6.js} +192 -889
- package/dist/chunk-WLZBVYC6.js.map +1 -0
- package/dist/{chunk-3R2UZV3U.js → chunk-X7EJF46S.js} +2 -2
- package/dist/{chunk-54KDA6UK.js → chunk-XG4NAWAV.js} +3 -3
- package/dist/{chunk-P2D2MM47.js → chunk-YROCXMCK.js} +2 -2
- package/dist/{cli-DAsHklrf.d.ts → cli-CuVEQWKr.d.ts} +3 -3
- package/dist/cli.d.ts +6 -5
- package/dist/cli.js +18 -17
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -2
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -2
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -2
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/contradiction/index.js +4 -4
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +3 -2
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -3
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +49 -45
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +4 -3
- package/dist/mcp-memory-inspector-app.d.ts +5 -4
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +4 -3
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +3 -2
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +7 -6
- package/dist/{orchestrator-BexeSJ2j.d.ts → orchestrator-CoqytbK_.d.ts} +102 -10
- package/dist/orchestrator.d.ts +4 -3
- package/dist/orchestrator.js +12 -10
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-PwkzNfdK.d.ts → semantic-consolidation-BPs6BURk.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -3
- package/dist/semantic-rule-promotion.js +3 -2
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +38 -2
- package/dist/storage.js +6 -3
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/{types-BCF2wqKa.d.ts → types-CpMPD8xl.d.ts} +59 -11
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -2
- package/package.json +1 -1
- package/src/orchestrator.ts +74 -0
- package/src/storage.ts +100 -0
- package/src/wearables/auto-sync.test.ts +181 -0
- package/src/wearables/auto-sync.ts +129 -0
- package/src/wearables/cli.ts +6 -0
- package/src/wearables/config.test.ts +90 -11
- package/src/wearables/config.ts +113 -11
- package/src/wearables/memory-gen.test.ts +416 -1
- package/src/wearables/memory-gen.ts +381 -23
- package/src/wearables/pipeline.test.ts +396 -5
- package/src/wearables/pipeline.ts +174 -22
- package/src/wearables/service.test.ts +172 -0
- package/src/wearables/service.ts +84 -3
- package/src/wearables/storage-io.test.ts +81 -0
- package/src/wearables/trust.test.ts +123 -0
- package/src/wearables/trust.ts +168 -0
- package/src/wearables/types.ts +57 -9
- package/dist/chunk-3GLCUPXP.js.map +0 -1
- package/dist/chunk-UGHUNQ74.js.map +0 -1
- /package/dist/{chunk-532VCWYW.js.map → chunk-242XFZ36.js.map} +0 -0
- /package/dist/{chunk-XXO5TI3B.js.map → chunk-32U3N7H5.js.map} +0 -0
- /package/dist/{chunk-KB4MFBF5.js.map → chunk-3RDYU3JS.js.map} +0 -0
- /package/dist/{chunk-57QXN2CS.js.map → chunk-4S3N6HFG.js.map} +0 -0
- /package/dist/{chunk-GE7Q7KXP.js.map → chunk-7A2QKUUA.js.map} +0 -0
- /package/dist/{chunk-3MNBW7R7.js.map → chunk-C4KKM62E.js.map} +0 -0
- /package/dist/{chunk-NKCW223V.js.map → chunk-CMN5AWAZ.js.map} +0 -0
- /package/dist/{chunk-JXHMAQYT.js.map → chunk-DOBJH4I6.js.map} +0 -0
- /package/dist/{chunk-TZDSNIRO.js.map → chunk-IFVFQRZ2.js.map} +0 -0
- /package/dist/{chunk-LQYTQCXM.js.map → chunk-JCLECECB.js.map} +0 -0
- /package/dist/{chunk-QDV6VAD4.js.map → chunk-LEG7XWS2.js.map} +0 -0
- /package/dist/{chunk-N5RGXWLQ.js.map → chunk-PUEAEQSN.js.map} +0 -0
- /package/dist/{chunk-JKCDQBDW.js.map → chunk-UXFOGILU.js.map} +0 -0
- /package/dist/{chunk-MVQN73GT.js.map → chunk-VTR3MNYF.js.map} +0 -0
- /package/dist/{chunk-KVFYTRMV.js.map → chunk-W25I7G6U.js.map} +0 -0
- /package/dist/{chunk-3R2UZV3U.js.map → chunk-X7EJF46S.js.map} +0 -0
- /package/dist/{chunk-54KDA6UK.js.map → chunk-XG4NAWAV.js.map} +0 -0
- /package/dist/{chunk-P2D2MM47.js.map → chunk-YROCXMCK.js.map} +0 -0
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import './types-BliCnURB.js';
|
|
2
2
|
import './index-DJ9QWMw-.js';
|
|
3
|
-
export { y as AccessTrackingEntry, A as ActiveRecallChatType, d as ActiveRecallModelFallbackPolicy, b as ActiveRecallPromptStyle, a as ActiveRecallQueryMode, c as ActiveRecallThinking,
|
|
3
|
+
export { y as AccessTrackingEntry, A as ActiveRecallChatType, d as ActiveRecallModelFallbackPolicy, b as ActiveRecallPromptStyle, a as ActiveRecallQueryMode, c as ActiveRecallThinking, a7 as AgentAccessAuthToken, b2 as AgentAccessHttpConfig, b3 as AgentDefaultsConfig, b4 as AgentPersona, f as AgentPersonaModelConfig, b5 as BehaviorLoopAdjustment, e as BehaviorLoopPolicyState, b6 as BehaviorSignalDirection, B as BehaviorSignalEvent, b7 as BehaviorSignalType, aH as BootstrapOptions, aI as BootstrapResult, V as BriefingActiveThread, b8 as BriefingCalendarSourceError, aO as BriefingConfig, Q as BriefingFocus, O as BriefingFollowup, aP as BriefingOpenCommitment, X as BriefingRecentEntity, W as BriefingResult, N as BriefingSections, aQ as BriefingWindow, b9 as BufferEntryState, k as BufferState, o as BufferSurpriseEvent, Y as BufferTurn, U as CalendarEvent, S as CalendarSource, ba as CaptureMode, $ as Checkpoint, bb as CodexCliReasoningEffort, bc as CodexCompactionFlushMode, aR as CodexCompatConfig, bd as CodexConnectorConfig, aG as CodingContext, aL as CodingModeConfig, be as CompressionGuidelineActivationState, a9 as CompressionGuidelineOptimizerActionSummary, a8 as CompressionGuidelineOptimizerEventCounts, aa as CompressionGuidelineOptimizerRuleUpdate, bf as CompressionGuidelineOptimizerSourceWindow, C as CompressionGuidelineOptimizerState, bg as ConfidenceTier, bh as ConsolidationAction, bi as ConsolidationItem, aJ as ConsolidationObservation, ag as ConsolidationResult, s as ContinuityImprovementLoop, r as ContinuityIncidentCloseInput, p as ContinuityIncidentOpenInput, q as ContinuityIncidentRecord, bj as ContinuityIncidentState, bk as ContinuityLoopCadence, u as ContinuityLoopReviewInput, bl as ContinuityLoopStatus, t as ContinuityLoopUpsertInput, bm as ContradictionScanConfig, as as ConversationThread, bn as CronConversationRecallMode, bo as CronRecallMode, aS as DEFAULT_RECALL_DISCLOSURE, ah as DaySummaryResult, bp as DreamingConfig, bq as DreamingNarrativePromptStyle, br as DreamsDeepSleepConfig, bs as DreamsLightSleepConfig, au as DreamsPhase, bt as DreamsPhaseStatus, bu as DreamsPhasesConfig, bv as DreamsRemConfig, av as DreamsRunResult, at as DreamsStatusResult, bw as EngramTraceEvent, w as EntityActivityEntry, x as EntityFile, ac as EntityMention, v as EntityRelationship, ad as EntitySchemaDefinition, ae as EntitySchemaSectionDefinition, E as EntityStructuredSection, aK as EntityTimelineEntry, aT as ExtractedFact, bx as ExtractedProcedureStep, by as ExtractedQuestion, bz as ExtractedReasoningTrace, bA as ExtractedReasoningTraceStep, bB as ExtractedRelationship, bC as ExtractionPassSource, af as ExtractionResult, F as FileHygieneConfig, G as GatewayConfig, bD as GitHubLiveConnectorConfig, bE as GmailLiveConnectorConfig, bF as GoogleDriveLiveConnectorConfig, bG as HeartbeatConfig, bH as HeartbeatDetectionMode, a0 as HourlySummary, J as IdentityInjectionMode, a5 as ImportanceLevel, I as ImportanceScore, a2 as LifecycleState, L as LiveConnectorsConfig, bI as LlmTraceCallback, bJ as LlmTraceEvent, ak as MemoryActionEligibilityContext, aU as MemoryActionEligibilitySource, m as MemoryActionEvent, bK as MemoryActionOutcome, bL as MemoryActionPolicyDecision, al as MemoryActionPolicyResult, bM as MemoryActionStatus, ab as MemoryActionType, M as MemoryCategory, g as MemoryFile, j as MemoryFrontmatter, ai as MemoryIntent, n as MemoryLifecycleEvent, am as MemoryLifecycleEventType, an as MemoryLifecycleStateSummary, i as MemoryLink, bN as MemoryLinkType, aV as MemoryObservation, bO as MemoryOsPresetName, z as MemoryProjectionCurrentState, aW as MemoryScope, h as MemoryStatus, D as MemorySummary, l as MetaState, bP as ModelApi, bQ as ModelDefinitionConfig, bR as ModelProviderAuthMode, ao as ModelProviderConfig, bS as NamespacePolicy, ap as NativeKnowledgeConfig, bT as NativeKnowledgeFolderRuleConfig, bU as NativeKnowledgeObsidianVaultConfig, bV as NativeKnowledgeOpenClawWorkspaceConfig, bW as NotionLiveConnectorConfig, P as PluginConfig, a4 as PolicyClass, bX as PrincipalFromSessionKeyMode, bY as PrincipalRule, bZ as ProceduralConfig, aj as QmdSearchExplain, Z as QmdSearchResult, b_ as QuestionEntry, aX as RECALL_DISCLOSURE_LEVELS, b$ as ReasoningEffort, K as RecallDisclosure, c0 as RecallPipelineConfig, H as RecallPlanMode, c1 as RecallSectionConfig, R as RecallTierExplain, c2 as RecallTraceEvent, c3 as RelevanceFeedback, aq as RetrievalTier, c4 as SPECULATIVE_TTL_DAYS, c5 as ScoredEntity, a6 as SecretRef, c6 as SemanticChunkingConfigShape, a1 as SessionObserverBandConfig, c7 as SignalLevel, ar as SignalScanResult, c8 as SlotBehaviorConfig, c9 as SlotMismatchMode, T as TopicScore, _ as TranscriptEntry, ca as TriggerMode, a3 as VerificationState, cb as confidenceTier, b1 as isRecallDisclosure } from './types-CpMPD8xl.js';
|
package/dist/verified-recall.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
searchVerifiedEpisodes
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YROCXMCK.js";
|
|
4
4
|
import "./chunk-HQ6NIBL6.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-QYGIQ5NM.js";
|
|
6
|
+
import "./chunk-M7XQSUBB.js";
|
|
6
7
|
import "./chunk-5UZXUTVO.js";
|
|
7
8
|
import "./chunk-J6A3CX5N.js";
|
|
8
9
|
import "./chunk-FPNQF475.js";
|
package/package.json
CHANGED
package/src/orchestrator.ts
CHANGED
|
@@ -1700,6 +1700,7 @@ export class Orchestrator {
|
|
|
1700
1700
|
>();
|
|
1701
1701
|
private qmdMaintenanceTimer: NodeJS.Timeout | null = null;
|
|
1702
1702
|
private wearablesServiceInstance: WearablesService | null = null;
|
|
1703
|
+
private wearablesAutoSyncHandle: { stop(): Promise<void> } | null = null;
|
|
1703
1704
|
private qmdMaintenancePending = false;
|
|
1704
1705
|
private qmdMaintenanceInFlight = false;
|
|
1705
1706
|
private lastQmdEmbedAtMs = 0;
|
|
@@ -1781,6 +1782,12 @@ export class Orchestrator {
|
|
|
1781
1782
|
*/
|
|
1782
1783
|
async destroy(): Promise<void> {
|
|
1783
1784
|
this.abortDeferredInit();
|
|
1785
|
+
if (this.wearablesAutoSyncHandle) {
|
|
1786
|
+
// Aborts in-flight provider fetches and waits for the tick to
|
|
1787
|
+
// settle, so nothing is writing or reindexing past destroy().
|
|
1788
|
+
await this.wearablesAutoSyncHandle.stop();
|
|
1789
|
+
this.wearablesAutoSyncHandle = null;
|
|
1790
|
+
}
|
|
1784
1791
|
if (this.qmdMaintenanceTimer) {
|
|
1785
1792
|
clearTimeout(this.qmdMaintenanceTimer);
|
|
1786
1793
|
this.qmdMaintenanceTimer = null;
|
|
@@ -2957,6 +2964,57 @@ export class Orchestrator {
|
|
|
2957
2964
|
}
|
|
2958
2965
|
}
|
|
2959
2966
|
|
|
2967
|
+
// Wearables auto-sync: in-process periodic transcript refresh for
|
|
2968
|
+
// long-lived hosts (default on). Today's transcript keeps growing
|
|
2969
|
+
// while the wearable records; a once-per-local-day deep pass picks
|
|
2970
|
+
// up late uploads and provider re-processing. Static config gate —
|
|
2971
|
+
// sources can't appear at runtime, so checking once here is safe.
|
|
2972
|
+
// The timer is unref'd, so one-shot CLI runs exit naturally without
|
|
2973
|
+
// ever ticking; idempotent across stop/start cycles via the handle
|
|
2974
|
+
// guard. Non-fatal: a failure to start must not break init.
|
|
2975
|
+
if (signal.aborted) return;
|
|
2976
|
+
if (
|
|
2977
|
+
!this.wearablesAutoSyncHandle &&
|
|
2978
|
+
this.config.wearables.enabled &&
|
|
2979
|
+
this.config.wearables.autoSyncEnabled &&
|
|
2980
|
+
Object.values(this.config.wearables.sources).some((source) => source.enabled)
|
|
2981
|
+
) {
|
|
2982
|
+
try {
|
|
2983
|
+
const { startWearablesAutoSync } = await import("./wearables/auto-sync.js");
|
|
2984
|
+
// Re-check after the await: destroy() may have aborted while
|
|
2985
|
+
// the import was in flight, having found no handle to stop —
|
|
2986
|
+
// starting now would leave a live interval on a destroyed
|
|
2987
|
+
// orchestrator (Cursor review on PR #1464). Handle creation
|
|
2988
|
+
// below is synchronous, so no further window exists.
|
|
2989
|
+
if (signal.aborted) return;
|
|
2990
|
+
this.wearablesAutoSyncHandle = startWearablesAutoSync(
|
|
2991
|
+
{
|
|
2992
|
+
intervalMinutes: this.config.wearables.autoSyncIntervalMinutes,
|
|
2993
|
+
days: this.config.wearables.autoSyncDays,
|
|
2994
|
+
deepDays: this.config.wearables.autoSyncDeepDays,
|
|
2995
|
+
...(this.config.wearables.timezone !== undefined
|
|
2996
|
+
? { timezone: this.config.wearables.timezone }
|
|
2997
|
+
: {}),
|
|
2998
|
+
},
|
|
2999
|
+
{
|
|
3000
|
+
sync: (options) => this.getWearablesService().sync(options),
|
|
3001
|
+
log: {
|
|
3002
|
+
info: (message) => log.info(message),
|
|
3003
|
+
warn: (message) => log.warn(message),
|
|
3004
|
+
},
|
|
3005
|
+
},
|
|
3006
|
+
);
|
|
3007
|
+
log.info(
|
|
3008
|
+
`wearables auto-sync started: every ${this.config.wearables.autoSyncIntervalMinutes}m over ${this.config.wearables.autoSyncDays}d (deep ${this.config.wearables.autoSyncDeepDays}d daily)`,
|
|
3009
|
+
);
|
|
3010
|
+
} catch (err) {
|
|
3011
|
+
const { displayErrorDetail } = await import("./runtime/better-sqlite.js");
|
|
3012
|
+
log.warn(
|
|
3013
|
+
`wearables auto-sync failed to start (non-fatal): ${displayErrorDetail(err)}`,
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
|
|
2960
3018
|
log.info("orchestrator initialized (full — deferred steps complete)");
|
|
2961
3019
|
}
|
|
2962
3020
|
|
|
@@ -10807,6 +10865,22 @@ export class Orchestrator {
|
|
|
10807
10865
|
getStorage: async () =>
|
|
10808
10866
|
await this.getStorageForNamespace(this.bulkImportWriteNamespace()),
|
|
10809
10867
|
extract: (turns) => this.extraction.extract(turns),
|
|
10868
|
+
// Smart memoryMode runs candidates through the SAME extraction
|
|
10869
|
+
// judge (cache + defer counters included) the live extraction
|
|
10870
|
+
// pipeline uses, so wearable facts get identical LLM-as-judge
|
|
10871
|
+
// durability gating.
|
|
10872
|
+
judgeFacts: (candidates) =>
|
|
10873
|
+
judgeFactDurability(
|
|
10874
|
+
candidates,
|
|
10875
|
+
this.config,
|
|
10876
|
+
this.localLlm,
|
|
10877
|
+
new FallbackLlmClient(
|
|
10878
|
+
this.config.gatewayConfig,
|
|
10879
|
+
fallbackLlmRuntimeContextFromConfig(this.config),
|
|
10880
|
+
),
|
|
10881
|
+
this.judgeVerdictCache,
|
|
10882
|
+
this.judgeDeferCounts,
|
|
10883
|
+
),
|
|
10810
10884
|
searchBackend: {
|
|
10811
10885
|
search: async (query, maxResults) => {
|
|
10812
10886
|
if (!this.qmd.isAvailable()) return null;
|
package/src/storage.ts
CHANGED
|
@@ -1168,6 +1168,27 @@ export class ContentHashIndex {
|
|
|
1168
1168
|
* normalizeAttributePairs({ foo: "bar", BAZ: "qux" })
|
|
1169
1169
|
* // → "baz: qux; foo: bar"
|
|
1170
1170
|
*/
|
|
1171
|
+
/**
|
|
1172
|
+
* Remove the "[Attributes: ...]" suffix `writeMemory` appends to the
|
|
1173
|
+
* stored body when structuredAttributes are present, yielding the raw
|
|
1174
|
+
* fact text for content comparison. Inverse companion of
|
|
1175
|
+
* `normalizeAttributePairs` enrichment. String operations, not regex —
|
|
1176
|
+
* CodeQL js/polynomial-redos on a suffix-anchored pattern over
|
|
1177
|
+
* library-supplied content.
|
|
1178
|
+
*/
|
|
1179
|
+
export function stripAttributesSuffix(content: string): string {
|
|
1180
|
+
const trimmed = content.trimEnd();
|
|
1181
|
+
if (!trimmed.endsWith("]")) return content.trim();
|
|
1182
|
+
const marker = "\n[Attributes: ";
|
|
1183
|
+
const markerIndex = trimmed.lastIndexOf(marker);
|
|
1184
|
+
if (markerIndex === -1) return content.trim();
|
|
1185
|
+
// The block must be the FINAL line and contain no "]" before the
|
|
1186
|
+
// closing bracket (mirrors the shape normalizeAttributePairs emits).
|
|
1187
|
+
const inner = trimmed.slice(markerIndex + marker.length, -1);
|
|
1188
|
+
if (inner.includes("]") || inner.includes("\n")) return content.trim();
|
|
1189
|
+
return trimmed.slice(0, markerIndex).trim();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1171
1192
|
export function normalizeAttributePairs(pairs: Record<string, string>): string {
|
|
1172
1193
|
return Object.entries(pairs)
|
|
1173
1194
|
.map(([k, v]) => [k.trim().toLowerCase(), v.trim()] as [string, string])
|
|
@@ -2596,6 +2617,85 @@ export class StorageManager {
|
|
|
2596
2617
|
return days;
|
|
2597
2618
|
}
|
|
2598
2619
|
|
|
2620
|
+
/**
|
|
2621
|
+
* Locate a wearable-sourced memory by exact (trimmed) content,
|
|
2622
|
+
* ignoring the "[Attributes: ...]" suffix writeMemory appends for
|
|
2623
|
+
* structuredAttributes — callers pass the raw fact text. Used by the
|
|
2624
|
+
* smart trust pipeline to find an earlier borderline write when the
|
|
2625
|
+
* same fact re-extracts with stronger evidence.
|
|
2626
|
+
*/
|
|
2627
|
+
async findWearableMemoryByContent(
|
|
2628
|
+
content: string,
|
|
2629
|
+
): Promise<{ id: string; status: MemoryStatus | undefined } | null> {
|
|
2630
|
+
const needle = stripAttributesSuffix(content);
|
|
2631
|
+
const memories = await this.readAllMemories();
|
|
2632
|
+
for (const memory of memories) {
|
|
2633
|
+
if (
|
|
2634
|
+
typeof memory.frontmatter.source === "string" &&
|
|
2635
|
+
memory.frontmatter.source.startsWith("wearable:") &&
|
|
2636
|
+
stripAttributesSuffix(memory.content) === needle
|
|
2637
|
+
) {
|
|
2638
|
+
return { id: memory.frontmatter.id, status: memory.frontmatter.status };
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
return null;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
/**
|
|
2645
|
+
* Promote a pending_review wearable memory to active in place,
|
|
2646
|
+
* merging updated trust evidence into structuredAttributes. Returns
|
|
2647
|
+
* false when the memory is missing or no longer pending_review (a
|
|
2648
|
+
* concurrent review decision wins).
|
|
2649
|
+
*/
|
|
2650
|
+
async promoteWearableMemory(
|
|
2651
|
+
id: string,
|
|
2652
|
+
attributeUpdates: Record<string, string>,
|
|
2653
|
+
confidence?: number,
|
|
2654
|
+
): Promise<boolean> {
|
|
2655
|
+
const memories = await this.readAllMemories();
|
|
2656
|
+
const memory = memories.find((entry) => entry.frontmatter.id === id);
|
|
2657
|
+
if (!memory) return false;
|
|
2658
|
+
if (memory.frontmatter.status !== "pending_review") return false;
|
|
2659
|
+
return this.writeMemoryFrontmatter(memory, {
|
|
2660
|
+
status: "active",
|
|
2661
|
+
// Keep frontmatter confidence in step with the re-scored trust —
|
|
2662
|
+
// new smart writes persist trust as confidence, and a promoted
|
|
2663
|
+
// row must not keep its stale borderline value.
|
|
2664
|
+
...(typeof confidence === "number" && Number.isFinite(confidence)
|
|
2665
|
+
? { confidence: Math.min(1, Math.max(0, confidence)) }
|
|
2666
|
+
: {}),
|
|
2667
|
+
structuredAttributes: {
|
|
2668
|
+
...(memory.frontmatter.structuredAttributes ?? {}),
|
|
2669
|
+
...attributeUpdates,
|
|
2670
|
+
},
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
/**
|
|
2675
|
+
* Demote a pending_review wearable memory to rejected when a re-pass
|
|
2676
|
+
* produced an explicit judge-reject verdict, merging the evidence.
|
|
2677
|
+
* Returns false when the memory is missing or no longer
|
|
2678
|
+
* pending_review — active rows are NEVER auto-demoted (operator
|
|
2679
|
+
* approvals and accrued recall signals win; contradiction scans and
|
|
2680
|
+
* supersession own active-row retirement).
|
|
2681
|
+
*/
|
|
2682
|
+
async demoteWearableMemory(
|
|
2683
|
+
id: string,
|
|
2684
|
+
attributeUpdates: Record<string, string>,
|
|
2685
|
+
): Promise<boolean> {
|
|
2686
|
+
const memories = await this.readAllMemories();
|
|
2687
|
+
const memory = memories.find((entry) => entry.frontmatter.id === id);
|
|
2688
|
+
if (!memory) return false;
|
|
2689
|
+
if (memory.frontmatter.status !== "pending_review") return false;
|
|
2690
|
+
return this.writeMemoryFrontmatter(memory, {
|
|
2691
|
+
status: "rejected",
|
|
2692
|
+
structuredAttributes: {
|
|
2693
|
+
...(memory.frontmatter.structuredAttributes ?? {}),
|
|
2694
|
+
...attributeUpdates,
|
|
2695
|
+
},
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2599
2699
|
private get factsDir(): string {
|
|
2600
2700
|
return path.join(this.baseDir, "facts");
|
|
2601
2701
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { startWearablesAutoSync } from "./auto-sync.js";
|
|
5
|
+
|
|
6
|
+
function makeDeps(overrides: { failTimes?: number } = {}) {
|
|
7
|
+
const calls: Array<{ days: number }> = [];
|
|
8
|
+
const warnings: string[] = [];
|
|
9
|
+
let remainingFailures = overrides.failTimes ?? 0;
|
|
10
|
+
let nowIso = "2026-06-12T10:00:00.000Z";
|
|
11
|
+
return {
|
|
12
|
+
calls,
|
|
13
|
+
warnings,
|
|
14
|
+
setNow(iso: string) {
|
|
15
|
+
nowIso = iso;
|
|
16
|
+
},
|
|
17
|
+
deps: {
|
|
18
|
+
sync: async (options: { days: number }) => {
|
|
19
|
+
if (remainingFailures > 0) {
|
|
20
|
+
remainingFailures -= 1;
|
|
21
|
+
throw new Error("provider down");
|
|
22
|
+
}
|
|
23
|
+
calls.push(options);
|
|
24
|
+
return [];
|
|
25
|
+
},
|
|
26
|
+
log: {
|
|
27
|
+
info: () => {},
|
|
28
|
+
warn: (message: string) => {
|
|
29
|
+
warnings.push(message);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
now: () => new Date(nowIso),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const SETTINGS = {
|
|
38
|
+
intervalMinutes: 15,
|
|
39
|
+
days: 2,
|
|
40
|
+
deepDays: 7,
|
|
41
|
+
timezone: "UTC",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
test("first tick runs the deep window, same-day ticks run the shallow window", async () => {
|
|
45
|
+
const { calls, deps } = makeDeps();
|
|
46
|
+
const handle = startWearablesAutoSync(SETTINGS, deps);
|
|
47
|
+
try {
|
|
48
|
+
await handle.tick();
|
|
49
|
+
await handle.tick();
|
|
50
|
+
await handle.tick();
|
|
51
|
+
assert.deepEqual(
|
|
52
|
+
calls.map((call) => call.days),
|
|
53
|
+
[7, 2, 2],
|
|
54
|
+
"deep once, then shallow for the rest of the day",
|
|
55
|
+
);
|
|
56
|
+
} finally {
|
|
57
|
+
handle.stop();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("a new local day triggers the deep pass again", async () => {
|
|
62
|
+
const { calls, deps, setNow } = makeDeps();
|
|
63
|
+
const handle = startWearablesAutoSync(SETTINGS, deps);
|
|
64
|
+
try {
|
|
65
|
+
await handle.tick();
|
|
66
|
+
await handle.tick();
|
|
67
|
+
setNow("2026-06-13T00:05:00.000Z");
|
|
68
|
+
await handle.tick();
|
|
69
|
+
await handle.tick();
|
|
70
|
+
assert.deepEqual(
|
|
71
|
+
calls.map((call) => call.days),
|
|
72
|
+
[7, 2, 7, 2],
|
|
73
|
+
);
|
|
74
|
+
} finally {
|
|
75
|
+
handle.stop();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("a failed deep pass retries deep on the next tick, never throws", async () => {
|
|
80
|
+
const { calls, warnings, deps } = makeDeps({ failTimes: 1 });
|
|
81
|
+
const handle = startWearablesAutoSync(SETTINGS, deps);
|
|
82
|
+
try {
|
|
83
|
+
await handle.tick();
|
|
84
|
+
assert.equal(calls.length, 0, "first tick failed");
|
|
85
|
+
assert.equal(warnings.length, 1);
|
|
86
|
+
assert.match(warnings[0], /retrying on the next tick/);
|
|
87
|
+
await handle.tick();
|
|
88
|
+
assert.deepEqual(
|
|
89
|
+
calls.map((call) => call.days),
|
|
90
|
+
[7],
|
|
91
|
+
"deep window retries — a failure must not consume the daily deep slot",
|
|
92
|
+
);
|
|
93
|
+
} finally {
|
|
94
|
+
handle.stop();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("deepDays 0 disables the deep pass entirely", async () => {
|
|
99
|
+
const { calls, deps } = makeDeps();
|
|
100
|
+
const handle = startWearablesAutoSync({ ...SETTINGS, deepDays: 0 }, deps);
|
|
101
|
+
try {
|
|
102
|
+
await handle.tick();
|
|
103
|
+
await handle.tick();
|
|
104
|
+
assert.deepEqual(
|
|
105
|
+
calls.map((call) => call.days),
|
|
106
|
+
[2, 2],
|
|
107
|
+
);
|
|
108
|
+
} finally {
|
|
109
|
+
handle.stop();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("overlapping ticks are skipped, not stacked", async () => {
|
|
114
|
+
const calls: Array<{ days: number }> = [];
|
|
115
|
+
let release: (() => void) | null = null;
|
|
116
|
+
const handle = startWearablesAutoSync(SETTINGS, {
|
|
117
|
+
sync: async (options) => {
|
|
118
|
+
calls.push(options);
|
|
119
|
+
await new Promise<void>((resolve) => {
|
|
120
|
+
release = resolve;
|
|
121
|
+
});
|
|
122
|
+
return [];
|
|
123
|
+
},
|
|
124
|
+
log: { info: () => {}, warn: () => {} },
|
|
125
|
+
now: () => new Date("2026-06-12T10:00:00.000Z"),
|
|
126
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
const first = handle.tick();
|
|
129
|
+
const second = handle.tick();
|
|
130
|
+
await second;
|
|
131
|
+
assert.equal(calls.length, 1, "second tick is a no-op while the first runs");
|
|
132
|
+
release!();
|
|
133
|
+
await first;
|
|
134
|
+
} finally {
|
|
135
|
+
handle.stop();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("stop() prevents any further ticks", async () => {
|
|
140
|
+
const { calls, deps } = makeDeps();
|
|
141
|
+
const handle = startWearablesAutoSync(SETTINGS, deps);
|
|
142
|
+
await handle.stop();
|
|
143
|
+
await handle.tick();
|
|
144
|
+
assert.equal(calls.length, 0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("stop() aborts the in-flight sync and awaits its settlement", async () => {
|
|
148
|
+
const warnings: string[] = [];
|
|
149
|
+
let sawSignal: AbortSignal | undefined;
|
|
150
|
+
let syncSettled = false;
|
|
151
|
+
const handle = startWearablesAutoSync(SETTINGS, {
|
|
152
|
+
sync: async (options) => {
|
|
153
|
+
sawSignal = options.signal;
|
|
154
|
+
// Hang until the shutdown abort arrives, like a slow provider.
|
|
155
|
+
await new Promise<void>((_resolve, reject) => {
|
|
156
|
+
options.signal!.addEventListener("abort", () =>
|
|
157
|
+
reject(new Error("aborted")),
|
|
158
|
+
);
|
|
159
|
+
}).finally(() => {
|
|
160
|
+
syncSettled = true;
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
log: {
|
|
164
|
+
info: () => {},
|
|
165
|
+
warn: (message: string) => {
|
|
166
|
+
warnings.push(message);
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
now: () => new Date("2026-06-12T10:00:00.000Z"),
|
|
170
|
+
});
|
|
171
|
+
const inFlight = handle.tick();
|
|
172
|
+
assert.ok(sawSignal instanceof AbortSignal, "sync receives the abort signal");
|
|
173
|
+
await handle.stop();
|
|
174
|
+
assert.equal(syncSettled, true, "stop() resolves only after the tick settled");
|
|
175
|
+
assert.equal(
|
|
176
|
+
warnings.length,
|
|
177
|
+
0,
|
|
178
|
+
"an abort raised by shutdown is intentional, never warned",
|
|
179
|
+
);
|
|
180
|
+
await inFlight;
|
|
181
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wearables auto-sync — in-process periodic transcript refresh.
|
|
3
|
+
*
|
|
4
|
+
* Every tick re-syncs a short rolling window ending today for ALL
|
|
5
|
+
* enabled sources, so the current day's transcript keeps growing while
|
|
6
|
+
* the wearable records and provider-side revisions of recent days flow
|
|
7
|
+
* in without operator action. Once per local day the window deepens to
|
|
8
|
+
* `autoSyncDeepDays` to pick up late uploads and provider reprocessing
|
|
9
|
+
* further back (phones syncing hours later, re-diarized transcripts).
|
|
10
|
+
*
|
|
11
|
+
* Day fetches are unconditional within the window — existing day files
|
|
12
|
+
* are always re-fetched and re-composed; the content-hash skip in the
|
|
13
|
+
* pipeline keeps unchanged days write-free, so a tick on a quiet day
|
|
14
|
+
* is read-only. The timer is unref'd: it never keeps a one-shot CLI
|
|
15
|
+
* process alive, and long-lived hosts stop it via the returned handle.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describeErrorForOperator } from "./errors.js";
|
|
19
|
+
import { dateInTimezone, defaultTimezone } from "./pipeline.js";
|
|
20
|
+
|
|
21
|
+
export interface WearablesAutoSyncSettings {
|
|
22
|
+
/** Minutes between ticks. */
|
|
23
|
+
intervalMinutes: number;
|
|
24
|
+
/** Rolling window (days ending today) refreshed on a normal tick. */
|
|
25
|
+
days: number;
|
|
26
|
+
/**
|
|
27
|
+
* Window for the once-per-local-day deep pass. 0 disables the deep
|
|
28
|
+
* pass; otherwise must be >= `days` (validated at config parse).
|
|
29
|
+
*/
|
|
30
|
+
deepDays: number;
|
|
31
|
+
/** IANA timezone used to detect the local-day rollover. */
|
|
32
|
+
timezone?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface WearablesAutoSyncDeps {
|
|
36
|
+
/** Run a sync across all enabled sources (WearablesService.sync). */
|
|
37
|
+
sync(options: { days: number; signal?: AbortSignal }): Promise<unknown>;
|
|
38
|
+
log: { info(message: string): void; warn(message: string): void };
|
|
39
|
+
/** Clock injection for tests. */
|
|
40
|
+
now?: () => Date;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface WearablesAutoSyncHandle {
|
|
44
|
+
/** Run one scheduler tick now (first-run hook and test seam). */
|
|
45
|
+
tick(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Stop the scheduler: clears the timer, aborts the in-flight sync's
|
|
48
|
+
* provider fetches via AbortSignal, and resolves once the in-flight
|
|
49
|
+
* tick has fully settled — after `await stop()` nothing is writing
|
|
50
|
+
* or reindexing anymore. The handle is single-use; a restarted host
|
|
51
|
+
* starts a fresh one.
|
|
52
|
+
*/
|
|
53
|
+
stop(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Start the periodic refresh. The first tick fires after one full
|
|
58
|
+
* interval rather than at start — gateways on low-power hardware can
|
|
59
|
+
* restart-loop during setup (issue #462), and an immediate fetch on
|
|
60
|
+
* every restart would hammer provider APIs. Operators who want an
|
|
61
|
+
* instant refresh run `wearables sync` (or call `tick()`).
|
|
62
|
+
*/
|
|
63
|
+
export function startWearablesAutoSync(
|
|
64
|
+
settings: WearablesAutoSyncSettings,
|
|
65
|
+
deps: WearablesAutoSyncDeps,
|
|
66
|
+
): WearablesAutoSyncHandle {
|
|
67
|
+
let inFlight: Promise<void> | null = null;
|
|
68
|
+
let stopped = false;
|
|
69
|
+
let lastDeepDate: string | null = null;
|
|
70
|
+
// Aborting cancels the in-flight sync's provider fetches promptly on
|
|
71
|
+
// shutdown — without it, a slow tick would keep writing/reindexing
|
|
72
|
+
// after orchestrator.destroy() while a restarted host starts a
|
|
73
|
+
// second scheduler (Kilo review on PR #1464).
|
|
74
|
+
const abortController = new AbortController();
|
|
75
|
+
|
|
76
|
+
const tick = async (): Promise<void> => {
|
|
77
|
+
// Overlap guard: a slow provider or large deep window must never
|
|
78
|
+
// stack a second sync on top of a running one. The skipped call
|
|
79
|
+
// resolves immediately — it must NOT await the running tick, or a
|
|
80
|
+
// caller holding both promises could deadlock itself.
|
|
81
|
+
if (inFlight || stopped) return;
|
|
82
|
+
const run = (async () => {
|
|
83
|
+
try {
|
|
84
|
+
const now = deps.now ? deps.now() : new Date();
|
|
85
|
+
const today = dateInTimezone(now, settings.timezone ?? defaultTimezone());
|
|
86
|
+
const deep = settings.deepDays > 0 && lastDeepDate !== today;
|
|
87
|
+
const days = deep ? settings.deepDays : settings.days;
|
|
88
|
+
await deps.sync({ days, signal: abortController.signal });
|
|
89
|
+
// Mark the deep pass done only AFTER it succeeded — a failed
|
|
90
|
+
// deep pass retries on the next tick instead of silently
|
|
91
|
+
// waiting for tomorrow (CLAUDE.md rule 25's "confirm before
|
|
92
|
+
// consuming the one-shot" shape).
|
|
93
|
+
if (deep) lastDeepDate = today;
|
|
94
|
+
deps.log.info(
|
|
95
|
+
`wearables auto-sync: refreshed ${days}-day window${deep ? " (daily deep pass)" : ""}`,
|
|
96
|
+
);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
// An abort raised by stop() is intentional shutdown, not a
|
|
99
|
+
// failure — warning about it would be noise in every clean
|
|
100
|
+
// shutdown log.
|
|
101
|
+
if (!stopped) {
|
|
102
|
+
deps.log.warn(
|
|
103
|
+
`wearables auto-sync failed: ${describeErrorForOperator(err)} — retrying on the next tick`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
inFlight = null;
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
inFlight = run;
|
|
111
|
+
await run;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const timer = setInterval(() => {
|
|
115
|
+
void tick();
|
|
116
|
+
}, settings.intervalMinutes * 60_000);
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
tick,
|
|
121
|
+
stop: async () => {
|
|
122
|
+
stopped = true;
|
|
123
|
+
clearInterval(timer);
|
|
124
|
+
abortController.abort();
|
|
125
|
+
// tick() never rejects (all paths caught), so this only waits.
|
|
126
|
+
if (inFlight) await inFlight;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
package/src/wearables/cli.ts
CHANGED
|
@@ -125,6 +125,12 @@ function renderSyncSummary(summary: WearableSyncSummary): string {
|
|
|
125
125
|
` transcripts written: ${summary.transcriptsWritten.length > 0 ? summary.transcriptsWritten.join(", ") : "(none — unchanged)"}`,
|
|
126
126
|
` memories created: ${summary.memoriesCreated} (skipped ${summary.memoriesSkipped})`,
|
|
127
127
|
];
|
|
128
|
+
if (summary.memoriesPromoted > 0) {
|
|
129
|
+
lines.push(` memories promoted: ${summary.memoriesPromoted}`);
|
|
130
|
+
}
|
|
131
|
+
if (summary.memoriesDemoted > 0) {
|
|
132
|
+
lines.push(` memories demoted: ${summary.memoriesDemoted}`);
|
|
133
|
+
}
|
|
128
134
|
if (summary.nativeMemoriesImported > 0) {
|
|
129
135
|
lines.push(` native memories queued: ${summary.nativeMemoriesImported}`);
|
|
130
136
|
}
|