@vxnus/siduri 0.1.5 → 0.1.7
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/builtin-manifests.js +4 -4
- package/dist/clean-machine-e2e.test.js +14 -14
- package/dist/configurators/voice.js +11 -8
- package/dist/configurators.test.js +18 -1
- package/dist/generator.js +86 -72
- package/dist/generator.test.js +20 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/release-check.js +6 -6
- package/dist/runtime-parity.test.d.ts +1 -0
- package/dist/runtime-parity.test.js +140 -0
- package/dist/web-dist/404/index.html +1 -1
- package/dist/web-dist/404.html +1 -1
- package/dist/web-dist/__next.__PAGE__.txt +22 -22
- package/dist/web-dist/__next._full.txt +26 -22
- package/dist/web-dist/__next._tree.txt +2 -2
- package/dist/web-dist/_next/static/chunks/0bncolc2nuzmo.css +1 -0
- package/dist/web-dist/_next/static/chunks/{1eusi7dzngpy3.js → 0xe6aaej0xsj8.js} +1 -1
- package/dist/web-dist/_next/static/chunks/10rmct1ax-opq.js +1 -0
- package/dist/web-dist/_not-found/__next._full.txt +2 -2
- package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/dist/web-dist/_not-found/__next._tree.txt +2 -2
- package/dist/web-dist/_not-found/index.html +1 -1
- package/dist/web-dist/_not-found/index.txt +2 -2
- package/dist/web-dist/chat/__next._full.txt +3 -3
- package/dist/web-dist/chat/__next._tree.txt +2 -2
- package/dist/web-dist/chat/__next.chat.__PAGE__.txt +3 -3
- package/dist/web-dist/chat/index.html +1 -1
- package/dist/web-dist/chat/index.txt +3 -3
- package/dist/web-dist/index.html +1 -1
- package/dist/web-dist/index.txt +26 -22
- package/dist/web-dist/operator/__next._full.txt +3 -3
- package/dist/web-dist/operator/__next._tree.txt +2 -2
- package/dist/web-dist/operator/__next.operator.__PAGE__.txt +3 -3
- package/dist/web-dist/operator/index.html +1 -1
- package/dist/web-dist/operator/index.txt +3 -3
- package/dist/web-template.js +15 -6
- package/package.json +1 -1
- package/dist/web-dist/_next/static/chunks/1dc9n0p39yo65.css +0 -1
- package/dist/web-dist/_next/static/chunks/1hz3jco387c-k.js +0 -1
- /package/dist/web-dist/_next/static/{fySnDRfSgiBzStXXyXjTp → uvsK8ULhL7fHQMR8sr_JL}/_buildManifest.js +0 -0
- /package/dist/web-dist/_next/static/{fySnDRfSgiBzStXXyXjTp → uvsK8ULhL7fHQMR8sr_JL}/_clientMiddlewareManifest.js +0 -0
- /package/dist/web-dist/_next/static/{fySnDRfSgiBzStXXyXjTp → uvsK8ULhL7fHQMR8sr_JL}/_ssgManifest.js +0 -0
|
@@ -5,7 +5,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
|
|
|
5
5
|
{
|
|
6
6
|
name: '@siduri-x/behavior',
|
|
7
7
|
organType: 'behavior',
|
|
8
|
-
version: '1.0.
|
|
8
|
+
version: '1.0.5',
|
|
9
9
|
displayName: 'Behavior (Active Self Directives)',
|
|
10
10
|
description: 'Atomic directive state machine and personality projection compiler',
|
|
11
11
|
entrypoint: './dist/index.js',
|
|
@@ -66,7 +66,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
|
|
|
66
66
|
{
|
|
67
67
|
name: '@siduri-x/brain',
|
|
68
68
|
organType: 'brain',
|
|
69
|
-
version: '1.0.
|
|
69
|
+
version: '1.0.3',
|
|
70
70
|
displayName: 'Brain (Cognition & Planning)',
|
|
71
71
|
description: 'Provider-neutral LLM reasoning, response planning, and proposal generation',
|
|
72
72
|
entrypoint: './dist/index.js',
|
|
@@ -272,7 +272,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
|
|
|
272
272
|
{
|
|
273
273
|
name: '@siduri-x/memory',
|
|
274
274
|
organType: 'memory',
|
|
275
|
-
version: '1.0.
|
|
275
|
+
version: '1.0.3',
|
|
276
276
|
displayName: 'Memory (PostgreSQL FTS Claims)',
|
|
277
277
|
description: 'Relational semantic claims with Full-Text Search and companion isolation',
|
|
278
278
|
entrypoint: './dist/index.js',
|
|
@@ -389,7 +389,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
|
|
|
389
389
|
{
|
|
390
390
|
name: '@siduri-x/voice',
|
|
391
391
|
organType: 'voice',
|
|
392
|
-
version: '1.0.
|
|
392
|
+
version: '1.0.5',
|
|
393
393
|
displayName: 'Voice (VOICEVOX Speech Synthesis)',
|
|
394
394
|
description: 'Queued speech synthesis and audio rendering lifecycle adapter',
|
|
395
395
|
entrypoint: './dist/index.js',
|
|
@@ -13,18 +13,18 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
|
|
|
13
13
|
const tempPackDir = node_path_1.default.resolve(__dirname, '../temp-packs-e2e');
|
|
14
14
|
const cleanMachineRoot = node_path_1.default.resolve(__dirname, '../temp-clean-machine-e2e');
|
|
15
15
|
const ALL_CANONICAL_PACKAGES = [
|
|
16
|
-
{ filter: '@siduri-x/core', tarName: 'siduri-x-core-1.0.
|
|
17
|
-
{ filter: '@siduri-x/brain', tarName: 'siduri-x-brain-1.0.
|
|
18
|
-
{ filter: '@siduri-x/memory', tarName: 'siduri-x-memory-1.0.
|
|
16
|
+
{ filter: '@siduri-x/core', tarName: 'siduri-x-core-1.0.4.tgz', isOrgan: false },
|
|
17
|
+
{ filter: '@siduri-x/brain', tarName: 'siduri-x-brain-1.0.3.tgz', isOrgan: true },
|
|
18
|
+
{ filter: '@siduri-x/memory', tarName: 'siduri-x-memory-1.0.3.tgz', isOrgan: true },
|
|
19
19
|
{ filter: '@siduri-x/knowledge', tarName: 'siduri-x-knowledge-1.0.2.tgz', isOrgan: true },
|
|
20
|
-
{ filter: '@siduri-x/behavior', tarName: 'siduri-x-behavior-1.0.
|
|
20
|
+
{ filter: '@siduri-x/behavior', tarName: 'siduri-x-behavior-1.0.5.tgz', isOrgan: true },
|
|
21
21
|
{ filter: '@siduri-x/ear', tarName: 'siduri-x-ear-1.0.2.tgz', isOrgan: true },
|
|
22
22
|
{ filter: '@siduri-x/vision', tarName: 'siduri-x-vision-1.0.2.tgz', isOrgan: true },
|
|
23
23
|
{ filter: '@siduri-x/hands', tarName: 'siduri-x-hands-1.0.2.tgz', isOrgan: true },
|
|
24
24
|
{ filter: '@siduri-x/body', tarName: 'siduri-x-body-1.0.4.tgz', isOrgan: true },
|
|
25
|
-
{ filter: '@siduri-x/voice', tarName: 'siduri-x-voice-1.0.
|
|
25
|
+
{ filter: '@siduri-x/voice', tarName: 'siduri-x-voice-1.0.5.tgz', isOrgan: true },
|
|
26
26
|
{ filter: '@siduri-x/observation', tarName: 'siduri-x-observation-1.0.2.tgz', isOrgan: true },
|
|
27
|
-
{ filter: '@vxnus/siduri', tarName: 'vxnus-siduri-0.1.
|
|
27
|
+
{ filter: '@vxnus/siduri', tarName: 'vxnus-siduri-0.1.7.tgz', isOrgan: false },
|
|
28
28
|
];
|
|
29
29
|
beforeAll(() => {
|
|
30
30
|
// 1. Prepare clean directories
|
|
@@ -86,7 +86,7 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
|
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
test('memory organ tarball packages migrations/001_initial_schema.sql', () => {
|
|
89
|
-
const memoryTarPath = node_path_1.default.join(tempPackDir, 'siduri-x-memory-1.0.
|
|
89
|
+
const memoryTarPath = node_path_1.default.join(tempPackDir, 'siduri-x-memory-1.0.3.tgz');
|
|
90
90
|
const listing = (0, node_child_process_1.execSync)(`tar -tzf ${memoryTarPath}`, { encoding: 'utf8' });
|
|
91
91
|
expect(listing).toContain('package/migrations/001_initial_schema.sql');
|
|
92
92
|
});
|
|
@@ -106,8 +106,8 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
|
|
|
106
106
|
// Write instance files referencing packed tarballs directly for true clean machine install
|
|
107
107
|
const pkgObj = JSON.parse(files['package.json']);
|
|
108
108
|
pkgObj.dependencies = {
|
|
109
|
-
'@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.
|
|
110
|
-
'@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.
|
|
109
|
+
'@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.4.tgz')}`,
|
|
110
|
+
'@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.3.tgz')}`,
|
|
111
111
|
};
|
|
112
112
|
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'package.json'), JSON.stringify(pkgObj, null, 2) + '\n');
|
|
113
113
|
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'siduri.config.json'), files['siduri.config.json']);
|
|
@@ -154,8 +154,8 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
|
|
|
154
154
|
});
|
|
155
155
|
const pkgObj = JSON.parse(files['package.json']);
|
|
156
156
|
pkgObj.dependencies = {
|
|
157
|
-
'@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.
|
|
158
|
-
'@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.
|
|
157
|
+
'@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.4.tgz')}`,
|
|
158
|
+
'@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.3.tgz')}`,
|
|
159
159
|
'@siduri-x/hands': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-hands-1.0.2.tgz')}`,
|
|
160
160
|
};
|
|
161
161
|
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'package.json'), JSON.stringify(pkgObj, null, 2) + '\n');
|
|
@@ -193,9 +193,9 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
|
|
|
193
193
|
});
|
|
194
194
|
const pkgObj = JSON.parse(files['package.json']);
|
|
195
195
|
pkgObj.dependencies = {
|
|
196
|
-
'@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.
|
|
197
|
-
'@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.
|
|
198
|
-
'@siduri-x/memory': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-memory-1.0.
|
|
196
|
+
'@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.4.tgz')}`,
|
|
197
|
+
'@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.3.tgz')}`,
|
|
198
|
+
'@siduri-x/memory': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-memory-1.0.3.tgz')}`,
|
|
199
199
|
};
|
|
200
200
|
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'package.json'), JSON.stringify(pkgObj, null, 2) + '\n');
|
|
201
201
|
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'siduri.config.json'), files['siduri.config.json']);
|
|
@@ -61,10 +61,15 @@ async function configureVoice(_context) {
|
|
|
61
61
|
default: 'rmvpe',
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
|
-
type: '
|
|
65
|
-
name: '
|
|
66
|
-
message: 'Base TTS
|
|
67
|
-
|
|
64
|
+
type: 'list',
|
|
65
|
+
name: 'baseTts',
|
|
66
|
+
message: 'Base TTS Engine for RVC (Generates initial audio):',
|
|
67
|
+
choices: [
|
|
68
|
+
{ name: 'Edge-TTS (Cloud API, 0MB)', value: 'edge-tts' },
|
|
69
|
+
{ name: 'Kokoro TTS (Local, ~80MB)', value: 'kokoro' },
|
|
70
|
+
{ name: 'Piper TTS (Local, ~20MB)', value: 'piper' },
|
|
71
|
+
],
|
|
72
|
+
default: 'edge-tts',
|
|
68
73
|
},
|
|
69
74
|
{
|
|
70
75
|
type: 'input',
|
|
@@ -75,9 +80,7 @@ async function configureVoice(_context) {
|
|
|
75
80
|
]);
|
|
76
81
|
const pitchShift = parseInt(rvcAnswers.pitchShift, 10) || 0;
|
|
77
82
|
const config = {
|
|
78
|
-
provider:
|
|
79
|
-
baseUrl: rvcAnswers.baseUrl.trim(),
|
|
80
|
-
speakerId: 1,
|
|
83
|
+
provider: rvcAnswers.baseTts,
|
|
81
84
|
maxQueueDepth: 50,
|
|
82
85
|
rvc: {
|
|
83
86
|
enabled: true,
|
|
@@ -92,11 +95,11 @@ async function configureVoice(_context) {
|
|
|
92
95
|
};
|
|
93
96
|
const summary = {
|
|
94
97
|
Provider: 'RVC (Custom Voice Model)',
|
|
98
|
+
'Base TTS': rvcAnswers.baseTts,
|
|
95
99
|
'Model Path': rvcAnswers.modelPath.trim(),
|
|
96
100
|
'Pitch Shift': pitchShift,
|
|
97
101
|
'F0 Method': rvcAnswers.f0Method,
|
|
98
102
|
'RVC Service': rvcAnswers.serviceUrl.trim(),
|
|
99
|
-
'Base TTS': rvcAnswers.baseUrl.trim(),
|
|
100
103
|
};
|
|
101
104
|
return {
|
|
102
105
|
config,
|
|
@@ -198,6 +198,23 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
|
|
|
198
198
|
expect(result.config.speakerId).toBe(2);
|
|
199
199
|
expect(result.summary?.['Speaker ID']).toBe(2);
|
|
200
200
|
});
|
|
201
|
+
test('Voice configurator configures RVC with Base TTS Engine', async () => {
|
|
202
|
+
inquirer_1.default.prompt
|
|
203
|
+
.mockResolvedValueOnce({ provider: 'rvc' })
|
|
204
|
+
.mockResolvedValueOnce({
|
|
205
|
+
modelPath: './assets/voice/sparkle/sparkle.pth',
|
|
206
|
+
indexPath: './assets/voice/sparkle/sparkle.index',
|
|
207
|
+
pitchShift: '12',
|
|
208
|
+
f0Method: 'rmvpe',
|
|
209
|
+
baseTts: 'edge-tts',
|
|
210
|
+
serviceUrl: 'http://localhost:50055',
|
|
211
|
+
});
|
|
212
|
+
const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
|
|
213
|
+
expect(result.config.provider).toBe('edge-tts');
|
|
214
|
+
expect(result.config.rvc.enabled).toBe(true);
|
|
215
|
+
expect(result.config.rvc.pitchShift).toBe(12);
|
|
216
|
+
expect(result.summary?.['Base TTS']).toBe('edge-tts');
|
|
217
|
+
});
|
|
201
218
|
test('Body configurator configures Live2D, custom model path, and expression', async () => {
|
|
202
219
|
inquirer_1.default.prompt
|
|
203
220
|
.mockResolvedValueOnce({ provider: 'live2d' })
|
|
@@ -276,7 +293,7 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
|
|
|
276
293
|
expect(files['src/index.js']).toContain("import { OpenRouterBrain } from '@siduri-x/brain'");
|
|
277
294
|
expect(files['src/index.js']).toContain("import { PostgresMemoryOrgan } from '@siduri-x/memory'");
|
|
278
295
|
expect(files['src/index.js']).toContain("import { EKnowledgeAdapter } from '@siduri-x/knowledge'");
|
|
279
|
-
expect(files['src/index.js']).toContain("import {
|
|
296
|
+
expect(files['src/index.js']).toContain("import { VoiceAdapter } from '@siduri-x/voice'");
|
|
280
297
|
});
|
|
281
298
|
});
|
|
282
299
|
});
|
package/dist/generator.js
CHANGED
|
@@ -68,7 +68,7 @@ function getDefaultConfigForManifest(manifest) {
|
|
|
68
68
|
function generateInstanceFiles(options) {
|
|
69
69
|
const instanceName = options.name || 'my-siduri';
|
|
70
70
|
const instanceId = options.id || 'default';
|
|
71
|
-
const coreVersion = options.coreVersion || '^1.0.
|
|
71
|
+
const coreVersion = options.coreVersion || '^1.0.4';
|
|
72
72
|
const manifests = options.selectedManifests;
|
|
73
73
|
const hasMemory = manifests.some((m) => m.organType === 'memory');
|
|
74
74
|
const hasVoice = manifests.some((m) => m.organType === 'voice');
|
|
@@ -101,14 +101,6 @@ function generateInstanceFiles(options) {
|
|
|
101
101
|
].join('\n'));
|
|
102
102
|
dockerVolumes.push(' postgres_data:');
|
|
103
103
|
}
|
|
104
|
-
if (isVoicevox) {
|
|
105
|
-
dockerServices.push([
|
|
106
|
-
' voicevox:',
|
|
107
|
-
' image: voicevox/voicevox_engine:cpu-latest',
|
|
108
|
-
' ports:',
|
|
109
|
-
' - "50021:50021"',
|
|
110
|
-
].join('\n'));
|
|
111
|
-
}
|
|
112
104
|
if (hasVoice && voiceConfig?.rvc?.enabled) {
|
|
113
105
|
dockerServices.push([
|
|
114
106
|
' rvc:',
|
|
@@ -218,7 +210,7 @@ function generateInstanceFiles(options) {
|
|
|
218
210
|
`import { readFile, stat, readdir } from 'node:fs/promises';`,
|
|
219
211
|
`import path from 'node:path';`,
|
|
220
212
|
`import { fileURLToPath } from 'node:url';`,
|
|
221
|
-
`import { SiduriRuntime } from '@siduri-x/core';`,
|
|
213
|
+
`import { SiduriRuntime, dispatchCompanionChat } from '@siduri-x/core';`,
|
|
222
214
|
];
|
|
223
215
|
for (const m of manifests) {
|
|
224
216
|
importLines.push(`import { ${m.factory} } from '${m.name}';`);
|
|
@@ -256,14 +248,11 @@ function generateInstanceFiles(options) {
|
|
|
256
248
|
` const parsedUrl = new URL(req.url, \`http://\${req.headers.host || 'localhost'}\`);`,
|
|
257
249
|
` const pathname = parsedUrl.pathname;`,
|
|
258
250
|
'',
|
|
259
|
-
` // API: Status & Health Probes`,
|
|
251
|
+
` // API: Status & Health Probes (minimal safe operational state, no secrets/credentials/config.organs)`,
|
|
260
252
|
` if ((pathname === '/health' || pathname === '/api/status') && req.method === 'GET') {`,
|
|
261
253
|
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
262
254
|
` res.end(JSON.stringify({`,
|
|
263
255
|
` status: 'ok',`,
|
|
264
|
-
` name: config.name,`,
|
|
265
|
-
` id: config.id,`,
|
|
266
|
-
` organs: config.organs,`,
|
|
267
256
|
` uptime: process.uptime(),`,
|
|
268
257
|
` }));`,
|
|
269
258
|
` return;`,
|
|
@@ -324,8 +313,35 @@ function generateInstanceFiles(options) {
|
|
|
324
313
|
'',
|
|
325
314
|
` // API: Memory Proposal Approval / Rejection`,
|
|
326
315
|
` if ((pathname === '/memory/proposals/approve' || pathname === '/memory/proposals/reject') && req.method === 'POST') {`,
|
|
327
|
-
`
|
|
328
|
-
`
|
|
316
|
+
` let body = '';`,
|
|
317
|
+
` req.on('data', (chunk) => { body += chunk; });`,
|
|
318
|
+
` req.on('end', async () => {`,
|
|
319
|
+
` try {`,
|
|
320
|
+
` const payload = JSON.parse(body || '{}');`,
|
|
321
|
+
` const claimId = payload.id || payload.claimId;`,
|
|
322
|
+
` if (!claimId) {`,
|
|
323
|
+
` res.writeHead(400, { 'Content-Type': 'application/json' });`,
|
|
324
|
+
` res.end(JSON.stringify({ error: 'Missing required claim id' }));`,
|
|
325
|
+
` return;`,
|
|
326
|
+
` }`,
|
|
327
|
+
` if (pathname.endsWith('approve')) {`,
|
|
328
|
+
` if (typeof memory?.approveClaim === 'function') {`,
|
|
329
|
+
` await memory.approveClaim(claimId);`,
|
|
330
|
+
` }`,
|
|
331
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
332
|
+
` res.end(JSON.stringify({ approved: true, id: claimId }));`,
|
|
333
|
+
` } else {`,
|
|
334
|
+
` if (typeof memory?.rejectClaim === 'function') {`,
|
|
335
|
+
` await memory.rejectClaim(claimId);`,
|
|
336
|
+
` }`,
|
|
337
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
338
|
+
` res.end(JSON.stringify({ rejected: true, id: claimId }));`,
|
|
339
|
+
` }`,
|
|
340
|
+
` } catch (err) {`,
|
|
341
|
+
` res.writeHead(500, { 'Content-Type': 'application/json' });`,
|
|
342
|
+
` res.end(JSON.stringify({ error: err.message }));`,
|
|
343
|
+
` }`,
|
|
344
|
+
` });`,
|
|
329
345
|
` return;`,
|
|
330
346
|
` }`,
|
|
331
347
|
'',
|
|
@@ -336,43 +352,23 @@ function generateInstanceFiles(options) {
|
|
|
336
352
|
` return;`,
|
|
337
353
|
` }`,
|
|
338
354
|
'',
|
|
339
|
-
` // API: Chat interaction (
|
|
355
|
+
` // API: Chat interaction (routed through canonical dispatchCompanionChat from @siduri-x/core)`,
|
|
340
356
|
` if ((pathname === '/chat' || pathname === '/api/chat') && req.method === 'POST') {`,
|
|
341
357
|
` let body = '';`,
|
|
342
358
|
` req.on('data', (chunk) => { body += chunk; });`,
|
|
343
359
|
` req.on('end', async () => {`,
|
|
344
360
|
` try {`,
|
|
345
361
|
` const payload = JSON.parse(body || '{}');`,
|
|
346
|
-
` const
|
|
347
|
-
`
|
|
348
|
-
`
|
|
349
|
-
`
|
|
350
|
-
''
|
|
351
|
-
`
|
|
352
|
-
`
|
|
353
|
-
` const plan = await brain.generatePlan({ prompt: userMessage });`,
|
|
354
|
-
` reply = plan.speech || plan.text || reply;`,
|
|
355
|
-
` } catch (err) {`,
|
|
356
|
-
` reply = \`Thinking about "\${userMessage}"... (Brain active)\`;`,
|
|
357
|
-
` }`,
|
|
358
|
-
` }`,
|
|
359
|
-
'',
|
|
360
|
-
` if (typeof voice?.synthesize === 'function') {`,
|
|
361
|
-
` try {`,
|
|
362
|
-
` const wavData = await voice.synthesize(reply);`,
|
|
363
|
-
` const audioId = \`audio_\${Date.now()}\`;`,
|
|
364
|
-
` audioCache.set(audioId, wavData);`,
|
|
365
|
-
` audioUrl = \`/api/audio/\${audioId}\`;`,
|
|
366
|
-
` } catch (e) {}`,
|
|
367
|
-
` }`,
|
|
368
|
-
'',
|
|
369
|
-
` if (typeof body?.setExpression === 'function') {`,
|
|
370
|
-
` expression = reply.includes('!') ? 'happy' : 'calm';`,
|
|
371
|
-
` body.setExpression(expression);`,
|
|
372
|
-
` }`,
|
|
362
|
+
` const response = await dispatchCompanionChat(runtime, {`,
|
|
363
|
+
` id: config.id,`,
|
|
364
|
+
` companionId: config.id,`,
|
|
365
|
+
` message: payload.message || payload.text || '',`,
|
|
366
|
+
` role: payload.role || 'OWNER',`,
|
|
367
|
+
` history: Array.isArray(payload.history) ? payload.history : [],`,
|
|
368
|
+
` });`,
|
|
373
369
|
'',
|
|
374
370
|
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
375
|
-
` res.end(JSON.stringify(
|
|
371
|
+
` res.end(JSON.stringify(response));`,
|
|
376
372
|
` } catch (err) {`,
|
|
377
373
|
` res.writeHead(500, { 'Content-Type': 'application/json' });`,
|
|
378
374
|
` res.end(JSON.stringify({ error: err.message }));`,
|
|
@@ -408,32 +404,49 @@ function generateInstanceFiles(options) {
|
|
|
408
404
|
` return;`,
|
|
409
405
|
` }`,
|
|
410
406
|
'',
|
|
411
|
-
` // Static files & Next.js apps/web export routing`,
|
|
412
|
-
`
|
|
413
|
-
`
|
|
414
|
-
`
|
|
415
|
-
`
|
|
416
|
-
`
|
|
417
|
-
`
|
|
418
|
-
`
|
|
407
|
+
` // Static files & Next.js apps/web export routing with strict boundary verification`,
|
|
408
|
+
` let decodedPathname = pathname;`,
|
|
409
|
+
` try {`,
|
|
410
|
+
` while (decodedPathname.includes('%')) {`,
|
|
411
|
+
` const next = decodeURIComponent(decodedPathname);`,
|
|
412
|
+
` if (next === decodedPathname) break;`,
|
|
413
|
+
` decodedPathname = next;`,
|
|
414
|
+
` }`,
|
|
415
|
+
` } catch {`,
|
|
416
|
+
` res.writeHead(400, { 'Content-Type': 'text/plain' });`,
|
|
417
|
+
` res.end('Bad Request');`,
|
|
418
|
+
` return;`,
|
|
419
|
+
` }`,
|
|
420
|
+
` const normalizedPath = path.normalize(decodedPathname).replace(/^[/\\\\]+/, '');`,
|
|
421
|
+
` const canonicalPublicRoot = path.resolve(rootDir, 'public');`,
|
|
422
|
+
` const canonicalAssetsRoot = path.resolve(rootDir, 'assets');`,
|
|
423
|
+
` const candidatePairs = [`,
|
|
424
|
+
` { root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, normalizedPath, 'index.html') },`,
|
|
425
|
+
` { root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, normalizedPath + '.html') },`,
|
|
426
|
+
` { root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, normalizedPath) },`,
|
|
419
427
|
` ];`,
|
|
420
428
|
'',
|
|
421
|
-
` if (!normalizedPath) {`,
|
|
422
|
-
`
|
|
429
|
+
` if (!normalizedPath || normalizedPath === '.' || normalizedPath === './') {`,
|
|
430
|
+
` candidatePairs.unshift({ root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, 'index.html') });`,
|
|
423
431
|
` }`,
|
|
424
432
|
'',
|
|
425
433
|
` // Compatibility fallback for legacy /live2d/<modelName>/... -> ./assets/body/<modelName>/...`,
|
|
426
|
-
` if (
|
|
427
|
-
` const relativeAssetPath =
|
|
428
|
-
`
|
|
429
|
-
`
|
|
434
|
+
` if (decodedPathname.startsWith('/live2d/')) {`,
|
|
435
|
+
` const relativeAssetPath = path.normalize(decodedPathname.slice('/live2d/'.length)).replace(/^[/\\\\]+/, '');`,
|
|
436
|
+
` candidatePairs.unshift({ root: canonicalAssetsRoot, target: path.resolve(canonicalAssetsRoot, 'body', relativeAssetPath) });`,
|
|
437
|
+
` candidatePairs.unshift({ root: canonicalAssetsRoot, target: path.resolve(canonicalAssetsRoot, 'body', 'model', relativeAssetPath) });`,
|
|
430
438
|
` }`,
|
|
431
439
|
'',
|
|
432
|
-
` for (const
|
|
440
|
+
` for (const { root, target } of candidatePairs) {`,
|
|
433
441
|
` try {`,
|
|
434
|
-
`
|
|
442
|
+
` // Strict path containment check: target must reside within configured root directory`,
|
|
443
|
+
` const relative = path.relative(root, target);`,
|
|
444
|
+
` if (relative.startsWith('..') || path.isAbsolute(relative)) {`,
|
|
445
|
+
` continue;`,
|
|
446
|
+
` }`,
|
|
447
|
+
` const fileStat = await stat(target);`,
|
|
435
448
|
` if (fileStat.isFile()) {`,
|
|
436
|
-
` const ext = path.extname(
|
|
449
|
+
` const ext = path.extname(target).toLowerCase();`,
|
|
437
450
|
` const mimeTypes = {`,
|
|
438
451
|
` '.html': 'text/html; charset=utf-8',`,
|
|
439
452
|
` '.js': 'application/javascript; charset=utf-8',`,
|
|
@@ -448,7 +461,7 @@ function generateInstanceFiles(options) {
|
|
|
448
461
|
` '.moc3': 'application/octet-stream',`,
|
|
449
462
|
` };`,
|
|
450
463
|
` const contentType = mimeTypes[ext] || 'application/octet-stream';`,
|
|
451
|
-
` const content = await readFile(
|
|
464
|
+
` const content = await readFile(target);`,
|
|
452
465
|
` res.writeHead(200, { 'Content-Type': contentType });`,
|
|
453
466
|
` res.end(content);`,
|
|
454
467
|
` return;`,
|
|
@@ -461,9 +474,12 @@ function generateInstanceFiles(options) {
|
|
|
461
474
|
`});`,
|
|
462
475
|
'',
|
|
463
476
|
`const PORT = process.env.PORT || 3000;`,
|
|
464
|
-
`server.listen(PORT, () => {`,
|
|
477
|
+
`server.listen(Number(PORT), '127.0.0.1', () => {`,
|
|
465
478
|
` console.log(\`✓ Siduri [\${config.name}] initialized with [${selectedDisplayNames}].\`);`,
|
|
466
|
-
` console.log(\`➜ Web Companion & Memory Console running at: http://
|
|
479
|
+
` console.log(\`➜ Web Companion & Memory Console running at: http://127.0.0.1:\${PORT}\`);`,
|
|
480
|
+
` if (process.env.PORT === '0') {`,
|
|
481
|
+
` server.close();`,
|
|
482
|
+
` }`,
|
|
467
483
|
`});`,
|
|
468
484
|
'',
|
|
469
485
|
].join('\n');
|
|
@@ -480,14 +496,12 @@ function generateInstanceFiles(options) {
|
|
|
480
496
|
'- **Node.js**: `v20.0.0` or higher',
|
|
481
497
|
'- **Environment**: Valid `.env` file (configured from `.env.example`)',
|
|
482
498
|
];
|
|
483
|
-
if (hasMemory
|
|
499
|
+
if (hasMemory) {
|
|
484
500
|
readmeLines.push('- **Local Services (Optional)**: Docker (or standalone alternatives):');
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if
|
|
489
|
-
readmeLines.push(' - **VOICEVOX**: Required for voice synthesis (or run the official desktop app from [voicevox.hiroshiba.jp](https://voicevox.hiroshiba.jp/))');
|
|
490
|
-
}
|
|
501
|
+
readmeLines.push(' - **PostgreSQL**: Required for memory claims & durable state (or use cloud Supabase/Neon)');
|
|
502
|
+
}
|
|
503
|
+
if (isVoicevox) {
|
|
504
|
+
readmeLines.push('- **VOICEVOX**: Note: The Voicevox engine executable will be securely auto-downloaded at runtime by Siduri if no local URL is provided.');
|
|
491
505
|
}
|
|
492
506
|
readmeLines.push('', '## Getting Started', '', '### 1. Install Dependencies', '```bash', 'npm install', '```', '', '### 2. Configure Environment', '```bash', 'cp .env.example .env', '```', 'Fill in your LLM API key (e.g. `OPENROUTER_API_KEY`) and any other service credentials in `.env`.');
|
|
493
507
|
if (dockerComposeYaml) {
|
package/dist/generator.test.js
CHANGED
|
@@ -58,7 +58,7 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
58
58
|
version: '1.0.0',
|
|
59
59
|
displayName: 'Voice (VOICEVOX Speech Synthesis)',
|
|
60
60
|
entrypoint: './dist/index.js',
|
|
61
|
-
factory: '
|
|
61
|
+
factory: 'VoiceAdapter',
|
|
62
62
|
configKey: 'voice',
|
|
63
63
|
configSchema: { type: 'object', properties: { provider: { type: 'string' } } },
|
|
64
64
|
environment: [{ name: 'VOICEVOX_URL', default: 'http://localhost:50021' }],
|
|
@@ -158,10 +158,10 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
158
158
|
expect(files['README.md']).toContain('assets/body/companion-full');
|
|
159
159
|
expect(files['README.md']).toContain('assets/voice/companion-full');
|
|
160
160
|
expect(files['README.md']).toContain('Prerequisites');
|
|
161
|
-
// Docker compose generated for memory
|
|
161
|
+
// Docker compose generated for memory (voicevox is auto-downloaded at runtime)
|
|
162
162
|
expect(files['docker-compose.yml']).toBeDefined();
|
|
163
163
|
expect(files['docker-compose.yml']).toContain('postgres:15');
|
|
164
|
-
expect(files['
|
|
164
|
+
expect(files['README.md']).toContain('Voicevox engine executable will be securely auto-downloaded');
|
|
165
165
|
expect(pkg.scripts['services:up']).toBe('docker compose up -d');
|
|
166
166
|
expect(pkg.scripts['services:down']).toBe('docker compose down');
|
|
167
167
|
});
|
|
@@ -177,4 +177,21 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
177
177
|
expect(files['public/index.html']).toContain('Memory Console');
|
|
178
178
|
expect(files['public/index.html']).toContain('Companion Chat');
|
|
179
179
|
});
|
|
180
|
+
test('Generated runtime server template enforces loopback binding, safe health, and path containment', () => {
|
|
181
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
182
|
+
name: 'secure-companion',
|
|
183
|
+
selectedManifests: [MOCK_MANIFESTS.brain, MOCK_MANIFESTS.memory],
|
|
184
|
+
});
|
|
185
|
+
const srcIndexJs = files['src/index.js'];
|
|
186
|
+
// 1. Explicit loopback binding 127.0.0.1
|
|
187
|
+
expect(srcIndexJs).toContain("server.listen(Number(PORT), '127.0.0.1'");
|
|
188
|
+
expect(srcIndexJs).toContain("http://127.0.0.1:");
|
|
189
|
+
// 2. Minimal /health endpoint without config.organs or secrets
|
|
190
|
+
expect(srcIndexJs).toContain("pathname === '/health'");
|
|
191
|
+
expect(srcIndexJs).not.toContain('organs: config.organs');
|
|
192
|
+
// 3. Strict path traversal containment
|
|
193
|
+
expect(srcIndexJs).toContain('relative.startsWith(\'..\')');
|
|
194
|
+
expect(srcIndexJs).toContain('path.isAbsolute(relative)');
|
|
195
|
+
expect(srcIndexJs).toContain('decodeURIComponent');
|
|
196
|
+
});
|
|
180
197
|
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ const doctor_1 = require("./doctor");
|
|
|
26
26
|
const db_1 = require("./db");
|
|
27
27
|
const configurators_1 = require("./configurators");
|
|
28
28
|
const execFile = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
29
|
-
exports.CLI_VERSION = '0.1.
|
|
29
|
+
exports.CLI_VERSION = '0.1.7';
|
|
30
30
|
exports.colors = {
|
|
31
31
|
cyan: '\u001b[36m',
|
|
32
32
|
dim: '\u001b[2m',
|
package/dist/release-check.js
CHANGED
|
@@ -17,18 +17,18 @@ function runReleaseCheck(repoRoot = node_path_1.default.resolve(__dirname, '../.
|
|
|
17
17
|
node_fs_1.default.rmSync(tempPackDir, { recursive: true, force: true });
|
|
18
18
|
node_fs_1.default.mkdirSync(tempPackDir, { recursive: true });
|
|
19
19
|
const canonicalPackages = [
|
|
20
|
-
{ name: '@siduri-x/core', dir: 'packages/core', isOrgan: false, tarName: 'siduri-x-core-1.0.
|
|
21
|
-
{ name: '@siduri-x/brain', dir: 'packages/organs/brain', isOrgan: true, tarName: 'siduri-x-brain-1.0.
|
|
22
|
-
{ name: '@siduri-x/memory', dir: 'packages/organs/memory', isOrgan: true, tarName: 'siduri-x-memory-1.0.
|
|
20
|
+
{ name: '@siduri-x/core', dir: 'packages/core', isOrgan: false, tarName: 'siduri-x-core-1.0.4.tgz' },
|
|
21
|
+
{ name: '@siduri-x/brain', dir: 'packages/organs/brain', isOrgan: true, tarName: 'siduri-x-brain-1.0.3.tgz' },
|
|
22
|
+
{ name: '@siduri-x/memory', dir: 'packages/organs/memory', isOrgan: true, tarName: 'siduri-x-memory-1.0.3.tgz' },
|
|
23
23
|
{ name: '@siduri-x/knowledge', dir: 'packages/organs/knowledge', isOrgan: true, tarName: 'siduri-x-knowledge-1.0.2.tgz' },
|
|
24
|
-
{ name: '@siduri-x/behavior', dir: 'packages/organs/behavior', isOrgan: true, tarName: 'siduri-x-behavior-1.0.
|
|
24
|
+
{ name: '@siduri-x/behavior', dir: 'packages/organs/behavior', isOrgan: true, tarName: 'siduri-x-behavior-1.0.5.tgz' },
|
|
25
25
|
{ name: '@siduri-x/ear', dir: 'packages/organs/ear', isOrgan: true, tarName: 'siduri-x-ear-1.0.2.tgz' },
|
|
26
26
|
{ name: '@siduri-x/vision', dir: 'packages/organs/vision', isOrgan: true, tarName: 'siduri-x-vision-1.0.2.tgz' },
|
|
27
27
|
{ name: '@siduri-x/hands', dir: 'packages/organs/hands', isOrgan: true, tarName: 'siduri-x-hands-1.0.2.tgz' },
|
|
28
28
|
{ name: '@siduri-x/body', dir: 'packages/organs/body', isOrgan: true, tarName: 'siduri-x-body-1.0.4.tgz' },
|
|
29
|
-
{ name: '@siduri-x/voice', dir: 'packages/organs/voice', isOrgan: true, tarName: 'siduri-x-voice-1.0.
|
|
29
|
+
{ name: '@siduri-x/voice', dir: 'packages/organs/voice', isOrgan: true, tarName: 'siduri-x-voice-1.0.5.tgz' },
|
|
30
30
|
{ name: '@siduri-x/observation', dir: 'packages/organs/observation', isOrgan: true, tarName: 'siduri-x-observation-1.0.2.tgz' },
|
|
31
|
-
{ name: '@vxnus/siduri', dir: 'cli', isOrgan: false, tarName: 'vxnus-siduri-0.1.
|
|
31
|
+
{ name: '@vxnus/siduri', dir: 'cli', isOrgan: false, tarName: 'vxnus-siduri-0.1.7.tgz' },
|
|
32
32
|
];
|
|
33
33
|
let packagesChecked = 0;
|
|
34
34
|
let tarballsInspected = 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|