pacs-core 0.1.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/LICENSE +21 -0
- package/README.md +602 -0
- package/bin/pacs-cli.js +7 -0
- package/package.json +31 -0
- package/phase2-test.mjs +53 -0
- package/src/agent-creator.js +178 -0
- package/src/agent-registry.js +165 -0
- package/src/bootstrap.js +245 -0
- package/src/cli.js +384 -0
- package/src/crypto.js +118 -0
- package/src/index.js +74 -0
- package/src/inter-agent-bus.js +187 -0
- package/src/learn-mode.js +97 -0
- package/src/learning-loop.js +254 -0
- package/src/memory-store.js +226 -0
- package/src/openclaw-bridge.cjs +214 -0
- package/src/openclaw-bridge.js +304 -0
- package/src/routing-engine.js +221 -0
- package/src/test.js +298 -0
- package/src/trigger-parser.js +187 -0
- package/src/triggers.json +122 -0
package/src/test.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PACS Core — Test Script
|
|
3
|
+
* Run with: npm test
|
|
4
|
+
* Requires PACS_ENCRYPTION_KEY to be set.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const crypto = require('./crypto');
|
|
8
|
+
const memoryStore = require('./memory-store');
|
|
9
|
+
const triggerParser = require('./trigger-parser');
|
|
10
|
+
|
|
11
|
+
// Set test key globally
|
|
12
|
+
if (!process.env.PACS_ENCRYPTION_KEY) {
|
|
13
|
+
process.env.PACS_ENCRYPTION_KEY = 'test-key-for-testing-only-32chars';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let passed = 0;
|
|
17
|
+
let failed = 0;
|
|
18
|
+
|
|
19
|
+
function test(name, fn) {
|
|
20
|
+
try {
|
|
21
|
+
fn();
|
|
22
|
+
console.log(` ✓ ${name}`);
|
|
23
|
+
passed++;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.log(` ✗ ${name}`);
|
|
26
|
+
console.log(` Error: ${err.message}`);
|
|
27
|
+
failed++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function assertEqual(actual, expected, msg) {
|
|
32
|
+
if (actual !== expected) {
|
|
33
|
+
throw new Error(`${msg}: expected ${expected}, got ${actual}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function assertMatch(str, regex, msg) {
|
|
38
|
+
if (!regex.test(str)) {
|
|
39
|
+
throw new Error(`${msg}: "${str}" did not match ${regex}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function runTests() {
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log('[PACS Test Suite]');
|
|
46
|
+
console.log('');
|
|
47
|
+
|
|
48
|
+
// Set a test key if none is set
|
|
49
|
+
if (!process.env.PACS_ENCRYPTION_KEY) {
|
|
50
|
+
process.env.PACS_ENCRYPTION_KEY = 'test-key-for-testing-only-32chars';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log('1. Crypto Module');
|
|
54
|
+
test('encrypt() produces a colon-separated string', () => {
|
|
55
|
+
const encrypted = crypto.encrypt('hello world');
|
|
56
|
+
assertMatch(encrypted, /^[^:]+:[^:]+:[^:]+$/, 'encrypted format');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('decrypt() recovers plaintext', () => {
|
|
60
|
+
const encrypted = crypto.encrypt('hello world');
|
|
61
|
+
const decrypted = crypto.decrypt(encrypted);
|
|
62
|
+
assertEqual(decrypted, 'hello world', 'decrypted value');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('generateKey() produces 64-char hex', () => {
|
|
66
|
+
const key = crypto.generateKey();
|
|
67
|
+
assertEqual(key.length, 64, 'key length');
|
|
68
|
+
assertMatch(key, /^[a-f0-9]+$/, 'hex format');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('encrypt with custom password works', () => {
|
|
72
|
+
const encrypted = crypto.encrypt('secret', 'my-password');
|
|
73
|
+
const decrypted = crypto.decrypt(encrypted, 'my-password');
|
|
74
|
+
assertEqual(decrypted, 'secret', 'custom password decrypt');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log('2. Trigger Parser Module');
|
|
79
|
+
|
|
80
|
+
test('parse() detects MERKE trigger', () => {
|
|
81
|
+
const result = triggerParser.parse('MERKE: Ich spreche Deutsch');
|
|
82
|
+
if (!result) throw new Error('expected trigger');
|
|
83
|
+
assertEqual(result.trigger.id, 'MERKE', 'trigger id');
|
|
84
|
+
assertEqual(result.payload, 'Ich spreche Deutsch', 'payload');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('parse() detects LEARN alias', () => {
|
|
88
|
+
const result = triggerParser.parse('LEARN: I prefer English');
|
|
89
|
+
if (!result) throw new Error('expected trigger');
|
|
90
|
+
assertEqual(result.trigger.id, 'MERKE', 'trigger id (LEARN alias)');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('parse() returns null for plain text', () => {
|
|
94
|
+
const result = triggerParser.parse('Hello, how are you?');
|
|
95
|
+
if (result !== null) throw new Error('expected null');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('detectLearnMode() detects auto mode', () => {
|
|
99
|
+
const mode = triggerParser.detectLearnMode('AI: lern automatisch');
|
|
100
|
+
assertEqual(mode, 'auto', 'learn mode');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('detectLearnMode() detects on-demand mode', () => {
|
|
104
|
+
const mode = triggerParser.detectLearnMode('AI: nur auf Befehl');
|
|
105
|
+
assertEqual(mode, 'onDemand', 'learn mode');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('getHelpText() returns non-empty string', () => {
|
|
109
|
+
const help = triggerParser.getHelpText();
|
|
110
|
+
if (typeof help !== 'string' || help.length === 0) {
|
|
111
|
+
throw new Error('help text is empty');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log('3. Memory Store Module');
|
|
117
|
+
|
|
118
|
+
test('readMemory() returns object', () => {
|
|
119
|
+
const mem = memoryStore.readMemory();
|
|
120
|
+
if (typeof mem !== 'object') throw new Error('expected object');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('addFact() adds to memory', () => {
|
|
124
|
+
const mem = memoryStore.addFact('Test fact 123xyz');
|
|
125
|
+
if (!mem.facts.includes('Test fact 123xyz')) {
|
|
126
|
+
throw new Error('fact not added');
|
|
127
|
+
}
|
|
128
|
+
// Cleanup
|
|
129
|
+
memoryStore.removeFact('Test fact 123xyz');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('searchFacts() finds matching facts', () => {
|
|
133
|
+
memoryStore.addFact('UniqueSearchTermABC123');
|
|
134
|
+
const results = memoryStore.searchFacts('UniqueSearchTermABC123');
|
|
135
|
+
if (!results.includes('UniqueSearchTermABC123')) {
|
|
136
|
+
throw new Error('search failed');
|
|
137
|
+
}
|
|
138
|
+
memoryStore.removeFact('UniqueSearchTermABC123');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
144
|
+
console.log('');
|
|
145
|
+
|
|
146
|
+
if (failed > 0) {
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function runPhase2Tests() {
|
|
152
|
+
const { RoutingEngine, LearningLoop, LearnMode } = await import('./index.js');
|
|
153
|
+
|
|
154
|
+
console.log('4. Routing Engine Module');
|
|
155
|
+
|
|
156
|
+
test('route() returns object with plan array', () => {
|
|
157
|
+
const result = RoutingEngine.route('Organisiere meine Finanzen');
|
|
158
|
+
if (!result || !Array.isArray(result.plan)) throw new Error('expected plan array');
|
|
159
|
+
if (typeof result.confidence !== 'number') throw new Error('expected confidence number');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('route() returns plan with agent names', () => {
|
|
163
|
+
const result = RoutingEngine.route('Code review und Finanzen');
|
|
164
|
+
if (result.plan.length === 0) throw new Error('expected at least one agent');
|
|
165
|
+
for (const p of result.plan) {
|
|
166
|
+
if (typeof p.agent !== 'string') throw new Error('expected agent string');
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('routeParallel() returns parallel mode plan', () => {
|
|
171
|
+
const result = RoutingEngine.routeParallel('Marketing und Code');
|
|
172
|
+
if (!result.plan.every((p) => p.mode === 'parallel')) {
|
|
173
|
+
throw new Error('all plans should be parallel');
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('routeSequential() returns sequential mode plan', () => {
|
|
178
|
+
const result = RoutingEngine.routeSequential('Plan my week and send emails');
|
|
179
|
+
if (!result.plan.every((p) => p.mode === 'sequential')) {
|
|
180
|
+
throw new Error('all plans should be sequential');
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('whoHandles() returns array of agent names', () => {
|
|
185
|
+
const handlers = RoutingEngine.whoHandles('Steuererklärung');
|
|
186
|
+
if (!Array.isArray(handlers)) throw new Error('expected array');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log('5. Learn Mode Module');
|
|
191
|
+
|
|
192
|
+
test('getMode() returns a valid mode string', () => {
|
|
193
|
+
const mode = LearnMode.getMode();
|
|
194
|
+
if (!['safe', 'explicit', 'auto'].includes(mode)) {
|
|
195
|
+
throw new Error(`unexpected mode: ${mode}`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('setMode() accepts valid modes', () => {
|
|
200
|
+
LearnMode.setMode('explicit');
|
|
201
|
+
const m = LearnMode.getMode();
|
|
202
|
+
if (m !== 'explicit') throw new Error('mode not set to explicit');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('setMode() rejects invalid modes', () => {
|
|
206
|
+
let threw = false;
|
|
207
|
+
try {
|
|
208
|
+
LearnMode.setMode('invalid-mode');
|
|
209
|
+
} catch {
|
|
210
|
+
threw = true;
|
|
211
|
+
}
|
|
212
|
+
if (!threw) throw new Error('expected error for invalid mode');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('getModeDescription() returns non-empty string', () => {
|
|
216
|
+
const desc = LearnMode.getModeDescription();
|
|
217
|
+
if (!desc || desc.length < 5) throw new Error('description too short');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Restore default
|
|
221
|
+
LearnMode.setMode('safe');
|
|
222
|
+
|
|
223
|
+
console.log('');
|
|
224
|
+
console.log('6. Learning Loop Module');
|
|
225
|
+
|
|
226
|
+
test('shouldRemember() detects explicit MERKE trigger', () => {
|
|
227
|
+
const result = LearningLoop.shouldRemember('MERKE: Ich spreche Deutsch');
|
|
228
|
+
if (!result.should) throw new Error('expected should=true for MERKE trigger');
|
|
229
|
+
if (result.fact !== 'Ich spreche Deutsch') throw new Error('wrong fact extracted');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('shouldRemember() returns false for questions', () => {
|
|
233
|
+
const result = LearningLoop.shouldRemember('Was ist meine Steuernummer?');
|
|
234
|
+
if (result.should) throw new Error('expected should=false for question');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('shouldRemember() returns false for noise', () => {
|
|
238
|
+
const result = LearningLoop.shouldRemember('Hi');
|
|
239
|
+
if (result.should) throw new Error('expected should=false for noise');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('shouldRemember() detects personal fact in safe mode', () => {
|
|
243
|
+
LearnMode.setMode('safe');
|
|
244
|
+
const result = LearningLoop.shouldRemember('Ich heiße Max und arbeite bei Google');
|
|
245
|
+
// May or may not trigger depending on heuristics — just check structure
|
|
246
|
+
if (typeof result.should !== 'boolean') throw new Error('expected boolean should');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('shouldRemember() returns false in explicit mode for non-trigger', () => {
|
|
250
|
+
LearnMode.setMode('explicit');
|
|
251
|
+
const result = LearningLoop.shouldRemember('Ich bin Entwickler bei Acme Corp');
|
|
252
|
+
if (result.should) throw new Error('expected should=false in explicit mode without MERKE');
|
|
253
|
+
LearnMode.setMode('safe');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('promptConfirmation() returns DE confirmation', () => {
|
|
257
|
+
const prompt = LearningLoop.promptConfirmation('Ich spreche Deutsch', 'DE');
|
|
258
|
+
if (!prompt.includes('Soll ich mir das merken')) throw new Error('wrong DE prompt');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('promptConfirmation() returns EN confirmation', () => {
|
|
262
|
+
const prompt = LearningLoop.promptConfirmation('I prefer English', 'EN');
|
|
263
|
+
if (!prompt.includes('Should I remember')) throw new Error('wrong EN prompt');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('storeConfirmed() adds fact to memory', () => {
|
|
267
|
+
const uniqueFact = `Test fact ${Date.now()} unique`;
|
|
268
|
+
LearningLoop.storeConfirmed('test-user', uniqueFact);
|
|
269
|
+
const found = memoryStore.searchFacts(uniqueFact);
|
|
270
|
+
if (!found.includes(uniqueFact)) throw new Error('fact not stored');
|
|
271
|
+
memoryStore.removeFact(uniqueFact);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('rejectLearning() adds to rejected list', () => {
|
|
275
|
+
const uniqueFact = `Rejected ${Date.now()}`;
|
|
276
|
+
LearningLoop.rejectLearning('test-user', uniqueFact);
|
|
277
|
+
const wasR = LearningLoop.wasRejected(uniqueFact);
|
|
278
|
+
if (!wasR) throw new Error('expected fact to be marked rejected');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
283
|
+
console.log('');
|
|
284
|
+
|
|
285
|
+
if (failed > 0) {
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
runTests().catch((err) => {
|
|
291
|
+
console.error('Test error:', err);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
runPhase2Tests().catch((err) => {
|
|
296
|
+
console.error('Phase 2 test error:', err);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PACS Core — Trigger Parser Module
|
|
3
|
+
* Parses plaintext trigger commands from triggers.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
let triggersCache = null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load triggers from triggers.json.
|
|
13
|
+
* Cached after first load.
|
|
14
|
+
* @returns {object} Parsed triggers.json
|
|
15
|
+
*/
|
|
16
|
+
function loadTriggers() {
|
|
17
|
+
if (triggersCache) {
|
|
18
|
+
return triggersCache;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const triggerPath = path.join(__dirname, 'triggers.json');
|
|
22
|
+
|
|
23
|
+
if (!fs.existsSync(triggerPath)) {
|
|
24
|
+
throw new Error(`triggers.json not found at ${triggerPath}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const raw = fs.readFileSync(triggerPath, 'utf8');
|
|
28
|
+
triggersCache = JSON.parse(raw);
|
|
29
|
+
return triggersCache;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get all trigger definitions.
|
|
34
|
+
* @returns {object[]} Array of trigger objects
|
|
35
|
+
*/
|
|
36
|
+
function getAllTriggers() {
|
|
37
|
+
return loadTriggers().triggers;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get learn mode configurations.
|
|
42
|
+
* @returns {object} Learn modes
|
|
43
|
+
*/
|
|
44
|
+
function getLearnModes() {
|
|
45
|
+
return loadTriggers().learnModes;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find a trigger by its ID or alias.
|
|
50
|
+
* @param {string} input - The command string (e.g. "MERKE:", "LEARN:")
|
|
51
|
+
* @returns {object|null} Trigger object or null
|
|
52
|
+
*/
|
|
53
|
+
function findTrigger(input) {
|
|
54
|
+
if (!input || typeof input !== 'string') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const normalized = input.trim().toUpperCase().replace(/^:\s*/, '').replace(/:$/, '');
|
|
59
|
+
const triggers = getAllTriggers();
|
|
60
|
+
|
|
61
|
+
for (const trigger of triggers) {
|
|
62
|
+
// Check main ID
|
|
63
|
+
if (trigger.id.toUpperCase() === normalized) {
|
|
64
|
+
return trigger;
|
|
65
|
+
}
|
|
66
|
+
// Check aliases
|
|
67
|
+
for (const alias of trigger.aliases) {
|
|
68
|
+
if (alias.toUpperCase() === normalized) {
|
|
69
|
+
return trigger;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse a trigger command from a message.
|
|
79
|
+
* Extracts the trigger type and its payload.
|
|
80
|
+
* @param {string} message - The full message text
|
|
81
|
+
* @returns {object|null} { trigger: object, payload: string } or null
|
|
82
|
+
*/
|
|
83
|
+
function parse(message) {
|
|
84
|
+
if (!message || typeof message !== 'string') {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Find which trigger matches the start of the message
|
|
89
|
+
const triggers = getAllTriggers();
|
|
90
|
+
const upperMessage = message.toUpperCase().trim();
|
|
91
|
+
|
|
92
|
+
for (const trigger of triggers) {
|
|
93
|
+
const ids = [trigger.id, ...trigger.aliases].map((id) => id.toUpperCase());
|
|
94
|
+
|
|
95
|
+
for (const id of ids) {
|
|
96
|
+
// Match "ID:" or "ID: " or "ID " at the start
|
|
97
|
+
const escapedId = id.replace(/[.+*?^${}()|[\]\\]/g, '\\$&');
|
|
98
|
+
const patterns = [
|
|
99
|
+
new RegExp(`^${escapedId}:?\\s*`),
|
|
100
|
+
new RegExp(`^${escapedId}\\s+`),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
for (const pattern of patterns) {
|
|
104
|
+
const match = upperMessage.match(pattern);
|
|
105
|
+
if (match) {
|
|
106
|
+
// Extract payload after the trigger
|
|
107
|
+
const payload = message.slice(match[0].length).trim();
|
|
108
|
+
return {
|
|
109
|
+
trigger,
|
|
110
|
+
payload,
|
|
111
|
+
raw: message,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if a message contains any trigger command.
|
|
123
|
+
* @param {string} message - The message to check
|
|
124
|
+
* @returns {boolean}
|
|
125
|
+
*/
|
|
126
|
+
function containsTrigger(message) {
|
|
127
|
+
return parse(message) !== null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Detect learn mode from a message.
|
|
132
|
+
* @param {string} message - The message to check
|
|
133
|
+
* @returns {string|null} 'auto', 'onDemand', or null
|
|
134
|
+
*/
|
|
135
|
+
function detectLearnMode(message) {
|
|
136
|
+
if (!message) return null;
|
|
137
|
+
|
|
138
|
+
const modes = getLearnModes();
|
|
139
|
+
const upperMessage = message.toUpperCase();
|
|
140
|
+
|
|
141
|
+
for (const [modeKey, mode] of Object.entries(modes)) {
|
|
142
|
+
for (const keyword of mode.keywords) {
|
|
143
|
+
if (upperMessage.includes(keyword.toUpperCase())) {
|
|
144
|
+
return modeKey;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get a formatted help string for all triggers.
|
|
154
|
+
* @returns {string} Formatted help text
|
|
155
|
+
*/
|
|
156
|
+
function getHelpText() {
|
|
157
|
+
const triggers = getAllTriggers();
|
|
158
|
+
const lines = ['📋 PACS Commands (Phase 1):', ''];
|
|
159
|
+
|
|
160
|
+
for (const trigger of triggers) {
|
|
161
|
+
const aliasList = trigger.aliases.slice(0, 2).join('/');
|
|
162
|
+
const aliasStr = aliasList ? ` (${aliasList})` : '';
|
|
163
|
+
lines.push(` ${trigger.id}${aliasStr}: ${trigger.description}`);
|
|
164
|
+
if (trigger.examples && trigger.examples.length > 0) {
|
|
165
|
+
lines.push(` → ${trigger.examples[0]}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
lines.push('');
|
|
170
|
+
lines.push('Learn Modes:');
|
|
171
|
+
for (const [key, mode] of Object.entries(getLearnModes())) {
|
|
172
|
+
lines.push(` ${mode.name}: ${mode.keywords[0]}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return lines.join('\n');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
loadTriggers,
|
|
180
|
+
getAllTriggers,
|
|
181
|
+
getLearnModes,
|
|
182
|
+
findTrigger,
|
|
183
|
+
parse,
|
|
184
|
+
containsTrigger,
|
|
185
|
+
detectLearnMode,
|
|
186
|
+
getHelpText,
|
|
187
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "PACS Trigger Commands — Layer 1 (plaintext, human-editable)",
|
|
3
|
+
"_version": "1.0",
|
|
4
|
+
"triggers": [
|
|
5
|
+
{
|
|
6
|
+
"id": "MERKE",
|
|
7
|
+
"aliases": ["LEARN", "LERNE", "REMEMBER"],
|
|
8
|
+
"languages": ["DE", "EN"],
|
|
9
|
+
"description": "Store a new fact in encrypted memory",
|
|
10
|
+
"syntax": "MERKE: <fact>",
|
|
11
|
+
"examples": [
|
|
12
|
+
"MERKE: Ich spreche lieber Deutsch",
|
|
13
|
+
"LEARN: I prefer working on weekends"
|
|
14
|
+
],
|
|
15
|
+
"exampleResponse": "Soll ich mir das merken? \"Ich spreche lieber Deutsch\"",
|
|
16
|
+
"securityNote": "AI asks before storing — never auto-stores"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "PRIVAT",
|
|
20
|
+
"aliases": ["PRIVATE", "VERTRAULICH", "CONFIDENTIAL"],
|
|
21
|
+
"languages": ["DE", "EN"],
|
|
22
|
+
"description": "Mark information as private/confidential",
|
|
23
|
+
"syntax": "PRIVAT: <info>",
|
|
24
|
+
"examples": [
|
|
25
|
+
"PRIVAT: Meine Steuer-ID ist 123456",
|
|
26
|
+
"PRIVATE: My SSN is 123-45-6789"
|
|
27
|
+
],
|
|
28
|
+
"securityNote": "Stored separately, never included in exports"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "ROUTING",
|
|
32
|
+
"aliases": ["ROUTE", "WEITERLEITEN", "ROUTE_TO"],
|
|
33
|
+
"languages": ["DE", "EN"],
|
|
34
|
+
"description": "Add routing hint for task delegation",
|
|
35
|
+
"syntax": "ROUTING: <pattern> → <agent>",
|
|
36
|
+
"examples": [
|
|
37
|
+
"ROUTING: finance → finance-agent",
|
|
38
|
+
"ROUTE: code-review → code-agent"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "REGEL",
|
|
43
|
+
"aliases": ["RULE", "REGELN", "RULES"],
|
|
44
|
+
"languages": ["DE", "EN"],
|
|
45
|
+
"description": "Add a personal rule for AI behavior",
|
|
46
|
+
"syntax": "REGEL: <rule>",
|
|
47
|
+
"examples": [
|
|
48
|
+
"REGEL: Antworte immer auf Deutsch wenn der User Deutsch schreibt",
|
|
49
|
+
"RULE: Always use bullet points for lists"
|
|
50
|
+
],
|
|
51
|
+
"securityNote": "AI can reject rules that contradict its core logic"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "BEFEHLE",
|
|
55
|
+
"aliases": ["COMMANDS", "HELP", "HILFE", "?"],
|
|
56
|
+
"languages": ["DE", "EN"],
|
|
57
|
+
"description": "Show available PACS commands",
|
|
58
|
+
"syntax": "BEFEHLE:",
|
|
59
|
+
"examples": ["BEFEHLE:", "COMMANDS:"],
|
|
60
|
+
"note": "This is always shown in plaintext — no encryption needed"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "WAS_WEISST_DU",
|
|
64
|
+
"aliases": ["WHAT_DO_YOU_KNOW", "MEMORY", "GEDÄCHTNIS"],
|
|
65
|
+
"languages": ["DE", "EN"],
|
|
66
|
+
"description": "Query what the AI knows about the user",
|
|
67
|
+
"syntax": "WAS WEISST DU?",
|
|
68
|
+
"examples": ["WAS WEISST DU?", "WHAT DO YOU KNOW?"],
|
|
69
|
+
"note": "Shows non-private facts only"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "SUCHE",
|
|
73
|
+
"aliases": ["SEARCH", "FIND", "FINDE"],
|
|
74
|
+
"languages": ["DE", "EN"],
|
|
75
|
+
"description": "Search encrypted memory",
|
|
76
|
+
"syntax": "SUCHE: <query>",
|
|
77
|
+
"examples": [
|
|
78
|
+
"SUCHE: Sprache",
|
|
79
|
+
"SEARCH: weekend"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "VERGISS",
|
|
84
|
+
"aliases": ["FORGET", "DELETE", "LÖSCHE", "ENTFERNEN"],
|
|
85
|
+
"languages": ["DE", "EN"],
|
|
86
|
+
"description": "Delete something from memory",
|
|
87
|
+
"syntax": "VERGISS: <fact or keyword>",
|
|
88
|
+
"examples": [
|
|
89
|
+
"VERGISS: Steuer-ID",
|
|
90
|
+
"FORGET: weekend preferences"
|
|
91
|
+
],
|
|
92
|
+
"securityNote": "Confirms before deletion"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "EXPORTIEREN",
|
|
96
|
+
"aliases": ["EXPORT", "DUMP"],
|
|
97
|
+
"languages": ["DE", "EN"],
|
|
98
|
+
"description": "Export memory (if decryption possible)",
|
|
99
|
+
"syntax": "EXPORTIEREN:",
|
|
100
|
+
"examples": ["EXPORTIEREN:", "EXPORT:"],
|
|
101
|
+
"securityNote": "Excludes private facts. Requires confirmation."
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"learnModes": {
|
|
105
|
+
"auto": {
|
|
106
|
+
"name": "Auto-Learn",
|
|
107
|
+
"keywords": ["AI: lern automatisch", "AI: auto-learn on", "AI: learning enabled"],
|
|
108
|
+
"description": "AI will proactively offer to remember important facts"
|
|
109
|
+
},
|
|
110
|
+
"onDemand": {
|
|
111
|
+
"name": "On-Demand Only",
|
|
112
|
+
"keywords": ["AI: nur auf Befehl", "AI: manual only", "AI: learning disabled"],
|
|
113
|
+
"description": "AI only learns when explicitly commanded via MERKE:/LEARN:"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"security": {
|
|
117
|
+
"note": "AI ALWAYS has the final say. It can reject facts that contradict its logic.",
|
|
118
|
+
"encryption": "AES-256-GCM via crypto module",
|
|
119
|
+
"keyStorage": "Environment variable PACS_ENCRYPTION_KEY only — never in files",
|
|
120
|
+
"privateHandling": "Private facts are excluded from exports and never shared"
|
|
121
|
+
}
|
|
122
|
+
}
|