@vxnus/siduri 0.1.4 → 0.1.6

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.
Files changed (42) hide show
  1. package/dist/builtin-manifests.js +10 -10
  2. package/dist/clean-machine-e2e.test.js +21 -21
  3. package/dist/configurators/voice.js +11 -8
  4. package/dist/configurators.test.js +18 -1
  5. package/dist/generator.js +86 -71
  6. package/dist/generator.test.js +20 -3
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/release-check.js +12 -12
  10. package/dist/runtime-parity.test.d.ts +1 -0
  11. package/dist/runtime-parity.test.js +140 -0
  12. package/dist/web-dist/404/index.html +1 -1
  13. package/dist/web-dist/404.html +1 -1
  14. package/dist/web-dist/__next.__PAGE__.txt +22 -22
  15. package/dist/web-dist/__next._full.txt +26 -22
  16. package/dist/web-dist/__next._tree.txt +2 -2
  17. package/dist/web-dist/_next/static/chunks/10rmct1ax-opq.js +1 -0
  18. package/dist/web-dist/_next/static/chunks/2vh8hinyh9xfg.css +1 -0
  19. package/dist/web-dist/_not-found/__next._full.txt +2 -2
  20. package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +2 -2
  21. package/dist/web-dist/_not-found/__next._tree.txt +2 -2
  22. package/dist/web-dist/_not-found/index.html +1 -1
  23. package/dist/web-dist/_not-found/index.txt +2 -2
  24. package/dist/web-dist/chat/__next._full.txt +2 -2
  25. package/dist/web-dist/chat/__next._tree.txt +2 -2
  26. package/dist/web-dist/chat/__next.chat.__PAGE__.txt +2 -2
  27. package/dist/web-dist/chat/index.html +1 -1
  28. package/dist/web-dist/chat/index.txt +2 -2
  29. package/dist/web-dist/index.html +1 -1
  30. package/dist/web-dist/index.txt +26 -22
  31. package/dist/web-dist/operator/__next._full.txt +3 -3
  32. package/dist/web-dist/operator/__next._tree.txt +2 -2
  33. package/dist/web-dist/operator/__next.operator.__PAGE__.txt +3 -3
  34. package/dist/web-dist/operator/index.html +1 -1
  35. package/dist/web-dist/operator/index.txt +3 -3
  36. package/dist/web-template.js +15 -6
  37. package/package.json +2 -2
  38. package/dist/web-dist/_next/static/chunks/1dc9n0p39yo65.css +0 -1
  39. package/dist/web-dist/_next/static/chunks/1hz3jco387c-k.js +0 -1
  40. /package/dist/web-dist/_next/static/{fySnDRfSgiBzStXXyXjTp → D38KTbM_PIeZqLpHvixAH}/_buildManifest.js +0 -0
  41. /package/dist/web-dist/_next/static/{fySnDRfSgiBzStXXyXjTp → D38KTbM_PIeZqLpHvixAH}/_clientMiddlewareManifest.js +0 -0
  42. /package/dist/web-dist/_next/static/{fySnDRfSgiBzStXXyXjTp → D38KTbM_PIeZqLpHvixAH}/_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.1',
8
+ version: '1.0.3',
9
9
  displayName: 'Behavior (Active Self Directives)',
10
10
  description: 'Atomic directive state machine and personality projection compiler',
11
11
  entrypoint: './dist/index.js',
