@remnic/core 9.3.755 → 9.3.756
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-admin-ops-surface.d.ts +1 -1
- package/dist/access-admin-ops-surface.js +34 -34
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +35 -35
- package/dist/access-cli.js +83 -83
- package/dist/access-http.d.ts +1 -1
- package/dist/access-http.js +44 -44
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +34 -34
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +41 -41
- package/dist/access-observe-write-surface.d.ts +225 -0
- package/dist/access-observe-write-surface.js +128 -0
- package/dist/access-operations-batch.js +38 -38
- package/dist/access-operations.d.ts +1 -1
- package/dist/access-operations.js +40 -40
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +34 -34
- package/dist/access-schema.d.ts +2 -2
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-C3jkLi2U.d.ts → access-service-CJwcDoQp.d.ts} +4 -5
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +34 -34
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/{auto-sync-G4YKBPOU.js → auto-sync-RZO5ZPHR.js} +3 -3
- package/dist/briefing.js +7 -7
- package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-FFWJ4X4A.js} +2 -2
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +15 -15
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-M5QKGHCR.js → chunk-3I2IASNK.js} +2 -2
- package/dist/{chunk-OCFITZRV.js → chunk-4AC57RCM.js} +4 -4
- package/dist/{chunk-3DOD6ICJ.js → chunk-4I45UZ4V.js} +5 -5
- package/dist/{chunk-TAPSNIMA.js → chunk-5CEJH5ZN.js} +4 -4
- package/dist/{chunk-I6IAJCJS.js → chunk-5VWRYFQE.js} +7 -7
- package/dist/{chunk-4LQUYBZM.js → chunk-5ZKT7WTR.js} +7 -7
- package/dist/{chunk-VL5QHTJI.js → chunk-6DA5BBLS.js} +117 -117
- package/dist/{chunk-LFKEFIJA.js → chunk-6DXXIXED.js} +4 -4
- package/dist/{chunk-XBM5DG4O.js → chunk-7APVFQ34.js} +680 -595
- package/dist/chunk-7APVFQ34.js.map +1 -0
- package/dist/{chunk-NCNOYM6F.js → chunk-7CFGXWYX.js} +2 -2
- package/dist/{chunk-TJATYB3F.js → chunk-A6XCHI53.js} +5 -5
- package/dist/{chunk-UQJ6YTKR.js → chunk-BTH6MQJT.js} +7 -7
- package/dist/{chunk-G7LCG56O.js → chunk-CXW6Y7SI.js} +4 -4
- package/dist/{chunk-GCEV6SXP.js → chunk-DWDDQLSZ.js} +2298 -2218
- package/dist/chunk-DWDDQLSZ.js.map +1 -0
- package/dist/{chunk-DS4QM6I7.js → chunk-EEKDRAWX.js} +4 -4
- package/dist/{chunk-I3UWEZTK.js → chunk-EF3VFQC4.js} +2 -2
- package/dist/{chunk-437S3G37.js → chunk-EJJQDP3H.js} +1 -1
- package/dist/{chunk-FQLM6ZUP.js → chunk-EVVLGYHR.js} +3 -3
- package/dist/{chunk-GGYUR4M2.js → chunk-GBXP62CL.js} +10 -10
- package/dist/{chunk-N4AGSRVA.js → chunk-GG6G2RYR.js} +9 -9
- package/dist/{chunk-JITAVROY.js → chunk-H2T4OJNI.js} +4 -4
- package/dist/{chunk-2MR3MFQB.js → chunk-JTYM4HPA.js} +2 -2
- package/dist/{chunk-LD53WPMU.js → chunk-LQ6JI4VH.js} +4 -4
- package/dist/{chunk-GPFXPC2V.js → chunk-NG2NHB4C.js} +7 -7
- package/dist/{chunk-CIDGUFAL.js → chunk-NLXKSM46.js} +2 -2
- package/dist/{chunk-T6WHI3JW.js → chunk-OLAVQS6S.js} +6 -6
- package/dist/{chunk-ER3CCWK2.js → chunk-ORAK3LNZ.js} +2 -2
- package/dist/{chunk-PJ7DIEFC.js → chunk-PBZQVVLG.js} +5 -5
- package/dist/{chunk-PJQMISPD.js → chunk-PPAEJXVF.js} +5 -5
- package/dist/{chunk-GMWKWA6N.js → chunk-PRX77NPC.js} +5 -5
- package/dist/{chunk-24463PIV.js → chunk-PS572XYO.js} +2 -2
- package/dist/{chunk-KKOCFUI5.js → chunk-REZ4TUD2.js} +10 -10
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-V5UV7BOA.js → chunk-SQNED75R.js} +1 -1
- package/dist/{chunk-FLVL4L3L.js → chunk-TCKP4EHY.js} +2 -2
- package/dist/{chunk-SP42M3XP.js → chunk-TXV3I2O6.js} +7 -7
- package/dist/{chunk-S6UCPBPL.js → chunk-UNV6ZGTS.js} +2 -2
- package/dist/{chunk-2BFLDH5F.js → chunk-WEPWY4HB.js} +2 -2
- package/dist/{chunk-YQGHW5ML.js → chunk-WRFKZEO6.js} +4 -4
- package/dist/{chunk-XCXN3D2U.js → chunk-Y4TAFMUE.js} +6 -6
- package/dist/{chunk-H6S3DJVE.js → chunk-YMGQXBPS.js} +2 -2
- package/dist/{chunk-T422SYM6.js → chunk-ZUDM75KG.js} +5 -5
- package/dist/{chunk-XD6JVK5P.js → chunk-ZUHD6XZZ.js} +43 -43
- package/dist/{chunk-FOJY75BL.js → chunk-ZW5SZ3TH.js} +12 -12
- package/dist/{cli-BJQ2ahrh.d.ts → cli-cZyANcmY.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +59 -59
- package/dist/compounding/engine.js +8 -8
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.js +8 -8
- package/dist/connectors/index.js +9 -9
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-2SODRNUF.js → contradiction-scan-3FJYWS3G.js} +3 -3
- package/dist/contradiction-scan-3FJYWS3G.js.map +1 -0
- package/dist/conversation-index/backend.js +2 -2
- package/dist/entity-retrieval.js +8 -8
- package/dist/extraction.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +126 -126
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +2 -2
- package/dist/maintenance/memory-governance.js +8 -8
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +9 -9
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/namespaces/migrate.js +18 -18
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.js +9 -9
- package/dist/operator-toolkit.js +24 -24
- package/dist/orchestration/maintenance.js +12 -12
- package/dist/orchestrator.js +82 -82
- package/dist/resume-bundles.js +2 -2
- package/dist/schemas.d.ts +10 -10
- package/dist/search/factory.js +8 -8
- package/dist/search/index.js +8 -8
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +11 -11
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.js +8 -8
- package/dist/shared-context/manager.d.ts +2 -2
- package/dist/storage.d.ts +60 -78
- package/dist/storage.js +14 -6
- package/dist/summarizer.js +2 -2
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +8 -8
- package/package.json +2 -2
- package/src/access-observe-write-surface.ts +1007 -0
- package/src/access-service.ts +48 -794
- package/src/orchestration/self-deps.ts +9 -1
- package/src/storage/entity-store.ts +4 -2
- package/src/storage/memory-read-store.ts +756 -0
- package/src/storage.ts +67 -627
- package/dist/chunk-GCEV6SXP.js.map +0 -1
- package/dist/chunk-XBM5DG4O.js.map +0 -1
- /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-observe-write-surface.js.map} +0 -0
- /package/dist/{auto-sync-G4YKBPOU.js.map → auto-sync-RZO5ZPHR.js.map} +0 -0
- /package/dist/{contradiction-scan-2SODRNUF.js.map → capsule-crypto-FFWJ4X4A.js.map} +0 -0
- /package/dist/{chunk-M5QKGHCR.js.map → chunk-3I2IASNK.js.map} +0 -0
- /package/dist/{chunk-OCFITZRV.js.map → chunk-4AC57RCM.js.map} +0 -0
- /package/dist/{chunk-3DOD6ICJ.js.map → chunk-4I45UZ4V.js.map} +0 -0
- /package/dist/{chunk-TAPSNIMA.js.map → chunk-5CEJH5ZN.js.map} +0 -0
- /package/dist/{chunk-I6IAJCJS.js.map → chunk-5VWRYFQE.js.map} +0 -0
- /package/dist/{chunk-4LQUYBZM.js.map → chunk-5ZKT7WTR.js.map} +0 -0
- /package/dist/{chunk-VL5QHTJI.js.map → chunk-6DA5BBLS.js.map} +0 -0
- /package/dist/{chunk-LFKEFIJA.js.map → chunk-6DXXIXED.js.map} +0 -0
- /package/dist/{chunk-NCNOYM6F.js.map → chunk-7CFGXWYX.js.map} +0 -0
- /package/dist/{chunk-TJATYB3F.js.map → chunk-A6XCHI53.js.map} +0 -0
- /package/dist/{chunk-UQJ6YTKR.js.map → chunk-BTH6MQJT.js.map} +0 -0
- /package/dist/{chunk-G7LCG56O.js.map → chunk-CXW6Y7SI.js.map} +0 -0
- /package/dist/{chunk-DS4QM6I7.js.map → chunk-EEKDRAWX.js.map} +0 -0
- /package/dist/{chunk-I3UWEZTK.js.map → chunk-EF3VFQC4.js.map} +0 -0
- /package/dist/{chunk-437S3G37.js.map → chunk-EJJQDP3H.js.map} +0 -0
- /package/dist/{chunk-FQLM6ZUP.js.map → chunk-EVVLGYHR.js.map} +0 -0
- /package/dist/{chunk-GGYUR4M2.js.map → chunk-GBXP62CL.js.map} +0 -0
- /package/dist/{chunk-N4AGSRVA.js.map → chunk-GG6G2RYR.js.map} +0 -0
- /package/dist/{chunk-JITAVROY.js.map → chunk-H2T4OJNI.js.map} +0 -0
- /package/dist/{chunk-2MR3MFQB.js.map → chunk-JTYM4HPA.js.map} +0 -0
- /package/dist/{chunk-LD53WPMU.js.map → chunk-LQ6JI4VH.js.map} +0 -0
- /package/dist/{chunk-GPFXPC2V.js.map → chunk-NG2NHB4C.js.map} +0 -0
- /package/dist/{chunk-CIDGUFAL.js.map → chunk-NLXKSM46.js.map} +0 -0
- /package/dist/{chunk-T6WHI3JW.js.map → chunk-OLAVQS6S.js.map} +0 -0
- /package/dist/{chunk-ER3CCWK2.js.map → chunk-ORAK3LNZ.js.map} +0 -0
- /package/dist/{chunk-PJ7DIEFC.js.map → chunk-PBZQVVLG.js.map} +0 -0
- /package/dist/{chunk-PJQMISPD.js.map → chunk-PPAEJXVF.js.map} +0 -0
- /package/dist/{chunk-GMWKWA6N.js.map → chunk-PRX77NPC.js.map} +0 -0
- /package/dist/{chunk-24463PIV.js.map → chunk-PS572XYO.js.map} +0 -0
- /package/dist/{chunk-KKOCFUI5.js.map → chunk-REZ4TUD2.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-V5UV7BOA.js.map → chunk-SQNED75R.js.map} +0 -0
- /package/dist/{chunk-FLVL4L3L.js.map → chunk-TCKP4EHY.js.map} +0 -0
- /package/dist/{chunk-SP42M3XP.js.map → chunk-TXV3I2O6.js.map} +0 -0
- /package/dist/{chunk-S6UCPBPL.js.map → chunk-UNV6ZGTS.js.map} +0 -0
- /package/dist/{chunk-2BFLDH5F.js.map → chunk-WEPWY4HB.js.map} +0 -0
- /package/dist/{chunk-YQGHW5ML.js.map → chunk-WRFKZEO6.js.map} +0 -0
- /package/dist/{chunk-XCXN3D2U.js.map → chunk-Y4TAFMUE.js.map} +0 -0
- /package/dist/{chunk-H6S3DJVE.js.map → chunk-YMGQXBPS.js.map} +0 -0
- /package/dist/{chunk-T422SYM6.js.map → chunk-ZUDM75KG.js.map} +0 -0
- /package/dist/{chunk-XD6JVK5P.js.map → chunk-ZUHD6XZZ.js.map} +0 -0
- /package/dist/{chunk-FOJY75BL.js.map → chunk-ZW5SZ3TH.js.map} +0 -0
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-GDB4J2H3.js";
|
|
9
9
|
import {
|
|
10
10
|
importCapsule
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-3I2IASNK.js";
|
|
12
12
|
import {
|
|
13
13
|
getTrustZoneStoreStatus,
|
|
14
14
|
isTrustZoneName,
|
|
@@ -29,6 +29,9 @@ import {
|
|
|
29
29
|
normalizeTags,
|
|
30
30
|
parseTagMatch
|
|
31
31
|
} from "./chunk-BT7NVCML.js";
|
|
32
|
+
import {
|
|
33
|
+
decideDisclosureEscalation
|
|
34
|
+
} from "./chunk-H7XKCNR6.js";
|
|
32
35
|
import {
|
|
33
36
|
toRecallExplainJson
|
|
34
37
|
} from "./chunk-2KK5WNQB.js";
|
|
@@ -36,14 +39,8 @@ import {
|
|
|
36
39
|
estimateRecallTokens
|
|
37
40
|
} from "./chunk-6MMONPHM.js";
|
|
38
41
|
import {
|
|
39
|
-
|
|
40
|
-
} from "./chunk-
|
|
41
|
-
import {
|
|
42
|
-
recordObjectiveStateSnapshotsFromObservedMessages
|
|
43
|
-
} from "./chunk-ICP53SKG.js";
|
|
44
|
-
import {
|
|
45
|
-
objectiveStateStoreOverrideForNamespace
|
|
46
|
-
} from "./chunk-6HMYUWXR.js";
|
|
42
|
+
namespaceCollectionName
|
|
43
|
+
} from "./chunk-PS572XYO.js";
|
|
47
44
|
import {
|
|
48
45
|
combineNamespaces,
|
|
49
46
|
defaultGitInvoker,
|
|
@@ -57,9 +54,6 @@ import {
|
|
|
57
54
|
resolveWritableNamespaceValue,
|
|
58
55
|
sanitizeFragment
|
|
59
56
|
} from "./chunk-W3M34XT4.js";
|
|
60
|
-
import {
|
|
61
|
-
namespaceCollectionName
|
|
62
|
-
} from "./chunk-24463PIV.js";
|
|
63
57
|
import {
|
|
64
58
|
namespaceIdentityFromToken
|
|
65
59
|
} from "./chunk-ZFXCQPNO.js";
|
|
@@ -70,13 +64,24 @@ import {
|
|
|
70
64
|
listMemoryGovernanceRuns,
|
|
71
65
|
readMemoryGovernanceRunArtifact,
|
|
72
66
|
runMemoryGovernance
|
|
73
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-PRX77NPC.js";
|
|
68
|
+
import {
|
|
69
|
+
recordObjectiveStateSnapshotsFromObservedMessages
|
|
70
|
+
} from "./chunk-ICP53SKG.js";
|
|
71
|
+
import {
|
|
72
|
+
objectiveStateStoreOverrideForNamespace
|
|
73
|
+
} from "./chunk-6HMYUWXR.js";
|
|
74
74
|
import {
|
|
75
75
|
recordMemoryOutcome
|
|
76
76
|
} from "./chunk-EIR5VLIH.js";
|
|
77
77
|
import {
|
|
78
78
|
buildGraphSnapshot
|
|
79
79
|
} from "./chunk-TQT3USCG.js";
|
|
80
|
+
import {
|
|
81
|
+
persistExplicitCapture,
|
|
82
|
+
queueExplicitCaptureForReview,
|
|
83
|
+
validateExplicitCaptureInput
|
|
84
|
+
} from "./chunk-K5LLP4CY.js";
|
|
80
85
|
import {
|
|
81
86
|
wrapWorkLayerContext
|
|
82
87
|
} from "./chunk-EI6V5UXY.js";
|
|
@@ -87,54 +92,20 @@ import {
|
|
|
87
92
|
buildProcedurePersistBody,
|
|
88
93
|
normalizeProcedureSteps
|
|
89
94
|
} from "./chunk-QDW3E4RD.js";
|
|
90
|
-
import {
|
|
91
|
-
persistExplicitCapture,
|
|
92
|
-
queueExplicitCaptureForReview,
|
|
93
|
-
validateExplicitCaptureInput
|
|
94
|
-
} from "./chunk-K5LLP4CY.js";
|
|
95
95
|
import {
|
|
96
96
|
CrossNamespaceBudget
|
|
97
97
|
} from "./chunk-ODPLEWB6.js";
|
|
98
|
-
import {
|
|
99
|
-
defaultCapsulesDir
|
|
100
|
-
} from "./chunk-ZY2MNJR6.js";
|
|
101
|
-
import {
|
|
102
|
-
filterTrajectoriesByLookbackDays,
|
|
103
|
-
readCausalTrajectoryRecords
|
|
104
|
-
} from "./chunk-V5UV7BOA.js";
|
|
105
|
-
import {
|
|
106
|
-
canReadNamespace,
|
|
107
|
-
canWriteNamespace,
|
|
108
|
-
defaultNamespaceForPrincipal,
|
|
109
|
-
recallNamespacesForPrincipal,
|
|
110
|
-
resolvePrincipal
|
|
111
|
-
} from "./chunk-PE6XR4GG.js";
|
|
112
|
-
import {
|
|
113
|
-
resolveAccessSetupCapabilities,
|
|
114
|
-
resolveCompressionCapabilities,
|
|
115
|
-
resolveConsolidationCapabilities,
|
|
116
|
-
resolveGraphConstructionCapabilities,
|
|
117
|
-
resolveIdentityContinuityCapabilities,
|
|
118
|
-
resolveIndexingCapabilities,
|
|
119
|
-
resolveMemoryLifecycleCapabilities,
|
|
120
|
-
resolveNamespaceCapabilities,
|
|
121
|
-
resolveObjectiveStateCapabilities,
|
|
122
|
-
resolveQmdCapabilities,
|
|
123
|
-
resolveRecallAuxiliaryCapabilities,
|
|
124
|
-
resolveRecallEnhancementCapabilities,
|
|
125
|
-
resolveSecurityCapabilities
|
|
126
|
-
} from "./chunk-VSF7AAGE.js";
|
|
127
98
|
import {
|
|
128
99
|
FileCalendarSource,
|
|
129
100
|
buildBriefing,
|
|
130
101
|
enqueuePassiveCorrectionNotification,
|
|
131
102
|
parseBriefingFocus,
|
|
132
103
|
parseBriefingWindow
|
|
133
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-YMGQXBPS.js";
|
|
134
105
|
import {
|
|
135
106
|
parseEntityFile,
|
|
136
107
|
selfDeps
|
|
137
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-7APVFQ34.js";
|
|
138
109
|
import {
|
|
139
110
|
supersessionKeysForFact
|
|
140
111
|
} from "./chunk-TKZ3AL5V.js";
|
|
@@ -148,6 +119,10 @@ import {
|
|
|
148
119
|
DEFAULT_RECALL_DISCLOSURE,
|
|
149
120
|
isRecallDisclosure
|
|
150
121
|
} from "./chunk-JS2HKKOK.js";
|
|
122
|
+
import {
|
|
123
|
+
inferMemoryStatus,
|
|
124
|
+
toMemoryPathRel
|
|
125
|
+
} from "./chunk-RULE4VG5.js";
|
|
151
126
|
import {
|
|
152
127
|
normalizeProjectionPreview,
|
|
153
128
|
normalizeProjectionTags
|
|
@@ -159,15 +134,40 @@ import {
|
|
|
159
134
|
displayErrorDetail
|
|
160
135
|
} from "./chunk-6KYMPV2O.js";
|
|
161
136
|
import {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} from "./chunk-RULE4VG5.js";
|
|
137
|
+
computeContentHash
|
|
138
|
+
} from "./chunk-W5H6ZRPT.js";
|
|
165
139
|
import {
|
|
166
140
|
createVersion
|
|
167
141
|
} from "./chunk-VF4XKTX3.js";
|
|
168
142
|
import {
|
|
169
|
-
|
|
170
|
-
} from "./chunk-
|
|
143
|
+
defaultCapsulesDir
|
|
144
|
+
} from "./chunk-ZY2MNJR6.js";
|
|
145
|
+
import {
|
|
146
|
+
filterTrajectoriesByLookbackDays,
|
|
147
|
+
readCausalTrajectoryRecords
|
|
148
|
+
} from "./chunk-SQNED75R.js";
|
|
149
|
+
import {
|
|
150
|
+
canReadNamespace,
|
|
151
|
+
canWriteNamespace,
|
|
152
|
+
defaultNamespaceForPrincipal,
|
|
153
|
+
recallNamespacesForPrincipal,
|
|
154
|
+
resolvePrincipal
|
|
155
|
+
} from "./chunk-PE6XR4GG.js";
|
|
156
|
+
import {
|
|
157
|
+
resolveAccessSetupCapabilities,
|
|
158
|
+
resolveCompressionCapabilities,
|
|
159
|
+
resolveConsolidationCapabilities,
|
|
160
|
+
resolveGraphConstructionCapabilities,
|
|
161
|
+
resolveIdentityContinuityCapabilities,
|
|
162
|
+
resolveIndexingCapabilities,
|
|
163
|
+
resolveMemoryLifecycleCapabilities,
|
|
164
|
+
resolveNamespaceCapabilities,
|
|
165
|
+
resolveObjectiveStateCapabilities,
|
|
166
|
+
resolveQmdCapabilities,
|
|
167
|
+
resolveRecallAuxiliaryCapabilities,
|
|
168
|
+
resolveRecallEnhancementCapabilities,
|
|
169
|
+
resolveSecurityCapabilities
|
|
170
|
+
} from "./chunk-VSF7AAGE.js";
|
|
171
171
|
import {
|
|
172
172
|
sanitizeMemoryContent
|
|
173
173
|
} from "./chunk-5GKPFL3S.js";
|
|
@@ -182,7 +182,7 @@ import {
|
|
|
182
182
|
} from "./chunk-NU3CSQ4H.js";
|
|
183
183
|
import {
|
|
184
184
|
exportCapsule
|
|
185
|
-
} from "./chunk-
|
|
185
|
+
} from "./chunk-TCKP4EHY.js";
|
|
186
186
|
import {
|
|
187
187
|
evaluateActionConfidence
|
|
188
188
|
} from "./chunk-Z2YFLXHU.js";
|
|
@@ -208,6 +208,9 @@ import {
|
|
|
208
208
|
hashAccessIdempotencyPayload
|
|
209
209
|
} from "./chunk-VFUEZZBS.js";
|
|
210
210
|
|
|
211
|
+
// src/access-observe-write-surface.ts
|
|
212
|
+
import { createHash as createHash6 } from "crypto";
|
|
213
|
+
|
|
211
214
|
// src/access-service.ts
|
|
212
215
|
import { stat as stat2 } from "fs/promises";
|
|
213
216
|
import * as nodeFs2 from "fs/promises";
|
|
@@ -4749,1266 +4752,1574 @@ async function promoteMemory(options) {
|
|
|
4749
4752
|
// src/access-service.ts
|
|
4750
4753
|
import * as nodePath3 from "path";
|
|
4751
4754
|
|
|
4752
|
-
// src/access-
|
|
4753
|
-
|
|
4754
|
-
import * as nodePath from "path";
|
|
4755
|
-
var AccessAdminOpsSurface = class {
|
|
4755
|
+
// src/access-lcm-surface.ts
|
|
4756
|
+
var AccessLcmSurface = class {
|
|
4756
4757
|
constructor(deps) {
|
|
4757
4758
|
this.deps = deps;
|
|
4758
4759
|
}
|
|
4759
4760
|
deps;
|
|
4760
|
-
async
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
} catch {
|
|
4779
|
-
return null;
|
|
4780
|
-
}
|
|
4781
|
-
})();
|
|
4782
|
-
const metrics = projected.metrics;
|
|
4783
|
-
const fallbackTransitionReport = {
|
|
4784
|
-
proposed: groupActionsByStatus(projectedProposedActions),
|
|
4785
|
-
applied: groupActionsByStatus(projectedAppliedActions)
|
|
4761
|
+
async lcmSearch(request) {
|
|
4762
|
+
if (!request.query || typeof request.query !== "string" || request.query.trim().length === 0) {
|
|
4763
|
+
throw new EngramAccessInputError("query is required and must be a non-empty string");
|
|
4764
|
+
}
|
|
4765
|
+
const principal = this.deps.resolveRequestPrincipal(request.sessionKey, request.authenticatedPrincipal);
|
|
4766
|
+
const hasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
|
|
4767
|
+
const profileLcmReadNamespaces = hasExplicitNamespace ? null : this.deps.resolveScopeProfileLcmReadNamespaces(
|
|
4768
|
+
request.sessionKey,
|
|
4769
|
+
request.authenticatedPrincipal
|
|
4770
|
+
);
|
|
4771
|
+
const namespace = hasExplicitNamespace ? this.deps.resolveReadableNamespace(request.namespace, principal) : profileLcmReadNamespaces !== null ? profileLcmReadNamespaces[0] : this.deps.resolveImplicitLcmReadFallbackNamespace(principal);
|
|
4772
|
+
if (!this.deps.orchestrator.lcmEngine || !this.deps.orchestrator.lcmEngine.enabled) {
|
|
4773
|
+
return {
|
|
4774
|
+
query: request.query,
|
|
4775
|
+
namespace: namespace ?? this.deps.orchestrator.config.defaultNamespace,
|
|
4776
|
+
results: [],
|
|
4777
|
+
count: 0,
|
|
4778
|
+
lcmEnabled: false
|
|
4786
4779
|
};
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
applied: hasGroupedGovernanceActions(projectedArtifact.transitionReport.applied) || projectedAppliedActions.length === 0 ? projectedArtifact.transitionReport.applied : fallbackTransitionReport.applied
|
|
4790
|
-
} : fallbackTransitionReport;
|
|
4791
|
-
const qualityScore = projectedArtifact?.qualityScore ?? metrics?.qualityScore ?? buildQualityScore(metrics?.reviewReasons ?? {
|
|
4792
|
-
exact_duplicate: 0,
|
|
4793
|
-
semantic_duplicate_candidate: 0,
|
|
4794
|
-
disputed_memory: 0,
|
|
4795
|
-
speculative_low_confidence: 0,
|
|
4796
|
-
archive_candidate: 0,
|
|
4797
|
-
explicit_capture_review: 0,
|
|
4798
|
-
malformed_import: 0
|
|
4799
|
-
});
|
|
4800
|
-
const effectiveMetrics = metrics ? { ...metrics, qualityScore: metrics.qualityScore ?? qualityScore } : metrics;
|
|
4780
|
+
}
|
|
4781
|
+
if (profileLcmReadNamespaces !== null && profileLcmReadNamespaces.length === 0) {
|
|
4801
4782
|
return {
|
|
4802
|
-
|
|
4803
|
-
namespace:
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
qualityScore,
|
|
4808
|
-
reviewQueue: projected.reviewQueueRows.map((row) => ({
|
|
4809
|
-
entryId: row.entryId,
|
|
4810
|
-
memoryId: row.memoryId,
|
|
4811
|
-
path: row.path,
|
|
4812
|
-
reasonCode: row.reasonCode,
|
|
4813
|
-
severity: row.severity,
|
|
4814
|
-
suggestedAction: row.suggestedAction,
|
|
4815
|
-
suggestedStatus: row.suggestedStatus,
|
|
4816
|
-
relatedMemoryIds: row.relatedMemoryIds
|
|
4817
|
-
})),
|
|
4818
|
-
appliedActions: projectedAppliedActions,
|
|
4819
|
-
transitionReport,
|
|
4820
|
-
report: projected.report
|
|
4783
|
+
query: request.query,
|
|
4784
|
+
namespace: this.deps.orchestrator.config.defaultNamespace,
|
|
4785
|
+
results: [],
|
|
4786
|
+
count: 0,
|
|
4787
|
+
lcmEnabled: true
|
|
4821
4788
|
};
|
|
4822
4789
|
}
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
metrics: artifact.metrics,
|
|
4832
|
-
qualityScore: artifact.qualityScore,
|
|
4833
|
-
reviewQueue: artifact.reviewQueue,
|
|
4834
|
-
appliedActions: artifact.appliedActions,
|
|
4835
|
-
transitionReport: artifact.transitionReport,
|
|
4836
|
-
report: artifact.report
|
|
4837
|
-
};
|
|
4838
|
-
}
|
|
4839
|
-
async governanceRun(request, principal) {
|
|
4840
|
-
const deepSleep = this.deps.orchestrator.config.dreamsPhases.deepSleep;
|
|
4841
|
-
if (deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
|
|
4842
|
-
throw new Error("memory governance is disabled by dreams.phases.deepSleep.enabled=false");
|
|
4790
|
+
if (namespace === void 0) {
|
|
4791
|
+
return {
|
|
4792
|
+
query: request.query,
|
|
4793
|
+
namespace: this.deps.orchestrator.config.defaultNamespace,
|
|
4794
|
+
results: [],
|
|
4795
|
+
count: 0,
|
|
4796
|
+
lcmEnabled: true
|
|
4797
|
+
};
|
|
4843
4798
|
}
|
|
4844
|
-
const
|
|
4799
|
+
const limit = Math.max(1, Math.min(request.limit ?? 10, 100));
|
|
4800
|
+
const lcmReadNamespace = profileLcmReadNamespaces !== null ? profileLcmReadNamespaces[0] ?? this.deps.orchestrator.config.defaultNamespace : this.deps.resolveLcmReadNamespace(
|
|
4845
4801
|
request.namespace,
|
|
4846
|
-
|
|
4847
|
-
request.
|
|
4802
|
+
namespace,
|
|
4803
|
+
request.sessionKey,
|
|
4804
|
+
request.authenticatedPrincipal
|
|
4848
4805
|
);
|
|
4849
|
-
const
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
namespace: resolvedNamespace,
|
|
4871
|
-
runId: result.runId,
|
|
4872
|
-
traceId: result.traceId,
|
|
4873
|
-
mode: result.mode,
|
|
4874
|
-
reviewQueueCount: result.reviewQueue.length,
|
|
4875
|
-
proposedActionCount: result.proposedActions.length,
|
|
4876
|
-
appliedActionCount: result.appliedActions.length,
|
|
4877
|
-
summaryPath: result.summaryPath,
|
|
4878
|
-
reportPath: result.reportPath
|
|
4879
|
-
};
|
|
4880
|
-
}
|
|
4881
|
-
async conversationIndexUpdate(request = {}) {
|
|
4882
|
-
if (!resolveIndexingCapabilities(this.deps.orchestrator.config).conversationIndex) {
|
|
4883
|
-
return {
|
|
4884
|
-
enabled: false,
|
|
4885
|
-
sessions: 0,
|
|
4886
|
-
chunks: 0,
|
|
4887
|
-
skipped: 0,
|
|
4888
|
-
skippedSessionKeys: [],
|
|
4889
|
-
embeddedRuns: 0,
|
|
4890
|
-
reason: "disabled"
|
|
4891
|
-
};
|
|
4892
|
-
}
|
|
4893
|
-
const hours = typeof request.hours === "number" && Number.isFinite(request.hours) ? Math.max(1, Math.floor(request.hours)) : 24;
|
|
4894
|
-
let sessionKey;
|
|
4895
|
-
if (request.sessionKey !== void 0) {
|
|
4896
|
-
if (typeof request.sessionKey !== "string" || request.sessionKey.trim().length === 0) {
|
|
4897
|
-
throw new EngramAccessInputError("sessionKey must be a non-empty string when provided");
|
|
4898
|
-
}
|
|
4899
|
-
sessionKey = request.sessionKey.trim();
|
|
4900
|
-
}
|
|
4901
|
-
if (sessionKey) {
|
|
4902
|
-
const result = await this.deps.orchestrator.updateConversationIndex(
|
|
4903
|
-
sessionKey,
|
|
4904
|
-
hours,
|
|
4905
|
-
{ embed: request.embed }
|
|
4906
|
-
);
|
|
4806
|
+
const lcmSessionKeyIds = request.sessionKey ? profileLcmReadNamespaces !== null ? this.deps.lcmSessionIdsForNamespaces(
|
|
4807
|
+
profileLcmReadNamespaces,
|
|
4808
|
+
request.sessionKey
|
|
4809
|
+
) : this.deps.resolveLcmReadSessionIds(
|
|
4810
|
+
request.namespace,
|
|
4811
|
+
namespace,
|
|
4812
|
+
request.sessionKey,
|
|
4813
|
+
request.authenticatedPrincipal
|
|
4814
|
+
) : [void 0];
|
|
4815
|
+
const lcmSessionPrefixes = request.sessionPrefix ? profileLcmReadNamespaces !== null && !request.sessionKey ? this.deps.lcmSessionIdsForNamespaces(
|
|
4816
|
+
profileLcmReadNamespaces,
|
|
4817
|
+
request.sessionPrefix
|
|
4818
|
+
) : [
|
|
4819
|
+
lcmSessionKeyForNamespace(
|
|
4820
|
+
lcmReadNamespace,
|
|
4821
|
+
request.sessionPrefix,
|
|
4822
|
+
this.deps.orchestrator.config.defaultNamespace
|
|
4823
|
+
) ?? request.sessionPrefix
|
|
4824
|
+
] : [void 0];
|
|
4825
|
+
const hasScopedSession = typeof request.sessionKey === "string" && request.sessionKey.length > 0 || lcmSessionPrefixes.some((prefix) => typeof prefix === "string" && prefix.length > 0);
|
|
4826
|
+
if (!hasScopedSession && resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces === true) {
|
|
4907
4827
|
return {
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
skippedSessionKeys: result.skipped ? [sessionKey] : [],
|
|
4914
|
-
embeddedRuns: result.embedded ? 1 : 0,
|
|
4915
|
-
reason: result.reason,
|
|
4916
|
-
retryAfterMs: result.retryAfterMs
|
|
4828
|
+
query: request.query,
|
|
4829
|
+
namespace,
|
|
4830
|
+
results: [],
|
|
4831
|
+
count: 0,
|
|
4832
|
+
lcmEnabled: true
|
|
4917
4833
|
};
|
|
4918
4834
|
}
|
|
4919
|
-
const
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
const
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
}
|
|
4935
|
-
if (result.embedded) {
|
|
4936
|
-
embeddedRuns += 1;
|
|
4835
|
+
const seenRows = /* @__PURE__ */ new Set();
|
|
4836
|
+
const results = [];
|
|
4837
|
+
const lcmSearches = [];
|
|
4838
|
+
for (const lcmSessionKey of lcmSessionKeyIds) {
|
|
4839
|
+
for (const lcmSessionPrefix of lcmSessionPrefixes) {
|
|
4840
|
+
lcmSearches.push({
|
|
4841
|
+
key: lcmSessionKey,
|
|
4842
|
+
prefix: lcmSessionPrefix,
|
|
4843
|
+
promise: this.deps.orchestrator.lcmEngine.searchContextFull(
|
|
4844
|
+
request.query,
|
|
4845
|
+
limit,
|
|
4846
|
+
lcmSessionKey,
|
|
4847
|
+
lcmSessionPrefix
|
|
4848
|
+
)
|
|
4849
|
+
});
|
|
4937
4850
|
}
|
|
4938
4851
|
}
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
return {
|
|
4952
|
-
enabled: false,
|
|
4953
|
-
reason: "disabled",
|
|
4954
|
-
message: "Profiling is disabled. Set profilingEnabled: true in your plugin config to enable."
|
|
4955
|
-
};
|
|
4956
|
-
}
|
|
4957
|
-
const format = request.format ?? "ascii";
|
|
4958
|
-
if (format !== "ascii" && format !== "json") {
|
|
4959
|
-
throw new EngramAccessInputError("format must be one of: ascii, json");
|
|
4960
|
-
}
|
|
4961
|
-
const limit = request.limit ?? 5;
|
|
4962
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 20) {
|
|
4963
|
-
throw new EngramAccessInputError("limit must be an integer between 1 and 20");
|
|
4964
|
-
}
|
|
4965
|
-
const traces = profiler.getRecentTraces(limit);
|
|
4966
|
-
const stats = profiler.getStats();
|
|
4967
|
-
const bottleneck = profiler.identifyBottleneck();
|
|
4968
|
-
if (format === "json") {
|
|
4969
|
-
return {
|
|
4970
|
-
enabled: true,
|
|
4971
|
-
format,
|
|
4972
|
-
traces,
|
|
4973
|
-
stats,
|
|
4974
|
-
bottleneck
|
|
4975
|
-
};
|
|
4976
|
-
}
|
|
4977
|
-
const lines = [];
|
|
4978
|
-
lines.push("Engram Profiling Report");
|
|
4979
|
-
lines.push("=".repeat(60));
|
|
4980
|
-
lines.push("");
|
|
4981
|
-
const allBuckets = [
|
|
4982
|
-
["byKind", stats.byKind],
|
|
4983
|
-
["bySpan", stats.bySpan]
|
|
4984
|
-
];
|
|
4985
|
-
const hasStats = allBuckets.some(([, entries]) => Object.keys(entries).length > 0);
|
|
4986
|
-
if (hasStats) {
|
|
4987
|
-
lines.push("Aggregate Stats (all retained traces):");
|
|
4988
|
-
for (const [bucket, entries] of allBuckets) {
|
|
4989
|
-
for (const [key, summary] of Object.entries(entries)) {
|
|
4990
|
-
lines.push(
|
|
4991
|
-
` ${bucket}/${key}: avg=${summary.avgMs}ms p50=${summary.p50Ms}ms p95=${summary.p95Ms}ms max=${summary.maxMs}ms (n=${summary.count})`
|
|
4992
|
-
);
|
|
4993
|
-
}
|
|
4852
|
+
const settledSearches = await Promise.allSettled(
|
|
4853
|
+
lcmSearches.map((search) => search.promise)
|
|
4854
|
+
);
|
|
4855
|
+
for (let i = 0; i < settledSearches.length; i += 1) {
|
|
4856
|
+
if (results.length >= limit) break;
|
|
4857
|
+
const settled = settledSearches[i];
|
|
4858
|
+
if (!settled || settled.status === "rejected") {
|
|
4859
|
+
const failed = lcmSearches[i];
|
|
4860
|
+
log.warn(
|
|
4861
|
+
`lcmSearch: failed for key=${failed?.key ?? "<none>"} prefix=${failed?.prefix ?? "<none>"}: ${settled?.status === "rejected" ? settled.reason : "missing result"}`
|
|
4862
|
+
);
|
|
4863
|
+
continue;
|
|
4994
4864
|
}
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
lines.push(formatProfileTraceAscii(trace));
|
|
5006
|
-
lines.push("");
|
|
4865
|
+
for (const r of settled.value) {
|
|
4866
|
+
const dedupeKey = `${r.session_id}\0${r.turn_index}`;
|
|
4867
|
+
if (seenRows.has(dedupeKey)) continue;
|
|
4868
|
+
seenRows.add(dedupeKey);
|
|
4869
|
+
results.push({
|
|
4870
|
+
sessionId: r.session_id,
|
|
4871
|
+
content: r.content,
|
|
4872
|
+
turnIndex: r.turn_index
|
|
4873
|
+
});
|
|
4874
|
+
if (results.length >= limit) break;
|
|
5007
4875
|
}
|
|
5008
4876
|
}
|
|
5009
4877
|
return {
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
4878
|
+
query: request.query,
|
|
4879
|
+
namespace,
|
|
4880
|
+
results,
|
|
4881
|
+
count: results.length,
|
|
4882
|
+
lcmEnabled: true
|
|
5013
4883
|
};
|
|
5014
4884
|
}
|
|
5015
4885
|
/**
|
|
5016
|
-
*
|
|
4886
|
+
* Resolve the effective LCM NAMESPACE a same-session operation must prefix
|
|
4887
|
+
* with (the namespace half of {@link resolveLcmReadSessionKey}). Split out so
|
|
4888
|
+
* `lcmSearch` can apply ONE namespace to BOTH its `sessionKey` and its
|
|
4889
|
+
* `sessionPrefix` — the prefix is a search fragment, not a real session, so its
|
|
4890
|
+
* own coding context can't be looked up; it must inherit the namespace resolved
|
|
4891
|
+
* from the real session (`sessionKeyForOverlay`).
|
|
5017
4892
|
*
|
|
5018
|
-
*
|
|
5019
|
-
*
|
|
5020
|
-
*
|
|
5021
|
-
* `
|
|
5022
|
-
*
|
|
4893
|
+
* `purpose` selects the AUTHORIZATION gate applied before honouring the
|
|
4894
|
+
* coding overlay (#1505 round 3 + round 4, codex P2):
|
|
4895
|
+
*
|
|
4896
|
+
* - `"read"` (`lcmSearch` / raw-excerpt recall): the overlay rows are only
|
|
4897
|
+
* visible when the principal SELF base is in the READABLE RECALL SET — the
|
|
4898
|
+
* same gate the orchestrator's `lcmReadNamespaceForSession` and the recall
|
|
4899
|
+
* namespace set use (`recallNamespacesForPrincipal`, gated by both
|
|
4900
|
+
* `defaultRecallNamespaces.includes("self")` AND `canReadNamespace`). A
|
|
4901
|
+
* caller that passed the default read check must NOT receive
|
|
4902
|
+
* `<principal>-project-*` rows the policy never granted (cross-tenant read
|
|
4903
|
+
* leak). When the self base is not readable, keep the just-authorized
|
|
4904
|
+
* namespace (collapses to the raw key on the default store).
|
|
4905
|
+
*
|
|
4906
|
+
* - `"write"` (`lcmCompactionFlush` / `lcmCompactionRecord`): these are
|
|
4907
|
+
* write/maintenance operations on the SAME queue `observe` just wrote, so
|
|
4908
|
+
* the gate must mirror observe's WRITE authorization (`canWriteNamespace`
|
|
4909
|
+
* on the self base), NOT readability. A principal that can WRITE but not
|
|
4910
|
+
* READ its self namespace (or whose `defaultRecallNamespaces` omits `self`)
|
|
4911
|
+
* archived under the overlay key via `observe`; gating compaction by
|
|
4912
|
+
* readability would fall back to the default/raw key and leave that queue
|
|
4913
|
+
* never flushed/recorded (round-4 codex P2). Write-authorized ⇒ overlay
|
|
4914
|
+
* key, matching the observe write key (rule 42 read/write parity; rule 39
|
|
4915
|
+
* identical gates across paths).
|
|
5023
4916
|
*/
|
|
5024
|
-
|
|
5025
|
-
const
|
|
5026
|
-
|
|
4917
|
+
resolveLcmReadNamespace(explicitNamespace, resolvedNamespace, sessionKeyForOverlay, authenticatedPrincipal, purpose = "read") {
|
|
4918
|
+
const hasExplicitNamespace = typeof explicitNamespace === "string" && explicitNamespace.trim().length > 0;
|
|
4919
|
+
if (hasExplicitNamespace) return resolvedNamespace;
|
|
4920
|
+
const principal = this.deps.resolveRequestPrincipal(
|
|
4921
|
+
sessionKeyForOverlay,
|
|
5027
4922
|
authenticatedPrincipal
|
|
5028
4923
|
);
|
|
5029
|
-
const
|
|
5030
|
-
const
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
`graphSnapshot: rejected absolute edge endpoint (len=${relPath.length}) outside namespace root`
|
|
5043
|
-
);
|
|
5044
|
-
return null;
|
|
5045
|
-
}
|
|
5046
|
-
const candidate = nodePath.resolve(namespaceRootReal, relPath);
|
|
5047
|
-
if (candidate !== namespaceRootReal && !candidate.startsWith(namespaceRootWithSep)) {
|
|
5048
|
-
log.warn(
|
|
5049
|
-
`graphSnapshot: rejected traversing edge endpoint (len=${relPath.length}) outside namespace root`
|
|
5050
|
-
);
|
|
5051
|
-
return null;
|
|
5052
|
-
}
|
|
5053
|
-
let canonical;
|
|
5054
|
-
try {
|
|
5055
|
-
canonical = await nodeFs.realpath(candidate);
|
|
5056
|
-
} catch {
|
|
5057
|
-
canonical = candidate;
|
|
5058
|
-
}
|
|
5059
|
-
if (canonical !== namespaceRootReal && !canonical.startsWith(namespaceRootWithSep)) {
|
|
5060
|
-
log.warn(
|
|
5061
|
-
`graphSnapshot: rejected symlinked edge endpoint (len=${relPath.length}) that resolved outside namespace root`
|
|
5062
|
-
);
|
|
5063
|
-
return null;
|
|
5064
|
-
}
|
|
5065
|
-
const memory = await storage.readMemoryByPath(canonical);
|
|
5066
|
-
if (!memory) return null;
|
|
5067
|
-
const fm = memory.frontmatter;
|
|
5068
|
-
return {
|
|
5069
|
-
category: fm.category ?? "unknown",
|
|
5070
|
-
label: fm.id ?? nodePath.basename(canonical, nodePath.extname(canonical)),
|
|
5071
|
-
updated: fm.updated
|
|
5072
|
-
};
|
|
5073
|
-
};
|
|
5074
|
-
return buildGraphSnapshot({
|
|
5075
|
-
memoryDir: namespaceRootReal,
|
|
5076
|
-
graphConfig: {
|
|
5077
|
-
entityGraph: graphCaps.entityGraph,
|
|
5078
|
-
timeGraph: graphCaps.timeGraph,
|
|
5079
|
-
causalGraph: graphCaps.causalGraph
|
|
5080
|
-
},
|
|
5081
|
-
request: {
|
|
5082
|
-
limit: request.limit,
|
|
5083
|
-
since: request.since,
|
|
5084
|
-
focusNodeId: request.focusNodeId,
|
|
5085
|
-
categories: request.categories
|
|
5086
|
-
},
|
|
5087
|
-
loadNode
|
|
5088
|
-
});
|
|
4924
|
+
const base = defaultNamespaceForPrincipal(principal, this.deps.orchestrator.config);
|
|
4925
|
+
const overlaid = this.deps.orchestrator.applyCodingNamespaceOverlay(
|
|
4926
|
+
sessionKeyForOverlay,
|
|
4927
|
+
base
|
|
4928
|
+
);
|
|
4929
|
+
if (overlaid === base) return this.deps.orchestrator.config.defaultNamespace;
|
|
4930
|
+
const authorized = purpose === "write" ? canWriteNamespace(principal, base, this.deps.orchestrator.config) : recallNamespacesForPrincipal(
|
|
4931
|
+
principal,
|
|
4932
|
+
this.deps.orchestrator.config
|
|
4933
|
+
).includes(base);
|
|
4934
|
+
if (authorized) return overlaid;
|
|
4935
|
+
if (purpose === "read") return this.deps.orchestrator.config.defaultNamespace;
|
|
4936
|
+
return resolvedNamespace;
|
|
5089
4937
|
}
|
|
5090
4938
|
/**
|
|
5091
|
-
*
|
|
4939
|
+
* Resolve the namespace the raw-disclosure excerpt lookup
|
|
4940
|
+
* ({@link fetchRawExcerpts}) must prefix its LCM `session_id` with (#1505
|
|
4941
|
+
* thread 2f7). Raw disclosure reads the SAME LCM archive `lcmSearch` and the
|
|
4942
|
+
* in-prompt LCM sections read, so it MUST pass through the identical
|
|
4943
|
+
* read-authorization gate — NOT `snapshot.namespace`, which records the
|
|
4944
|
+
* effective WRITE/overlay namespace (`<principal>-project-*`) even when the
|
|
4945
|
+
* principal can WRITE but not READ its self base (or `defaultRecallNamespaces`
|
|
4946
|
+
* omits `self`). Routing through `resolveLcmReadNamespace(..., "read")` makes
|
|
4947
|
+
* raw disclosure fall back to the default store exactly like normal recall +
|
|
4948
|
+
* `lcmSearch`, so it never attaches overlay transcript rows the read gate
|
|
4949
|
+
* excludes (cross-tenant read leak). Collapses to the default store / raw
|
|
4950
|
+
* sessionKey for single-store / no-overlay / explicit-default flows, so
|
|
4951
|
+
* single-user recall is byte-for-byte unchanged.
|
|
5092
4952
|
*
|
|
5093
|
-
*
|
|
5094
|
-
*
|
|
5095
|
-
*
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
const capsulesDir = defaultCapsulesDir(storage.dir);
|
|
5101
|
-
let dirEntries;
|
|
5102
|
-
try {
|
|
5103
|
-
const capsulesDirStat = await nodeFs.lstat(capsulesDir);
|
|
5104
|
-
if (capsulesDirStat.isSymbolicLink()) {
|
|
5105
|
-
throw new EngramAccessInputError("capsule list failed: capsule store directory must not be a symlink");
|
|
5106
|
-
}
|
|
5107
|
-
if (!capsulesDirStat.isDirectory()) {
|
|
5108
|
-
throw new EngramAccessInputError("capsule list failed: capsule store path must be a directory");
|
|
5109
|
-
}
|
|
5110
|
-
dirEntries = await nodeFs.readdir(capsulesDir, { withFileTypes: true });
|
|
5111
|
-
} catch (err) {
|
|
5112
|
-
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
5113
|
-
if (code === "ENOENT") {
|
|
5114
|
-
return { namespace: resolvedNamespace, capsulesDir, capsules: [] };
|
|
5115
|
-
}
|
|
5116
|
-
throw err;
|
|
5117
|
-
}
|
|
5118
|
-
const archiveNames = dirEntries.filter(
|
|
5119
|
-
(entry) => entry.isFile() && (entry.name.endsWith(".capsule.json.gz") || entry.name.endsWith(".capsule.json.gz.enc"))
|
|
5120
|
-
).map((entry) => entry.name).sort();
|
|
5121
|
-
const capsules = [];
|
|
5122
|
-
for (const archiveName of archiveNames) {
|
|
5123
|
-
const archivePath = nodePath.join(capsulesDir, archiveName);
|
|
5124
|
-
const id = archiveName.replace(/\.capsule\.json\.gz\.enc$/, "").replace(/\.capsule\.json\.gz$/, "");
|
|
5125
|
-
const manifestPath = nodePath.join(capsulesDir, `${id}.manifest.json`);
|
|
5126
|
-
let createdAt = null;
|
|
5127
|
-
let pluginVersion = null;
|
|
5128
|
-
let fileCount = null;
|
|
5129
|
-
let description = null;
|
|
5130
|
-
let manifestPathOrNull = manifestPath;
|
|
5131
|
-
try {
|
|
5132
|
-
const manifestStat = await nodeFs.lstat(manifestPath);
|
|
5133
|
-
if (manifestStat.isSymbolicLink() || !manifestStat.isFile()) {
|
|
5134
|
-
capsules.push({
|
|
5135
|
-
id,
|
|
5136
|
-
archivePath,
|
|
5137
|
-
manifestPath: manifestPathOrNull,
|
|
5138
|
-
createdAt,
|
|
5139
|
-
pluginVersion,
|
|
5140
|
-
fileCount,
|
|
5141
|
-
description
|
|
5142
|
-
});
|
|
5143
|
-
continue;
|
|
5144
|
-
}
|
|
5145
|
-
const raw = await nodeFs.readFile(manifestPath, "utf-8");
|
|
5146
|
-
const sidecar = JSON.parse(raw);
|
|
5147
|
-
createdAt = typeof sidecar.createdAt === "string" ? sidecar.createdAt : null;
|
|
5148
|
-
pluginVersion = typeof sidecar.pluginVersion === "string" ? sidecar.pluginVersion : null;
|
|
5149
|
-
fileCount = Array.isArray(sidecar.files) ? sidecar.files.length : null;
|
|
5150
|
-
const capsule = sidecar.capsule;
|
|
5151
|
-
description = capsule && typeof capsule.description === "string" ? capsule.description : null;
|
|
5152
|
-
} catch (err) {
|
|
5153
|
-
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
5154
|
-
if (code === "ENOENT") {
|
|
5155
|
-
manifestPathOrNull = null;
|
|
5156
|
-
}
|
|
5157
|
-
}
|
|
5158
|
-
capsules.push({
|
|
5159
|
-
id,
|
|
5160
|
-
archivePath,
|
|
5161
|
-
manifestPath: manifestPathOrNull,
|
|
5162
|
-
createdAt,
|
|
5163
|
-
pluginVersion,
|
|
5164
|
-
fileCount,
|
|
5165
|
-
description
|
|
5166
|
-
});
|
|
5167
|
-
}
|
|
5168
|
-
return { namespace: resolvedNamespace, capsulesDir, capsules };
|
|
5169
|
-
}
|
|
5170
|
-
/**
|
|
5171
|
-
* Manually invoke a single Dreams phase pass (PR 4/4).
|
|
4953
|
+
* Returns `undefined` when NO readable LCM namespace exists for an IMPLICIT
|
|
4954
|
+
* (no explicit `namespace`) raw recall — i.e. a restrictive `default` READ
|
|
4955
|
+
* policy denies the principal `default` AND no overlay/self namespace is
|
|
4956
|
+
* readable. In that case the caller emits NO excerpts rather than throwing
|
|
4957
|
+
* `namespace is not readable: default` (#1505 thread NBHWz): normal recall
|
|
4958
|
+
* still succeeds via `recallNamespacesForPrincipal`, so `disclosure: "raw"`
|
|
4959
|
+
* must degrade gracefully (empty excerpts), never pre-authorize `default`.
|
|
5172
4960
|
*
|
|
5173
|
-
*
|
|
5174
|
-
*
|
|
5175
|
-
*
|
|
4961
|
+
* IMPLICIT-namespace fallback selection derives from the ALREADY
|
|
4962
|
+
* read-authorized recall namespace set (`recallNamespacesForPrincipal` +
|
|
4963
|
+
* `canReadNamespace`) — the principal's self base when it is in the readable
|
|
4964
|
+
* recall set, else `config.defaultNamespace` ONLY when the principal may read
|
|
4965
|
+
* it. It NEVER pre-authorizes `default`. An EXPLICIT `namespace` is still
|
|
4966
|
+
* authorized strictly via `resolveReadableNamespace` (explicit reads must pass
|
|
4967
|
+
* the ACL — no behavior change).
|
|
5176
4968
|
*/
|
|
5177
|
-
|
|
5178
|
-
const
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
4969
|
+
resolveRawExcerptReadNamespace(explicitNamespace, sessionKey, authenticatedPrincipal) {
|
|
4970
|
+
const principal = this.deps.resolveRequestPrincipal(
|
|
4971
|
+
sessionKey,
|
|
4972
|
+
authenticatedPrincipal
|
|
4973
|
+
);
|
|
4974
|
+
const hasExplicitNamespace = typeof explicitNamespace === "string" && explicitNamespace.trim().length > 0;
|
|
4975
|
+
if (hasExplicitNamespace) {
|
|
4976
|
+
const resolvedNamespace = this.deps.resolveReadableNamespace(
|
|
4977
|
+
explicitNamespace,
|
|
4978
|
+
principal
|
|
5183
4979
|
);
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
4980
|
+
return this.deps.resolveLcmReadNamespace(
|
|
4981
|
+
explicitNamespace,
|
|
4982
|
+
resolvedNamespace,
|
|
4983
|
+
sessionKey,
|
|
4984
|
+
authenticatedPrincipal,
|
|
4985
|
+
"read"
|
|
5189
4986
|
);
|
|
5190
4987
|
}
|
|
5191
|
-
const
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
void 0,
|
|
5195
|
-
options.authenticatedPrincipal
|
|
4988
|
+
const profileReadNamespaces = this.deps.resolveScopeProfileLcmReadNamespaces(
|
|
4989
|
+
sessionKey,
|
|
4990
|
+
authenticatedPrincipal
|
|
5196
4991
|
);
|
|
5197
|
-
|
|
5198
|
-
const
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
}
|
|
5207
|
-
const result2 = await this.deps.orchestrator.runSemanticConsolidationNow({
|
|
5208
|
-
dryRun: false,
|
|
5209
|
-
storage
|
|
5210
|
-
});
|
|
5211
|
-
const itemsProcessed = result2.clusters.reduce(
|
|
5212
|
-
(sum, cluster) => sum + cluster.memories.length,
|
|
5213
|
-
0
|
|
5214
|
-
);
|
|
5215
|
-
return {
|
|
5216
|
-
itemsProcessed,
|
|
5217
|
-
notes: `REM consolidation found ${result2.clustersFound} clusters`
|
|
5218
|
-
};
|
|
5219
|
-
};
|
|
5220
|
-
const governanceRunner = options.phase === "deepSleep" ? async (_opts) => {
|
|
5221
|
-
return this.deps.orchestrator.runDeepSleepGovernanceNow({
|
|
5222
|
-
storage,
|
|
5223
|
-
dryRun: _opts.dryRun
|
|
5224
|
-
});
|
|
5225
|
-
} : void 0;
|
|
5226
|
-
const result = await runDreamsPhase(
|
|
5227
|
-
{ memoryDir, phase: options.phase, dryRun },
|
|
5228
|
-
governanceRunner,
|
|
5229
|
-
phaseRunner
|
|
4992
|
+
if (profileReadNamespaces !== null) return profileReadNamespaces[0];
|
|
4993
|
+
const fallbackNamespace = this.deps.resolveImplicitLcmReadFallbackNamespace(principal);
|
|
4994
|
+
if (fallbackNamespace === void 0) return void 0;
|
|
4995
|
+
return this.deps.resolveLcmReadNamespace(
|
|
4996
|
+
explicitNamespace,
|
|
4997
|
+
fallbackNamespace,
|
|
4998
|
+
sessionKey,
|
|
4999
|
+
authenticatedPrincipal,
|
|
5000
|
+
"read"
|
|
5230
5001
|
);
|
|
5231
|
-
return {
|
|
5232
|
-
phase: result.phase,
|
|
5233
|
-
dryRun: result.dryRun,
|
|
5234
|
-
durationMs: result.durationMs,
|
|
5235
|
-
itemsProcessed: result.itemsProcessed,
|
|
5236
|
-
notes: result.notes
|
|
5237
|
-
};
|
|
5238
5002
|
}
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
const
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
const codingContext2 = request.sessionKey ? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
|
|
5256
|
-
const plan = resolveScopePlan({
|
|
5257
|
-
config,
|
|
5258
|
-
sessionKey: request.sessionKey,
|
|
5259
|
-
principalOverride: request.principal,
|
|
5260
|
-
codingContext: codingContext2,
|
|
5261
|
-
namespacesEnabled
|
|
5262
|
-
});
|
|
5263
|
-
sourceNamespace = this.deps.resolveReadableNamespace(
|
|
5264
|
-
plan.baseNamespace,
|
|
5265
|
-
request.principal
|
|
5266
|
-
);
|
|
5003
|
+
resolveLcmReadSessionIds(explicitNamespace, resolvedNamespace, sessionKey, authenticatedPrincipal) {
|
|
5004
|
+
const primary = this.deps.resolveLcmReadSessionKey(
|
|
5005
|
+
explicitNamespace,
|
|
5006
|
+
resolvedNamespace,
|
|
5007
|
+
sessionKey,
|
|
5008
|
+
authenticatedPrincipal,
|
|
5009
|
+
"read"
|
|
5010
|
+
);
|
|
5011
|
+
const hasExplicitNamespace = typeof explicitNamespace === "string" && explicitNamespace.trim().length > 0;
|
|
5012
|
+
if (hasExplicitNamespace) return [primary];
|
|
5013
|
+
const profileReadNamespaces = this.deps.resolveScopeProfileLcmReadNamespaces(
|
|
5014
|
+
sessionKey,
|
|
5015
|
+
authenticatedPrincipal
|
|
5016
|
+
);
|
|
5017
|
+
if (profileReadNamespaces !== null) {
|
|
5018
|
+
return this.deps.lcmSessionIdsForNamespaces(profileReadNamespaces, sessionKey);
|
|
5267
5019
|
}
|
|
5268
|
-
const
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
}
|
|
5306
|
-
);
|
|
5307
|
-
if (tombstoneBlocked) {
|
|
5308
|
-
throw new Error(
|
|
5309
|
-
"target namespace tombstone-blocked the promoted content (pending_review)"
|
|
5310
|
-
);
|
|
5311
|
-
}
|
|
5312
|
-
return id;
|
|
5313
|
-
}
|
|
5314
|
-
};
|
|
5315
|
-
try {
|
|
5316
|
-
return await promoteMemory({
|
|
5317
|
-
config,
|
|
5318
|
-
sourceMemoryId: request.sourceMemoryId,
|
|
5319
|
-
sourceNamespace,
|
|
5320
|
-
principal: request.principal,
|
|
5321
|
-
targets: request.targets,
|
|
5322
|
-
reason: request.reason,
|
|
5323
|
-
actor: request.principal ?? "admin-console",
|
|
5324
|
-
storage,
|
|
5325
|
-
scopeProfilePlan
|
|
5326
|
-
});
|
|
5327
|
-
} catch (err) {
|
|
5328
|
-
if (err instanceof AdminPromotionError) {
|
|
5329
|
-
throw new EngramAccessInputError(err.message);
|
|
5020
|
+
const principal = this.deps.resolveRequestPrincipal(
|
|
5021
|
+
sessionKey,
|
|
5022
|
+
authenticatedPrincipal
|
|
5023
|
+
);
|
|
5024
|
+
const base = defaultNamespaceForPrincipal(
|
|
5025
|
+
principal,
|
|
5026
|
+
this.deps.orchestrator.config
|
|
5027
|
+
);
|
|
5028
|
+
const overlaid = this.deps.orchestrator.applyCodingNamespaceOverlay(
|
|
5029
|
+
sessionKey,
|
|
5030
|
+
base
|
|
5031
|
+
);
|
|
5032
|
+
if (overlaid === base) return [primary];
|
|
5033
|
+
const selfReadableInRecall = recallNamespacesForPrincipal(
|
|
5034
|
+
principal,
|
|
5035
|
+
this.deps.orchestrator.config
|
|
5036
|
+
).includes(base);
|
|
5037
|
+
if (!selfReadableInRecall) return [primary];
|
|
5038
|
+
const overlay = resolveCodingNamespaceOverlay(
|
|
5039
|
+
this.deps.orchestrator.getCodingContextForSession(sessionKey),
|
|
5040
|
+
this.deps.orchestrator.config.codingMode,
|
|
5041
|
+
this.deps.orchestrator.config.defaultNamespace
|
|
5042
|
+
);
|
|
5043
|
+
const fallbackNamespaces = (overlay?.readFallbacks ?? []).map(
|
|
5044
|
+
(fallback) => combineNamespaces(base, fallback)
|
|
5045
|
+
);
|
|
5046
|
+
const out = [primary];
|
|
5047
|
+
const seen = /* @__PURE__ */ new Set([primary]);
|
|
5048
|
+
for (const ns of fallbackNamespaces) {
|
|
5049
|
+
const key = lcmSessionKeyForNamespace(
|
|
5050
|
+
ns,
|
|
5051
|
+
sessionKey,
|
|
5052
|
+
this.deps.orchestrator.config.defaultNamespace
|
|
5053
|
+
) ?? sessionKey;
|
|
5054
|
+
if (!seen.has(key)) {
|
|
5055
|
+
seen.add(key);
|
|
5056
|
+
out.push(key);
|
|
5330
5057
|
}
|
|
5331
|
-
throw err;
|
|
5332
5058
|
}
|
|
5059
|
+
return out;
|
|
5333
5060
|
}
|
|
5334
5061
|
};
|
|
5335
5062
|
|
|
5336
|
-
// src/access-
|
|
5337
|
-
import
|
|
5338
|
-
import * as
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
function qmdCollectionPathParts(resultPath) {
|
|
5343
|
-
if (!resultPath || path8.isAbsolute(resultPath)) return null;
|
|
5344
|
-
const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
5345
|
-
const slashIndex = normalized.indexOf("/");
|
|
5346
|
-
if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
|
|
5347
|
-
const collection = normalized.slice(0, slashIndex);
|
|
5348
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(collection)) return null;
|
|
5349
|
-
return {
|
|
5350
|
-
collection,
|
|
5351
|
-
relativePath: normalized.slice(slashIndex + 1)
|
|
5352
|
-
};
|
|
5353
|
-
}
|
|
5354
|
-
function qmdResultPathCandidates(storageDir, resultPath) {
|
|
5355
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
5356
|
-
const storageRoot = path8.resolve(storageDir);
|
|
5357
|
-
const addCandidate = (candidate) => {
|
|
5358
|
-
const resolved = path8.resolve(candidate);
|
|
5359
|
-
if (isPathInsideStorageRoot(storageRoot, resolved)) {
|
|
5360
|
-
candidates.add(resolved);
|
|
5361
|
-
}
|
|
5362
|
-
};
|
|
5363
|
-
const addRelativeCandidates = (relativePath) => {
|
|
5364
|
-
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
5365
|
-
if (!normalized) return;
|
|
5366
|
-
addCandidate(path8.join(storageRoot, normalized));
|
|
5367
|
-
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
5368
|
-
addCandidate(path8.join(storageRoot, "facts", normalized));
|
|
5369
|
-
}
|
|
5370
|
-
};
|
|
5371
|
-
if (path8.isAbsolute(resultPath)) {
|
|
5372
|
-
addCandidate(resultPath);
|
|
5373
|
-
} else {
|
|
5374
|
-
addRelativeCandidates(resultPath);
|
|
5375
|
-
}
|
|
5376
|
-
return [...candidates];
|
|
5377
|
-
}
|
|
5378
|
-
function storageDirOrNull(storage) {
|
|
5379
|
-
const dir = storage.dir;
|
|
5380
|
-
return typeof dir === "string" && dir.length > 0 ? dir : null;
|
|
5381
|
-
}
|
|
5382
|
-
var QmdResultResolver = class {
|
|
5383
|
-
getConfig;
|
|
5384
|
-
storageFor;
|
|
5385
|
-
storageDirNamespace;
|
|
5386
|
-
qmdCollectionNamespaceFromPrefix;
|
|
5387
|
-
namespaceFromPath;
|
|
5388
|
-
constructor(options) {
|
|
5389
|
-
this.getConfig = options.getConfig;
|
|
5390
|
-
this.storageFor = options.storageFor;
|
|
5391
|
-
this.storageDirNamespace = options.storageDirNamespace;
|
|
5392
|
-
this.qmdCollectionNamespaceFromPrefix = options.qmdCollectionNamespaceFromPrefix;
|
|
5393
|
-
this.namespaceFromPath = options.namespaceFromPath;
|
|
5063
|
+
// src/access-admin-ops-surface.ts
|
|
5064
|
+
import * as nodeFs from "fs/promises";
|
|
5065
|
+
import * as nodePath from "path";
|
|
5066
|
+
var AccessAdminOpsSurface = class {
|
|
5067
|
+
constructor(deps) {
|
|
5068
|
+
this.deps = deps;
|
|
5394
5069
|
}
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
const
|
|
5398
|
-
const
|
|
5399
|
-
const
|
|
5400
|
-
if (
|
|
5401
|
-
const
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
};
|
|
5410
|
-
const
|
|
5411
|
-
|
|
5412
|
-
addStorage(fallbackStorage);
|
|
5413
|
-
}
|
|
5414
|
-
if (recallNamespaces.length > 0) {
|
|
5415
|
-
for (const namespace of recallNamespaces) {
|
|
5416
|
-
try {
|
|
5417
|
-
addStorage(await this.storageFor(namespace));
|
|
5418
|
-
} catch (err) {
|
|
5419
|
-
log.debug("qmd cold result namespace storage lookup skipped", {
|
|
5420
|
-
path: resultPath,
|
|
5421
|
-
namespace,
|
|
5422
|
-
error: err.message
|
|
5423
|
-
});
|
|
5424
|
-
}
|
|
5425
|
-
}
|
|
5426
|
-
}
|
|
5427
|
-
for (const storage of storages) {
|
|
5428
|
-
const storageDir = storageDirOrNull(storage);
|
|
5429
|
-
if (!storageDir) {
|
|
5430
|
-
const memory = await storage.readMemoryByPath(resultPath);
|
|
5431
|
-
if (memory) return memory;
|
|
5432
|
-
continue;
|
|
5433
|
-
}
|
|
5070
|
+
deps;
|
|
5071
|
+
async reviewQueue(runId, namespace, principal) {
|
|
5072
|
+
const resolvedNamespace = this.deps.resolveReadableNamespace(namespace, principal);
|
|
5073
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
5074
|
+
const projected = await storage.getProjectedGovernanceRecord();
|
|
5075
|
+
if (projected && (!runId || projected.runId === runId.trim())) {
|
|
5076
|
+
const projectedAppliedActions = projected.appliedActionRows.map((row) => ({
|
|
5077
|
+
action: row.action,
|
|
5078
|
+
memoryId: row.memoryId,
|
|
5079
|
+
reasonCode: row.reasonCode,
|
|
5080
|
+
beforeStatus: row.beforeStatus,
|
|
5081
|
+
afterStatus: row.afterStatus,
|
|
5082
|
+
originalPath: row.originalPath,
|
|
5083
|
+
currentPath: row.currentPath
|
|
5084
|
+
}));
|
|
5085
|
+
const projectedProposedActions = await buildProjectedGovernanceProposedActions(storage, projected);
|
|
5086
|
+
const projectedArtifact = await (async () => {
|
|
5434
5087
|
try {
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
parts.relativePath
|
|
5439
|
-
)) {
|
|
5440
|
-
const memory = await storage.readMemoryByPath(candidate);
|
|
5441
|
-
if (memory) return memory;
|
|
5442
|
-
}
|
|
5443
|
-
} catch (err) {
|
|
5444
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5445
|
-
log.debug("qmd cold result path lookup failed open", {
|
|
5446
|
-
path: resultPath,
|
|
5447
|
-
collection: coldCollection,
|
|
5448
|
-
error: err.message
|
|
5449
|
-
});
|
|
5088
|
+
return await readMemoryGovernanceRunArtifact(storage.dir, projected.runId);
|
|
5089
|
+
} catch {
|
|
5090
|
+
return null;
|
|
5450
5091
|
}
|
|
5451
|
-
}
|
|
5452
|
-
|
|
5092
|
+
})();
|
|
5093
|
+
const metrics = projected.metrics;
|
|
5094
|
+
const fallbackTransitionReport = {
|
|
5095
|
+
proposed: groupActionsByStatus(projectedProposedActions),
|
|
5096
|
+
applied: groupActionsByStatus(projectedAppliedActions)
|
|
5097
|
+
};
|
|
5098
|
+
const transitionReport = projectedArtifact?.transitionReport ? {
|
|
5099
|
+
proposed: hasGroupedGovernanceActions(projectedArtifact.transitionReport.proposed) || projectedProposedActions.length === 0 ? projectedArtifact.transitionReport.proposed : fallbackTransitionReport.proposed,
|
|
5100
|
+
applied: hasGroupedGovernanceActions(projectedArtifact.transitionReport.applied) || projectedAppliedActions.length === 0 ? projectedArtifact.transitionReport.applied : fallbackTransitionReport.applied
|
|
5101
|
+
} : fallbackTransitionReport;
|
|
5102
|
+
const qualityScore = projectedArtifact?.qualityScore ?? metrics?.qualityScore ?? buildQualityScore(metrics?.reviewReasons ?? {
|
|
5103
|
+
exact_duplicate: 0,
|
|
5104
|
+
semantic_duplicate_candidate: 0,
|
|
5105
|
+
disputed_memory: 0,
|
|
5106
|
+
speculative_low_confidence: 0,
|
|
5107
|
+
archive_candidate: 0,
|
|
5108
|
+
explicit_capture_review: 0,
|
|
5109
|
+
malformed_import: 0
|
|
5110
|
+
});
|
|
5111
|
+
const effectiveMetrics = metrics ? { ...metrics, qualityScore: metrics.qualityScore ?? qualityScore } : metrics;
|
|
5112
|
+
return {
|
|
5113
|
+
found: true,
|
|
5114
|
+
namespace: resolvedNamespace,
|
|
5115
|
+
runId: projected.runId,
|
|
5116
|
+
summary: projected.summary,
|
|
5117
|
+
metrics: effectiveMetrics,
|
|
5118
|
+
qualityScore,
|
|
5119
|
+
reviewQueue: projected.reviewQueueRows.map((row) => ({
|
|
5120
|
+
entryId: row.entryId,
|
|
5121
|
+
memoryId: row.memoryId,
|
|
5122
|
+
path: row.path,
|
|
5123
|
+
reasonCode: row.reasonCode,
|
|
5124
|
+
severity: row.severity,
|
|
5125
|
+
suggestedAction: row.suggestedAction,
|
|
5126
|
+
suggestedStatus: row.suggestedStatus,
|
|
5127
|
+
relatedMemoryIds: row.relatedMemoryIds
|
|
5128
|
+
})),
|
|
5129
|
+
appliedActions: projectedAppliedActions,
|
|
5130
|
+
transitionReport,
|
|
5131
|
+
report: projected.report
|
|
5132
|
+
};
|
|
5453
5133
|
}
|
|
5454
|
-
const
|
|
5455
|
-
if (
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
}
|
|
5134
|
+
const resolvedRunId = runId?.trim() || (await listMemoryGovernanceRuns(storage.dir))[0];
|
|
5135
|
+
if (!resolvedRunId) return { found: false, namespace: resolvedNamespace };
|
|
5136
|
+
const artifact = await readMemoryGovernanceRunArtifact(storage.dir, resolvedRunId);
|
|
5137
|
+
return {
|
|
5138
|
+
found: true,
|
|
5139
|
+
namespace: resolvedNamespace,
|
|
5140
|
+
runId: resolvedRunId,
|
|
5141
|
+
summary: artifact.summary,
|
|
5142
|
+
metrics: artifact.metrics,
|
|
5143
|
+
qualityScore: artifact.qualityScore,
|
|
5144
|
+
reviewQueue: artifact.reviewQueue,
|
|
5145
|
+
appliedActions: artifact.appliedActions,
|
|
5146
|
+
transitionReport: artifact.transitionReport,
|
|
5147
|
+
report: artifact.report
|
|
5148
|
+
};
|
|
5149
|
+
}
|
|
5150
|
+
async governanceRun(request, principal) {
|
|
5151
|
+
const deepSleep = this.deps.orchestrator.config.dreamsPhases.deepSleep;
|
|
5152
|
+
if (deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
|
|
5153
|
+
throw new Error("memory governance is disabled by dreams.phases.deepSleep.enabled=false");
|
|
5475
5154
|
}
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5155
|
+
const resolvedNamespace = this.deps.writableNamespaceFor(
|
|
5156
|
+
request.namespace,
|
|
5157
|
+
void 0,
|
|
5158
|
+
request.authenticatedPrincipal ?? principal
|
|
5159
|
+
);
|
|
5160
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
5161
|
+
const mode = request.mode === "apply" ? "apply" : "shadow";
|
|
5162
|
+
const boundedBatchSize = typeof request.batchSize === "number" && Number.isFinite(request.batchSize) ? Math.max(1, Math.floor(request.batchSize)) : void 0;
|
|
5163
|
+
const result = await runMemoryGovernance({
|
|
5164
|
+
memoryDir: storage.dir,
|
|
5165
|
+
mode,
|
|
5166
|
+
recentDays: typeof request.recentDays === "number" && Number.isFinite(request.recentDays) ? Math.max(1, Math.floor(request.recentDays)) : void 0,
|
|
5167
|
+
maxMemories: typeof request.maxMemories === "number" && Number.isFinite(request.maxMemories) ? Math.max(1, Math.floor(request.maxMemories)) : void 0,
|
|
5168
|
+
batchSize: boundedBatchSize
|
|
5169
|
+
});
|
|
5170
|
+
if (mode === "apply") {
|
|
5171
|
+
try {
|
|
5172
|
+
await this.deps.orchestrator.processEntitySynthesisQueue(
|
|
5173
|
+
resolvedNamespace,
|
|
5174
|
+
Math.min(boundedBatchSize ?? 5, 5)
|
|
5175
|
+
);
|
|
5176
|
+
} catch (error) {
|
|
5177
|
+
log.debug(`governanceRun: entity synthesis refresh failed after governance apply: ${error}`);
|
|
5492
5178
|
}
|
|
5493
|
-
return null;
|
|
5494
5179
|
}
|
|
5495
|
-
|
|
5496
|
-
|
|
5180
|
+
return {
|
|
5181
|
+
namespace: resolvedNamespace,
|
|
5182
|
+
runId: result.runId,
|
|
5183
|
+
traceId: result.traceId,
|
|
5184
|
+
mode: result.mode,
|
|
5185
|
+
reviewQueueCount: result.reviewQueue.length,
|
|
5186
|
+
proposedActionCount: result.proposedActions.length,
|
|
5187
|
+
appliedActionCount: result.appliedActions.length,
|
|
5188
|
+
summaryPath: result.summaryPath,
|
|
5189
|
+
reportPath: result.reportPath
|
|
5190
|
+
};
|
|
5191
|
+
}
|
|
5192
|
+
async conversationIndexUpdate(request = {}) {
|
|
5193
|
+
if (!resolveIndexingCapabilities(this.deps.orchestrator.config).conversationIndex) {
|
|
5194
|
+
return {
|
|
5195
|
+
enabled: false,
|
|
5196
|
+
sessions: 0,
|
|
5197
|
+
chunks: 0,
|
|
5198
|
+
skipped: 0,
|
|
5199
|
+
skippedSessionKeys: [],
|
|
5200
|
+
embeddedRuns: 0,
|
|
5201
|
+
reason: "disabled"
|
|
5202
|
+
};
|
|
5497
5203
|
}
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5204
|
+
const hours = typeof request.hours === "number" && Number.isFinite(request.hours) ? Math.max(1, Math.floor(request.hours)) : 24;
|
|
5205
|
+
let sessionKey;
|
|
5206
|
+
if (request.sessionKey !== void 0) {
|
|
5207
|
+
if (typeof request.sessionKey !== "string" || request.sessionKey.trim().length === 0) {
|
|
5208
|
+
throw new EngramAccessInputError("sessionKey must be a non-empty string when provided");
|
|
5209
|
+
}
|
|
5210
|
+
sessionKey = request.sessionKey.trim();
|
|
5504
5211
|
}
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
fallbackStorage,
|
|
5511
|
-
recallNamespaces
|
|
5512
|
-
);
|
|
5513
|
-
if (!memory) return null;
|
|
5514
|
-
let ownerNamespace = null;
|
|
5515
|
-
if (path8.isAbsolute(memory.path)) {
|
|
5516
|
-
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
5517
|
-
memory.path,
|
|
5518
|
-
fallbackStorage,
|
|
5519
|
-
recallNamespaces
|
|
5212
|
+
if (sessionKey) {
|
|
5213
|
+
const result = await this.deps.orchestrator.updateConversationIndex(
|
|
5214
|
+
sessionKey,
|
|
5215
|
+
hours,
|
|
5216
|
+
{ embed: request.embed }
|
|
5520
5217
|
);
|
|
5521
|
-
|
|
5522
|
-
|
|
5218
|
+
return {
|
|
5219
|
+
enabled: true,
|
|
5220
|
+
sessionKey,
|
|
5221
|
+
sessions: 1,
|
|
5222
|
+
chunks: result.chunks,
|
|
5223
|
+
skipped: result.skipped ? 1 : 0,
|
|
5224
|
+
skippedSessionKeys: result.skipped ? [sessionKey] : [],
|
|
5225
|
+
embeddedRuns: result.embedded ? 1 : 0,
|
|
5226
|
+
reason: result.reason,
|
|
5227
|
+
retryAfterMs: result.retryAfterMs
|
|
5228
|
+
};
|
|
5523
5229
|
}
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5230
|
+
const sessionKeys = await this.deps.orchestrator.transcript.listSessionKeys();
|
|
5231
|
+
let chunks = 0;
|
|
5232
|
+
let skipped = 0;
|
|
5233
|
+
const skippedSessionKeys = [];
|
|
5234
|
+
let embeddedRuns = 0;
|
|
5235
|
+
for (const sessionKey2 of sessionKeys) {
|
|
5236
|
+
const result = await this.deps.orchestrator.updateConversationIndex(
|
|
5237
|
+
sessionKey2,
|
|
5238
|
+
hours,
|
|
5239
|
+
{ embed: request.embed }
|
|
5240
|
+
);
|
|
5241
|
+
chunks += result.chunks;
|
|
5242
|
+
if (result.skipped) {
|
|
5243
|
+
skipped += 1;
|
|
5244
|
+
skippedSessionKeys.push(sessionKey2);
|
|
5245
|
+
}
|
|
5246
|
+
if (result.embedded) {
|
|
5247
|
+
embeddedRuns += 1;
|
|
5248
|
+
}
|
|
5527
5249
|
}
|
|
5528
5250
|
return {
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
}
|
|
5251
|
+
enabled: true,
|
|
5252
|
+
sessions: sessionKeys.length,
|
|
5253
|
+
chunks,
|
|
5254
|
+
skipped,
|
|
5255
|
+
skippedSessionKeys,
|
|
5256
|
+
embeddedRuns
|
|
5536
5257
|
};
|
|
5537
5258
|
}
|
|
5538
|
-
async
|
|
5539
|
-
const
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
const maybeAddStorage = (storage, namespace) => {
|
|
5547
|
-
const storageDir = storageDirOrNull(storage);
|
|
5548
|
-
if (!storageDir) return;
|
|
5549
|
-
const candidateRoot = path8.resolve(storageDir);
|
|
5550
|
-
if (seenDirs.has(candidateRoot)) return;
|
|
5551
|
-
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
|
|
5552
|
-
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
5553
|
-
return;
|
|
5554
|
-
}
|
|
5555
|
-
seenDirs.add(candidateRoot);
|
|
5556
|
-
matches.push({ storage, dir: candidateRoot, namespace });
|
|
5557
|
-
};
|
|
5558
|
-
const fallbackNamespace = fallbackStorageDir !== null ? this.storageDirNamespace(fallbackStorageDir) : config.defaultNamespace;
|
|
5559
|
-
maybeAddStorage(fallbackStorage, fallbackNamespace);
|
|
5560
|
-
const candidateNamespaces = /* @__PURE__ */ new Set();
|
|
5561
|
-
candidateNamespaces.add(config.defaultNamespace);
|
|
5562
|
-
candidateNamespaces.add(config.sharedNamespace);
|
|
5563
|
-
for (const ns of recallNamespaces) {
|
|
5564
|
-
candidateNamespaces.add(ns);
|
|
5259
|
+
async profilingReport(request = {}) {
|
|
5260
|
+
const profiler = this.deps.orchestrator.profiler;
|
|
5261
|
+
if (!profiler.isEnabled) {
|
|
5262
|
+
return {
|
|
5263
|
+
enabled: false,
|
|
5264
|
+
reason: "disabled",
|
|
5265
|
+
message: "Profiling is disabled. Set profilingEnabled: true in your plugin config to enable."
|
|
5266
|
+
};
|
|
5565
5267
|
}
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
if (namespaceSegment) {
|
|
5570
|
-
candidateNamespaces.add(
|
|
5571
|
-
namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
|
|
5572
|
-
);
|
|
5573
|
-
}
|
|
5268
|
+
const format = request.format ?? "ascii";
|
|
5269
|
+
if (format !== "ascii" && format !== "json") {
|
|
5270
|
+
throw new EngramAccessInputError("format must be one of: ascii, json");
|
|
5574
5271
|
}
|
|
5575
|
-
|
|
5576
|
-
|
|
5272
|
+
const limit = request.limit ?? 5;
|
|
5273
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 20) {
|
|
5274
|
+
throw new EngramAccessInputError("limit must be an integer between 1 and 20");
|
|
5577
5275
|
}
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5276
|
+
const traces = profiler.getRecentTraces(limit);
|
|
5277
|
+
const stats = profiler.getStats();
|
|
5278
|
+
const bottleneck = profiler.identifyBottleneck();
|
|
5279
|
+
if (format === "json") {
|
|
5280
|
+
return {
|
|
5281
|
+
enabled: true,
|
|
5282
|
+
format,
|
|
5283
|
+
traces,
|
|
5284
|
+
stats,
|
|
5285
|
+
bottleneck
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
const lines = [];
|
|
5289
|
+
lines.push("Engram Profiling Report");
|
|
5290
|
+
lines.push("=".repeat(60));
|
|
5291
|
+
lines.push("");
|
|
5292
|
+
const allBuckets = [
|
|
5293
|
+
["byKind", stats.byKind],
|
|
5294
|
+
["bySpan", stats.bySpan]
|
|
5295
|
+
];
|
|
5296
|
+
const hasStats = allBuckets.some(([, entries]) => Object.keys(entries).length > 0);
|
|
5297
|
+
if (hasStats) {
|
|
5298
|
+
lines.push("Aggregate Stats (all retained traces):");
|
|
5299
|
+
for (const [bucket, entries] of allBuckets) {
|
|
5300
|
+
for (const [key, summary] of Object.entries(entries)) {
|
|
5301
|
+
lines.push(
|
|
5302
|
+
` ${bucket}/${key}: avg=${summary.avgMs}ms p50=${summary.p50Ms}ms p95=${summary.p95Ms}ms max=${summary.maxMs}ms (n=${summary.count})`
|
|
5303
|
+
);
|
|
5304
|
+
}
|
|
5584
5305
|
}
|
|
5306
|
+
lines.push("");
|
|
5585
5307
|
}
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
}
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
deps;
|
|
5597
|
-
async buildRecallResponseFromXraySnapshot(options) {
|
|
5598
|
-
const memoryIds = options.snapshot.results.map((result) => result.memoryId);
|
|
5599
|
-
const resultPaths = options.snapshot.results.map((result) => result.path);
|
|
5600
|
-
const namespace = options.snapshot.namespace ? this.deps.resolveNamespace(options.snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
|
|
5601
|
-
const sourcesUsed = Array.from(
|
|
5602
|
-
new Set(options.snapshot.results.map((result) => result.servedBy))
|
|
5603
|
-
);
|
|
5604
|
-
const snapshotForSerialization = {
|
|
5605
|
-
sessionKey: options.sessionKey ?? "",
|
|
5606
|
-
recordedAt: new Date(options.snapshot.capturedAt).toISOString(),
|
|
5607
|
-
queryHash: createHash4("sha256").update(options.query).digest("hex"),
|
|
5608
|
-
queryLen: options.query.length,
|
|
5609
|
-
memoryIds,
|
|
5610
|
-
namespace,
|
|
5611
|
-
recallNamespaces: [namespace],
|
|
5612
|
-
traceId: options.snapshot.traceId,
|
|
5613
|
-
plannerMode: options.normalizedMode,
|
|
5614
|
-
requestedMode: options.requestedMode && options.requestedMode !== "auto" ? options.requestedMode : void 0,
|
|
5615
|
-
sourcesUsed,
|
|
5616
|
-
budgetsApplied: {
|
|
5617
|
-
appliedTopK: memoryIds.length,
|
|
5618
|
-
recallBudgetChars: options.snapshot.budget.chars,
|
|
5619
|
-
maxMemoryTokens: this.deps.orchestrator.config.maxMemoryTokens,
|
|
5620
|
-
finalContextChars: options.snapshot.budget.used
|
|
5621
|
-
},
|
|
5622
|
-
latencyMs: Date.now() - options.startedAt,
|
|
5623
|
-
resultPaths
|
|
5624
|
-
};
|
|
5625
|
-
const results = await this.deps.serializeRecallResults(
|
|
5626
|
-
snapshotForSerialization,
|
|
5627
|
-
options.disclosure,
|
|
5628
|
-
{
|
|
5629
|
-
query: options.query,
|
|
5630
|
-
...options.sessionKey ? { sessionKey: options.sessionKey } : {},
|
|
5631
|
-
...options.rawExcerptNamespace ? { rawExcerptNamespace: options.rawExcerptNamespace } : {},
|
|
5632
|
-
...options.rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: options.rawExcerptSessionIds } : {},
|
|
5633
|
-
...options.rawExcerptsSuppressed ? { rawExcerptsSuppressed: options.rawExcerptsSuppressed } : {}
|
|
5308
|
+
if (bottleneck) {
|
|
5309
|
+
lines.push(`Bottleneck: ${bottleneck}`);
|
|
5310
|
+
lines.push("");
|
|
5311
|
+
}
|
|
5312
|
+
if (traces.length === 0) {
|
|
5313
|
+
lines.push("No traces recorded yet. Trigger a recall or extraction to see timing data.");
|
|
5314
|
+
} else {
|
|
5315
|
+
for (const trace of traces) {
|
|
5316
|
+
lines.push(formatProfileTraceAscii(trace));
|
|
5317
|
+
lines.push("");
|
|
5634
5318
|
}
|
|
5635
|
-
|
|
5636
|
-
const context = results.map((result) => {
|
|
5637
|
-
const content = typeof result.content === "string" && result.content.length > 0 ? result.content : "";
|
|
5638
|
-
return content || result.preview;
|
|
5639
|
-
}).filter((text) => text.length > 0).join("\n\n");
|
|
5319
|
+
}
|
|
5640
5320
|
return {
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
context,
|
|
5645
|
-
count: memoryIds.length,
|
|
5646
|
-
memoryIds,
|
|
5647
|
-
results,
|
|
5648
|
-
recordedAt: snapshotForSerialization.recordedAt,
|
|
5649
|
-
traceId: options.snapshot.traceId,
|
|
5650
|
-
plannerMode: options.normalizedMode,
|
|
5651
|
-
fallbackUsed: sourcesUsed.some((source) => source !== "hybrid"),
|
|
5652
|
-
sourcesUsed,
|
|
5653
|
-
disclosure: options.disclosure,
|
|
5654
|
-
budgetsApplied: snapshotForSerialization.budgetsApplied,
|
|
5655
|
-
latencyMs: snapshotForSerialization.latencyMs
|
|
5321
|
+
enabled: true,
|
|
5322
|
+
format,
|
|
5323
|
+
report: lines.join("\n")
|
|
5656
5324
|
};
|
|
5657
5325
|
}
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
].filter((ns) => typeof ns === "string" && ns.length > 0)
|
|
5672
|
-
)
|
|
5326
|
+
/**
|
|
5327
|
+
* Read-only graph snapshot for the admin pane (issue #691 PR 2/5).
|
|
5328
|
+
*
|
|
5329
|
+
* Reads adjacency from the JSONL edge store written by `GraphIndex` and
|
|
5330
|
+
* resolves node metadata via the namespaced storage manager. Namespace
|
|
5331
|
+
* resolution mirrors the read-side path used by `recall` /
|
|
5332
|
+
* `procedureStats`, so multi-principal deployments can't leak edges from
|
|
5333
|
+
* a peer namespace (CLAUDE.md rule 42).
|
|
5334
|
+
*/
|
|
5335
|
+
async graphSnapshot(request, authenticatedPrincipal) {
|
|
5336
|
+
const namespace = this.deps.resolveReadableNamespace(
|
|
5337
|
+
request.namespace,
|
|
5338
|
+
authenticatedPrincipal
|
|
5673
5339
|
);
|
|
5674
|
-
const
|
|
5675
|
-
const
|
|
5676
|
-
const
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5340
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
5341
|
+
const cfg = this.deps.orchestrator.config;
|
|
5342
|
+
const graphCaps = resolveGraphConstructionCapabilities(cfg);
|
|
5343
|
+
let namespaceRootReal;
|
|
5344
|
+
try {
|
|
5345
|
+
namespaceRootReal = await nodeFs.realpath(storage.dir);
|
|
5346
|
+
} catch {
|
|
5347
|
+
namespaceRootReal = nodePath.resolve(storage.dir);
|
|
5348
|
+
}
|
|
5349
|
+
const namespaceRootWithSep = namespaceRootReal.endsWith(nodePath.sep) ? namespaceRootReal : namespaceRootReal + nodePath.sep;
|
|
5350
|
+
const loadNode = async (relPath) => {
|
|
5351
|
+
if (nodePath.isAbsolute(relPath)) {
|
|
5352
|
+
log.warn(
|
|
5353
|
+
`graphSnapshot: rejected absolute edge endpoint (len=${relPath.length}) outside namespace root`
|
|
5354
|
+
);
|
|
5355
|
+
return null;
|
|
5686
5356
|
}
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
if (parts && parts.collection === coldCollection) {
|
|
5693
|
-
const storages = [];
|
|
5694
|
-
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
5695
|
-
const addStorage = (candidateStorage) => {
|
|
5696
|
-
const candidateDir = nodePath2.resolve(candidateStorage.dir);
|
|
5697
|
-
if (seenStorageDirs.has(candidateDir)) return;
|
|
5698
|
-
seenStorageDirs.add(candidateDir);
|
|
5699
|
-
storages.push({ storage: candidateStorage, dir: candidateDir });
|
|
5700
|
-
};
|
|
5701
|
-
addStorage(storage);
|
|
5702
|
-
for (const recallNamespace of recallNamespaces) {
|
|
5703
|
-
try {
|
|
5704
|
-
addStorage(await this.deps.orchestrator.getStorage(recallNamespace));
|
|
5705
|
-
} catch {
|
|
5706
|
-
continue;
|
|
5707
|
-
}
|
|
5708
|
-
}
|
|
5709
|
-
for (const candidateStorage of storages) {
|
|
5710
|
-
try {
|
|
5711
|
-
const coldRoot = nodePath2.join(candidateStorage.dir, "cold");
|
|
5712
|
-
for (const candidatePath of qmdResultPathCandidates(
|
|
5713
|
-
coldRoot,
|
|
5714
|
-
parts.relativePath
|
|
5715
|
-
)) {
|
|
5716
|
-
const memory = await candidateStorage.storage.readMemoryByPath(candidatePath);
|
|
5717
|
-
if (memory) return { memory, baseDir: candidateStorage.dir };
|
|
5718
|
-
}
|
|
5719
|
-
} catch (err) {
|
|
5720
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5721
|
-
continue;
|
|
5722
|
-
}
|
|
5723
|
-
}
|
|
5357
|
+
const candidate = nodePath.resolve(namespaceRootReal, relPath);
|
|
5358
|
+
if (candidate !== namespaceRootReal && !candidate.startsWith(namespaceRootWithSep)) {
|
|
5359
|
+
log.warn(
|
|
5360
|
+
`graphSnapshot: rejected traversing edge endpoint (len=${relPath.length}) outside namespace root`
|
|
5361
|
+
);
|
|
5724
5362
|
return null;
|
|
5725
5363
|
}
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
collectionStorage.dir,
|
|
5732
|
-
parts.relativePath
|
|
5733
|
-
)) {
|
|
5734
|
-
const memory = await collectionStorage.readMemoryByPath(candidate);
|
|
5735
|
-
if (memory) return { memory, baseDir: collectionStorage.dir };
|
|
5736
|
-
}
|
|
5737
|
-
return null;
|
|
5738
|
-
} catch (err) {
|
|
5739
|
-
if (err instanceof SecureStoreLockedError) throw err;
|
|
5740
|
-
return null;
|
|
5741
|
-
}
|
|
5364
|
+
let canonical;
|
|
5365
|
+
try {
|
|
5366
|
+
canonical = await nodeFs.realpath(candidate);
|
|
5367
|
+
} catch {
|
|
5368
|
+
canonical = candidate;
|
|
5742
5369
|
}
|
|
5743
|
-
if (
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
namespace,
|
|
5747
|
-
recallNamespaces
|
|
5370
|
+
if (canonical !== namespaceRootReal && !canonical.startsWith(namespaceRootWithSep)) {
|
|
5371
|
+
log.warn(
|
|
5372
|
+
`graphSnapshot: rejected symlinked edge endpoint (len=${relPath.length}) that resolved outside namespace root`
|
|
5748
5373
|
);
|
|
5749
|
-
if (!ownerStorage) return null;
|
|
5750
|
-
for (const candidate of qmdResultPathCandidates(
|
|
5751
|
-
ownerStorage.dir,
|
|
5752
|
-
memoryPath
|
|
5753
|
-
)) {
|
|
5754
|
-
const memory = await ownerStorage.storage.readMemoryByPath(candidate);
|
|
5755
|
-
if (memory) return { memory, baseDir: ownerStorage.dir };
|
|
5756
|
-
}
|
|
5757
5374
|
return null;
|
|
5758
5375
|
}
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5376
|
+
const memory = await storage.readMemoryByPath(canonical);
|
|
5377
|
+
if (!memory) return null;
|
|
5378
|
+
const fm = memory.frontmatter;
|
|
5379
|
+
return {
|
|
5380
|
+
category: fm.category ?? "unknown",
|
|
5381
|
+
label: fm.id ?? nodePath.basename(canonical, nodePath.extname(canonical)),
|
|
5382
|
+
updated: fm.updated
|
|
5383
|
+
};
|
|
5764
5384
|
};
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
}
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
for (const memoryPath of snapshot.resultPaths ?? []) {
|
|
5781
|
-
if (!memoryPath || seen.has(memoryPath)) continue;
|
|
5782
|
-
const resolved = await readResultPath(memoryPath);
|
|
5783
|
-
if (!resolved) continue;
|
|
5784
|
-
const { memory, baseDir } = resolved;
|
|
5785
|
-
seen.add(memoryPath);
|
|
5786
|
-
results.push(
|
|
5787
|
-
this.deps.serializeMemorySummary(
|
|
5788
|
-
memory,
|
|
5789
|
-
baseDir,
|
|
5790
|
-
disclosure,
|
|
5791
|
-
// Attach the (possibly empty) raw excerpts to the first raw
|
|
5792
|
-
// result; subsequent results do not duplicate the array.
|
|
5793
|
-
results.length === 0 ? rawExcerpts : void 0
|
|
5794
|
-
)
|
|
5795
|
-
);
|
|
5796
|
-
}
|
|
5797
|
-
if (results.length > 0) return results;
|
|
5798
|
-
for (const memoryId of snapshot.memoryIds) {
|
|
5799
|
-
const memory = await storage.getMemoryById(memoryId);
|
|
5800
|
-
if (!memory || seen.has(memory.path)) continue;
|
|
5801
|
-
seen.add(memory.path);
|
|
5802
|
-
results.push(
|
|
5803
|
-
this.deps.serializeMemorySummary(
|
|
5804
|
-
memory,
|
|
5805
|
-
storageDir,
|
|
5806
|
-
disclosure,
|
|
5807
|
-
results.length === 0 ? rawExcerpts : void 0
|
|
5808
|
-
)
|
|
5809
|
-
);
|
|
5810
|
-
}
|
|
5811
|
-
return results;
|
|
5385
|
+
return buildGraphSnapshot({
|
|
5386
|
+
memoryDir: namespaceRootReal,
|
|
5387
|
+
graphConfig: {
|
|
5388
|
+
entityGraph: graphCaps.entityGraph,
|
|
5389
|
+
timeGraph: graphCaps.timeGraph,
|
|
5390
|
+
causalGraph: graphCaps.causalGraph
|
|
5391
|
+
},
|
|
5392
|
+
request: {
|
|
5393
|
+
limit: request.limit,
|
|
5394
|
+
since: request.since,
|
|
5395
|
+
focusNodeId: request.focusNodeId,
|
|
5396
|
+
categories: request.categories
|
|
5397
|
+
},
|
|
5398
|
+
loadNode
|
|
5399
|
+
});
|
|
5812
5400
|
}
|
|
5813
5401
|
/**
|
|
5814
|
-
*
|
|
5815
|
-
* "raw"` recalls (issue #677 PR 2/4). Returns `null` for non-raw recall
|
|
5816
|
-
* depths, an empty array when LCM is disabled / not initialized / has no
|
|
5817
|
-
* matches, and an array of LCM-side excerpts otherwise. Errors are
|
|
5818
|
-
* swallowed and treated as "no excerpts" so a failing LCM never breaks
|
|
5819
|
-
* the recall response.
|
|
5402
|
+
* List capsule archives in the namespace-scoped capsule store.
|
|
5820
5403
|
*
|
|
5821
|
-
*
|
|
5822
|
-
*
|
|
5823
|
-
*
|
|
5824
|
-
* non-default namespaces miss their own excerpts.
|
|
5404
|
+
* MCP uses this access-layer method instead of reading arbitrary paths so
|
|
5405
|
+
* capsule discovery remains bound to the same namespace ACLs as export and
|
|
5406
|
+
* import.
|
|
5825
5407
|
*/
|
|
5826
|
-
async
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
if (!lcm || !lcm.enabled) return [];
|
|
5408
|
+
async capsuleList(options) {
|
|
5409
|
+
const resolvedNamespace = this.deps.resolveReadableNamespace(options?.namespace, options?.principal);
|
|
5410
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
5411
|
+
const capsulesDir = defaultCapsulesDir(storage.dir);
|
|
5412
|
+
let dirEntries;
|
|
5832
5413
|
try {
|
|
5833
|
-
const
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
const seenRows = /* @__PURE__ */ new Set();
|
|
5837
|
-
const excerpts = [];
|
|
5838
|
-
const settledRows = await Promise.allSettled(
|
|
5839
|
-
lcmSessionIds.map(
|
|
5840
|
-
async (lcmSessionKey) => lcm.searchContextFull(context.query, limit, lcmSessionKey)
|
|
5841
|
-
)
|
|
5842
|
-
);
|
|
5843
|
-
for (const result of settledRows) {
|
|
5844
|
-
if (excerpts.length >= limit) break;
|
|
5845
|
-
if (result.status !== "fulfilled") continue;
|
|
5846
|
-
for (const r of result.value) {
|
|
5847
|
-
const dedupeKey = `${r.session_id} ${r.turn_index}`;
|
|
5848
|
-
if (seenRows.has(dedupeKey)) continue;
|
|
5849
|
-
seenRows.add(dedupeKey);
|
|
5850
|
-
excerpts.push({
|
|
5851
|
-
turnIndex: r.turn_index,
|
|
5852
|
-
role: r.role,
|
|
5853
|
-
content: r.content,
|
|
5854
|
-
sessionId: r.session_id
|
|
5855
|
-
});
|
|
5856
|
-
if (excerpts.length >= limit) break;
|
|
5857
|
-
}
|
|
5414
|
+
const capsulesDirStat = await nodeFs.lstat(capsulesDir);
|
|
5415
|
+
if (capsulesDirStat.isSymbolicLink()) {
|
|
5416
|
+
throw new EngramAccessInputError("capsule list failed: capsule store directory must not be a symlink");
|
|
5858
5417
|
}
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
return [];
|
|
5862
|
-
}
|
|
5863
|
-
}
|
|
5864
|
-
async executeRecall(request) {
|
|
5865
|
-
const query = request.query;
|
|
5866
|
-
const callerProvidedDisclosure = request.disclosure !== void 0 && request.disclosure !== null;
|
|
5867
|
-
const requestedDisclosure = (() => {
|
|
5868
|
-
if (!callerProvidedDisclosure) {
|
|
5869
|
-
return DEFAULT_RECALL_DISCLOSURE;
|
|
5418
|
+
if (!capsulesDirStat.isDirectory()) {
|
|
5419
|
+
throw new EngramAccessInputError("capsule list failed: capsule store path must be a directory");
|
|
5870
5420
|
}
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5421
|
+
dirEntries = await nodeFs.readdir(capsulesDir, { withFileTypes: true });
|
|
5422
|
+
} catch (err) {
|
|
5423
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
5424
|
+
if (code === "ENOENT") {
|
|
5425
|
+
return { namespace: resolvedNamespace, capsulesDir, capsules: [] };
|
|
5875
5426
|
}
|
|
5876
|
-
|
|
5877
|
-
})();
|
|
5878
|
-
if (request.codingContext !== void 0 && request.sessionKey) {
|
|
5879
|
-
this.deps.setCodingContext({
|
|
5880
|
-
sessionKey: request.sessionKey,
|
|
5881
|
-
codingContext: request.codingContext
|
|
5882
|
-
});
|
|
5427
|
+
throw err;
|
|
5883
5428
|
}
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5429
|
+
const archiveNames = dirEntries.filter(
|
|
5430
|
+
(entry) => entry.isFile() && (entry.name.endsWith(".capsule.json.gz") || entry.name.endsWith(".capsule.json.gz.enc"))
|
|
5431
|
+
).map((entry) => entry.name).sort();
|
|
5432
|
+
const capsules = [];
|
|
5433
|
+
for (const archiveName of archiveNames) {
|
|
5434
|
+
const archivePath = nodePath.join(capsulesDir, archiveName);
|
|
5435
|
+
const id = archiveName.replace(/\.capsule\.json\.gz\.enc$/, "").replace(/\.capsule\.json\.gz$/, "");
|
|
5436
|
+
const manifestPath = nodePath.join(capsulesDir, `${id}.manifest.json`);
|
|
5437
|
+
let createdAt = null;
|
|
5438
|
+
let pluginVersion = null;
|
|
5439
|
+
let fileCount = null;
|
|
5440
|
+
let description = null;
|
|
5441
|
+
let manifestPathOrNull = manifestPath;
|
|
5442
|
+
try {
|
|
5443
|
+
const manifestStat = await nodeFs.lstat(manifestPath);
|
|
5444
|
+
if (manifestStat.isSymbolicLink() || !manifestStat.isFile()) {
|
|
5445
|
+
capsules.push({
|
|
5446
|
+
id,
|
|
5447
|
+
archivePath,
|
|
5448
|
+
manifestPath: manifestPathOrNull,
|
|
5449
|
+
createdAt,
|
|
5450
|
+
pluginVersion,
|
|
5451
|
+
fileCount,
|
|
5452
|
+
description
|
|
5453
|
+
});
|
|
5454
|
+
continue;
|
|
5455
|
+
}
|
|
5456
|
+
const raw = await nodeFs.readFile(manifestPath, "utf-8");
|
|
5457
|
+
const sidecar = JSON.parse(raw);
|
|
5458
|
+
createdAt = typeof sidecar.createdAt === "string" ? sidecar.createdAt : null;
|
|
5459
|
+
pluginVersion = typeof sidecar.pluginVersion === "string" ? sidecar.pluginVersion : null;
|
|
5460
|
+
fileCount = Array.isArray(sidecar.files) ? sidecar.files.length : null;
|
|
5461
|
+
const capsule = sidecar.capsule;
|
|
5462
|
+
description = capsule && typeof capsule.description === "string" ? capsule.description : null;
|
|
5463
|
+
} catch (err) {
|
|
5464
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
5465
|
+
if (code === "ENOENT") {
|
|
5466
|
+
manifestPathOrNull = null;
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
capsules.push({
|
|
5470
|
+
id,
|
|
5471
|
+
archivePath,
|
|
5472
|
+
manifestPath: manifestPathOrNull,
|
|
5473
|
+
createdAt,
|
|
5474
|
+
pluginVersion,
|
|
5475
|
+
fileCount,
|
|
5476
|
+
description
|
|
5888
5477
|
});
|
|
5889
5478
|
}
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5479
|
+
return { namespace: resolvedNamespace, capsulesDir, capsules };
|
|
5480
|
+
}
|
|
5481
|
+
/**
|
|
5482
|
+
* Manually invoke a single Dreams phase pass (PR 4/4).
|
|
5483
|
+
*
|
|
5484
|
+
* Deep-sleep delegates to memory governance (shadow → dry-run, apply → live).
|
|
5485
|
+
* Light-sleep and REM scan the observation ledger and memory corpus
|
|
5486
|
+
* respectively, returning the same telemetry shape as a scheduled run.
|
|
5487
|
+
*/
|
|
5488
|
+
async dreamsRun(options) {
|
|
5489
|
+
const { runDreamsPhase } = await import("./dreams-ledger-6UODQOX6.js");
|
|
5490
|
+
const validPhases = ["lightSleep", "rem", "deepSleep"];
|
|
5491
|
+
if (!validPhases.includes(options.phase)) {
|
|
5900
5492
|
throw new EngramAccessInputError(
|
|
5901
|
-
|
|
5493
|
+
`Invalid phase: ${String(options.phase)}. Must be one of: ${validPhases.join(", ")}`
|
|
5902
5494
|
);
|
|
5903
5495
|
}
|
|
5904
|
-
const
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
const
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
5938
|
-
hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
5939
|
-
windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
5940
|
-
}
|
|
5496
|
+
const deepSleep = this.deps.orchestrator.config.dreamsPhases.deepSleep;
|
|
5497
|
+
if (options.phase === "deepSleep" && deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
|
|
5498
|
+
throw new EngramAccessInputError(
|
|
5499
|
+
"memory governance is disabled by dreams.phases.deepSleep.enabled=false"
|
|
5500
|
+
);
|
|
5501
|
+
}
|
|
5502
|
+
const dryRun = options.dryRun === true;
|
|
5503
|
+
const resolvedNamespace = this.deps.writableNamespaceFor(
|
|
5504
|
+
options.namespace,
|
|
5505
|
+
void 0,
|
|
5506
|
+
options.authenticatedPrincipal
|
|
5507
|
+
);
|
|
5508
|
+
const storage = await this.deps.orchestrator.getStorage(resolvedNamespace);
|
|
5509
|
+
const memoryDir = storage.dir;
|
|
5510
|
+
const phaseRunner = dryRun || options.phase === "deepSleep" ? void 0 : async (_opts) => {
|
|
5511
|
+
if (_opts.phase === "lightSleep") {
|
|
5512
|
+
const result3 = await this.deps.orchestrator.runLifecyclePolicyNow(storage);
|
|
5513
|
+
return {
|
|
5514
|
+
itemsProcessed: result3.memoriesAssessed,
|
|
5515
|
+
notes: `scored ${result3.memoriesAssessed} memories`
|
|
5516
|
+
};
|
|
5517
|
+
}
|
|
5518
|
+
const result2 = await this.deps.orchestrator.runSemanticConsolidationNow({
|
|
5519
|
+
dryRun: false,
|
|
5520
|
+
storage
|
|
5521
|
+
});
|
|
5522
|
+
const itemsProcessed = result2.clusters.reduce(
|
|
5523
|
+
(sum, cluster) => sum + cluster.memories.length,
|
|
5524
|
+
0
|
|
5525
|
+
);
|
|
5526
|
+
return {
|
|
5527
|
+
itemsProcessed,
|
|
5528
|
+
notes: `REM consolidation found ${result2.clustersFound} clusters`
|
|
5941
5529
|
};
|
|
5530
|
+
};
|
|
5531
|
+
const governanceRunner = options.phase === "deepSleep" ? async (_opts) => {
|
|
5532
|
+
return this.deps.orchestrator.runDeepSleepGovernanceNow({
|
|
5533
|
+
storage,
|
|
5534
|
+
dryRun: _opts.dryRun
|
|
5535
|
+
});
|
|
5536
|
+
} : void 0;
|
|
5537
|
+
const result = await runDreamsPhase(
|
|
5538
|
+
{ memoryDir, phase: options.phase, dryRun },
|
|
5539
|
+
governanceRunner,
|
|
5540
|
+
phaseRunner
|
|
5541
|
+
);
|
|
5542
|
+
return {
|
|
5543
|
+
phase: result.phase,
|
|
5544
|
+
dryRun: result.dryRun,
|
|
5545
|
+
durationMs: result.durationMs,
|
|
5546
|
+
itemsProcessed: result.itemsProcessed,
|
|
5547
|
+
notes: result.notes
|
|
5548
|
+
};
|
|
5549
|
+
}
|
|
5550
|
+
/**
|
|
5551
|
+
* Manually promote a memory into one or more authorized targets. Requires
|
|
5552
|
+
* a non-empty reason (audit-logged). Reuses the scope-profile promotion
|
|
5553
|
+
* resolution and `canWriteNamespace` gate — there is no dashboard-only
|
|
5554
|
+
* write path.
|
|
5555
|
+
*/
|
|
5556
|
+
async adminPromoteMemory(request) {
|
|
5557
|
+
const config = this.deps.orchestrator.config;
|
|
5558
|
+
const namespacesEnabled = resolveNamespaceCapabilities(config).namespaces === true;
|
|
5559
|
+
let sourceNamespace;
|
|
5560
|
+
if (request.namespace) {
|
|
5561
|
+
sourceNamespace = this.deps.resolveReadableNamespace(
|
|
5562
|
+
request.namespace,
|
|
5563
|
+
request.principal
|
|
5564
|
+
);
|
|
5942
5565
|
} else {
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5566
|
+
const codingContext2 = request.sessionKey ? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
|
|
5567
|
+
const plan = resolveScopePlan({
|
|
5568
|
+
config,
|
|
5569
|
+
sessionKey: request.sessionKey,
|
|
5570
|
+
principalOverride: request.principal,
|
|
5571
|
+
codingContext: codingContext2,
|
|
5572
|
+
namespacesEnabled
|
|
5573
|
+
});
|
|
5574
|
+
sourceNamespace = this.deps.resolveReadableNamespace(
|
|
5575
|
+
plan.baseNamespace,
|
|
5576
|
+
request.principal
|
|
5577
|
+
);
|
|
5578
|
+
}
|
|
5579
|
+
const codingContext = request.sessionKey ? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
|
|
5580
|
+
const codingOverlay = codingContext ? resolveCodingNamespaceOverlay(
|
|
5581
|
+
codingContext,
|
|
5582
|
+
config.codingMode,
|
|
5583
|
+
config.defaultNamespace
|
|
5584
|
+
) : null;
|
|
5585
|
+
const scopeProfilePlan = resolveScopeProfilePlan({
|
|
5586
|
+
config,
|
|
5587
|
+
principal: request.principal,
|
|
5588
|
+
codingContext,
|
|
5589
|
+
codingOverlay
|
|
5590
|
+
});
|
|
5591
|
+
const storage = {
|
|
5592
|
+
readMemory: async (namespace, memoryId) => {
|
|
5593
|
+
const resolved = await this.deps.orchestrator.getStorage(namespace);
|
|
5594
|
+
const memory = await resolved.getMemoryById(memoryId);
|
|
5595
|
+
if (!memory) return null;
|
|
5596
|
+
return {
|
|
5597
|
+
category: memory.frontmatter.category,
|
|
5598
|
+
content: memory.content,
|
|
5599
|
+
frontmatter: memory.frontmatter
|
|
5973
5600
|
};
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5601
|
+
},
|
|
5602
|
+
writePromotedMemory: async (namespace, memory) => {
|
|
5603
|
+
const resolved = await this.deps.orchestrator.getStorage(namespace);
|
|
5604
|
+
const { id, tombstoneBlocked } = await resolved.writeMemory(
|
|
5605
|
+
memory.category,
|
|
5606
|
+
memory.content,
|
|
5607
|
+
{
|
|
5608
|
+
confidence: memory.confidence,
|
|
5609
|
+
tags: memory.tags,
|
|
5610
|
+
entityRef: memory.entityRef,
|
|
5611
|
+
source: `admin-promotion:${memory.sourceNamespace}:${memory.reason.slice(0, 120)}`,
|
|
5612
|
+
lineage: memory.lineage,
|
|
5613
|
+
sourceMemoryId: memory.sourceMemoryId,
|
|
5614
|
+
actor: memory.actor,
|
|
5615
|
+
validAt: memory.validAt
|
|
5984
5616
|
}
|
|
5985
|
-
};
|
|
5986
|
-
}
|
|
5987
|
-
if (!budgetDecision.allowed) {
|
|
5988
|
-
throw new EngramAccessInputError(
|
|
5989
|
-
`recall denied: cross-namespace budget exceeded (${budgetDecision.count}/${budgetDecision.limit.hardLimit} in ${budgetDecision.limit.windowMs}ms window)`
|
|
5990
|
-
);
|
|
5991
|
-
}
|
|
5992
|
-
this.deps.budget.gc();
|
|
5993
|
-
}
|
|
5994
|
-
const topK = Number.isFinite(request.topK) ? Math.max(0, Math.floor(request.topK ?? 0)) : void 0;
|
|
5995
|
-
let asOf;
|
|
5996
|
-
if (request.asOf !== void 0 && request.asOf !== null) {
|
|
5997
|
-
if (typeof request.asOf !== "string" || request.asOf.trim().length === 0) {
|
|
5998
|
-
throw new EngramAccessInputError(
|
|
5999
|
-
"asOf must be a non-empty ISO 8601 timestamp string"
|
|
6000
5617
|
);
|
|
5618
|
+
if (tombstoneBlocked) {
|
|
5619
|
+
throw new Error(
|
|
5620
|
+
"target namespace tombstone-blocked the promoted content (pending_review)"
|
|
5621
|
+
);
|
|
5622
|
+
}
|
|
5623
|
+
return id;
|
|
6001
5624
|
}
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
5625
|
+
};
|
|
5626
|
+
try {
|
|
5627
|
+
return await promoteMemory({
|
|
5628
|
+
config,
|
|
5629
|
+
sourceMemoryId: request.sourceMemoryId,
|
|
5630
|
+
sourceNamespace,
|
|
5631
|
+
principal: request.principal,
|
|
5632
|
+
targets: request.targets,
|
|
5633
|
+
reason: request.reason,
|
|
5634
|
+
actor: request.principal ?? "admin-console",
|
|
5635
|
+
storage,
|
|
5636
|
+
scopeProfilePlan
|
|
5637
|
+
});
|
|
5638
|
+
} catch (err) {
|
|
5639
|
+
if (err instanceof AdminPromotionError) {
|
|
5640
|
+
throw new EngramAccessInputError(err.message);
|
|
6007
5641
|
}
|
|
6008
|
-
|
|
5642
|
+
throw err;
|
|
6009
5643
|
}
|
|
6010
|
-
|
|
6011
|
-
|
|
5644
|
+
}
|
|
5645
|
+
};
|
|
5646
|
+
|
|
5647
|
+
// src/access-recall-surface.ts
|
|
5648
|
+
import { createHash as createHash4 } from "crypto";
|
|
5649
|
+
import * as nodePath2 from "path";
|
|
5650
|
+
|
|
5651
|
+
// src/orchestration/qmd-result-resolver.ts
|
|
5652
|
+
import path8 from "path";
|
|
5653
|
+
function qmdCollectionPathParts(resultPath) {
|
|
5654
|
+
if (!resultPath || path8.isAbsolute(resultPath)) return null;
|
|
5655
|
+
const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
5656
|
+
const slashIndex = normalized.indexOf("/");
|
|
5657
|
+
if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
|
|
5658
|
+
const collection = normalized.slice(0, slashIndex);
|
|
5659
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(collection)) return null;
|
|
5660
|
+
return {
|
|
5661
|
+
collection,
|
|
5662
|
+
relativePath: normalized.slice(slashIndex + 1)
|
|
5663
|
+
};
|
|
5664
|
+
}
|
|
5665
|
+
function qmdResultPathCandidates(storageDir, resultPath) {
|
|
5666
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
5667
|
+
const storageRoot = path8.resolve(storageDir);
|
|
5668
|
+
const addCandidate = (candidate) => {
|
|
5669
|
+
const resolved = path8.resolve(candidate);
|
|
5670
|
+
if (isPathInsideStorageRoot(storageRoot, resolved)) {
|
|
5671
|
+
candidates.add(resolved);
|
|
5672
|
+
}
|
|
5673
|
+
};
|
|
5674
|
+
const addRelativeCandidates = (relativePath) => {
|
|
5675
|
+
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
5676
|
+
if (!normalized) return;
|
|
5677
|
+
addCandidate(path8.join(storageRoot, normalized));
|
|
5678
|
+
if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
|
|
5679
|
+
addCandidate(path8.join(storageRoot, "facts", normalized));
|
|
5680
|
+
}
|
|
5681
|
+
};
|
|
5682
|
+
if (path8.isAbsolute(resultPath)) {
|
|
5683
|
+
addCandidate(resultPath);
|
|
5684
|
+
} else {
|
|
5685
|
+
addRelativeCandidates(resultPath);
|
|
5686
|
+
}
|
|
5687
|
+
return [...candidates];
|
|
5688
|
+
}
|
|
5689
|
+
function storageDirOrNull(storage) {
|
|
5690
|
+
const dir = storage.dir;
|
|
5691
|
+
return typeof dir === "string" && dir.length > 0 ? dir : null;
|
|
5692
|
+
}
|
|
5693
|
+
var QmdResultResolver = class {
|
|
5694
|
+
getConfig;
|
|
5695
|
+
storageFor;
|
|
5696
|
+
storageDirNamespace;
|
|
5697
|
+
qmdCollectionNamespaceFromPrefix;
|
|
5698
|
+
namespaceFromPath;
|
|
5699
|
+
constructor(options) {
|
|
5700
|
+
this.getConfig = options.getConfig;
|
|
5701
|
+
this.storageFor = options.storageFor;
|
|
5702
|
+
this.storageDirNamespace = options.storageDirNamespace;
|
|
5703
|
+
this.qmdCollectionNamespaceFromPrefix = options.qmdCollectionNamespaceFromPrefix;
|
|
5704
|
+
this.namespaceFromPath = options.namespaceFromPath;
|
|
5705
|
+
}
|
|
5706
|
+
async readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces = []) {
|
|
5707
|
+
const parts = qmdCollectionPathParts(resultPath);
|
|
5708
|
+
const fallbackStorageDir = storageDirOrNull(fallbackStorage);
|
|
5709
|
+
const config = this.getConfig();
|
|
5710
|
+
const coldCollection = config.qmdColdCollection ?? "openclaw-engram-cold";
|
|
5711
|
+
if (parts && parts.collection === coldCollection) {
|
|
5712
|
+
const storages = [];
|
|
5713
|
+
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
5714
|
+
const addStorage = (storage) => {
|
|
5715
|
+
const storageDir = storageDirOrNull(storage);
|
|
5716
|
+
const storageKey = storageDir ? path8.resolve(storageDir) : `storage-without-dir-${storages.length}`;
|
|
5717
|
+
if (seenStorageDirs.has(storageKey)) return;
|
|
5718
|
+
seenStorageDirs.add(storageKey);
|
|
5719
|
+
storages.push(storage);
|
|
5720
|
+
};
|
|
5721
|
+
const fallbackNamespace = fallbackStorageDir !== null ? this.storageDirNamespace(fallbackStorageDir) : config.defaultNamespace;
|
|
5722
|
+
if (recallNamespaces.length === 0 || !resolveNamespaceCapabilities(config).namespaces || recallNamespaces.includes(fallbackNamespace)) {
|
|
5723
|
+
addStorage(fallbackStorage);
|
|
5724
|
+
}
|
|
5725
|
+
if (recallNamespaces.length > 0) {
|
|
5726
|
+
for (const namespace of recallNamespaces) {
|
|
5727
|
+
try {
|
|
5728
|
+
addStorage(await this.storageFor(namespace));
|
|
5729
|
+
} catch (err) {
|
|
5730
|
+
log.debug("qmd cold result namespace storage lookup skipped", {
|
|
5731
|
+
path: resultPath,
|
|
5732
|
+
namespace,
|
|
5733
|
+
error: err.message
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
for (const storage of storages) {
|
|
5739
|
+
const storageDir = storageDirOrNull(storage);
|
|
5740
|
+
if (!storageDir) {
|
|
5741
|
+
const memory = await storage.readMemoryByPath(resultPath);
|
|
5742
|
+
if (memory) return memory;
|
|
5743
|
+
continue;
|
|
5744
|
+
}
|
|
5745
|
+
try {
|
|
5746
|
+
const coldRoot = path8.join(storageDir, "cold");
|
|
5747
|
+
for (const candidate of qmdResultPathCandidates(
|
|
5748
|
+
coldRoot,
|
|
5749
|
+
parts.relativePath
|
|
5750
|
+
)) {
|
|
5751
|
+
const memory = await storage.readMemoryByPath(candidate);
|
|
5752
|
+
if (memory) return memory;
|
|
5753
|
+
}
|
|
5754
|
+
} catch (err) {
|
|
5755
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5756
|
+
log.debug("qmd cold result path lookup failed open", {
|
|
5757
|
+
path: resultPath,
|
|
5758
|
+
collection: coldCollection,
|
|
5759
|
+
error: err.message
|
|
5760
|
+
});
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
return null;
|
|
5764
|
+
}
|
|
5765
|
+
const collectionNamespace = parts ? this.qmdCollectionNamespaceFromPrefix(parts.collection) : null;
|
|
5766
|
+
if (parts && collectionNamespace) {
|
|
5767
|
+
try {
|
|
5768
|
+
const collectionStorage = await this.storageFor(collectionNamespace);
|
|
5769
|
+
for (const candidate of qmdResultPathCandidates(
|
|
5770
|
+
collectionStorage.dir,
|
|
5771
|
+
parts.relativePath
|
|
5772
|
+
)) {
|
|
5773
|
+
const memory = await collectionStorage.readMemoryByPath(candidate);
|
|
5774
|
+
if (memory) return memory;
|
|
5775
|
+
}
|
|
5776
|
+
return null;
|
|
5777
|
+
} catch (err) {
|
|
5778
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
5779
|
+
log.debug("qmd result namespace path lookup failed open", {
|
|
5780
|
+
path: resultPath,
|
|
5781
|
+
namespace: collectionNamespace,
|
|
5782
|
+
error: err.message
|
|
5783
|
+
});
|
|
5784
|
+
return null;
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
if (path8.isAbsolute(resultPath)) {
|
|
5788
|
+
if (!fallbackStorageDir) {
|
|
5789
|
+
return await fallbackStorage.readMemoryByPath(resultPath);
|
|
5790
|
+
}
|
|
5791
|
+
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
5792
|
+
resultPath,
|
|
5793
|
+
fallbackStorage,
|
|
5794
|
+
recallNamespaces
|
|
5795
|
+
);
|
|
5796
|
+
if (!ownerStorage) return null;
|
|
5797
|
+
for (const candidate of qmdResultPathCandidates(
|
|
5798
|
+
ownerStorage.dir,
|
|
5799
|
+
resultPath
|
|
5800
|
+
)) {
|
|
5801
|
+
const memory = await ownerStorage.storage.readMemoryByPath(candidate);
|
|
5802
|
+
if (memory) return memory;
|
|
5803
|
+
}
|
|
5804
|
+
return null;
|
|
5805
|
+
}
|
|
5806
|
+
if (!fallbackStorageDir) {
|
|
5807
|
+
return await fallbackStorage.readMemoryByPath(resultPath);
|
|
5808
|
+
}
|
|
5809
|
+
for (const candidate of qmdResultPathCandidates(
|
|
5810
|
+
fallbackStorageDir,
|
|
5811
|
+
resultPath
|
|
5812
|
+
)) {
|
|
5813
|
+
const memory = await fallbackStorage.readMemoryByPath(candidate);
|
|
5814
|
+
if (memory) return memory;
|
|
5815
|
+
}
|
|
5816
|
+
return null;
|
|
5817
|
+
}
|
|
5818
|
+
async resolveColdQmdResultForRecall(result, fallbackStorage, recallNamespaces = []) {
|
|
5819
|
+
const memory = await this.readQmdResultMemory(
|
|
5820
|
+
result.path,
|
|
5821
|
+
fallbackStorage,
|
|
5822
|
+
recallNamespaces
|
|
5823
|
+
);
|
|
5824
|
+
if (!memory) return null;
|
|
5825
|
+
let ownerNamespace = null;
|
|
5826
|
+
if (path8.isAbsolute(memory.path)) {
|
|
5827
|
+
const ownerStorage = await this.storageForAbsoluteQmdResultPath(
|
|
5828
|
+
memory.path,
|
|
5829
|
+
fallbackStorage,
|
|
5830
|
+
recallNamespaces
|
|
5831
|
+
);
|
|
5832
|
+
ownerNamespace = ownerStorage?.namespace ?? null;
|
|
5833
|
+
if (!ownerNamespace && resolveNamespaceCapabilities(this.getConfig()).namespaces) return null;
|
|
5834
|
+
}
|
|
5835
|
+
ownerNamespace ??= this.namespaceFromPath(memory.path);
|
|
5836
|
+
if (recallNamespaces.length > 0 && !recallNamespaces.includes(ownerNamespace)) {
|
|
5837
|
+
return null;
|
|
5838
|
+
}
|
|
5839
|
+
return {
|
|
5840
|
+
namespace: ownerNamespace,
|
|
5841
|
+
result: {
|
|
5842
|
+
...result,
|
|
5843
|
+
docid: result.docid || memory.frontmatter.id,
|
|
5844
|
+
path: memory.path,
|
|
5845
|
+
snippet: result.snippet || memory.content.slice(0, 400)
|
|
5846
|
+
}
|
|
5847
|
+
};
|
|
5848
|
+
}
|
|
5849
|
+
async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
|
|
5850
|
+
const resolvedPath = path8.resolve(resultPath);
|
|
5851
|
+
const config = this.getConfig();
|
|
5852
|
+
const memoryRoot = path8.resolve(config.memoryDir);
|
|
5853
|
+
const namespacesRoot = path8.join(memoryRoot, "namespaces");
|
|
5854
|
+
const fallbackStorageDir = storageDirOrNull(fallbackStorage);
|
|
5855
|
+
const matches = [];
|
|
5856
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
5857
|
+
const maybeAddStorage = (storage, namespace) => {
|
|
5858
|
+
const storageDir = storageDirOrNull(storage);
|
|
5859
|
+
if (!storageDir) return;
|
|
5860
|
+
const candidateRoot = path8.resolve(storageDir);
|
|
5861
|
+
if (seenDirs.has(candidateRoot)) return;
|
|
5862
|
+
if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
|
|
5863
|
+
if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
5864
|
+
return;
|
|
5865
|
+
}
|
|
5866
|
+
seenDirs.add(candidateRoot);
|
|
5867
|
+
matches.push({ storage, dir: candidateRoot, namespace });
|
|
5868
|
+
};
|
|
5869
|
+
const fallbackNamespace = fallbackStorageDir !== null ? this.storageDirNamespace(fallbackStorageDir) : config.defaultNamespace;
|
|
5870
|
+
maybeAddStorage(fallbackStorage, fallbackNamespace);
|
|
5871
|
+
const candidateNamespaces = /* @__PURE__ */ new Set();
|
|
5872
|
+
candidateNamespaces.add(config.defaultNamespace);
|
|
5873
|
+
candidateNamespaces.add(config.sharedNamespace);
|
|
5874
|
+
for (const ns of recallNamespaces) {
|
|
5875
|
+
candidateNamespaces.add(ns);
|
|
5876
|
+
}
|
|
5877
|
+
if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
|
|
5878
|
+
const relativeToNamespaces = path8.relative(namespacesRoot, resolvedPath);
|
|
5879
|
+
const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
|
|
5880
|
+
if (namespaceSegment) {
|
|
5881
|
+
candidateNamespaces.add(
|
|
5882
|
+
namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
|
|
5883
|
+
);
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
for (const policy of config.namespacePolicies ?? []) {
|
|
5887
|
+
candidateNamespaces.add(policy.name);
|
|
5888
|
+
}
|
|
5889
|
+
for (const ns of candidateNamespaces) {
|
|
5890
|
+
if (!ns) continue;
|
|
5891
|
+
try {
|
|
5892
|
+
maybeAddStorage(await this.storageFor(ns), ns);
|
|
5893
|
+
} catch {
|
|
5894
|
+
continue;
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
matches.sort((a, b) => b.dir.length - a.dir.length);
|
|
5898
|
+
return matches[0] ?? null;
|
|
5899
|
+
}
|
|
5900
|
+
};
|
|
5901
|
+
|
|
5902
|
+
// src/access-recall-surface.ts
|
|
5903
|
+
var AccessRecallSurface = class {
|
|
5904
|
+
constructor(deps) {
|
|
5905
|
+
this.deps = deps;
|
|
5906
|
+
}
|
|
5907
|
+
deps;
|
|
5908
|
+
async buildRecallResponseFromXraySnapshot(options) {
|
|
5909
|
+
const memoryIds = options.snapshot.results.map((result) => result.memoryId);
|
|
5910
|
+
const resultPaths = options.snapshot.results.map((result) => result.path);
|
|
5911
|
+
const namespace = options.snapshot.namespace ? this.deps.resolveNamespace(options.snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
|
|
5912
|
+
const sourcesUsed = Array.from(
|
|
5913
|
+
new Set(options.snapshot.results.map((result) => result.servedBy))
|
|
5914
|
+
);
|
|
5915
|
+
const snapshotForSerialization = {
|
|
5916
|
+
sessionKey: options.sessionKey ?? "",
|
|
5917
|
+
recordedAt: new Date(options.snapshot.capturedAt).toISOString(),
|
|
5918
|
+
queryHash: createHash4("sha256").update(options.query).digest("hex"),
|
|
5919
|
+
queryLen: options.query.length,
|
|
5920
|
+
memoryIds,
|
|
5921
|
+
namespace,
|
|
5922
|
+
recallNamespaces: [namespace],
|
|
5923
|
+
traceId: options.snapshot.traceId,
|
|
5924
|
+
plannerMode: options.normalizedMode,
|
|
5925
|
+
requestedMode: options.requestedMode && options.requestedMode !== "auto" ? options.requestedMode : void 0,
|
|
5926
|
+
sourcesUsed,
|
|
5927
|
+
budgetsApplied: {
|
|
5928
|
+
appliedTopK: memoryIds.length,
|
|
5929
|
+
recallBudgetChars: options.snapshot.budget.chars,
|
|
5930
|
+
maxMemoryTokens: this.deps.orchestrator.config.maxMemoryTokens,
|
|
5931
|
+
finalContextChars: options.snapshot.budget.used
|
|
5932
|
+
},
|
|
5933
|
+
latencyMs: Date.now() - options.startedAt,
|
|
5934
|
+
resultPaths
|
|
5935
|
+
};
|
|
5936
|
+
const results = await this.deps.serializeRecallResults(
|
|
5937
|
+
snapshotForSerialization,
|
|
5938
|
+
options.disclosure,
|
|
5939
|
+
{
|
|
5940
|
+
query: options.query,
|
|
5941
|
+
...options.sessionKey ? { sessionKey: options.sessionKey } : {},
|
|
5942
|
+
...options.rawExcerptNamespace ? { rawExcerptNamespace: options.rawExcerptNamespace } : {},
|
|
5943
|
+
...options.rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: options.rawExcerptSessionIds } : {},
|
|
5944
|
+
...options.rawExcerptsSuppressed ? { rawExcerptsSuppressed: options.rawExcerptsSuppressed } : {}
|
|
5945
|
+
}
|
|
5946
|
+
);
|
|
5947
|
+
const context = results.map((result) => {
|
|
5948
|
+
const content = typeof result.content === "string" && result.content.length > 0 ? result.content : "";
|
|
5949
|
+
return content || result.preview;
|
|
5950
|
+
}).filter((text) => text.length > 0).join("\n\n");
|
|
5951
|
+
return {
|
|
5952
|
+
query: options.query,
|
|
5953
|
+
...options.sessionKey ? { sessionKey: options.sessionKey } : {},
|
|
5954
|
+
namespace,
|
|
5955
|
+
context,
|
|
5956
|
+
count: memoryIds.length,
|
|
5957
|
+
memoryIds,
|
|
5958
|
+
results,
|
|
5959
|
+
recordedAt: snapshotForSerialization.recordedAt,
|
|
5960
|
+
traceId: options.snapshot.traceId,
|
|
5961
|
+
plannerMode: options.normalizedMode,
|
|
5962
|
+
fallbackUsed: sourcesUsed.some((source) => source !== "hybrid"),
|
|
5963
|
+
sourcesUsed,
|
|
5964
|
+
disclosure: options.disclosure,
|
|
5965
|
+
budgetsApplied: snapshotForSerialization.budgetsApplied,
|
|
5966
|
+
latencyMs: snapshotForSerialization.latencyMs
|
|
5967
|
+
};
|
|
5968
|
+
}
|
|
5969
|
+
async serializeRecallResults(snapshot, disclosure, rawContext = null) {
|
|
5970
|
+
if (!snapshot) return [];
|
|
5971
|
+
const namespace = snapshot.namespace ? this.deps.resolveNamespace(snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
|
|
5972
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
5973
|
+
const storageDir = storage.dir;
|
|
5974
|
+
const recallNamespaces = Array.from(
|
|
5975
|
+
new Set(
|
|
5976
|
+
[
|
|
5977
|
+
namespace,
|
|
5978
|
+
...Array.isArray(snapshot.recallNamespaces) ? snapshot.recallNamespaces.map((ns) => this.deps.resolveNamespace(ns)) : [],
|
|
5979
|
+
this.deps.orchestrator.config.defaultNamespace,
|
|
5980
|
+
this.deps.orchestrator.config.sharedNamespace,
|
|
5981
|
+
...(this.deps.orchestrator.config.namespacePolicies ?? []).map((p) => p.name)
|
|
5982
|
+
].filter((ns) => typeof ns === "string" && ns.length > 0)
|
|
5983
|
+
)
|
|
5984
|
+
);
|
|
5985
|
+
const results = [];
|
|
5986
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5987
|
+
const collectionNamespaceFromPrefix = (collectionPrefix) => {
|
|
5988
|
+
const baseCollection = this.deps.orchestrator.config.qmdCollection;
|
|
5989
|
+
if (collectionPrefix === baseCollection) return this.deps.orchestrator.config.defaultNamespace;
|
|
5990
|
+
const namespaceSuffix = collectionPrefix.startsWith(`${baseCollection}--`) ? collectionPrefix.slice(baseCollection.length + 2) : "";
|
|
5991
|
+
if (!namespaceSuffix) return null;
|
|
5992
|
+
const decoded = namespaceIdentityFromToken(namespaceSuffix);
|
|
5993
|
+
if (decoded !== null) return decoded || this.deps.orchestrator.config.defaultNamespace;
|
|
5994
|
+
if (namespaceSuffix.startsWith("ns--")) {
|
|
5995
|
+
const legacyNamespace = namespaceSuffix.slice("ns--".length).trim();
|
|
5996
|
+
return legacyNamespace || null;
|
|
5997
|
+
}
|
|
5998
|
+
return null;
|
|
5999
|
+
};
|
|
6000
|
+
const readResultPath = async (memoryPath) => {
|
|
6001
|
+
const parts = qmdCollectionPathParts(memoryPath);
|
|
6002
|
+
const coldCollection = this.deps.orchestrator.config.qmdColdCollection ?? "openclaw-engram-cold";
|
|
6003
|
+
if (parts && parts.collection === coldCollection) {
|
|
6004
|
+
const storages = [];
|
|
6005
|
+
const seenStorageDirs = /* @__PURE__ */ new Set();
|
|
6006
|
+
const addStorage = (candidateStorage) => {
|
|
6007
|
+
const candidateDir = nodePath2.resolve(candidateStorage.dir);
|
|
6008
|
+
if (seenStorageDirs.has(candidateDir)) return;
|
|
6009
|
+
seenStorageDirs.add(candidateDir);
|
|
6010
|
+
storages.push({ storage: candidateStorage, dir: candidateDir });
|
|
6011
|
+
};
|
|
6012
|
+
addStorage(storage);
|
|
6013
|
+
for (const recallNamespace of recallNamespaces) {
|
|
6014
|
+
try {
|
|
6015
|
+
addStorage(await this.deps.orchestrator.getStorage(recallNamespace));
|
|
6016
|
+
} catch {
|
|
6017
|
+
continue;
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
for (const candidateStorage of storages) {
|
|
6021
|
+
try {
|
|
6022
|
+
const coldRoot = nodePath2.join(candidateStorage.dir, "cold");
|
|
6023
|
+
for (const candidatePath of qmdResultPathCandidates(
|
|
6024
|
+
coldRoot,
|
|
6025
|
+
parts.relativePath
|
|
6026
|
+
)) {
|
|
6027
|
+
const memory = await candidateStorage.storage.readMemoryByPath(candidatePath);
|
|
6028
|
+
if (memory) return { memory, baseDir: candidateStorage.dir };
|
|
6029
|
+
}
|
|
6030
|
+
} catch (err) {
|
|
6031
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
6032
|
+
continue;
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
return null;
|
|
6036
|
+
}
|
|
6037
|
+
const collectionNamespace = parts ? collectionNamespaceFromPrefix(parts.collection) : null;
|
|
6038
|
+
if (parts && collectionNamespace) {
|
|
6039
|
+
try {
|
|
6040
|
+
const collectionStorage = await this.deps.orchestrator.getStorage(collectionNamespace);
|
|
6041
|
+
for (const candidate of qmdResultPathCandidates(
|
|
6042
|
+
collectionStorage.dir,
|
|
6043
|
+
parts.relativePath
|
|
6044
|
+
)) {
|
|
6045
|
+
const memory = await collectionStorage.readMemoryByPath(candidate);
|
|
6046
|
+
if (memory) return { memory, baseDir: collectionStorage.dir };
|
|
6047
|
+
}
|
|
6048
|
+
return null;
|
|
6049
|
+
} catch (err) {
|
|
6050
|
+
if (err instanceof SecureStoreLockedError) throw err;
|
|
6051
|
+
return null;
|
|
6052
|
+
}
|
|
6053
|
+
}
|
|
6054
|
+
if (nodePath2.isAbsolute(memoryPath)) {
|
|
6055
|
+
const ownerStorage = await this.deps.storageForAbsoluteRecallPath(
|
|
6056
|
+
memoryPath,
|
|
6057
|
+
namespace,
|
|
6058
|
+
recallNamespaces
|
|
6059
|
+
);
|
|
6060
|
+
if (!ownerStorage) return null;
|
|
6061
|
+
for (const candidate of qmdResultPathCandidates(
|
|
6062
|
+
ownerStorage.dir,
|
|
6063
|
+
memoryPath
|
|
6064
|
+
)) {
|
|
6065
|
+
const memory = await ownerStorage.storage.readMemoryByPath(candidate);
|
|
6066
|
+
if (memory) return { memory, baseDir: ownerStorage.dir };
|
|
6067
|
+
}
|
|
6068
|
+
return null;
|
|
6069
|
+
}
|
|
6070
|
+
for (const candidate of qmdResultPathCandidates(storageDir, memoryPath)) {
|
|
6071
|
+
const memory = await storage.readMemoryByPath(candidate);
|
|
6072
|
+
if (memory) return { memory, baseDir: storageDir };
|
|
6073
|
+
}
|
|
6074
|
+
return null;
|
|
6075
|
+
};
|
|
6076
|
+
const rawExcerptsResult = rawContext?.rawExcerptsSuppressed === true ? (
|
|
6077
|
+
// Implicit raw recall with NO readable LCM namespace (#1505 thread
|
|
6078
|
+
// NBHWz): emit empty excerpts rather than falling back to the
|
|
6079
|
+
// write/overlay `namespace` the read gate excludes.
|
|
6080
|
+
[]
|
|
6081
|
+
) : await this.deps.fetchRawExcerpts(
|
|
6082
|
+
disclosure,
|
|
6083
|
+
rawContext ? {
|
|
6084
|
+
query: rawContext.query,
|
|
6085
|
+
...rawContext.sessionKey ? { sessionKey: rawContext.sessionKey } : {},
|
|
6086
|
+
namespace: rawContext.rawExcerptNamespace ?? namespace,
|
|
6087
|
+
...rawContext.rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawContext.rawExcerptSessionIds } : {}
|
|
6088
|
+
} : null
|
|
6089
|
+
);
|
|
6090
|
+
const rawExcerpts = rawExcerptsResult ?? void 0;
|
|
6091
|
+
for (const memoryPath of snapshot.resultPaths ?? []) {
|
|
6092
|
+
if (!memoryPath || seen.has(memoryPath)) continue;
|
|
6093
|
+
const resolved = await readResultPath(memoryPath);
|
|
6094
|
+
if (!resolved) continue;
|
|
6095
|
+
const { memory, baseDir } = resolved;
|
|
6096
|
+
seen.add(memoryPath);
|
|
6097
|
+
results.push(
|
|
6098
|
+
this.deps.serializeMemorySummary(
|
|
6099
|
+
memory,
|
|
6100
|
+
baseDir,
|
|
6101
|
+
disclosure,
|
|
6102
|
+
// Attach the (possibly empty) raw excerpts to the first raw
|
|
6103
|
+
// result; subsequent results do not duplicate the array.
|
|
6104
|
+
results.length === 0 ? rawExcerpts : void 0
|
|
6105
|
+
)
|
|
6106
|
+
);
|
|
6107
|
+
}
|
|
6108
|
+
if (results.length > 0) return results;
|
|
6109
|
+
for (const memoryId of snapshot.memoryIds) {
|
|
6110
|
+
const memory = await storage.getMemoryById(memoryId);
|
|
6111
|
+
if (!memory || seen.has(memory.path)) continue;
|
|
6112
|
+
seen.add(memory.path);
|
|
6113
|
+
results.push(
|
|
6114
|
+
this.deps.serializeMemorySummary(
|
|
6115
|
+
memory,
|
|
6116
|
+
storageDir,
|
|
6117
|
+
disclosure,
|
|
6118
|
+
results.length === 0 ? rawExcerpts : void 0
|
|
6119
|
+
)
|
|
6120
|
+
);
|
|
6121
|
+
}
|
|
6122
|
+
return results;
|
|
6123
|
+
}
|
|
6124
|
+
/**
|
|
6125
|
+
* Fetch raw transcript excerpts from the LCM archive for `disclosure ===
|
|
6126
|
+
* "raw"` recalls (issue #677 PR 2/4). Returns `null` for non-raw recall
|
|
6127
|
+
* depths, an empty array when LCM is disabled / not initialized / has no
|
|
6128
|
+
* matches, and an array of LCM-side excerpts otherwise. Errors are
|
|
6129
|
+
* swallowed and treated as "no excerpts" so a failing LCM never breaks
|
|
6130
|
+
* the recall response.
|
|
6131
|
+
*
|
|
6132
|
+
* Namespace handling: LCM archival prefixes non-default-namespace
|
|
6133
|
+
* sessions with `${namespace}:${sessionKey}` (see `observe()` around
|
|
6134
|
+
* line 2498), so the lookup must mirror that prefix or raw recalls in
|
|
6135
|
+
* non-default namespaces miss their own excerpts.
|
|
6136
|
+
*/
|
|
6137
|
+
async fetchRawExcerpts(disclosure, context) {
|
|
6138
|
+
if (disclosure !== "raw") return null;
|
|
6139
|
+
if (!context || !context.query) return [];
|
|
6140
|
+
if (!context.sessionKey) return [];
|
|
6141
|
+
const lcm = this.deps.orchestrator.lcmEngine;
|
|
6142
|
+
if (!lcm || !lcm.enabled) return [];
|
|
6143
|
+
try {
|
|
6144
|
+
const legacyKey = context.namespace && context.namespace !== this.deps.orchestrator.config.defaultNamespace ? `${context.namespace}:${context.sessionKey}` : context.sessionKey;
|
|
6145
|
+
const lcmSessionIds = context.lcmSessionIds !== void 0 ? context.lcmSessionIds : [legacyKey];
|
|
6146
|
+
const limit = 5;
|
|
6147
|
+
const seenRows = /* @__PURE__ */ new Set();
|
|
6148
|
+
const excerpts = [];
|
|
6149
|
+
const settledRows = await Promise.allSettled(
|
|
6150
|
+
lcmSessionIds.map(
|
|
6151
|
+
async (lcmSessionKey) => lcm.searchContextFull(context.query, limit, lcmSessionKey)
|
|
6152
|
+
)
|
|
6153
|
+
);
|
|
6154
|
+
for (const result of settledRows) {
|
|
6155
|
+
if (excerpts.length >= limit) break;
|
|
6156
|
+
if (result.status !== "fulfilled") continue;
|
|
6157
|
+
for (const r of result.value) {
|
|
6158
|
+
const dedupeKey = `${r.session_id} ${r.turn_index}`;
|
|
6159
|
+
if (seenRows.has(dedupeKey)) continue;
|
|
6160
|
+
seenRows.add(dedupeKey);
|
|
6161
|
+
excerpts.push({
|
|
6162
|
+
turnIndex: r.turn_index,
|
|
6163
|
+
role: r.role,
|
|
6164
|
+
content: r.content,
|
|
6165
|
+
sessionId: r.session_id
|
|
6166
|
+
});
|
|
6167
|
+
if (excerpts.length >= limit) break;
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
return excerpts;
|
|
6171
|
+
} catch {
|
|
6172
|
+
return [];
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
async executeRecall(request) {
|
|
6176
|
+
const query = request.query;
|
|
6177
|
+
const callerProvidedDisclosure = request.disclosure !== void 0 && request.disclosure !== null;
|
|
6178
|
+
const requestedDisclosure = (() => {
|
|
6179
|
+
if (!callerProvidedDisclosure) {
|
|
6180
|
+
return DEFAULT_RECALL_DISCLOSURE;
|
|
6181
|
+
}
|
|
6182
|
+
if (!isRecallDisclosure(request.disclosure)) {
|
|
6183
|
+
throw new EngramAccessInputError(
|
|
6184
|
+
`disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
|
|
6185
|
+
);
|
|
6186
|
+
}
|
|
6187
|
+
return request.disclosure;
|
|
6188
|
+
})();
|
|
6189
|
+
if (request.codingContext !== void 0 && request.sessionKey) {
|
|
6190
|
+
this.deps.setCodingContext({
|
|
6191
|
+
sessionKey: request.sessionKey,
|
|
6192
|
+
codingContext: request.codingContext
|
|
6193
|
+
});
|
|
6194
|
+
}
|
|
6195
|
+
if (request.codingContext === void 0 && request.sessionKey) {
|
|
6196
|
+
await this.deps.maybeAttachCodingContext(request.sessionKey, {
|
|
6197
|
+
cwd: request.cwd,
|
|
6198
|
+
projectTag: request.projectTag
|
|
6199
|
+
});
|
|
6200
|
+
}
|
|
6201
|
+
const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
|
|
6202
|
+
const namespaceOverride = this.deps.resolveRecallNamespace(
|
|
6203
|
+
request.namespace,
|
|
6204
|
+
request.sessionKey,
|
|
6205
|
+
authenticatedPrincipal
|
|
6206
|
+
);
|
|
6207
|
+
const namespace = namespaceOverride ?? this.deps.orchestrator.config.defaultNamespace;
|
|
6208
|
+
const mode = this.deps.normalizeRecallMode(request.mode);
|
|
6209
|
+
const maybePrincipal = this.deps.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
|
|
6210
|
+
if (resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces && !maybePrincipal) {
|
|
6211
|
+
throw new EngramAccessInputError(
|
|
6212
|
+
"authentication required: namespaces are enabled and no principal was supplied"
|
|
6213
|
+
);
|
|
6214
|
+
}
|
|
6215
|
+
const principal = maybePrincipal ?? "default";
|
|
6216
|
+
const principalNamespace = defaultNamespaceForPrincipal(principal, this.deps.orchestrator.config);
|
|
6217
|
+
const profileCodingContext = request.sessionKey && typeof this.deps.orchestrator.getCodingContextForSession === "function" ? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) : null;
|
|
6218
|
+
const profileCodingOverlay = !namespaceOverride && profileCodingContext && resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces && this.deps.orchestrator.config.codingMode?.projectScope ? resolveCodingNamespaceOverlay(
|
|
6219
|
+
profileCodingContext,
|
|
6220
|
+
this.deps.orchestrator.config.codingMode,
|
|
6221
|
+
this.deps.orchestrator.config.defaultNamespace
|
|
6222
|
+
) : null;
|
|
6223
|
+
const profilePlan = namespaceOverride ? null : resolveScopeProfilePlan({
|
|
6224
|
+
config: this.deps.orchestrator.config,
|
|
6225
|
+
principal,
|
|
6226
|
+
codingContext: profileCodingContext,
|
|
6227
|
+
codingOverlay: profileCodingOverlay
|
|
6228
|
+
});
|
|
6229
|
+
const modeSkipsBudget = mode === "no_recall";
|
|
6230
|
+
const legacyRecallNamespaces = Array.isArray(this.deps.orchestrator.config.defaultRecallNamespaces) ? recallNamespacesForPrincipal(principal, this.deps.orchestrator.config) : [];
|
|
6231
|
+
const effectiveNamespaces = namespaceOverride ? [namespaceOverride] : profilePlan ? expandScopeProfileReadNamespaces({
|
|
6232
|
+
profilePlan,
|
|
6233
|
+
principalSelfNamespace: profilePlan.baseNamespace,
|
|
6234
|
+
config: this.deps.orchestrator.config,
|
|
6235
|
+
principal,
|
|
6236
|
+
codingOverlay: profileCodingOverlay,
|
|
6237
|
+
legacyRecallNamespaces
|
|
6238
|
+
}) : legacyRecallNamespaces;
|
|
6239
|
+
const budgetPrincipalNamespace = profilePlan?.baseNamespace ?? principalNamespace;
|
|
6240
|
+
let budgetDecision;
|
|
6241
|
+
let recordBudgetAfterSuccess = false;
|
|
6242
|
+
if (modeSkipsBudget) {
|
|
6243
|
+
budgetDecision = {
|
|
6244
|
+
allowed: true,
|
|
6245
|
+
reason: "allowed-same-namespace",
|
|
6246
|
+
count: 0,
|
|
6247
|
+
limit: {
|
|
6248
|
+
softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
6249
|
+
hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
6250
|
+
windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
6251
|
+
}
|
|
6252
|
+
};
|
|
6253
|
+
} else {
|
|
6254
|
+
let anyCrossNamespace = false;
|
|
6255
|
+
let denied = null;
|
|
6256
|
+
let crossNamespaceDecision = null;
|
|
6257
|
+
for (const ns of effectiveNamespaces) {
|
|
6258
|
+
const peek = this.deps.budget.peek({
|
|
6259
|
+
principal,
|
|
6260
|
+
principalNamespace: budgetPrincipalNamespace,
|
|
6261
|
+
queryNamespace: ns
|
|
6262
|
+
});
|
|
6263
|
+
if (peek.reason !== "allowed-same-namespace") {
|
|
6264
|
+
anyCrossNamespace = true;
|
|
6265
|
+
crossNamespaceDecision ??= peek;
|
|
6266
|
+
}
|
|
6267
|
+
if (!peek.allowed) {
|
|
6268
|
+
denied = peek;
|
|
6269
|
+
break;
|
|
6270
|
+
}
|
|
6271
|
+
}
|
|
6272
|
+
if (denied) {
|
|
6273
|
+
budgetDecision = denied;
|
|
6274
|
+
} else if (anyCrossNamespace) {
|
|
6275
|
+
budgetDecision = crossNamespaceDecision ?? {
|
|
6276
|
+
allowed: true,
|
|
6277
|
+
reason: "allowed-under-soft",
|
|
6278
|
+
count: 0,
|
|
6279
|
+
limit: {
|
|
6280
|
+
softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
6281
|
+
hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
6282
|
+
windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
6283
|
+
}
|
|
6284
|
+
};
|
|
6285
|
+
recordBudgetAfterSuccess = true;
|
|
6286
|
+
} else {
|
|
6287
|
+
budgetDecision = {
|
|
6288
|
+
allowed: true,
|
|
6289
|
+
reason: "allowed-same-namespace",
|
|
6290
|
+
count: 0,
|
|
6291
|
+
limit: {
|
|
6292
|
+
softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
|
|
6293
|
+
hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
|
|
6294
|
+
windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
|
|
6295
|
+
}
|
|
6296
|
+
};
|
|
6297
|
+
}
|
|
6298
|
+
if (!budgetDecision.allowed) {
|
|
6299
|
+
throw new EngramAccessInputError(
|
|
6300
|
+
`recall denied: cross-namespace budget exceeded (${budgetDecision.count}/${budgetDecision.limit.hardLimit} in ${budgetDecision.limit.windowMs}ms window)`
|
|
6301
|
+
);
|
|
6302
|
+
}
|
|
6303
|
+
this.deps.budget.gc();
|
|
6304
|
+
}
|
|
6305
|
+
const topK = Number.isFinite(request.topK) ? Math.max(0, Math.floor(request.topK ?? 0)) : void 0;
|
|
6306
|
+
let asOf;
|
|
6307
|
+
if (request.asOf !== void 0 && request.asOf !== null) {
|
|
6308
|
+
if (typeof request.asOf !== "string" || request.asOf.trim().length === 0) {
|
|
6309
|
+
throw new EngramAccessInputError(
|
|
6310
|
+
"asOf must be a non-empty ISO 8601 timestamp string"
|
|
6311
|
+
);
|
|
6312
|
+
}
|
|
6313
|
+
const parsed = Date.parse(request.asOf);
|
|
6314
|
+
if (!Number.isFinite(parsed)) {
|
|
6315
|
+
throw new EngramAccessInputError(
|
|
6316
|
+
`asOf must be a parseable ISO 8601 timestamp (got: "${request.asOf}")`
|
|
6317
|
+
);
|
|
6318
|
+
}
|
|
6319
|
+
asOf = request.asOf;
|
|
6320
|
+
}
|
|
6321
|
+
const recallOptions = {
|
|
6322
|
+
namespace: namespaceOverride,
|
|
6012
6323
|
topK,
|
|
6013
6324
|
mode,
|
|
6014
6325
|
...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
|
|
@@ -6554,6 +6865,16 @@ var EngramAccessService = class {
|
|
|
6554
6865
|
budgetLocks = /* @__PURE__ */ new Map();
|
|
6555
6866
|
budget;
|
|
6556
6867
|
auditAdapter;
|
|
6868
|
+
/** AccessObserveWriteSurface (access-service decomposition). Lazy; selfDeps live wiring. */
|
|
6869
|
+
_accessObserveWriteSurface;
|
|
6870
|
+
get accessObserveWriteSurface() {
|
|
6871
|
+
if (!this._accessObserveWriteSurface) {
|
|
6872
|
+
this._accessObserveWriteSurface = new AccessObserveWriteSurface(
|
|
6873
|
+
selfDeps(this)
|
|
6874
|
+
);
|
|
6875
|
+
}
|
|
6876
|
+
return this._accessObserveWriteSurface;
|
|
6877
|
+
}
|
|
6557
6878
|
/** AccessLcmSurface (access-service decomposition). Lazy; selfDeps live wiring. */
|
|
6558
6879
|
_accessLcmSurface;
|
|
6559
6880
|
get accessLcmSurface() {
|
|
@@ -7291,114 +7612,20 @@ var EngramAccessService = class {
|
|
|
7291
7612
|
});
|
|
7292
7613
|
}
|
|
7293
7614
|
async qmdHealth(searchBackend, qmdEnabled, namespace, collection) {
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
collection,
|
|
7301
|
-
collectionState: "skipped",
|
|
7302
|
-
installedVersion: null,
|
|
7303
|
-
supportedVersion: null,
|
|
7304
|
-
supported: null,
|
|
7305
|
-
upgradeAvailable: null,
|
|
7306
|
-
doctorAvailable: null,
|
|
7307
|
-
debugStatus: searchBackend !== "qmd" ? `backend=${searchBackend}` : "backend=disabled"
|
|
7308
|
-
};
|
|
7309
|
-
}
|
|
7310
|
-
if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces === true) {
|
|
7311
|
-
const namespaceHealth = await this.namespaceQmdHealth(searchBackend, qmdEnabled, namespace, collection);
|
|
7312
|
-
if (namespaceHealth) return namespaceHealth;
|
|
7313
|
-
}
|
|
7314
|
-
const qmd = this.orchestrator.qmd;
|
|
7315
|
-
if (!qmd) {
|
|
7316
|
-
return {
|
|
7317
|
-
enabled: true,
|
|
7318
|
-
active: false,
|
|
7319
|
-
degraded: true,
|
|
7320
|
-
mode: "fallback",
|
|
7321
|
-
collection,
|
|
7322
|
-
collectionState: "unknown",
|
|
7323
|
-
installedVersion: null,
|
|
7324
|
-
supportedVersion: null,
|
|
7325
|
-
supported: null,
|
|
7326
|
-
upgradeAvailable: null,
|
|
7327
|
-
doctorAvailable: null,
|
|
7328
|
-
debugStatus: "backend=unavailable"
|
|
7329
|
-
};
|
|
7330
|
-
}
|
|
7331
|
-
const diagnosticAvailable = await this.qmdProbeAvailable(searchBackend, qmdEnabled);
|
|
7332
|
-
const operationalAvailable = diagnosticAvailable || qmd.isAvailable();
|
|
7333
|
-
const collectionState = diagnosticAvailable ? await this.qmdCollectionState(searchBackend, qmdEnabled, collection) : "unknown";
|
|
7334
|
-
const active = operationalAvailable && collectionState !== "missing";
|
|
7335
|
-
const degraded = searchBackend === "qmd" && qmdEnabled && (!active || !diagnosticAvailable || collectionState === "unknown");
|
|
7336
|
-
const debugStatus = qmd.debugStatus();
|
|
7337
|
-
const versionStatus = "getVersionStatus" in qmd && typeof qmd.getVersionStatus === "function" ? qmd.getVersionStatus() : null;
|
|
7338
|
-
const daemonMode = "isDaemonMode" in qmd && typeof qmd.isDaemonMode === "function" ? qmd.isDaemonMode() === true : false;
|
|
7339
|
-
const mode = searchBackend !== "qmd" ? "not-selected" : !qmdEnabled ? "disabled" : !active ? "fallback" : daemonMode ? "daemon" : "cli";
|
|
7340
|
-
return {
|
|
7341
|
-
enabled: qmdEnabled,
|
|
7342
|
-
active,
|
|
7343
|
-
degraded,
|
|
7344
|
-
mode,
|
|
7345
|
-
collection,
|
|
7346
|
-
collectionState,
|
|
7347
|
-
installedVersion: versionStatus?.installedVersion ?? null,
|
|
7348
|
-
supportedVersion: versionStatus?.supportedVersion ?? null,
|
|
7349
|
-
supported: versionStatus?.supported ?? null,
|
|
7350
|
-
upgradeAvailable: versionStatus?.upgradeAvailable ?? null,
|
|
7351
|
-
doctorAvailable: versionStatus?.capabilities?.doctor ?? null,
|
|
7352
|
-
debugStatus
|
|
7353
|
-
};
|
|
7615
|
+
return this.accessObserveWriteSurface.qmdHealth(
|
|
7616
|
+
searchBackend,
|
|
7617
|
+
qmdEnabled,
|
|
7618
|
+
namespace,
|
|
7619
|
+
collection
|
|
7620
|
+
);
|
|
7354
7621
|
}
|
|
7355
7622
|
async namespaceQmdHealth(searchBackend, qmdEnabled, namespace, fallbackCollection) {
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
try {
|
|
7363
|
-
const health = await searchHealthForNamespace.call(this.orchestrator, namespace, {
|
|
7364
|
-
signal: controller.signal
|
|
7365
|
-
});
|
|
7366
|
-
const active = health.available && health.collectionState !== "missing";
|
|
7367
|
-
const degraded = !active || health.collectionState === "unknown";
|
|
7368
|
-
const mode = !active ? "fallback" : health.daemonMode === true ? "daemon" : "cli";
|
|
7369
|
-
return {
|
|
7370
|
-
enabled: true,
|
|
7371
|
-
active,
|
|
7372
|
-
degraded,
|
|
7373
|
-
mode,
|
|
7374
|
-
collection: health.collection || fallbackCollection,
|
|
7375
|
-
collectionState: health.collectionState,
|
|
7376
|
-
installedVersion: health.installedVersion,
|
|
7377
|
-
supportedVersion: health.supportedVersion,
|
|
7378
|
-
supported: health.supported,
|
|
7379
|
-
upgradeAvailable: health.upgradeAvailable,
|
|
7380
|
-
doctorAvailable: health.doctorAvailable,
|
|
7381
|
-
debugStatus: health.debugStatus
|
|
7382
|
-
};
|
|
7383
|
-
} catch (error) {
|
|
7384
|
-
const detail = displayErrorDetail(error) || "unknown";
|
|
7385
|
-
return {
|
|
7386
|
-
enabled: true,
|
|
7387
|
-
active: false,
|
|
7388
|
-
degraded: true,
|
|
7389
|
-
mode: "fallback",
|
|
7390
|
-
collection: fallbackCollection,
|
|
7391
|
-
collectionState: "unknown",
|
|
7392
|
-
installedVersion: null,
|
|
7393
|
-
supportedVersion: null,
|
|
7394
|
-
supported: null,
|
|
7395
|
-
upgradeAvailable: null,
|
|
7396
|
-
doctorAvailable: null,
|
|
7397
|
-
debugStatus: `backend=namespace-unavailable error=${detail}`
|
|
7398
|
-
};
|
|
7399
|
-
} finally {
|
|
7400
|
-
clearTimeout(timer);
|
|
7401
|
-
}
|
|
7623
|
+
return this.accessObserveWriteSurface.namespaceQmdHealth(
|
|
7624
|
+
searchBackend,
|
|
7625
|
+
qmdEnabled,
|
|
7626
|
+
namespace,
|
|
7627
|
+
fallbackCollection
|
|
7628
|
+
);
|
|
7402
7629
|
}
|
|
7403
7630
|
async qmdCollectionState(searchBackend, qmdEnabled, collection) {
|
|
7404
7631
|
if (searchBackend !== "qmd" || !qmdEnabled) return "skipped";
|
|
@@ -7453,70 +7680,14 @@ var EngramAccessService = class {
|
|
|
7453
7680
|
if (memories.length === 0) {
|
|
7454
7681
|
return this.orchestrator.generateDaySummaryAuto(namespace, {
|
|
7455
7682
|
timeZone: request.timeZone
|
|
7456
|
-
});
|
|
7457
|
-
}
|
|
7458
|
-
return this.orchestrator.generateDaySummary(memories);
|
|
7459
|
-
}
|
|
7460
|
-
/**
|
|
7461
|
-
* Build a daily context briefing. Gracefully degrades when the OpenAI key
|
|
7462
|
-
* or Responses API is unavailable — never throws for LLM-related problems.
|
|
7463
|
-
*/
|
|
7464
|
-
async briefing(request) {
|
|
7465
|
-
const config = this.orchestrator.config;
|
|
7466
|
-
if (!config.briefing.enabled) {
|
|
7467
|
-
throw new EngramAccessInputError("briefing is disabled");
|
|
7468
|
-
}
|
|
7469
|
-
const namespace = this.resolveReadableNamespace(request.namespace, request.principal);
|
|
7470
|
-
const storage = await this.orchestrator.getStorage(namespace);
|
|
7471
|
-
const token = typeof request.since === "string" && request.since.trim().length > 0 ? request.since.trim() : config.briefing.defaultWindow;
|
|
7472
|
-
const window = parseBriefingWindow(token);
|
|
7473
|
-
if (!window) {
|
|
7474
|
-
throw new EngramAccessInputError(`invalid briefing window: ${token}`);
|
|
7475
|
-
}
|
|
7476
|
-
const rawFocus = typeof request.focus === "string" ? request.focus.trim() : "";
|
|
7477
|
-
let focus = null;
|
|
7478
|
-
if (rawFocus.length > 0) {
|
|
7479
|
-
focus = parseBriefingFocus(rawFocus);
|
|
7480
|
-
if (!focus) {
|
|
7481
|
-
throw new EngramAccessInputError(
|
|
7482
|
-
`invalid briefing focus filter: ${request.focus}`
|
|
7483
|
-
);
|
|
7484
|
-
}
|
|
7485
|
-
}
|
|
7486
|
-
const SUPPORTED_FORMATS = ["markdown", "json"];
|
|
7487
|
-
if (typeof request.format === "string" && !SUPPORTED_FORMATS.includes(request.format)) {
|
|
7488
|
-
throw new EngramAccessInputError(
|
|
7489
|
-
`unsupported briefing format: "${request.format}". Accepted: ${SUPPORTED_FORMATS.join(", ")}.`
|
|
7490
|
-
);
|
|
7683
|
+
});
|
|
7491
7684
|
}
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
window,
|
|
7499
|
-
focus,
|
|
7500
|
-
calendarSource,
|
|
7501
|
-
maxFollowups,
|
|
7502
|
-
allowLlm: config.briefing.llmFollowups,
|
|
7503
|
-
openaiApiKey: config.openaiApiKey,
|
|
7504
|
-
openaiBaseUrl: config.openaiBaseUrl,
|
|
7505
|
-
model: config.model,
|
|
7506
|
-
// Without a direct OpenAI key, route follow-ups through the configured
|
|
7507
|
-
// LLM chain (gateway model source or local LLM) — same fallback every
|
|
7508
|
-
// other LLM feature uses. A configured key keeps its precedence so
|
|
7509
|
-
// existing deployments are unchanged.
|
|
7510
|
-
followupGenerator: config.openaiApiKey ? void 0 : this.orchestrator.briefingChainFollowupGenerator
|
|
7511
|
-
});
|
|
7512
|
-
return {
|
|
7513
|
-
format,
|
|
7514
|
-
window: result.window,
|
|
7515
|
-
namespace,
|
|
7516
|
-
markdown: result.markdown,
|
|
7517
|
-
json: result.json,
|
|
7518
|
-
followupsUnavailableReason: result.followupsUnavailableReason
|
|
7519
|
-
};
|
|
7685
|
+
return this.orchestrator.generateDaySummary(memories);
|
|
7686
|
+
}
|
|
7687
|
+
async briefing(request) {
|
|
7688
|
+
return this.accessObserveWriteSurface.briefing(
|
|
7689
|
+
request
|
|
7690
|
+
);
|
|
7520
7691
|
}
|
|
7521
7692
|
/**
|
|
7522
7693
|
* Attach a coding context to a session (issue #569). Used by the Claude
|
|
@@ -7742,63 +7913,10 @@ var EngramAccessService = class {
|
|
|
7742
7913
|
// per-tenant) do not block each other.
|
|
7743
7914
|
xrayQueue = Promise.resolve();
|
|
7744
7915
|
async memoryStore(request, hooks) {
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
}
|
|
7750
|
-
const execute = async () => {
|
|
7751
|
-
const candidate = this.validateWriteCandidate(request, namespace);
|
|
7752
|
-
if (request.dryRun === true) {
|
|
7753
|
-
return {
|
|
7754
|
-
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
7755
|
-
operation: "memory_store",
|
|
7756
|
-
namespace,
|
|
7757
|
-
dryRun: true,
|
|
7758
|
-
accepted: true,
|
|
7759
|
-
queued: false,
|
|
7760
|
-
status: "validated",
|
|
7761
|
-
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
7762
|
-
};
|
|
7763
|
-
}
|
|
7764
|
-
const result = await persistExplicitCapture(this.orchestrator, candidate, "memory_store");
|
|
7765
|
-
await this.attachCodingContextAfterScopedWrite(request);
|
|
7766
|
-
const blocked = result.tombstoneBlocked === true;
|
|
7767
|
-
const response = {
|
|
7768
|
-
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
7769
|
-
operation: "memory_store",
|
|
7770
|
-
namespace,
|
|
7771
|
-
dryRun: false,
|
|
7772
|
-
accepted: true,
|
|
7773
|
-
queued: blocked,
|
|
7774
|
-
status: blocked ? "queued_for_review" : result.duplicateOf ? "duplicate" : "stored",
|
|
7775
|
-
memoryId: result.id,
|
|
7776
|
-
duplicateOf: result.duplicateOf,
|
|
7777
|
-
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
7778
|
-
};
|
|
7779
|
-
log.info(
|
|
7780
|
-
`access-write op=memory_store namespace=${namespace} dryRun=false status=${response.status} memoryId=${response.memoryId ?? "-"} idempotency=${response.idempotencyKey ? "yes" : "no"}`
|
|
7781
|
-
);
|
|
7782
|
-
return response;
|
|
7783
|
-
};
|
|
7784
|
-
return this.handleIdempotentWrite({
|
|
7785
|
-
operation: "memory_store",
|
|
7786
|
-
idempotencyKey: request.idempotencyKey,
|
|
7787
|
-
requestFingerprint: {
|
|
7788
|
-
schemaVersion,
|
|
7789
|
-
content: request.content,
|
|
7790
|
-
category: request.category,
|
|
7791
|
-
confidence: request.confidence,
|
|
7792
|
-
namespace,
|
|
7793
|
-
tags: request.tags,
|
|
7794
|
-
entityRef: request.entityRef,
|
|
7795
|
-
ttl: request.ttl,
|
|
7796
|
-
sourceReason: request.sourceReason
|
|
7797
|
-
},
|
|
7798
|
-
skip: request.dryRun === true,
|
|
7799
|
-
beforeExecute: hooks?.enforceWriteQuota,
|
|
7800
|
-
execute
|
|
7801
|
-
});
|
|
7916
|
+
return this.accessObserveWriteSurface.memoryStore(
|
|
7917
|
+
request,
|
|
7918
|
+
hooks
|
|
7919
|
+
);
|
|
7802
7920
|
}
|
|
7803
7921
|
async peekMemoryStoreIdempotency(request) {
|
|
7804
7922
|
const namespace = await this.resolveCodingScopedWriteNamespace(request);
|
|
@@ -7824,67 +7942,10 @@ var EngramAccessService = class {
|
|
|
7824
7942
|
});
|
|
7825
7943
|
}
|
|
7826
7944
|
async suggestionSubmit(request, hooks) {
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
}
|
|
7832
|
-
const execute = async () => {
|
|
7833
|
-
const candidate = this.validateWriteCandidate(request, namespace);
|
|
7834
|
-
if (request.dryRun === true) {
|
|
7835
|
-
return {
|
|
7836
|
-
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
7837
|
-
operation: "suggestion_submit",
|
|
7838
|
-
namespace,
|
|
7839
|
-
dryRun: true,
|
|
7840
|
-
accepted: true,
|
|
7841
|
-
queued: true,
|
|
7842
|
-
status: "validated",
|
|
7843
|
-
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
7844
|
-
};
|
|
7845
|
-
}
|
|
7846
|
-
const result = await queueExplicitCaptureForReview(
|
|
7847
|
-
this.orchestrator,
|
|
7848
|
-
candidate,
|
|
7849
|
-
"suggestion_submit",
|
|
7850
|
-
new Error(request.sourceReason?.trim() || "submitted via engram suggestion_submit")
|
|
7851
|
-
);
|
|
7852
|
-
await this.attachCodingContextAfterScopedWrite(request);
|
|
7853
|
-
const response = {
|
|
7854
|
-
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
7855
|
-
operation: "suggestion_submit",
|
|
7856
|
-
namespace,
|
|
7857
|
-
dryRun: false,
|
|
7858
|
-
accepted: true,
|
|
7859
|
-
queued: true,
|
|
7860
|
-
status: "queued_for_review",
|
|
7861
|
-
memoryId: result.id,
|
|
7862
|
-
duplicateOf: result.duplicateOf,
|
|
7863
|
-
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
7864
|
-
};
|
|
7865
|
-
log.info(
|
|
7866
|
-
`access-write op=suggestion_submit namespace=${namespace} dryRun=false status=${response.status} memoryId=${response.memoryId ?? "-"} idempotency=${response.idempotencyKey ? "yes" : "no"}`
|
|
7867
|
-
);
|
|
7868
|
-
return response;
|
|
7869
|
-
};
|
|
7870
|
-
return this.handleIdempotentWrite({
|
|
7871
|
-
operation: "suggestion_submit",
|
|
7872
|
-
idempotencyKey: request.idempotencyKey,
|
|
7873
|
-
requestFingerprint: {
|
|
7874
|
-
schemaVersion,
|
|
7875
|
-
content: request.content,
|
|
7876
|
-
category: request.category,
|
|
7877
|
-
confidence: request.confidence,
|
|
7878
|
-
namespace,
|
|
7879
|
-
tags: request.tags,
|
|
7880
|
-
entityRef: request.entityRef,
|
|
7881
|
-
ttl: request.ttl,
|
|
7882
|
-
sourceReason: request.sourceReason
|
|
7883
|
-
},
|
|
7884
|
-
skip: request.dryRun === true,
|
|
7885
|
-
beforeExecute: hooks?.enforceWriteQuota,
|
|
7886
|
-
execute
|
|
7887
|
-
});
|
|
7945
|
+
return this.accessObserveWriteSurface.suggestionSubmit(
|
|
7946
|
+
request,
|
|
7947
|
+
hooks
|
|
7948
|
+
);
|
|
7888
7949
|
}
|
|
7889
7950
|
async peekSuggestionSubmitIdempotency(request) {
|
|
7890
7951
|
const namespace = await this.resolveCodingScopedWriteNamespace(request);
|
|
@@ -8698,145 +8759,34 @@ var EngramAccessService = class {
|
|
|
8698
8759
|
}
|
|
8699
8760
|
serializeMemorySummary(memory, baseDir, disclosure, rawExcerpts) {
|
|
8700
8761
|
return shapeMemorySummary(memory, baseDir, disclosure, rawExcerpts);
|
|
8701
|
-
}
|
|
8702
|
-
async observe(request, hooks) {
|
|
8703
|
-
const idempotencyKey = request.idempotencyKey?.trim();
|
|
8704
|
-
let effectiveCodingContext;
|
|
8705
|
-
if (idempotencyKey && !(typeof request.namespace === "string" && request.namespace.trim().length > 0)) {
|
|
8706
|
-
effectiveCodingContext = (typeof this.orchestrator.getCodingContextForSession === "function" ? this.orchestrator.getCodingContextForSession(request.sessionKey) : null) ?? await this.resolveCodingContextFromOptions(request);
|
|
8707
|
-
}
|
|
8708
|
-
return this.handleIdempotentWrite({
|
|
8709
|
-
operation: "observe",
|
|
8710
|
-
idempotencyKey: request.idempotencyKey,
|
|
8711
|
-
requestFingerprint: {
|
|
8712
|
-
sessionKey: request.sessionKey,
|
|
8713
|
-
messages: request.messages,
|
|
8714
|
-
namespace: request.namespace,
|
|
8715
|
-
skipExtraction: request.skipExtraction,
|
|
8716
|
-
authenticatedPrincipal: request.authenticatedPrincipal,
|
|
8717
|
-
cwd: request.cwd,
|
|
8718
|
-
projectTag: request.projectTag,
|
|
8719
|
-
effectiveCodingContext: effectiveCodingContext ?? null
|
|
8720
|
-
},
|
|
8721
|
-
beforeExecute: hooks?.enforceWriteQuota,
|
|
8722
|
-
execute: () => this.runObserve(request)
|
|
8723
|
-
});
|
|
8724
|
-
}
|
|
8725
|
-
async runObserve(request) {
|
|
8726
|
-
if (!request.sessionKey || typeof request.sessionKey !== "string" || request.sessionKey.trim().length === 0) {
|
|
8727
|
-
throw new EngramAccessInputError("sessionKey is required and must be a non-empty string");
|
|
8728
|
-
}
|
|
8729
|
-
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
8730
|
-
throw new EngramAccessInputError("messages is required and must be a non-empty array");
|
|
8731
|
-
}
|
|
8732
|
-
for (const msg of request.messages) {
|
|
8733
|
-
if (!msg || typeof msg !== "object" || typeof msg.role !== "string" || typeof msg.content !== "string") {
|
|
8734
|
-
throw new EngramAccessInputError("each message must have a string 'role' and 'content'");
|
|
8735
|
-
}
|
|
8736
|
-
if (msg.role !== "user" && msg.role !== "assistant") {
|
|
8737
|
-
throw new EngramAccessInputError(`invalid message role: ${msg.role} (expected 'user' or 'assistant')`);
|
|
8738
|
-
}
|
|
8739
|
-
}
|
|
8740
|
-
const scope = await this.resolveMemoryScopePlan(request);
|
|
8741
|
-
const writeNamespace = scope.writeNamespace;
|
|
8742
|
-
const namespace = this.legacyResponseNamespaceForScope(scope);
|
|
8743
|
-
const shouldWriteObjectiveState = resolveObjectiveStateCapabilities(this.orchestrator.config).objectiveStateMemory === true && resolveObjectiveStateCapabilities(this.orchestrator.config).objectiveStateSnapshotWrites === true;
|
|
8744
|
-
await this.maybeAttachCodingContext(request.sessionKey, {
|
|
8745
|
-
cwd: request.cwd,
|
|
8746
|
-
projectTag: request.projectTag
|
|
8747
|
-
});
|
|
8748
|
-
const lcmSessionKey = lcmSessionKeyForNamespace(
|
|
8749
|
-
writeNamespace,
|
|
8750
|
-
request.sessionKey,
|
|
8751
|
-
this.orchestrator.config.defaultNamespace
|
|
8752
|
-
) ?? request.sessionKey;
|
|
8753
|
-
if (shouldWriteObjectiveState) {
|
|
8754
|
-
try {
|
|
8755
|
-
const objectiveStateLocation = await this.objectiveStateStoreLocationForNamespace(
|
|
8756
|
-
scope.objectiveStateNamespace
|
|
8757
|
-
);
|
|
8758
|
-
await recordObjectiveStateSnapshotsFromObservedMessages({
|
|
8759
|
-
memoryDir: objectiveStateLocation.memoryDir,
|
|
8760
|
-
objectiveStateStoreDir: objectiveStateLocation.objectiveStateStoreDir,
|
|
8761
|
-
objectiveStateMemoryEnabled: resolveObjectiveStateCapabilities(this.orchestrator.config).objectiveStateMemory,
|
|
8762
|
-
objectiveStateSnapshotWritesEnabled: resolveObjectiveStateCapabilities(this.orchestrator.config).objectiveStateSnapshotWrites,
|
|
8763
|
-
sessionKey: request.sessionKey,
|
|
8764
|
-
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8765
|
-
messages: request.messages
|
|
8766
|
-
});
|
|
8767
|
-
} catch (err) {
|
|
8768
|
-
log.error(`access-observe objective-state snapshot write failed: ${err}`);
|
|
8769
|
-
}
|
|
8770
|
-
}
|
|
8771
|
-
let lcmArchived = false;
|
|
8772
|
-
if (this.orchestrator.lcmEngine && this.orchestrator.lcmEngine.enabled) {
|
|
8773
|
-
try {
|
|
8774
|
-
this.orchestrator.lcmEngine.enqueueObserveMessages(lcmSessionKey, request.messages);
|
|
8775
|
-
lcmArchived = true;
|
|
8776
|
-
} catch (err) {
|
|
8777
|
-
log.error(`access-observe LCM enqueue failed: ${err}`);
|
|
8778
|
-
}
|
|
8779
|
-
}
|
|
8780
|
-
let extractionQueued = false;
|
|
8781
|
-
if (request.skipExtraction !== true) {
|
|
8782
|
-
const turns = request.messages.map((m) => ({
|
|
8783
|
-
source: "openclaw",
|
|
8784
|
-
// Identity-vs-routing separation (#1505 thread 1, cursor): extraction
|
|
8785
|
-
// derives the provenance principal via `resolvePrincipal(turn.sessionKey)`
|
|
8786
|
-
// and threads `turn.sessionKey` into conversation threading. Feeding the
|
|
8787
|
-
// namespace-PREFIXED `lcmSessionKey` here mis-derived the principal to
|
|
8788
|
-
// `default` (a `<ns>:<key>` string matches no prefix/map rule and fails
|
|
8789
|
-
// the `agent:` heuristic). Pass the ORIGINAL sessionKey so identity is
|
|
8790
|
-
// correct; storage routing is pinned separately via
|
|
8791
|
-
// writeNamespaceOverride below, and the authenticated principal is pinned
|
|
8792
|
-
// via principalOverride.
|
|
8793
|
-
sessionKey: request.sessionKey,
|
|
8794
|
-
role: m.role,
|
|
8795
|
-
content: m.content,
|
|
8796
|
-
parts: m.parts,
|
|
8797
|
-
rawContent: m.rawContent,
|
|
8798
|
-
sourceFormat: m.sourceFormat,
|
|
8799
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8800
|
-
}));
|
|
8801
|
-
const writeNamespaceOverride = resolveNamespaceCapabilities(this.orchestrator.config).namespaces === true ? writeNamespace : void 0;
|
|
8802
|
-
const principalOverride = typeof scope.principal === "string" && scope.principal.length > 0 ? scope.principal : void 0;
|
|
8803
|
-
try {
|
|
8804
|
-
const extractionPromise = this.orchestrator.ingestReplayBatch(turns, {
|
|
8805
|
-
archiveLcm: false,
|
|
8806
|
-
writeNamespaceOverride,
|
|
8807
|
-
principalOverride
|
|
8808
|
-
});
|
|
8809
|
-
extractionPromise.catch((err) => {
|
|
8810
|
-
log.error(`access-observe background extraction failed: ${err}`);
|
|
8811
|
-
});
|
|
8812
|
-
extractionQueued = true;
|
|
8813
|
-
} catch (err) {
|
|
8814
|
-
log.error(`access-observe extraction enqueue failed: ${err}`);
|
|
8815
|
-
}
|
|
8762
|
+
}
|
|
8763
|
+
async observe(request, hooks) {
|
|
8764
|
+
const idempotencyKey = request.idempotencyKey?.trim();
|
|
8765
|
+
let effectiveCodingContext;
|
|
8766
|
+
if (idempotencyKey && !(typeof request.namespace === "string" && request.namespace.trim().length > 0)) {
|
|
8767
|
+
effectiveCodingContext = (typeof this.orchestrator.getCodingContextForSession === "function" ? this.orchestrator.getCodingContextForSession(request.sessionKey) : null) ?? await this.resolveCodingContextFromOptions(request);
|
|
8816
8768
|
}
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
writeNamespace: scope.writeNamespace,
|
|
8830
|
-
codingOverlayApplied: scope.codingOverlayApplied,
|
|
8831
|
-
readNamespaces: scope.readNamespaces,
|
|
8832
|
-
scopeProfile: scope.scopeProfile,
|
|
8833
|
-
writeLayer: scope.writeLayer,
|
|
8834
|
-
layers: scope.layers,
|
|
8835
|
-
promotionTargets: scope.promotionTargets
|
|
8769
|
+
return this.handleIdempotentWrite({
|
|
8770
|
+
operation: "observe",
|
|
8771
|
+
idempotencyKey: request.idempotencyKey,
|
|
8772
|
+
requestFingerprint: {
|
|
8773
|
+
sessionKey: request.sessionKey,
|
|
8774
|
+
messages: request.messages,
|
|
8775
|
+
namespace: request.namespace,
|
|
8776
|
+
skipExtraction: request.skipExtraction,
|
|
8777
|
+
authenticatedPrincipal: request.authenticatedPrincipal,
|
|
8778
|
+
cwd: request.cwd,
|
|
8779
|
+
projectTag: request.projectTag,
|
|
8780
|
+
effectiveCodingContext: effectiveCodingContext ?? null
|
|
8836
8781
|
},
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
};
|
|
8782
|
+
beforeExecute: hooks?.enforceWriteQuota,
|
|
8783
|
+
execute: () => this.runObserve(request)
|
|
8784
|
+
});
|
|
8785
|
+
}
|
|
8786
|
+
async runObserve(request) {
|
|
8787
|
+
return this.accessObserveWriteSurface.runObserve(
|
|
8788
|
+
request
|
|
8789
|
+
);
|
|
8840
8790
|
}
|
|
8841
8791
|
async lcmSearch(request) {
|
|
8842
8792
|
return this.accessLcmSurface.lcmSearch(
|
|
@@ -9233,137 +9183,19 @@ var EngramAccessService = class {
|
|
|
9233
9183
|
}
|
|
9234
9184
|
// ── Work Layer ──────────────────────────────────────────────────────────
|
|
9235
9185
|
async workTask(request) {
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
const asPriority = (v) => v && PRIORITIES.has(v) ? v : void 0;
|
|
9240
|
-
const storage = new WorkStorage(this.orchestrator.config.memoryDir);
|
|
9241
|
-
await storage.ensureDirectories();
|
|
9242
|
-
const action = request.action;
|
|
9243
|
-
if (action === "create") {
|
|
9244
|
-
if (!request.title?.trim()) throw new EngramAccessInputError("title is required for create");
|
|
9245
|
-
const task = await storage.createTask({
|
|
9246
|
-
title: request.title,
|
|
9247
|
-
description: request.description,
|
|
9248
|
-
status: asStatus(request.status),
|
|
9249
|
-
priority: asPriority(request.priority),
|
|
9250
|
-
owner: request.owner?.trim() || void 0,
|
|
9251
|
-
assignee: request.assignee?.trim() || void 0,
|
|
9252
|
-
projectId: request.projectId?.trim() || void 0,
|
|
9253
|
-
tags: request.tags,
|
|
9254
|
-
dueAt: request.dueAt?.trim() || void 0
|
|
9255
|
-
});
|
|
9256
|
-
return { action, task };
|
|
9257
|
-
}
|
|
9258
|
-
if (action === "get") {
|
|
9259
|
-
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for get");
|
|
9260
|
-
return { action, task: await storage.getTask(request.id) };
|
|
9261
|
-
}
|
|
9262
|
-
if (action === "list") {
|
|
9263
|
-
const tasks = await storage.listTasks({
|
|
9264
|
-
status: asStatus(request.status),
|
|
9265
|
-
owner: request.owner?.trim() || void 0,
|
|
9266
|
-
assignee: request.assignee?.trim() || void 0,
|
|
9267
|
-
projectId: request.projectId?.trim() || void 0
|
|
9268
|
-
});
|
|
9269
|
-
return { action, count: tasks.length, tasks };
|
|
9270
|
-
}
|
|
9271
|
-
if (action === "update") {
|
|
9272
|
-
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for update");
|
|
9273
|
-
const patch = {};
|
|
9274
|
-
if (request.title !== void 0) patch.title = request.title;
|
|
9275
|
-
if (request.description !== void 0) patch.description = request.description;
|
|
9276
|
-
const st = asStatus(request.status);
|
|
9277
|
-
if (st) patch.status = st;
|
|
9278
|
-
const pr = asPriority(request.priority);
|
|
9279
|
-
if (pr) patch.priority = pr;
|
|
9280
|
-
if (request.owner !== void 0) patch.owner = request.owner || null;
|
|
9281
|
-
if (request.assignee !== void 0) patch.assignee = request.assignee || null;
|
|
9282
|
-
if (request.projectId !== void 0) patch.projectId = request.projectId || null;
|
|
9283
|
-
if (request.tags) patch.tags = request.tags;
|
|
9284
|
-
if (request.dueAt !== void 0) patch.dueAt = request.dueAt || null;
|
|
9285
|
-
return { action, task: await storage.updateTask(request.id, patch) };
|
|
9286
|
-
}
|
|
9287
|
-
if (action === "transition") {
|
|
9288
|
-
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for transition");
|
|
9289
|
-
const st = asStatus(request.status);
|
|
9290
|
-
if (!st) throw new EngramAccessInputError("valid status is required for transition");
|
|
9291
|
-
return { action, task: await storage.transitionTask(request.id, st) };
|
|
9292
|
-
}
|
|
9293
|
-
if (action === "delete") {
|
|
9294
|
-
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for delete");
|
|
9295
|
-
return { action, deleted: await storage.deleteTask(request.id) };
|
|
9296
|
-
}
|
|
9297
|
-
throw new EngramAccessInputError(`Unsupported work_task action: ${action}`);
|
|
9186
|
+
return this.accessObserveWriteSurface.workTask(
|
|
9187
|
+
request
|
|
9188
|
+
);
|
|
9298
9189
|
}
|
|
9299
9190
|
async workProject(request) {
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
await storage.ensureDirectories();
|
|
9304
|
-
const action = request.action;
|
|
9305
|
-
if (action === "create") {
|
|
9306
|
-
if (!request.name?.trim()) throw new EngramAccessInputError("name is required for create");
|
|
9307
|
-
const project = await storage.createProject({
|
|
9308
|
-
name: request.name,
|
|
9309
|
-
description: request.description,
|
|
9310
|
-
status: asStatus(request.status),
|
|
9311
|
-
owner: request.owner?.trim() || void 0,
|
|
9312
|
-
tags: request.tags
|
|
9313
|
-
});
|
|
9314
|
-
return { action, project };
|
|
9315
|
-
}
|
|
9316
|
-
if (action === "get") {
|
|
9317
|
-
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for get");
|
|
9318
|
-
return { action, project: await storage.getProject(request.id) };
|
|
9319
|
-
}
|
|
9320
|
-
if (action === "list") {
|
|
9321
|
-
const projects = await storage.listProjects();
|
|
9322
|
-
return { action, count: projects.length, projects };
|
|
9323
|
-
}
|
|
9324
|
-
if (action === "update") {
|
|
9325
|
-
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for update");
|
|
9326
|
-
const patch = {};
|
|
9327
|
-
if (request.name !== void 0) patch.name = request.name;
|
|
9328
|
-
if (request.description !== void 0) patch.description = request.description;
|
|
9329
|
-
const st = asStatus(request.status);
|
|
9330
|
-
if (st) patch.status = st;
|
|
9331
|
-
if (request.owner !== void 0) patch.owner = request.owner || null;
|
|
9332
|
-
if (request.tags) patch.tags = request.tags;
|
|
9333
|
-
return { action, project: await storage.updateProject(request.id, patch) };
|
|
9334
|
-
}
|
|
9335
|
-
if (action === "delete") {
|
|
9336
|
-
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for delete");
|
|
9337
|
-
return { action, deleted: await storage.deleteProject(request.id) };
|
|
9338
|
-
}
|
|
9339
|
-
if (action === "link_task") {
|
|
9340
|
-
if (!request.taskId?.trim() || !request.projectId?.trim()) {
|
|
9341
|
-
throw new EngramAccessInputError("taskId and projectId are required for link_task");
|
|
9342
|
-
}
|
|
9343
|
-
return { action, linked: await storage.linkTaskToProject(request.taskId, request.projectId) };
|
|
9344
|
-
}
|
|
9345
|
-
throw new EngramAccessInputError(`Unsupported work_project action: ${action}`);
|
|
9191
|
+
return this.accessObserveWriteSurface.workProject(
|
|
9192
|
+
request
|
|
9193
|
+
);
|
|
9346
9194
|
}
|
|
9347
9195
|
async workBoard(request) {
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
const projectId = request.projectId?.trim() || void 0;
|
|
9352
|
-
if (action === "export_markdown") {
|
|
9353
|
-
const markdown = await exportWorkBoardMarkdown({ memoryDir, projectId });
|
|
9354
|
-
return { action, markdown: wrapWorkLayerContext(markdown, { linkToMemory: request.linkToMemory === true }) };
|
|
9355
|
-
}
|
|
9356
|
-
if (action === "export_snapshot") {
|
|
9357
|
-
const snapshot = await exportWorkBoardSnapshot({ memoryDir, projectId });
|
|
9358
|
-
return { action, snapshot };
|
|
9359
|
-
}
|
|
9360
|
-
if (action === "import_snapshot") {
|
|
9361
|
-
if (!request.snapshotJson?.trim()) throw new EngramAccessInputError("snapshotJson is required for import_snapshot");
|
|
9362
|
-
const snapshot = JSON.parse(request.snapshotJson);
|
|
9363
|
-
const result = await importWorkBoardSnapshot({ memoryDir, snapshot, projectId });
|
|
9364
|
-
return { action, result };
|
|
9365
|
-
}
|
|
9366
|
-
throw new EngramAccessInputError(`Unsupported work_board action: ${action}`);
|
|
9196
|
+
return this.accessObserveWriteSurface.workBoard(
|
|
9197
|
+
request
|
|
9198
|
+
);
|
|
9367
9199
|
}
|
|
9368
9200
|
// ── Shared Context / Compounding ────────────────────────────────────────
|
|
9369
9201
|
async sharedContextWriteOutput(request) {
|
|
@@ -9647,90 +9479,35 @@ ${next}`);
|
|
|
9647
9479
|
*/
|
|
9648
9480
|
async memoryOutcome(request) {
|
|
9649
9481
|
if (request.memoryId.includes("/") || request.memoryId.includes("\\")) {
|
|
9650
|
-
throw new EngramAccessInputError(
|
|
9651
|
-
"memoryId must not contain path separators"
|
|
9652
|
-
);
|
|
9653
|
-
}
|
|
9654
|
-
const resolvedNs = this.writableNamespaceFor(
|
|
9655
|
-
request.namespace,
|
|
9656
|
-
request.sessionKey,
|
|
9657
|
-
request.principal
|
|
9658
|
-
);
|
|
9659
|
-
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
9660
|
-
return recordMemoryOutcome(storage, {
|
|
9661
|
-
memoryPath: `${request.memoryId}.md`,
|
|
9662
|
-
outcome: request.outcome,
|
|
9663
|
-
timestamp: request.timestamp
|
|
9664
|
-
});
|
|
9665
|
-
}
|
|
9666
|
-
async memoryPromote(request) {
|
|
9667
|
-
const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
|
|
9668
|
-
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
9669
|
-
await storage.updateMemoryFrontmatter(request.memoryId, {
|
|
9670
|
-
lifecycleState: "active",
|
|
9671
|
-
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
9672
|
-
});
|
|
9673
|
-
return { promoted: true, memoryId: request.memoryId };
|
|
9674
|
-
}
|
|
9675
|
-
async memoryActionApply(request) {
|
|
9676
|
-
const actionTypes = /* @__PURE__ */ new Set([
|
|
9677
|
-
"store_episode",
|
|
9678
|
-
"store_note",
|
|
9679
|
-
"update_note",
|
|
9680
|
-
"create_artifact",
|
|
9681
|
-
"summarize_node",
|
|
9682
|
-
"discard",
|
|
9683
|
-
"link_graph"
|
|
9684
|
-
]);
|
|
9685
|
-
if (!actionTypes.has(request.action)) {
|
|
9686
|
-
throw new EngramAccessInputError(
|
|
9687
|
-
`memory_action_apply: invalid action ${JSON.stringify(request.action)}`
|
|
9688
|
-
);
|
|
9689
|
-
}
|
|
9690
|
-
if (resolveCompressionCapabilities(this.orchestrator.config).contextCompressionActions !== true) {
|
|
9691
|
-
throw new EngramAccessInputError(
|
|
9692
|
-
"memory_action_apply is disabled; enable contextCompressionActionsEnabled to use this tool"
|
|
9693
|
-
);
|
|
9694
|
-
}
|
|
9695
|
-
const outcome = request.outcome ?? "skipped";
|
|
9696
|
-
if (outcome !== "applied" && outcome !== "skipped" && outcome !== "failed") {
|
|
9697
|
-
throw new EngramAccessInputError(
|
|
9698
|
-
`memory_action_apply: outcome must be "applied", "skipped", or "failed"; got ${JSON.stringify(outcome)}`
|
|
9699
|
-
);
|
|
9700
|
-
}
|
|
9701
|
-
const resolvedNs = this.writableNamespaceFor(
|
|
9702
|
-
request.namespace,
|
|
9703
|
-
request.sessionKey,
|
|
9704
|
-
request.principal
|
|
9705
|
-
);
|
|
9706
|
-
const inputSummaryParts = [
|
|
9707
|
-
request.content,
|
|
9708
|
-
request.category ? `category=${request.category}` : void 0,
|
|
9709
|
-
request.linkTargetId ? `linkTargetId=${request.linkTargetId}` : void 0,
|
|
9710
|
-
request.linkType ? `linkType=${request.linkType}` : void 0,
|
|
9711
|
-
typeof request.linkStrength === "number" ? `linkStrength=${request.linkStrength}` : void 0,
|
|
9712
|
-
request.artifactType ? `artifactType=${request.artifactType}` : void 0,
|
|
9713
|
-
typeof request.execute === "boolean" ? `execute=${request.execute}` : void 0
|
|
9714
|
-
].filter((part) => typeof part === "string" && part.length > 0);
|
|
9715
|
-
const event = {
|
|
9716
|
-
action: request.action,
|
|
9717
|
-
outcome,
|
|
9718
|
-
namespace: resolvedNs,
|
|
9719
|
-
actor: "access.memory_action_apply",
|
|
9720
|
-
subsystem: "access.memory_action_apply",
|
|
9721
|
-
reason: request.reason,
|
|
9722
|
-
memoryId: request.memoryId,
|
|
9723
|
-
sourceSessionKey: request.sessionKey,
|
|
9724
|
-
inputSummary: inputSummaryParts.length > 0 ? inputSummaryParts.join(" | ").slice(0, 500) : void 0,
|
|
9725
|
-
dryRun: request.dryRun === true,
|
|
9726
|
-
promptHash: typeof request.sourcePrompt === "string" && request.sourcePrompt.length > 0 ? createHash5("sha256").update(request.sourcePrompt).digest("hex") : void 0
|
|
9727
|
-
};
|
|
9728
|
-
const preview = this.orchestrator.previewMemoryActionEvent(event);
|
|
9729
|
-
if (request.dryRun === true) {
|
|
9730
|
-
return { recorded: false, dryRun: true, event: preview };
|
|
9482
|
+
throw new EngramAccessInputError(
|
|
9483
|
+
"memoryId must not contain path separators"
|
|
9484
|
+
);
|
|
9731
9485
|
}
|
|
9732
|
-
const
|
|
9733
|
-
|
|
9486
|
+
const resolvedNs = this.writableNamespaceFor(
|
|
9487
|
+
request.namespace,
|
|
9488
|
+
request.sessionKey,
|
|
9489
|
+
request.principal
|
|
9490
|
+
);
|
|
9491
|
+
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
9492
|
+
return recordMemoryOutcome(storage, {
|
|
9493
|
+
memoryPath: `${request.memoryId}.md`,
|
|
9494
|
+
outcome: request.outcome,
|
|
9495
|
+
timestamp: request.timestamp
|
|
9496
|
+
});
|
|
9497
|
+
}
|
|
9498
|
+
async memoryPromote(request) {
|
|
9499
|
+
const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
|
|
9500
|
+
const storage = await this.orchestrator.getStorage(resolvedNs);
|
|
9501
|
+
await storage.updateMemoryFrontmatter(request.memoryId, {
|
|
9502
|
+
lifecycleState: "active",
|
|
9503
|
+
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
9504
|
+
});
|
|
9505
|
+
return { promoted: true, memoryId: request.memoryId };
|
|
9506
|
+
}
|
|
9507
|
+
async memoryActionApply(request) {
|
|
9508
|
+
return this.accessObserveWriteSurface.memoryActionApply(
|
|
9509
|
+
request
|
|
9510
|
+
);
|
|
9734
9511
|
}
|
|
9735
9512
|
async contextCheckpoint(request) {
|
|
9736
9513
|
const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
|
|
@@ -10347,397 +10124,699 @@ ${next}`);
|
|
|
10347
10124
|
limit: request.limit
|
|
10348
10125
|
});
|
|
10349
10126
|
}
|
|
10350
|
-
async wearablesTranscriptMemories(request) {
|
|
10351
|
-
return this.orchestrator.getWearablesService().transcriptMemories({
|
|
10352
|
-
source: request.source,
|
|
10353
|
-
date: request.date,
|
|
10354
|
-
limit: request.limit
|
|
10127
|
+
async wearablesTranscriptMemories(request) {
|
|
10128
|
+
return this.orchestrator.getWearablesService().transcriptMemories({
|
|
10129
|
+
source: request.source,
|
|
10130
|
+
date: request.date,
|
|
10131
|
+
limit: request.limit
|
|
10132
|
+
});
|
|
10133
|
+
}
|
|
10134
|
+
// ── Admin console surfaces (issue #1502) ────────────────────────────────
|
|
10135
|
+
//
|
|
10136
|
+
// Thin delegators. Every method threads `this.orchestrator` references
|
|
10137
|
+
// into the pure `admin/admin-surfaces.ts` functions so the dashboard
|
|
10138
|
+
// never re-resolves scope, re-derives promotion targets, or re-lists
|
|
10139
|
+
// namespaces.
|
|
10140
|
+
/**
|
|
10141
|
+
* Resolve an effective scope inspection for the console. Inputs are
|
|
10142
|
+
* redacted before return so an operator-supplied principal preview /
|
|
10143
|
+
* namespace override never echoes a pasted credential back.
|
|
10144
|
+
*/
|
|
10145
|
+
async adminInspectScope(options) {
|
|
10146
|
+
const config = this.orchestrator.config;
|
|
10147
|
+
const codingContext = options.sessionKey ? this.orchestrator.getCodingContextForSession(options.sessionKey) ?? null : null;
|
|
10148
|
+
const inspection = inspectScope({
|
|
10149
|
+
config,
|
|
10150
|
+
sessionKey: options.sessionKey,
|
|
10151
|
+
namespace: options.namespace,
|
|
10152
|
+
principalOverride: options.principalOverride,
|
|
10153
|
+
codingContext,
|
|
10154
|
+
operation: options.operation
|
|
10155
|
+
});
|
|
10156
|
+
return redactSensitive(inspection);
|
|
10157
|
+
}
|
|
10158
|
+
/**
|
|
10159
|
+
* List configured + discovered namespaces from the catalog with the
|
|
10160
|
+
* admin filters the console exposes.
|
|
10161
|
+
*/
|
|
10162
|
+
async adminListNamespaces(filter) {
|
|
10163
|
+
const result = await listAdminNamespaces({
|
|
10164
|
+
catalog: this.orchestrator.namespaceCatalog,
|
|
10165
|
+
filter
|
|
10166
|
+
});
|
|
10167
|
+
return redactSensitive(result);
|
|
10168
|
+
}
|
|
10169
|
+
/**
|
|
10170
|
+
* Per-namespace maintenance + QMD health. QMD diagnostics are probed via
|
|
10171
|
+
* the orchestrator's search health accessor (one probe per namespace).
|
|
10172
|
+
*/
|
|
10173
|
+
async adminMaintenanceHealth() {
|
|
10174
|
+
const report = await gatherMaintenanceHealth({
|
|
10175
|
+
catalog: this.orchestrator.namespaceCatalog,
|
|
10176
|
+
qmdHealthProvider: async (namespace) => {
|
|
10177
|
+
try {
|
|
10178
|
+
const health = await this.orchestrator.searchHealthForNamespace(namespace);
|
|
10179
|
+
return {
|
|
10180
|
+
namespace,
|
|
10181
|
+
collection: health.collection,
|
|
10182
|
+
available: health.available,
|
|
10183
|
+
collectionState: health.collectionState,
|
|
10184
|
+
debugStatus: health.debugStatus,
|
|
10185
|
+
installedVersion: health.installedVersion,
|
|
10186
|
+
supportedVersion: health.supportedVersion,
|
|
10187
|
+
supported: health.supported,
|
|
10188
|
+
upgradeAvailable: health.upgradeAvailable,
|
|
10189
|
+
daemonMode: health.daemonMode
|
|
10190
|
+
};
|
|
10191
|
+
} catch (err) {
|
|
10192
|
+
throw err;
|
|
10193
|
+
}
|
|
10194
|
+
}
|
|
10195
|
+
});
|
|
10196
|
+
return redactSensitive(report);
|
|
10197
|
+
}
|
|
10198
|
+
/**
|
|
10199
|
+
* Dry-run transcript/session audit. Detects mixed `other/default` data
|
|
10200
|
+
* and legacy stranded directories. Never applies a migration.
|
|
10201
|
+
*/
|
|
10202
|
+
async adminTranscriptAudit() {
|
|
10203
|
+
const result = await auditTranscripts(this.orchestrator.config.memoryDir);
|
|
10204
|
+
return redactSensitive(result);
|
|
10205
|
+
}
|
|
10206
|
+
async adminPromoteMemory(request) {
|
|
10207
|
+
return this.accessAdminOpsSurface.adminPromoteMemory(
|
|
10208
|
+
request
|
|
10209
|
+
);
|
|
10210
|
+
}
|
|
10211
|
+
};
|
|
10212
|
+
|
|
10213
|
+
// src/access-observe-write-surface.ts
|
|
10214
|
+
var AccessObserveWriteSurface = class {
|
|
10215
|
+
constructor(deps) {
|
|
10216
|
+
this.deps = deps;
|
|
10217
|
+
}
|
|
10218
|
+
deps;
|
|
10219
|
+
async qmdHealth(searchBackend, qmdEnabled, namespace, collection) {
|
|
10220
|
+
if (searchBackend !== "qmd" || !qmdEnabled) {
|
|
10221
|
+
return {
|
|
10222
|
+
enabled: qmdEnabled,
|
|
10223
|
+
active: false,
|
|
10224
|
+
degraded: false,
|
|
10225
|
+
mode: searchBackend !== "qmd" ? "not-selected" : "disabled",
|
|
10226
|
+
collection,
|
|
10227
|
+
collectionState: "skipped",
|
|
10228
|
+
installedVersion: null,
|
|
10229
|
+
supportedVersion: null,
|
|
10230
|
+
supported: null,
|
|
10231
|
+
upgradeAvailable: null,
|
|
10232
|
+
doctorAvailable: null,
|
|
10233
|
+
debugStatus: searchBackend !== "qmd" ? `backend=${searchBackend}` : "backend=disabled"
|
|
10234
|
+
};
|
|
10235
|
+
}
|
|
10236
|
+
if (resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces === true) {
|
|
10237
|
+
const namespaceHealth = await this.deps.namespaceQmdHealth(searchBackend, qmdEnabled, namespace, collection);
|
|
10238
|
+
if (namespaceHealth) return namespaceHealth;
|
|
10239
|
+
}
|
|
10240
|
+
const qmd = this.deps.orchestrator.qmd;
|
|
10241
|
+
if (!qmd) {
|
|
10242
|
+
return {
|
|
10243
|
+
enabled: true,
|
|
10244
|
+
active: false,
|
|
10245
|
+
degraded: true,
|
|
10246
|
+
mode: "fallback",
|
|
10247
|
+
collection,
|
|
10248
|
+
collectionState: "unknown",
|
|
10249
|
+
installedVersion: null,
|
|
10250
|
+
supportedVersion: null,
|
|
10251
|
+
supported: null,
|
|
10252
|
+
upgradeAvailable: null,
|
|
10253
|
+
doctorAvailable: null,
|
|
10254
|
+
debugStatus: "backend=unavailable"
|
|
10255
|
+
};
|
|
10256
|
+
}
|
|
10257
|
+
const diagnosticAvailable = await this.deps.qmdProbeAvailable(searchBackend, qmdEnabled);
|
|
10258
|
+
const operationalAvailable = diagnosticAvailable || qmd.isAvailable();
|
|
10259
|
+
const collectionState = diagnosticAvailable ? await this.deps.qmdCollectionState(searchBackend, qmdEnabled, collection) : "unknown";
|
|
10260
|
+
const active = operationalAvailable && collectionState !== "missing";
|
|
10261
|
+
const degraded = searchBackend === "qmd" && qmdEnabled && (!active || !diagnosticAvailable || collectionState === "unknown");
|
|
10262
|
+
const debugStatus = qmd.debugStatus();
|
|
10263
|
+
const versionStatus = "getVersionStatus" in qmd && typeof qmd.getVersionStatus === "function" ? qmd.getVersionStatus() : null;
|
|
10264
|
+
const daemonMode = "isDaemonMode" in qmd && typeof qmd.isDaemonMode === "function" ? qmd.isDaemonMode() === true : false;
|
|
10265
|
+
const mode = searchBackend !== "qmd" ? "not-selected" : !qmdEnabled ? "disabled" : !active ? "fallback" : daemonMode ? "daemon" : "cli";
|
|
10266
|
+
return {
|
|
10267
|
+
enabled: qmdEnabled,
|
|
10268
|
+
active,
|
|
10269
|
+
degraded,
|
|
10270
|
+
mode,
|
|
10271
|
+
collection,
|
|
10272
|
+
collectionState,
|
|
10273
|
+
installedVersion: versionStatus?.installedVersion ?? null,
|
|
10274
|
+
supportedVersion: versionStatus?.supportedVersion ?? null,
|
|
10275
|
+
supported: versionStatus?.supported ?? null,
|
|
10276
|
+
upgradeAvailable: versionStatus?.upgradeAvailable ?? null,
|
|
10277
|
+
doctorAvailable: versionStatus?.capabilities?.doctor ?? null,
|
|
10278
|
+
debugStatus
|
|
10279
|
+
};
|
|
10280
|
+
}
|
|
10281
|
+
async namespaceQmdHealth(searchBackend, qmdEnabled, namespace, fallbackCollection) {
|
|
10282
|
+
if (searchBackend !== "qmd" || !qmdEnabled) return null;
|
|
10283
|
+
const searchHealthForNamespace = this.deps.orchestrator.searchHealthForNamespace;
|
|
10284
|
+
if (typeof searchHealthForNamespace !== "function") return null;
|
|
10285
|
+
const controller = new AbortController();
|
|
10286
|
+
const timer = setTimeout(() => controller.abort(), 2e3);
|
|
10287
|
+
timer.unref?.();
|
|
10288
|
+
try {
|
|
10289
|
+
const health = await searchHealthForNamespace.call(this.deps.orchestrator, namespace, {
|
|
10290
|
+
signal: controller.signal
|
|
10291
|
+
});
|
|
10292
|
+
const active = health.available && health.collectionState !== "missing";
|
|
10293
|
+
const degraded = !active || health.collectionState === "unknown";
|
|
10294
|
+
const mode = !active ? "fallback" : health.daemonMode === true ? "daemon" : "cli";
|
|
10295
|
+
return {
|
|
10296
|
+
enabled: true,
|
|
10297
|
+
active,
|
|
10298
|
+
degraded,
|
|
10299
|
+
mode,
|
|
10300
|
+
collection: health.collection || fallbackCollection,
|
|
10301
|
+
collectionState: health.collectionState,
|
|
10302
|
+
installedVersion: health.installedVersion,
|
|
10303
|
+
supportedVersion: health.supportedVersion,
|
|
10304
|
+
supported: health.supported,
|
|
10305
|
+
upgradeAvailable: health.upgradeAvailable,
|
|
10306
|
+
doctorAvailable: health.doctorAvailable,
|
|
10307
|
+
debugStatus: health.debugStatus
|
|
10308
|
+
};
|
|
10309
|
+
} catch (error) {
|
|
10310
|
+
const detail = displayErrorDetail(error) || "unknown";
|
|
10311
|
+
return {
|
|
10312
|
+
enabled: true,
|
|
10313
|
+
active: false,
|
|
10314
|
+
degraded: true,
|
|
10315
|
+
mode: "fallback",
|
|
10316
|
+
collection: fallbackCollection,
|
|
10317
|
+
collectionState: "unknown",
|
|
10318
|
+
installedVersion: null,
|
|
10319
|
+
supportedVersion: null,
|
|
10320
|
+
supported: null,
|
|
10321
|
+
upgradeAvailable: null,
|
|
10322
|
+
doctorAvailable: null,
|
|
10323
|
+
debugStatus: `backend=namespace-unavailable error=${detail}`
|
|
10324
|
+
};
|
|
10325
|
+
} finally {
|
|
10326
|
+
clearTimeout(timer);
|
|
10327
|
+
}
|
|
10328
|
+
}
|
|
10329
|
+
/**
|
|
10330
|
+
* Build a daily context briefing. Gracefully degrades when the OpenAI key
|
|
10331
|
+
* or Responses API is unavailable — never throws for LLM-related problems.
|
|
10332
|
+
*/
|
|
10333
|
+
async briefing(request) {
|
|
10334
|
+
const config = this.deps.orchestrator.config;
|
|
10335
|
+
if (!config.briefing.enabled) {
|
|
10336
|
+
throw new EngramAccessInputError("briefing is disabled");
|
|
10337
|
+
}
|
|
10338
|
+
const namespace = this.deps.resolveReadableNamespace(request.namespace, request.principal);
|
|
10339
|
+
const storage = await this.deps.orchestrator.getStorage(namespace);
|
|
10340
|
+
const token = typeof request.since === "string" && request.since.trim().length > 0 ? request.since.trim() : config.briefing.defaultWindow;
|
|
10341
|
+
const window = parseBriefingWindow(token);
|
|
10342
|
+
if (!window) {
|
|
10343
|
+
throw new EngramAccessInputError(`invalid briefing window: ${token}`);
|
|
10344
|
+
}
|
|
10345
|
+
const rawFocus = typeof request.focus === "string" ? request.focus.trim() : "";
|
|
10346
|
+
let focus = null;
|
|
10347
|
+
if (rawFocus.length > 0) {
|
|
10348
|
+
focus = parseBriefingFocus(rawFocus);
|
|
10349
|
+
if (!focus) {
|
|
10350
|
+
throw new EngramAccessInputError(
|
|
10351
|
+
`invalid briefing focus filter: ${request.focus}`
|
|
10352
|
+
);
|
|
10353
|
+
}
|
|
10354
|
+
}
|
|
10355
|
+
const SUPPORTED_FORMATS = ["markdown", "json"];
|
|
10356
|
+
if (typeof request.format === "string" && !SUPPORTED_FORMATS.includes(request.format)) {
|
|
10357
|
+
throw new EngramAccessInputError(
|
|
10358
|
+
`unsupported briefing format: "${request.format}". Accepted: ${SUPPORTED_FORMATS.join(", ")}.`
|
|
10359
|
+
);
|
|
10360
|
+
}
|
|
10361
|
+
const format = request.format === "json" ? "json" : request.format === "markdown" ? "markdown" : config.briefing.defaultFormat;
|
|
10362
|
+
const maxFollowups = typeof request.maxFollowups === "number" && Number.isFinite(request.maxFollowups) ? Math.max(0, Math.min(10, Math.floor(request.maxFollowups))) : config.briefing.maxFollowups;
|
|
10363
|
+
const calendarSource = config.briefing.calendarSource ? new FileCalendarSource(config.briefing.calendarSource) : void 0;
|
|
10364
|
+
const result = await buildBriefing({
|
|
10365
|
+
storage,
|
|
10366
|
+
namespace,
|
|
10367
|
+
window,
|
|
10368
|
+
focus,
|
|
10369
|
+
calendarSource,
|
|
10370
|
+
maxFollowups,
|
|
10371
|
+
allowLlm: config.briefing.llmFollowups,
|
|
10372
|
+
openaiApiKey: config.openaiApiKey,
|
|
10373
|
+
openaiBaseUrl: config.openaiBaseUrl,
|
|
10374
|
+
model: config.model,
|
|
10375
|
+
// Without a direct OpenAI key, route follow-ups through the configured
|
|
10376
|
+
// LLM chain (gateway model source or local LLM) — same fallback every
|
|
10377
|
+
// other LLM feature uses. A configured key keeps its precedence so
|
|
10378
|
+
// existing deployments are unchanged.
|
|
10379
|
+
followupGenerator: config.openaiApiKey ? void 0 : this.deps.orchestrator.briefingChainFollowupGenerator
|
|
10380
|
+
});
|
|
10381
|
+
return {
|
|
10382
|
+
format,
|
|
10383
|
+
window: result.window,
|
|
10384
|
+
namespace,
|
|
10385
|
+
markdown: result.markdown,
|
|
10386
|
+
json: result.json,
|
|
10387
|
+
followupsUnavailableReason: result.followupsUnavailableReason
|
|
10388
|
+
};
|
|
10389
|
+
}
|
|
10390
|
+
async memoryStore(request, hooks) {
|
|
10391
|
+
const namespace = await this.deps.resolveCodingScopedWriteNamespace(request);
|
|
10392
|
+
const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
|
|
10393
|
+
if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
|
|
10394
|
+
throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
|
|
10395
|
+
}
|
|
10396
|
+
const execute = async () => {
|
|
10397
|
+
const candidate = this.deps.validateWriteCandidate(request, namespace);
|
|
10398
|
+
if (request.dryRun === true) {
|
|
10399
|
+
return {
|
|
10400
|
+
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
10401
|
+
operation: "memory_store",
|
|
10402
|
+
namespace,
|
|
10403
|
+
dryRun: true,
|
|
10404
|
+
accepted: true,
|
|
10405
|
+
queued: false,
|
|
10406
|
+
status: "validated",
|
|
10407
|
+
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
10408
|
+
};
|
|
10409
|
+
}
|
|
10410
|
+
const result = await persistExplicitCapture(this.deps.orchestrator, candidate, "memory_store");
|
|
10411
|
+
await this.deps.attachCodingContextAfterScopedWrite(request);
|
|
10412
|
+
const blocked = result.tombstoneBlocked === true;
|
|
10413
|
+
const response = {
|
|
10414
|
+
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
10415
|
+
operation: "memory_store",
|
|
10416
|
+
namespace,
|
|
10417
|
+
dryRun: false,
|
|
10418
|
+
accepted: true,
|
|
10419
|
+
queued: blocked,
|
|
10420
|
+
status: blocked ? "queued_for_review" : result.duplicateOf ? "duplicate" : "stored",
|
|
10421
|
+
memoryId: result.id,
|
|
10422
|
+
duplicateOf: result.duplicateOf,
|
|
10423
|
+
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
10424
|
+
};
|
|
10425
|
+
log.info(
|
|
10426
|
+
`access-write op=memory_store namespace=${namespace} dryRun=false status=${response.status} memoryId=${response.memoryId ?? "-"} idempotency=${response.idempotencyKey ? "yes" : "no"}`
|
|
10427
|
+
);
|
|
10428
|
+
return response;
|
|
10429
|
+
};
|
|
10430
|
+
return this.deps.handleIdempotentWrite({
|
|
10431
|
+
operation: "memory_store",
|
|
10432
|
+
idempotencyKey: request.idempotencyKey,
|
|
10433
|
+
requestFingerprint: {
|
|
10434
|
+
schemaVersion,
|
|
10435
|
+
content: request.content,
|
|
10436
|
+
category: request.category,
|
|
10437
|
+
confidence: request.confidence,
|
|
10438
|
+
namespace,
|
|
10439
|
+
tags: request.tags,
|
|
10440
|
+
entityRef: request.entityRef,
|
|
10441
|
+
ttl: request.ttl,
|
|
10442
|
+
sourceReason: request.sourceReason
|
|
10443
|
+
},
|
|
10444
|
+
skip: request.dryRun === true,
|
|
10445
|
+
beforeExecute: hooks?.enforceWriteQuota,
|
|
10446
|
+
execute
|
|
10355
10447
|
});
|
|
10356
10448
|
}
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10449
|
+
async suggestionSubmit(request, hooks) {
|
|
10450
|
+
const namespace = await this.deps.resolveCodingScopedWriteNamespace(request);
|
|
10451
|
+
const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
|
|
10452
|
+
if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
|
|
10453
|
+
throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
|
|
10454
|
+
}
|
|
10455
|
+
const execute = async () => {
|
|
10456
|
+
const candidate = this.deps.validateWriteCandidate(request, namespace);
|
|
10457
|
+
if (request.dryRun === true) {
|
|
10458
|
+
return {
|
|
10459
|
+
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
10460
|
+
operation: "suggestion_submit",
|
|
10461
|
+
namespace,
|
|
10462
|
+
dryRun: true,
|
|
10463
|
+
accepted: true,
|
|
10464
|
+
queued: true,
|
|
10465
|
+
status: "validated",
|
|
10466
|
+
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
10467
|
+
};
|
|
10468
|
+
}
|
|
10469
|
+
const result = await queueExplicitCaptureForReview(
|
|
10470
|
+
this.deps.orchestrator,
|
|
10471
|
+
candidate,
|
|
10472
|
+
"suggestion_submit",
|
|
10473
|
+
new Error(request.sourceReason?.trim() || "submitted via engram suggestion_submit")
|
|
10474
|
+
);
|
|
10475
|
+
await this.deps.attachCodingContextAfterScopedWrite(request);
|
|
10476
|
+
const response = {
|
|
10477
|
+
schemaVersion: ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
|
|
10478
|
+
operation: "suggestion_submit",
|
|
10479
|
+
namespace,
|
|
10480
|
+
dryRun: false,
|
|
10481
|
+
accepted: true,
|
|
10482
|
+
queued: true,
|
|
10483
|
+
status: "queued_for_review",
|
|
10484
|
+
memoryId: result.id,
|
|
10485
|
+
duplicateOf: result.duplicateOf,
|
|
10486
|
+
idempotencyKey: request.idempotencyKey?.trim() || void 0
|
|
10487
|
+
};
|
|
10488
|
+
log.info(
|
|
10489
|
+
`access-write op=suggestion_submit namespace=${namespace} dryRun=false status=${response.status} memoryId=${response.memoryId ?? "-"} idempotency=${response.idempotencyKey ? "yes" : "no"}`
|
|
10490
|
+
);
|
|
10491
|
+
return response;
|
|
10492
|
+
};
|
|
10493
|
+
return this.deps.handleIdempotentWrite({
|
|
10494
|
+
operation: "suggestion_submit",
|
|
10495
|
+
idempotencyKey: request.idempotencyKey,
|
|
10496
|
+
requestFingerprint: {
|
|
10497
|
+
schemaVersion,
|
|
10498
|
+
content: request.content,
|
|
10499
|
+
category: request.category,
|
|
10500
|
+
confidence: request.confidence,
|
|
10501
|
+
namespace,
|
|
10502
|
+
tags: request.tags,
|
|
10503
|
+
entityRef: request.entityRef,
|
|
10504
|
+
ttl: request.ttl,
|
|
10505
|
+
sourceReason: request.sourceReason
|
|
10506
|
+
},
|
|
10507
|
+
skip: request.dryRun === true,
|
|
10508
|
+
beforeExecute: hooks?.enforceWriteQuota,
|
|
10509
|
+
execute
|
|
10378
10510
|
});
|
|
10379
|
-
return redactSensitive(inspection);
|
|
10380
10511
|
}
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10512
|
+
async runObserve(request) {
|
|
10513
|
+
if (!request.sessionKey || typeof request.sessionKey !== "string" || request.sessionKey.trim().length === 0) {
|
|
10514
|
+
throw new EngramAccessInputError("sessionKey is required and must be a non-empty string");
|
|
10515
|
+
}
|
|
10516
|
+
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
10517
|
+
throw new EngramAccessInputError("messages is required and must be a non-empty array");
|
|
10518
|
+
}
|
|
10519
|
+
for (const msg of request.messages) {
|
|
10520
|
+
if (!msg || typeof msg !== "object" || typeof msg.role !== "string" || typeof msg.content !== "string") {
|
|
10521
|
+
throw new EngramAccessInputError("each message must have a string 'role' and 'content'");
|
|
10522
|
+
}
|
|
10523
|
+
if (msg.role !== "user" && msg.role !== "assistant") {
|
|
10524
|
+
throw new EngramAccessInputError(`invalid message role: ${msg.role} (expected 'user' or 'assistant')`);
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
const scope = await this.deps.resolveMemoryScopePlan(request);
|
|
10528
|
+
const writeNamespace = scope.writeNamespace;
|
|
10529
|
+
const namespace = this.deps.legacyResponseNamespaceForScope(scope);
|
|
10530
|
+
const shouldWriteObjectiveState = resolveObjectiveStateCapabilities(this.deps.orchestrator.config).objectiveStateMemory === true && resolveObjectiveStateCapabilities(this.deps.orchestrator.config).objectiveStateSnapshotWrites === true;
|
|
10531
|
+
await this.deps.maybeAttachCodingContext(request.sessionKey, {
|
|
10532
|
+
cwd: request.cwd,
|
|
10533
|
+
projectTag: request.projectTag
|
|
10389
10534
|
});
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10535
|
+
const lcmSessionKey = lcmSessionKeyForNamespace(
|
|
10536
|
+
writeNamespace,
|
|
10537
|
+
request.sessionKey,
|
|
10538
|
+
this.deps.orchestrator.config.defaultNamespace
|
|
10539
|
+
) ?? request.sessionKey;
|
|
10540
|
+
if (shouldWriteObjectiveState) {
|
|
10541
|
+
try {
|
|
10542
|
+
const objectiveStateLocation = await this.deps.objectiveStateStoreLocationForNamespace(
|
|
10543
|
+
scope.objectiveStateNamespace
|
|
10544
|
+
);
|
|
10545
|
+
await recordObjectiveStateSnapshotsFromObservedMessages({
|
|
10546
|
+
memoryDir: objectiveStateLocation.memoryDir,
|
|
10547
|
+
objectiveStateStoreDir: objectiveStateLocation.objectiveStateStoreDir,
|
|
10548
|
+
objectiveStateMemoryEnabled: resolveObjectiveStateCapabilities(this.deps.orchestrator.config).objectiveStateMemory,
|
|
10549
|
+
objectiveStateSnapshotWritesEnabled: resolveObjectiveStateCapabilities(this.deps.orchestrator.config).objectiveStateSnapshotWrites,
|
|
10550
|
+
sessionKey: request.sessionKey,
|
|
10551
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10552
|
+
messages: request.messages
|
|
10553
|
+
});
|
|
10554
|
+
} catch (err) {
|
|
10555
|
+
log.error(`access-observe objective-state snapshot write failed: ${err}`);
|
|
10556
|
+
}
|
|
10557
|
+
}
|
|
10558
|
+
let lcmArchived = false;
|
|
10559
|
+
if (this.deps.orchestrator.lcmEngine && this.deps.orchestrator.lcmEngine.enabled) {
|
|
10560
|
+
try {
|
|
10561
|
+
this.deps.orchestrator.lcmEngine.enqueueObserveMessages(lcmSessionKey, request.messages);
|
|
10562
|
+
lcmArchived = true;
|
|
10563
|
+
} catch (err) {
|
|
10564
|
+
log.error(`access-observe LCM enqueue failed: ${err}`);
|
|
10565
|
+
}
|
|
10566
|
+
}
|
|
10567
|
+
let extractionQueued = false;
|
|
10568
|
+
if (request.skipExtraction !== true) {
|
|
10569
|
+
const turns = request.messages.map((m) => ({
|
|
10570
|
+
source: "openclaw",
|
|
10571
|
+
// Identity-vs-routing separation (#1505 thread 1, cursor): extraction
|
|
10572
|
+
// derives the provenance principal via `resolvePrincipal(turn.sessionKey)`
|
|
10573
|
+
// and threads `turn.sessionKey` into conversation threading. Feeding the
|
|
10574
|
+
// namespace-PREFIXED `lcmSessionKey` here mis-derived the principal to
|
|
10575
|
+
// `default` (a `<ns>:<key>` string matches no prefix/map rule and fails
|
|
10576
|
+
// the `agent:` heuristic). Pass the ORIGINAL sessionKey so identity is
|
|
10577
|
+
// correct; storage routing is pinned separately via
|
|
10578
|
+
// writeNamespaceOverride below, and the authenticated principal is pinned
|
|
10579
|
+
// via principalOverride.
|
|
10580
|
+
sessionKey: request.sessionKey,
|
|
10581
|
+
role: m.role,
|
|
10582
|
+
content: m.content,
|
|
10583
|
+
parts: m.parts,
|
|
10584
|
+
rawContent: m.rawContent,
|
|
10585
|
+
sourceFormat: m.sourceFormat,
|
|
10586
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10587
|
+
}));
|
|
10588
|
+
const writeNamespaceOverride = resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces === true ? writeNamespace : void 0;
|
|
10589
|
+
const principalOverride = typeof scope.principal === "string" && scope.principal.length > 0 ? scope.principal : void 0;
|
|
10590
|
+
try {
|
|
10591
|
+
const extractionPromise = this.deps.orchestrator.ingestReplayBatch(turns, {
|
|
10592
|
+
archiveLcm: false,
|
|
10593
|
+
writeNamespaceOverride,
|
|
10594
|
+
principalOverride
|
|
10595
|
+
});
|
|
10596
|
+
extractionPromise.catch((err) => {
|
|
10597
|
+
log.error(`access-observe background extraction failed: ${err}`);
|
|
10598
|
+
});
|
|
10599
|
+
extractionQueued = true;
|
|
10600
|
+
} catch (err) {
|
|
10601
|
+
log.error(`access-observe extraction enqueue failed: ${err}`);
|
|
10417
10602
|
}
|
|
10418
|
-
}
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
/**
|
|
10422
|
-
* Dry-run transcript/session audit. Detects mixed `other/default` data
|
|
10423
|
-
* and legacy stranded directories. Never applies a migration.
|
|
10424
|
-
*/
|
|
10425
|
-
async adminTranscriptAudit() {
|
|
10426
|
-
const result = await auditTranscripts(this.orchestrator.config.memoryDir);
|
|
10427
|
-
return redactSensitive(result);
|
|
10428
|
-
}
|
|
10429
|
-
async adminPromoteMemory(request) {
|
|
10430
|
-
return this.accessAdminOpsSurface.adminPromoteMemory(
|
|
10431
|
-
request
|
|
10603
|
+
}
|
|
10604
|
+
log.info(
|
|
10605
|
+
`access-observe namespace=${namespace} effectiveNamespace=${writeNamespace} sessionKey=${request.sessionKey} messages=${request.messages.length} lcm=${lcmArchived} extraction=${extractionQueued}`
|
|
10432
10606
|
);
|
|
10607
|
+
return {
|
|
10608
|
+
accepted: request.messages.length,
|
|
10609
|
+
sessionKey: request.sessionKey,
|
|
10610
|
+
namespace,
|
|
10611
|
+
effectiveNamespace: writeNamespace,
|
|
10612
|
+
scopeDebug: {
|
|
10613
|
+
principal: scope.principal,
|
|
10614
|
+
explicitNamespace: scope.explicitNamespace,
|
|
10615
|
+
baseNamespace: scope.baseNamespace,
|
|
10616
|
+
writeNamespace: scope.writeNamespace,
|
|
10617
|
+
codingOverlayApplied: scope.codingOverlayApplied,
|
|
10618
|
+
readNamespaces: scope.readNamespaces,
|
|
10619
|
+
scopeProfile: scope.scopeProfile,
|
|
10620
|
+
writeLayer: scope.writeLayer,
|
|
10621
|
+
layers: scope.layers,
|
|
10622
|
+
promotionTargets: scope.promotionTargets
|
|
10623
|
+
},
|
|
10624
|
+
lcmArchived,
|
|
10625
|
+
extractionQueued
|
|
10626
|
+
};
|
|
10433
10627
|
}
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10628
|
+
async workTask(request) {
|
|
10629
|
+
const STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress", "blocked", "done", "cancelled"]);
|
|
10630
|
+
const PRIORITIES = /* @__PURE__ */ new Set(["low", "medium", "high"]);
|
|
10631
|
+
const asStatus = (v) => v && STATUSES.has(v) ? v : void 0;
|
|
10632
|
+
const asPriority = (v) => v && PRIORITIES.has(v) ? v : void 0;
|
|
10633
|
+
const storage = new WorkStorage(this.deps.orchestrator.config.memoryDir);
|
|
10634
|
+
await storage.ensureDirectories();
|
|
10635
|
+
const action = request.action;
|
|
10636
|
+
if (action === "create") {
|
|
10637
|
+
if (!request.title?.trim()) throw new EngramAccessInputError("title is required for create");
|
|
10638
|
+
const task = await storage.createTask({
|
|
10639
|
+
title: request.title,
|
|
10640
|
+
description: request.description,
|
|
10641
|
+
status: asStatus(request.status),
|
|
10642
|
+
priority: asPriority(request.priority),
|
|
10643
|
+
owner: request.owner?.trim() || void 0,
|
|
10644
|
+
assignee: request.assignee?.trim() || void 0,
|
|
10645
|
+
projectId: request.projectId?.trim() || void 0,
|
|
10646
|
+
tags: request.tags,
|
|
10647
|
+
dueAt: request.dueAt?.trim() || void 0
|
|
10648
|
+
});
|
|
10649
|
+
return { action, task };
|
|
10445
10650
|
}
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
request.sessionKey,
|
|
10450
|
-
request.authenticatedPrincipal
|
|
10451
|
-
);
|
|
10452
|
-
const namespace = hasExplicitNamespace ? this.deps.resolveReadableNamespace(request.namespace, principal) : profileLcmReadNamespaces !== null ? profileLcmReadNamespaces[0] : this.deps.resolveImplicitLcmReadFallbackNamespace(principal);
|
|
10453
|
-
if (!this.deps.orchestrator.lcmEngine || !this.deps.orchestrator.lcmEngine.enabled) {
|
|
10454
|
-
return {
|
|
10455
|
-
query: request.query,
|
|
10456
|
-
namespace: namespace ?? this.deps.orchestrator.config.defaultNamespace,
|
|
10457
|
-
results: [],
|
|
10458
|
-
count: 0,
|
|
10459
|
-
lcmEnabled: false
|
|
10460
|
-
};
|
|
10651
|
+
if (action === "get") {
|
|
10652
|
+
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for get");
|
|
10653
|
+
return { action, task: await storage.getTask(request.id) };
|
|
10461
10654
|
}
|
|
10462
|
-
if (
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
};
|
|
10655
|
+
if (action === "list") {
|
|
10656
|
+
const tasks = await storage.listTasks({
|
|
10657
|
+
status: asStatus(request.status),
|
|
10658
|
+
owner: request.owner?.trim() || void 0,
|
|
10659
|
+
assignee: request.assignee?.trim() || void 0,
|
|
10660
|
+
projectId: request.projectId?.trim() || void 0
|
|
10661
|
+
});
|
|
10662
|
+
return { action, count: tasks.length, tasks };
|
|
10470
10663
|
}
|
|
10471
|
-
if (
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10664
|
+
if (action === "update") {
|
|
10665
|
+
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for update");
|
|
10666
|
+
const patch = {};
|
|
10667
|
+
if (request.title !== void 0) patch.title = request.title;
|
|
10668
|
+
if (request.description !== void 0) patch.description = request.description;
|
|
10669
|
+
const st = asStatus(request.status);
|
|
10670
|
+
if (st) patch.status = st;
|
|
10671
|
+
const pr = asPriority(request.priority);
|
|
10672
|
+
if (pr) patch.priority = pr;
|
|
10673
|
+
if (request.owner !== void 0) patch.owner = request.owner || null;
|
|
10674
|
+
if (request.assignee !== void 0) patch.assignee = request.assignee || null;
|
|
10675
|
+
if (request.projectId !== void 0) patch.projectId = request.projectId || null;
|
|
10676
|
+
if (request.tags) patch.tags = request.tags;
|
|
10677
|
+
if (request.dueAt !== void 0) patch.dueAt = request.dueAt || null;
|
|
10678
|
+
return { action, task: await storage.updateTask(request.id, patch) };
|
|
10479
10679
|
}
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
request.
|
|
10483
|
-
|
|
10484
|
-
request.
|
|
10485
|
-
request.authenticatedPrincipal
|
|
10486
|
-
);
|
|
10487
|
-
const lcmSessionKeyIds = request.sessionKey ? profileLcmReadNamespaces !== null ? this.deps.lcmSessionIdsForNamespaces(
|
|
10488
|
-
profileLcmReadNamespaces,
|
|
10489
|
-
request.sessionKey
|
|
10490
|
-
) : this.deps.resolveLcmReadSessionIds(
|
|
10491
|
-
request.namespace,
|
|
10492
|
-
namespace,
|
|
10493
|
-
request.sessionKey,
|
|
10494
|
-
request.authenticatedPrincipal
|
|
10495
|
-
) : [void 0];
|
|
10496
|
-
const lcmSessionPrefixes = request.sessionPrefix ? profileLcmReadNamespaces !== null && !request.sessionKey ? this.deps.lcmSessionIdsForNamespaces(
|
|
10497
|
-
profileLcmReadNamespaces,
|
|
10498
|
-
request.sessionPrefix
|
|
10499
|
-
) : [
|
|
10500
|
-
lcmSessionKeyForNamespace(
|
|
10501
|
-
lcmReadNamespace,
|
|
10502
|
-
request.sessionPrefix,
|
|
10503
|
-
this.deps.orchestrator.config.defaultNamespace
|
|
10504
|
-
) ?? request.sessionPrefix
|
|
10505
|
-
] : [void 0];
|
|
10506
|
-
const hasScopedSession = typeof request.sessionKey === "string" && request.sessionKey.length > 0 || lcmSessionPrefixes.some((prefix) => typeof prefix === "string" && prefix.length > 0);
|
|
10507
|
-
if (!hasScopedSession && resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces === true) {
|
|
10508
|
-
return {
|
|
10509
|
-
query: request.query,
|
|
10510
|
-
namespace,
|
|
10511
|
-
results: [],
|
|
10512
|
-
count: 0,
|
|
10513
|
-
lcmEnabled: true
|
|
10514
|
-
};
|
|
10680
|
+
if (action === "transition") {
|
|
10681
|
+
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for transition");
|
|
10682
|
+
const st = asStatus(request.status);
|
|
10683
|
+
if (!st) throw new EngramAccessInputError("valid status is required for transition");
|
|
10684
|
+
return { action, task: await storage.transitionTask(request.id, st) };
|
|
10515
10685
|
}
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
for (const lcmSessionKey of lcmSessionKeyIds) {
|
|
10520
|
-
for (const lcmSessionPrefix of lcmSessionPrefixes) {
|
|
10521
|
-
lcmSearches.push({
|
|
10522
|
-
key: lcmSessionKey,
|
|
10523
|
-
prefix: lcmSessionPrefix,
|
|
10524
|
-
promise: this.deps.orchestrator.lcmEngine.searchContextFull(
|
|
10525
|
-
request.query,
|
|
10526
|
-
limit,
|
|
10527
|
-
lcmSessionKey,
|
|
10528
|
-
lcmSessionPrefix
|
|
10529
|
-
)
|
|
10530
|
-
});
|
|
10531
|
-
}
|
|
10686
|
+
if (action === "delete") {
|
|
10687
|
+
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for delete");
|
|
10688
|
+
return { action, deleted: await storage.deleteTask(request.id) };
|
|
10532
10689
|
}
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10690
|
+
throw new EngramAccessInputError(`Unsupported work_task action: ${action}`);
|
|
10691
|
+
}
|
|
10692
|
+
async workProject(request) {
|
|
10693
|
+
const STATUSES = /* @__PURE__ */ new Set(["active", "on_hold", "completed", "archived"]);
|
|
10694
|
+
const asStatus = (v) => v && STATUSES.has(v) ? v : void 0;
|
|
10695
|
+
const storage = new WorkStorage(this.deps.orchestrator.config.memoryDir);
|
|
10696
|
+
await storage.ensureDirectories();
|
|
10697
|
+
const action = request.action;
|
|
10698
|
+
if (action === "create") {
|
|
10699
|
+
if (!request.name?.trim()) throw new EngramAccessInputError("name is required for create");
|
|
10700
|
+
const project = await storage.createProject({
|
|
10701
|
+
name: request.name,
|
|
10702
|
+
description: request.description,
|
|
10703
|
+
status: asStatus(request.status),
|
|
10704
|
+
owner: request.owner?.trim() || void 0,
|
|
10705
|
+
tags: request.tags
|
|
10706
|
+
});
|
|
10707
|
+
return { action, project };
|
|
10708
|
+
}
|
|
10709
|
+
if (action === "get") {
|
|
10710
|
+
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for get");
|
|
10711
|
+
return { action, project: await storage.getProject(request.id) };
|
|
10712
|
+
}
|
|
10713
|
+
if (action === "list") {
|
|
10714
|
+
const projects = await storage.listProjects();
|
|
10715
|
+
return { action, count: projects.length, projects };
|
|
10716
|
+
}
|
|
10717
|
+
if (action === "update") {
|
|
10718
|
+
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for update");
|
|
10719
|
+
const patch = {};
|
|
10720
|
+
if (request.name !== void 0) patch.name = request.name;
|
|
10721
|
+
if (request.description !== void 0) patch.description = request.description;
|
|
10722
|
+
const st = asStatus(request.status);
|
|
10723
|
+
if (st) patch.status = st;
|
|
10724
|
+
if (request.owner !== void 0) patch.owner = request.owner || null;
|
|
10725
|
+
if (request.tags) patch.tags = request.tags;
|
|
10726
|
+
return { action, project: await storage.updateProject(request.id, patch) };
|
|
10727
|
+
}
|
|
10728
|
+
if (action === "delete") {
|
|
10729
|
+
if (!request.id?.trim()) throw new EngramAccessInputError("id is required for delete");
|
|
10730
|
+
return { action, deleted: await storage.deleteProject(request.id) };
|
|
10731
|
+
}
|
|
10732
|
+
if (action === "link_task") {
|
|
10733
|
+
if (!request.taskId?.trim() || !request.projectId?.trim()) {
|
|
10734
|
+
throw new EngramAccessInputError("taskId and projectId are required for link_task");
|
|
10556
10735
|
}
|
|
10736
|
+
return { action, linked: await storage.linkTaskToProject(request.taskId, request.projectId) };
|
|
10557
10737
|
}
|
|
10558
|
-
|
|
10559
|
-
query: request.query,
|
|
10560
|
-
namespace,
|
|
10561
|
-
results,
|
|
10562
|
-
count: results.length,
|
|
10563
|
-
lcmEnabled: true
|
|
10564
|
-
};
|
|
10738
|
+
throw new EngramAccessInputError(`Unsupported work_project action: ${action}`);
|
|
10565
10739
|
}
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
*
|
|
10587
|
-
* - `"write"` (`lcmCompactionFlush` / `lcmCompactionRecord`): these are
|
|
10588
|
-
* write/maintenance operations on the SAME queue `observe` just wrote, so
|
|
10589
|
-
* the gate must mirror observe's WRITE authorization (`canWriteNamespace`
|
|
10590
|
-
* on the self base), NOT readability. A principal that can WRITE but not
|
|
10591
|
-
* READ its self namespace (or whose `defaultRecallNamespaces` omits `self`)
|
|
10592
|
-
* archived under the overlay key via `observe`; gating compaction by
|
|
10593
|
-
* readability would fall back to the default/raw key and leave that queue
|
|
10594
|
-
* never flushed/recorded (round-4 codex P2). Write-authorized ⇒ overlay
|
|
10595
|
-
* key, matching the observe write key (rule 42 read/write parity; rule 39
|
|
10596
|
-
* identical gates across paths).
|
|
10597
|
-
*/
|
|
10598
|
-
resolveLcmReadNamespace(explicitNamespace, resolvedNamespace, sessionKeyForOverlay, authenticatedPrincipal, purpose = "read") {
|
|
10599
|
-
const hasExplicitNamespace = typeof explicitNamespace === "string" && explicitNamespace.trim().length > 0;
|
|
10600
|
-
if (hasExplicitNamespace) return resolvedNamespace;
|
|
10601
|
-
const principal = this.deps.resolveRequestPrincipal(
|
|
10602
|
-
sessionKeyForOverlay,
|
|
10603
|
-
authenticatedPrincipal
|
|
10604
|
-
);
|
|
10605
|
-
const base = defaultNamespaceForPrincipal(principal, this.deps.orchestrator.config);
|
|
10606
|
-
const overlaid = this.deps.orchestrator.applyCodingNamespaceOverlay(
|
|
10607
|
-
sessionKeyForOverlay,
|
|
10608
|
-
base
|
|
10609
|
-
);
|
|
10610
|
-
if (overlaid === base) return this.deps.orchestrator.config.defaultNamespace;
|
|
10611
|
-
const authorized = purpose === "write" ? canWriteNamespace(principal, base, this.deps.orchestrator.config) : recallNamespacesForPrincipal(
|
|
10612
|
-
principal,
|
|
10613
|
-
this.deps.orchestrator.config
|
|
10614
|
-
).includes(base);
|
|
10615
|
-
if (authorized) return overlaid;
|
|
10616
|
-
if (purpose === "read") return this.deps.orchestrator.config.defaultNamespace;
|
|
10617
|
-
return resolvedNamespace;
|
|
10740
|
+
async workBoard(request) {
|
|
10741
|
+
const memoryDir = this.deps.orchestrator.config.memoryDir;
|
|
10742
|
+
await new WorkStorage(memoryDir).ensureDirectories();
|
|
10743
|
+
const action = request.action;
|
|
10744
|
+
const projectId = request.projectId?.trim() || void 0;
|
|
10745
|
+
if (action === "export_markdown") {
|
|
10746
|
+
const markdown = await exportWorkBoardMarkdown({ memoryDir, projectId });
|
|
10747
|
+
return { action, markdown: wrapWorkLayerContext(markdown, { linkToMemory: request.linkToMemory === true }) };
|
|
10748
|
+
}
|
|
10749
|
+
if (action === "export_snapshot") {
|
|
10750
|
+
const snapshot = await exportWorkBoardSnapshot({ memoryDir, projectId });
|
|
10751
|
+
return { action, snapshot };
|
|
10752
|
+
}
|
|
10753
|
+
if (action === "import_snapshot") {
|
|
10754
|
+
if (!request.snapshotJson?.trim()) throw new EngramAccessInputError("snapshotJson is required for import_snapshot");
|
|
10755
|
+
const snapshot = JSON.parse(request.snapshotJson);
|
|
10756
|
+
const result = await importWorkBoardSnapshot({ memoryDir, snapshot, projectId });
|
|
10757
|
+
return { action, result };
|
|
10758
|
+
}
|
|
10759
|
+
throw new EngramAccessInputError(`Unsupported work_board action: ${action}`);
|
|
10618
10760
|
}
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
* single-user recall is byte-for-byte unchanged.
|
|
10633
|
-
*
|
|
10634
|
-
* Returns `undefined` when NO readable LCM namespace exists for an IMPLICIT
|
|
10635
|
-
* (no explicit `namespace`) raw recall — i.e. a restrictive `default` READ
|
|
10636
|
-
* policy denies the principal `default` AND no overlay/self namespace is
|
|
10637
|
-
* readable. In that case the caller emits NO excerpts rather than throwing
|
|
10638
|
-
* `namespace is not readable: default` (#1505 thread NBHWz): normal recall
|
|
10639
|
-
* still succeeds via `recallNamespacesForPrincipal`, so `disclosure: "raw"`
|
|
10640
|
-
* must degrade gracefully (empty excerpts), never pre-authorize `default`.
|
|
10641
|
-
*
|
|
10642
|
-
* IMPLICIT-namespace fallback selection derives from the ALREADY
|
|
10643
|
-
* read-authorized recall namespace set (`recallNamespacesForPrincipal` +
|
|
10644
|
-
* `canReadNamespace`) — the principal's self base when it is in the readable
|
|
10645
|
-
* recall set, else `config.defaultNamespace` ONLY when the principal may read
|
|
10646
|
-
* it. It NEVER pre-authorizes `default`. An EXPLICIT `namespace` is still
|
|
10647
|
-
* authorized strictly via `resolveReadableNamespace` (explicit reads must pass
|
|
10648
|
-
* the ACL — no behavior change).
|
|
10649
|
-
*/
|
|
10650
|
-
resolveRawExcerptReadNamespace(explicitNamespace, sessionKey, authenticatedPrincipal) {
|
|
10651
|
-
const principal = this.deps.resolveRequestPrincipal(
|
|
10652
|
-
sessionKey,
|
|
10653
|
-
authenticatedPrincipal
|
|
10654
|
-
);
|
|
10655
|
-
const hasExplicitNamespace = typeof explicitNamespace === "string" && explicitNamespace.trim().length > 0;
|
|
10656
|
-
if (hasExplicitNamespace) {
|
|
10657
|
-
const resolvedNamespace = this.deps.resolveReadableNamespace(
|
|
10658
|
-
explicitNamespace,
|
|
10659
|
-
principal
|
|
10761
|
+
async memoryActionApply(request) {
|
|
10762
|
+
const actionTypes = /* @__PURE__ */ new Set([
|
|
10763
|
+
"store_episode",
|
|
10764
|
+
"store_note",
|
|
10765
|
+
"update_note",
|
|
10766
|
+
"create_artifact",
|
|
10767
|
+
"summarize_node",
|
|
10768
|
+
"discard",
|
|
10769
|
+
"link_graph"
|
|
10770
|
+
]);
|
|
10771
|
+
if (!actionTypes.has(request.action)) {
|
|
10772
|
+
throw new EngramAccessInputError(
|
|
10773
|
+
`memory_action_apply: invalid action ${JSON.stringify(request.action)}`
|
|
10660
10774
|
);
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
authenticatedPrincipal,
|
|
10666
|
-
"read"
|
|
10775
|
+
}
|
|
10776
|
+
if (resolveCompressionCapabilities(this.deps.orchestrator.config).contextCompressionActions !== true) {
|
|
10777
|
+
throw new EngramAccessInputError(
|
|
10778
|
+
"memory_action_apply is disabled; enable contextCompressionActionsEnabled to use this tool"
|
|
10667
10779
|
);
|
|
10668
10780
|
}
|
|
10669
|
-
const
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
const fallbackNamespace = this.deps.resolveImplicitLcmReadFallbackNamespace(principal);
|
|
10675
|
-
if (fallbackNamespace === void 0) return void 0;
|
|
10676
|
-
return this.deps.resolveLcmReadNamespace(
|
|
10677
|
-
explicitNamespace,
|
|
10678
|
-
fallbackNamespace,
|
|
10679
|
-
sessionKey,
|
|
10680
|
-
authenticatedPrincipal,
|
|
10681
|
-
"read"
|
|
10682
|
-
);
|
|
10683
|
-
}
|
|
10684
|
-
resolveLcmReadSessionIds(explicitNamespace, resolvedNamespace, sessionKey, authenticatedPrincipal) {
|
|
10685
|
-
const primary = this.deps.resolveLcmReadSessionKey(
|
|
10686
|
-
explicitNamespace,
|
|
10687
|
-
resolvedNamespace,
|
|
10688
|
-
sessionKey,
|
|
10689
|
-
authenticatedPrincipal,
|
|
10690
|
-
"read"
|
|
10691
|
-
);
|
|
10692
|
-
const hasExplicitNamespace = typeof explicitNamespace === "string" && explicitNamespace.trim().length > 0;
|
|
10693
|
-
if (hasExplicitNamespace) return [primary];
|
|
10694
|
-
const profileReadNamespaces = this.deps.resolveScopeProfileLcmReadNamespaces(
|
|
10695
|
-
sessionKey,
|
|
10696
|
-
authenticatedPrincipal
|
|
10697
|
-
);
|
|
10698
|
-
if (profileReadNamespaces !== null) {
|
|
10699
|
-
return this.deps.lcmSessionIdsForNamespaces(profileReadNamespaces, sessionKey);
|
|
10781
|
+
const outcome = request.outcome ?? "skipped";
|
|
10782
|
+
if (outcome !== "applied" && outcome !== "skipped" && outcome !== "failed") {
|
|
10783
|
+
throw new EngramAccessInputError(
|
|
10784
|
+
`memory_action_apply: outcome must be "applied", "skipped", or "failed"; got ${JSON.stringify(outcome)}`
|
|
10785
|
+
);
|
|
10700
10786
|
}
|
|
10701
|
-
const
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
const base = defaultNamespaceForPrincipal(
|
|
10706
|
-
principal,
|
|
10707
|
-
this.deps.orchestrator.config
|
|
10708
|
-
);
|
|
10709
|
-
const overlaid = this.deps.orchestrator.applyCodingNamespaceOverlay(
|
|
10710
|
-
sessionKey,
|
|
10711
|
-
base
|
|
10712
|
-
);
|
|
10713
|
-
if (overlaid === base) return [primary];
|
|
10714
|
-
const selfReadableInRecall = recallNamespacesForPrincipal(
|
|
10715
|
-
principal,
|
|
10716
|
-
this.deps.orchestrator.config
|
|
10717
|
-
).includes(base);
|
|
10718
|
-
if (!selfReadableInRecall) return [primary];
|
|
10719
|
-
const overlay = resolveCodingNamespaceOverlay(
|
|
10720
|
-
this.deps.orchestrator.getCodingContextForSession(sessionKey),
|
|
10721
|
-
this.deps.orchestrator.config.codingMode,
|
|
10722
|
-
this.deps.orchestrator.config.defaultNamespace
|
|
10723
|
-
);
|
|
10724
|
-
const fallbackNamespaces = (overlay?.readFallbacks ?? []).map(
|
|
10725
|
-
(fallback) => combineNamespaces(base, fallback)
|
|
10787
|
+
const resolvedNs = this.deps.writableNamespaceFor(
|
|
10788
|
+
request.namespace,
|
|
10789
|
+
request.sessionKey,
|
|
10790
|
+
request.principal
|
|
10726
10791
|
);
|
|
10727
|
-
const
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10792
|
+
const inputSummaryParts = [
|
|
10793
|
+
request.content,
|
|
10794
|
+
request.category ? `category=${request.category}` : void 0,
|
|
10795
|
+
request.linkTargetId ? `linkTargetId=${request.linkTargetId}` : void 0,
|
|
10796
|
+
request.linkType ? `linkType=${request.linkType}` : void 0,
|
|
10797
|
+
typeof request.linkStrength === "number" ? `linkStrength=${request.linkStrength}` : void 0,
|
|
10798
|
+
request.artifactType ? `artifactType=${request.artifactType}` : void 0,
|
|
10799
|
+
typeof request.execute === "boolean" ? `execute=${request.execute}` : void 0
|
|
10800
|
+
].filter((part) => typeof part === "string" && part.length > 0);
|
|
10801
|
+
const event = {
|
|
10802
|
+
action: request.action,
|
|
10803
|
+
outcome,
|
|
10804
|
+
namespace: resolvedNs,
|
|
10805
|
+
actor: "access.memory_action_apply",
|
|
10806
|
+
subsystem: "access.memory_action_apply",
|
|
10807
|
+
reason: request.reason,
|
|
10808
|
+
memoryId: request.memoryId,
|
|
10809
|
+
sourceSessionKey: request.sessionKey,
|
|
10810
|
+
inputSummary: inputSummaryParts.length > 0 ? inputSummaryParts.join(" | ").slice(0, 500) : void 0,
|
|
10811
|
+
dryRun: request.dryRun === true,
|
|
10812
|
+
promptHash: typeof request.sourcePrompt === "string" && request.sourcePrompt.length > 0 ? createHash6("sha256").update(request.sourcePrompt).digest("hex") : void 0
|
|
10813
|
+
};
|
|
10814
|
+
const preview = this.deps.orchestrator.previewMemoryActionEvent(event);
|
|
10815
|
+
if (request.dryRun === true) {
|
|
10816
|
+
return { recorded: false, dryRun: true, event: preview };
|
|
10739
10817
|
}
|
|
10740
|
-
|
|
10818
|
+
const recorded = await this.deps.orchestrator.appendMemoryActionEvent(event);
|
|
10819
|
+
return { recorded, event: preview };
|
|
10741
10820
|
}
|
|
10742
10821
|
};
|
|
10743
10822
|
|
|
@@ -10752,6 +10831,7 @@ export {
|
|
|
10752
10831
|
clusterByKey,
|
|
10753
10832
|
computeProcedureStats,
|
|
10754
10833
|
formatProcedureStatsText,
|
|
10834
|
+
AccessObserveWriteSurface,
|
|
10755
10835
|
AccessLcmSurface,
|
|
10756
10836
|
qmdCollectionPathParts,
|
|
10757
10837
|
QmdResultResolver,
|
|
@@ -10764,4 +10844,4 @@ export {
|
|
|
10764
10844
|
EngramAccessService,
|
|
10765
10845
|
AccessAdminOpsSurface
|
|
10766
10846
|
};
|
|
10767
|
-
//# sourceMappingURL=chunk-
|
|
10847
|
+
//# sourceMappingURL=chunk-DWDDQLSZ.js.map
|