@siduri-x/api 2.0.1 → 2.0.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +16 -13
- package/dist/app.d.ts +2 -34
- package/dist/app.js +143 -129
- package/dist/boot.d.ts +92 -0
- package/dist/boot.js +146 -0
- package/dist/boot.test.d.ts +1 -0
- package/dist/boot.test.js +84 -0
- package/dist/context-mapper.d.ts +3 -1
- package/dist/context-mapper.js +78 -20
- package/dist/context-mapper.test.js +91 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -90
- package/dist/index.test.js +4 -4
- package/dist/knowledge.test.d.ts +1 -0
- package/dist/knowledge.test.js +138 -0
- package/dist/t6-security.test.js +52 -3
- package/package.json +6 -6
- package/src/app.ts +154 -173
- package/src/boot.test.ts +103 -0
- package/src/boot.ts +190 -0
- package/src/context-mapper.test.ts +100 -0
- package/src/context-mapper.ts +85 -20
- package/src/index.test.ts +4 -4
- package/src/index.ts +10 -100
- package/src/knowledge.test.ts +156 -0
- package/src/t6-security.test.ts +56 -3
package/src/app.ts
CHANGED
|
@@ -2,49 +2,25 @@ import express, { Express } from 'express';
|
|
|
2
2
|
import cors from 'cors';
|
|
3
3
|
import { createCorsOptions } from './cors';
|
|
4
4
|
import { SiduriRuntime, dispatchCompanionChat } from './runtime';
|
|
5
|
-
import {
|
|
6
|
-
import { SqliteMemoryStore } from '@siduri-x/memory';
|
|
7
|
-
import { VoiceAdapter, VoiceConfig } from '@siduri-x/voice';
|
|
8
|
-
import { EKnowledgeAdapter, EKnowledgeConfig } from '@siduri-x/eknowledge';
|
|
9
|
-
import { OpenRouterVisionAdapter, OpenRouterVisionConfig } from '@siduri-x/vision';
|
|
10
|
-
import { ActiveSelfCompiler, SelfPackageParser, SqliteSelfRepository, scanDirective } from '@siduri-x/self';
|
|
11
|
-
import { Live2DAdapter, Live2DAdapterConfig } from '@siduri-x/body';
|
|
5
|
+
import { SelfPackageParser, SqliteSelfRepository, scanDirective } from '@siduri-x/self';
|
|
12
6
|
import { FixtureObservationOrgan } from '@siduri-x/observation';
|
|
13
|
-
import {
|
|
14
|
-
import { DefaultEarOrgan, EarOrganConfig } from '@siduri-x/ear';
|
|
15
|
-
import { DefaultMouthOrgan, DefaultMouthOrganConfig } from '@siduri-x/mouth';
|
|
16
|
-
import { attachIdentity, requireAuth, Identity } from './auth';
|
|
7
|
+
import { attachIdentity, requireAuth, Identity, isLocalRequest } from './auth';
|
|
17
8
|
import { mapRequestContext } from './context-mapper';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface AppBootCompanionConfig {
|
|
36
|
-
name: string;
|
|
37
|
-
brain?: AppBrainConfig;
|
|
38
|
-
voice?: VoiceConfig;
|
|
39
|
-
knowledge?: EKnowledgeConfig;
|
|
40
|
-
vision?: OpenRouterVisionConfig;
|
|
41
|
-
behavior?: AppBehaviorConfig;
|
|
42
|
-
body?: Live2DAdapterConfig;
|
|
43
|
-
hands?: DefaultHandsOrganConfig;
|
|
44
|
-
ear?: EarOrganConfig;
|
|
45
|
-
mouth?: DefaultMouthOrganConfig;
|
|
46
|
-
[key: string]: unknown;
|
|
47
|
-
}
|
|
9
|
+
import {
|
|
10
|
+
bootCompanion,
|
|
11
|
+
AppBrainConfig,
|
|
12
|
+
AppBehaviorConfig,
|
|
13
|
+
AppBootCompanionConfig,
|
|
14
|
+
BootCompanionOptions,
|
|
15
|
+
} from './boot';
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
AppBrainConfig,
|
|
19
|
+
AppBehaviorConfig,
|
|
20
|
+
AppBootCompanionConfig,
|
|
21
|
+
BootCompanionOptions,
|
|
22
|
+
bootCompanion,
|
|
23
|
+
};
|
|
48
24
|
|
|
49
25
|
export interface AppInstance {
|
|
50
26
|
app: Express;
|
|
@@ -59,77 +35,6 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
59
35
|
|
|
60
36
|
let observationOrgan: FixtureObservationOrgan | undefined;
|
|
61
37
|
|
|
62
|
-
function createBrain(config?: AppBrainConfig) {
|
|
63
|
-
const provider = config?.provider || 'openrouter';
|
|
64
|
-
const defaultKeyEnv = provider === 'openai-compatible' ? 'OPENAI_COMPATIBLE_API_KEY' : 'OPENROUTER_API_KEY';
|
|
65
|
-
const apiKey = config?.apiKey || process.env[config?.apiKeyEnv || defaultKeyEnv] || '';
|
|
66
|
-
if (provider === 'openai-compatible') {
|
|
67
|
-
return new OpenAICompatibleBrain({
|
|
68
|
-
apiKey,
|
|
69
|
-
model: config?.model || 'local-model',
|
|
70
|
-
baseUrl: config?.baseUrl || 'http://127.0.0.1:1234/v1',
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
return new OpenRouterBrain({ apiKey, model: config?.model || 'gpt-4o-mini' });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isDisabled(config?: { provider?: string }): boolean {
|
|
77
|
-
return !config || config.provider === 'none';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function createVoice(config?: VoiceConfig) {
|
|
81
|
-
return isDisabled(config)
|
|
82
|
-
? undefined
|
|
83
|
-
: new VoiceAdapter({
|
|
84
|
-
provider: (config?.provider as any) || 'voicevox',
|
|
85
|
-
baseUrl: config?.baseUrl || process.env.VOICEVOX_URL || 'http://localhost:50021',
|
|
86
|
-
speakerId: config?.speakerId || 1,
|
|
87
|
-
...config,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function createKnowledge(config?: EKnowledgeConfig) {
|
|
92
|
-
return isDisabled(config) ? undefined : new EKnowledgeAdapter(config || {});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function createVision(config?: OpenRouterVisionConfig & { provider?: string }) {
|
|
96
|
-
return isDisabled(config)
|
|
97
|
-
? undefined
|
|
98
|
-
: new OpenRouterVisionAdapter({
|
|
99
|
-
apiKey: config?.apiKey || process.env.OPENROUTER_API_KEY || '',
|
|
100
|
-
model: config?.model || 'gpt-4-vision',
|
|
101
|
-
...config,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function createBehavior(config?: AppBehaviorConfig) {
|
|
106
|
-
return isDisabled(config) ? undefined : new ActiveSelfCompiler();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function createBody(config?: Live2DAdapterConfig & { provider?: string }) {
|
|
110
|
-
return isDisabled(config)
|
|
111
|
-
? undefined
|
|
112
|
-
: new Live2DAdapter(config);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function createHands(config?: DefaultHandsOrganConfig & { provider?: string }) {
|
|
116
|
-
return isDisabled(config)
|
|
117
|
-
? new DefaultHandsOrgan()
|
|
118
|
-
: new DefaultHandsOrgan(config);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function createEar(config?: EarOrganConfig & { provider?: string }) {
|
|
122
|
-
return isDisabled(config)
|
|
123
|
-
? new DefaultEarOrgan()
|
|
124
|
-
: new DefaultEarOrgan(config);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function createMouth(config?: DefaultMouthOrganConfig & { provider?: string }, voice?: any) {
|
|
128
|
-
return isDisabled(config)
|
|
129
|
-
? new DefaultMouthOrgan({ voice })
|
|
130
|
-
: new DefaultMouthOrgan({ ...config, voice });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
38
|
app.post('/boot', requireAuth, async (req, res) => {
|
|
134
39
|
try {
|
|
135
40
|
const { id, config } = req.body;
|
|
@@ -137,36 +42,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
137
42
|
return res.status(400).json({ error: "Already booted" });
|
|
138
43
|
}
|
|
139
44
|
|
|
140
|
-
const
|
|
141
|
-
const brain = createBrain(organs.brain || config?.brain);
|
|
142
|
-
const memory = new SqliteMemoryStore({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
|
|
143
|
-
const selfRepo = new SqliteSelfRepository({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
|
|
144
|
-
const voice = createVoice(organs.voice || config?.voice);
|
|
145
|
-
const knowledge = createKnowledge(organs.knowledge || config?.knowledge);
|
|
146
|
-
const vision = createVision(organs.vision || config?.vision);
|
|
147
|
-
const behavior = createBehavior(organs.behavior || config?.behavior);
|
|
148
|
-
const body = createBody(organs.body || config?.body);
|
|
149
|
-
const hands = createHands(organs.hands || config?.hands);
|
|
150
|
-
const ear = createEar(organs.ear || config?.ear);
|
|
151
|
-
const mouth = createMouth(organs.mouth || config?.mouth, voice);
|
|
152
|
-
|
|
153
|
-
const runtime = new SiduriRuntime(id, config, {
|
|
154
|
-
brain,
|
|
155
|
-
memory,
|
|
156
|
-
voice,
|
|
157
|
-
knowledge,
|
|
158
|
-
vision,
|
|
159
|
-
behavior,
|
|
160
|
-
body,
|
|
161
|
-
hands,
|
|
162
|
-
ear,
|
|
163
|
-
mouth,
|
|
164
|
-
observation: observationOrgan,
|
|
165
|
-
self: selfRepo,
|
|
166
|
-
externalKnowledge: knowledge,
|
|
167
|
-
});
|
|
168
|
-
await runtime.initialize();
|
|
169
|
-
|
|
45
|
+
const runtime = await bootCompanion(id, config, { observationOrgan });
|
|
170
46
|
runtimes.set(id, runtime);
|
|
171
47
|
|
|
172
48
|
res.json({ success: true, id });
|
|
@@ -285,13 +161,20 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
285
161
|
app.post('/chat', attachIdentity, async (req, res) => {
|
|
286
162
|
const { id, message, history } = req.body;
|
|
287
163
|
const identity = (req as any).identity as Identity;
|
|
164
|
+
const local = isLocalRequest(req);
|
|
165
|
+
const hasConfiguredToken = Boolean(process.env.AUTH_TOKEN || process.env.OPERATOR_TOKEN || process.env.OWNER_TOKEN);
|
|
166
|
+
const isLocalDirectOwner = local && !hasConfiguredToken;
|
|
288
167
|
|
|
289
|
-
|
|
168
|
+
const authenticated = identity?.authenticated || isLocalDirectOwner;
|
|
169
|
+
const serverRole = identity?.authenticated ? identity.role : (isLocalDirectOwner ? 'OWNER' : 'VIEWER');
|
|
170
|
+
|
|
171
|
+
// Single-owner companion model: map request context directly with server identity enforcement
|
|
290
172
|
const mappingResult = mapRequestContext({
|
|
291
173
|
...req.body,
|
|
292
174
|
id: id || req.body.companionId,
|
|
293
|
-
authenticated
|
|
294
|
-
|
|
175
|
+
authenticated,
|
|
176
|
+
serverRole,
|
|
177
|
+
source: identity?.source ?? (local ? 'local' : 'external'),
|
|
295
178
|
generateCorrelationId: true,
|
|
296
179
|
});
|
|
297
180
|
|
|
@@ -325,12 +208,19 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
325
208
|
app.post('/chat/stream', attachIdentity, async (req, res) => {
|
|
326
209
|
const { id, message, history } = req.body;
|
|
327
210
|
const identity = (req as any).identity as Identity;
|
|
211
|
+
const local = isLocalRequest(req);
|
|
212
|
+
const hasConfiguredToken = Boolean(process.env.AUTH_TOKEN || process.env.OPERATOR_TOKEN || process.env.OWNER_TOKEN);
|
|
213
|
+
const isLocalDirectOwner = local && !hasConfiguredToken;
|
|
214
|
+
|
|
215
|
+
const authenticated = identity?.authenticated || isLocalDirectOwner;
|
|
216
|
+
const serverRole = identity?.authenticated ? identity.role : (isLocalDirectOwner ? 'OWNER' : 'VIEWER');
|
|
328
217
|
|
|
329
218
|
const mappingResult = mapRequestContext({
|
|
330
219
|
...req.body,
|
|
331
220
|
id: id || req.body.companionId,
|
|
332
|
-
authenticated
|
|
333
|
-
|
|
221
|
+
authenticated,
|
|
222
|
+
serverRole,
|
|
223
|
+
source: identity?.source ?? (local ? 'local' : 'external'),
|
|
334
224
|
generateCorrelationId: true,
|
|
335
225
|
});
|
|
336
226
|
|
|
@@ -353,8 +243,8 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
353
243
|
const abortController = new AbortController();
|
|
354
244
|
const onClose = () => {
|
|
355
245
|
abortController.abort('client_disconnect');
|
|
356
|
-
if (typeof runtime.
|
|
357
|
-
runtime.
|
|
246
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
247
|
+
runtime.mouth.interrupt('client_disconnect');
|
|
358
248
|
}
|
|
359
249
|
};
|
|
360
250
|
req.on('close', onClose);
|
|
@@ -427,12 +317,16 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
427
317
|
const reason = req.body?.reason || 'user_barge_in';
|
|
428
318
|
|
|
429
319
|
if (runtime) {
|
|
430
|
-
runtime.
|
|
320
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
321
|
+
runtime.mouth.interrupt(reason);
|
|
322
|
+
}
|
|
431
323
|
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
432
324
|
}
|
|
433
325
|
|
|
434
326
|
for (const r of runtimes.values()) {
|
|
435
|
-
r.
|
|
327
|
+
if (r.mouth && typeof r.mouth.interrupt === 'function') {
|
|
328
|
+
r.mouth.interrupt(reason);
|
|
329
|
+
}
|
|
436
330
|
}
|
|
437
331
|
return res.json({ success: true, interrupted: true, reason });
|
|
438
332
|
});
|
|
@@ -443,12 +337,16 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
443
337
|
const reason = req.body?.reason || 'user_barge_in';
|
|
444
338
|
|
|
445
339
|
if (runtime) {
|
|
446
|
-
runtime.
|
|
340
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
341
|
+
runtime.mouth.interrupt(reason);
|
|
342
|
+
}
|
|
447
343
|
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
448
344
|
}
|
|
449
345
|
|
|
450
346
|
for (const r of runtimes.values()) {
|
|
451
|
-
r.
|
|
347
|
+
if (r.mouth && typeof r.mouth.interrupt === 'function') {
|
|
348
|
+
r.mouth.interrupt(reason);
|
|
349
|
+
}
|
|
452
350
|
}
|
|
453
351
|
return res.json({ success: true, interrupted: true, reason });
|
|
454
352
|
});
|
|
@@ -460,7 +358,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
460
358
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
461
359
|
if (!runtime.memory) return res.json({ proposals: [] });
|
|
462
360
|
try {
|
|
463
|
-
const proposals = await runtime.getPendingClaims();
|
|
361
|
+
const proposals = await runtime.memory.getPendingClaims();
|
|
464
362
|
res.json({ proposals });
|
|
465
363
|
} catch (e: any) {
|
|
466
364
|
res.status(500).json({ error: e.message });
|
|
@@ -473,7 +371,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
473
371
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
474
372
|
if (!runtime.memory) return res.json({ items: [] });
|
|
475
373
|
try {
|
|
476
|
-
const items = await runtime.getClaims();
|
|
374
|
+
const items = await runtime.memory.getClaims();
|
|
477
375
|
res.json({ items });
|
|
478
376
|
} catch (e: any) {
|
|
479
377
|
res.status(500).json({ error: e.message });
|
|
@@ -486,7 +384,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
486
384
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
487
385
|
if (!runtime.memory) return res.json({ claims: [] });
|
|
488
386
|
try {
|
|
489
|
-
const claims = await runtime.getClaims();
|
|
387
|
+
const claims = await runtime.memory.getClaims();
|
|
490
388
|
res.json({ claims });
|
|
491
389
|
} catch (e: any) {
|
|
492
390
|
res.status(500).json({ error: e.message });
|
|
@@ -499,13 +397,96 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
499
397
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
500
398
|
if (!runtime.memory) return res.json({ directives: [] });
|
|
501
399
|
try {
|
|
502
|
-
const directives = await runtime.getDirectives();
|
|
400
|
+
const directives = await runtime.memory.getDirectives();
|
|
503
401
|
res.json({ directives });
|
|
504
402
|
} catch (e: any) {
|
|
505
403
|
res.status(500).json({ error: e.message });
|
|
506
404
|
}
|
|
507
405
|
});
|
|
508
406
|
|
|
407
|
+
// KNOWLEDGE / LIFE DB GETTERS
|
|
408
|
+
app.get('/knowledge/life', requireAuth, async (req, res) => {
|
|
409
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
410
|
+
const runtime = runtimes.get(id);
|
|
411
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
412
|
+
const query = (req.query.q as string) || '';
|
|
413
|
+
if (!runtime.knowledge || typeof (runtime.knowledge as any).queryLifeContext !== 'function') {
|
|
414
|
+
return res.json({ matchedInventory: [], recentFinances: [], upcomingSchedule: [], preferences: [], formattedContext: '' });
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const result = await (runtime.knowledge as any).queryLifeContext(id, query);
|
|
418
|
+
res.json(result);
|
|
419
|
+
} catch (e: any) {
|
|
420
|
+
res.status(500).json({ error: e.message });
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
app.get('/knowledge/inventory', requireAuth, async (req, res) => {
|
|
425
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
426
|
+
const runtime = runtimes.get(id);
|
|
427
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
428
|
+
const domain = req.query.domain as string | undefined;
|
|
429
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).inventory) {
|
|
430
|
+
return res.json({ items: [] });
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const items = await (runtime.knowledge as any).inventory.getItems(id, domain);
|
|
434
|
+
res.json({ items });
|
|
435
|
+
} catch (e: any) {
|
|
436
|
+
res.status(500).json({ error: e.message });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
app.get('/knowledge/finance', requireAuth, async (req, res) => {
|
|
441
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
442
|
+
const runtime = runtimes.get(id);
|
|
443
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
444
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).finance) {
|
|
445
|
+
return res.json({ summary: null, entries: [] });
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
const limit = Number(req.query.limit || 20);
|
|
449
|
+
const [summary, entries] = await Promise.all([
|
|
450
|
+
(runtime.knowledge as any).finance.getSummary(id),
|
|
451
|
+
(runtime.knowledge as any).finance.getEntries(id, limit),
|
|
452
|
+
]);
|
|
453
|
+
res.json({ summary, entries });
|
|
454
|
+
} catch (e: any) {
|
|
455
|
+
res.status(500).json({ error: e.message });
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
app.get('/knowledge/schedule', requireAuth, async (req, res) => {
|
|
460
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
461
|
+
const runtime = runtimes.get(id);
|
|
462
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
463
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).schedule) {
|
|
464
|
+
return res.json({ items: [] });
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
const items = await (runtime.knowledge as any).schedule.getUpcoming(id);
|
|
468
|
+
res.json({ items });
|
|
469
|
+
} catch (e: any) {
|
|
470
|
+
res.status(500).json({ error: e.message });
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
app.get('/knowledge/preferences', requireAuth, async (req, res) => {
|
|
475
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
476
|
+
const runtime = runtimes.get(id);
|
|
477
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
478
|
+
const category = req.query.category as string | undefined;
|
|
479
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).preferences) {
|
|
480
|
+
return res.json({ preferences: [] });
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const preferences = await (runtime.knowledge as any).preferences.getPreferences(id, category);
|
|
484
|
+
res.json({ preferences });
|
|
485
|
+
} catch (e: any) {
|
|
486
|
+
res.status(500).json({ error: e.message });
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
509
490
|
// MEMORY MUTATIONS - PROPOSALS
|
|
510
491
|
app.post('/memory/proposals/update', requireAuth, async (req, res) => {
|
|
511
492
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
@@ -519,7 +500,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
519
500
|
if (!claimId) {
|
|
520
501
|
return res.status(400).json({ error: "Missing required claim id" });
|
|
521
502
|
}
|
|
522
|
-
const updated = await runtime.updateClaim(claimId, req.body.updates || req.body);
|
|
503
|
+
const updated = await runtime.memory.updateClaim(claimId, req.body.updates || req.body);
|
|
523
504
|
res.json({ success: true, claim: updated });
|
|
524
505
|
} catch (e: any) {
|
|
525
506
|
res.status(500).json({ error: e.message });
|
|
@@ -532,7 +513,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
532
513
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
533
514
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
534
515
|
try {
|
|
535
|
-
await runtime.approveClaim(req.body.id);
|
|
516
|
+
await runtime.memory.approveClaim(req.body.id);
|
|
536
517
|
res.json({ approved: true });
|
|
537
518
|
} catch (e: any) {
|
|
538
519
|
res.status(500).json({ error: e.message });
|
|
@@ -545,7 +526,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
545
526
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
546
527
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
547
528
|
try {
|
|
548
|
-
await runtime.rejectClaim(req.body.id);
|
|
529
|
+
await runtime.memory.rejectClaim(req.body.id);
|
|
549
530
|
res.json({ rejected: true });
|
|
550
531
|
} catch (e: any) {
|
|
551
532
|
res.status(500).json({ error: e.message });
|
|
@@ -559,7 +540,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
559
540
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
560
541
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
561
542
|
try {
|
|
562
|
-
await runtime.approveDirective(req.body.id);
|
|
543
|
+
await runtime.memory.approveDirective(req.body.id);
|
|
563
544
|
res.json({ approved: true });
|
|
564
545
|
} catch (e: any) {
|
|
565
546
|
res.status(500).json({ error: e.message });
|
|
@@ -572,7 +553,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
572
553
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
573
554
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
574
555
|
try {
|
|
575
|
-
await runtime.rejectDirective(req.body.id);
|
|
556
|
+
await runtime.memory.rejectDirective(req.body.id);
|
|
576
557
|
res.json({ rejected: true });
|
|
577
558
|
} catch (e: any) {
|
|
578
559
|
res.status(500).json({ error: e.message });
|
|
@@ -585,7 +566,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
585
566
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
586
567
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
587
568
|
try {
|
|
588
|
-
await runtime.revokeDirective(req.body.id);
|
|
569
|
+
await runtime.memory.revokeDirective(req.body.id);
|
|
589
570
|
res.json({ revoked: true });
|
|
590
571
|
} catch (e: any) {
|
|
591
572
|
res.status(500).json({ error: e.message });
|
|
@@ -598,7 +579,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
598
579
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
599
580
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
600
581
|
try {
|
|
601
|
-
await runtime.disableDirective(req.body.id);
|
|
582
|
+
await runtime.memory.disableDirective(req.body.id);
|
|
602
583
|
res.json({ disabled: true });
|
|
603
584
|
} catch (e: any) {
|
|
604
585
|
res.status(500).json({ error: e.message });
|
|
@@ -615,7 +596,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
615
596
|
return res.status(400).json({ error: "Memory organ does not support reset" });
|
|
616
597
|
}
|
|
617
598
|
try {
|
|
618
|
-
await runtime.resetMemory();
|
|
599
|
+
await runtime.memory.resetMemory();
|
|
619
600
|
res.json({ reset: true });
|
|
620
601
|
} catch (e: any) {
|
|
621
602
|
res.status(500).json({ error: e.message });
|
|
@@ -626,7 +607,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
626
607
|
const companionId = req.body?.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
627
608
|
const runtime = runtimes.get(companionId);
|
|
628
609
|
if (!runtime) return res.status(404).json({ accepted: false, error: 'Companion not found' });
|
|
629
|
-
const staged = runtime.stageResponse({
|
|
610
|
+
const staged = runtime.gating.stageResponse({
|
|
630
611
|
requestContext: {
|
|
631
612
|
companionId,
|
|
632
613
|
actor: {
|
|
@@ -662,12 +643,12 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
662
643
|
let correlationId = req.body?.correlation_id;
|
|
663
644
|
|
|
664
645
|
if (!responseId && correlationId) {
|
|
665
|
-
const found = runtime.findStagedPlanByCorrelation(companionId, correlationId);
|
|
646
|
+
const found = runtime.gating.findStagedPlanByCorrelation(companionId, correlationId);
|
|
666
647
|
if (found) {
|
|
667
648
|
responseId = found.responseId;
|
|
668
649
|
}
|
|
669
650
|
} else if (responseId && !correlationId) {
|
|
670
|
-
const found = runtime.getStagedPlan(responseId);
|
|
651
|
+
const found = runtime.gating.getStagedPlan(responseId);
|
|
671
652
|
if (found) {
|
|
672
653
|
correlationId = found.correlationId;
|
|
673
654
|
}
|
|
@@ -677,7 +658,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
677
658
|
return res.status(400).json({ approved: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
678
659
|
}
|
|
679
660
|
|
|
680
|
-
const result = runtime.approveResponse({
|
|
661
|
+
const result = runtime.gating.approveResponse({
|
|
681
662
|
responseId,
|
|
682
663
|
companionId,
|
|
683
664
|
correlationId: correlationId || '',
|
|
@@ -688,7 +669,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
688
669
|
}
|
|
689
670
|
|
|
690
671
|
// Now evaluated as approved
|
|
691
|
-
const evaluation = runtime.evaluateGate(result.plan!);
|
|
672
|
+
const evaluation = runtime.gating.evaluateGate(result.plan!);
|
|
692
673
|
res.json({
|
|
693
674
|
approved: true,
|
|
694
675
|
status: evaluation.disposition,
|
|
@@ -707,12 +688,12 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
707
688
|
let correlationId = req.body?.correlation_id;
|
|
708
689
|
|
|
709
690
|
if (!responseId && correlationId) {
|
|
710
|
-
const found = runtime.findStagedPlanByCorrelation(companionId, correlationId);
|
|
691
|
+
const found = runtime.gating.findStagedPlanByCorrelation(companionId, correlationId);
|
|
711
692
|
if (found) {
|
|
712
693
|
responseId = found.responseId;
|
|
713
694
|
}
|
|
714
695
|
} else if (responseId && !correlationId) {
|
|
715
|
-
const found = runtime.getStagedPlan(responseId);
|
|
696
|
+
const found = runtime.gating.getStagedPlan(responseId);
|
|
716
697
|
if (found) {
|
|
717
698
|
correlationId = found.correlationId;
|
|
718
699
|
}
|
|
@@ -722,7 +703,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
722
703
|
return res.status(400).json({ rejected: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
723
704
|
}
|
|
724
705
|
|
|
725
|
-
const result = runtime.rejectResponse({
|
|
706
|
+
const result = runtime.gating.rejectResponse({
|
|
726
707
|
responseId,
|
|
727
708
|
companionId,
|
|
728
709
|
correlationId: correlationId || '',
|
package/src/boot.test.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { bootCompanion, isDisabled, createBrain, createMemory, createVoice, createKnowledge, createVision, createBehavior, createBody, createHands, createEar, createMouth, createSelf, createObservation } from './boot';
|
|
2
|
+
import { FixtureObservationOrgan } from '@siduri-x/observation';
|
|
3
|
+
|
|
4
|
+
describe('Canonical bootCompanion & Organ Factory Suite', () => {
|
|
5
|
+
const originalEnv = process.env;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
process.env = { ...originalEnv };
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterAll(() => {
|
|
12
|
+
process.env = originalEnv;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('isDisabled correctly identifies undefined or none provider', () => {
|
|
16
|
+
expect(isDisabled(undefined)).toBe(true);
|
|
17
|
+
expect(isDisabled({ provider: 'none' })).toBe(true);
|
|
18
|
+
expect(isDisabled({ provider: 'openrouter' })).toBe(false);
|
|
19
|
+
expect(isDisabled({ provider: 'sqlite' })).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('creates all standard organs with fallback configurations', () => {
|
|
23
|
+
const brain = createBrain({ provider: 'openai-compatible', baseUrl: 'http://localhost:11434/v1', apiKey: 'test' });
|
|
24
|
+
expect(brain).toBeDefined();
|
|
25
|
+
|
|
26
|
+
const voice = createVoice({ provider: 'none' });
|
|
27
|
+
expect(voice).toBeUndefined();
|
|
28
|
+
|
|
29
|
+
const memory = createMemory({ provider: 'sqlite', dbPath: ':memory:' });
|
|
30
|
+
expect(memory).toBeDefined();
|
|
31
|
+
|
|
32
|
+
const selfRepo = createSelf({ dbPath: ':memory:' });
|
|
33
|
+
expect(selfRepo).toBeDefined();
|
|
34
|
+
|
|
35
|
+
const vision = createVision({ provider: 'none' });
|
|
36
|
+
expect(vision).toBeUndefined();
|
|
37
|
+
|
|
38
|
+
const behavior = createBehavior({ provider: 'active_self' });
|
|
39
|
+
expect(behavior).toBeDefined();
|
|
40
|
+
|
|
41
|
+
const body = createBody({ provider: 'none' });
|
|
42
|
+
expect(body).toBeUndefined();
|
|
43
|
+
|
|
44
|
+
const hands = createHands();
|
|
45
|
+
expect(hands).toBeDefined();
|
|
46
|
+
|
|
47
|
+
const ear = createEar();
|
|
48
|
+
expect(ear).toBeDefined();
|
|
49
|
+
|
|
50
|
+
const mouth = createMouth(undefined, voice);
|
|
51
|
+
expect(mouth).toBeDefined();
|
|
52
|
+
|
|
53
|
+
const observation = createObservation();
|
|
54
|
+
expect(observation).toBeInstanceOf(FixtureObservationOrgan);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('bootCompanion wires all organs and runs migrations', async () => {
|
|
58
|
+
let migrationsRun = false;
|
|
59
|
+
const testConfig = {
|
|
60
|
+
name: 'Test Companion',
|
|
61
|
+
brain: { provider: 'openrouter', apiKey: 'mock-key', model: 'mock-model' },
|
|
62
|
+
memory: { provider: 'none' }, // will use custom mock
|
|
63
|
+
voice: { provider: 'none' },
|
|
64
|
+
vision: { provider: 'none' },
|
|
65
|
+
behavior: { provider: 'none' },
|
|
66
|
+
body: { provider: 'none' },
|
|
67
|
+
hands: { provider: 'none' },
|
|
68
|
+
ear: { provider: 'none' },
|
|
69
|
+
mouth: { provider: 'none' },
|
|
70
|
+
knowledge: { provider: 'none' },
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const mockObservation = new FixtureObservationOrgan({ analyze: async () => JSON.stringify({ readings: [] }) });
|
|
74
|
+
|
|
75
|
+
const runtime = await bootCompanion('test-comp-1', testConfig as any, {
|
|
76
|
+
observationOrgan: mockObservation,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(runtime).toBeDefined();
|
|
80
|
+
expect(runtime.id).toBe('test-comp-1');
|
|
81
|
+
expect(runtime.observation).toBe(mockObservation);
|
|
82
|
+
expect(runtime.hands).toBeDefined();
|
|
83
|
+
expect(runtime.ear).toBeDefined();
|
|
84
|
+
expect(runtime.mouth).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('bootCompanion handles nested organs configuration structure', async () => {
|
|
88
|
+
const nestedConfig = {
|
|
89
|
+
name: 'Nested Companion',
|
|
90
|
+
organs: {
|
|
91
|
+
brain: { provider: 'openrouter', apiKey: 'mock' },
|
|
92
|
+
memory: { provider: 'none' },
|
|
93
|
+
knowledge: { provider: 'none' },
|
|
94
|
+
voice: { provider: 'none' },
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const runtime = await bootCompanion('nested-comp', nestedConfig as any);
|
|
99
|
+
expect(runtime).toBeDefined();
|
|
100
|
+
expect(runtime.id).toBe('nested-comp');
|
|
101
|
+
expect(runtime.brain).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
});
|