@vxnus/siduri 0.1.7 → 0.1.10

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 (54) hide show
  1. package/dist/builtin-manifests.js +32 -4
  2. package/dist/clean-machine-e2e.test.js +29 -23
  3. package/dist/configurators/index.d.ts +1 -0
  4. package/dist/configurators/index.js +4 -0
  5. package/dist/configurators/mouth.d.ts +2 -0
  6. package/dist/configurators/mouth.js +15 -0
  7. package/dist/configurators/voice.js +116 -2
  8. package/dist/configurators.test.js +32 -0
  9. package/dist/discovery.test.js +5 -3
  10. package/dist/generator.js +126 -2
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/release-check.js +14 -12
  14. package/dist/web-template.js +2 -1
  15. package/package.json +1 -1
  16. package/dist/web-dist/404/index.html +0 -1
  17. package/dist/web-dist/404.html +0 -1
  18. package/dist/web-dist/__next.__PAGE__.txt +0 -34
  19. package/dist/web-dist/__next._full.txt +0 -31
  20. package/dist/web-dist/__next._tree.txt +0 -3
  21. package/dist/web-dist/_next/static/chunks/0bncolc2nuzmo.css +0 -1
  22. package/dist/web-dist/_next/static/chunks/0cz1d0mv5g_q7.js +0 -1
  23. package/dist/web-dist/_next/static/chunks/0uwih7sydv413.js +0 -1
  24. package/dist/web-dist/_next/static/chunks/0xe6aaej0xsj8.js +0 -30
  25. package/dist/web-dist/_next/static/chunks/10rmct1ax-opq.js +0 -1
  26. package/dist/web-dist/_next/static/chunks/1_yae15ffa5f6.js +0 -1
  27. package/dist/web-dist/_next/static/chunks/2kcqevsmv6kh2.js +0 -31
  28. package/dist/web-dist/_next/static/chunks/2s3d1bfr0l4b0.js +0 -1
  29. package/dist/web-dist/_next/static/chunks/32z87id9k6i2z.js +0 -22
  30. package/dist/web-dist/_next/static/chunks/3bx2gs3idjmo1.js +0 -1
  31. package/dist/web-dist/_next/static/chunks/turbopack-3eipe4c-imn5l.js +0 -1
  32. package/dist/web-dist/_next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  33. package/dist/web-dist/_next/static/uvsK8ULhL7fHQMR8sr_JL/_buildManifest.js +0 -11
  34. package/dist/web-dist/_next/static/uvsK8ULhL7fHQMR8sr_JL/_clientMiddlewareManifest.js +0 -1
  35. package/dist/web-dist/_next/static/uvsK8ULhL7fHQMR8sr_JL/_ssgManifest.js +0 -1
  36. package/dist/web-dist/_not-found/__next._full.txt +0 -17
  37. package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +0 -23
  38. package/dist/web-dist/_not-found/__next._tree.txt +0 -3
  39. package/dist/web-dist/_not-found/index.html +0 -1
  40. package/dist/web-dist/_not-found/index.txt +0 -17
  41. package/dist/web-dist/chat/__next._full.txt +0 -18
  42. package/dist/web-dist/chat/__next._tree.txt +0 -3
  43. package/dist/web-dist/chat/__next.chat.__PAGE__.txt +0 -24
  44. package/dist/web-dist/chat/index.html +0 -1
  45. package/dist/web-dist/chat/index.txt +0 -18
  46. package/dist/web-dist/favicon.ico +0 -0
  47. package/dist/web-dist/index.html +0 -1
  48. package/dist/web-dist/index.txt +0 -31
  49. package/dist/web-dist/live2d/live2dcubismcore.min.js +0 -9
  50. package/dist/web-dist/operator/__next._full.txt +0 -18
  51. package/dist/web-dist/operator/__next._tree.txt +0 -3
  52. package/dist/web-dist/operator/__next.operator.__PAGE__.txt +0 -24
  53. package/dist/web-dist/operator/index.html +0 -1
  54. package/dist/web-dist/operator/index.txt +0 -18
@@ -5,7 +5,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
5
5
  {
6
6
  name: '@siduri-x/behavior',
7
7
  organType: 'behavior',
8
- version: '1.0.5',
8
+ version: '1.0.6',
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.3',
69
+ version: '1.0.5',
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.3',
275
+ version: '1.0.6',
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.2',
327
+ version: '1.0.3',
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',
@@ -460,5 +460,33 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
460
460
  ],
