@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/dist/boot.js ADDED
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isDisabled = isDisabled;
4
+ exports.createBrain = createBrain;
5
+ exports.createVoice = createVoice;
6
+ exports.createKnowledge = createKnowledge;
7
+ exports.createVision = createVision;
8
+ exports.createBehavior = createBehavior;
9
+ exports.createBody = createBody;
10
+ exports.createHands = createHands;
11
+ exports.createEar = createEar;
12
+ exports.createMouth = createMouth;
13
+ exports.createMemory = createMemory;
14
+ exports.createSelf = createSelf;
15
+ exports.createObservation = createObservation;
16
+ exports.bootCompanion = bootCompanion;
17
+ const brain_1 = require("@siduri-x/brain");
18
+ const memory_1 = require("@siduri-x/memory");
19
+ const voice_1 = require("@siduri-x/voice");
20
+ const knowledge_1 = require("@siduri-x/knowledge");
21
+ const vision_1 = require("@siduri-x/vision");
22
+ const self_1 = require("@siduri-x/self");
23
+ const body_1 = require("@siduri-x/body");
24
+ const observation_1 = require("@siduri-x/observation");
25
+ const hands_1 = require("@siduri-x/hands");
26
+ const ear_1 = require("@siduri-x/ear");
27
+ const mouth_1 = require("@siduri-x/mouth");
28
+ const runtime_1 = require("./runtime");
29
+ function isDisabled(config) {
30
+ return !config || config.provider === 'none';
31
+ }
32
+ function createBrain(config) {
33
+ const provider = config?.provider || 'openrouter';
34
+ const defaultKeyEnv = provider === 'openai-compatible' ? 'OPENAI_COMPATIBLE_API_KEY' : 'OPENROUTER_API_KEY';
35
+ const apiKey = config?.apiKey || process.env[config?.apiKeyEnv || defaultKeyEnv] || '';
36
+ if (provider === 'openai-compatible') {
37
+ return new brain_1.OpenAICompatibleBrain({
38
+ apiKey,
39
+ model: config?.model || 'local-model',
40
+ baseUrl: config?.baseUrl || 'http://127.0.0.1:1234/v1',
41
+ });
42
+ }
43
+ return new brain_1.OpenRouterBrain({ apiKey, model: config?.model || 'gpt-4o-mini' });
44
+ }
45
+ function createVoice(config) {
46
+ return isDisabled(config)
47
+ ? undefined
48
+ : new voice_1.VoiceAdapter({
49
+ provider: config?.provider || 'voicevox',
50
+ baseUrl: config?.baseUrl || process.env.VOICEVOX_URL || 'http://localhost:50021',
51
+ speakerId: config?.speakerId || 1,
52
+ ...config,
53
+ });
54
+ }
55
+ function createKnowledge(config) {
56
+ if (isDisabled(config))
57
+ return undefined;
58
+ const dbPath = config?.dbPath || process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite';
59
+ return new knowledge_1.UnifiedKnowledgeOrgan({
60
+ ...config,
61
+ dbPath,
62
+ lifeDatabase: config?.lifeDatabase ?? true,
63
+ });
64
+ }
65
+ function createVision(config) {
66
+ return isDisabled(config)
67
+ ? undefined
68
+ : new vision_1.OpenRouterVisionAdapter({
69
+ apiKey: config?.apiKey || process.env.OPENROUTER_API_KEY || '',
70
+ model: config?.model || 'gpt-4-vision',
71
+ ...config,
72
+ });
73
+ }
74
+ function createBehavior(config) {
75
+ return isDisabled(config) ? undefined : new self_1.ActiveSelfCompiler();
76
+ }
77
+ function createBody(config) {
78
+ return isDisabled(config)
79
+ ? undefined
80
+ : new body_1.Live2DAdapter(config);
81
+ }
82
+ function createHands(config) {
83
+ return isDisabled(config)
84
+ ? new hands_1.DefaultHandsOrgan()
85
+ : new hands_1.DefaultHandsOrgan(config);
86
+ }
87
+ function createEar(config) {
88
+ return isDisabled(config)
89
+ ? new ear_1.DefaultEarOrgan()
90
+ : new ear_1.DefaultEarOrgan(config);
91
+ }
92
+ function createMouth(config, voice) {
93
+ return isDisabled(config)
94
+ ? new mouth_1.DefaultMouthOrgan({ voice })
95
+ : new mouth_1.DefaultMouthOrgan({ ...config, voice });
96
+ }
97
+ function createMemory(config) {
98
+ if (isDisabled(config))
99
+ return undefined;
100
+ return new memory_1.SqliteMemoryStore({ dbPath: config?.dbPath || process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
101
+ }
102
+ function createSelf(config) {
103
+ return new self_1.SqliteSelfRepository({ dbPath: config?.dbPath || process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
104
+ }
105
+ function createObservation(vision) {
106
+ return new observation_1.FixtureObservationOrgan(vision ?? { analyze: async () => JSON.stringify({ readings: [] }) });
107
+ }
108
+ /**
109
+ * Canonical companion bootstrapper.
110
+ * Wires all 10 organs, runs memory migrations, and initializes the SiduriRuntime.
111
+ */
112
+ async function bootCompanion(id, config, options) {
113
+ const organs = config?.organs || {};
114
+ const brain = createBrain(organs.brain || config?.brain);
115
+ const memory = createMemory(organs.memory || config?.memory);
116
+ const selfRepo = createSelf(organs.self || config?.self);
117
+ const voice = createVoice(organs.voice || config?.voice);
118
+ const knowledge = createKnowledge(organs.knowledge || config?.knowledge);
119
+ const vision = createVision(organs.vision || config?.vision);
120
+ const behavior = createBehavior(organs.behavior || config?.behavior);
121
+ const body = createBody(organs.body || config?.body);
122
+ const hands = createHands(organs.hands || config?.hands);
123
+ const ear = createEar(organs.ear || config?.ear);
124
+ const mouth = createMouth(organs.mouth || config?.mouth, voice);
125
+ const observation = options?.observationOrgan;
126
+ if (memory && typeof memory.runMigrations === 'function') {
127
+ await memory.runMigrations().catch((e) => console.warn("Migrations warning:", e.message));
128
+ }
129
+ const runtime = new runtime_1.SiduriRuntime(id, config, {
130
+ brain,
131
+ memory,
132
+ voice,
133
+ knowledge,
134
+ vision,
135
+ behavior,
136
+ body,
137
+ hands,
138
+ ear,
139
+ mouth,
140
+ observation,
141
+ self: selfRepo,
142
+ externalKnowledge: knowledge?.eAdapter ?? knowledge,
143
+ });
144
+ await runtime.initialize();
145
+ return runtime;
146
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const boot_1 = require("./boot");
4
+ const observation_1 = require("@siduri-x/observation");
5
+ describe('Canonical bootCompanion & Organ Factory Suite', () => {
6
+ const originalEnv = process.env;
7
+ beforeEach(() => {
8
+ process.env = { ...originalEnv };
9
+ });
10
+ afterAll(() => {
11
+ process.env = originalEnv;
12
+ });
13
+ test('isDisabled correctly identifies undefined or none provider', () => {
14
+ expect((0, boot_1.isDisabled)(undefined)).toBe(true);
15
+ expect((0, boot_1.isDisabled)({ provider: 'none' })).toBe(true);
16
+ expect((0, boot_1.isDisabled)({ provider: 'openrouter' })).toBe(false);
17
+ expect((0, boot_1.isDisabled)({ provider: 'sqlite' })).toBe(false);
18
+ });
19
+ test('creates all standard organs with fallback configurations', () => {
20
+ const brain = (0, boot_1.createBrain)({ provider: 'openai-compatible', baseUrl: 'http://localhost:11434/v1', apiKey: 'test' });
21
+ expect(brain).toBeDefined();
22
+ const voice = (0, boot_1.createVoice)({ provider: 'none' });
23
+ expect(voice).toBeUndefined();
24
+ const memory = (0, boot_1.createMemory)({ provider: 'sqlite', dbPath: ':memory:' });
25
+ expect(memory).toBeDefined();
26
+ const selfRepo = (0, boot_1.createSelf)({ dbPath: ':memory:' });
27
+ expect(selfRepo).toBeDefined();
28
+ const vision = (0, boot_1.createVision)({ provider: 'none' });
29
+ expect(vision).toBeUndefined();
30
+ const behavior = (0, boot_1.createBehavior)({ provider: 'active_self' });
31
+ expect(behavior).toBeDefined();
32
+ const body = (0, boot_1.createBody)({ provider: 'none' });
33
+ expect(body).toBeUndefined();
34
+ const hands = (0, boot_1.createHands)();
35
+ expect(hands).toBeDefined();
36
+ const ear = (0, boot_1.createEar)();
37
+ expect(ear).toBeDefined();
38
+ const mouth = (0, boot_1.createMouth)(undefined, voice);
39
+ expect(mouth).toBeDefined();
40
+ const observation = (0, boot_1.createObservation)();
41
+ expect(observation).toBeInstanceOf(observation_1.FixtureObservationOrgan);
42
+ });
43
+ test('bootCompanion wires all organs and runs migrations', async () => {
44
+ let migrationsRun = false;
45
+ const testConfig = {
46
+ name: 'Test Companion',
47
+ brain: { provider: 'openrouter', apiKey: 'mock-key', model: 'mock-model' },
48
+ memory: { provider: 'none' }, // will use custom mock
49
+ voice: { provider: 'none' },
50
+ vision: { provider: 'none' },
51
+ behavior: { provider: 'none' },
52
+ body: { provider: 'none' },
53
+ hands: { provider: 'none' },
54
+ ear: { provider: 'none' },
55
+ mouth: { provider: 'none' },
56
+ knowledge: { provider: 'none' },
57
+ };
58
+ const mockObservation = new observation_1.FixtureObservationOrgan({ analyze: async () => JSON.stringify({ readings: [] }) });
59
+ const runtime = await (0, boot_1.bootCompanion)('test-comp-1', testConfig, {
60
+ observationOrgan: mockObservation,
61
+ });
62
+ expect(runtime).toBeDefined();
63
+ expect(runtime.id).toBe('test-comp-1');
64
+ expect(runtime.observation).toBe(mockObservation);
65
+ expect(runtime.hands).toBeDefined();
66
+ expect(runtime.ear).toBeDefined();
67
+ expect(runtime.mouth).toBeDefined();
68
+ });
69
+ test('bootCompanion handles nested organs configuration structure', async () => {
70
+ const nestedConfig = {
71
+ name: 'Nested Companion',
72
+ organs: {
73
+ brain: { provider: 'openrouter', apiKey: 'mock' },
74
+ memory: { provider: 'none' },
75
+ knowledge: { provider: 'none' },
76
+ voice: { provider: 'none' },
77
+ },
78
+ };
79
+ const runtime = await (0, boot_1.bootCompanion)('nested-comp', nestedConfig);
80
+ expect(runtime).toBeDefined();
81
+ expect(runtime.id).toBe('nested-comp');
82
+ expect(runtime.brain).toBeDefined();
83
+ });
84
+ });
@@ -12,7 +12,9 @@ export interface MapRequestContextResult {
12
12
  /**
13
13
  * Maps incoming HTTP requests to a canonical RequestContext.
14
14
  * In a single-owner, single-machine model:
15
- * - Security is enforced at the external boundary, not internally between roles.
15
+ * - Security is enforced at the external machine boundary, NOT internally between roles.
16
16
  * - No internal audience or viewer/operator/owner role hierarchies.
17
+ * - Authenticated callers are the single owner with full companion access.
18
+ * - Unauthenticated callers are bounded to public chat.
17
19
  */
18
20
  export declare function mapRequestContext(input: any, options?: ContextMapperOptions): MapRequestContextResult;
@@ -5,8 +5,10 @@ const core_1 = require("@siduri-x/core");
5
5
  /**
6
6
  * Maps incoming HTTP requests to a canonical RequestContext.
7
7
  * In a single-owner, single-machine model:
8
- * - Security is enforced at the external boundary, not internally between roles.
8
+ * - Security is enforced at the external machine boundary, NOT internally between roles.
9
9
  * - No internal audience or viewer/operator/owner role hierarchies.
10
+ * - Authenticated callers are the single owner with full companion access.
11
+ * - Unauthenticated callers are bounded to public chat.
10
12
  */
11
13
  function mapRequestContext(input, options = {}) {
12
14
  const diagnostics = [];
@@ -23,7 +25,9 @@ function mapRequestContext(input, options = {}) {
23
25
  if (input.context && typeof input.context === 'object') {
24
26
  const rawCtx = input.context;
25
27
  const companionId = input.companionId || rawCtx.companionId || input.id;
26
- const correlationId = rawCtx.conversation?.correlationId || input.correlationId;
28
+ const correlationId = rawCtx.conversation?.correlationId ||
29
+ input.correlationId ||
30
+ (input.generateCorrelationId ? `corr-${Date.now()}` : undefined);
27
31
  if (!companionId) {
28
32
  return {
29
33
  accepted: false,
@@ -57,16 +61,42 @@ function mapRequestContext(input, options = {}) {
57
61
  },
58
62
  };
59
63
  }
60
- // Determine server-enforced authentication status
64
+ const isViewerRequested = (typeof input.role === 'string' && input.role.toLowerCase() === 'viewer') ||
65
+ (typeof input.serverRole === 'string' && input.serverRole.toLowerCase() === 'viewer') ||
66
+ (typeof actor.authorizationRole === 'string' && actor.authorizationRole.toLowerCase() === 'viewer') ||
67
+ (typeof actor.role === 'string' && actor.role.toLowerCase() === 'viewer');
68
+ // Determine server-enforced authentication status at the machine boundary
61
69
  const isAuthenticated = input.authenticated !== undefined
62
70
  ? Boolean(input.authenticated)
63
71
  : (actor.authenticated !== undefined ? Boolean(actor.authenticated) : true);
64
- const safeCapabilities = isAuthenticated
65
- ? (Array.isArray(actor.capabilities) ? actor.capabilities : ['chat'])
66
- : ['chat'];
67
- const safeRole = isAuthenticated
68
- ? actor.authorizationRole
69
- : 'viewer';
72
+ const isViewer = !isAuthenticated || isViewerRequested;
73
+ const CANONICAL_CAPABILITIES = new Set(['chat', 'memory:approve', 'action:execute', 'system']);
74
+ const DEFAULT_OWNER_CAPABILITIES = ['chat', 'memory:approve', 'action:execute', 'system'];
75
+ let safeCapabilities;
76
+ let authorizationRole;
77
+ if (isViewer) {
78
+ safeCapabilities = ['chat'];
79
+ authorizationRole = 'viewer';
80
+ if (Array.isArray(actor.capabilities) && actor.capabilities.some((c) => c !== 'chat' && c !== 'chat:public')) {
81
+ diagnostics.push('capability_escalation_attempt_suppressed');
82
+ }
83
+ if (actor.authorizationRole && actor.authorizationRole.toLowerCase() !== 'viewer') {
84
+ diagnostics.push('role_escalation_attempt_suppressed');
85
+ }
86
+ }
87
+ else {
88
+ authorizationRole = 'administrator';
89
+ if (Array.isArray(actor.capabilities) && actor.capabilities.length > 0) {
90
+ const filtered = actor.capabilities.filter((c) => CANONICAL_CAPABILITIES.has(c));
91
+ if (filtered.length < actor.capabilities.length) {
92
+ diagnostics.push('capability_escalation_attempt_suppressed');
93
+ }
94
+ safeCapabilities = filtered.length > 0 ? filtered : [...DEFAULT_OWNER_CAPABILITIES];
95
+ }
96
+ else {
97
+ safeCapabilities = [...DEFAULT_OWNER_CAPABILITIES];
98
+ }
99
+ }
70
100
  const constructed = {
71
101
  companionId,
72
102
  actor: {
@@ -75,7 +105,7 @@ function mapRequestContext(input, options = {}) {
75
105
  sessionId: actor.sessionId,
76
106
  capabilities: safeCapabilities,
77
107
  authenticated: isAuthenticated,
78
- authorizationRole: safeRole,
108
+ authorizationRole,
79
109
  },
80
110
  conversation: {
81
111
  correlationId,
@@ -132,19 +162,47 @@ function mapRequestContext(input, options = {}) {
132
162
  },
133
163
  };
134
164
  }
135
- const actorId = input.actorId || input.actor?.actorId || 'local-user';
165
+ const isViewerRequested = (typeof input.role === 'string' && input.role.toLowerCase() === 'viewer') ||
166
+ (typeof input.serverRole === 'string' && input.serverRole.toLowerCase() === 'viewer');
167
+ const isAuthenticated = input.authenticated !== undefined ? Boolean(input.authenticated) : true;
168
+ const isViewer = !isAuthenticated || isViewerRequested;
169
+ const actorId = input.actorId || input.actor?.actorId || (isAuthenticated && !isViewer ? 'local-user' : 'anonymous-session');
136
170
  const sessionId = input.sessionId || input.actor?.sessionId || `session-${Date.now()}`;
137
171
  if (!input.actorId && !input.actor?.actorId) {
138
172
  diagnostics.push('anonymous_session_generated');
139
173
  }
140
- const isAuthenticated = input.authenticated !== undefined ? Boolean(input.authenticated) : true;
141
- const capabilities = isAuthenticated
142
- ? (Array.isArray(input.capabilities)
143
- ? input.capabilities
144
- : Array.isArray(input.actor?.capabilities)
145
- ? input.actor.capabilities
146
- : ['chat', 'system'])
147
- : ['chat'];
174
+ const CANONICAL_CAPABILITIES = new Set(['chat', 'memory:approve', 'action:execute', 'system']);
175
+ const DEFAULT_OWNER_CAPABILITIES = ['chat', 'memory:approve', 'action:execute', 'system'];
176
+ const rawCaps = Array.isArray(input.capabilities)
177
+ ? input.capabilities
178
+ : Array.isArray(input.actor?.capabilities)
179
+ ? input.actor.capabilities
180
+ : undefined;
181
+ let capabilities;
182
+ let authorizationRole;
183
+ if (isViewer) {
184
+ capabilities = ['chat'];
185
+ authorizationRole = 'viewer';
186
+ if (rawCaps && rawCaps.some((c) => c !== 'chat' && c !== 'chat:public')) {
187
+ diagnostics.push('capability_escalation_attempt_suppressed');
188
+ }
189
+ if (input.role && input.role.toLowerCase() !== 'viewer') {
190
+ diagnostics.push('role_escalation_attempt_suppressed');
191
+ }
192
+ }
193
+ else {
194
+ authorizationRole = 'administrator';
195
+ if (rawCaps && rawCaps.length > 0) {
196
+ const filtered = rawCaps.filter((c) => CANONICAL_CAPABILITIES.has(c));
197
+ if (filtered.length < rawCaps.length) {
198
+ diagnostics.push('capability_escalation_attempt_suppressed');
199
+ }
200
+ capabilities = filtered.length > 0 ? filtered : [...DEFAULT_OWNER_CAPABILITIES];
201
+ }
202
+ else {
203
+ capabilities = [...DEFAULT_OWNER_CAPABILITIES];
204
+ }
205
+ }
148
206
  const mappedContext = {
149
207
  companionId,
150
208
  actor: {
@@ -152,7 +210,7 @@ function mapRequestContext(input, options = {}) {
152
210
  sessionId,
153
211
  capabilities,
154
212
  authenticated: isAuthenticated,
155
- authorizationRole: input.role ? (input.role.toLowerCase() === 'viewer' ? 'viewer' : 'administrator') : undefined,
213
+ authorizationRole,
156
214
  },
157
215
  conversation: {
158
216
  channel: input.channel || input.conversation?.channel || 'direct',
@@ -157,5 +157,96 @@ describe('API Request Context Mapper (Single-Owner, Single-Machine)', () => {
157
157
  expect(result.context?.actor.capabilities).toEqual(['chat']);
158
158
  expect(result.context?.actor.authorizationRole).toBe('viewer');
159
159
  expect(result.context?.source).toBe('external');
160
+ expect(result.diagnostics).toContain('role_escalation_attempt_suppressed');
161
+ expect(result.diagnostics).toContain('capability_escalation_attempt_suppressed');
162
+ });
163
+ test('Prevents attenuated caller from escalating to administrator role or forging elevated capabilities', () => {
164
+ const attenuatedPayload = {
165
+ companionId: 'companion-a',
166
+ authenticated: true,
167
+ role: 'VIEWER',
168
+ source: 'external',
169
+ context: {
170
+ actor: {
171
+ actorId: 'visitor-bob',
172
+ sessionId: 'session-vis-1',
173
+ authorizationRole: 'administrator', // Forged owner/admin role
174
+ capabilities: ['chat', 'system', 'root:manage', 'action:execute'], // Forged system/root capabilities
175
+ },
176
+ conversation: {
177
+ correlationId: 'corr-att-escalate',
178
+ },
179
+ },
180
+ };
181
+ const result = (0, context_mapper_1.mapRequestContext)(attenuatedPayload);
182
+ expect(result.accepted).toBe(true);
183
+ expect(result.context?.actor.authenticated).toBe(true);
184
+ expect(result.context?.actor.authorizationRole).toBe('viewer');
185
+ expect(result.context?.actor.capabilities).toEqual(['chat']);
186
+ expect(result.diagnostics).toContain('role_escalation_attempt_suppressed');
187
+ expect(result.diagnostics).toContain('capability_escalation_attempt_suppressed');
188
+ });
189
+ test('Strips unknown capabilities for authenticated owner while preserving canonical companion capabilities', () => {
190
+ const ownerPayload = {
191
+ companionId: 'companion-a',
192
+ authenticated: true,
193
+ source: 'local',
194
+ context: {
195
+ actor: {
196
+ actorId: 'owner-user',
197
+ sessionId: 'session-owner-1',
198
+ capabilities: ['chat', 'system', 'root:unauthorized', 'arbitrary:hack'],
199
+ },
200
+ conversation: {
201
+ correlationId: 'corr-owner-caps',
202
+ },
203
+ },
204
+ };
205
+ const result = (0, context_mapper_1.mapRequestContext)(ownerPayload);
206
+ expect(result.accepted).toBe(true);
207
+ expect(result.context?.actor.authorizationRole).toBe('administrator');
208
+ expect(result.context?.actor.capabilities).toEqual(['chat', 'system']);
209
+ expect(result.diagnostics).toContain('capability_escalation_attempt_suppressed');
210
+ });
211
+ test('Flat envelope: suppresses role and capability escalation when serverRole is VIEWER', () => {
212
+ const viewerPayload = {
213
+ companionId: 'companion-a',
214
+ authenticated: false,
215
+ serverRole: 'VIEWER',
216
+ role: 'OWNER', // Forged in flat envelope
217
+ capabilities: ['chat', 'system', 'action:execute'],
218
+ generateCorrelationId: true,
219
+ };
220
+ const result = (0, context_mapper_1.mapRequestContext)(viewerPayload);
221
+ expect(result.accepted).toBe(true);
222
+ expect(result.context?.actor.authorizationRole).toBe('viewer');
223
+ expect(result.context?.actor.capabilities).toEqual(['chat']);
224
+ expect(result.diagnostics).toContain('role_escalation_attempt_suppressed');
225
+ });
226
+ test('Authenticated owner receives canonical administrator role and capabilities, and can safely attenuate to viewer', () => {
227
+ const ownerPayload = {
228
+ companionId: 'companion-a',
229
+ authenticated: true,
230
+ serverRole: 'OWNER',
231
+ generateCorrelationId: true,
232
+ };
233
+ const ownerResult = (0, context_mapper_1.mapRequestContext)(ownerPayload);
234
+ expect(ownerResult.accepted).toBe(true);
235
+ expect(ownerResult.context?.actor.authorizationRole).toBe('administrator');
236
+ expect(ownerResult.context?.actor.capabilities).toEqual([
237
+ 'chat',
238
+ 'memory:approve',
239
+ 'action:execute',
240
+ 'system',
241
+ ]);
242
+ // Attenuation to viewer
243
+ const attenuatedPayload = {
244
+ ...ownerPayload,
245
+ role: 'VIEWER',
246
+ };
247
+ const attenuatedResult = (0, context_mapper_1.mapRequestContext)(attenuatedPayload);
248
+ expect(attenuatedResult.accepted).toBe(true);
249
+ expect(attenuatedResult.context?.actor.authorizationRole).toBe('viewer');
250
+ expect(attenuatedResult.diagnostics).not.toContain('role_escalation_attempt_suppressed');
160
251
  });
161
252
  });
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@ import { Express } from 'express';
2
2
  import { createApp, AppInstance } from './app';
3
3
  export { createApp, AppInstance };
4
4
  export * from './context-mapper';
5
+ export * from './boot';
5
6
  export declare const app: Express;
6
7
  export default app;
package/dist/index.js CHANGED
@@ -24,76 +24,13 @@ const promises_1 = require("node:fs/promises");
24
24
  const node_path_1 = __importDefault(require("node:path"));
25
25
  const app_1 = require("./app");
26
26
  Object.defineProperty(exports, "createApp", { enumerable: true, get: function () { return app_1.createApp; } });
27
- const runtime_1 = require("./runtime");
28
- const brain_1 = require("@siduri-x/brain");
29
- const memory_1 = require("@siduri-x/memory");
30
- const voice_1 = require("@siduri-x/voice");
31
- const eknowledge_1 = require("@siduri-x/eknowledge");
32
- const vision_1 = require("@siduri-x/vision");
33
- const self_1 = require("@siduri-x/self");
34
- const body_1 = require("@siduri-x/body");
35
- const observation_1 = require("@siduri-x/observation");
27
+ const boot_1 = require("./boot");
36
28
  __exportStar(require("./context-mapper"), exports);
29
+ __exportStar(require("./boot"), exports);
37
30
  const runtimes = new Map();
38
31
  const instance = (0, app_1.createApp)(runtimes);
39
32
  exports.app = instance.app;
40
33
  exports.default = exports.app;
41
- function createBrain(config) {
42
- const provider = config?.provider || 'openrouter';
43
- const defaultKeyEnv = provider === 'openai-compatible' ? 'OPENAI_COMPATIBLE_API_KEY' : 'OPENROUTER_API_KEY';
44
- const apiKey = config?.apiKey || process.env[config?.apiKeyEnv || defaultKeyEnv] || '';
45
- if (provider === 'openai-compatible') {
46
- return new brain_1.OpenAICompatibleBrain({
47
- apiKey,
48
- model: config?.model || 'local-model',
49
- baseUrl: config?.baseUrl || 'http://127.0.0.1:1234/v1',
50
- });
51
- }
52
- return new brain_1.OpenRouterBrain({ apiKey, model: config?.model || 'gpt-4o-mini' });
53
- }
54
- function isDisabled(config) {
55
- return !config || config.provider === 'none';
56
- }
57
- function createVoice(config) {
58
- return isDisabled(config)
59
- ? undefined
60
- : new voice_1.VoiceAdapter({
61
- provider: config?.provider || 'voicevox',
62
- baseUrl: config?.baseUrl || process.env.VOICEVOX_URL || 'http://localhost:50021',
63
- speakerId: config?.speakerId || 1,
64
- ...config,
65
- });
66
- }
67
- function createKnowledge(config) {
68
- if (isDisabled(config))
69
- return undefined;
70
- if (!config?.packPath && !config?.registryUrl && !config?.baseUrl) {
71
- return undefined;
72
- }
73
- return new eknowledge_1.EKnowledgeAdapter(config || {});
74
- }
75
- function createVision(config) {
76
- return isDisabled(config)
77
- ? undefined
78
- : new vision_1.OpenRouterVisionAdapter({
79
- apiKey: config?.apiKey || process.env.OPENROUTER_API_KEY || '',
80
- model: config?.model || 'gpt-4-vision',
81
- ...config,
82
- });
83
- }
84
- function createBehavior(config) {
85
- return isDisabled(config) ? undefined : new self_1.ActiveSelfCompiler();
86
- }
87
- function createBody(config) {
88
- return isDisabled(config)
89
- ? undefined
90
- : new body_1.Live2DAdapter(config);
91
- }
92
- function createMemory(config) {
93
- if (isDisabled(config))
94
- return undefined;
95
- return new memory_1.SqliteMemoryStore({ dbPath: config?.dbPath || process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
96
- }
97
34
  const PORT = process.env.PORT || 3001;
98
35
  const defaultCompanionConfig = {
99
36
  id: 'default',
@@ -102,7 +39,9 @@ const defaultCompanionConfig = {
102
39
  voice: { provider: 'voicevox', speakerId: 1 },
103
40
  memory: { provider: 'sqlite' },
104
41
  knowledge: {
105
- provider: process.env.SIDURI_KNOWLEDGE_PROVIDER || 'e-knowledge',
42
+ provider: process.env.SIDURI_KNOWLEDGE_PROVIDER || 'unified',
43
+ lifeDatabase: true,
44
+ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite',
106
45
  packPath: process.env.SIDURI_KNOWLEDGE_PACK || '',
107
46
  registryUrl: process.env.SIDURI_KNOWLEDGE_REGISTRY_URL || '',
108
47
  packId: process.env.SIDURI_KNOWLEDGE_PACK_ID || '',
@@ -149,6 +88,8 @@ async function loadCompanionConfig() {
149
88
  config.knowledge.packId = process.env.SIDURI_KNOWLEDGE_PACK_ID;
150
89
  if (process.env.SIDURI_KNOWLEDGE_MODE)
151
90
  config.knowledge.preferredMode = process.env.SIDURI_KNOWLEDGE_MODE;
91
+ if (process.env.SIDURI_KNOWLEDGE_DB_PATH)
92
+ config.knowledge.dbPath = process.env.SIDURI_KNOWLEDGE_DB_PATH;
152
93
  return config;
153
94
  }
154
95
  async function bootDefaultCompanion() {
@@ -156,31 +97,10 @@ async function bootDefaultCompanion() {
156
97
  return;
157
98
  console.log("Booting default companion...");
158
99
  const config = await loadCompanionConfig();
159
- const brain = createBrain(config.brain);
160
- const memory = createMemory(config.memory);
161
- const voice = createVoice(config.voice);
162
- const knowledge = createKnowledge(config.knowledge);
163
- const vision = createVision(config.vision);
164
- const observation = new observation_1.FixtureObservationOrgan(vision ?? { analyze: async () => JSON.stringify({ readings: [] }) });
100
+ const vision = (0, boot_1.createVision)(config.vision);
101
+ const observation = (0, boot_1.createObservation)(vision);
165
102
  instance.setObservationOrgan(observation);
166
- const selfRepo = new self_1.SqliteSelfRepository({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
167
- const behavior = createBehavior(config.behavior);
168
- const body = createBody(config.body);
169
- if (memory && typeof memory.runMigrations === 'function') {
170
- await memory.runMigrations().catch((e) => console.warn("Migrations warning:", e.message));
171
- }
172
- const runtime = new runtime_1.SiduriRuntime('default', config, {
173
- brain,
174
- memory,
175
- voice,
176
- knowledge,
177
- vision,
178
- behavior,
179
- body,
180
- self: selfRepo,
181
- externalKnowledge: knowledge
182
- });
183
- await runtime.initialize();
103
+ const runtime = await (0, boot_1.bootCompanion)('default', config, { observationOrgan: observation });
184
104
  runtimes.set('default', runtime);
185
105
  console.log("Default companion booted successfully.");
186
106
  }
@@ -111,7 +111,7 @@ describe('API Boundary Context Validation (P2 Route Integration)', () => {
111
111
  expect(res.text).toContain('event: done');
112
112
  });
113
113
  test('handles barge-in interruption via POST /chat/interrupt', async () => {
114
- fakeRuntime.interruptMouth = jest.fn();
114
+ fakeRuntime.mouth = { interrupt: jest.fn() };
115
115
  const res = await (0, supertest_1.default)(app)
116
116
  .post('/chat/interrupt')
117
117
  .send({
@@ -120,10 +120,10 @@ describe('API Boundary Context Validation (P2 Route Integration)', () => {
120
120
  });
121
121
  expect(res.status).toBe(200);
122
122
  expect(res.body.interrupted).toBe(true);
123
- expect(fakeRuntime.interruptMouth).toHaveBeenCalledWith('user_stop');
123
+ expect(fakeRuntime.mouth.interrupt).toHaveBeenCalledWith('user_stop');
124
124
  });
125
125
  test('handles mouth interruption via POST /mouth/interrupt', async () => {
126
- fakeRuntime.interruptMouth = jest.fn();
126
+ fakeRuntime.mouth = { interrupt: jest.fn() };
127
127
  const res = await (0, supertest_1.default)(app)
128
128
  .post('/mouth/interrupt')
129
129
  .send({
@@ -132,6 +132,6 @@ describe('API Boundary Context Validation (P2 Route Integration)', () => {
132
132
  });
133
133
  expect(res.status).toBe(200);
134
134
  expect(res.body.interrupted).toBe(true);
135
- expect(fakeRuntime.interruptMouth).toHaveBeenCalledWith('user_barge_in');
135
+ expect(fakeRuntime.mouth.interrupt).toHaveBeenCalledWith('user_barge_in');
136
136
  });
137
137
  });
@@ -0,0 +1 @@
1
+ export {};