@siduri-x/api 2.0.1 → 2.0.3
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 +17 -14
- package/dist/app.d.ts +2 -34
- package/dist/app.js +163 -130
- 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 +80 -20
- package/dist/context-mapper.test.js +126 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -90
- package/dist/index.test.js +18 -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/dist/teach-mode.test.js +67 -0
- package/package.json +6 -6
- package/siduri.sqlite +0 -0
- package/src/app.ts +176 -174
- package/src/boot.test.ts +103 -0
- package/src/boot.ts +190 -0
- package/src/context-mapper.test.ts +139 -0
- package/src/context-mapper.ts +87 -20
- package/src/index.test.ts +24 -4
- package/src/index.ts +10 -100
- package/src/knowledge.test.ts +156 -0
- package/src/t6-security.test.ts +56 -3
- package/src/teach-mode.test.ts +77 -0
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 });
|
|
@@ -260,6 +136,8 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
260
136
|
companionId,
|
|
261
137
|
name: manifest.identity.name,
|
|
262
138
|
archetype: manifest.identity.archetype,
|
|
139
|
+
origin: manifest.identity.origin,
|
|
140
|
+
ethos: manifest.identity.ethos,
|
|
263
141
|
version: manifest.version || '1.0.0',
|
|
264
142
|
updatedAt: new Date().toISOString(),
|
|
265
143
|
});
|
|
@@ -268,6 +146,25 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
268
146
|
await repo.setPersonality(companionId, manifest.personality);
|
|
269
147
|
}
|
|
270
148
|
|
|
149
|
+
if (Array.isArray(manifest.relationships)) {
|
|
150
|
+
for (const rel of manifest.relationships) {
|
|
151
|
+
if (rel && rel.entityId) {
|
|
152
|
+
await repo.updateRelationship(companionId, {
|
|
153
|
+
companionId,
|
|
154
|
+
entityId: rel.entityId,
|
|
155
|
+
entityType: 'human',
|
|
156
|
+
role: rel.role || 'user',
|
|
157
|
+
stance: rel.stance || 'neutral',
|
|
158
|
+
interactionConventions: rel.conventions || [],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (Array.isArray(manifest.dialogueExamples) && repo.setExemplars) {
|
|
165
|
+
await repo.setExemplars(companionId, manifest.dialogueExamples);
|
|
166
|
+
}
|
|
167
|
+
|
|
271
168
|
if (directivesToCommit.length > 0) {
|
|
272
169
|
await repo.commitDirectives(companionId, directivesToCommit);
|
|
273
170
|
}
|
|
@@ -285,13 +182,20 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
285
182
|
app.post('/chat', attachIdentity, async (req, res) => {
|
|
286
183
|
const { id, message, history } = req.body;
|
|
287
184
|
const identity = (req as any).identity as Identity;
|
|
185
|
+
const local = isLocalRequest(req);
|
|
186
|
+
const hasConfiguredToken = Boolean(process.env.AUTH_TOKEN || process.env.OPERATOR_TOKEN || process.env.OWNER_TOKEN);
|
|
187
|
+
const isLocalDirectOwner = local && !hasConfiguredToken;
|
|
188
|
+
|
|
189
|
+
const authenticated = identity?.authenticated || isLocalDirectOwner;
|
|
190
|
+
const serverRole = identity?.authenticated ? identity.role : (isLocalDirectOwner ? 'OWNER' : 'VIEWER');
|
|
288
191
|
|
|
289
|
-
// Single-owner companion model: map request context directly
|
|
192
|
+
// Single-owner companion model: map request context directly with server identity enforcement
|
|
290
193
|
const mappingResult = mapRequestContext({
|
|
291
194
|
...req.body,
|
|
292
195
|
id: id || req.body.companionId,
|
|
293
|
-
authenticated
|
|
294
|
-
|
|
196
|
+
authenticated,
|
|
197
|
+
serverRole,
|
|
198
|
+
source: identity?.source ?? (local ? 'local' : 'external'),
|
|
295
199
|
generateCorrelationId: true,
|
|
296
200
|
});
|
|
297
201
|
|
|
@@ -325,12 +229,19 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
325
229
|
app.post('/chat/stream', attachIdentity, async (req, res) => {
|
|
326
230
|
const { id, message, history } = req.body;
|
|
327
231
|
const identity = (req as any).identity as Identity;
|
|
232
|
+
const local = isLocalRequest(req);
|
|
233
|
+
const hasConfiguredToken = Boolean(process.env.AUTH_TOKEN || process.env.OPERATOR_TOKEN || process.env.OWNER_TOKEN);
|
|
234
|
+
const isLocalDirectOwner = local && !hasConfiguredToken;
|
|
235
|
+
|
|
236
|
+
const authenticated = identity?.authenticated || isLocalDirectOwner;
|
|
237
|
+
const serverRole = identity?.authenticated ? identity.role : (isLocalDirectOwner ? 'OWNER' : 'VIEWER');
|
|
328
238
|
|
|
329
239
|
const mappingResult = mapRequestContext({
|
|
330
240
|
...req.body,
|
|
331
241
|
id: id || req.body.companionId,
|
|
332
|
-
authenticated
|
|
333
|
-
|
|
242
|
+
authenticated,
|
|
243
|
+
serverRole,
|
|
244
|
+
source: identity?.source ?? (local ? 'local' : 'external'),
|
|
334
245
|
generateCorrelationId: true,
|
|
335
246
|
});
|
|
336
247
|
|
|
@@ -353,8 +264,8 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
353
264
|
const abortController = new AbortController();
|
|
354
265
|
const onClose = () => {
|
|
355
266
|
abortController.abort('client_disconnect');
|
|
356
|
-
if (typeof runtime.
|
|
357
|
-
runtime.
|
|
267
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
268
|
+
runtime.mouth.interrupt('client_disconnect');
|
|
358
269
|
}
|
|
359
270
|
};
|
|
360
271
|
req.on('close', onClose);
|
|
@@ -370,7 +281,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
370
281
|
signal: abortController.signal,
|
|
371
282
|
});
|
|
372
283
|
|
|
373
|
-
res.write(`event: staged\ndata: ${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status })}\n\n`);
|
|
284
|
+
res.write(`event: staged\ndata: ${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status, mode: response.metadata?.mode })}\n\n`);
|
|
374
285
|
|
|
375
286
|
const avatarEvent = response.metadata?.events?.find(
|
|
376
287
|
(e: any) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval === 'APPROVED' || !e.approval)
|
|
@@ -427,12 +338,16 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
427
338
|
const reason = req.body?.reason || 'user_barge_in';
|
|
428
339
|
|
|
429
340
|
if (runtime) {
|
|
430
|
-
runtime.
|
|
341
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
342
|
+
runtime.mouth.interrupt(reason);
|
|
343
|
+
}
|
|
431
344
|
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
432
345
|
}
|
|
433
346
|
|
|
434
347
|
for (const r of runtimes.values()) {
|
|
435
|
-
r.
|
|
348
|
+
if (r.mouth && typeof r.mouth.interrupt === 'function') {
|
|
349
|
+
r.mouth.interrupt(reason);
|
|
350
|
+
}
|
|
436
351
|
}
|
|
437
352
|
return res.json({ success: true, interrupted: true, reason });
|
|
438
353
|
});
|
|
@@ -443,12 +358,16 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
443
358
|
const reason = req.body?.reason || 'user_barge_in';
|
|
444
359
|
|
|
445
360
|
if (runtime) {
|
|
446
|
-
runtime.
|
|
361
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
362
|
+
runtime.mouth.interrupt(reason);
|
|
363
|
+
}
|
|
447
364
|
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
448
365
|
}
|
|
449
366
|
|
|
450
367
|
for (const r of runtimes.values()) {
|
|
451
|
-
r.
|
|
368
|
+
if (r.mouth && typeof r.mouth.interrupt === 'function') {
|
|
369
|
+
r.mouth.interrupt(reason);
|
|
370
|
+
}
|
|
452
371
|
}
|
|
453
372
|
return res.json({ success: true, interrupted: true, reason });
|
|
454
373
|
});
|
|
@@ -460,7 +379,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
460
379
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
461
380
|
if (!runtime.memory) return res.json({ proposals: [] });
|
|
462
381
|
try {
|
|
463
|
-
const proposals = await runtime.getPendingClaims();
|
|
382
|
+
const proposals = await runtime.memory.getPendingClaims();
|
|
464
383
|
res.json({ proposals });
|
|
465
384
|
} catch (e: any) {
|
|
466
385
|
res.status(500).json({ error: e.message });
|
|
@@ -473,7 +392,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
473
392
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
474
393
|
if (!runtime.memory) return res.json({ items: [] });
|
|
475
394
|
try {
|
|
476
|
-
const items = await runtime.getClaims();
|
|
395
|
+
const items = await runtime.memory.getClaims();
|
|
477
396
|
res.json({ items });
|
|
478
397
|
} catch (e: any) {
|
|
479
398
|
res.status(500).json({ error: e.message });
|
|
@@ -486,7 +405,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
486
405
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
487
406
|
if (!runtime.memory) return res.json({ claims: [] });
|
|
488
407
|
try {
|
|
489
|
-
const claims = await runtime.getClaims();
|
|
408
|
+
const claims = await runtime.memory.getClaims();
|
|
490
409
|
res.json({ claims });
|
|
491
410
|
} catch (e: any) {
|
|
492
411
|
res.status(500).json({ error: e.message });
|
|
@@ -499,13 +418,96 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
499
418
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
500
419
|
if (!runtime.memory) return res.json({ directives: [] });
|
|
501
420
|
try {
|
|
502
|
-
const directives = await runtime.getDirectives();
|
|
421
|
+
const directives = await runtime.memory.getDirectives();
|
|
503
422
|
res.json({ directives });
|
|
504
423
|
} catch (e: any) {
|
|
505
424
|
res.status(500).json({ error: e.message });
|
|
506
425
|
}
|
|
507
426
|
});
|
|
508
427
|
|
|
428
|
+
// KNOWLEDGE / LIFE DB GETTERS
|
|
429
|
+
app.get('/knowledge/life', requireAuth, async (req, res) => {
|
|
430
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
431
|
+
const runtime = runtimes.get(id);
|
|
432
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
433
|
+
const query = (req.query.q as string) || '';
|
|
434
|
+
if (!runtime.knowledge || typeof (runtime.knowledge as any).queryLifeContext !== 'function') {
|
|
435
|
+
return res.json({ matchedInventory: [], recentFinances: [], upcomingSchedule: [], preferences: [], formattedContext: '' });
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const result = await (runtime.knowledge as any).queryLifeContext(id, query);
|
|
439
|
+
res.json(result);
|
|
440
|
+
} catch (e: any) {
|
|
441
|
+
res.status(500).json({ error: e.message });
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
app.get('/knowledge/inventory', requireAuth, async (req, res) => {
|
|
446
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
447
|
+
const runtime = runtimes.get(id);
|
|
448
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
449
|
+
const domain = req.query.domain as string | undefined;
|
|
450
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).inventory) {
|
|
451
|
+
return res.json({ items: [] });
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const items = await (runtime.knowledge as any).inventory.getItems(id, domain);
|
|
455
|
+
res.json({ items });
|
|
456
|
+
} catch (e: any) {
|
|
457
|
+
res.status(500).json({ error: e.message });
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
app.get('/knowledge/finance', requireAuth, async (req, res) => {
|
|
462
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
463
|
+
const runtime = runtimes.get(id);
|
|
464
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
465
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).finance) {
|
|
466
|
+
return res.json({ summary: null, entries: [] });
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
const limit = Number(req.query.limit || 20);
|
|
470
|
+
const [summary, entries] = await Promise.all([
|
|
471
|
+
(runtime.knowledge as any).finance.getSummary(id),
|
|
472
|
+
(runtime.knowledge as any).finance.getEntries(id, limit),
|
|
473
|
+
]);
|
|
474
|
+
res.json({ summary, entries });
|
|
475
|
+
} catch (e: any) {
|
|
476
|
+
res.status(500).json({ error: e.message });
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
app.get('/knowledge/schedule', requireAuth, async (req, res) => {
|
|
481
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
482
|
+
const runtime = runtimes.get(id);
|
|
483
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
484
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).schedule) {
|
|
485
|
+
return res.json({ items: [] });
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
const items = await (runtime.knowledge as any).schedule.getUpcoming(id);
|
|
489
|
+
res.json({ items });
|
|
490
|
+
} catch (e: any) {
|
|
491
|
+
res.status(500).json({ error: e.message });
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
app.get('/knowledge/preferences', requireAuth, async (req, res) => {
|
|
496
|
+
const id = req.query.id as string || Array.from(runtimes.keys())[0];
|
|
497
|
+
const runtime = runtimes.get(id);
|
|
498
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
499
|
+
const category = req.query.category as string | undefined;
|
|
500
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).preferences) {
|
|
501
|
+
return res.json({ preferences: [] });
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
const preferences = await (runtime.knowledge as any).preferences.getPreferences(id, category);
|
|
505
|
+
res.json({ preferences });
|
|
506
|
+
} catch (e: any) {
|
|
507
|
+
res.status(500).json({ error: e.message });
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
509
511
|
// MEMORY MUTATIONS - PROPOSALS
|
|
510
512
|
app.post('/memory/proposals/update', requireAuth, async (req, res) => {
|
|
511
513
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
@@ -519,7 +521,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
519
521
|
if (!claimId) {
|
|
520
522
|
return res.status(400).json({ error: "Missing required claim id" });
|
|
521
523
|
}
|
|
522
|
-
const updated = await runtime.updateClaim(claimId, req.body.updates || req.body);
|
|
524
|
+
const updated = await runtime.memory.updateClaim(claimId, req.body.updates || req.body);
|
|
523
525
|
res.json({ success: true, claim: updated });
|
|
524
526
|
} catch (e: any) {
|
|
525
527
|
res.status(500).json({ error: e.message });
|
|
@@ -532,7 +534,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
532
534
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
533
535
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
534
536
|
try {
|
|
535
|
-
await runtime.approveClaim(req.body.id);
|
|
537
|
+
await runtime.memory.approveClaim(req.body.id);
|
|
536
538
|
res.json({ approved: true });
|
|
537
539
|
} catch (e: any) {
|
|
538
540
|
res.status(500).json({ error: e.message });
|
|
@@ -545,7 +547,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
545
547
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
546
548
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
547
549
|
try {
|
|
548
|
-
await runtime.rejectClaim(req.body.id);
|
|
550
|
+
await runtime.memory.rejectClaim(req.body.id);
|
|
549
551
|
res.json({ rejected: true });
|
|
550
552
|
} catch (e: any) {
|
|
551
553
|
res.status(500).json({ error: e.message });
|
|
@@ -559,7 +561,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
559
561
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
560
562
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
561
563
|
try {
|
|
562
|
-
await runtime.approveDirective(req.body.id);
|
|
564
|
+
await runtime.memory.approveDirective(req.body.id);
|
|
563
565
|
res.json({ approved: true });
|
|
564
566
|
} catch (e: any) {
|
|
565
567
|
res.status(500).json({ error: e.message });
|
|
@@ -572,7 +574,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
572
574
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
573
575
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
574
576
|
try {
|
|
575
|
-
await runtime.rejectDirective(req.body.id);
|
|
577
|
+
await runtime.memory.rejectDirective(req.body.id);
|
|
576
578
|
res.json({ rejected: true });
|
|
577
579
|
} catch (e: any) {
|
|
578
580
|
res.status(500).json({ error: e.message });
|
|
@@ -585,7 +587,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
585
587
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
586
588
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
587
589
|
try {
|
|
588
|
-
await runtime.revokeDirective(req.body.id);
|
|
590
|
+
await runtime.memory.revokeDirective(req.body.id);
|
|
589
591
|
res.json({ revoked: true });
|
|
590
592
|
} catch (e: any) {
|
|
591
593
|
res.status(500).json({ error: e.message });
|
|
@@ -598,7 +600,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
598
600
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
599
601
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
600
602
|
try {
|
|
601
|
-
await runtime.disableDirective(req.body.id);
|
|
603
|
+
await runtime.memory.disableDirective(req.body.id);
|
|
602
604
|
res.json({ disabled: true });
|
|
603
605
|
} catch (e: any) {
|
|
604
606
|
res.status(500).json({ error: e.message });
|
|
@@ -615,7 +617,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
615
617
|
return res.status(400).json({ error: "Memory organ does not support reset" });
|
|
616
618
|
}
|
|
617
619
|
try {
|
|
618
|
-
await runtime.resetMemory();
|
|
620
|
+
await runtime.memory.resetMemory();
|
|
619
621
|
res.json({ reset: true });
|
|
620
622
|
} catch (e: any) {
|
|
621
623
|
res.status(500).json({ error: e.message });
|
|
@@ -626,7 +628,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
626
628
|
const companionId = req.body?.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
627
629
|
const runtime = runtimes.get(companionId);
|
|
628
630
|
if (!runtime) return res.status(404).json({ accepted: false, error: 'Companion not found' });
|
|
629
|
-
const staged = runtime.stageResponse({
|
|
631
|
+
const staged = runtime.gating.stageResponse({
|
|
630
632
|
requestContext: {
|
|
631
633
|
companionId,
|
|
632
634
|
actor: {
|
|
@@ -662,12 +664,12 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
662
664
|
let correlationId = req.body?.correlation_id;
|
|
663
665
|
|
|
664
666
|
if (!responseId && correlationId) {
|
|
665
|
-
const found = runtime.findStagedPlanByCorrelation(companionId, correlationId);
|
|
667
|
+
const found = runtime.gating.findStagedPlanByCorrelation(companionId, correlationId);
|
|
666
668
|
if (found) {
|
|
667
669
|
responseId = found.responseId;
|
|
668
670
|
}
|
|
669
671
|
} else if (responseId && !correlationId) {
|
|
670
|
-
const found = runtime.getStagedPlan(responseId);
|
|
672
|
+
const found = runtime.gating.getStagedPlan(responseId);
|
|
671
673
|
if (found) {
|
|
672
674
|
correlationId = found.correlationId;
|
|
673
675
|
}
|
|
@@ -677,7 +679,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
677
679
|
return res.status(400).json({ approved: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
678
680
|
}
|
|
679
681
|
|
|
680
|
-
const result = runtime.approveResponse({
|
|
682
|
+
const result = runtime.gating.approveResponse({
|
|
681
683
|
responseId,
|
|
682
684
|
companionId,
|
|
683
685
|
correlationId: correlationId || '',
|
|
@@ -688,7 +690,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
688
690
|
}
|
|
689
691
|
|
|
690
692
|
// Now evaluated as approved
|
|
691
|
-
const evaluation = runtime.evaluateGate(result.plan!);
|
|
693
|
+
const evaluation = runtime.gating.evaluateGate(result.plan!);
|
|
692
694
|
res.json({
|
|
693
695
|
approved: true,
|
|
694
696
|
status: evaluation.disposition,
|
|
@@ -707,12 +709,12 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
707
709
|
let correlationId = req.body?.correlation_id;
|
|
708
710
|
|
|
709
711
|
if (!responseId && correlationId) {
|
|
710
|
-
const found = runtime.findStagedPlanByCorrelation(companionId, correlationId);
|
|
712
|
+
const found = runtime.gating.findStagedPlanByCorrelation(companionId, correlationId);
|
|
711
713
|
if (found) {
|
|
712
714
|
responseId = found.responseId;
|
|
713
715
|
}
|
|
714
716
|
} else if (responseId && !correlationId) {
|
|
715
|
-
const found = runtime.getStagedPlan(responseId);
|
|
717
|
+
const found = runtime.gating.getStagedPlan(responseId);
|
|
716
718
|
if (found) {
|
|
717
719
|
correlationId = found.correlationId;
|
|
718
720
|
}
|
|
@@ -722,7 +724,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
722
724
|
return res.status(400).json({ rejected: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
723
725
|
}
|
|
724
726
|
|
|
725
|
-
const result = runtime.rejectResponse({
|
|
727
|
+
const result = runtime.gating.rejectResponse({
|
|
726
728
|
responseId,
|
|
727
729
|
companionId,
|
|
728
730
|
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
|
+
});
|