461
461
  database: null,
462
462
  healthCheck: null
463
+ },
464
+ {
465
+ name: '@siduri-x/mouth',
466
+ organType: 'mouth',
467
+ version: '1.0.0',
468
+ displayName: 'Mouth (Communication & UI Delivery)',
469
+ description: 'Decoupled output delivery organ formatting cognitive decisions for web interfaces',
470
+ entrypoint: './dist/index.js',
471
+ factory: 'DefaultMouthOrgan',
472
+ configKey: 'mouth',
473
+ configSchema: {
474
+ type: 'object',
475
+ properties: {
476
+ defaultMedium: {
477
+ type: 'string',
478
+ enum: ['web'],
479
+ default: 'web'
480
+ },
481
+ maxTextLength: {
482
+ type: 'number',
483
+ default: 8000
484
+ }
485
+ }
486
+ },
487
+ environment: [],
488
+ services: [],
489
+ database: null,
490
+ healthCheck: null
463
491
  }
464
492
  ];
@@ -12,20 +12,26 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
12
12
  const repoRoot = node_path_1.default.resolve(__dirname, '../..');
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
+ const getPkgVer = (dir) => JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.resolve(repoRoot, dir, 'package.json'), 'utf8')).version;
15
16
  const ALL_CANONICAL_PACKAGES = [
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
- { 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.5.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.5.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.7.tgz', isOrgan: false },
17
+ { filter: '@siduri-x/core', tarName: `siduri-x-core-${getPkgVer('packages/core')}.tgz`, isOrgan: false },
18
+ { filter: '@siduri-x/brain', tarName: `siduri-x-brain-${getPkgVer('packages/organs/brain')}.tgz`, isOrgan: true },
19
+ { filter: '@siduri-x/memory', tarName: `siduri-x-memory-${getPkgVer('packages/organs/memory')}.tgz`, isOrgan: true },
20
+ { filter: '@siduri-x/knowledge', tarName: `siduri-x-knowledge-${getPkgVer('packages/organs/knowledge')}.tgz`, isOrgan: true },
21
+ { filter: '@siduri-x/behavior', tarName: `siduri-x-behavior-${getPkgVer('packages/organs/behavior')}.tgz`, isOrgan: true },
22
+ { filter: '@siduri-x/ear', tarName: `siduri-x-ear-${getPkgVer('packages/organs/ear')}.tgz`, isOrgan: true },
23
+ { filter: '@siduri-x/vision', tarName: `siduri-x-vision-${getPkgVer('packages/organs/vision')}.tgz`, isOrgan: true },
24
+ { filter: '@siduri-x/hands', tarName: `siduri-x-hands-${getPkgVer('packages/organs/hands')}.tgz`, isOrgan: true },
25
+ { filter: '@siduri-x/body', tarName: `siduri-x-body-${getPkgVer('packages/organs/body')}.tgz`, isOrgan: true },
26
+ { filter: '@siduri-x/voice', tarName: `siduri-x-voice-${getPkgVer('packages/organs/voice')}.tgz`, isOrgan: true },
27
+ { filter: '@siduri-x/observation', tarName: `siduri-x-observation-${getPkgVer('packages/organs/observation')}.tgz`, isOrgan: true },
28
+ { filter: '@siduri-x/mouth', tarName: `siduri-x-mouth-${getPkgVer('packages/organs/mouth')}.tgz`, isOrgan: true },
29
+ { filter: '@vxnus/siduri', tarName: `vxnus-siduri-${getPkgVer('cli')}.tgz`, isOrgan: false },
28
30
  ];
31
+ const getTarPath = (pkgName) => {
32
+ const pkg = ALL_CANONICAL_PACKAGES.find(p => p.filter === pkgName);
33
+ return node_path_1.default.join(tempPackDir, pkg.tarName);
34
+ };
29
35
  beforeAll(() => {
30
36
  // 1. Prepare clean directories
31
37
  if (node_fs_1.default.existsSync(tempPackDir))
@@ -35,7 +41,7 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
35
41
  node_fs_1.default.mkdirSync(tempPackDir, { recursive: true });
36
42
  node_fs_1.default.mkdirSync(cleanMachineRoot, { recursive: true });
37
43
  // 2. Build all packages in repo
38
- (0, node_child_process_1.execSync)('pnpm build', { cwd: repoRoot, stdio: 'pipe' });
44
+ (0, node_child_process_1.execSync)("pnpm turbo run build --filter='!web'", { cwd: repoRoot, stdio: 'pipe' });
39
45
  // 3. Pack each canonical package into tempPackDir
40
46
  for (const pkg of ALL_CANONICAL_PACKAGES) {
41
47
  (0, node_child_process_1.execSync)(`pnpm --filter ${pkg.filter} pack --pack-destination ${tempPackDir}`, {
@@ -51,7 +57,7 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
51
57
  node_fs_1.default.rmSync(cleanMachineRoot, { recursive: true, force: true });
52
58
  });
53
59
  describe('Phase 5A: Package Artifact Verification', () => {
54
- test('all 12 packages produce valid tarballs', () => {
60
+ test('all 13 packages produce valid tarballs', () => {
55
61
  for (const pkg of ALL_CANONICAL_PACKAGES) {
56
62
  const tarPath = node_path_1.default.join(tempPackDir, pkg.tarName);
57
63
  expect(node_fs_1.default.existsSync(tarPath)).toBe(true);
@@ -86,7 +92,7 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
86
92
  }
87
93
  });
88
94
  test('memory organ tarball packages migrations/001_initial_schema.sql', () => {
89
- const memoryTarPath = node_path_1.default.join(tempPackDir, 'siduri-x-memory-1.0.3.tgz');
95
+ const memoryTarPath = node_path_1.default.join(tempPackDir, 'siduri-x-memory-1.0.4.tgz');
90
96
  const listing = (0, node_child_process_1.execSync)(`tar -tzf ${memoryTarPath}`, { encoding: 'utf8' });
91
97
  expect(listing).toContain('package/migrations/001_initial_schema.sql');
92
98
  });
@@ -106,8 +112,8 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
106
112
  // Write instance files referencing packed tarballs directly for true clean machine install
107
113
  const pkgObj = JSON.parse(files['package.json']);
108
114
  pkgObj.dependencies = {
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')}`,
115
+ '@siduri-x/core': `file:${getTarPath('@siduri-x/core')}`,
116
+ '@siduri-x/brain': `file:${getTarPath('@siduri-x/brain')}`,
111
117
  };
112
118
  node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'package.json'), JSON.stringify(pkgObj, null, 2) + '\n');
113
119
  node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'siduri.config.json'), files['siduri.config.json']);
@@ -154,9 +160,9 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
154
160
  });
155
161
  const pkgObj = JSON.parse(files['package.json']);
156
162
  pkgObj.dependencies = {
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
- '@siduri-x/hands': `file:${node_path_1.default.join(tempPackDir, 'siduri-x-hands-1.0.2.tgz')}`,
163
+ '@siduri-x/core': `file:${getTarPath('@siduri-x/core')}`,
164
+ '@siduri-x/brain': `file:${getTarPath('@siduri-x/brain')}`,
165
+ '@siduri-x/hands': `file:${getTarPath('@siduri-x/hands')}`,
160
166
  };
161
167
  node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'package.json'), JSON.stringify(pkgObj, null, 2) + '\n');
