@siduri-x/api 2.0.10 → 2.0.11

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @siduri-x/api@2.0.10 build /home/zagin/Projects/vxnus-studio/projects/siduri-x/apps/api
2
+ > @siduri-x/api@2.0.11 build /home/zagin/Projects/vxnus-studio/projects/siduri-x/apps/api
3
3
  > tsc
4
4
 
@@ -2,8 +2,8 @@
2
2
  > @siduri-x/api@2.0.10 test /home/zagin/Projects/vxnus-studio/projects/siduri-x/apps/api
3
3
  > jest --config jest.config.json
4
4
 
5
- PASS src/context-mapper.test.ts (14.575 s)
6
- PASS src/runtime.test.ts (14.839 s)
5
+ PASS src/context-mapper.test.ts (5.346 s)
6
+ PASS src/runtime.test.ts (5.447 s)
7
7
  ● Console
8
8
 
9
9
  console.error
@@ -24,20 +24,20 @@ PASS src/runtime.test.ts (14.839 s)
24
24
  at PerceptionPipeline.execute (../../packages/core/dist/perception-pipeline.js:21:34)
25
25
  at Object.<anonymous> (src/runtime.test.ts:74:22)
26
26
 
27
- PASS src/t6-security.test.ts (16.358 s)
28
- PASS src/t4-gating.test.ts (15.402 s)
29
- PASS src/t7-release.test.ts
30
- PASS src/index.test.ts (15.608 s)
31
- PASS src/smoke.test.ts
27
+ PASS src/teach-mode.test.ts (6.257 s)
28
+ PASS src/index.test.ts (7.027 s)
29
+ PASS src/t5-experience.test.ts (6.923 s)
32
30
  PASS src/auth.test.ts
33
- PASS src/t5-experience.test.ts (16.078 s)
34
- PASS src/teach-mode.test.ts (16.355 s)
31
+ PASS src/smoke.test.ts
32
+ PASS src/t4-gating.test.ts (7.311 s)
35
33
  PASS src/boot.test.ts
34
+ PASS src/t7-release.test.ts
35
+ PASS src/t6-security.test.ts (7.421 s)
36
36
  PASS src/knowledge.test.ts
37
37
  PASS src/b0-b6.test.ts
38
38
  
39
39
  Test Suites: 13 passed, 13 total
40
- Tests: 90 passed, 90 total
40
+ Tests: 92 passed, 92 total
41
41
  Snapshots: 0 total
42
- Time: 21.926 s
42
+ Time: 8.614 s
43
43
  Ran all test suites.
package/dist/app.js CHANGED
@@ -7,6 +7,8 @@ exports.bootCompanion = void 0;
7
7
  exports.createApp = createApp;
8
8
  const express_1 = __importDefault(require("express"));
9
9
  const cors_1 = __importDefault(require("cors"));
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
10
12
  const cors_2 = require("./cors");
11
13
  const runtime_1 = require("./runtime");
12
14
  const self_1 = require("@siduri-x/self");
@@ -73,6 +75,93 @@ function createApp(runtimes = new Map()) {
73
75
  });
74
76
  app.put('/me', auth_1.requireAuth, (req, res) => res.json({ success: true }));
75
77
  // TEACH MODE ENDPOINTS