@@ -32,7 +32,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
32
32
  {
33
33
  name: '@siduri-x/body',
34
34
  organType: 'body',
35
- version: '1.0.3',
35
+ version: '1.0.4',
36
36
  displayName: 'Body (Live2D & Avatar State)',
37
37
  description: 'Renderer-agnostic avatar expression and embodiment event adapter',
38
38
  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.1',
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',
@@ -122,7 +122,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
122
122
  {
123
123
  name: '@siduri-x/ear',
124
124
  organType: 'ear',
125
- version: '1.0.1',
125
+ version: '1.0.2',
126
126
  displayName: 'Ear (Perception Ingress)',
127
127
  description: 'Multi-modal sensory input ingestion, transcription bounds, and mime validation',
128
128
  entrypoint: './dist/index.js',
@@ -153,7 +153,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
153
153
  {
154
154
  name: '@siduri-x/hands',
155
155
  organType: 'hands',
156
- version: '1.0.1',
156
+ version: '1.0.2',
157
157
  displayName: 'Hands (MCP Tool Execution)',
158
158
  description: 'Model Context Protocol tool management and cryptographically authorized action execution',
159
159
  entrypoint: './dist/index.js',
@@ -207,7 +207,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
207
207
  {
208
208
  name: '@siduri-x/knowledge',
209
209
  organType: 'knowledge',
210
- version: '1.0.1',
210
+ version: '1.0.2',
211
211
  displayName: 'Knowledge (E-Compatible Cited Facts)',
212
212
  description: 'Factual context retrieval from local or hosted E Knowledge packs',
213
213
  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.1',
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',
@@ -324,7 +324,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
324
324
  {
325
325
  name: '@siduri-x/observation',
326
326
  organType: 'observation',
327
- version: '1.0.1',
327
+ version: '1.0.2',
328
328
  displayName: 'Observation (Screen Perception & Frame Ingest)',
329
329
  description: 'Screen capture frame ingest, SHA-256 deduplication, and visual grounding for Eyes/Vision',
330
330
  entrypoint: './dist/index.js',
@@ -342,7 +342,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
342
342
  {
343
343
  name: '@siduri-x/vision',
344
344
  organType: 'vision',
345
- version: '1.0.1',
345
+ version: '1.0.2',
346
346
  displayName: 'Vision (Visual Perception & OCR)',
347
347
  description: 'Image inspection, cropping, and multi-pass OCR perception adapter',
348
348
  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.2',
392
+ version: '1.0.4',
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.1.tgz', isOrgan: false },
17
- { filter: '@siduri-x/brain', tarName: 'siduri-x-brain-1.0.1.tgz', isOrgan: true },
18
- { filter: '@siduri-x/memory', tarName: 'siduri-x-memory-1.0.1.tgz', isOrgan: true },
19
- { filter: '@siduri-x/knowledge', tarName: 'siduri-x-knowledge-1.0.1.tgz', isOrgan: true },
20
- { filter: '@siduri-x/behavior', tarName: 'siduri-x-behavior-1.0.1.tgz', isOrgan: true },
21
- { filter: '@siduri-x/ear', tarName: 'siduri-x-ear-1.0.1.tgz', isOrgan: true },
22
- { filter: '@siduri-x/vision', tarName: 'siduri-x-vision-1.0.1.tgz', isOrgan: true },
23
- { filter: '@siduri-x/hands', tarName: 'siduri-x-hands-1.0.1.tgz', isOrgan: true },
24
- { filter: '@siduri-x/body', tarName: 'siduri-x-body-1.0.1.tgz', isOrgan: true },
25
- { filter: '@siduri-x/voice', tarName: 'siduri-x-voice-1.0.2.tgz', isOrgan: true },
26
- { filter: '@siduri-x/observation', tarName: 'siduri-x-observation-1.0.1.tgz', isOrgan: true },
27
- { filter: '@vxnus/siduri', tarName: 'vxnus-siduri-0.1.2.tgz', isOrgan: false },
16
+ { filter: '@siduri-x/core', tarName: 'siduri-x-core-1.0.3.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
+ { 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.3.tgz', isOrgan: true },
21
+ { filter: '@siduri-x/ear', tarName: 'siduri-x-ear-1.0.2.tgz', isOrgan: true },
22
+ { filter: '@siduri-x/vision', tarName: 'siduri-x-vision-1.0.2.tgz', isOrgan: true },
23
+ { filter: '@siduri-x/hands', tarName: 'siduri-x-hands-1.0.2.tgz', isOrgan: true },
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.4.tgz', isOrgan: true },
26
+ { filter: '@siduri-x/observation', tarName: 'siduri-x-observation-1.0.2.tgz', isOrgan: true },
27
+ { filter: '@vxnus/siduri', tarName: 'vxnus-siduri-0.1.6.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.1.tgz');
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.1.tgz')}`,
110
- '@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.1.tgz')}`,
109
+ '@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.3.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,9 +154,9 @@ 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.1.tgz')}`,
158
- '@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.1.tgz')}`,
159
- '@siduri-x/hands': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-hands-1.0.1.tgz')}`,
157
+ '@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.3.tgz')}`,
158
+ '@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.3.tgz')}`,
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');
162
162
  node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'siduri.config.json'), files['siduri.config.json']);
