memorix 1.2.1 → 1.2.2
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/CHANGELOG.md +16 -0
- package/README.md +14 -2
- package/README.zh-CN.md +14 -2
- package/TEAM.md +86 -86
- package/dist/cli/index.js +15407 -13779
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1321 -529
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8458 -8087
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +16 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +1349 -535
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +49 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
- package/docs/API_REFERENCE.md +24 -4
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/README.md +1 -1
- package/docs/dev-log/progress.txt +91 -11
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +15 -5
- package/src/cli/commands/context.ts +3 -2
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/explain.ts +9 -3
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +75 -61
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +71 -69
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +93 -92
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +123 -118
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +74 -64
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +135 -121
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +8 -2
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +29 -3
- package/src/cli/commands/skills.ts +124 -119
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +193 -184
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +108 -55
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/identity.ts +89 -0
- package/src/cli/index.ts +96 -19
- package/src/cli/invocation.ts +115 -0
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/auto-context.ts +31 -2
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/project-context.ts +2 -0
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/compact/token-budget.ts +74 -74
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/server.ts +46 -9
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/workset.ts +179 -10
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/admission.ts +57 -0
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +140 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/export-import.ts +11 -3
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/graph.ts +197 -197
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +22 -2
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +5 -2
- package/src/memory/visibility.ts +80 -0
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +378 -340
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +18 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/maintenance-runner.ts +2 -0
- package/src/runtime/project-maintenance.ts +89 -0
- package/src/sdk.ts +334 -304
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +260 -81
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/orama-store.ts +61 -6
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +168 -161
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
package/src/server.ts
CHANGED
|
@@ -29,12 +29,13 @@ import { checkProjectAttribution, auditProjectObservations } from './memory/attr
|
|
|
29
29
|
import { createAutoRelations } from './memory/auto-relations.js';
|
|
30
30
|
import { extractEntities } from './memory/entity-extractor.js';
|
|
31
31
|
import { scopeKnowledgeGraphToProject } from './memory/graph-scope.js';
|
|
32
|
+
import { canManageObservation, canReadObservation, filterReadableObservations, resolveObservationVisibility } from './memory/visibility.js';
|
|
32
33
|
import { compactSearch, compactTimeline, compactDetail } from './compact/engine.js';
|
|
33
34
|
import { buildGraphContextPacket, formatGraphContextPrompt } from './memory/graph-context.js';
|
|
34
35
|
import { detectProject } from './project/detector.js';
|
|
35
36
|
import { registerAlias, initAliasRegistry, resolveAliases, autoMergeByBaseName } from './project/aliases.js';
|
|
36
37
|
import { getProjectDataDir } from './store/persistence.js';
|
|
37
|
-
import type { ObservationType, RuleSource, AgentTarget, MCPServerEntry } from './types.js';
|
|
38
|
+
import type { ObservationType, RuleSource, AgentTarget, MCPServerEntry, ObservationReader } from './types.js';
|
|
38
39
|
import { RulesSyncer } from './rules/syncer.js';
|
|
39
40
|
import { WorkspaceSyncEngine } from './workspace/engine.js';
|
|
40
41
|
import {
|
|
@@ -261,8 +262,10 @@ export async function createMemorixServer(
|
|
|
261
262
|
let rawProject: import('./types.js').ProjectInfo;
|
|
262
263
|
let projectResolved = true;
|
|
263
264
|
let projectResolutionError: string | null = null;
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
let explicitProjectBound = false; // Set true when memorix_session_start binds via projectRoot
|
|
266
|
+
let currentAgentId: string | undefined; // Session-scoped coordination identity for attribution after explicit join
|
|
267
|
+
let teamStore!: import('./team/team-store.js').TeamStore;
|
|
268
|
+
let initTeamStoreForProject: ((dataDir: string) => Promise<import('./team/team-store.js').TeamStore>) | undefined;
|
|
266
269
|
if (detectedProject) {
|
|
267
270
|
rawProject = detectedProject;
|
|
268
271
|
} else {
|
|
@@ -582,6 +585,23 @@ export async function createMemorixServer(
|
|
|
582
585
|
return (originalRegisterTool as (...innerArgs: unknown[]) => unknown)(name, ...args) as never;
|
|
583
586
|
}) as typeof server.registerTool;
|
|
584
587
|
|
|
588
|
+
const getObservationReader = (scope: 'project' | 'global' = 'project'): ObservationReader => {
|
|
589
|
+
let isTeamMember = false;
|
|
590
|
+
if (teamFeaturesEnabled && currentAgentId) {
|
|
591
|
+
try {
|
|
592
|
+
const agent = teamStore.getAgent(currentAgentId);
|
|
593
|
+
isTeamMember = agent?.project_id === project.id && agent.status === 'active';
|
|
594
|
+
} catch {
|
|
595
|
+
// A missing coordination store must never grant team visibility.
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
...(scope === 'project' ? { projectId: project.id } : {}),
|
|
600
|
+
...(currentAgentId ? { agentId: currentAgentId } : {}),
|
|
601
|
+
isTeamMember,
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
|
|
585
605
|
// ================================================================
|
|
586
606
|
// Memorix Extended Tools (3-layer Progressive Disclosure)
|
|
587
607
|
// ================================================================
|
|
@@ -602,7 +622,7 @@ export async function createMemorixServer(
|
|
|
602
622
|
'problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), ' +
|
|
603
623
|
'discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), ' +
|
|
604
624
|
'session-request ([SESSION] original goal). ' +
|
|
605
|
-
'
|
|
625
|
+
'Project visibility is the default. Personal or team visibility requires an explicitly joined coordination identity.',
|
|
606
626
|
inputSchema: {
|
|
607
627
|
entityName: z.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
|
|
608
628
|
type: z.enum(OBSERVATION_TYPES).describe('Observation type for classification'),
|
|
@@ -623,12 +643,30 @@ export async function createMemorixServer(
|
|
|
623
643
|
}).optional().describe('Progress tracking for task/feature observations'),
|
|
624
644
|
relatedCommits: z.array(z.string()).optional().describe('Git commit hashes this memory relates to (links ground truth ↔ reasoning)'),
|
|
625
645
|
relatedEntities: z.array(z.string()).optional().describe('Other entity names this memory cross-references'),
|
|
646
|
+
visibility: z.enum(['personal', 'project', 'team']).optional().describe(
|
|
647
|
+
'Retrieval scope. Project is the normal shared default; personal/team require memorix_session_start with joinTeam=true.',
|
|
648
|
+
),
|
|
626
649
|
},
|
|
627
650
|
},
|
|
628
|
-
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities }) => {
|
|
651
|
+
async ({ entityName: rawEntityName, type: rawType, title: rawTitle, narrative, facts, filesModified, concepts, topicKey, progress, relatedCommits, relatedEntities, visibility }) => {
|
|
629
652
|
const unresolved = requireResolvedProject('store memory in the current project');
|
|
630
653
|
if (unresolved) return unresolved;
|
|
631
|
-
|
|
654
|
+
const requestedVisibility = (visibility ?? 'project') as 'personal' | 'project' | 'team';
|
|
655
|
+
const reader = getObservationReader();
|
|
656
|
+
if (requestedVisibility !== 'project' && !currentAgentId) {
|
|
657
|
+
return {
|
|
658
|
+
content: [{ type: 'text' as const, text: 'Personal or team memory requires memorix_session_start with joinTeam=true so Memorix can bind an owner.' }],
|
|
659
|
+
isError: true as const,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (requestedVisibility === 'team' && !reader.isTeamMember) {
|
|
663
|
+
return {
|
|
664
|
+
content: [{ type: 'text' as const, text: 'Team memory requires an active coordination membership in the current project.' }],
|
|
665
|
+
isError: true as const,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
return await withFreshIndex(async () => {
|
|
632
670
|
|
|
633
671
|
// Mutable copies — Formation Pipeline may improve these
|
|
634
672
|
let entityName = rawEntityName;
|
|
@@ -678,9 +716,9 @@ export async function createMemorixServer(
|
|
|
678
716
|
useLLM: isLLMEnabled(),
|
|
679
717
|
minValueScore: 0.3,
|
|
680
718
|
searchMemories: async (q: string, limit: number, pid: string): Promise<SearchHit[]> => {
|
|
681
|
-
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active' });
|
|
719
|
+
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active', reader });
|
|
682
720
|
if (result.entries.length === 0) return [];
|
|
683
|
-
const details = await compactDetail(result.entries.map(e => e.id));
|
|
721
|
+
const details = await compactDetail(result.entries.map(e => e.id), { reader });
|
|
684
722
|
return details.documents.map((d, i) => ({
|
|
685
723
|
id: Number(d.id.replace('obs-', '')),
|
|
686
724
|
observationId: d.observationId,
|
|
@@ -694,7 +732,7 @@ export async function createMemorixServer(
|
|
|
694
732
|
},
|
|
695
733
|
getObservation: (id: number) => {
|
|
696
734
|
const o = getObservation(id);
|
|
697
|
-
if (!o) return null;
|
|
735
|
+
if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
|
|
698
736
|
return {
|
|
699
737
|
id: o.id,
|
|
700
738
|
entityName: o.entityName,
|
|
@@ -753,7 +791,7 @@ export async function createMemorixServer(
|
|
|
753
791
|
if (action === 'merge' && targetId) {
|
|
754
792
|
// Merge into existing observation
|
|
755
793
|
const targetObs = getObservation(targetId);
|
|
756
|
-
if (targetObs) {
|
|
794
|
+
if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
|
|
757
795
|
await storeObservation({
|
|
758
796
|
entityName: targetObs.entityName,
|
|
759
797
|
type: targetObs.type,
|
|
@@ -767,6 +805,8 @@ export async function createMemorixServer(
|
|
|
767
805
|
progress: progress as import('./types.js').ProgressInfo | undefined,
|
|
768
806
|
sourceDetail: 'explicit',
|
|
769
807
|
createdByAgentId: currentAgentId,
|
|
808
|
+
visibility,
|
|
809
|
+
visibilityReader: reader,
|
|
770
810
|
});
|
|
771
811
|
return {
|
|
772
812
|
content: [{
|
|
@@ -778,7 +818,7 @@ export async function createMemorixServer(
|
|
|
778
818
|
} else if (action === 'evolve' && targetId) {
|
|
779
819
|
// Evolve existing observation
|
|
780
820
|
const targetObs = getObservation(targetId);
|
|
781
|
-
if (targetObs) {
|
|
821
|
+
if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
|
|
782
822
|
await storeObservation({
|
|
783
823
|
entityName: targetObs.entityName,
|
|
784
824
|
type: targetObs.type,
|
|
@@ -792,6 +832,8 @@ export async function createMemorixServer(
|
|
|
792
832
|
progress: progress as import('./types.js').ProgressInfo | undefined,
|
|
793
833
|
sourceDetail: 'explicit',
|
|
794
834
|
createdByAgentId: currentAgentId,
|
|
835
|
+
visibility,
|
|
836
|
+
visibilityReader: reader,
|
|
795
837
|
});
|
|
796
838
|
return {
|
|
797
839
|
content: [{
|
|
@@ -824,12 +866,13 @@ export async function createMemorixServer(
|
|
|
824
866
|
limit: 5,
|
|
825
867
|
projectId: project.id,
|
|
826
868
|
status: 'active',
|
|
869
|
+
reader,
|
|
827
870
|
});
|
|
828
871
|
const similarEntries = searchResult.entries.map(e => e);
|
|
829
872
|
if (similarEntries.length > 0) {
|
|
830
873
|
// Fetch full details for comparison
|
|
831
874
|
const similarIds = similarEntries.map(e => e.id);
|
|
832
|
-
const details = await compactDetail(similarIds);
|
|
875
|
+
const details = await compactDetail(similarIds, { reader });
|
|
833
876
|
const existingMemories: ExistingMemory[] = details.documents.map((d, i) => ({
|
|
834
877
|
id: d.observationId,
|
|
835
878
|
title: d.title,
|
|
@@ -850,7 +893,7 @@ export async function createMemorixServer(
|
|
|
850
893
|
if (decision.action === 'UPDATE' && decision.targetId) {
|
|
851
894
|
// Merge into existing memory (Mem0-style UPDATE)
|
|
852
895
|
const targetObs = getObservation(decision.targetId);
|
|
853
|
-
if (targetObs) {
|
|
896
|
+
if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
|
|
854
897
|
await storeObservation({
|
|
855
898
|
entityName: targetObs.entityName,
|
|
856
899
|
type: targetObs.type,
|
|
@@ -864,6 +907,8 @@ export async function createMemorixServer(
|
|
|
864
907
|
progress: progress as import('./types.js').ProgressInfo | undefined,
|
|
865
908
|
sourceDetail: 'explicit',
|
|
866
909
|
createdByAgentId: currentAgentId,
|
|
910
|
+
visibility,
|
|
911
|
+
visibilityReader: reader,
|
|
867
912
|
});
|
|
868
913
|
compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
|
|
869
914
|
compactMerged = true;
|
|
@@ -963,7 +1008,11 @@ export async function createMemorixServer(
|
|
|
963
1008
|
// in a different project — signals a potential wrong-bucket write.
|
|
964
1009
|
let attributionWarning = '';
|
|
965
1010
|
try {
|
|
966
|
-
const attrCheck = await checkProjectAttribution(
|
|
1011
|
+
const attrCheck = await checkProjectAttribution(
|
|
1012
|
+
entityName,
|
|
1013
|
+
project.id,
|
|
1014
|
+
filterReadableObservations(getAllObservations(), reader),
|
|
1015
|
+
);
|
|
967
1016
|
if (attrCheck.suspicious) {
|
|
968
1017
|
attributionWarning = `\n[WARN] Attribution notice: entity "${entityName}" has 0 observations in ` +
|
|
969
1018
|
`"${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" ` +
|
|
@@ -989,6 +1038,8 @@ export async function createMemorixServer(
|
|
|
989
1038
|
sourceDetail: 'explicit',
|
|
990
1039
|
valueCategory: formationResult?.evaluation.category,
|
|
991
1040
|
createdByAgentId: currentAgentId,
|
|
1041
|
+
visibility,
|
|
1042
|
+
visibilityReader: reader,
|
|
992
1043
|
});
|
|
993
1044
|
|
|
994
1045
|
// Add a reference to the entity's observations
|
|
@@ -1023,14 +1074,15 @@ export async function createMemorixServer(
|
|
|
1023
1074
|
const compactStart = Date.now();
|
|
1024
1075
|
const searchResult = await compactSearch({
|
|
1025
1076
|
query: `${title} ${narrative.substring(0, 200)}`,
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1077
|
+
limit: 5,
|
|
1078
|
+
projectId: project.id,
|
|
1079
|
+
status: 'active',
|
|
1080
|
+
reader,
|
|
1029
1081
|
});
|
|
1030
1082
|
const similarEntries = searchResult.entries.map(e => e);
|
|
1031
1083
|
if (similarEntries.length > 0) {
|
|
1032
1084
|
const similarIds = similarEntries.map(e => e.id);
|
|
1033
|
-
const details = await compactDetail(similarIds);
|
|
1085
|
+
const details = await compactDetail(similarIds, { reader });
|
|
1034
1086
|
const existingMemories: ExistingMemory[] = details.documents.map((d, i) => ({
|
|
1035
1087
|
id: d.observationId,
|
|
1036
1088
|
title: d.title,
|
|
@@ -1056,9 +1108,9 @@ export async function createMemorixServer(
|
|
|
1056
1108
|
useLLM: isLLMEnabled(),
|
|
1057
1109
|
minValueScore: 0.3,
|
|
1058
1110
|
searchMemories: async (q: string, limit: number, pid: string): Promise<SearchHit[]> => {
|
|
1059
|
-
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active' });
|
|
1111
|
+
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active', reader });
|
|
1060
1112
|
if (result.entries.length === 0) return [];
|
|
1061
|
-
const details = await compactDetail(result.entries.map(e => e.id));
|
|
1113
|
+
const details = await compactDetail(result.entries.map(e => e.id), { reader });
|
|
1062
1114
|
return details.documents.map((d, i) => ({
|
|
1063
1115
|
id: Number(d.id.replace('obs-', '')),
|
|
1064
1116
|
observationId: d.observationId,
|
|
@@ -1072,7 +1124,7 @@ export async function createMemorixServer(
|
|
|
1072
1124
|
},
|
|
1073
1125
|
getObservation: (id: number) => {
|
|
1074
1126
|
const o = getObservation(id);
|
|
1075
|
-
if (!o) return null;
|
|
1127
|
+
if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
|
|
1076
1128
|
return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
|
|
1077
1129
|
},
|
|
1078
1130
|
getEntityNames: () => graphManager.getEntityNames(),
|
|
@@ -1112,6 +1164,15 @@ export async function createMemorixServer(
|
|
|
1112
1164
|
],
|
|
1113
1165
|
};
|
|
1114
1166
|
}); // withFreshIndex
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
return {
|
|
1169
|
+
content: [{
|
|
1170
|
+
type: 'text' as const,
|
|
1171
|
+
text: error instanceof Error ? error.message : 'Failed to store memory.',
|
|
1172
|
+
}],
|
|
1173
|
+
isError: true as const,
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1115
1176
|
},
|
|
1116
1177
|
);
|
|
1117
1178
|
|
|
@@ -1208,6 +1269,7 @@ export async function createMemorixServer(
|
|
|
1208
1269
|
projectId: scope === 'global' ? undefined : project.id,
|
|
1209
1270
|
status: (status as 'active' | 'resolved' | 'archived' | 'all') ?? 'active',
|
|
1210
1271
|
source: source as 'agent' | 'git' | 'manual' | undefined,
|
|
1272
|
+
reader: getObservationReader(scope === 'global' ? 'global' : 'project'),
|
|
1211
1273
|
});
|
|
1212
1274
|
|
|
1213
1275
|
const timeoutPromise = new Promise<never>((_, reject) =>
|
|
@@ -1279,7 +1341,10 @@ export async function createMemorixServer(
|
|
|
1279
1341
|
if (unresolved) return unresolved;
|
|
1280
1342
|
|
|
1281
1343
|
const { getObservationStore } = await import('./store/obs-store.js');
|
|
1282
|
-
const observations =
|
|
1344
|
+
const observations = filterReadableObservations(
|
|
1345
|
+
await getObservationStore().loadByProject(project.id),
|
|
1346
|
+
getObservationReader(),
|
|
1347
|
+
);
|
|
1283
1348
|
const packet = buildGraphContextPacket(observations, {
|
|
1284
1349
|
projectId: project.id,
|
|
1285
1350
|
query,
|
|
@@ -1339,7 +1404,10 @@ export async function createMemorixServer(
|
|
|
1339
1404
|
import('./runtime/maintenance-jobs.js'),
|
|
1340
1405
|
import('./runtime/lifecycle.js'),
|
|
1341
1406
|
]);
|
|
1342
|
-
const observations =
|
|
1407
|
+
const observations = filterReadableObservations(
|
|
1408
|
+
await getObservationStore().loadByProject(project.id, { status: 'active' }),
|
|
1409
|
+
getObservationReader(),
|
|
1410
|
+
);
|
|
1343
1411
|
const context = await buildAutoProjectContext({
|
|
1344
1412
|
project,
|
|
1345
1413
|
dataDir: projectDir,
|
|
@@ -1441,7 +1509,10 @@ export async function createMemorixServer(
|
|
|
1441
1509
|
await store.init(projectDir);
|
|
1442
1510
|
const codegraphConfig = getResolvedConfig({ projectRoot: project.rootPath }).codegraph;
|
|
1443
1511
|
const exclude = codegraphConfig.excludePatterns;
|
|
1444
|
-
const observations =
|
|
1512
|
+
const observations = filterReadableObservations(
|
|
1513
|
+
await getObservationStore().loadByProject(project.id, { status: 'active' }),
|
|
1514
|
+
getObservationReader(),
|
|
1515
|
+
);
|
|
1445
1516
|
observations.reverse();
|
|
1446
1517
|
const basePack = assembleContextPackForTask({
|
|
1447
1518
|
store,
|
|
@@ -1863,9 +1934,15 @@ export async function createMemorixServer(
|
|
|
1863
1934
|
},
|
|
1864
1935
|
},
|
|
1865
1936
|
async ({ ids, status }) => {
|
|
1866
|
-
const { resolveObservations } = await import('./memory/observations.js');
|
|
1937
|
+
const { resolveObservations, getObservation } = await import('./memory/observations.js');
|
|
1867
1938
|
const safeIds = (Array.isArray(ids) ? ids : [ids]).map(id => coerceNumber(id, 0)).filter(id => id > 0);
|
|
1868
|
-
const
|
|
1939
|
+
const reader = getObservationReader();
|
|
1940
|
+
const authorizedIds = safeIds.filter((id) => {
|
|
1941
|
+
const observation = getObservation(id, project.id);
|
|
1942
|
+
return observation ? canManageObservation(observation, reader) : false;
|
|
1943
|
+
});
|
|
1944
|
+
const result = await resolveObservations(authorizedIds, (status as 'resolved' | 'archived') ?? 'resolved');
|
|
1945
|
+
const deniedCount = safeIds.length - authorizedIds.length;
|
|
1869
1946
|
|
|
1870
1947
|
const parts: string[] = [];
|
|
1871
1948
|
if (result.resolved.length > 0) {
|
|
@@ -1874,6 +1951,9 @@ export async function createMemorixServer(
|
|
|
1874
1951
|
if (result.notFound.length > 0) {
|
|
1875
1952
|
parts.push(`[WARN] Not found: #${result.notFound.join(', #')}`);
|
|
1876
1953
|
}
|
|
1954
|
+
if (deniedCount > 0) {
|
|
1955
|
+
parts.push(`[WARN] Skipped ${deniedCount} observation(s) outside this session's write scope.`);
|
|
1956
|
+
}
|
|
1877
1957
|
parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
|
|
1878
1958
|
parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
|
|
1879
1959
|
|
|
@@ -1919,6 +1999,7 @@ export async function createMemorixServer(
|
|
|
1919
1999
|
async ({ entityName, decision, alternatives, rationale, constraints, expectedOutcome, risks, concepts, filesModified, relatedCommits, relatedEntities }) => {
|
|
1920
2000
|
const unresolved = requireResolvedProject('store reasoning in the current project');
|
|
1921
2001
|
if (unresolved) return unresolved;
|
|
2002
|
+
const reader = getObservationReader();
|
|
1922
2003
|
return withFreshIndex(async () => {
|
|
1923
2004
|
|
|
1924
2005
|
// Build structured narrative from reasoning fields
|
|
@@ -1948,7 +2029,11 @@ export async function createMemorixServer(
|
|
|
1948
2029
|
// ── Attribution guard (passive, non-blocking) ─────────────────
|
|
1949
2030
|
let reasoningAttributionWarning = '';
|
|
1950
2031
|
try {
|
|
1951
|
-
const attrCheck = await checkProjectAttribution(
|
|
2032
|
+
const attrCheck = await checkProjectAttribution(
|
|
2033
|
+
entityName,
|
|
2034
|
+
project.id,
|
|
2035
|
+
filterReadableObservations(getAllObservations(), reader),
|
|
2036
|
+
);
|
|
1952
2037
|
if (attrCheck.suspicious) {
|
|
1953
2038
|
reasoningAttributionWarning = `\n[WARN] Attribution notice: entity "${entityName}" has 0 observations in ` +
|
|
1954
2039
|
`"${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" ` +
|
|
@@ -1970,6 +2055,7 @@ export async function createMemorixServer(
|
|
|
1970
2055
|
relatedEntities,
|
|
1971
2056
|
sourceDetail: 'explicit',
|
|
1972
2057
|
createdByAgentId: currentAgentId,
|
|
2058
|
+
visibility: 'project',
|
|
1973
2059
|
});
|
|
1974
2060
|
|
|
1975
2061
|
await graphManager.addObservations([
|
|
@@ -2013,7 +2099,11 @@ export async function createMemorixServer(
|
|
|
2013
2099
|
const minCount = threshold ?? 2;
|
|
2014
2100
|
let entries: import('./memory/attribution-guard.js').AuditEntry[];
|
|
2015
2101
|
try {
|
|
2016
|
-
entries = await auditProjectObservations(
|
|
2102
|
+
entries = await auditProjectObservations(
|
|
2103
|
+
project.id,
|
|
2104
|
+
await withFreshIndex(() => filterReadableObservations(getAllObservations(), getObservationReader())),
|
|
2105
|
+
minCount,
|
|
2106
|
+
);
|
|
2017
2107
|
} catch (err) {
|
|
2018
2108
|
return {
|
|
2019
2109
|
content: [{
|
|
@@ -2096,6 +2186,7 @@ export async function createMemorixServer(
|
|
|
2096
2186
|
type: 'reasoning' as ObservationType,
|
|
2097
2187
|
projectId: scope === 'global' ? undefined : project.id,
|
|
2098
2188
|
status: 'active',
|
|
2189
|
+
reader: getObservationReader(scope === 'global' ? 'global' : 'project'),
|
|
2099
2190
|
}));
|
|
2100
2191
|
|
|
2101
2192
|
if (result.entries.length === 0) {
|
|
@@ -2132,7 +2223,11 @@ export async function createMemorixServer(
|
|
|
2132
2223
|
},
|
|
2133
2224
|
async ({ query, dryRun }) => {
|
|
2134
2225
|
const { getAllObservations, resolveObservations } = await import('./memory/observations.js');
|
|
2135
|
-
const
|
|
2226
|
+
const reader = getObservationReader();
|
|
2227
|
+
const allObs = await withFreshIndex(() => filterReadableObservations(
|
|
2228
|
+
getAllObservations().filter(o => (o.status ?? 'active') === 'active' && o.projectId === project.id),
|
|
2229
|
+
reader,
|
|
2230
|
+
).filter((observation) => canManageObservation(observation, reader)));
|
|
2136
2231
|
|
|
2137
2232
|
if (allObs.length < 2) {
|
|
2138
2233
|
return { content: [{ type: 'text' as const, text: 'Not enough active memories to deduplicate.' }] };
|
|
@@ -2151,7 +2246,7 @@ export async function createMemorixServer(
|
|
|
2151
2246
|
// If query provided, search for relevant memories; otherwise take latest 20
|
|
2152
2247
|
let candidates: typeof allObs;
|
|
2153
2248
|
if (query) {
|
|
2154
|
-
const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: 'active' });
|
|
2249
|
+
const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: 'active', reader });
|
|
2155
2250
|
const idSet = new Set(searchResult.entries.map(e => e.id));
|
|
2156
2251
|
candidates = allObs.filter(o => idSet.has(o.id));
|
|
2157
2252
|
} else {
|
|
@@ -2257,6 +2352,7 @@ export async function createMemorixServer(
|
|
|
2257
2352
|
project.id,
|
|
2258
2353
|
safeBefore,
|
|
2259
2354
|
safeAfter,
|
|
2355
|
+
getObservationReader(),
|
|
2260
2356
|
);
|
|
2261
2357
|
|
|
2262
2358
|
return {
|
|
@@ -2307,14 +2403,17 @@ export async function createMemorixServer(
|
|
|
2307
2403
|
// Priority: typedRefs > refs > ids (each is a complete, homogeneous input path)
|
|
2308
2404
|
let result;
|
|
2309
2405
|
try {
|
|
2406
|
+
const hasCrossProjectRef = safeRefs.some((ref) => ref.projectId && ref.projectId !== project.id)
|
|
2407
|
+
|| safeTypedRefs.some((ref) => ref.includes('@') && !ref.endsWith(`@${project.id}`));
|
|
2408
|
+
const reader = getObservationReader(hasCrossProjectRef ? 'global' : 'project');
|
|
2310
2409
|
if (safeTypedRefs.length > 0) {
|
|
2311
2410
|
// Pass typed ref strings directly — compactDetail handles parsing
|
|
2312
|
-
result = await compactDetail(safeTypedRefs);
|
|
2411
|
+
result = await compactDetail(safeTypedRefs, { reader });
|
|
2313
2412
|
} else if (safeRefs.length > 0) {
|
|
2314
|
-
result = await compactDetail(safeRefs);
|
|
2413
|
+
result = await compactDetail(safeRefs, { reader });
|
|
2315
2414
|
} else {
|
|
2316
2415
|
// Bare numeric IDs are scoped to the current project
|
|
2317
|
-
result = await compactDetail(safeIds.map(id => ({ id, projectId: project.id })));
|
|
2416
|
+
result = await compactDetail(safeIds.map(id => ({ id, projectId: project.id })), { reader });
|
|
2318
2417
|
}
|
|
2319
2418
|
} catch (err) {
|
|
2320
2419
|
return { content: [{ type: 'text' as const, text: err instanceof Error ? err.message : String(err) }], isError: true };
|
|
@@ -2363,13 +2462,17 @@ export async function createMemorixServer(
|
|
|
2363
2462
|
},
|
|
2364
2463
|
async (args: { action?: string }) => {
|
|
2365
2464
|
const action = args.action ?? 'report';
|
|
2366
|
-
const { getRetentionSummary, getArchiveCandidates, rankByRelevance,
|
|
2465
|
+
const { getRetentionSummary, getArchiveCandidates, rankByRelevance, getRetentionZone, explainRetention } = await import('./memory/retention.js');
|
|
2367
2466
|
const { getDb } = await import('./store/orama-store.js');
|
|
2368
2467
|
const { search } = await import('@orama/orama');
|
|
2369
2468
|
|
|
2370
2469
|
// Shared: build MemorixDocument[] from in-memory observations
|
|
2371
|
-
const { getAllObservations } = await import('./memory/observations.js');
|
|
2372
|
-
const
|
|
2470
|
+
const { getAllObservations, resolveObservations } = await import('./memory/observations.js');
|
|
2471
|
+
const reader = getObservationReader();
|
|
2472
|
+
const allObs = await withFreshIndex(() => filterReadableObservations(
|
|
2473
|
+
getAllObservations().filter((observation) => observation.projectId === project.id),
|
|
2474
|
+
reader,
|
|
2475
|
+
));
|
|
2373
2476
|
|
|
2374
2477
|
// Pull current access metadata from the live Orama index so access-based
|
|
2375
2478
|
// immunity (e.g. accessCount >= 3) still works in retention/report/archive
|
|
@@ -2393,19 +2496,6 @@ export async function createMemorixServer(
|
|
|
2393
2496
|
// less precise immunity/reporting.
|
|
2394
2497
|
}
|
|
2395
2498
|
|
|
2396
|
-
// Handle archive action
|
|
2397
|
-
if (action === 'archive') {
|
|
2398
|
-
const result = await archiveExpired(projectDir, undefined, accessMap, project.id);
|
|
2399
|
-
if (result.archived === 0) {
|
|
2400
|
-
return {
|
|
2401
|
-
content: [{ type: 'text' as const, text: '[OK] No expired observations to archive. All memories are within their retention period.' }],
|
|
2402
|
-
};
|
|
2403
|
-
}
|
|
2404
|
-
return {
|
|
2405
|
-
content: [{ type: 'text' as const, text: `[ARCHIVED] Archived ${result.archived} expired observations (status set to 'archived' in-place)\n${result.remaining} active observations remaining.\n\nArchived memories are hidden from default search but can be found with status: "all".` }],
|
|
2406
|
-
};
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
2499
|
const docs: import('./types.js').MemorixDocument[] = allObs.map(obs => ({
|
|
2410
2500
|
id: `obs-${obs.id}`,
|
|
2411
2501
|
observationId: obs.id,
|
|
@@ -2425,6 +2515,11 @@ export async function createMemorixServer(
|
|
|
2425
2515
|
source: obs.source ?? 'agent',
|
|
2426
2516
|
sourceDetail: obs.sourceDetail ?? '',
|
|
2427
2517
|
valueCategory: obs.valueCategory ?? '',
|
|
2518
|
+
admissionState: obs.admissionState ?? '',
|
|
2519
|
+
admissionReason: obs.admissionReason ?? '',
|
|
2520
|
+
visibility: obs.visibility ?? 'project',
|
|
2521
|
+
createdByAgentId: obs.createdByAgentId ?? '',
|
|
2522
|
+
sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
|
|
2428
2523
|
}));
|
|
2429
2524
|
|
|
2430
2525
|
if (docs.length === 0) {
|
|
@@ -2433,6 +2528,24 @@ export async function createMemorixServer(
|
|
|
2433
2528
|
};
|
|
2434
2529
|
}
|
|
2435
2530
|
|
|
2531
|
+
if (action === 'archive') {
|
|
2532
|
+
const managedIds = new Set(
|
|
2533
|
+
allObs
|
|
2534
|
+
.filter((observation) => canManageObservation(observation, reader))
|
|
2535
|
+
.map((observation) => observation.id),
|
|
2536
|
+
);
|
|
2537
|
+
const candidates = getArchiveCandidates(docs).filter((document) => managedIds.has(document.observationId));
|
|
2538
|
+
if (candidates.length === 0) {
|
|
2539
|
+
return {
|
|
2540
|
+
content: [{ type: 'text' as const, text: '[OK] No expired memories in this session\'s write scope to archive.' }],
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
const result = await resolveObservations(candidates.map((document) => document.observationId), 'archived');
|
|
2544
|
+
return {
|
|
2545
|
+
content: [{ type: 'text' as const, text: `[ARCHIVED] Archived ${result.resolved.length} expired observation(s) in this session's write scope.\n${Math.max(0, managedIds.size - result.resolved.length)} visible writable observations remaining.` }],
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2436
2549
|
// ── action="stale": full table of stale observations with explanation ──
|
|
2437
2550
|
if (action === 'stale') {
|
|
2438
2551
|
const staleDocs = docs.filter(d => getRetentionZone(d) === 'stale');
|
|
@@ -2807,7 +2920,10 @@ export async function createMemorixServer(
|
|
|
2807
2920
|
const allObs = await withFreshIndex(() => getAllObservations());
|
|
2808
2921
|
const scoped = scopeKnowledgeGraphToProject(
|
|
2809
2922
|
graph,
|
|
2810
|
-
|
|
2923
|
+
filterReadableObservations(
|
|
2924
|
+
allObs.filter(observation => observation.projectId === project.id),
|
|
2925
|
+
getObservationReader(),
|
|
2926
|
+
),
|
|
2811
2927
|
);
|
|
2812
2928
|
return { entities: scoped.entities, relations: scoped.relations };
|
|
2813
2929
|
}
|
|
@@ -3149,12 +3265,10 @@ export async function createMemorixServer(
|
|
|
3149
3265
|
|
|
3150
3266
|
// action === 'generate'
|
|
3151
3267
|
const { getObservationStore: getStore } = await import('./store/obs-store.js');
|
|
3152
|
-
const allObs =
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
status?: string; source?: 'agent' | 'git' | 'manual';
|
|
3157
|
-
}>;
|
|
3268
|
+
const allObs = filterReadableObservations(
|
|
3269
|
+
(await getStore().loadAll()).filter((observation) => observation.projectId === project.id),
|
|
3270
|
+
getObservationReader(),
|
|
3271
|
+
);
|
|
3158
3272
|
|
|
3159
3273
|
const obsData = allObs.map(o => ({
|
|
3160
3274
|
id: o.id || 0,
|
|
@@ -3286,7 +3400,11 @@ export async function createMemorixServer(
|
|
|
3286
3400
|
// Load observations by ID — only active observations can be promoted
|
|
3287
3401
|
const { getAllObservations } = await import('./memory/observations.js');
|
|
3288
3402
|
const allObs = await withFreshIndex(() => getAllObservations());
|
|
3289
|
-
const
|
|
3403
|
+
const reader = getObservationReader();
|
|
3404
|
+
const matched = filterReadableObservations(
|
|
3405
|
+
allObs.filter((observation) => observation.projectId === project.id && observationIds.includes(observation.id)),
|
|
3406
|
+
reader,
|
|
3407
|
+
);
|
|
3290
3408
|
|
|
3291
3409
|
if (matched.length === 0) {
|
|
3292
3410
|
return { content: [{ type: 'text' as const, text: `No observations found for IDs: [${observationIds.join(', ')}]. Use \`memorix_search\` to find valid IDs.` }], isError: true };
|
|
@@ -3298,6 +3416,14 @@ export async function createMemorixServer(
|
|
|
3298
3416
|
return { content: [{ type: 'text' as const, text: `Cannot promote: ${nonActive.length} observation(s) are not active: ${nonActive.map(o => `#${o.id} (${o.status})`).join(', ')}. Only active observations can be promoted to permanent knowledge.` }], isError: true };
|
|
3299
3417
|
}
|
|
3300
3418
|
|
|
3419
|
+
const nonProjectShared = matched.filter((observation) => resolveObservationVisibility(observation) !== 'project');
|
|
3420
|
+
if (nonProjectShared.length > 0) {
|
|
3421
|
+
return {
|
|
3422
|
+
content: [{ type: 'text' as const, text: `Cannot promote private or team-scoped observations: ${nonProjectShared.map((observation) => `#${observation.id}`).join(', ')}. Promote only deliberate project-shared knowledge.` }],
|
|
3423
|
+
isError: true,
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3301
3427
|
const skill = await promoteToMiniSkill(projectDir, project.id, matched, { trigger, instruction, tags });
|
|
3302
3428
|
|
|
3303
3429
|
const lines = [
|
|
@@ -3531,8 +3657,11 @@ export async function createMemorixServer(
|
|
|
3531
3657
|
const lastSeen = registeredAgent.last_seen_obs_generation;
|
|
3532
3658
|
const store = getObservationStore();
|
|
3533
3659
|
const currentGen = store.getGeneration();
|
|
3534
|
-
const projectObs = await withFreshIndex(() =>
|
|
3535
|
-
|
|
3660
|
+
const projectObs = await withFreshIndex(() => filterReadableObservations(
|
|
3661
|
+
getAllObservations().filter(
|
|
3662
|
+
o => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen,
|
|
3663
|
+
),
|
|
3664
|
+
getObservationReader(),
|
|
3536
3665
|
));
|
|
3537
3666
|
const wm = computeWatermark(lastSeen, currentGen, projectObs.length);
|
|
3538
3667
|
if (wm.newObservationCount > 0) {
|
|
@@ -3735,7 +3864,7 @@ export async function createMemorixServer(
|
|
|
3735
3864
|
title: 'Transfer Memories',
|
|
3736
3865
|
description:
|
|
3737
3866
|
'Export or import project memories. ' +
|
|
3738
|
-
'Action "export": export observations and sessions (JSON or Markdown). ' +
|
|
3867
|
+
'Action "export": export observations visible to the current agent and project sessions (JSON or Markdown). ' +
|
|
3739
3868
|
'Action "import": import from a JSON export (re-assigns IDs, skips duplicate topicKeys).',
|
|
3740
3869
|
inputSchema: {
|
|
3741
3870
|
action: z.enum(['export', 'import']).describe('Operation: export or import'),
|
|
@@ -3747,10 +3876,10 @@ export async function createMemorixServer(
|
|
|
3747
3876
|
if (action === 'export') {
|
|
3748
3877
|
const { exportAsJson, exportAsMarkdown } = await import('./memory/export-import.js');
|
|
3749
3878
|
if (format === 'markdown') {
|
|
3750
|
-
const md = await exportAsMarkdown(projectDir, project.id);
|
|
3879
|
+
const md = await exportAsMarkdown(projectDir, project.id, getObservationReader());
|
|
3751
3880
|
return { content: [{ type: 'text' as const, text: md }] };
|
|
3752
3881
|
}
|
|
3753
|
-
const data = await exportAsJson(projectDir, project.id);
|
|
3882
|
+
const data = await exportAsJson(projectDir, project.id, getObservationReader());
|
|
3754
3883
|
const json = JSON.stringify(data, null, 2);
|
|
3755
3884
|
return {
|
|
3756
3885
|
content: [{
|
|
@@ -3972,8 +4101,6 @@ export async function createMemorixServer(
|
|
|
3972
4101
|
|
|
3973
4102
|
// Use shared TeamStore (from HTTP server) or create new one (stdio mode).
|
|
3974
4103
|
// All team state is canonical in SQLite — no JSON persistence, no sync/flush.
|
|
3975
|
-
let teamStore!: import('./team/team-store.js').TeamStore;
|
|
3976
|
-
let initTeamStoreForProject: ((dataDir: string) => Promise<import('./team/team-store.js').TeamStore>) | undefined;
|
|
3977
4104
|
if (teamFeaturesEnabled) {
|
|
3978
4105
|
const { initTeamStore } = await import('./team/team-store.js');
|
|
3979
4106
|
initTeamStoreForProject = initTeamStore;
|
|
@@ -4259,7 +4386,7 @@ export async function createMemorixServer(
|
|
|
4259
4386
|
'Action "inbox": read this agent\'s inbox.',
|
|
4260
4387
|
inputSchema: {
|
|
4261
4388
|
action: z.enum(['send', 'broadcast', 'inbox']).describe('Operation to perform'),
|
|
4262
|
-
from: z.string().optional().describe('
|
|
4389
|
+
from: z.string().optional().describe('Your sender agent ID. Omit it to use this session identity.'),
|
|
4263
4390
|
to: z.string().optional().describe('Receiver agent ID (for send)'),
|
|
4264
4391
|
type: z.enum(['request', 'response', 'info', 'announcement', 'contract', 'error', 'handoff']).optional().describe('Message type (for send/broadcast)'),
|
|
4265
4392
|
content: z.string().optional().describe('Message content (for send/broadcast)'),
|
|
@@ -4270,13 +4397,20 @@ export async function createMemorixServer(
|
|
|
4270
4397
|
},
|
|
4271
4398
|
},
|
|
4272
4399
|
async ({ action, from, to, type: msgType, content, agentId, markRead, toRole, handoffStatus }) => {
|
|
4400
|
+
const requireCurrentTeamAgent = () => {
|
|
4401
|
+
if (!currentAgentId) return null;
|
|
4402
|
+
const agent = teamStore.getAgent(currentAgentId);
|
|
4403
|
+
return agent?.project_id === project.id && agent.status === 'active' ? agent : null;
|
|
4404
|
+
};
|
|
4273
4405
|
if (action === 'send') {
|
|
4274
|
-
|
|
4406
|
+
const sender = requireCurrentTeamAgent();
|
|
4407
|
+
if (!sender || !msgType || !content) return { content: [{ type: 'text' as const, text: '[ERROR] active session identity, type, and content required for send' }], isError: true };
|
|
4408
|
+
if (from && from !== currentAgentId) return { content: [{ type: 'text' as const, text: '[ERROR] from must match the current session identity' }], isError: true };
|
|
4275
4409
|
if (!to && !toRole) return { content: [{ type: 'text' as const, text: '[ERROR] either to (agent ID) or toRole is required for send' }], isError: true };
|
|
4276
4410
|
if (content.length > 10_000) return { content: [{ type: 'text' as const, text: '[ERROR] Message too large (max 10KB)' }], isError: true };
|
|
4277
4411
|
const msg = teamStore.sendMessage({
|
|
4278
4412
|
projectId: project.id,
|
|
4279
|
-
senderAgentId:
|
|
4413
|
+
senderAgentId: currentAgentId!,
|
|
4280
4414
|
recipientAgentId: to ?? null,
|
|
4281
4415
|
type: msgType,
|
|
4282
4416
|
content,
|
|
@@ -4288,11 +4422,13 @@ export async function createMemorixServer(
|
|
|
4288
4422
|
return { content: [{ type: 'text' as const, text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}…${toRole ? ` [role: ${toRole}]` : ''}` }] };
|
|
4289
4423
|
}
|
|
4290
4424
|
if (action === 'broadcast') {
|
|
4291
|
-
|
|
4425
|
+
const sender = requireCurrentTeamAgent();
|
|
4426
|
+
if (!sender || !msgType || !content) return { content: [{ type: 'text' as const, text: '[ERROR] active session identity, type, and content required for broadcast' }], isError: true };
|
|
4427
|
+
if (from && from !== currentAgentId) return { content: [{ type: 'text' as const, text: '[ERROR] from must match the current session identity' }], isError: true };
|
|
4292
4428
|
if (content.length > 10_000) return { content: [{ type: 'text' as const, text: '[ERROR] Message too large (max 10KB)' }], isError: true };
|
|
4293
4429
|
const msg = teamStore.sendMessage({
|
|
4294
4430
|
projectId: project.id,
|
|
4295
|
-
senderAgentId:
|
|
4431
|
+
senderAgentId: currentAgentId!,
|
|
4296
4432
|
recipientAgentId: null,
|
|
4297
4433
|
type: msgType,
|
|
4298
4434
|
content,
|
|
@@ -4301,8 +4437,12 @@ export async function createMemorixServer(
|
|
|
4301
4437
|
return { content: [{ type: 'text' as const, text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}…` }] };
|
|
4302
4438
|
}
|
|
4303
4439
|
// inbox
|
|
4304
|
-
const
|
|
4305
|
-
if (!
|
|
4440
|
+
const inboxAgent = requireCurrentTeamAgent();
|
|
4441
|
+
if (!inboxAgent) return { content: [{ type: 'text' as const, text: '[ERROR] active session identity required for inbox' }], isError: true };
|
|
4442
|
+
if ((agentId && agentId !== currentAgentId) || (from && from !== currentAgentId)) {
|
|
4443
|
+
return { content: [{ type: 'text' as const, text: '[ERROR] inbox access is limited to the current session identity' }], isError: true };
|
|
4444
|
+
}
|
|
4445
|
+
const inboxId = currentAgentId!;
|
|
4306
4446
|
const inbox = teamStore.getInbox(project.id, inboxId);
|
|
4307
4447
|
const unread = teamStore.getUnreadCount(project.id, inboxId);
|
|
4308
4448
|
if (inbox.length === 0) return { content: [{ type: 'text' as const, text: 'Inbox empty' }] };
|
|
@@ -4334,32 +4474,42 @@ export async function createMemorixServer(
|
|
|
4334
4474
|
},
|
|
4335
4475
|
async ({ agentId, markInboxRead }) => {
|
|
4336
4476
|
const { computeWatermark, computePoll } = await import('./team/poll.js');
|
|
4477
|
+
if (agentId && agentId !== currentAgentId) {
|
|
4478
|
+
return {
|
|
4479
|
+
content: [{ type: 'text' as const, text: '[ERROR] agentId must match the current session identity.' }],
|
|
4480
|
+
isError: true as const,
|
|
4481
|
+
};
|
|
4482
|
+
}
|
|
4483
|
+
const effectiveAgentId = currentAgentId;
|
|
4337
4484
|
|
|
4338
4485
|
// Compute watermark — requires observation store access
|
|
4339
4486
|
let watermark = computeWatermark(0, 0, 0);
|
|
4340
|
-
if (
|
|
4341
|
-
const agent = teamStore.getAgent(
|
|
4487
|
+
if (effectiveAgentId) {
|
|
4488
|
+
const agent = teamStore.getAgent(effectiveAgentId);
|
|
4342
4489
|
if (agent) {
|
|
4343
4490
|
const lastSeen = agent.last_seen_obs_generation;
|
|
4344
4491
|
const store = getObservationStore();
|
|
4345
4492
|
const currentGen = store.getGeneration();
|
|
4346
|
-
const projectObs = await withFreshIndex(() =>
|
|
4347
|
-
|
|
4493
|
+
const projectObs = await withFreshIndex(() => filterReadableObservations(
|
|
4494
|
+
getAllObservations().filter(
|
|
4495
|
+
o => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen,
|
|
4496
|
+
),
|
|
4497
|
+
getObservationReader(),
|
|
4348
4498
|
));
|
|
4349
4499
|
watermark = computeWatermark(lastSeen, currentGen, projectObs.length);
|
|
4350
4500
|
|
|
4351
4501
|
// Advance watermark so next poll sees only truly new observations
|
|
4352
|
-
teamStore.updateWatermark(
|
|
4502
|
+
teamStore.updateWatermark(effectiveAgentId, currentGen);
|
|
4353
4503
|
// Heartbeat — proves this agent is alive
|
|
4354
|
-
teamStore.heartbeat(
|
|
4504
|
+
teamStore.heartbeat(effectiveAgentId);
|
|
4355
4505
|
}
|
|
4356
4506
|
}
|
|
4357
4507
|
|
|
4358
|
-
const poll = computePoll(teamStore, project.id,
|
|
4508
|
+
const poll = computePoll(teamStore, project.id, effectiveAgentId ?? null, watermark);
|
|
4359
4509
|
|
|
4360
4510
|
// Optionally mark inbox as read
|
|
4361
|
-
if (markInboxRead &&
|
|
4362
|
-
teamStore.markAllRead(project.id,
|
|
4511
|
+
if (markInboxRead && effectiveAgentId) {
|
|
4512
|
+
teamStore.markAllRead(project.id, effectiveAgentId);
|
|
4363
4513
|
}
|
|
4364
4514
|
|
|
4365
4515
|
// Format as readable text
|
|
@@ -4432,7 +4582,8 @@ export async function createMemorixServer(
|
|
|
4432
4582
|
title: 'Team Handoff — Agent Context Transfer',
|
|
4433
4583
|
description:
|
|
4434
4584
|
'Create a structured handoff artifact when passing work to another agent. ' +
|
|
4435
|
-
'
|
|
4585
|
+
'A targeted handoff is visible only to its sender and recipient; a broadcast handoff is team-visible. ' +
|
|
4586
|
+
'The handoff is stored as a durable observation (immune to archival) ' +
|
|
4436
4587
|
'and a notification message is sent to the recipient. ' +
|
|
4437
4588
|
'Use this when completing a task and another agent should continue, ' +
|
|
4438
4589
|
'or when you want to leave context for whoever works on this next.',
|
|
@@ -4448,6 +4599,34 @@ export async function createMemorixServer(
|
|
|
4448
4599
|
},
|
|
4449
4600
|
async ({ fromAgentId, summary, context, toAgentId, taskId, filesModified, concepts }) => {
|
|
4450
4601
|
const { createHandoffArtifact } = await import('./team/handoff.js');
|
|
4602
|
+
if (!currentAgentId) {
|
|
4603
|
+
return {
|
|
4604
|
+
content: [{ type: 'text' as const, text: 'Create a coordination identity first: call memorix_session_start with joinTeam=true.' }],
|
|
4605
|
+
isError: true as const,
|
|
4606
|
+
};
|
|
4607
|
+
}
|
|
4608
|
+
if (fromAgentId !== currentAgentId) {
|
|
4609
|
+
return {
|
|
4610
|
+
content: [{ type: 'text' as const, text: 'fromAgentId must match the identity returned for this session. Memorix will not create a handoff on behalf of another agent.' }],
|
|
4611
|
+
isError: true as const,
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
const sender = teamStore.getAgent(currentAgentId);
|
|
4615
|
+
if (!sender || sender.project_id !== project.id || sender.status !== 'active') {
|
|
4616
|
+
return {
|
|
4617
|
+
content: [{ type: 'text' as const, text: 'The current coordination identity is not an active member of this project.' }],
|
|
4618
|
+
isError: true as const,
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
if (toAgentId) {
|
|
4622
|
+
const recipient = teamStore.getAgent(toAgentId);
|
|
4623
|
+
if (!recipient || recipient.project_id !== project.id) {
|
|
4624
|
+
return {
|
|
4625
|
+
content: [{ type: 'text' as const, text: 'The handoff recipient must be an agent registered in the current project.' }],
|
|
4626
|
+
isError: true as const,
|
|
4627
|
+
};
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4451
4630
|
|
|
4452
4631
|
const result = await createHandoffArtifact(
|
|
4453
4632
|
{
|