78
+ app.get('/teach/detected-self', auth_1.attachIdentity, async (req, res) => {
79
+ try {
80
+ const companionId = req.query.companionId || Array.from(runtimes.keys())[0] || 'default';
81
+ const runtime = runtimes.get(companionId);
82
+ const explicitPath = req.query.path || undefined;
83
+ const envPath = process.env.SIDURI_SELF_PATH || process.env.SELF_PATH;
84
+ const configPath = runtime?.config?.organs?.behavior?.selfPath ||
85
+ runtime?.config?.behavior?.selfPath;
86
+ const candidates = [];
87
+ if (explicitPath)
88
+ candidates.push(node_path_1.default.resolve(process.cwd(), explicitPath));
89
+ if (envPath)
90
+ candidates.push(node_path_1.default.resolve(process.cwd(), envPath));
91
+ if (configPath)
92
+ candidates.push(node_path_1.default.resolve(process.cwd(), configPath));
93
+ candidates.push(node_path_1.default.resolve(process.cwd(), 'assets', 'self', `${companionId}.self`));
94
+ candidates.push(node_path_1.default.resolve(process.cwd(), `${companionId}.self`));
95
+ const searchDirs = [
96
+ node_path_1.default.resolve(process.cwd(), 'assets', 'self'),
97
+ node_path_1.default.resolve(process.cwd(), 'assets'),
98
+ node_path_1.default.resolve(process.cwd()),
99
+ node_path_1.default.resolve(process.cwd(), '..', '..', 'assets', 'self'),
100
+ node_path_1.default.resolve(process.cwd(), '..', '..'),
101
+ ];
102
+ let matchedFilePath;
103
+ for (const candidate of candidates) {
104
+ if (node_fs_1.default.existsSync(candidate)) {
105
+ try {
106
+ const stat = node_fs_1.default.statSync(candidate);
107
+ if (stat.isFile()) {
108
+ matchedFilePath = candidate;
109
+ break;
110
+ }
111
+ }
112
+ catch { }
113
+ }
114
+ }
115
+ if (!matchedFilePath) {
116
+ for (const dir of searchDirs) {
117
+ if (node_fs_1.default.existsSync(dir)) {
118
+ try {
119
+ const entries = node_fs_1.default.readdirSync(dir);
120
+ const selfFiles = entries.filter((f) => f.endsWith('.self'));
121
+ if (selfFiles.length > 0) {
122
+ const companionSelf = selfFiles.find((f) => f === `${companionId}.self`);
123
+ matchedFilePath = node_path_1.default.join(dir, companionSelf || selfFiles[0]);
124
+ break;
125
+ }
126
+ }
127
+ catch { }
128
+ }
129
+ }
130
+ }
131
+ if (!matchedFilePath) {
132
+ return res.json({ detected: false });
133
+ }
134
+ const content = await node_fs_1.default.promises.readFile(matchedFilePath, 'utf8');
135
+ const parsed = self_1.SelfPackageParser.parse(content);
136
+ let alreadyInstalled = false;
137
+ const repo = new self_1.SqliteSelfRepository({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
138
+ try {
139
+ const existingIdentity = await repo.getIdentity(companionId);
140
+ const existingDirectives = await repo.getActiveDirectives(companionId);
141
+ if (existingIdentity &&
142
+ parsed.manifest?.identity?.name &&
143
+ existingIdentity.name.toLowerCase() === parsed.manifest.identity.name.toLowerCase() &&
144
+ existingDirectives.length > 0) {
145
+ alreadyInstalled = true;
146
+ }
147
+ }
148
+ catch { }
149
+ finally {
150
+ repo.close();
151
+ }
152
+ res.json({
153
+ detected: true,
154
+ filename: node_path_1.default.basename(matchedFilePath),
155
+ path: matchedFilePath,
156
+ content,
157
+ parsed,
158
+ alreadyInstalled,
159
+ });
160
+ }
161
+ catch (e) {
162
+ res.status(500).json({ error: e.message });
163
+ }
164
+ });
76
165
  app.post('/teach/upload-self', auth_1.requireAuth, async (req, res) => {
77
166
  try {
78
167
  const { content } = req.body;
@@ -3,6 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
6
9
  const supertest_1 = __importDefault(require("supertest"));
7
10
  const app_1 = require("./app");
8
11
  const self_1 = require("@siduri-x/self");
@@ -200,4 +203,31 @@ kind: "other"
200
203
  { id: 'dir-rel-1', directive: 'Honor creator root privileges' },
201
204
  ]);
202
205
  });
206
+ describe('GET /teach/detected-self', () => {
207
+ it('returns detected: false when no candidate .self exists on path', async () => {
208
+ const res = await (0, supertest_1.default)(app)
209
+ .get('/teach/detected-self?path=/tmp/non-existent-path-to-nothing.self');
210
+ expect(res.status).toBe(200);
211
+ expect(res.body.detected).toBe(false);
212
+ });
213
+ it('detects and parses a .self file located on path', async () => {
214
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'siduri-detected-self-'));
215
+ const selfFilePath = path_1.default.join(tmpDir, 'elena.self');
216
+ fs_1.default.writeFileSync(selfFilePath, validSelfContent, 'utf8');
217
+ try {
218
+ const res = await (0, supertest_1.default)(app)
219
+ .get(`/teach/detected-self?path=${encodeURIComponent(selfFilePath)}`);
220
+ expect(res.status).toBe(200);
221
+ expect(res.body.detected).toBe(true);
222
+ expect(res.body.filename).toBe('elena.self');
223
+ expect(res.body.parsed).toBeDefined();
224
+ expect(res.body.parsed.manifest?.name).toBe('Test Bot');
225
+ expect(res.body.parsed.scannedDirectives).toHaveLength(2);
226
+ expect(res.body.content).toContain('kind: "self"');
227
+ }
228
+ finally {
229
+ fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
230
+ }
231
+ });
232
+ });
203
233
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/api",
3
- "version": "2.0.10",
3
+ "version": "2.0.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -21,8 +21,8 @@
21
21
  "@siduri-x/knowledge": "2.0.3",