@@ -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.1.tgz')}`,
197
- '@siduri-x/brain': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-brain-1.0.1.tgz')}`,
198
- '@siduri-x/memory': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-memory-1.0.1.tgz')}`,
196
+ '@siduri-x/core': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-core-1.0.3.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: 'input',
65
- name: 'baseUrl',
66
- message: 'Base TTS Synthesis Engine URL (VOICEVOX):',
67
- default: 'http://localhost:50021',
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: 'voicevox',
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 { VoicevoxAdapter } from '@siduri-x/voice'");
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.1';
71
+ const coreVersion = options.coreVersion || '^1.0.3';
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
- ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
328
- ` res.end(JSON.stringify({ success: true, approved: pathname.endsWith('approve') }));`,
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 (supports both /chat and /api/chat)`,
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 userMessage = payload.message || payload.text || '';`,
347
- ` let reply = \`Hello! I received: "\${userMessage}"\`;`,
348
- ` let expression = 'neutral';`,
349
- ` let audioUrl = undefined;`,
350
- '',
351
- ` if (typeof brain?.generatePlan === 'function') {`,
352
- ` try {`,
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({ reply, text: reply, expression, audioUrl, status: 'ok' }));`,
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,31 +404,49 @@ function generateInstanceFiles(options) {
408
404
  ` return;`,
409
405
  ` }`,
410
406
  '',
411
- ` // Static files & Next.js apps/web export routing`,
412
- ` const normalizedPath = pathname.replace(/^[/]+|[/]+$/g, '');`,
413
- ` const candidatePaths = [`,
414
- ` path.join(rootDir, 'public', normalizedPath, 'index.html'),`,
415
- ` path.join(rootDir, 'public', normalizedPath + '.html'),`,
416
- ` path.join(rootDir, 'public', normalizedPath),`,
417
- ` path.join(rootDir, normalizedPath),`,
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) },`,
418
427
  ` ];`,
419
428
  '',
420
- ` if (!normalizedPath) {`,
421
- ` candidatePaths.unshift(path.join(rootDir, 'public', 'index.html'));`,
429
+ ` if (!normalizedPath || normalizedPath === '.' || normalizedPath === './') {`,
430
+ ` candidatePairs.unshift({ root: canonicalPublicRoot, target: path.resolve(canonicalPublicRoot, 'index.html') });`,
422
431
  ` }`,
423
432
  '',
424
433
  ` // Compatibility fallback for legacy /live2d/<modelName>/... -> ./assets/body/<modelName>/...`,
425
- ` if (pathname.startsWith('/live2d/')) {`,
426
- ` const relativeAssetPath = pathname.slice('/live2d/'.length);`,
427
- ` candidatePaths.unshift(path.join(rootDir, 'assets', 'body', relativeAssetPath));`,
428
- ` candidatePaths.unshift(path.join(rootDir, 'assets', 'body', 'model', relativeAssetPath));`,
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) });`,
429
438
  ` }`,
430
439
  '',
431
- ` for (const candidate of candidatePaths) {`,
440
+ ` for (const { root, target } of candidatePairs) {`,
432
441
  ` try {`,
433
- ` const fileStat = await stat(candidate);`,
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);`,
434
448
  ` if (fileStat.isFile()) {`,
435
- ` const ext = path.extname(candidate).toLowerCase();`,
449
+ ` const ext = path.extname(target).toLowerCase();`,
436
450
  ` const mimeTypes = {`,
437
451
  ` '.html': 'text/html; charset=utf-8',`,
438
452
  ` '.js': 'application/javascript; charset=utf-8',`,
@@ -447,7 +461,7 @@ function generateInstanceFiles(options) {
447
461
  ` '.moc3': 'application/octet-stream',`,
448
462
  ` };`,
449
463
  ` const contentType = mimeTypes[ext] || 'application/octet-stream';`,
450
- ` const content = await readFile(candidate);`,
464
+ ` const content = await readFile(target);`,
451
465
  ` res.writeHead(200, { 'Content-Type': contentType });`,
452
466
  ` res.end(content);`,
453
467
  ` return;`,
@@ -460,9 +474,12 @@ function generateInstanceFiles(options) {
460
474
  `});`,
461
475
  '',
462
476
  `const PORT = process.env.PORT || 3000;`,
463
- `server.listen(PORT, () => {`,
477
+ `server.listen(Number(PORT), '127.0.0.1', () => {`,
464
478
  ` console.log(\`✓ Siduri [\${config.name}] initialized with [${selectedDisplayNames}].\`);`,
465
- ` console.log(\`➜ Web Companion & Memory Console running at: http://localhost:\${PORT}\`);`,
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
+ ` }`,
466
483
  `});`,
467
484
  '',
468
485
  ].join('\n');
@@ -479,14 +496,12 @@ function generateInstanceFiles(options) {
479
496
  '- **Node.js**: `v20.0.0` or higher',
480
497
  '- **Environment**: Valid `.env` file (configured from `.env.example`)',
481
498
  ];
482
- if (hasMemory || isVoicevox) {
499
+ if (hasMemory) {
483
500
  readmeLines.push('- **Local Services (Optional)**: Docker (or standalone alternatives):');
484
- if (hasMemory) {
485
- readmeLines.push(' - **PostgreSQL**: Required for memory claims & durable state (or use cloud Supabase/Neon)');
486
- }
487
- if (isVoicevox) {
488
- readmeLines.push(' - **VOICEVOX**: Required for voice synthesis (or run the official desktop app from [voicevox.hiroshiba.jp](https://voicevox.hiroshiba.jp/))');
489
- }
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.');
490
505
  }
491
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`.');
492
507
  if (dockerComposeYaml) {
@@ -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: 'VoicevoxAdapter',
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 & voice
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['docker-compose.yml']).toContain('voicevox/voicevox_engine');
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
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { OrganManifest } from './manifest';
3
- export declare const CLI_VERSION = "0.1.3";
3
+ export declare const CLI_VERSION = "0.1.6";
4
4
  export declare const colors: {
5
5
  cyan: string;
6
6
  dim: string;
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.3';
29
+ exports.CLI_VERSION = '0.1.6';
30
30
  exports.colors = {
31
31
  cyan: '\u001b[36m',
32
32
  dim: '\u001b[2m',
@@ -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.1.tgz' },
21
- { name: '@siduri-x/brain', dir: 'packages/organs/brain', isOrgan: true, tarName: 'siduri-x-brain-1.0.1.tgz' },
22
- { name: '@siduri-x/memory', dir: 'packages/organs/memory', isOrgan: true, tarName: 'siduri-x-memory-1.0.1.tgz' },
23
- { name: '@siduri-x/knowledge', dir: 'packages/organs/knowledge', isOrgan: true, tarName: 'siduri-x-knowledge-1.0.1.tgz' },
24
- { name: '@siduri-x/behavior', dir: 'packages/organs/behavior', isOrgan: true, tarName: 'siduri-x-behavior-1.0.1.tgz' },
25
- { name: '@siduri-x/ear', dir: 'packages/organs/ear', isOrgan: true, tarName: 'siduri-x-ear-1.0.1.tgz' },
26
- { name: '@siduri-x/vision', dir: 'packages/organs/vision', isOrgan: true, tarName: 'siduri-x-vision-1.0.1.tgz' },
27
- { name: '@siduri-x/hands', dir: 'packages/organs/hands', isOrgan: true, tarName: 'siduri-x-hands-1.0.1.tgz' },
28
- { name: '@siduri-x/body', dir: 'packages/organs/body', isOrgan: true, tarName: 'siduri-x-body-1.0.3.tgz' },
29
- { name: '@siduri-x/voice', dir: 'packages/organs/voice', isOrgan: true, tarName: 'siduri-x-voice-1.0.2.tgz' },
30
- { name: '@siduri-x/observation', dir: 'packages/organs/observation', isOrgan: true, tarName: 'siduri-x-observation-1.0.1.tgz' },
31
- { name: '@vxnus/siduri', dir: 'cli', isOrgan: false, tarName: 'vxnus-siduri-0.1.3.tgz' },
20
+ { name: '@siduri-x/core', dir: 'packages/core', isOrgan: false, tarName: 'siduri-x-core-1.0.3.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
+ { 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.3.tgz' },
25
+ { name: '@siduri-x/ear', dir: 'packages/organs/ear', isOrgan: true, tarName: 'siduri-x-ear-1.0.2.tgz' },
26
+ { name: '@siduri-x/vision', dir: 'packages/organs/vision', isOrgan: true, tarName: 'siduri-x-vision-1.0.2.tgz' },
27
+ { name: '@siduri-x/hands', dir: 'packages/organs/hands', isOrgan: true, tarName: 'siduri-x-hands-1.0.2.tgz' },
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.4.tgz' },
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.6.tgz' },
32
32
  ];
33
33
  let packagesChecked = 0;
34
34
  let tarballsInspected = 0;
@@ -0,0 +1 @@
1
+ export {};