@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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +11 -11
- package/dist/app.js +89 -0
- package/dist/teach-mode.test.js +30 -0
- package/package.json +2 -2
- package/src/app.ts +95 -0
- package/src/teach-mode.test.ts +34 -0
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-test.log
CHANGED
|
@@ -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 (
|
|
6
|
-
PASS src/runtime.test.ts (
|
|
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/
|
|
28
|
-
PASS src/
|
|
29
|
-
PASS src/
|
|
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/
|
|
34
|
-
PASS src/
|
|
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
|
[999D[K
|
|
39
39
|
Test Suites: 13 passed, 13 total
|
|
40
|
-
Tests:
|
|
40
|
+
Tests: 92 passed, 92 total
|
|
41
41
|
Snapshots: 0 total
|
|
42
|
-
Time:
|
|
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;
|
package/dist/teach-mode.test.js
CHANGED
|
@@ -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.
|
|
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;
|
package/src/teach-mode.test.ts
CHANGED
|
@@ -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
|
});
|