162
168
  node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'siduri.config.json'), files['siduri.config.json']);
@@ -193,9 +199,9 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
193
199
  });
194
200
  const pkgObj = JSON.parse(files['package.json']);
195
201
  pkgObj.dependencies = {
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')}`,
202
+ '@siduri-x/core': `file:${getTarPath('@siduri-x/core')}`,
203
+ '@siduri-x/brain': `file:${getTarPath('@siduri-x/brain')}`,
204
+ '@siduri-x/memory': `file:${getTarPath('@siduri-x/memory')}`,
199
205
  };
200
206
  node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'package.json'), JSON.stringify(pkgObj, null, 2) + '\n');
201
207
  node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, 'siduri.config.json'), files['siduri.config.json']);
@@ -13,6 +13,7 @@ export * from './behavior';
13
13
  export * from './vision';
14
14
  export * from './ear';
15
15
  export * from './observation';
16
+ export * from './mouth';
16
17
  export interface ConfigureOrganOptions {
17
18
  brainOptions?: BrainConfiguratorOptions;
18
19
  knowledgeOptions?: KnowledgeConfiguratorOptions;
@@ -25,6 +25,7 @@ const behavior_1 = require("./behavior");
25
25
  const vision_1 = require("./vision");
26
26
  const ear_1 = require("./ear");
27
27
  const observation_1 = require("./observation");
28
+ const mouth_1 = require("./mouth");
28
29
  __exportStar(require("./types"), exports);
29
30
  __exportStar(require("./brain"), exports);
30
31
  __exportStar(require("./knowledge"), exports);
@@ -36,6 +37,7 @@ __exportStar(require("./behavior"), exports);
36
37
  __exportStar(require("./vision"), exports);
37
38
  __exportStar(require("./ear"), exports);
38
39
  __exportStar(require("./observation"), exports);
40
+ __exportStar(require("./mouth"), exports);
39
41
  async function configureOrgan(manifest, context, options = {}) {
40
42
  const ctx = {
41
43
  companionName: context.companionName,
@@ -63,6 +65,8 @@ async function configureOrgan(manifest, context, options = {}) {
63
65
  return (0, ear_1.configureEar)(ctx);
64
66
  case 'observation':
65
67
  return (0, observation_1.configureObservation)(ctx);
68
+ case 'mouth':
69
+ return (0, mouth_1.configureMouth)(ctx);
66
70
  default:
67
71
  return {
68
72
  config: ctx.existingConfig || {},
@@ -0,0 +1,2 @@
1
+ import { OrganConfiguratorContext, OrganConfigurationResult } from './types';
2
+ export declare function configureMouth(_context: OrganConfiguratorContext): Promise<OrganConfigurationResult>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configureMouth = configureMouth;
4
+ async function configureMouth(_context) {
5
+ return {
6
+ config: {
7
+ defaultMedium: 'web',
8
+ maxTextLength: 8000,
9
+ },
10
+ summary: {
11
+ 'Default Medium': 'Web',
12
+ 'Max Text Length': '8000 chars',
13
+ },
14
+ };
15
+ }
@@ -13,13 +13,25 @@ async function configureVoice(_context) {
13
13
  message: 'Voice provider?',
14
14
  choices: [
15
15
  {
16
- name: 'RVC (Custom user-owned voice model via .pth / .index weights)',
17
- value: 'rvc',
16
+ name: 'Edge-TTS (Zero-setup cloud TTS, no downloads/no Python required)',
17
+ value: 'edge-tts',
18
18
  },
19
19
  {
20
20
  name: 'VOICEVOX (Standard VOICEVOX stock character voice banks)',
21
21
  value: 'voicevox',
22
22
  },
23
+ {
24
+ name: 'Kokoro TTS (Fast lightweight neural TTS via HTTP/CLI)',
25
+ value: 'kokoro',
26
+ },
27
+ {
28
+ name: 'Piper TTS (Fast local neural TTS via HTTP/CLI)',
29
+ value: 'piper',
30
+ },
31
+ {
32
+ name: 'RVC (Custom user-owned voice model via .pth / .index weights)',
33
+ value: 'rvc',
34
+ },
23
35
  {
24
36
  name: 'None (Disable voice synthesis)',
25
37
  value: 'none',
@@ -32,6 +44,108 @@ async function configureVoice(_context) {
32
44
  summary: { Provider: 'None (Voice disabled)' },
33
45
  };
34
46
  }
47
+ if (provider === 'edge-tts') {
48
+ const edgeAnswers = await inquirer_1.default.prompt([
49
+ {
50
+ type: 'input',
51
+ name: 'voice',
52
+ message: 'Edge-TTS Voice Identifier (e.g. ja-JP-NanamiNeural, en-US-AriaNeural):',
53
+ default: 'ja-JP-NanamiNeural',
54
+ },
55
+ ]);
56
+ const voice = edgeAnswers.voice.trim() || 'ja-JP-NanamiNeural';
57
+ return {
58
+ config: {
59
+ provider: 'edge-tts',
60
+ voice,
61
+ maxQueueDepth: 50,
62
+ },
63
+ summary: {
64
+ Provider: 'Edge-TTS (Cloud Neural)',
65
+ Voice: voice,
66
+ },
67
+ };
68
+ }
69
+ if (provider === 'kokoro') {
70
+ const kokoroAnswers = await inquirer_1.default.prompt([
71
+ {
72
+ type: 'input',
73
+ name: 'baseUrl',
74
+ message: 'Kokoro HTTP Base URL (leave empty if using local kokoro-tts CLI):',
75
+ default: 'http://localhost:8880',
76
+ },
77
+ {
78
+ type: 'input',
79
+ name: 'voice',
80
+ message: 'Kokoro Voice Name (e.g. af_bella, af_heart):',
81
+ default: 'af_bella',
82
+ },
83
+ {
84
+ type: 'input',
85
+ name: 'speed',
86
+ message: 'Speech Speed multiplier (e.g. 1.0):',
87
+ default: '1.0',
88
+ },
89
+ ]);
90
+ const baseUrl = kokoroAnswers.baseUrl.trim() || undefined;
91
+ const voice = kokoroAnswers.voice.trim() || 'af_bella';
92
+ const speed = parseFloat(kokoroAnswers.speed) || 1.0;
93
+ return {
94
+ config: {
95
+ provider: 'kokoro',
96
+ baseUrl,
97
+ voice,
98
+ speed,
99
+ maxQueueDepth: 50,
100
+ },
101
+ summary: {
102
+ Provider: 'Kokoro TTS (Local / Server)',
103
+ Mode: baseUrl ? `HTTP (${baseUrl})` : 'CLI (kokoro-tts)',
104
+ Voice: voice,
105
+ Speed: speed,
106
+ },
107
+ };
108
+ }
109
+ if (provider === 'piper') {
110
+ const piperAnswers = await inquirer_1.default.prompt([
111
+ {
112
+ type: 'input',
113
+ name: 'baseUrl',
114
+ message: 'Piper HTTP Base URL (leave empty if using local piper CLI):',
115
+ default: 'http://localhost:5000',
116
+ },
117
+ {
118
+ type: 'input',
119
+ name: 'model',
120
+ message: 'Piper Model Path or Name (.onnx, optional):',
121
+ default: '',
122
+ },
123
+ {
124
+ type: 'input',
125
+ name: 'speakerId',
126
+ message: 'Piper Speaker ID (optional):',
127
+ default: '',
128
+ },
129
+ ]);
130
+ const baseUrl = piperAnswers.baseUrl.trim() || undefined;
131
+ const model = piperAnswers.model.trim() || undefined;
132
+ const speakerId = piperAnswers.speakerId.trim() ? parseInt(piperAnswers.speakerId.trim(), 10) : undefined;
133
+ return {
134
+ config: {
135
+ provider: 'piper',
136
+ baseUrl,
137
+ model,
138
+ speakerId,
139
+ maxQueueDepth: 50,
140
+ },
141
+ summary: {
142
+ Provider: 'Piper TTS (Local / Server)',
143
+ Mode: baseUrl ? `HTTP (${baseUrl})` : 'CLI (piper)',
144
+ Model: model || 'default',
145
+ 'Speaker ID': speakerId !== undefined ? speakerId : 'default',
146
+ },
147
+ };
148
+ }
35
149
  if (provider === 'rvc') {
36
150
  const rvcAnswers = await inquirer_1.default.prompt([
37
151
  {
@@ -215,6 +215,38 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
215
215
  expect(result.config.rvc.pitchShift).toBe(12);
216
216
  expect(result.summary?.['Base TTS']).toBe('edge-tts');
217
217
  });
218
+ test('Voice configurator configures standalone Edge-TTS', async () => {
219
+ inquirer_1.default.prompt
220
+ .mockResolvedValueOnce({ provider: 'edge-tts' })
221
+ .mockResolvedValueOnce({ voice: 'en-US-AriaNeural' });
222
+ const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
223
+ expect(result.config.provider).toBe('edge-tts');
224
+ expect(result.config.voice).toBe('en-US-AriaNeural');
225
+ expect(result.summary?.Provider).toBe('Edge-TTS (Cloud Neural)');
226
+ expect(result.summary?.Voice).toBe('en-US-AriaNeural');
227
+ });
228
+ test('Voice configurator configures standalone Kokoro TTS', async () => {
229
+ inquirer_1.default.prompt
230
+ .mockResolvedValueOnce({ provider: 'kokoro' })
231
+ .mockResolvedValueOnce({ baseUrl: 'http://localhost:8880', voice: 'af_heart', speed: '1.2' });
232
+ const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
233
+ expect(result.config.provider).toBe('kokoro');
234
+ expect(result.config.baseUrl).toBe('http://localhost:8880');
235
+ expect(result.config.voice).toBe('af_heart');
236
+ expect(result.config.speed).toBe(1.2);
237
+ expect(result.summary?.Provider).toBe('Kokoro TTS (Local / Server)');
238
+ });
239
+ test('Voice configurator configures standalone Piper TTS', async () => {
240
+ inquirer_1.default.prompt
241
+ .mockResolvedValueOnce({ provider: 'piper' })
242
+ .mockResolvedValueOnce({ baseUrl: 'http://localhost:5000', model: 'en_US-lessac', speakerId: '2' });
243
+ const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
244
+ expect(result.config.provider).toBe('piper');
245
+ expect(result.config.baseUrl).toBe('http://localhost:5000');
246
+ expect(result.config.model).toBe('en_US-lessac');
247
+ expect(result.config.speakerId).toBe(2);
248
+ expect(result.summary?.Provider).toBe('Piper TTS (Local / Server)');
249
+ });
218
250
  test('Body configurator configures Live2D, custom model path, and expression', async () => {
219
251
  inquirer_1.default.prompt
220
252
  .mockResolvedValueOnce({ provider: 'live2d' })
@@ -8,10 +8,10 @@ const discovery_1 = require("./discovery");
8
8
  const generator_1 = require("./generator");
9
9
  describe('Discovery & Dynamic Composition System Tests (Phase 3)', () => {
10
10
  const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
11
- test('Registry successfully discovers all 10 organ packages from workspace', () => {
11
+ test('Registry successfully discovers all 11 organ packages from workspace', () => {
12
12
  const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
13
13
  const manifests = registry.getAll();
14
- expect(manifests.length).toBe(10);
14
+ expect(manifests.length).toBe(11);
15
15
  const organTypes = registry.getAvailableOrganTypes().sort();
16
16
  expect(organTypes).toEqual([
17
17
  'behavior',
@@ -21,6 +21,7 @@ describe('Discovery & Dynamic Composition System Tests (Phase 3)', () => {
21
21
  'hands',
22
22
  'knowledge',
23
23
  'memory',
24
+ 'mouth',
24
25
  'observation',
25
26
  'vision',
26
27
  'voice',
@@ -60,8 +61,9 @@ describe('Discovery & Dynamic Composition System Tests (Phase 3)', () => {
60
61
  test('Fallback to builtin organ manifests when scanning non-existent search roots', () => {
61
62
  const registry = discovery_1.OrganRegistry.discover(['/non-existent-directory/empty']);
62
63
  const manifests = registry.getAll();
63
- expect(manifests.length).toBe(10);
64
+ expect(manifests.length).toBe(11);
64
65
  expect(registry.get('brain')).toBeDefined();
65
66
  expect(registry.get('memory')).toBeDefined();
67
+ expect(registry.get('mouth')).toBeDefined();
66
68
  });
67
69
  });
package/dist/generator.js CHANGED
@@ -63,16 +63,21 @@ function getDefaultConfigForManifest(manifest) {
63
63
  config.provider = config.provider || 'openrouter';
64
64
  config.model = config.model || 'gpt-4-vision';
65
65
  }
66
+ else if (manifest.organType === 'mouth') {
67
+ config.defaultMedium = 'web';
68
+ config.maxTextLength = config.maxTextLength || 8000;
69
+ }
66
70
  return config;
67
71
  }
68
72
  function generateInstanceFiles(options) {
69
73
  const instanceName = options.name || 'my-siduri';
70
74
  const instanceId = options.id || 'default';
71
- const coreVersion = options.coreVersion || '^1.0.4';
75
+ const coreVersion = options.coreVersion || '^1.0.8';
72
76
  const manifests = options.selectedManifests;
73
77
  const hasMemory = manifests.some((m) => m.organType === 'memory');
74
78
  const hasVoice = manifests.some((m) => m.organType === 'voice');
75
79
  const hasBody = manifests.some((m) => m.organType === 'body');
80
+ const hasMouth = manifests.some((m) => m.organType === 'mouth');
76
81
  const voiceConfig = options.organConfigs?.voice || options.organConfigs?.['@siduri-x/voice'];
77
82
  const isVoicevox = hasVoice && (!voiceConfig || voiceConfig.provider === 'voicevox');
78
83
  const memoryConfig = options.organConfigs?.memory || options.organConfigs?.['@siduri-x/memory'];
@@ -222,6 +227,9 @@ function generateInstanceFiles(options) {
222
227
  instantiationLines.push(`const ${varName} = new ${m.factory}(config.organs.${m.configKey});`);
223
228
  organMapEntries.push(` ${varName},`);
224
229
  }
230
+ if (hasMouth && hasVoice) {
231
+ instantiationLines.push(`if (typeof mouth?.setVoiceOrgan === 'function') mouth.setVoiceOrgan(voice);`);
232
+ }
225
233
  const selectedDisplayNames = manifests.map((m) => m.displayName.split(' ')[0] || m.organType).join(', ');
226
234
  const srcIndexJs = [
227
235
  ...importLines,
@@ -363,7 +371,7 @@ function generateInstanceFiles(options) {
363
371
  ` id: config.id,`,
364
372
  ` companionId: config.id,`,
365
373
  ` message: payload.message || payload.text || '',`,
366
- ` role: payload.role || 'OWNER',`,
374
+ ` context: payload.context,`,
367
375
  ` history: Array.isArray(payload.history) ? payload.history : [],`,
368
376
  ` });`,
369
377
  '',
@@ -377,6 +385,122 @@ function generateInstanceFiles(options) {
377
385
  ` return;`,
378
386
  ` }`,
379
387
  '',
388
+ ` // API: Real-time SSE streaming (Mouth transport)`,
389
+ ` if ((pathname === '/chat/stream' || pathname === '/api/chat/stream') && req.method === 'POST') {`,
390
+ ` let body = '';`,
391
+ ` req.on('data', (chunk) => { body += chunk; });`,
392
+ ` req.on('end', async () => {`,
393
+ ` res.writeHead(200, {`,
394
+ ` 'Content-Type': 'text/event-stream',`,
395
+ ` 'Cache-Control': 'no-cache, no-transform',`,
396
+ ` 'Connection': 'keep-alive',`,
397
+ ` });`,
398
+ ` const abortController = new AbortController();`,
399
+ ` const onClose = () => {`,
400
+ ` abortController.abort('client_disconnect');`,
401
+ ` if (typeof runtime.interruptMouth === 'function') {`,
402
+ ` runtime.interruptMouth('client_disconnect');`,
403
+ ` }`,
404
+ ` };`,
405
+ ` req.on('close', onClose);`,
406
+ '',
407
+ ` try {`,
408
+ ` const payload = JSON.parse(body || '{}');`,
409
+ ` const response = await dispatchCompanionChat(runtime, {`,
410
+ ` id: config.id,`,
411
+ ` companionId: config.id,`,
412
+ ` message: payload.message || payload.text || '',`,
413
+ ` context: payload.context,`,
414
+ ` history: Array.isArray(payload.history) ? payload.history : [],`,
415
+ ` medium: 'web',`,
416
+ ` signal: abortController.signal,`,
417
+ ` });`,
418
+ '',
419
+ ` res.write(\`event: staged\\ndata: \${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status })}\\n\\n\`);`,
420
+ '',
421
+ ` const avatarEvent = response.metadata?.events?.find(`,
422
+ ` (e) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval === 'APPROVED' || !e.approval)`,
423
+ ` );`,
424
+ ` if (avatarEvent) {`,
425
+ ` res.write(\`event: avatar\\ndata: \${JSON.stringify(avatarEvent)}\\n\\n\`);`,
426
+ ` }`,
427
+ '',
428
+ ` const speechText = response.delivery?.text || response.response?.subtitle_en || response.response?.spoken_ja || '';`,
429
+ ` const utterance = {`,
430
+ ` utteranceId: response.response_id || 'utt-stream',`,
431
+ ` companionId: config.id,`,
432
+ ` responseId: response.response_id,`,
433
+ ` correlationId: response.correlation_id,`,
434
+ ` text: speechText,`,
435
+ ` medium: 'web',`,
436
+ ` expression: avatarEvent?.expression,`,
437
+ ` action: avatarEvent?.action,`,
438
+ ` signal: abortController.signal,`,
439
+ ` };`,
440
+ '',
441
+ ` if (runtime.mouth && typeof runtime.mouth.stream === 'function') {`,
442
+ ` for await (const chunk of runtime.mouth.stream(utterance)) {`,
443
+ ` if (abortController.signal.aborted) {`,
444
+ ` res.write(\`event: chunk\\ndata: \${JSON.stringify({ ...chunk, interrupted: true })}\\n\\n\`);`,
445
+ ` break;`,
446
+ ` }`,
447
+ ` res.write(\`event: chunk\\ndata: \${JSON.stringify(chunk)}\\n\\n\`);`,
448
+ ` }`,
449
+ ` } else {`,
450
+ ` res.write(\`event: chunk\\ndata: \${JSON.stringify({ utteranceId: utterance.utteranceId, index: 1, deltaText: speechText, isComplete: true, medium: 'web' })}\\n\\n\`);`,
451
+ ` }`,
452
+ '',
453
+ ` res.write(\`event: done\\ndata: \${JSON.stringify(response)}\\n\\n\`);`,
454
+ ` res.end();`,
455
+ ` } catch (err) {`,
456
+ ` if (abortController.signal.aborted) {`,
457
+ ` res.write(\`event: interrupted\\ndata: \${JSON.stringify({ reason: abortController.signal.reason })}\\n\\n\`);`,
458
+ ` } else {`,
459
+ ` res.write(\`event: error\\ndata: \${JSON.stringify({ error: err.message })}\\n\\n\`);`,
460
+ ` }`,
461
+ ` res.end();`,
462
+ ` } finally {`,
463
+ ` req.removeListener('close', onClose);`,
464
+ ` }`,
465
+ ` });`,
466
+ ` return;`,
467
+ ` }`,
468
+ '',
469
+ ` // API: Barge-in interruption`,
470
+ ` if ((pathname === '/chat/interrupt' || pathname === '/mouth/interrupt' || pathname === '/api/chat/interrupt') && req.method === 'POST') {`,
471
+ ` let body = '';`,
472
+ ` req.on('data', (chunk) => { body += chunk; });`,
473
+ ` req.on('end', () => {`,
474
+ ` try {`,
475
+ ` const payload = JSON.parse(body || '{}');`,
476
+ ` const reason = payload.reason || 'user_barge_in';`,
477
+ ` if (typeof runtime.interruptMouth === 'function') {`,
478
+ ` runtime.interruptMouth(reason);`,
479
+ ` }`,
480
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
481
+ ` res.end(JSON.stringify({ success: true, interrupted: true, reason }));`,
482
+ ` } catch (e) {`,
483
+ ` res.writeHead(500, { 'Content-Type': 'application/json' });`,
484
+ ` res.end(JSON.stringify({ error: e.message }));`,
485
+ ` }`,
486
+ ` });`,
487
+ ` return;`,
488
+ ` }`,
489
+ '',
490
+ ` // API: Mouth channels & health`,
491
+ ` if ((pathname === '/mouth/health' || pathname === '/api/mouth/health') && req.method === 'GET') {`,
492
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
493
+ ` res.end(JSON.stringify({ provider: 'siduri-mouth', configured: Boolean(runtime.mouth) }));`,
494
+ ` return;`,
495
+ ` }`,
496
+ '',
497
+ ` if ((pathname === '/mouth/channels' || pathname === '/api/mouth/channels') && req.method === 'GET') {`,
498
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
499
+ ` const channels = typeof runtime.mouth?.getRegisteredChannels === 'function' ? runtime.mouth.getRegisteredChannels() : [];`,
500
+ ` res.end(JSON.stringify({ channels }));`,
501
+ ` return;`,
502
+ ` }`,
503
+ '',
380
504
  ` // API: Model Catalog Discovery (Body & Voice)`,
381
505
  ` if (pathname === '/api/models/body' && req.method === 'GET') {`,
382
506
  ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
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.7";
3
+ export declare const CLI_VERSION = "0.1.10";
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.7';
29
+ exports.CLI_VERSION = '0.1.10';
30
30
  exports.colors = {
31
31
  cyan: '\u001b[36m',
32
32
  dim: '\u001b[2m',