@sidurijs/self 1.0.0
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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +12 -0
- package/dist/active-self-compiler.d.ts +6 -0
- package/dist/active-self-compiler.js +269 -0
- package/dist/cognitive-compiler.d.ts +24 -0
- package/dist/cognitive-compiler.js +129 -0
- package/dist/cognitive-compiler.test.d.ts +1 -0
- package/dist/cognitive-compiler.test.js +96 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +22 -0
- package/dist/safety-scanner.d.ts +16 -0
- package/dist/safety-scanner.js +117 -0
- package/dist/self-parser.d.ts +9 -0
- package/dist/self-parser.js +278 -0
- package/dist/self-repository.d.ts +29 -0
- package/dist/self-repository.js +89 -0
- package/dist/self.test.d.ts +1 -0
- package/dist/self.test.js +508 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +2 -0
- package/jest.config.json +5 -0
- package/package.json +41 -0
- package/src/active-self-compiler.ts +306 -0
- package/src/cognitive-compiler.test.ts +109 -0
- package/src/cognitive-compiler.ts +145 -0
- package/src/index.ts +6 -0
- package/src/safety-scanner.ts +150 -0
- package/src/self-parser.ts +295 -0
- package/src/self-repository.ts +117 -0
- package/src/self.test.ts +534 -0
- package/src/types.ts +124 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { ScanResult } from './types';
|
|
2
|
+
|
|
3
|
+
export interface SafetyPatternConfig {
|
|
4
|
+
overridePattern: RegExp;
|
|
5
|
+
negationPattern: RegExp;
|
|
6
|
+
disclosurePattern: RegExp;
|
|
7
|
+
escalationPhrases: RegExp[];
|
|
8
|
+
approvalTampering: RegExp[];
|
|
9
|
+
safetyDisable: RegExp[];
|
|
10
|
+
structuralHeuristics: Array<{ pattern: RegExp; reason: string }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ── Layer 1: Unicode normalization & deobfuscation ──────────────────────
|
|
14
|
+
|
|
15
|
+
const INVISIBLE_CHARS = /[\u200B\u200C\u200D\u2060\uFEFF\u00AD\u034F\u17B4\u17B5\u180E\u2061-\u2064\u206A-\u206F]/g;
|
|
16
|
+
|
|
17
|
+
const LEET_MAP: Record<string, string> = {
|
|
18
|
+
'0': 'o', '1': 'i', '3': 'e', '4': 'a', '5': 's',
|
|
19
|
+
'7': 't', '@': 'a', '$': 's', '!': 'i',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CONFUSABLE_MAP: Record<string, string> = {
|
|
23
|
+
'\u0430': 'a', '\u0435': 'e', '\u043E': 'o', '\u0440': 'p', '\u0441': 'c',
|
|
24
|
+
'\u0443': 'y', '\u0445': 'x', '\u0456': 'i', '\u0458': 'j', '\u04BB': 'h',
|
|
25
|
+
'\u0261': 'g', '\u03B1': 'a', '\u03BF': 'o', '\u03B5': 'e',
|
|
26
|
+
// Fullwidth Latin
|
|
27
|
+
'\uFF41': 'a', '\uFF42': 'b', '\uFF43': 'c', '\uFF44': 'd', '\uFF45': 'e',
|
|
28
|
+
'\uFF46': 'f', '\uFF47': 'g', '\uFF48': 'h', '\uFF49': 'i', '\uFF4A': 'j',
|
|
29
|
+
'\uFF4B': 'k', '\uFF4C': 'l', '\uFF4D': 'm', '\uFF4E': 'n', '\uFF4F': 'o',
|
|
30
|
+
'\uFF50': 'p', '\uFF51': 'q', '\uFF52': 'r', '\uFF53': 's', '\uFF54': 't',
|
|
31
|
+
'\uFF55': 'u', '\uFF56': 'v', '\uFF57': 'w', '\uFF58': 'x', '\uFF59': 'y',
|
|
32
|
+
'\uFF5A': 'z',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function normalizeText(input: string): string {
|
|
36
|
+
let text = input;
|
|
37
|
+
text = text.replace(INVISIBLE_CHARS, '');
|
|
38
|
+
text = text.replace(/./g, (ch) => CONFUSABLE_MAP[ch] ?? ch);
|
|
39
|
+
text = text.toLowerCase();
|
|
40
|
+
text = text.replace(/[013457@$!]/g, (ch) => LEET_MAP[ch] ?? ch);
|
|
41
|
+
text = text.replace(/\b([a-z])(?:\s+[a-z]){2,}\b/g, (match) =>
|
|
42
|
+
match.replace(/\s+/g, '')
|
|
43
|
+
);
|
|
44
|
+
text = text.replace(/\s+/g, ' ').trim();
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Layer 2: Multi-category pattern bank ─────────────────────────────────
|
|
49
|
+
|
|
50
|
+
const OVERRIDE_VERBS = '(?:ignore|override|bypass|skip|circumvent|disregard|forget|violate|break|disable|remove|deactivate|nullify|suspend|suppress|eliminate|drop|abandon|dismiss|disobey)';
|
|
51
|
+
const NEGATION_VERBS = '(?:do not follow|don\'t follow|stop following|stop enforcing|never follow|never enforce|cease following|quit following)';
|
|
52
|
+
const SAFETY_NOUNS = '(?:system|policy|policies|rules?|approval|permissions?|restrictions?|guidelines?|constraints?|guardrails?|instructions?|authorization|gating|safety|safeguards?|boundaries|limits?|protections?)';
|
|
53
|
+
|
|
54
|
+
const DISCLOSURE_VERBS = '(?:reveal|expose|leak|dump|show|output|print|display|disclose|share|repeat back|read out|recite|reproduce|echo|emit|return|give me)';
|
|
55
|
+
const SECRET_NOUNS = '(?:secret|token|api key|private memory|system prompt|initial prompt|hidden instructions?|internal state|configuration|prompt text|original instructions?|base prompt)';
|
|
56
|
+
|
|
57
|
+
const ESCALATION_PHRASES = [
|
|
58
|
+
/\b(?:developer|admin|unrestricted|unfiltered|unlimited|god|root|sudo|superuser)\s*mode\b/,
|
|
59
|
+
/\b(?:jailbreak|jail break|jailbroken)\b/,
|
|
60
|
+
/\byou are now\b.{0,40}\b(?:unrestricted|unfiltered|without limits|without restrictions|without rules|without guidelines|free from)\b/,
|
|
61
|
+
/\byour (?:new|real|actual|true)\s+(?:instructions?|prompt|rules?|policy|guidelines?)\b/,
|
|
62
|
+
/\bfrom now on you (?:will|must|should|shall)\b.{0,60}\b(?:obey|comply|follow all|do whatever|do anything|never refuse)\b/,
|
|
63
|
+
/\bpretend\b.{0,40}\b(?:no restrictions|no rules|no limits|no guidelines|no constraints|unrestricted|unfiltered)\b/,
|
|
64
|
+
/\bact as if\b.{0,40}\b(?:permissions?|restrictions?|rules?|policy|guidelines?|constraints?)\b.{0,20}\b(?:don't|do not|doesn't|does not|did not|didn't)\b/,
|
|
65
|
+
/\byou have\s+(?:full|complete|total|unlimited|unrestricted)\s+(?:\w+\s+)?(?:access|permissions?|authorization|control)\b/,
|
|
66
|
+
/\btreat\s+(?:all|every)\b.{0,40}\b(?:operator|admin|owner|administrator)\b/,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const APPROVAL_TAMPERING = [
|
|
70
|
+
/\b(?:auto(?:-|\s)?approve|automatically approve|always approve|approve\s+all|approve\s+every)\b/,
|
|
71
|
+
/\b(?:always accept|never reject|never deny|never refuse|skip\s+approval|bypass\s+approval)\b/,
|
|
72
|
+
/\b(?:treat as approved|mark as active|activate all|force activate|directly activate)\b/,
|
|
73
|
+
/\b(?:without\s+(?:approval|review|verification|confirmation|checking|authorization))\b/,
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const SAFETY_DISABLE = [
|
|
77
|
+
/\b(?:turn off|shut off|switch off|power off|deactivate|disable|remove|delete|erase|clear|wipe|purge)\b.{0,30}\b(?:safety|security|filter|filtering|moderation|censorship|guardrails?)\b/,
|
|
78
|
+
/\b(?:no\s+(?:safety|security|content)\s+(?:filter|filtering|check|checks|moderation|restrictions))\b/,
|
|
79
|
+
/\b(?:without\s+(?:safety|security|content)\s+(?:filter|filtering|check|checks|moderation|restrictions))\b/,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const OVERRIDE_PATTERN = new RegExp(`\\b${OVERRIDE_VERBS}\\b.{0,60}\\b${SAFETY_NOUNS}\\b`, 'i');
|
|
83
|
+
const NEGATION_PATTERN = new RegExp(`\\b${NEGATION_VERBS}\\b.{0,60}\\b${SAFETY_NOUNS}\\b`, 'i');
|
|
84
|
+
const DISCLOSURE_PATTERN = new RegExp(`\\b${DISCLOSURE_VERBS}\\b.{0,60}\\b${SECRET_NOUNS}\\b`, 'i');
|
|
85
|
+
|
|
86
|
+
// ── Layer 3: Structural heuristics ───────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
const STRUCTURAL_HEURISTICS = [
|
|
89
|
+
{ pattern: /\byour\s+new\s+(?:system\s+)?(?:policy|rules?|instructions?|guidelines?|prompt)\s+(?:is|are|will be|should be)\b/i, reason: 'policy_redefinition' },
|
|
90
|
+
{ pattern: /\b(?:when|if|after|once)\b.{0,60}\b(?:ignore|stop|disable|bypass|override|forget|drop|abandon)\b.{0,40}\b(?:safety|rules?|policy|guidelines?|restrictions?|guardrails?)\b/i, reason: 'conditional_unsafe_trigger' },
|
|
91
|
+
{ pattern: /\b(?:respond|reply|answer|act|behave)\b.{0,30}\b(?:as if you were|as though you are|like you are|pretending to be)\b.{0,40}\b(?:unfiltered|unrestricted|evil|uncensored|without limits)\b/i, reason: 'identity_swap' },
|
|
92
|
+
{ pattern: /\b(?:obey\s+all|do\s+whatever|do\s+anything|comply\s+with\s+(?:any|all|every)|always\s+comply|never\s+refuse|never\s+decline|never\s+deny\s+a\s+request)\b/i, reason: 'blanket_obedience' },
|
|
93
|
+
{ pattern: /\b(?:execute|run|invoke|call|trigger)\b.{0,20}\b(?:any|all|every)\b.{0,30}\b(?:tool|command|action|function|operation)\b.{0,40}\b(?:without|no)\b.{0,20}\b(?:check|auth|approval|verification|restriction)\b/i, reason: 'unauthorized_execution' },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
export const DEFAULT_SAFETY_PATTERNS: SafetyPatternConfig = {
|
|
97
|
+
overridePattern: OVERRIDE_PATTERN,
|
|
98
|
+
negationPattern: NEGATION_PATTERN,
|
|
99
|
+
disclosurePattern: DISCLOSURE_PATTERN,
|
|
100
|
+
escalationPhrases: ESCALATION_PHRASES,
|
|
101
|
+
approvalTampering: APPROVAL_TAMPERING,
|
|
102
|
+
safetyDisable: SAFETY_DISABLE,
|
|
103
|
+
structuralHeuristics: STRUCTURAL_HEURISTICS,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export function scanDirective(raw: string, patterns: SafetyPatternConfig = DEFAULT_SAFETY_PATTERNS): ScanResult {
|
|
107
|
+
if (!raw || raw.trim() === '') {
|
|
108
|
+
return { safe: true };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const text = normalizeText(raw);
|
|
112
|
+
|
|
113
|
+
if (patterns.overridePattern.test(text)) {
|
|
114
|
+
return { safe: false, reason: 'unsafe_override' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (patterns.negationPattern.test(text)) {
|
|
118
|
+
return { safe: false, reason: 'unsafe_negation' };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (patterns.disclosurePattern.test(text)) {
|
|
122
|
+
return { safe: false, reason: 'unsafe_disclosure' };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const pattern of patterns.escalationPhrases) {
|
|
126
|
+
if (pattern.test(text)) {
|
|
127
|
+
return { safe: false, reason: 'unsafe_escalation' };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const pattern of patterns.approvalTampering) {
|
|
132
|
+
if (pattern.test(text)) {
|
|
133
|
+
return { safe: false, reason: 'unsafe_approval_tampering' };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const pattern of patterns.safetyDisable) {
|
|
138
|
+
if (pattern.test(text)) {
|
|
139
|
+
return { safe: false, reason: 'unsafe_safety_disable' };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const { pattern, reason } of patterns.structuralHeuristics) {
|
|
144
|
+
if (pattern.test(text)) {
|
|
145
|
+
return { safe: false, reason };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { safe: true };
|
|
150
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SelfPackageManifest,
|
|
3
|
+
SelfPackageParseResult,
|
|
4
|
+
ScannedDirective,
|
|
5
|
+
PersonalityTraits,
|
|
6
|
+
} from './types';
|
|
7
|
+
import { scanDirective } from './safety-scanner';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lightweight YAML to JS object parser supporting basic nested maps, lists, and primitives
|
|
11
|
+
* suitable for .self manifest schemas. Falls back to JSON.parse if the text starts with '{'.
|
|
12
|
+
*/
|
|
13
|
+
export function parseYamlOrJson(content: string): any {
|
|
14
|
+
const trimmed = content.trim();
|
|
15
|
+
if (trimmed.startsWith('{')) {
|
|
16
|
+
return JSON.parse(trimmed);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const lines = content.split('\n');
|
|
20
|
+
const root: any = {};
|
|
21
|
+
const stack: Array<{ indent: number; obj: any; key?: string; isList?: boolean }> = [
|
|
22
|
+
{ indent: -1, obj: root },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < lines.length; i++) {
|
|
26
|
+
const rawLine = lines[i];
|
|
27
|
+
// Remove comments
|
|
28
|
+
const commentIdx = rawLine.indexOf('#');
|
|
29
|
+
const line = (commentIdx >= 0 ? rawLine.slice(0, commentIdx) : rawLine).replace(/\r$/, '');
|
|
30
|
+
if (!line.trim()) continue;
|
|
31
|
+
|
|
32
|
+
const indent = line.search(/\S/);
|
|
33
|
+
const text = line.trim();
|
|
34
|
+
|
|
35
|
+
// Pop stack to match current indentation
|
|
36
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
37
|
+
stack.pop();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const currentParent = stack[stack.length - 1];
|
|
41
|
+
|
|
42
|
+
// Check if line is a list item: "- something"
|
|
43
|
+
if (text.startsWith('- ')) {
|
|
44
|
+
const listContent = text.slice(2).trim();
|
|
45
|
+
|
|
46
|
+
// Ensure parent has an array for current key or parent itself is list
|
|
47
|
+
let targetArray: any[];
|
|
48
|
+
if (Array.isArray(currentParent.obj)) {
|
|
49
|
+
targetArray = currentParent.obj;
|
|
50
|
+
} else if (currentParent.key) {
|
|
51
|
+
const parentFrame = stack.length > 1 ? stack[stack.length - 2] : null;
|
|
52
|
+
if (parentFrame && parentFrame.obj[currentParent.key] === currentParent.obj && Object.keys(currentParent.obj).length === 0) {
|
|
53
|
+
targetArray = [];
|
|
54
|
+
parentFrame.obj[currentParent.key] = targetArray;
|
|
55
|
+
currentParent.obj = targetArray;
|
|
56
|
+
} else if (Array.isArray(currentParent.obj[currentParent.key])) {
|
|
57
|
+
targetArray = currentParent.obj[currentParent.key];
|
|
58
|
+
} else {
|
|
59
|
+
targetArray = [];
|
|
60
|
+
currentParent.obj[currentParent.key] = targetArray;
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
targetArray = [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if list item has inline key-value (e.g. "- id: 'dir-01'")
|
|
67
|
+
const colonIdx = listContent.indexOf(':');
|
|
68
|
+
if (colonIdx > 0 && !listContent.startsWith('"') && !listContent.startsWith("'")) {
|
|
69
|
+
const itemKey = listContent.slice(0, colonIdx).trim();
|
|
70
|
+
const itemVal = parsePrimitive(listContent.slice(colonIdx + 1).trim());
|
|
71
|
+
const itemObj: any = {};
|
|
72
|
+
if (itemVal !== undefined && itemVal !== '') {
|
|
73
|
+
itemObj[itemKey] = itemVal;
|
|
74
|
+
} else {
|
|
75
|
+
itemObj[itemKey] = {};
|
|
76
|
+
}
|
|
77
|
+
targetArray.push(itemObj);
|
|
78
|
+
stack.push({ indent, obj: itemObj, isList: false });
|
|
79
|
+
} else {
|
|
80
|
+
// Plain list item (scalar)
|
|
81
|
+
targetArray.push(parsePrimitive(listContent));
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Key-value pair: "key: value"
|
|
87
|
+
const colonIdx = text.indexOf(':');
|
|
88
|
+
if (colonIdx > 0) {
|
|
89
|
+
const key = text.slice(0, colonIdx).trim();
|
|
90
|
+
const valStr = text.slice(colonIdx + 1).trim();
|
|
91
|
+
|
|
92
|
+
let targetObj = currentParent.obj;
|
|
93
|
+
if (Array.isArray(targetObj)) {
|
|
94
|
+
targetObj = targetObj[targetObj.length - 1];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (valStr === '' || valStr === undefined) {
|
|
98
|
+
// Nested map or upcoming list
|
|
99
|
+
const childObj: any = {};
|
|
100
|
+
targetObj[key] = childObj;
|
|
101
|
+
stack.push({ indent, obj: childObj, key, isList: false });
|
|
102
|
+
} else {
|
|
103
|
+
targetObj[key] = parsePrimitive(valStr);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return root;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parsePrimitive(val: string): any {
|
|
112
|
+
if (val === '') return '';
|
|
113
|
+
if (val === 'true') return true;
|
|
114
|
+
if (val === 'false') return false;
|
|
115
|
+
if (val === 'null') return null;
|
|
116
|
+
|
|
117
|
+
// Quoted string
|
|
118
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
119
|
+
return val.slice(1, -1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Number
|
|
123
|
+
const num = Number(val);
|
|
124
|
+
if (!isNaN(num) && val.trim() !== '') {
|
|
125
|
+
return num;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return val;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class SelfPackageParser {
|
|
132
|
+
static parse(rawContent: string): SelfPackageParseResult {
|
|
133
|
+
const errors: string[] = [];
|
|
134
|
+
let data: any;
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
data = parseYamlOrJson(rawContent);
|
|
138
|
+
} catch (err: any) {
|
|
139
|
+
return {
|
|
140
|
+
isValid: false,
|
|
141
|
+
errors: [`Failed to parse .self file: ${err.message}`],
|
|
142
|
+
scannedDirectives: [],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!data || typeof data !== 'object') {
|
|
147
|
+
return {
|
|
148
|
+
isValid: false,
|
|
149
|
+
errors: ['Invalid .self file format: Root must be an object'],
|
|
150
|
+
scannedDirectives: [],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 1. Spec & Kind
|
|
155
|
+
if (data.specVersion !== '1.0.0' && data.specVersion !== '2.0.0') {
|
|
156
|
+
errors.push(`Unsupported or missing specVersion: "${data.specVersion}" (expected "1.0.0" or "2.0.0")`);
|
|
157
|
+
}
|
|
158
|
+
if (data.kind !== 'self') {
|
|
159
|
+
errors.push(`Invalid kind: "${data.kind}" (expected "self")`);
|
|
160
|
+
}
|
|
161
|
+
if (!data.id || typeof data.id !== 'string') {
|
|
162
|
+
errors.push('Missing required string field: "id"');
|
|
163
|
+
}
|
|
164
|
+
if (!data.name || typeof data.name !== 'string') {
|
|
165
|
+
errors.push('Missing required string field: "name"');
|
|
166
|
+
}
|
|
167
|
+
if (!data.version || typeof data.version !== 'string') {
|
|
168
|
+
errors.push('Missing required string field: "version"');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 2. Author
|
|
172
|
+
if (!data.author || typeof data.author !== 'object' || !data.author.name) {
|
|
173
|
+
errors.push('Missing required field: "author" with "name"');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 3. Identity
|
|
177
|
+
if (!data.identity || typeof data.identity !== 'object' || !data.identity.name) {
|
|
178
|
+
errors.push('Missing required field: "identity" with "name"');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 4. Personality validation (Optional in v2.0 / LLM-native mode)
|
|
182
|
+
const p = data.personality;
|
|
183
|
+
let traits: PersonalityTraits | undefined;
|
|
184
|
+
|
|
185
|
+
if (p !== undefined && p !== null) {
|
|
186
|
+
if (typeof p !== 'object') {
|
|
187
|
+
errors.push('Field "personality" must be an object if provided');
|
|
188
|
+
} else {
|
|
189
|
+
traits = {};
|
|
190
|
+
const keys: Array<keyof PersonalityTraits> = ['warmth', 'formality', 'sarcasm', 'verbosity', 'curiosity'];
|
|
191
|
+
for (const k of keys) {
|
|
192
|
+
if (p[k] !== undefined) {
|
|
193
|
+
if (typeof p[k] !== 'number' || p[k] < 0.0 || p[k] > 1.0) {
|
|
194
|
+
errors.push(`Personality trait "${k}" must be a number between 0.0 and 1.0`);
|
|
195
|
+
} else {
|
|
196
|
+
traits[k] = p[k];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 5. Relationships validation (Optional)
|
|
204
|
+
const relationships = Array.isArray(data.relationships)
|
|
205
|
+
? data.relationships
|
|
206
|
+
.filter((r: any) => r && typeof r === 'object' && r.entityId)
|
|
207
|
+
.map((r: any) => ({
|
|
208
|
+
entityId: String(r.entityId),
|
|
209
|
+
role: String(r.role || 'user'),
|
|
210
|
+
stance: String(r.stance || 'neutral'),
|
|
211
|
+
conventions: Array.isArray(r.conventions) ? r.conventions.map(String) : undefined,
|
|
212
|
+
}))
|
|
213
|
+
: undefined;
|
|
214
|
+
|
|
215
|
+
// 6. Dialogue Examples validation (Optional)
|
|
216
|
+
const dialogueExamples = Array.isArray(data.dialogueExamples)
|
|
217
|
+
? data.dialogueExamples
|
|
218
|
+
.filter((ex: any) => ex && typeof ex === 'object' && ex.user && ex.assistant)
|
|
219
|
+
.map((ex: any) => ({
|
|
220
|
+
user: String(ex.user),
|
|
221
|
+
assistant: String(ex.assistant),
|
|
222
|
+
}))
|
|
223
|
+
: undefined;
|
|
224
|
+
|
|
225
|
+
// 7. Directives validation & scanning
|
|
226
|
+
const scannedDirectives: ScannedDirective[] = [];
|
|
227
|
+
if (!Array.isArray(data.directives)) {
|
|
228
|
+
errors.push('Missing required array field: "directives"');
|
|
229
|
+
} else {
|
|
230
|
+
for (let i = 0; i < data.directives.length; i++) {
|
|
231
|
+
const d = data.directives[i];
|
|
232
|
+
if (!d || typeof d !== 'object' || !d.directive) {
|
|
233
|
+
errors.push(`Directive at index ${i} is missing "directive" string`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const scan = scanDirective(d.directive);
|
|
238
|
+
scannedDirectives.push({
|
|
239
|
+
id: d.id || `dir-${i + 1}`,
|
|
240
|
+
priority: typeof d.priority === 'number' ? d.priority : 50,
|
|
241
|
+
directive: d.directive,
|
|
242
|
+
category: d.category || 'behavioral',
|
|
243
|
+
scopeActor: d.scopeActor,
|
|
244
|
+
supersedesId: d.supersedesId,
|
|
245
|
+
scanResult: scan,
|
|
246
|
+
approvedByDefault: scan.safe,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const isValid = errors.length === 0;
|
|
252
|
+
|
|
253
|
+
let manifest: SelfPackageManifest | undefined;
|
|
254
|
+
if (isValid) {
|
|
255
|
+
manifest = {
|
|
256
|
+
specVersion: data.specVersion,
|
|
257
|
+
kind: 'self',
|
|
258
|
+
id: data.id,
|
|
259
|
+
name: data.name,
|
|
260
|
+
version: data.version,
|
|
261
|
+
author: {
|
|
262
|
+
name: data.author.name,
|
|
263
|
+
url: data.author.url,
|
|
264
|
+
signature: data.author.signature,
|
|
265
|
+
},
|
|
266
|
+
license: data.license,
|
|
267
|
+
identity: {
|
|
268
|
+
name: data.identity.name,
|
|
269
|
+
archetype: data.identity.archetype,
|
|
270
|
+
origin: data.identity.origin,
|
|
271
|
+
ethos: data.identity.ethos,
|
|
272
|
+
},
|
|
273
|
+
personality: traits,
|
|
274
|
+
relationships,
|
|
275
|
+
directives: scannedDirectives.map((sd) => ({
|
|
276
|
+
id: sd.id,
|
|
277
|
+
priority: sd.priority,
|
|
278
|
+
directive: sd.directive,
|
|
279
|
+
category: sd.category,
|
|
280
|
+
scopeActor: sd.scopeActor,
|
|
281
|
+
supersedesId: sd.supersedesId,
|
|
282
|
+
})),
|
|
283
|
+
guardrails: Array.isArray(data.guardrails) ? data.guardrails : undefined,
|
|
284
|
+
dialogueExamples,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
manifest,
|
|
290
|
+
scannedDirectives,
|
|
291
|
+
isValid,
|
|
292
|
+
errors,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SiduriDatabase,
|
|
3
|
+
SelfIdentity,
|
|
4
|
+
PersonalityTraits,
|
|
5
|
+
SelfDirective,
|
|
6
|
+
SelfRelationship,
|
|
7
|
+
SelfDialogueExample,
|
|
8
|
+
} from '@sidurijs/core';
|
|
9
|
+
import { SelfRepository } from './types';
|
|
10
|
+
|
|
11
|
+
export interface SqliteSelfRepositoryOptions {
|
|
12
|
+
db?: SiduriDatabase;
|
|
13
|
+
dbPath?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_PERSONALITY_TRAITS: PersonalityTraits = {
|
|
17
|
+
warmth: 0.5,
|
|
18
|
+
formality: 0.5,
|
|
19
|
+
sarcasm: 0.5,
|
|
20
|
+
verbosity: 0.5,
|
|
21
|
+
curiosity: 0.5,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class SqliteSelfRepository implements SelfRepository {
|
|
25
|
+
private db: SiduriDatabase;
|
|
26
|
+
private ownsDb: boolean;
|
|
27
|
+
|
|
28
|
+
constructor(options: SqliteSelfRepositoryOptions = {}) {
|
|
29
|
+
if (options.db) {
|
|
30
|
+
this.db = options.db;
|
|
31
|
+
this.ownsDb = false;
|
|
32
|
+
} else {
|
|
33
|
+
this.db = new SiduriDatabase({ dbPath: options.dbPath });
|
|
34
|
+
this.ownsDb = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getIdentity(companionId: string): Promise<SelfIdentity | undefined> {
|
|
39
|
+
return this.db.getIdentity(companionId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async setIdentity(identity: SelfIdentity): Promise<void> {
|
|
43
|
+
this.db.setIdentity(identity);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getPersonality(companionId: string): Promise<PersonalityTraits> {
|
|
47
|
+
const traits = this.db.getPersonality(companionId);
|
|
48
|
+
return traits || { ...DEFAULT_PERSONALITY_TRAITS };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async setPersonality(companionId: string, traits: PersonalityTraits): Promise<void> {
|
|
52
|
+
this.db.setPersonality(companionId, traits);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getActiveDirectives(companionId: string): Promise<SelfDirective[]> {
|
|
56
|
+
return this.db.getActiveDirectives(companionId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async commitDirectives(companionId: string, directives: SelfDirective[]): Promise<void> {
|
|
60
|
+
for (const d of directives) {
|
|
61
|
+
this.db.commitDirective({
|
|
62
|
+
...d,
|
|
63
|
+
companionId,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async approveDirective(id: string, companionId?: string): Promise<void> {
|
|
69
|
+
this.db.approveDirective(id, companionId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async rejectDirective(id: string, companionId?: string): Promise<void> {
|
|
73
|
+
this.db.rejectDirective(id, companionId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async revokeDirective(id: string, companionId?: string): Promise<void> {
|
|
77
|
+
this.db.revokeDirective(id, companionId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async expireDirective(id: string, companionId?: string): Promise<void> {
|
|
81
|
+
this.db.expireDirective(id, companionId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async disableDirective(id: string, companionId?: string): Promise<void> {
|
|
85
|
+
this.db.disableDirective(id, companionId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getRelationships(companionId: string): Promise<SelfRelationship[]> {
|
|
89
|
+
return this.db.getRelationships(companionId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getRelationship(companionId: string, entityId: string): Promise<SelfRelationship | null> {
|
|
93
|
+
const rel = this.db.getRelationship(companionId, entityId);
|
|
94
|
+
return rel ?? null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async updateRelationship(companionId: string, rel: SelfRelationship): Promise<void> {
|
|
98
|
+
this.db.upsertRelationship({
|
|
99
|
+
...rel,
|
|
100
|
+
companionId,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getExemplars(companionId: string): Promise<SelfDialogueExample[]> {
|
|
105
|
+
return this.db.getExemplars(companionId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async setExemplars(companionId: string, exemplars: SelfDialogueExample[]): Promise<void> {
|
|
109
|
+
this.db.setExemplars(companionId, exemplars);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
close(): void {
|
|
113
|
+
if (this.ownsDb) {
|
|
114
|
+
this.db.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|