total-recall-brain 3.6.8 → 3.6.9
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/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from './schema.mjs';
|
|
11
11
|
import { atomicWrite, safeStringify } from './vault.mjs';
|
|
12
12
|
import { logger } from './logger.mjs';
|
|
13
|
+
import { validateMemoryNode } from './total-recall-memory-validator.mjs';
|
|
13
14
|
|
|
14
15
|
const APPEND_TYPES = new Set(['conversation', 'run']);
|
|
15
16
|
const PROTOCOL_PATHS = [
|
|
@@ -158,8 +159,14 @@ export function processOperation(envelope, vaultRoot, options = {}) {
|
|
|
158
159
|
if (!resolvedType) errors.push('Missing required frontmatter field: type');
|
|
159
160
|
else if (!SSSS_SCHEMAS[resolvedType]) errors.push(`Unknown SSSS type: '${resolvedType}'.`);
|
|
160
161
|
else {
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
if (resolvedType === 'memory' && envelope.path.includes('.agent/memory-vault/')) {
|
|
163
|
+
const vResult = validateMemoryNode(fmData);
|
|
164
|
+
if (!vResult.success) vResult.errors.forEach(e => errors.push(e));
|
|
165
|
+
} else {
|
|
166
|
+
const r = SSSS_SCHEMAS[resolvedType].safeParse(fmData);
|
|
167
|
+
if (!r.success) r.error.issues.forEach(i => errors.push(`${i.path.join('.')}: ${i.message}`));
|
|
168
|
+
}
|
|
169
|
+
|
|
163
170
|
if (envelope.type === 'operation' && APPEND_TYPES.has(resolvedType)) {
|
|
164
171
|
const ap = resolveVfsPath(vaultRoot, envelope.path);
|
|
165
172
|
if (fs.existsSync(ap)) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promotes a feedback-derived memory node to a wider scope after applying
|
|
3
|
+
* strict provenance stripping and privacy guarantees.
|
|
4
|
+
*
|
|
5
|
+
* Flow: workspace -> system_candidate -> system_promoted
|
|
6
|
+
*
|
|
7
|
+
* @param {object} memoryNode - The memory node data (frontmatter + __body__).
|
|
8
|
+
* @param {string} targetScope - 'system_candidate' or 'system_promoted'
|
|
9
|
+
* @returns {object} { success: boolean, node: object, errors: string[] }
|
|
10
|
+
*/
|
|
11
|
+
export function promoteFeedback(memoryNode, targetScope) {
|
|
12
|
+
const currentScope = memoryNode.feedback_scope || 'workspace';
|
|
13
|
+
|
|
14
|
+
if (targetScope === 'system_candidate') {
|
|
15
|
+
if (currentScope !== 'workspace' && currentScope !== 'local_thread') {
|
|
16
|
+
return { success: false, node: null, errors: [`Cannot promote to system_candidate from ${currentScope}`] };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Provenance stripping
|
|
20
|
+
const strippedNode = JSON.parse(JSON.stringify(memoryNode));
|
|
21
|
+
strippedNode.feedback_scope = 'system_candidate';
|
|
22
|
+
|
|
23
|
+
// Remove PII / workspace leaking fields
|
|
24
|
+
delete strippedNode.workspace_id;
|
|
25
|
+
delete strippedNode.user_id;
|
|
26
|
+
if (strippedNode.source) {
|
|
27
|
+
delete strippedNode.source.session_id;
|
|
28
|
+
delete strippedNode.source.agent;
|
|
29
|
+
}
|
|
30
|
+
delete strippedNode.x_browser_context;
|
|
31
|
+
delete strippedNode.x_location;
|
|
32
|
+
delete strippedNode.x_citations; // Citations might contain private workspace doc links
|
|
33
|
+
|
|
34
|
+
// Scrub body for UUIDs
|
|
35
|
+
if (strippedNode.__body__) {
|
|
36
|
+
strippedNode.__body__ = strippedNode.__body__.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ig, '[REDACTED_UUID]');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { success: true, node: strippedNode, errors: [] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (targetScope === 'system_promoted') {
|
|
43
|
+
if (currentScope !== 'system_candidate') {
|
|
44
|
+
return { success: false, node: null, errors: [`Cannot promote to system_promoted directly from ${currentScope}. Must be system_candidate first.`] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const promotedNode = JSON.parse(JSON.stringify(memoryNode));
|
|
48
|
+
promotedNode.feedback_scope = 'system_promoted';
|
|
49
|
+
|
|
50
|
+
return { success: true, node: promotedNode, errors: [] };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { success: false, node: null, errors: [`Unknown target scope: ${targetScope}`] };
|
|
54
|
+
}
|
package/src/core/schema.mjs
CHANGED
|
@@ -24,8 +24,8 @@ export const MemoryNodeSchema = z.object({
|
|
|
24
24
|
category: z.string(),
|
|
25
25
|
title: z.string(),
|
|
26
26
|
status: z.enum(['active', 'superseded', 'deprecated', 'draft']),
|
|
27
|
-
confidence: z.number().min(0).max(1),
|
|
28
|
-
importance: z.number().int().min(1).max(5),
|
|
27
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
28
|
+
importance: z.number().int().min(1).max(5).optional(),
|
|
29
29
|
created: ssssDatetime(),
|
|
30
30
|
updated: ssssDatetime(),
|
|
31
31
|
last_accessed: ssssDatetime(),
|
|
@@ -41,12 +41,12 @@ export const MemoryNodeSchema = z.object({
|
|
|
41
41
|
tags: z.array(z.string()),
|
|
42
42
|
related: z.array(z.string()),
|
|
43
43
|
routes_to_skills: z.array(z.string()),
|
|
44
|
-
sentiment_polarity: z.enum(['directive_must', 'directive_must_not', 'descriptive', 'preference']),
|
|
45
|
-
sentiment_target: z.string(),
|
|
46
|
-
modality: z.enum(['must', 'must_not', 'should', 'should_not', 'descriptive', 'preference']),
|
|
47
|
-
subject: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/),
|
|
48
|
-
predicate: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/),
|
|
49
|
-
object: z.string(),
|
|
44
|
+
sentiment_polarity: z.enum(['directive_must', 'directive_must_not', 'descriptive', 'preference']).optional(),
|
|
45
|
+
sentiment_target: z.string().optional(),
|
|
46
|
+
modality: z.enum(['must', 'must_not', 'should', 'should_not', 'descriptive', 'preference']).optional(),
|
|
47
|
+
subject: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/).optional(),
|
|
48
|
+
predicate: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/).optional(),
|
|
49
|
+
object: z.string().optional(),
|
|
50
50
|
decay: z.object({
|
|
51
51
|
half_life_days: z.number(),
|
|
52
52
|
access_count: z.number().int(),
|
|
@@ -87,7 +87,8 @@ export const MemoryNodeSchema = z.object({
|
|
|
87
87
|
expires_at: ssssDatetimeNullable().optional(),
|
|
88
88
|
// Absolute Invariant Extensions
|
|
89
89
|
priority: z.enum(['absolute', 'high', 'normal', 'low']).optional(),
|
|
90
|
-
immutable: z.boolean().optional()
|
|
90
|
+
immutable: z.boolean().optional(),
|
|
91
|
+
feedback_scope: z.enum(['local_thread', 'workspace', 'account', 'system_candidate', 'system_promoted']).optional()
|
|
91
92
|
});
|
|
92
93
|
|
|
93
94
|
export const ConflictRecordSchema = z.object({
|
|
@@ -464,6 +465,7 @@ export const EventSchema = z.object({
|
|
|
464
465
|
comment: z.string().optional().nullable(),
|
|
465
466
|
comment_lang: z.string().optional().nullable(),
|
|
466
467
|
timestamp: z.string().optional().nullable(),
|
|
468
|
+
feedback_scope: z.enum(['local_thread', 'workspace', 'account', 'system_candidate', 'system_promoted']).optional().nullable(),
|
|
467
469
|
});
|
|
468
470
|
|
|
469
471
|
export const LanguageConventionSchema = z.object({
|
package/src/core/surface.mjs
CHANGED
|
@@ -492,15 +492,7 @@ async function compilePointers(instructionsFile, skillsDir, nodes = [], { vaultD
|
|
|
492
492
|
const clientsPath = path.join(baseDir, '.agent', 'config', 'clients.json');
|
|
493
493
|
const connectedClients = readConnectedClients(clientsPath);
|
|
494
494
|
|
|
495
|
-
if (connectedClients
|
|
496
|
-
// Write out-of-the-box dotfiles to support IDEs automatically,
|
|
497
|
-
// avoiding duplicate .md files to prevent Antigravity context bloat.
|
|
498
|
-
for (const files of Object.values(CLIENT_SHIMS)) {
|
|
499
|
-
for (const file of files) {
|
|
500
|
-
await writeShim(path.join(baseDir, file), skillsDir, nodes, { vaultDir, derivedDir });
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
} else {
|
|
495
|
+
if (connectedClients !== null) {
|
|
504
496
|
// Only write shims for connected clients
|
|
505
497
|
for (const client of connectedClients) {
|
|
506
498
|
const files = CLIENT_SHIMS[client];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SSSS_SCHEMAS } from './schema.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates a memory node against the SSSS standard, enforcing
|
|
5
|
+
* Schema V2 conditional requirements for Total Recall.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} fmData - The parsed YAML frontmatter of the memory node.
|
|
8
|
+
* @returns {object} { success: boolean, errors: string[] }
|
|
9
|
+
*/
|
|
10
|
+
export function validateMemoryNode(fmData) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
|
|
13
|
+
// 1. Base schema validation (types, bounds, basic required fields)
|
|
14
|
+
const r = SSSS_SCHEMAS['memory'].safeParse(fmData);
|
|
15
|
+
if (!r.success) {
|
|
16
|
+
r.error.issues.forEach(i => errors.push(`${i.path.join('.')}: ${i.message}`));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Schema V2 Conditional Validation
|
|
20
|
+
// If schema_version === 2, the following fields are strictly required:
|
|
21
|
+
if (fmData.schema_version === 2) {
|
|
22
|
+
const v2Required = [
|
|
23
|
+
'confidence',
|
|
24
|
+
'importance',
|
|
25
|
+
'modality',
|
|
26
|
+
'subject',
|
|
27
|
+
'predicate',
|
|
28
|
+
'object',
|
|
29
|
+
'sentiment_polarity',
|
|
30
|
+
'sentiment_target'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
v2Required.forEach(field => {
|
|
34
|
+
if (fmData[field] === undefined || fmData[field] === null) {
|
|
35
|
+
errors.push(`V2 Schema Requirement: Missing required field '${field}'`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
success: errors.length === 0,
|
|
42
|
+
errors
|
|
43
|
+
};
|
|
44
|
+
}
|