@vxnus/siduri 0.1.11 → 2.0.1
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/README.md +7 -7
- package/dist/builtin-manifests.js +42 -51
- package/dist/clean-machine-e2e.test.js +25 -101
- package/dist/clean-machine-smoke.test.d.ts +1 -0
- package/dist/clean-machine-smoke.test.js +166 -0
- package/dist/configurators/knowledge.js +134 -46
- package/dist/configurators/memory.js +7 -25
- package/dist/configurators.test.js +12 -13
- package/dist/db.d.ts +0 -12
- package/dist/db.js +7 -144
- package/dist/discovery.js +5 -7
- package/dist/discovery.test.js +5 -4
- package/dist/doctor-db.test.js +2 -91
- package/dist/doctor.js +53 -41
- package/dist/generator.js +91 -15
- package/dist/generator.test.js +10 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +37 -32
- package/dist/manifest.d.ts +1 -1
- package/dist/providers/openrouter.js +1 -1
- package/dist/release-check.js +5 -9
- package/dist/web-dist/404/index.html +1 -1
- package/dist/web-dist/404.html +1 -1
- package/dist/web-dist/__next.__PAGE__.txt +12 -12
- package/dist/web-dist/__next._full.txt +12 -12
- package/dist/web-dist/__next._tree.txt +1 -1
- package/dist/web-dist/_next/static/chunks/0jgw-jf26m0ui.js +22 -0
- package/dist/web-dist/_next/static/chunks/0tdcnq1-n6kof.js +31 -0
- package/dist/web-dist/_next/static/chunks/12vwhiuvdy_i7.js +1 -0
- package/dist/web-dist/_next/static/chunks/{2_duynll0m51m.js → 15zsrb5462n_-.js} +2 -2
- package/dist/web-dist/_next/static/chunks/2dsfgoa225hpl.js +1 -0
- package/dist/web-dist/_next/static/chunks/2mno6yfdoee0x.js +1 -0
- package/dist/web-dist/_next/static/chunks/30a48dk5bjyhj.js +1 -0
- package/dist/web-dist/_next/static/chunks/360luxuz2x8y_.js +1 -0
- package/dist/web-dist/_next/static/chunks/turbopack-2hpygfb68u21h.js +1 -0
- package/dist/web-dist/_not-found/__next._full.txt +8 -8
- package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +7 -7
- package/dist/web-dist/_not-found/__next._tree.txt +1 -1
- package/dist/web-dist/_not-found/index.html +1 -1
- package/dist/web-dist/_not-found/index.txt +8 -8
- package/dist/web-dist/chat/__next._full.txt +9 -9
- package/dist/web-dist/chat/__next._tree.txt +1 -1
- package/dist/web-dist/chat/__next.chat.__PAGE__.txt +8 -8
- package/dist/web-dist/chat/index.html +1 -1
- package/dist/web-dist/chat/index.txt +9 -9
- package/dist/web-dist/index.html +1 -1
- package/dist/web-dist/index.txt +12 -12
- package/dist/web-dist/operator/__next._full.txt +9 -9
- package/dist/web-dist/operator/__next._tree.txt +1 -1
- package/dist/web-dist/operator/__next.operator.__PAGE__.txt +8 -8
- package/dist/web-dist/operator/index.html +1 -1
- package/dist/web-dist/operator/index.txt +9 -9
- package/dist/web-template.js +1 -1
- package/package.json +10 -12
- package/dist/web-dist/_next/static/chunks/09y7khbmco_v4.js +0 -1
- package/dist/web-dist/_next/static/chunks/0uwih7sydv413.js +0 -1
- package/dist/web-dist/_next/static/chunks/1_yae15ffa5f6.js +0 -1
- package/dist/web-dist/_next/static/chunks/2kcqevsmv6kh2.js +0 -31
- package/dist/web-dist/_next/static/chunks/2s3d1bfr0l4b0.js +0 -1
- package/dist/web-dist/_next/static/chunks/32z87id9k6i2z.js +0 -22
- package/dist/web-dist/_next/static/chunks/3bx2gs3idjmo1.js +0 -1
- package/dist/web-dist/_next/static/chunks/turbopack-3eipe4c-imn5l.js +0 -1
- /package/dist/web-dist/_next/static/{pWHUixFDDvANZeAf-shqT → lQfIAv021hLk8KVAElf2K}/_buildManifest.js +0 -0
- /package/dist/web-dist/_next/static/{pWHUixFDDvANZeAf-shqT → lQfIAv021hLk8KVAElf2K}/_clientMiddlewareManifest.js +0 -0
- /package/dist/web-dist/_next/static/{pWHUixFDDvANZeAf-shqT → lQfIAv021hLk8KVAElf2K}/_ssgManifest.js +0 -0
package/dist/doctor-db.test.js
CHANGED
|
@@ -11,10 +11,9 @@ const generator_1 = require("./generator");
|
|
|
11
11
|
const discovery_1 = require("./discovery");
|
|
12
12
|
describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
13
13
|
const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
|
|
14
|
-
const registry = discovery_1.OrganRegistry.discover(
|
|
14
|
+
const registry = discovery_1.OrganRegistry.discover();
|
|
15
15
|
const brain = registry.get('brain');
|
|
16
16
|
const hands = registry.get('hands');
|
|
17
|
-
const memory = registry.get('memory');
|
|
18
17
|
const testTempRoot = node_path_1.default.resolve(__dirname, '../temp-phase4-test');
|
|
19
18
|
beforeAll(() => {
|
|
20
19
|
if (node_fs_1.default.existsSync(testTempRoot)) {
|
|
@@ -27,14 +26,6 @@ describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
|
27
26
|
node_fs_1.default.rmSync(testTempRoot, { recursive: true, force: true });
|
|
28
27
|
}
|
|
29
28
|
});
|
|
30
|
-
test('redactDatabaseUrl redacts password and sensitive credentials', () => {
|
|
31
|
-
const raw = 'postgresql://myuser:supersecretpassword@db.example.com:5432/siduri_db';
|
|
32
|
-
const redacted = (0, db_1.redactDatabaseUrl)(raw);
|
|
33
|
-
expect(redacted).not.toContain('supersecretpassword');
|
|
34
|
-
expect(redacted).toContain('***');
|
|
35
|
-
expect(redacted).toContain('myuser');
|
|
36
|
-
expect(redacted).toContain('db.example.com:5432/siduri_db');
|
|
37
|
-
});
|
|
38
29
|
test('Test Matrix A: Brain only instance doctor and db push', async () => {
|
|
39
30
|
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-only');
|
|
40
31
|
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
@@ -49,93 +40,13 @@ describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
|
49
40
|
node_fs_1.default.writeFileSync(fullPath, content);
|
|
50
41
|
}
|
|
51
42
|
}
|
|
52
|
-
// 1. Doctor without OPENROUTER_API_KEY -> FAIL (Brain Health Probe fails)
|
|
53
|
-
const doctorFail = await (0, doctor_1.runDoctor)({ projectDir: instanceDir, env: { OPENROUTER_API_KEY: '' } });
|
|
54
|
-
expect(doctorFail.passed).toBe(false);
|
|
55
|
-
expect(doctorFail.results.some((r) => r.category === 'Health Probe' && r.status === 'FAIL')).toBe(true);
|
|
56
|
-
// Database check must be SKIPPED
|
|
57
|
-
expect(doctorFail.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
|
|
58
|
-
// 2. Doctor with OPENROUTER_API_KEY -> PASS
|
|
59
43
|
const doctorPass = await (0, doctor_1.runDoctor)({
|
|
60
44
|
projectDir: instanceDir,
|
|
61
45
|
env: { OPENROUTER_API_KEY: 'test-key-123' },
|
|
62
46
|
});
|
|
63
47
|
expect(doctorPass.passed).toBe(true);
|
|
64
|
-
// 3. db push -> NOOP ("No database migrations are required...")
|
|
65
|
-
const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
|
|
66
|
-
expect(dbResult.status).toBe('NOOP');
|
|
67
|
-
expect(dbResult.message).toContain('No database migrations are required');
|
|
68
|
-
});
|
|
69
|
-
test('Test Matrix B: Brain + Hands instance doctor and db push', async () => {
|
|
70
|
-
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-hands');
|
|
71
|
-
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
72
|
-
const files = (0, generator_1.generateInstanceFiles)({
|
|
73
|
-
name: 'BrainHandsInstance',
|
|
74
|
-
selectedManifests: [brain, hands],
|
|
75
|
-
});
|
|
76
|
-
for (const [name, content] of Object.entries(files)) {
|
|
77
|
-
if (typeof content === 'string') {
|
|
78
|
-
const fullPath = node_path_1.default.join(instanceDir, name);
|
|
79
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
80
|
-
node_fs_1.default.writeFileSync(fullPath, content);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Doctor checks both keys
|
|
84
|
-
const doctorRes = await (0, doctor_1.runDoctor)({
|
|
85
|
-
projectDir: instanceDir,
|
|
86
|
-
env: {
|
|
87
|
-
OPENROUTER_API_KEY: 'test-openrouter-key',
|
|
88
|
-
ACTION_POLICY_SECRET: 'test-policy-secret',
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
expect(doctorRes.passed).toBe(true);
|
|
92
|
-
expect(doctorRes.results.some((r) => r.name === 'OPENROUTER_API_KEY' && r.status === 'PASS')).toBe(true);
|
|
93
|
-
expect(doctorRes.results.some((r) => r.name === 'ACTION_POLICY_SECRET' && r.status === 'PASS')).toBe(true);
|
|
94
|
-
// Database check must be SKIPPED
|
|
95
|
-
expect(doctorRes.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
|
|
96
|
-
// db push returns NOOP
|
|
97
48
|
const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
|
|
98
49
|
expect(dbResult.status).toBe('NOOP');
|
|
99
|
-
|
|
100
|
-
test('Test Matrix C: Brain + Memory instance requires DATABASE_URL', async () => {
|
|
101
|
-
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-memory');
|
|
102
|
-
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
103
|
-
const files = (0, generator_1.generateInstanceFiles)({
|
|
104
|
-
name: 'BrainMemoryInstance',
|
|
105
|
-
selectedManifests: [brain, memory],
|
|
106
|
-
});
|
|
107
|
-
for (const [name, content] of Object.entries(files)) {
|
|
108
|
-
if (typeof content === 'string') {
|
|
109
|
-
const fullPath = node_path_1.default.join(instanceDir, name);
|
|
110
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
111
|
-
node_fs_1.default.writeFileSync(fullPath, content);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// 1. Doctor fails without DATABASE_URL
|
|
115
|
-
const doctorMissingDb = await (0, doctor_1.runDoctor)({
|
|
116
|
-
projectDir: instanceDir,
|
|
117
|
-
env: { OPENROUTER_API_KEY: 'test-key' },
|
|
118
|
-
});
|
|
119
|
-
expect(doctorMissingDb.passed).toBe(false);
|
|
120
|
-
expect(doctorMissingDb.results.some((r) => r.name === 'DATABASE_URL' && r.status === 'FAIL')).toBe(true);
|
|
121
|
-
// 2. db push fails clearly without DATABASE_URL
|
|
122
|
-
await expect((0, db_1.runDbPush)({ projectDir: instanceDir, env: {} })).rejects.toThrow(/DATABASE_URL is required/);
|
|
123
|
-
});
|
|
124
|
-
test('Test Matrix D: Diagnostics and Error Handling for invalid or missing configs', async () => {
|
|
125
|
-
// 1. Missing siduri.config.json
|
|
126
|
-
const nonExistentDir = node_path_1.default.join(testTempRoot, 'non-existent-dir');
|
|
127
|
-
await expect((0, doctor_1.runDoctor)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
|
|
128
|
-
await expect((0, db_1.runDbPush)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
|
|
129
|
-
// 2. Unreachable PostgreSQL host reported gracefully by doctor
|
|
130
|
-
const instDir = node_path_1.default.join(testTempRoot, 'brain-memory');
|
|
131
|
-
const doctorUnreachable = await (0, doctor_1.runDoctor)({
|
|
132
|
-
projectDir: instDir,
|
|
133
|
-
env: {
|
|
134
|
-
OPENROUTER_API_KEY: 'test-key',
|
|
135
|
-
DATABASE_URL: 'postgresql://postgres:postgres@127.0.0.1:59999/non_existent_db',
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
expect(doctorUnreachable.passed).toBe(false);
|
|
139
|
-
expect(doctorUnreachable.results.some((r) => r.category === 'Database' && r.status === 'FAIL')).toBe(true);
|
|
50
|
+
expect(dbResult.message).toContain('SQLite manages schema natively.');
|
|
140
51
|
});
|
|
141
52
|
});
|
package/dist/doctor.js
CHANGED
|
@@ -7,7 +7,6 @@ exports.runDoctor = runDoctor;
|
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const discovery_1 = require("./discovery");
|
|
10
|
-
const pg_1 = require("pg");
|
|
11
10
|
const schema_validator_1 = require("./schema-validator");
|
|
12
11
|
async function runDoctor(options = {}) {
|
|
13
12
|
const projectDir = options.projectDir ? node_path_1.default.resolve(options.projectDir) : process.cwd();
|
|
@@ -130,61 +129,74 @@ async function runDoctor(options = {}) {
|
|
|
130
129
|
// 3. Check External Services
|
|
131
130
|
for (const m of selectedManifests) {
|
|
132
131
|
for (const service of m.services || []) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
const organConf = configuredOrgansMap[m.configKey] || configuredOrgansMap[m.organType];
|
|
133
|
+
const configuredUrl = organConf?.baseUrl || organConf?.url || effectiveEnv[`${service.name.toUpperCase()}_URL`];
|
|
134
|
+
if (configuredUrl && typeof configuredUrl === 'string' && configuredUrl.startsWith('http')) {
|
|
135
|
+
try {
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const timer = setTimeout(() => controller.abort(), 1000);
|
|
138
|
+
await fetch(configuredUrl, { signal: controller.signal }).catch(() => { });
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
results.push({
|
|
141
|
+
category: 'Services',
|
|
142
|
+
name: `${service.name} (${m.organType})`,
|
|
143
|
+
status: 'PASS',
|
|
144
|
+
organName: m.name,
|
|
145
|
+
message: `Endpoint configured: ${configuredUrl}`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
results.push({
|
|
150
|
+
category: 'Services',
|
|
151
|
+
name: `${service.name} (${m.organType})`,
|
|
152
|
+
status: 'OPTIONAL_MISSING',
|
|
153
|
+
organName: m.name,
|
|
154
|
+
message: `Service at ${configuredUrl} is not responding (optional local service)`,
|
|
155
|
+
remediation: `Start ${service.name} or allow auto-download if applicable.`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
results.push({
|
|
161
|
+
category: 'Services',
|
|
162
|
+
name: `${service.name} (${m.organType})`,
|
|
163
|
+
status: 'PASS',
|
|
164
|
+
organName: m.name,
|
|
165
|
+
message: 'Service requirement declared (managed locally)',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
140
168
|
}
|
|
141
169
|
}
|
|
142
170
|
// 4. Database Check (only if an organ declares database requirement)
|
|
143
171
|
const dbOrgan = selectedManifests.find((m) => m.database !== null && m.database !== undefined);
|
|
144
172
|
if (dbOrgan) {
|
|
145
|
-
|
|
146
|
-
|
|
173
|
+
try {
|
|
174
|
+
const testDbPath = node_path_1.default.join(projectDir, '.doctor-sqlite-test.tmp');
|
|
175
|
+
node_fs_1.default.writeFileSync(testDbPath, '');
|
|
176
|
+
node_fs_1.default.unlinkSync(testDbPath);
|
|
147
177
|
results.push({
|
|
148
178
|
category: 'Database',
|
|
149
|
-
name: '
|
|
150
|
-
status: '
|
|
179
|
+
name: 'SQLite Database',
|
|
180
|
+
status: 'PASS',
|
|
151
181
|
organName: dbOrgan.name,
|
|
152
|
-
message: '
|
|
153
|
-
remediation: 'Provide DATABASE_URL in .env (e.g. postgresql://postgres:postgres@localhost:5432/siduri)',
|
|
182
|
+
message: 'Local storage directory writeable; SQLite operational',
|
|
154
183
|
});
|
|
155
184
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
name: 'PostgreSQL Connection',
|
|
166
|
-
status: 'PASS',
|
|
167
|
-
organName: dbOrgan.name,
|
|
168
|
-
message: 'Connection successful',
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
await pool.end().catch(() => { });
|
|
173
|
-
results.push({
|
|
174
|
-
category: 'Database',
|
|
175
|
-
name: 'PostgreSQL Connection',
|
|
176
|
-
status: 'FAIL',
|
|
177
|
-
organName: dbOrgan.name,
|
|
178
|
-
message: `Connection failed: ${err.message}`,
|
|
179
|
-
remediation: 'Check database host availability, port, credentials, and network accessibility.',
|
|
180
|
-
});
|
|
181
|
-
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
results.push({
|
|
187
|
+
category: 'Database',
|
|
188
|
+
name: 'SQLite Database',
|
|
189
|
+
status: 'FAIL',
|
|
190
|
+
organName: dbOrgan.name,
|
|
191
|
+
message: `Storage directory write check failed: ${err.message}`,
|
|
192
|
+
remediation: 'Verify write permissions for the companion directory.',
|
|
193
|
+
});
|
|
182
194
|
}
|
|
183
195
|
}
|
|
184
196
|
else {
|
|
185
197
|
results.push({
|
|
186
198
|
category: 'Database',
|
|
187
|
-
name: '
|
|
199
|
+
name: 'SQLite Database',
|
|
188
200
|
status: 'SKIPPED',
|
|
189
201
|
message: 'Not required by current composition',
|
|
190
202
|
});
|
package/dist/generator.js
CHANGED
|
@@ -36,7 +36,7 @@ function getDefaultConfigForManifest(manifest) {
|
|
|
36
36
|
config.apiKeyEnv = 'OPENROUTER_API_KEY';
|
|
37
37
|
}
|
|
38
38
|
else if (manifest.organType === 'memory') {
|
|
39
|
-
config.provider = config.provider || '
|
|
39
|
+
config.provider = config.provider || 'sqlite';
|
|
40
40
|
}
|
|
41
41
|
else if (manifest.organType === 'voice') {
|
|
42
42
|
config.provider = config.provider || 'voicevox';
|
|
@@ -72,7 +72,7 @@ function getDefaultConfigForManifest(manifest) {
|
|
|
72
72
|
function generateInstanceFiles(options) {
|
|
73
73
|
const instanceName = options.name || 'my-siduri';
|
|
74
74
|
const instanceId = options.id || 'default';
|
|
75
|
-
const coreVersion = options.coreVersion || '^
|
|
75
|
+
const coreVersion = options.coreVersion || '^2.0.1';
|
|
76
76
|
const manifests = options.selectedManifests;
|
|
77
77
|
const hasMemory = manifests.some((m) => m.organType === 'memory');
|
|
78
78
|
const hasVoice = manifests.some((m) => m.organType === 'voice');
|
|
@@ -80,14 +80,12 @@ function generateInstanceFiles(options) {
|
|
|
80
80
|
const hasMouth = manifests.some((m) => m.organType === 'mouth');
|
|
81
81
|
const voiceConfig = options.organConfigs?.voice || options.organConfigs?.['@siduri-x/voice'];
|
|
82
82
|
const isVoicevox = hasVoice && (!voiceConfig || voiceConfig.provider === 'voicevox');
|
|
83
|
-
const memoryConfig = options.organConfigs?.memory || options.organConfigs?.['@siduri-x/memory'];
|
|
84
|
-
const isPostgresLocal = hasMemory && (!memoryConfig || memoryConfig.deployment === 'local' || memoryConfig.provider === 'postgres');
|
|
85
83
|
// 1. package.json
|
|
86
84
|
const dependencies = {
|
|
87
85
|
'@siduri-x/core': coreVersion,
|
|
88
86
|
};
|
|
89
87
|
for (const m of manifests) {
|
|
90
|
-
dependencies[m.name] = `^${m.version || '
|
|
88
|
+
dependencies[m.name] = `^${m.version || '2.0.1'}`;
|
|
91
89
|
}
|
|
92
90
|
const scripts = {
|
|
93
91
|
start: 'node src/index.js',
|
|
@@ -95,12 +93,19 @@ function generateInstanceFiles(options) {
|
|
|
95
93
|
doctor: 'siduri doctor',
|
|
96
94
|
db: 'siduri db',
|
|
97
95
|
};
|
|
96
|
+
const devDependencies = {
|
|
97
|
+
'@vxnus/siduri': coreVersion,
|
|
98
|
+
};
|
|
98
99
|
const packageJsonObj = {
|
|
99
100
|
name: instanceName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'my-siduri',
|
|
100
101
|
private: true,
|
|
101
102
|
type: 'module',
|
|
103
|
+
engines: {
|
|
104
|
+
node: '>=22.16.0',
|
|
105
|
+
},
|
|
102
106
|
scripts,
|
|
103
107
|
dependencies,
|
|
108
|
+
devDependencies,
|
|
104
109
|
};
|
|
105
110
|
const packageJson = JSON.stringify(packageJsonObj, null, 2) + '\n';
|
|
106
111
|
// 3. siduri.config.json
|
|
@@ -164,14 +169,35 @@ function generateInstanceFiles(options) {
|
|
|
164
169
|
`import { SiduriRuntime, dispatchCompanionChat, validateCompanionConfig } from '@siduri-x/core';`,
|
|
165
170
|
];
|
|
166
171
|
for (const m of manifests) {
|
|
167
|
-
|
|
172
|
+
if (m.name === '@siduri-x/self') {
|
|
173
|
+
importLines.push(`import { ActiveSelfCompiler, SqliteSelfRepository } from '@siduri-x/self';`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
importLines.push(`import { ${m.factory} } from '${m.name}';`);
|
|
177
|
+
}
|
|
168
178
|
}
|
|
169
179
|
const instantiationLines = [];
|
|
170
180
|
const organMapEntries = [];
|
|
171
181
|
for (const m of manifests) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
if (m.name === '@siduri-x/self') {
|
|
183
|
+
instantiationLines.push(`const self = new SqliteSelfRepository({ dbPath: path.resolve(rootDir, 'siduri.sqlite') });`);
|
|
184
|
+
instantiationLines.push(`const behavior = new ActiveSelfCompiler(config.organs.behavior);`);
|
|
185
|
+
organMapEntries.push(` behavior,`);
|
|
186
|
+
organMapEntries.push(` self,`);
|
|
187
|
+
}
|
|
188
|
+
else if (m.name === '@siduri-x/memory') {
|
|
189
|
+
instantiationLines.push(`const memory = new ${m.factory}({ ...config.organs.${m.configKey}, dbPath: path.resolve(rootDir, config.organs.${m.configKey}?.dbPath || 'siduri.sqlite') });`);
|
|
190
|
+
organMapEntries.push(` ${m.configKey},`);
|
|
191
|
+
}
|
|
192
|
+
else if (m.name === '@siduri-x/knowledge') {
|
|
193
|
+
instantiationLines.push(`const knowledge = new ${m.factory}({ ...config.organs.${m.configKey}, dbPath: path.resolve(rootDir, config.organs.${m.configKey}?.dbPath || 'siduri.sqlite') });`);
|
|
194
|
+
organMapEntries.push(` ${m.configKey},`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const varName = m.configKey;
|
|
198
|
+
instantiationLines.push(`const ${varName} = new ${m.factory}(config.organs.${m.configKey});`);
|
|
199
|
+
organMapEntries.push(` ${varName},`);
|
|
200
|
+
}
|
|
175
201
|
}
|
|
176
202
|
if (hasMouth && hasVoice) {
|
|
177
203
|
instantiationLines.push(`if (typeof mouth?.setVoiceOrgan === 'function') mouth.setVoiceOrgan(voice);`);
|
|
@@ -184,6 +210,14 @@ function generateInstanceFiles(options) {
|
|
|
184
210
|
`const __dirname = path.dirname(__filename);`,
|
|
185
211
|
`const rootDir = path.resolve(__dirname, '..');`,
|
|
186
212
|
'',
|
|
213
|
+
`try {`,
|
|
214
|
+
` process.loadEnvFile(path.join(rootDir, '.env'));`,
|
|
215
|
+
`} catch (err) {`,
|
|
216
|
+
` if (err?.code !== 'ENOENT') {`,
|
|
217
|
+
` console.error('Failed to load .env file:', err.message);`,
|
|
218
|
+
` }`,
|
|
219
|
+
`}`,
|
|
220
|
+
'',
|
|
187
221
|
`const config = JSON.parse(`,
|
|
188
222
|
` await readFile(path.join(rootDir, 'siduri.config.json'), 'utf8')`,
|
|
189
223
|
`);`,
|
|
@@ -222,6 +256,51 @@ function generateInstanceFiles(options) {
|
|
|
222
256
|
` return;`,
|
|
223
257
|
` }`,
|
|
224
258
|
'',
|
|
259
|
+
` // API: Ready probe`,
|
|
260
|
+
` if (pathname === '/ready' && req.method === 'GET') {`,
|
|
261
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
262
|
+
` res.end(JSON.stringify({ status: 'ready', companionId: config.id }));`,
|
|
263
|
+
` return;`,
|
|
264
|
+
` }`,
|
|
265
|
+
'',
|
|
266
|
+
` // API: Version info`,
|
|
267
|
+
` if (pathname === '/version' && req.method === 'GET') {`,
|
|
268
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
269
|
+
` res.end(JSON.stringify({ version: '2.0.1', name: config.name, id: config.id }));`,
|
|
270
|
+
` return;`,
|
|
271
|
+
` }`,
|
|
272
|
+
'',
|
|
273
|
+
` // API: Identity info`,
|
|
274
|
+
` if (pathname === '/me' && req.method === 'GET') {`,
|
|
275
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
276
|
+
` res.end(JSON.stringify({`,
|
|
277
|
+
` id: config.id,`,
|
|
278
|
+
` name: config.name,`,
|
|
279
|
+
` organs: Object.keys(config.organs || {}),`,
|
|
280
|
+
` }));`,
|
|
281
|
+
` return;`,
|
|
282
|
+
` }`,
|
|
283
|
+
'',
|
|
284
|
+
` // API: Voice & Observation health probes`,
|
|
285
|
+
` if (pathname === '/voice/health' && req.method === 'GET') {`,
|
|
286
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
287
|
+
` res.end(JSON.stringify({ provider: config.organs?.voice?.provider || 'none', configured: Boolean(runtime.voice) }));`,
|
|
288
|
+
` return;`,
|
|
289
|
+
` }`,
|
|
290
|
+
'',
|
|
291
|
+
` if (pathname === '/obs/health' && req.method === 'GET') {`,
|
|
292
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
293
|
+
` res.end(JSON.stringify({ provider: config.organs?.vision?.provider || 'none', configured: Boolean(runtime.vision) }));`,
|
|
294
|
+
` return;`,
|
|
295
|
+
` }`,
|
|
296
|
+
'',
|
|
297
|
+
` // API: Evidence & observations`,
|
|
298
|
+
` if ((pathname === '/evidence' || pathname === '/observations') && req.method === 'GET') {`,
|
|
299
|
+
` res.writeHead(200, { 'Content-Type': 'application/json' });`,
|
|
300
|
+
` res.end(JSON.stringify({ items: [] }));`,
|
|
301
|
+
` return;`,
|
|
302
|
+
` }`,
|
|
303
|
+
'',
|
|
225
304
|
` // API: Audio buffer retrieval for voice playback`,
|
|
226
305
|
` if (pathname.startsWith('/api/audio/') && req.method === 'GET') {`,
|
|
227
306
|
` const audioId = pathname.slice('/api/audio/'.length);`,
|
|
@@ -573,20 +652,17 @@ function generateInstanceFiles(options) {
|
|
|
573
652
|
'',
|
|
574
653
|
'## Prerequisites',
|
|
575
654
|
'',
|
|
576
|
-
'- **Node.js**: `
|
|
655
|
+
'- **Node.js**: `v22.16.0` or higher',
|
|
577
656
|
'- **Environment**: Valid `.env` file (configured from `.env.example`)',
|
|
578
657
|
];
|
|
579
658
|
if (hasMemory) {
|
|
580
|
-
readmeLines.push('- **
|
|
659
|
+
readmeLines.push('- **Database**: Embedded SQLite (`siduri.sqlite`). Automatically initialized with WAL mode and FTS5 full-text indexing with zero external setup.');
|
|
581
660
|
}
|
|
582
661
|
if (isVoicevox) {
|
|
583
662
|
readmeLines.push('- **VOICEVOX**: Note: The Voicevox engine executable will be securely auto-downloaded at runtime by Siduri if no local URL is provided.');
|
|
584
663
|
}
|
|
585
664
|
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`.');
|
|
586
|
-
|
|
587
|
-
readmeLines.push('', '### 3. Database Migrations', 'Ensure `DATABASE_URL` in `.env` is reachable, then push the memory organ PostgreSQL schema:', '```bash', 'npx @vxnus/siduri db push', '```');
|
|
588
|
-
}
|
|
589
|
-
readmeLines.push('', `### ${hasMemory ? '4' : '3'}. Diagnostics & Health Probe`, 'Verify all environment variables, schema conformance, services, and organ connections:', '```bash', 'npm run doctor', '```', '', `### ${hasMemory ? '5' : '4'}. Start Companion & Web Console`, 'Launch your companion runtime and Web UI / Memory Control Panel:', '```bash', 'npm start', '```', 'Then open `http://localhost:3000` in your browser.');
|
|
665
|
+
readmeLines.push('', '### 3. Diagnostics & Health Probe', 'Verify all environment variables, schema conformance, services, and organ connections:', '```bash', 'npm run doctor', '```', '', '### 4. Start Companion & Web Console', 'Launch your companion runtime and Web UI / Memory Control Panel:', '```bash', 'npm start', '```', 'Then open `http://localhost:3000` in your browser.');
|
|
590
666
|
const companionSlug = instanceName.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
|
|
591
667
|
const createAssetsDirs = [];
|
|
592
668
|
if (hasBody) {
|
package/dist/generator.test.js
CHANGED
|
@@ -31,14 +31,14 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
31
31
|
name: '@siduri-x/memory',
|
|
32
32
|
organType: 'memory',
|
|
33
33
|
version: '1.0.0',
|
|
34
|
-
displayName: 'Memory (
|
|
34
|
+
displayName: 'Memory (SQLite FTS5 Claims)',
|
|
35
35
|
entrypoint: './dist/index.js',
|
|
36
|
-
factory: '
|
|
36
|
+
factory: 'SqliteMemoryStore',
|
|
37
37
|
configKey: 'memory',
|
|
38
38
|
configSchema: { type: 'object', properties: { provider: { type: 'string' } } },
|
|
39
|
-
environment: [
|
|
40
|
-
services: [
|
|
41
|
-
database: { engine: '
|
|
39
|
+
environment: [],
|
|
40
|
+
services: [],
|
|
41
|
+
database: { engine: 'sqlite' },
|
|
42
42
|
},
|
|
43
43
|
body: {
|
|
44
44
|
name: '@siduri-x/body',
|
|
@@ -77,6 +77,7 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
77
77
|
expect(pkg.dependencies['@siduri-x/hands']).toBeUndefined();
|
|
78
78
|
expect(pkg.dependencies['@siduri-x/voice']).toBeUndefined();
|
|
79
79
|
expect(pkg.dependencies['@siduri-x/body']).toBeUndefined();
|
|
80
|
+
expect(pkg.devDependencies['@vxnus/siduri']).toBeDefined();
|
|
80
81
|
// src/index.js
|
|
81
82
|
expect(files['src/index.js']).toContain("import { OpenRouterBrain } from '@siduri-x/brain'");
|
|
82
83
|
expect(files['src/index.js']).not.toContain('@siduri-x/hands');
|
|
@@ -126,11 +127,12 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
126
127
|
expect(pkg.dependencies['@siduri-x/hands']).toBeUndefined();
|
|
127
128
|
// .env.example
|
|
128
129
|
expect(files['.env.example']).toContain('OPENROUTER_API_KEY');
|
|
129
|
-
expect(files['.env.example']).toContain('DATABASE_URL');
|
|
130
|
+
expect(files['.env.example']).not.toContain('DATABASE_URL');
|
|
130
131
|
expect(files['.env.example']).not.toContain('ACTION_POLICY_SECRET');
|
|
131
132
|
// README mentions database
|
|
132
|
-
expect(files['README.md']).toContain('
|
|
133
|
-
expect(files['README.md']).toContain('
|
|
133
|
+
expect(files['README.md']).toContain('siduri.sqlite');
|
|
134
|
+
expect(files['README.md']).not.toContain('PostgreSQL');
|
|
135
|
+
expect(files['README.md']).not.toContain('npx @vxnus/siduri db push');
|
|
134
136
|
});
|
|
135
137
|
test('Composition D: Full with Body', () => {
|
|
136
138
|
const files = (0, generator_1.generateInstanceFiles)({
|
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 = '2.0.1';
|
|
30
30
|
exports.colors = {
|
|
31
31
|
cyan: '\u001b[36m',
|
|
32
32
|
dim: '\u001b[2m',
|
|
@@ -120,37 +120,46 @@ async function runCreateWizard(targetDir) {
|
|
|
120
120
|
]);
|
|
121
121
|
const companionName = basicAnswers.name;
|
|
122
122
|
const projectDir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : node_path_1.default.resolve(process.cwd(), projectDirectoryName(companionName));
|
|
123
|
-
printSection('Organ
|
|
124
|
-
console.log(`${exports.colors.dim}
|
|
125
|
-
// Brain is required by architecture contract for cognition
|
|
126
|
-
const brainManifest = registry.get('brain');
|
|
127
|
-
const nonBrainManifests = availableManifests.filter((m) => m.organType !== 'brain');
|
|
128
|
-
const organChoices = nonBrainManifests.map((m) => ({
|
|
129
|
-
name: `${m.displayName} (${m.name})`,
|
|
130
|
-
value: m.organType,
|
|
131
|
-
checked: m.organType === 'memory', // default memory checked
|
|
132
|
-
}));
|
|
133
|
-
const { selectedOrganTypes } = await inquirer_1.default.prompt({
|
|
134
|
-
type: 'checkbox',
|
|
135
|
-
name: 'selectedOrganTypes',
|
|
136
|
-
message: 'Select organs to install:',
|
|
137
|
-
choices: organChoices,
|
|
138
|
-
});
|
|
123
|
+
printSection('Organ Configuration');
|
|
124
|
+
console.log(`${exports.colors.dim}Configure each capability organ for your standalone companion instance.${exports.colors.reset}\n`);
|
|
139
125
|
const selectedManifests = [];
|
|
126
|
+
const organConfigs = {};
|
|
127
|
+
const organSummaries = {};
|
|
128
|
+
// Brain is required by architecture contract for cognition
|
|
129
|
+
const brainManifest = registry.get('brain') || availableManifests.find((m) => m.organType === 'brain');
|
|
140
130
|
if (brainManifest) {
|
|
141
131
|
selectedManifests.push(brainManifest);
|
|
132
|
+
printSection('Brain · required');
|
|
133
|
+
const res = await (0, configurators_1.configureOrgan)(brainManifest, {
|
|
134
|
+
companionName,
|
|
135
|
+
});
|
|
136
|
+
organConfigs[brainManifest.configKey] = res.config;
|
|
137
|
+
organSummaries[brainManifest.configKey] = res.summary || {};
|
|
142
138
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
139
|
+
// Canonical organ presentation order
|
|
140
|
+
const canonicalOrder = ['memory', 'knowledge', 'behavior', 'voice', 'body', 'mouth', 'hands', 'vision', 'ear', 'observation'];
|
|
141
|
+
const nonBrainManifests = availableManifests
|
|
142
|
+
.filter((m) => m.organType !== 'brain')
|
|
143
|
+
.sort((a, b) => {
|
|
144
|
+
const idxA = canonicalOrder.indexOf(a.organType);
|
|
145
|
+
const idxB = canonicalOrder.indexOf(b.organType);
|
|
146
|
+
const valA = idxA === -1 ? 99 : idxA;
|
|
147
|
+
const valB = idxB === -1 ? 99 : idxB;
|
|
148
|
+
return valA - valB;
|
|
149
|
+
});
|
|
150
|
+
for (const m of nonBrainManifests) {
|
|
151
|
+
const isRecommended = ['memory', 'knowledge', 'behavior', 'voice', 'body', 'mouth'].includes(m.organType);
|
|
152
|
+
const { enableOrgan } = await inquirer_1.default.prompt({
|
|
153
|
+
type: 'confirm',
|
|
154
|
+
name: 'enableOrgan',
|
|
155
|
+
message: `Enable ${m.displayName}?`,
|
|
156
|
+
default: isRecommended,
|
|
157
|
+
});
|
|
158
|
+
if (!enableOrgan) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
selectedManifests.push(m);
|
|
162
|
+
const sectionTitle = m.displayName.split(' ')[0] || m.organType;
|
|
154
163
|
printSection(sectionTitle);
|
|
155
164
|
const res = await (0, configurators_1.configureOrgan)(m, {
|
|
156
165
|
companionName,
|
|
@@ -264,10 +273,6 @@ async function runCreateWizard(targetDir) {
|
|
|
264
273
|
console.log(`Your Siduri companion is ready! Next steps:\n`);
|
|
265
274
|
console.log(` cd ${node_path_1.default.relative(process.cwd(), projectDir) || '.'}`);
|
|
266
275
|
console.log(` cp .env.example .env ${exports.colors.dim}# Fill in required API keys/credentials${exports.colors.reset}`);
|
|
267
|
-
const hasMemory = selectedManifests.some((m) => m.organType === 'memory');
|
|
268
|
-
if (hasMemory) {
|
|
269
|
-
console.log(` npx @vxnus/siduri db push ${exports.colors.dim}# Push PostgreSQL memory schema${exports.colors.reset}`);
|
|
270
|
-
}
|
|
271
276
|
console.log(` npm run doctor ${exports.colors.dim}# Run diagnostic health probes${exports.colors.reset}`);
|
|
272
277
|
console.log(` npm start ${exports.colors.dim}# Start Web Companion & Memory Console at http://localhost:3000${exports.colors.reset}\n`);
|
|
273
278
|
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface OrganServiceRequirement {
|
|
|
12
12
|
description?: string;
|
|
13
13
|
}
|
|
14
14
|
export interface OrganDatabaseRequirement {
|
|
15
|
-
engine: '
|
|
15
|
+
engine: 'sqlite' | string;
|
|
16
16
|
migrationsDir?: string;
|
|
17
17
|
}
|
|
18
18
|
export interface OrganManifest {
|
package/dist/release-check.js
CHANGED
|
@@ -19,11 +19,12 @@ function runReleaseCheck(repoRoot = node_path_1.default.resolve(__dirname, '../.
|
|
|
19
19
|
const getPkgVer = (dir) => JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.resolve(repoRoot, dir, 'package.json'), 'utf8')).version;
|
|
20
20
|
const canonicalPackages = [
|
|
21
21
|
{ name: '@siduri-x/core', dir: 'packages/core', isOrgan: false, tarName: `siduri-x-core-${getPkgVer('packages/core')}.tgz` },
|
|
22
|
+
{ name: '@siduri-x/self', dir: 'packages/self', isOrgan: false, tarName: `siduri-x-self-${getPkgVer('packages/self')}.tgz` },
|
|
23
|
+
{ name: '@siduri-x/knowledge', dir: 'packages/knowledge', isOrgan: false, tarName: `siduri-x-knowledge-${getPkgVer('packages/knowledge')}.tgz` },
|
|
24
|
+
{ name: '@siduri-x/memory', dir: 'packages/memory', isOrgan: false, tarName: `siduri-x-memory-${getPkgVer('packages/memory')}.tgz` },
|
|
22
25
|
{ name: '@siduri-x/brain', dir: 'packages/organs/brain', isOrgan: true, tarName: `siduri-x-brain-${getPkgVer('packages/organs/brain')}.tgz` },
|
|
23
|
-
{ name: '@siduri-x/memory', dir: 'packages/organs/memory', isOrgan: true, tarName: `siduri-x-memory-${getPkgVer('packages/organs/memory')}.tgz` },
|
|
24
|
-
{ name: '@siduri-x/knowledge', dir: 'packages/organs/knowledge', isOrgan: true, tarName: `siduri-x-knowledge-${getPkgVer('packages/organs/knowledge')}.tgz` },
|
|
25
|
-
{ name: '@siduri-x/behavior', dir: 'packages/organs/behavior', isOrgan: true, tarName: `siduri-x-behavior-${getPkgVer('packages/organs/behavior')}.tgz` },
|
|
26
26
|
{ name: '@siduri-x/ear', dir: 'packages/organs/ear', isOrgan: true, tarName: `siduri-x-ear-${getPkgVer('packages/organs/ear')}.tgz` },
|
|
27
|
+
{ name: '@siduri-x/eknowledge', dir: 'packages/organs/eknowledge', isOrgan: true, tarName: `siduri-x-eknowledge-${getPkgVer('packages/organs/eknowledge')}.tgz` },
|
|
27
28
|
{ name: '@siduri-x/vision', dir: 'packages/organs/vision', isOrgan: true, tarName: `siduri-x-vision-${getPkgVer('packages/organs/vision')}.tgz` },
|
|
28
29
|
{ name: '@siduri-x/hands', dir: 'packages/organs/hands', isOrgan: true, tarName: `siduri-x-hands-${getPkgVer('packages/organs/hands')}.tgz` },
|
|
29
30
|
{ name: '@siduri-x/body', dir: 'packages/organs/body', isOrgan: true, tarName: `siduri-x-body-${getPkgVer('packages/organs/body')}.tgz` },
|
|
@@ -40,7 +41,7 @@ function runReleaseCheck(repoRoot = node_path_1.default.resolve(__dirname, '../.
|
|
|
40
41
|
for (const pkg of canonicalPackages) {
|
|
41
42
|
packagesChecked++;
|
|
42
43
|
try {
|
|
43
|
-
run(`pnpm
|
|
44
|
+
run(`pnpm pack --pack-destination ${tempPackDir}`, node_path_1.default.resolve(repoRoot, pkg.dir));
|
|
44
45
|
}
|
|
45
46
|
catch (err) {
|
|
46
47
|
errors.push(`Failed to pack ${pkg.name}: ${err.message}`);
|
|
@@ -88,11 +89,6 @@ function runReleaseCheck(repoRoot = node_path_1.default.resolve(__dirname, '../.
|
|
|
88
89
|
manifestsValidated++;
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
if (pkg.name === '@siduri-x/memory') {
|
|
92
|
-
if (!listing.some((l) => l.includes('package/migrations/001_initial_schema.sql'))) {
|
|
93
|
-
errors.push(`${pkg.name}: migrations/001_initial_schema.sql missing in tarball`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
92
|
if (pkg.name === '@vxnus/siduri') {
|
|
97
93
|
if (!pkgJson.bin || !pkgJson.bin.siduri) {
|
|
98
94
|
errors.push(`${pkg.name}: bin.siduri field missing in package.json`);
|