22
22
  "@siduri-x/memory": "2.0.4",
23
23
  "@siduri-x/mouth": "2.0.2",
24
- "@siduri-x/self": "2.0.4",
25
24
  "@siduri-x/observation": "2.0.1",
25
+ "@siduri-x/self": "2.0.5",
26
26
  "@siduri-x/vision": "2.0.1",
27
27
  "@siduri-x/voice": "2.0.2"
28
28
  },
package/src/app.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import express, { Express } from 'express';
2
2
  import cors from 'cors';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
3
5
  import { createCorsOptions } from './cors';
4
6
  import { SiduriRuntime, dispatchCompanionChat } from './runtime';
5
7
  import { SelfPackageParser, SqliteSelfRepository, scanDirective } from '@siduri-x/self';
@@ -92,6 +94,99 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
92
94
  app.put('/me', requireAuth, (req, res) => res.json({ success: true }));
93
95
 
94
96
  // TEACH MODE ENDPOINTS
97
+ app.get('/teach/detected-self', attachIdentity, async (req, res) => {
98
+ try {
99
+ const companionId = (req.query.companionId as string) || Array.from(runtimes.keys())[0] || 'default';
100
+ const runtime = runtimes.get(companionId);
101
+
102
+ const explicitPath = (req.query.path as string) || undefined;
103
+ const envPath = process.env.SIDURI_SELF_PATH || process.env.SELF_PATH;
104
+ const configPath = (runtime?.config as any)?.organs?.behavior?.selfPath ||
105
+ (runtime?.config as any)?.behavior?.selfPath;
106
+
107
+ const candidates: string[] = [];
108
+ if (explicitPath) candidates.push(path.resolve(process.cwd(), explicitPath));
109
+ if (envPath) candidates.push(path.resolve(process.cwd(), envPath));
110
+ if (configPath) candidates.push(path.resolve(process.cwd(), configPath));
111
+
112
+ candidates.push(path.resolve(process.cwd(), 'assets', 'self', `${companionId}.self`));
113
+ candidates.push(path.resolve(process.cwd(), `${companionId}.self`));
114
+
115
+ const searchDirs = [
116
+ path.resolve(process.cwd(), 'assets', 'self'),
117
+ path.resolve(process.cwd(), 'assets'),
118
+ path.resolve(process.cwd()),
119
+ path.resolve(process.cwd(), '..', '..', 'assets', 'self'),
120
+ path.resolve(process.cwd(), '..', '..'),
121
+ ];
122
+
123
+ let matchedFilePath: string | undefined;
124
+
125
+ for (const candidate of candidates) {
126
+ if (fs.existsSync(candidate)) {
127
+ try {
128
+ const stat = fs.statSync(candidate);
129
+ if (stat.isFile()) {
130
+ matchedFilePath = candidate;
131
+ break;
132
+ }
133
+ } catch {}
134
+ }
135
+ }
136
+
137
+ if (!matchedFilePath) {
138
+ for (const dir of searchDirs) {
139
+ if (fs.existsSync(dir)) {
140
+ try {
141
+ const entries = fs.readdirSync(dir);
142
+ const selfFiles = entries.filter((f) => f.endsWith('.self'));
143
+ if (selfFiles.length > 0) {
144
+ const companionSelf = selfFiles.find((f) => f === `${companionId}.self`);
145
+ matchedFilePath = path.join(dir, companionSelf || selfFiles[0]);
146
+ break;
147
+ }
148
+ } catch {}
149
+ }
150
+ }
151
+ }
152
+
153
+ if (!matchedFilePath) {
154
+ return res.json({ detected: false });
155
+ }
156
+
157
+ const content = await fs.promises.readFile(matchedFilePath, 'utf8');
158
+ const parsed = SelfPackageParser.parse(content);
159
+
160
+ let alreadyInstalled = false;
161
+ const repo = new SqliteSelfRepository({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
162
+ try {
163
+ const existingIdentity = await repo.getIdentity(companionId);
164
+ const existingDirectives = await repo.getActiveDirectives(companionId);
165
+ if (
166
+ existingIdentity &&
167
+ parsed.manifest?.identity?.name &&
168
+ existingIdentity.name.toLowerCase() === parsed.manifest.identity.name.toLowerCase() &&
169
+ existingDirectives.length > 0
170
+ ) {
171
+ alreadyInstalled = true;
172
+ }
173
+ } catch {} finally {
174
+ repo.close();
175
+ }
176
+
177
+ res.json({
178
+ detected: true,
179
+ filename: path.basename(matchedFilePath),
180
+ path: matchedFilePath,
181
+ content,
182
+ parsed,
183
+ alreadyInstalled,
184
+ });
185
+ } catch (e: any) {
186
+ res.status(500).json({ error: e.message });
187
+ }
188
+ });
189
+
95
190
  app.post('/teach/upload-self', requireAuth, async (req, res) => {
96
191
  try {
97
192
  const { content } = req.body;
@@ -1,3 +1,6 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
1
4
  import request from 'supertest';
2
5
  import { createApp } from './app';
3
6
  import { SqliteSelfRepository } from '@siduri-x/self';
@@ -226,4 +229,35 @@ kind: "other"
226
229
  { id: 'dir-rel-1', directive: 'Honor creator root privileges' },
227
230
  ]);
228
231
  });
232
+
233
+ describe('GET /teach/detected-self', () => {
234
+ it('returns detected: false when no candidate .self exists on path', async () => {
235
+ const res = await request(app)
236
+ .get('/teach/detected-self?path=/tmp/non-existent-path-to-nothing.self');
237
+
238
+ expect(res.status).toBe(200);
239
+ expect(res.body.detected).toBe(false);
240
+ });
241
+
242
+ it('detects and parses a .self file located on path', async () => {
243
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'siduri-detected-self-'));
244
+ const selfFilePath = path.join(tmpDir, 'elena.self');
245
+ fs.writeFileSync(selfFilePath, validSelfContent, 'utf8');
246
+
247
+ try {
248
+ const res = await request(app)
249
+ .get(`/teach/detected-self?path=${encodeURIComponent(selfFilePath)}`);
250
+
251
+ expect(res.status).toBe(200);
252
+ expect(res.body.detected).toBe(true);
253
+ expect(res.body.filename).toBe('elena.self');
254
+ expect(res.body.parsed).toBeDefined();
255
+ expect(res.body.parsed.manifest?.name).toBe('Test Bot');
256
+ expect(res.body.parsed.scannedDirectives).toHaveLength(2);
257
+ expect(res.body.content).toContain('kind: "self"');
258
+ } finally {
259
+ fs.rmSync(tmpDir, { recursive: true, force: true });
260
+ }
261
+ });
262
+ });
229
263
  });