memorix 1.2.0 → 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 +30 -1
- package/README.md +18 -4
- package/README.zh-CN.md +18 -4
- package/TEAM.md +86 -86
- package/dist/cli/index.js +15919 -14055
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1997 -1021
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8481 -8005
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +30 -1
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +2022 -1024
- 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 +27 -5
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/DEVELOPMENT.md +4 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +7 -1
- package/docs/dev-log/progress.txt +91 -11
- package/docs/knowledge/workflows/memorix-release.md +57 -0
- 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 +17 -8
- 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/knowledge.ts +40 -0
- 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 +22 -43
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +29 -3
- package/src/cli/commands/setup.ts +9 -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 +34 -17
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/current-facts.ts +19 -1
- package/src/codegraph/project-context.ts +2 -0
- package/src/codegraph/task-lens.ts +49 -5
- 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 +58 -52
- 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/claims.ts +51 -1
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/types.ts +1 -0
- package/src/knowledge/workflows.ts +34 -3
- 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/auto-relations.ts +21 -0
- 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-scope.ts +46 -0
- 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/verify-gate.ts +33 -10
- 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 +334 -93
- 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
|
@@ -28,12 +28,14 @@ import { initSessionStore } from './store/session-store.js';
|
|
|
28
28
|
import { checkProjectAttribution, auditProjectObservations } from './memory/attribution-guard.js';
|
|
29
29
|
import { createAutoRelations } from './memory/auto-relations.js';
|
|
30
30
|
import { extractEntities } from './memory/entity-extractor.js';
|
|
31
|
+
import { scopeKnowledgeGraphToProject } from './memory/graph-scope.js';
|
|
32
|
+
import { canManageObservation, canReadObservation, filterReadableObservations, resolveObservationVisibility } from './memory/visibility.js';
|
|
31
33
|
import { compactSearch, compactTimeline, compactDetail } from './compact/engine.js';
|
|
32
34
|
import { buildGraphContextPacket, formatGraphContextPrompt } from './memory/graph-context.js';
|
|
33
35
|
import { detectProject } from './project/detector.js';
|
|
34
36
|
import { registerAlias, initAliasRegistry, resolveAliases, autoMergeByBaseName } from './project/aliases.js';
|
|
35
37
|
import { getProjectDataDir } from './store/persistence.js';
|
|
36
|
-
import type { ObservationType, RuleSource, AgentTarget, MCPServerEntry } from './types.js';
|
|
38
|
+
import type { ObservationType, RuleSource, AgentTarget, MCPServerEntry, ObservationReader } from './types.js';
|
|
37
39
|
import { RulesSyncer } from './rules/syncer.js';
|
|
38
40
|
import { WorkspaceSyncEngine } from './workspace/engine.js';
|
|
39
41
|
import {
|
|
@@ -260,8 +262,10 @@ export async function createMemorixServer(
|
|
|
260
262
|
let rawProject: import('./types.js').ProjectInfo;
|
|
261
263
|
let projectResolved = true;
|
|
262
264
|
let projectResolutionError: string | null = null;
|
|
263
|
-
|
|
264
|
-
|
|
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;
|
|
265
269
|
if (detectedProject) {
|
|
266
270
|
rawProject = detectedProject;
|
|
267
271
|
} else {
|
|
@@ -581,6 +585,23 @@ export async function createMemorixServer(
|
|
|
581
585
|
return (originalRegisterTool as (...innerArgs: unknown[]) => unknown)(name, ...args) as never;
|
|
582
586
|
}) as typeof server.registerTool;
|
|
583
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
|
+
|
|
584
605
|
// ================================================================
|
|
585
606
|
// Memorix Extended Tools (3-layer Progressive Disclosure)
|
|
586
607
|
// ================================================================
|
|
@@ -601,7 +622,7 @@ export async function createMemorixServer(
|
|
|
601
622
|
'problem-solution ([FIX] bug fix), how-it-works ([INFO] explanation), what-changed ([CHANGE] change), ' +
|
|
602
623
|
'discovery ([DISCOVERY] insight), why-it-exists ([WHY] rationale), trade-off ([TRADEOFF] compromise), ' +
|
|
603
624
|
'session-request ([SESSION] original goal). ' +
|
|
604
|
-
'
|
|
625
|
+
'Project visibility is the default. Personal or team visibility requires an explicitly joined coordination identity.',
|
|
605
626
|
inputSchema: {
|
|
606
627
|
entityName: z.string().describe('The entity this observation belongs to (e.g., "auth-module", "port-config")'),
|
|
607
628
|
type: z.enum(OBSERVATION_TYPES).describe('Observation type for classification'),
|
|
@@ -622,12 +643,30 @@ export async function createMemorixServer(
|
|
|
622
643
|
}).optional().describe('Progress tracking for task/feature observations'),
|
|
623
644
|
relatedCommits: z.array(z.string()).optional().describe('Git commit hashes this memory relates to (links ground truth ↔ reasoning)'),
|
|
624
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
|
+
),
|
|
625
649
|
},
|
|
626
650
|
},
|
|
627
|
-
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 }) => {
|
|
628
652
|
const unresolved = requireResolvedProject('store memory in the current project');
|
|
629
653
|
if (unresolved) return unresolved;
|
|
630
|
-
|
|
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 () => {
|
|
631
670
|
|
|
632
671
|
// Mutable copies — Formation Pipeline may improve these
|
|
633
672
|
let entityName = rawEntityName;
|
|
@@ -677,9 +716,9 @@ export async function createMemorixServer(
|
|
|
677
716
|
useLLM: isLLMEnabled(),
|
|
678
717
|
minValueScore: 0.3,
|
|
679
718
|
searchMemories: async (q: string, limit: number, pid: string): Promise<SearchHit[]> => {
|
|
680
|
-
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active' });
|
|
719
|
+
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active', reader });
|
|
681
720
|
if (result.entries.length === 0) return [];
|
|
682
|
-
const details = await compactDetail(result.entries.map(e => e.id));
|
|
721
|
+
const details = await compactDetail(result.entries.map(e => e.id), { reader });
|
|
683
722
|
return details.documents.map((d, i) => ({
|
|
684
723
|
id: Number(d.id.replace('obs-', '')),
|
|
685
724
|
observationId: d.observationId,
|
|
@@ -693,7 +732,7 @@ export async function createMemorixServer(
|
|
|
693
732
|
},
|
|
694
733
|
getObservation: (id: number) => {
|
|
695
734
|
const o = getObservation(id);
|
|
696
|
-
if (!o) return null;
|
|
735
|
+
if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
|
|
697
736
|
return {
|
|
698
737
|
id: o.id,
|
|
699
738
|
entityName: o.entityName,
|
|
@@ -752,7 +791,7 @@ export async function createMemorixServer(
|
|
|
752
791
|
if (action === 'merge' && targetId) {
|
|
753
792
|
// Merge into existing observation
|
|
754
793
|
const targetObs = getObservation(targetId);
|
|
755
|
-
if (targetObs) {
|
|
794
|
+
if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
|
|
756
795
|
await storeObservation({
|
|
757
796
|
entityName: targetObs.entityName,
|
|
758
797
|
type: targetObs.type,
|
|
@@ -766,6 +805,8 @@ export async function createMemorixServer(
|
|
|
766
805
|
progress: progress as import('./types.js').ProgressInfo | undefined,
|
|
767
806
|
sourceDetail: 'explicit',
|
|
768
807
|
createdByAgentId: currentAgentId,
|
|
808
|
+
visibility,
|
|
809
|
+
visibilityReader: reader,
|
|
769
810
|
});
|
|
770
811
|
return {
|
|
771
812
|
content: [{
|
|
@@ -777,7 +818,7 @@ export async function createMemorixServer(
|
|
|
777
818
|
} else if (action === 'evolve' && targetId) {
|
|
778
819
|
// Evolve existing observation
|
|
779
820
|
const targetObs = getObservation(targetId);
|
|
780
|
-
if (targetObs) {
|
|
821
|
+
if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
|
|
781
822
|
await storeObservation({
|
|
782
823
|
entityName: targetObs.entityName,
|
|
783
824
|
type: targetObs.type,
|
|
@@ -791,6 +832,8 @@ export async function createMemorixServer(
|
|
|
791
832
|
progress: progress as import('./types.js').ProgressInfo | undefined,
|
|
792
833
|
sourceDetail: 'explicit',
|
|
793
834
|
createdByAgentId: currentAgentId,
|
|
835
|
+
visibility,
|
|
836
|
+
visibilityReader: reader,
|
|
794
837
|
});
|
|
795
838
|
return {
|
|
796
839
|
content: [{
|
|
@@ -823,12 +866,13 @@ export async function createMemorixServer(
|
|
|
823
866
|
limit: 5,
|
|
824
867
|
projectId: project.id,
|
|
825
868
|
status: 'active',
|
|
869
|
+
reader,
|
|
826
870
|
});
|
|
827
871
|
const similarEntries = searchResult.entries.map(e => e);
|
|
828
872
|
if (similarEntries.length > 0) {
|
|
829
873
|
// Fetch full details for comparison
|
|
830
874
|
const similarIds = similarEntries.map(e => e.id);
|
|
831
|
-
const details = await compactDetail(similarIds);
|
|
875
|
+
const details = await compactDetail(similarIds, { reader });
|
|
832
876
|
const existingMemories: ExistingMemory[] = details.documents.map((d, i) => ({
|
|
833
877
|
id: d.observationId,
|
|
834
878
|
title: d.title,
|
|
@@ -849,7 +893,7 @@ export async function createMemorixServer(
|
|
|
849
893
|
if (decision.action === 'UPDATE' && decision.targetId) {
|
|
850
894
|
// Merge into existing memory (Mem0-style UPDATE)
|
|
851
895
|
const targetObs = getObservation(decision.targetId);
|
|
852
|
-
if (targetObs) {
|
|
896
|
+
if (targetObs && targetObs.projectId === project.id && canReadObservation(targetObs, reader)) {
|
|
853
897
|
await storeObservation({
|
|
854
898
|
entityName: targetObs.entityName,
|
|
855
899
|
type: targetObs.type,
|
|
@@ -863,6 +907,8 @@ export async function createMemorixServer(
|
|
|
863
907
|
progress: progress as import('./types.js').ProgressInfo | undefined,
|
|
864
908
|
sourceDetail: 'explicit',
|
|
865
909
|
createdByAgentId: currentAgentId,
|
|
910
|
+
visibility,
|
|
911
|
+
visibilityReader: reader,
|
|
866
912
|
});
|
|
867
913
|
compactAction = `[UPDATED] Compact UPDATE: merged into #${decision.targetId} (${decision.reason})`;
|
|
868
914
|
compactMerged = true;
|
|
@@ -962,7 +1008,11 @@ export async function createMemorixServer(
|
|
|
962
1008
|
// in a different project — signals a potential wrong-bucket write.
|
|
963
1009
|
let attributionWarning = '';
|
|
964
1010
|
try {
|
|
965
|
-
const attrCheck = await checkProjectAttribution(
|
|
1011
|
+
const attrCheck = await checkProjectAttribution(
|
|
1012
|
+
entityName,
|
|
1013
|
+
project.id,
|
|
1014
|
+
filterReadableObservations(getAllObservations(), reader),
|
|
1015
|
+
);
|
|
966
1016
|
if (attrCheck.suspicious) {
|
|
967
1017
|
attributionWarning = `\n[WARN] Attribution notice: entity "${entityName}" has 0 observations in ` +
|
|
968
1018
|
`"${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" ` +
|
|
@@ -988,6 +1038,8 @@ export async function createMemorixServer(
|
|
|
988
1038
|
sourceDetail: 'explicit',
|
|
989
1039
|
valueCategory: formationResult?.evaluation.category,
|
|
990
1040
|
createdByAgentId: currentAgentId,
|
|
1041
|
+
visibility,
|
|
1042
|
+
visibilityReader: reader,
|
|
991
1043
|
});
|
|
992
1044
|
|
|
993
1045
|
// Add a reference to the entity's observations
|
|
@@ -1022,14 +1074,15 @@ export async function createMemorixServer(
|
|
|
1022
1074
|
const compactStart = Date.now();
|
|
1023
1075
|
const searchResult = await compactSearch({
|
|
1024
1076
|
query: `${title} ${narrative.substring(0, 200)}`,
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1077
|
+
limit: 5,
|
|
1078
|
+
projectId: project.id,
|
|
1079
|
+
status: 'active',
|
|
1080
|
+
reader,
|
|
1028
1081
|
});
|
|
1029
1082
|
const similarEntries = searchResult.entries.map(e => e);
|
|
1030
1083
|
if (similarEntries.length > 0) {
|
|
1031
1084
|
const similarIds = similarEntries.map(e => e.id);
|
|
1032
|
-
const details = await compactDetail(similarIds);
|
|
1085
|
+
const details = await compactDetail(similarIds, { reader });
|
|
1033
1086
|
const existingMemories: ExistingMemory[] = details.documents.map((d, i) => ({
|
|
1034
1087
|
id: d.observationId,
|
|
1035
1088
|
title: d.title,
|
|
@@ -1055,9 +1108,9 @@ export async function createMemorixServer(
|
|
|
1055
1108
|
useLLM: isLLMEnabled(),
|
|
1056
1109
|
minValueScore: 0.3,
|
|
1057
1110
|
searchMemories: async (q: string, limit: number, pid: string): Promise<SearchHit[]> => {
|
|
1058
|
-
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active' });
|
|
1111
|
+
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active', reader });
|
|
1059
1112
|
if (result.entries.length === 0) return [];
|
|
1060
|
-
const details = await compactDetail(result.entries.map(e => e.id));
|
|
1113
|
+
const details = await compactDetail(result.entries.map(e => e.id), { reader });
|
|
1061
1114
|
return details.documents.map((d, i) => ({
|
|
1062
1115
|
id: Number(d.id.replace('obs-', '')),
|
|
1063
1116
|
observationId: d.observationId,
|
|
@@ -1071,7 +1124,7 @@ export async function createMemorixServer(
|
|
|
1071
1124
|
},
|
|
1072
1125
|
getObservation: (id: number) => {
|
|
1073
1126
|
const o = getObservation(id);
|
|
1074
|
-
if (!o) return null;
|
|
1127
|
+
if (!o || o.projectId !== project.id || !canReadObservation(o, reader)) return null;
|
|
1075
1128
|
return { id: o.id, entityName: o.entityName, type: o.type, title: o.title, narrative: o.narrative, facts: o.facts, topicKey: o.topicKey };
|
|
1076
1129
|
},
|
|
1077
1130
|
getEntityNames: () => graphManager.getEntityNames(),
|
|
@@ -1111,6 +1164,15 @@ export async function createMemorixServer(
|
|
|
1111
1164
|
],
|
|
1112
1165
|
};
|
|
1113
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
|
+
}
|
|
1114
1176
|
},
|
|
1115
1177
|
);
|
|
1116
1178
|
|
|
@@ -1207,6 +1269,7 @@ export async function createMemorixServer(
|
|
|
1207
1269
|
projectId: scope === 'global' ? undefined : project.id,
|
|
1208
1270
|
status: (status as 'active' | 'resolved' | 'archived' | 'all') ?? 'active',
|
|
1209
1271
|
source: source as 'agent' | 'git' | 'manual' | undefined,
|
|
1272
|
+
reader: getObservationReader(scope === 'global' ? 'global' : 'project'),
|
|
1210
1273
|
});
|
|
1211
1274
|
|
|
1212
1275
|
const timeoutPromise = new Promise<never>((_, reject) =>
|
|
@@ -1278,7 +1341,10 @@ export async function createMemorixServer(
|
|
|
1278
1341
|
if (unresolved) return unresolved;
|
|
1279
1342
|
|
|
1280
1343
|
const { getObservationStore } = await import('./store/obs-store.js');
|
|
1281
|
-
const observations =
|
|
1344
|
+
const observations = filterReadableObservations(
|
|
1345
|
+
await getObservationStore().loadByProject(project.id),
|
|
1346
|
+
getObservationReader(),
|
|
1347
|
+
);
|
|
1282
1348
|
const packet = buildGraphContextPacket(observations, {
|
|
1283
1349
|
projectId: project.id,
|
|
1284
1350
|
query,
|
|
@@ -1338,7 +1404,10 @@ export async function createMemorixServer(
|
|
|
1338
1404
|
import('./runtime/maintenance-jobs.js'),
|
|
1339
1405
|
import('./runtime/lifecycle.js'),
|
|
1340
1406
|
]);
|
|
1341
|
-
const observations =
|
|
1407
|
+
const observations = filterReadableObservations(
|
|
1408
|
+
await getObservationStore().loadByProject(project.id, { status: 'active' }),
|
|
1409
|
+
getObservationReader(),
|
|
1410
|
+
);
|
|
1342
1411
|
const context = await buildAutoProjectContext({
|
|
1343
1412
|
project,
|
|
1344
1413
|
dataDir: projectDir,
|
|
@@ -1425,7 +1494,7 @@ export async function createMemorixServer(
|
|
|
1425
1494
|
{ getResolvedConfig },
|
|
1426
1495
|
{ getExternalCodeGraphContext },
|
|
1427
1496
|
{ getObservationStore },
|
|
1428
|
-
{ collectCurrentProjectFacts },
|
|
1497
|
+
{ collectCurrentProjectFacts, formatGitFact },
|
|
1429
1498
|
{ resolveTaskLens },
|
|
1430
1499
|
] = await Promise.all([
|
|
1431
1500
|
import('./codegraph/store.js'),
|
|
@@ -1440,7 +1509,10 @@ export async function createMemorixServer(
|
|
|
1440
1509
|
await store.init(projectDir);
|
|
1441
1510
|
const codegraphConfig = getResolvedConfig({ projectRoot: project.rootPath }).codegraph;
|
|
1442
1511
|
const exclude = codegraphConfig.excludePatterns;
|
|
1443
|
-
const observations =
|
|
1512
|
+
const observations = filterReadableObservations(
|
|
1513
|
+
await getObservationStore().loadByProject(project.id, { status: 'active' }),
|
|
1514
|
+
getObservationReader(),
|
|
1515
|
+
);
|
|
1444
1516
|
observations.reverse();
|
|
1445
1517
|
const basePack = assembleContextPackForTask({
|
|
1446
1518
|
store,
|
|
@@ -1467,8 +1539,7 @@ export async function createMemorixServer(
|
|
|
1467
1539
|
worksetFacts.push('Latest changelog: ' + currentFacts.latestChangelog.version
|
|
1468
1540
|
+ (currentFacts.latestChangelog.date ? ' (' + currentFacts.latestChangelog.date + ')' : ''));
|
|
1469
1541
|
}
|
|
1470
|
-
worksetFacts.push(
|
|
1471
|
-
+ (currentFacts.git.dirty ? 'dirty worktree' : 'clean worktree'));
|
|
1542
|
+
worksetFacts.push(formatGitFact(currentFacts.git));
|
|
1472
1543
|
const codeState = snapshot
|
|
1473
1544
|
? '- Code state: ' + (snapshot.baseRevision ? snapshot.baseRevision.slice(0, 12) : 'Git unavailable')
|
|
1474
1545
|
+ ', ' + snapshot.worktreeState + ' worktree'
|
|
@@ -1513,11 +1584,13 @@ export async function createMemorixServer(
|
|
|
1513
1584
|
{
|
|
1514
1585
|
title: 'Knowledge Workspace',
|
|
1515
1586
|
description:
|
|
1516
|
-
'Manage the reviewable project Knowledge Workspace. Use it only for deliberate knowledge operations: initialize a local or versioned workspace,
|
|
1587
|
+
'Manage the reviewable project Knowledge Workspace. Use it only for deliberate knowledge operations: initialize a local or versioned workspace, review source-backed claims, compile proposals, lint, apply a reviewed proposal, or manage canonical workflows. It is intentionally absent from the default micro/lite profiles.',
|
|
1517
1588
|
inputSchema: {
|
|
1518
1589
|
action: z.enum([
|
|
1519
1590
|
'workspace_init',
|
|
1520
1591
|
'status',
|
|
1592
|
+
'claim_list',
|
|
1593
|
+
'claim_review',
|
|
1521
1594
|
'compile',
|
|
1522
1595
|
'lint',
|
|
1523
1596
|
'proposal_apply',
|
|
@@ -1532,6 +1605,9 @@ export async function createMemorixServer(
|
|
|
1532
1605
|
path: z.string().optional().describe('Explicit versioned workspace path, used only by workspace_init'),
|
|
1533
1606
|
proposalId: z.string().optional().describe('Pending proposal id for proposal_apply'),
|
|
1534
1607
|
allowManualOverwrite: z.boolean().optional().default(false).describe('Explicitly allow proposal_apply to replace a manually edited page'),
|
|
1608
|
+
claimId: z.string().optional().describe('Source-backed claim id for claim_review'),
|
|
1609
|
+
claimReviewState: z.enum(['approved', 'rejected']).optional().describe('Deliberate review verdict for claim_review'),
|
|
1610
|
+
reviewDetail: z.string().max(2_000).optional().describe('Evidence check performed before approving or rejecting a claim'),
|
|
1535
1611
|
workflowId: z.string().optional().describe('Canonical workflow id for workflow preview, apply, or run'),
|
|
1536
1612
|
agent: z.string().optional().describe('Target agent for a workflow adapter'),
|
|
1537
1613
|
task: z.string().optional().describe('Task text for workflow selection or a workflow run'),
|
|
@@ -1548,6 +1624,9 @@ export async function createMemorixServer(
|
|
|
1548
1624
|
path: workspacePath,
|
|
1549
1625
|
proposalId,
|
|
1550
1626
|
allowManualOverwrite,
|
|
1627
|
+
claimId,
|
|
1628
|
+
claimReviewState,
|
|
1629
|
+
reviewDetail,
|
|
1551
1630
|
workflowId,
|
|
1552
1631
|
agent,
|
|
1553
1632
|
task,
|
|
@@ -1572,6 +1651,7 @@ export async function createMemorixServer(
|
|
|
1572
1651
|
|
|
1573
1652
|
const [
|
|
1574
1653
|
{ ClaimStore },
|
|
1654
|
+
{ reviewClaim },
|
|
1575
1655
|
{ CodeGraphStore },
|
|
1576
1656
|
{ initializeKnowledgeWorkspace, loadKnowledgeWorkspace },
|
|
1577
1657
|
{ KnowledgeWorkspaceStore },
|
|
@@ -1587,6 +1667,7 @@ export async function createMemorixServer(
|
|
|
1587
1667
|
},
|
|
1588
1668
|
] = await Promise.all([
|
|
1589
1669
|
import('./knowledge/claim-store.js'),
|
|
1670
|
+
import('./knowledge/claims.js'),
|
|
1590
1671
|
import('./codegraph/store.js'),
|
|
1591
1672
|
import('./knowledge/workspace.js'),
|
|
1592
1673
|
import('./knowledge/workspace-store.js'),
|
|
@@ -1642,7 +1723,55 @@ export async function createMemorixServer(
|
|
|
1642
1723
|
reason: proposal.reason,
|
|
1643
1724
|
createdAt: proposal.createdAt,
|
|
1644
1725
|
})),
|
|
1726
|
+
reviewableClaims: claims.listClaims(project.id, { limit: 100 })
|
|
1727
|
+
.filter(claim => claim.reviewState === 'needs-review')
|
|
1728
|
+
.map(claim => ({ id: claim.id, subject: claim.subject, predicate: claim.predicate, objectValue: claim.objectValue })),
|
|
1729
|
+
},
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
if (action === 'claim_list') {
|
|
1734
|
+
return text({
|
|
1735
|
+
claims: claims.listClaims(project.id, { limit: 100 }).map(claim => ({
|
|
1736
|
+
id: claim.id,
|
|
1737
|
+
subject: claim.subject,
|
|
1738
|
+
predicate: claim.predicate,
|
|
1739
|
+
objectValue: claim.objectValue,
|
|
1740
|
+
status: claim.status,
|
|
1741
|
+
reviewState: claim.reviewState,
|
|
1742
|
+
origin: claim.origin,
|
|
1743
|
+
confidence: claim.confidence,
|
|
1744
|
+
evidenceCount: claims.listEvidence(claim.id).length,
|
|
1745
|
+
})),
|
|
1746
|
+
next: 'Approve only after checking the linked evidence. Rejected claims stay out of retrieval and publication.',
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
if (action === 'claim_review') {
|
|
1751
|
+
const requestedClaimId = requireText(claimId, 'claimId');
|
|
1752
|
+
const detail = requireText(reviewDetail, 'reviewDetail');
|
|
1753
|
+
if (!requestedClaimId || !claimReviewState || !detail) {
|
|
1754
|
+
return text({ error: 'claimId, claimReviewState, and reviewDetail are required for claim_review.' }, true);
|
|
1755
|
+
}
|
|
1756
|
+
const existing = claims.getClaim(requestedClaimId);
|
|
1757
|
+
if (!existing || existing.projectId !== project.id) {
|
|
1758
|
+
return text({ error: 'Claim was not found for the current project.' }, true);
|
|
1759
|
+
}
|
|
1760
|
+
const claim = reviewClaim(claims, {
|
|
1761
|
+
claimId: requestedClaimId,
|
|
1762
|
+
reviewState: claimReviewState,
|
|
1763
|
+
detail,
|
|
1764
|
+
});
|
|
1765
|
+
return text({
|
|
1766
|
+
claim: {
|
|
1767
|
+
id: claim.id,
|
|
1768
|
+
status: claim.status,
|
|
1769
|
+
reviewState: claim.reviewState,
|
|
1770
|
+
updatedAt: claim.updatedAt,
|
|
1645
1771
|
},
|
|
1772
|
+
next: claim.reviewState === 'approved'
|
|
1773
|
+
? 'This approved source-backed claim can now be considered by knowledge compilation.'
|
|
1774
|
+
: 'This rejected claim is excluded from retrieval and knowledge compilation.',
|
|
1646
1775
|
});
|
|
1647
1776
|
}
|
|
1648
1777
|
|
|
@@ -1688,7 +1817,13 @@ export async function createMemorixServer(
|
|
|
1688
1817
|
if (action === 'workflow_import') {
|
|
1689
1818
|
const result = await importWindsurfWorkflows({ workspace, projectRoot });
|
|
1690
1819
|
return text({
|
|
1691
|
-
imported: result.imported.map(workflow => ({
|
|
1820
|
+
imported: result.imported.map(workflow => ({
|
|
1821
|
+
id: workflow.id,
|
|
1822
|
+
title: workflow.title,
|
|
1823
|
+
sourcePath: workflow.sourcePath,
|
|
1824
|
+
importedFrom: workflow.importedFrom,
|
|
1825
|
+
verificationGates: workflow.verificationGates,
|
|
1826
|
+
})),
|
|
1692
1827
|
skipped: result.skipped,
|
|
1693
1828
|
});
|
|
1694
1829
|
}
|
|
@@ -1702,6 +1837,8 @@ export async function createMemorixServer(
|
|
|
1702
1837
|
status: workflow.status,
|
|
1703
1838
|
taskLenses: workflow.taskLenses,
|
|
1704
1839
|
sourcePath: workflow.sourcePath,
|
|
1840
|
+
importedFrom: workflow.importedFrom,
|
|
1841
|
+
verificationGates: workflow.verificationGates,
|
|
1705
1842
|
})),
|
|
1706
1843
|
parseErrors: synced.errors,
|
|
1707
1844
|
});
|
|
@@ -1797,9 +1934,15 @@ export async function createMemorixServer(
|
|
|
1797
1934
|
},
|
|
1798
1935
|
},
|
|
1799
1936
|
async ({ ids, status }) => {
|
|
1800
|
-
const { resolveObservations } = await import('./memory/observations.js');
|
|
1937
|
+
const { resolveObservations, getObservation } = await import('./memory/observations.js');
|
|
1801
1938
|
const safeIds = (Array.isArray(ids) ? ids : [ids]).map(id => coerceNumber(id, 0)).filter(id => id > 0);
|
|
1802
|
-
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;
|
|
1803
1946
|
|
|
1804
1947
|
const parts: string[] = [];
|
|
1805
1948
|
if (result.resolved.length > 0) {
|
|
@@ -1808,6 +1951,9 @@ export async function createMemorixServer(
|
|
|
1808
1951
|
if (result.notFound.length > 0) {
|
|
1809
1952
|
parts.push(`[WARN] Not found: #${result.notFound.join(', #')}`);
|
|
1810
1953
|
}
|
|
1954
|
+
if (deniedCount > 0) {
|
|
1955
|
+
parts.push(`[WARN] Skipped ${deniedCount} observation(s) outside this session's write scope.`);
|
|
1956
|
+
}
|
|
1811
1957
|
parts.push('\nResolved memories are hidden from default search. Use status="all" to include them.');
|
|
1812
1958
|
parts.push('[STATS] Run `memorix_retention` with `action: "report"` to check remaining cleanup status.');
|
|
1813
1959
|
|
|
@@ -1853,6 +1999,7 @@ export async function createMemorixServer(
|
|
|
1853
1999
|
async ({ entityName, decision, alternatives, rationale, constraints, expectedOutcome, risks, concepts, filesModified, relatedCommits, relatedEntities }) => {
|
|
1854
2000
|
const unresolved = requireResolvedProject('store reasoning in the current project');
|
|
1855
2001
|
if (unresolved) return unresolved;
|
|
2002
|
+
const reader = getObservationReader();
|
|
1856
2003
|
return withFreshIndex(async () => {
|
|
1857
2004
|
|
|
1858
2005
|
// Build structured narrative from reasoning fields
|
|
@@ -1882,7 +2029,11 @@ export async function createMemorixServer(
|
|
|
1882
2029
|
// ── Attribution guard (passive, non-blocking) ─────────────────
|
|
1883
2030
|
let reasoningAttributionWarning = '';
|
|
1884
2031
|
try {
|
|
1885
|
-
const attrCheck = await checkProjectAttribution(
|
|
2032
|
+
const attrCheck = await checkProjectAttribution(
|
|
2033
|
+
entityName,
|
|
2034
|
+
project.id,
|
|
2035
|
+
filterReadableObservations(getAllObservations(), reader),
|
|
2036
|
+
);
|
|
1886
2037
|
if (attrCheck.suspicious) {
|
|
1887
2038
|
reasoningAttributionWarning = `\n[WARN] Attribution notice: entity "${entityName}" has 0 observations in ` +
|
|
1888
2039
|
`"${project.id}" but ${attrCheck.count} in "${attrCheck.knownIn}" ` +
|
|
@@ -1904,6 +2055,7 @@ export async function createMemorixServer(
|
|
|
1904
2055
|
relatedEntities,
|
|
1905
2056
|
sourceDetail: 'explicit',
|
|
1906
2057
|
createdByAgentId: currentAgentId,
|
|
2058
|
+
visibility: 'project',
|
|
1907
2059
|
});
|
|
1908
2060
|
|
|
1909
2061
|
await graphManager.addObservations([
|
|
@@ -1947,7 +2099,11 @@ export async function createMemorixServer(
|
|
|
1947
2099
|
const minCount = threshold ?? 2;
|
|
1948
2100
|
let entries: import('./memory/attribution-guard.js').AuditEntry[];
|
|
1949
2101
|
try {
|
|
1950
|
-
entries = await auditProjectObservations(
|
|
2102
|
+
entries = await auditProjectObservations(
|
|
2103
|
+
project.id,
|
|
2104
|
+
await withFreshIndex(() => filterReadableObservations(getAllObservations(), getObservationReader())),
|
|
2105
|
+
minCount,
|
|
2106
|
+
);
|
|
1951
2107
|
} catch (err) {
|
|
1952
2108
|
return {
|
|
1953
2109
|
content: [{
|
|
@@ -2030,6 +2186,7 @@ export async function createMemorixServer(
|
|
|
2030
2186
|
type: 'reasoning' as ObservationType,
|
|
2031
2187
|
projectId: scope === 'global' ? undefined : project.id,
|
|
2032
2188
|
status: 'active',
|
|
2189
|
+
reader: getObservationReader(scope === 'global' ? 'global' : 'project'),
|
|
2033
2190
|
}));
|
|
2034
2191
|
|
|
2035
2192
|
if (result.entries.length === 0) {
|
|
@@ -2066,7 +2223,11 @@ export async function createMemorixServer(
|
|
|
2066
2223
|
},
|
|
2067
2224
|
async ({ query, dryRun }) => {
|
|
2068
2225
|
const { getAllObservations, resolveObservations } = await import('./memory/observations.js');
|
|
2069
|
-
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)));
|
|
2070
2231
|
|
|
2071
2232
|
if (allObs.length < 2) {
|
|
2072
2233
|
return { content: [{ type: 'text' as const, text: 'Not enough active memories to deduplicate.' }] };
|
|
@@ -2085,7 +2246,7 @@ export async function createMemorixServer(
|
|
|
2085
2246
|
// If query provided, search for relevant memories; otherwise take latest 20
|
|
2086
2247
|
let candidates: typeof allObs;
|
|
2087
2248
|
if (query) {
|
|
2088
|
-
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 });
|
|
2089
2250
|
const idSet = new Set(searchResult.entries.map(e => e.id));
|
|
2090
2251
|
candidates = allObs.filter(o => idSet.has(o.id));
|
|
2091
2252
|
} else {
|
|
@@ -2191,6 +2352,7 @@ export async function createMemorixServer(
|
|
|
2191
2352
|
project.id,
|
|
2192
2353
|
safeBefore,
|
|
2193
2354
|
safeAfter,
|
|
2355
|
+
getObservationReader(),
|
|
2194
2356
|
);
|
|
2195
2357
|
|
|
2196
2358
|
return {
|
|
@@ -2241,14 +2403,17 @@ export async function createMemorixServer(
|
|
|
2241
2403
|
// Priority: typedRefs > refs > ids (each is a complete, homogeneous input path)
|
|
2242
2404
|
let result;
|
|
2243
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');
|
|
2244
2409
|
if (safeTypedRefs.length > 0) {
|
|
2245
2410
|
// Pass typed ref strings directly — compactDetail handles parsing
|
|
2246
|
-
result = await compactDetail(safeTypedRefs);
|
|
2411
|
+
result = await compactDetail(safeTypedRefs, { reader });
|
|
2247
2412
|
} else if (safeRefs.length > 0) {
|
|
2248
|
-
result = await compactDetail(safeRefs);
|
|
2413
|
+
result = await compactDetail(safeRefs, { reader });
|
|
2249
2414
|
} else {
|
|
2250
2415
|
// Bare numeric IDs are scoped to the current project
|
|
2251
|
-
result = await compactDetail(safeIds.map(id => ({ id, projectId: project.id })));
|
|
2416
|
+
result = await compactDetail(safeIds.map(id => ({ id, projectId: project.id })), { reader });
|
|
2252
2417
|
}
|
|
2253
2418
|
} catch (err) {
|
|
2254
2419
|
return { content: [{ type: 'text' as const, text: err instanceof Error ? err.message : String(err) }], isError: true };
|
|
@@ -2297,13 +2462,17 @@ export async function createMemorixServer(
|
|
|
2297
2462
|
},
|
|
2298
2463
|
async (args: { action?: string }) => {
|
|
2299
2464
|
const action = args.action ?? 'report';
|
|
2300
|
-
const { getRetentionSummary, getArchiveCandidates, rankByRelevance,
|
|
2465
|
+
const { getRetentionSummary, getArchiveCandidates, rankByRelevance, getRetentionZone, explainRetention } = await import('./memory/retention.js');
|
|
2301
2466
|
const { getDb } = await import('./store/orama-store.js');
|
|
2302
2467
|
const { search } = await import('@orama/orama');
|
|
2303
2468
|
|
|
2304
2469
|
// Shared: build MemorixDocument[] from in-memory observations
|
|
2305
|
-
const { getAllObservations } = await import('./memory/observations.js');
|
|
2306
|
-
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
|
+
));
|
|
2307
2476
|
|
|
2308
2477
|
// Pull current access metadata from the live Orama index so access-based
|
|
2309
2478
|
// immunity (e.g. accessCount >= 3) still works in retention/report/archive
|
|
@@ -2327,19 +2496,6 @@ export async function createMemorixServer(
|
|
|
2327
2496
|
// less precise immunity/reporting.
|
|
2328
2497
|
}
|
|
2329
2498
|
|
|
2330
|
-
// Handle archive action
|
|
2331
|
-
if (action === 'archive') {
|
|
2332
|
-
const result = await archiveExpired(projectDir, undefined, accessMap, project.id);
|
|
2333
|
-
if (result.archived === 0) {
|
|
2334
|
-
return {
|
|
2335
|
-
content: [{ type: 'text' as const, text: '[OK] No expired observations to archive. All memories are within their retention period.' }],
|
|
2336
|
-
};
|
|
2337
|
-
}
|
|
2338
|
-
return {
|
|
2339
|
-
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".` }],
|
|
2340
|
-
};
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
2499
|
const docs: import('./types.js').MemorixDocument[] = allObs.map(obs => ({
|
|
2344
2500
|
id: `obs-${obs.id}`,
|
|
2345
2501
|
observationId: obs.id,
|
|
@@ -2359,6 +2515,11 @@ export async function createMemorixServer(
|
|
|
2359
2515
|
source: obs.source ?? 'agent',
|
|
2360
2516
|
sourceDetail: obs.sourceDetail ?? '',
|
|
2361
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 ?? []),
|
|
2362
2523
|
}));
|
|
2363
2524
|
|
|
2364
2525
|
if (docs.length === 0) {
|
|
@@ -2367,6 +2528,24 @@ export async function createMemorixServer(
|
|
|
2367
2528
|
};
|
|
2368
2529
|
}
|
|
2369
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
|
+
|
|
2370
2549
|
// ── action="stale": full table of stale observations with explanation ──
|
|
2371
2550
|
if (action === 'stale') {
|
|
2372
2551
|
const staleDocs = docs.filter(d => getRetentionZone(d) === 'stale');
|
|
@@ -2739,15 +2918,14 @@ export async function createMemorixServer(
|
|
|
2739
2918
|
async function scopeGraphToProject(graph: { entities: any[]; relations: any[] }) {
|
|
2740
2919
|
const { getAllObservations } = await import('./memory/observations.js');
|
|
2741
2920
|
const allObs = await withFreshIndex(() => getAllObservations());
|
|
2742
|
-
const
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
.
|
|
2921
|
+
const scoped = scopeKnowledgeGraphToProject(
|
|
2922
|
+
graph,
|
|
2923
|
+
filterReadableObservations(
|
|
2924
|
+
allObs.filter(observation => observation.projectId === project.id),
|
|
2925
|
+
getObservationReader(),
|
|
2926
|
+
),
|
|
2746
2927
|
);
|
|
2747
|
-
|
|
2748
|
-
const entityNameSet = new Set(entities.map((e: any) => e.name));
|
|
2749
|
-
const relations = graph.relations.filter((r: any) => entityNameSet.has(r.from) && entityNameSet.has(r.to));
|
|
2750
|
-
return { entities, relations };
|
|
2928
|
+
return { entities: scoped.entities, relations: scoped.relations };
|
|
2751
2929
|
}
|
|
2752
2930
|
|
|
2753
2931
|
/** read_graph — MCP Official compatible */
|
|
@@ -3087,12 +3265,10 @@ export async function createMemorixServer(
|
|
|
3087
3265
|
|
|
3088
3266
|
// action === 'generate'
|
|
3089
3267
|
const { getObservationStore: getStore } = await import('./store/obs-store.js');
|
|
3090
|
-
const allObs =
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
status?: string; source?: 'agent' | 'git' | 'manual';
|
|
3095
|
-
}>;
|
|
3268
|
+
const allObs = filterReadableObservations(
|
|
3269
|
+
(await getStore().loadAll()).filter((observation) => observation.projectId === project.id),
|
|
3270
|
+
getObservationReader(),
|
|
3271
|
+
);
|
|
3096
3272
|
|
|
3097
3273
|
const obsData = allObs.map(o => ({
|
|
3098
3274
|
id: o.id || 0,
|
|
@@ -3224,7 +3400,11 @@ export async function createMemorixServer(
|
|
|
3224
3400
|
// Load observations by ID — only active observations can be promoted
|
|
3225
3401
|
const { getAllObservations } = await import('./memory/observations.js');
|
|
3226
3402
|
const allObs = await withFreshIndex(() => getAllObservations());
|
|
3227
|
-
const
|
|
3403
|
+
const reader = getObservationReader();
|
|
3404
|
+
const matched = filterReadableObservations(
|
|
3405
|
+
allObs.filter((observation) => observation.projectId === project.id && observationIds.includes(observation.id)),
|
|
3406
|
+
reader,
|
|
3407
|
+
);
|
|
3228
3408
|
|
|
3229
3409
|
if (matched.length === 0) {
|
|
3230
3410
|
return { content: [{ type: 'text' as const, text: `No observations found for IDs: [${observationIds.join(', ')}]. Use \`memorix_search\` to find valid IDs.` }], isError: true };
|
|
@@ -3236,6 +3416,14 @@ export async function createMemorixServer(
|
|
|
3236
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 };
|
|
3237
3417
|
}
|
|
3238
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
|
+
|
|
3239
3427
|
const skill = await promoteToMiniSkill(projectDir, project.id, matched, { trigger, instruction, tags });
|
|
3240
3428
|
|
|
3241
3429
|
const lines = [
|
|
@@ -3469,8 +3657,11 @@ export async function createMemorixServer(
|
|
|
3469
3657
|
const lastSeen = registeredAgent.last_seen_obs_generation;
|
|
3470
3658
|
const store = getObservationStore();
|
|
3471
3659
|
const currentGen = store.getGeneration();
|
|
3472
|
-
const projectObs = await withFreshIndex(() =>
|
|
3473
|
-
|
|
3660
|
+
const projectObs = await withFreshIndex(() => filterReadableObservations(
|
|
3661
|
+
getAllObservations().filter(
|
|
3662
|
+
o => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen,
|
|
3663
|
+
),
|
|
3664
|
+
getObservationReader(),
|
|
3474
3665
|
));
|
|
3475
3666
|
const wm = computeWatermark(lastSeen, currentGen, projectObs.length);
|
|
3476
3667
|
if (wm.newObservationCount > 0) {
|
|
@@ -3673,7 +3864,7 @@ export async function createMemorixServer(
|
|
|
3673
3864
|
title: 'Transfer Memories',
|
|
3674
3865
|
description:
|
|
3675
3866
|
'Export or import project memories. ' +
|
|
3676
|
-
'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). ' +
|
|
3677
3868
|
'Action "import": import from a JSON export (re-assigns IDs, skips duplicate topicKeys).',
|
|
3678
3869
|
inputSchema: {
|
|
3679
3870
|
action: z.enum(['export', 'import']).describe('Operation: export or import'),
|
|
@@ -3685,10 +3876,10 @@ export async function createMemorixServer(
|
|
|
3685
3876
|
if (action === 'export') {
|
|
3686
3877
|
const { exportAsJson, exportAsMarkdown } = await import('./memory/export-import.js');
|
|
3687
3878
|
if (format === 'markdown') {
|
|
3688
|
-
const md = await exportAsMarkdown(projectDir, project.id);
|
|
3879
|
+
const md = await exportAsMarkdown(projectDir, project.id, getObservationReader());
|
|
3689
3880
|
return { content: [{ type: 'text' as const, text: md }] };
|
|
3690
3881
|
}
|
|
3691
|
-
const data = await exportAsJson(projectDir, project.id);
|
|
3882
|
+
const data = await exportAsJson(projectDir, project.id, getObservationReader());
|
|
3692
3883
|
const json = JSON.stringify(data, null, 2);
|
|
3693
3884
|
return {
|
|
3694
3885
|
content: [{
|
|
@@ -3910,8 +4101,6 @@ export async function createMemorixServer(
|
|
|
3910
4101
|
|
|
3911
4102
|
// Use shared TeamStore (from HTTP server) or create new one (stdio mode).
|
|
3912
4103
|
// All team state is canonical in SQLite — no JSON persistence, no sync/flush.
|
|
3913
|
-
let teamStore!: import('./team/team-store.js').TeamStore;
|
|
3914
|
-
let initTeamStoreForProject: ((dataDir: string) => Promise<import('./team/team-store.js').TeamStore>) | undefined;
|
|
3915
4104
|
if (teamFeaturesEnabled) {
|
|
3916
4105
|
const { initTeamStore } = await import('./team/team-store.js');
|
|
3917
4106
|
initTeamStoreForProject = initTeamStore;
|
|
@@ -4197,7 +4386,7 @@ export async function createMemorixServer(
|
|
|
4197
4386
|
'Action "inbox": read this agent\'s inbox.',
|
|
4198
4387
|
inputSchema: {
|
|
4199
4388
|
action: z.enum(['send', 'broadcast', 'inbox']).describe('Operation to perform'),
|
|
4200
|
-
from: z.string().optional().describe('
|
|
4389
|
+
from: z.string().optional().describe('Your sender agent ID. Omit it to use this session identity.'),
|
|
4201
4390
|
to: z.string().optional().describe('Receiver agent ID (for send)'),
|
|
4202
4391
|
type: z.enum(['request', 'response', 'info', 'announcement', 'contract', 'error', 'handoff']).optional().describe('Message type (for send/broadcast)'),
|
|
4203
4392
|
content: z.string().optional().describe('Message content (for send/broadcast)'),
|
|
@@ -4208,13 +4397,20 @@ export async function createMemorixServer(
|
|
|
4208
4397
|
},
|
|
4209
4398
|
},
|
|
4210
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
|
+
};
|
|
4211
4405
|
if (action === 'send') {
|
|
4212
|
-
|
|
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 };
|
|
4213
4409
|
if (!to && !toRole) return { content: [{ type: 'text' as const, text: '[ERROR] either to (agent ID) or toRole is required for send' }], isError: true };
|
|
4214
4410
|
if (content.length > 10_000) return { content: [{ type: 'text' as const, text: '[ERROR] Message too large (max 10KB)' }], isError: true };
|
|
4215
4411
|
const msg = teamStore.sendMessage({
|
|
4216
4412
|
projectId: project.id,
|
|
4217
|
-
senderAgentId:
|
|
4413
|
+
senderAgentId: currentAgentId!,
|
|
4218
4414
|
recipientAgentId: to ?? null,
|
|
4219
4415
|
type: msgType,
|
|
4220
4416
|
content,
|
|
@@ -4226,11 +4422,13 @@ export async function createMemorixServer(
|
|
|
4226
4422
|
return { content: [{ type: 'text' as const, text: `Message sent (${msgType}) to ${target} | ID: ${msg.id.slice(0, 8)}…${toRole ? ` [role: ${toRole}]` : ''}` }] };
|
|
4227
4423
|
}
|
|
4228
4424
|
if (action === 'broadcast') {
|
|
4229
|
-
|
|
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 };
|
|
4230
4428
|
if (content.length > 10_000) return { content: [{ type: 'text' as const, text: '[ERROR] Message too large (max 10KB)' }], isError: true };
|
|
4231
4429
|
const msg = teamStore.sendMessage({
|
|
4232
4430
|
projectId: project.id,
|
|
4233
|
-
senderAgentId:
|
|
4431
|
+
senderAgentId: currentAgentId!,
|
|
4234
4432
|
recipientAgentId: null,
|
|
4235
4433
|
type: msgType,
|
|
4236
4434
|
content,
|
|
@@ -4239,8 +4437,12 @@ export async function createMemorixServer(
|
|
|
4239
4437
|
return { content: [{ type: 'text' as const, text: `Broadcast (${msgType}) | ID: ${msg.id.slice(0, 8)}…` }] };
|
|
4240
4438
|
}
|
|
4241
4439
|
// inbox
|
|
4242
|
-
const
|
|
4243
|
-
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!;
|
|
4244
4446
|
const inbox = teamStore.getInbox(project.id, inboxId);
|
|
4245
4447
|
const unread = teamStore.getUnreadCount(project.id, inboxId);
|
|
4246
4448
|
if (inbox.length === 0) return { content: [{ type: 'text' as const, text: 'Inbox empty' }] };
|
|
@@ -4272,32 +4474,42 @@ export async function createMemorixServer(
|
|
|
4272
4474
|
},
|
|
4273
4475
|
async ({ agentId, markInboxRead }) => {
|
|
4274
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;
|
|
4275
4484
|
|
|
4276
4485
|
// Compute watermark — requires observation store access
|
|
4277
4486
|
let watermark = computeWatermark(0, 0, 0);
|
|
4278
|
-
if (
|
|
4279
|
-
const agent = teamStore.getAgent(
|
|
4487
|
+
if (effectiveAgentId) {
|
|
4488
|
+
const agent = teamStore.getAgent(effectiveAgentId);
|
|
4280
4489
|
if (agent) {
|
|
4281
4490
|
const lastSeen = agent.last_seen_obs_generation;
|
|
4282
4491
|
const store = getObservationStore();
|
|
4283
4492
|
const currentGen = store.getGeneration();
|
|
4284
|
-
const projectObs = await withFreshIndex(() =>
|
|
4285
|
-
|
|
4493
|
+
const projectObs = await withFreshIndex(() => filterReadableObservations(
|
|
4494
|
+
getAllObservations().filter(
|
|
4495
|
+
o => o.projectId === project.id && (o.writeGeneration ?? 0) > lastSeen,
|
|
4496
|
+
),
|
|
4497
|
+
getObservationReader(),
|
|
4286
4498
|
));
|
|
4287
4499
|
watermark = computeWatermark(lastSeen, currentGen, projectObs.length);
|
|
4288
4500
|
|
|
4289
4501
|
// Advance watermark so next poll sees only truly new observations
|
|
4290
|
-
teamStore.updateWatermark(
|
|
4502
|
+
teamStore.updateWatermark(effectiveAgentId, currentGen);
|
|
4291
4503
|
// Heartbeat — proves this agent is alive
|
|
4292
|
-
teamStore.heartbeat(
|
|
4504
|
+
teamStore.heartbeat(effectiveAgentId);
|
|
4293
4505
|
}
|
|
4294
4506
|
}
|
|
4295
4507
|
|
|
4296
|
-
const poll = computePoll(teamStore, project.id,
|
|
4508
|
+
const poll = computePoll(teamStore, project.id, effectiveAgentId ?? null, watermark);
|
|
4297
4509
|
|
|
4298
4510
|
// Optionally mark inbox as read
|
|
4299
|
-
if (markInboxRead &&
|
|
4300
|
-
teamStore.markAllRead(project.id,
|
|
4511
|
+
if (markInboxRead && effectiveAgentId) {
|
|
4512
|
+
teamStore.markAllRead(project.id, effectiveAgentId);
|
|
4301
4513
|
}
|
|
4302
4514
|
|
|
4303
4515
|
// Format as readable text
|
|
@@ -4370,7 +4582,8 @@ export async function createMemorixServer(
|
|
|
4370
4582
|
title: 'Team Handoff — Agent Context Transfer',
|
|
4371
4583
|
description:
|
|
4372
4584
|
'Create a structured handoff artifact when passing work to another agent. ' +
|
|
4373
|
-
'
|
|
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) ' +
|
|
4374
4587
|
'and a notification message is sent to the recipient. ' +
|
|
4375
4588
|
'Use this when completing a task and another agent should continue, ' +
|
|
4376
4589
|
'or when you want to leave context for whoever works on this next.',
|
|
@@ -4386,6 +4599,34 @@ export async function createMemorixServer(
|
|
|
4386
4599
|
},
|
|
4387
4600
|
async ({ fromAgentId, summary, context, toAgentId, taskId, filesModified, concepts }) => {
|
|
4388
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
|
+
}
|
|
4389
4630
|
|
|
4390
4631
|
const result = await createHandoffArtifact(
|
|
4391
4632
|
{
|