@sidurijs/api 1.0.0
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 +4 -0
- package/.turbo/turbo-test.log +78 -0
- package/dist/app.d.ts +11 -0
- package/dist/app.js +1190 -0
- package/dist/auth.d.ts +34 -0
- package/dist/auth.js +84 -0
- package/dist/auth.test.d.ts +1 -0
- package/dist/auth.test.js +74 -0
- package/dist/b0-b6.test.d.ts +1 -0
- package/dist/b0-b6.test.js +121 -0
- package/dist/boot.d.ts +91 -0
- package/dist/boot.js +154 -0
- package/dist/boot.test.d.ts +1 -0
- package/dist/boot.test.js +91 -0
- package/dist/context-mapper.d.ts +20 -0
- package/dist/context-mapper.js +233 -0
- package/dist/context-mapper.test.d.ts +1 -0
- package/dist/context-mapper.test.js +287 -0
- package/dist/cors.d.ts +3 -0
- package/dist/cors.js +42 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +116 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +151 -0
- package/dist/knowledge.test.d.ts +1 -0
- package/dist/knowledge.test.js +236 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +17 -0
- package/dist/runtime.test.d.ts +1 -0
- package/dist/runtime.test.js +282 -0
- package/dist/smoke.test.d.ts +0 -0
- package/dist/smoke.test.js +6 -0
- package/dist/t4-gating.test.d.ts +1 -0
- package/dist/t4-gating.test.js +193 -0
- package/dist/t5-experience.test.d.ts +1 -0
- package/dist/t5-experience.test.js +155 -0
- package/dist/t6-security.test.d.ts +1 -0
- package/dist/t6-security.test.js +535 -0
- package/dist/t7-release.test.d.ts +1 -0
- package/dist/t7-release.test.js +119 -0
- package/dist/teach-mode.test.d.ts +1 -0
- package/dist/teach-mode.test.js +277 -0
- package/jest.config.json +5 -0
- package/package.json +44 -0
- package/siduri.sqlite +0 -0
- package/src/app.ts +1209 -0
- package/src/auth.test.ts +90 -0
- package/src/auth.ts +101 -0
- package/src/b0-b6.test.ts +137 -0
- package/src/boot.test.ts +112 -0
- package/src/boot.ts +197 -0
- package/src/context-mapper.test.ts +319 -0
- package/src/context-mapper.ts +279 -0
- package/src/cors.ts +44 -0
- package/src/index.test.ts +178 -0
- package/src/index.ts +101 -0
- package/src/knowledge.test.ts +267 -0
- package/src/runtime.test.ts +336 -0
- package/src/runtime.ts +1 -0
- package/src/smoke.test.ts +5 -0
- package/src/t4-gating.test.ts +219 -0
- package/src/t5-experience.test.ts +174 -0
- package/src/t6-security.test.ts +589 -0
- package/src/t7-release.test.ts +131 -0
- package/src/teach-mode.test.ts +312 -0
- package/tsconfig.json +16 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.app = exports.createApp = void 0;
|
|
21
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
22
|
+
dotenv_1.default.config({ override: true });
|
|
23
|
+
const promises_1 = require("node:fs/promises");
|
|
24
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
25
|
+
const app_1 = require("./app");
|
|
26
|
+
Object.defineProperty(exports, "createApp", { enumerable: true, get: function () { return app_1.createApp; } });
|
|
27
|
+
const boot_1 = require("./boot");
|
|
28
|
+
__exportStar(require("./context-mapper"), exports);
|
|
29
|
+
__exportStar(require("./boot"), exports);
|
|
30
|
+
const runtimes = new Map();
|
|
31
|
+
const instance = (0, app_1.createApp)(runtimes);
|
|
32
|
+
exports.app = instance.app;
|
|
33
|
+
exports.default = exports.app;
|
|
34
|
+
const PORT = process.env.PORT || 3001;
|
|
35
|
+
const defaultCompanionConfig = {
|
|
36
|
+
id: 'default',
|
|
37
|
+
name: 'Siduri',
|
|
38
|
+
brain: { provider: 'openrouter', model: 'gpt-4o-mini' },
|
|
39
|
+
voice: { provider: 'voicevox', speakerId: 1 },
|
|
40
|
+
memory: { provider: 'sqlite' },
|
|
41
|
+
knowledge: {
|
|
42
|
+
provider: process.env.SIDURI_KNOWLEDGE_PROVIDER || 'unified',
|
|
43
|
+
lifeDatabase: true,
|
|
44
|
+
dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite',
|
|
45
|
+
packPath: process.env.SIDURI_KNOWLEDGE_PACK || '',
|
|
46
|
+
registryUrl: process.env.SIDURI_KNOWLEDGE_REGISTRY_URL || '',
|
|
47
|
+
packId: process.env.SIDURI_KNOWLEDGE_PACK_ID || '',
|
|
48
|
+
timeoutMs: Number(process.env.SIDURI_KNOWLEDGE_TIMEOUT_MS || 5000),
|
|
49
|
+
preferredMode: process.env.SIDURI_KNOWLEDGE_MODE || 'lexical',
|
|
50
|
+
},
|
|
51
|
+
behavior: { provider: 'active_self' },
|
|
52
|
+
body: {
|
|
53
|
+
provider: 'live2d',
|
|
54
|
+
},
|
|
55
|
+
vision: { provider: 'openrouter', model: 'gpt-4-vision' }
|
|
56
|
+
};
|
|
57
|
+
async function loadCompanionConfig() {
|
|
58
|
+
const configPath = process.env.SIDURI_CONFIG || node_path_1.default.resolve(process.cwd(), 'siduri.config.json');
|
|
59
|
+
let fileConfig = {};
|
|
60
|
+
try {
|
|
61
|
+
fileConfig = JSON.parse(await (0, promises_1.readFile)(configPath, 'utf8'));
|
|
62
|
+
console.log(`Loaded companion configuration from ${configPath}`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error?.code !== 'ENOENT')
|
|
66
|
+
throw new Error(`Unable to read ${configPath}: ${error.message}`);
|
|
67
|
+
console.log(`No ${configPath} found; using environment/default configuration.`);
|
|
68
|
+
}
|
|
69
|
+
const config = {
|
|
70
|
+
...defaultCompanionConfig,
|
|
71
|
+
...fileConfig,
|
|
72
|
+
id: fileConfig.id || defaultCompanionConfig.id,
|
|
73
|
+
brain: { ...defaultCompanionConfig.brain, ...fileConfig.brain },
|
|
74
|
+
voice: { ...defaultCompanionConfig.voice, ...fileConfig.voice },
|
|
75
|
+
memory: { ...defaultCompanionConfig.memory, ...fileConfig.memory },
|
|
76
|
+
knowledge: { ...defaultCompanionConfig.knowledge, ...fileConfig.knowledge },
|
|
77
|
+
behavior: { ...defaultCompanionConfig.behavior, ...fileConfig.behavior },
|
|
78
|
+
body: { ...defaultCompanionConfig.body, ...fileConfig.body },
|
|
79
|
+
vision: { ...defaultCompanionConfig.vision, ...fileConfig.vision },
|
|
80
|
+
};
|
|
81
|
+
if (process.env.SIDURI_KNOWLEDGE_PROVIDER)
|
|
82
|
+
config.knowledge.provider = process.env.SIDURI_KNOWLEDGE_PROVIDER;
|
|
83
|
+
if (process.env.SIDURI_KNOWLEDGE_PACK)
|
|
84
|
+
config.knowledge.packPath = process.env.SIDURI_KNOWLEDGE_PACK;
|
|
85
|
+
if (process.env.SIDURI_KNOWLEDGE_REGISTRY_URL)
|
|
86
|
+
config.knowledge.registryUrl = process.env.SIDURI_KNOWLEDGE_REGISTRY_URL;
|
|
87
|
+
if (process.env.SIDURI_KNOWLEDGE_PACK_ID)
|
|
88
|
+
config.knowledge.packId = process.env.SIDURI_KNOWLEDGE_PACK_ID;
|
|
89
|
+
if (process.env.SIDURI_KNOWLEDGE_MODE)
|
|
90
|
+
config.knowledge.preferredMode = process.env.SIDURI_KNOWLEDGE_MODE;
|
|
91
|
+
if (process.env.SIDURI_KNOWLEDGE_DB_PATH)
|
|
92
|
+
config.knowledge.dbPath = process.env.SIDURI_KNOWLEDGE_DB_PATH;
|
|
93
|
+
return config;
|
|
94
|
+
}
|
|
95
|
+
async function bootDefaultCompanion() {
|
|
96
|
+
if (runtimes.has('default'))
|
|
97
|
+
return;
|
|
98
|
+
console.log("Booting default companion...");
|
|
99
|
+
const config = await loadCompanionConfig();
|
|
100
|
+
const vision = (0, boot_1.createVision)(config.vision);
|
|
101
|
+
const observation = (0, boot_1.createObservation)(vision);
|
|
102
|
+
instance.setObservationOrgan(observation);
|
|
103
|
+
const runtime = await (0, boot_1.bootCompanion)('default', config, { observationOrgan: observation });
|
|
104
|
+
runtimes.set('default', runtime);
|
|
105
|
+
console.log("Default companion booted successfully.");
|
|
106
|
+
}
|
|
107
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
108
|
+
bootDefaultCompanion().then(() => {
|
|
109
|
+
exports.app.listen(Number(PORT), '127.0.0.1', () => {
|
|
110
|
+
console.log(`Siduri-X API running on port ${PORT} (127.0.0.1)`);
|
|
111
|
+
});
|
|
112
|
+
}).catch(e => {
|
|
113
|
+
console.error("Failed to boot default companion:", e);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const supertest_1 = __importDefault(require("supertest"));
|
|
7
|
+
const app_1 = require("./app");
|
|
8
|
+
describe('API Boundary Context Validation (P2 Route Integration)', () => {
|
|
9
|
+
const fakeRuntime = {
|
|
10
|
+
handleUserMessage: jest.fn().mockResolvedValue({
|
|
11
|
+
response: { subtitle_en: 'Hello there' },
|
|
12
|
+
metadata: {},
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
let app;
|
|
16
|
+
let runtimes;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
runtimes = new Map([['companion-a', fakeRuntime]]);
|
|
19
|
+
const created = (0, app_1.createApp)(runtimes);
|
|
20
|
+
app = created.app;
|
|
21
|
+
fakeRuntime.handleUserMessage.mockClear();
|
|
22
|
+
});
|
|
23
|
+
test('accepts valid anonymous chat request and maps through API boundary', async () => {
|
|
24
|
+
const res = await (0, supertest_1.default)(app)
|
|
25
|
+
.post('/chat')
|
|
26
|
+
.send({
|
|
27
|
+
id: 'companion-a',
|
|
28
|
+
message: 'Hello neutral world',
|
|
29
|
+
history: [],
|
|
30
|
+
});
|
|
31
|
+
expect(res.status).toBe(200);
|
|
32
|
+
expect(fakeRuntime.handleUserMessage).toHaveBeenCalledWith('Hello neutral world', expect.objectContaining({
|
|
33
|
+
companionId: 'companion-a',
|
|
34
|
+
conversation: expect.objectContaining({ channel: 'direct' }),
|
|
35
|
+
}), []);
|
|
36
|
+
});
|
|
37
|
+
test('accepts neutral context chat envelope at /chat route', async () => {
|
|
38
|
+
const res = await (0, supertest_1.default)(app)
|
|
39
|
+
.post('/chat')
|
|
40
|
+
.send({
|
|
41
|
+
companionId: 'companion-a',
|
|
42
|
+
context: {
|
|
43
|
+
actor: {
|
|
44
|
+
actorId: 'actor-a',
|
|
45
|
+
sessionId: 'session-a',
|
|
46
|
+
authorizationRole: 'viewer',
|
|
47
|
+
capabilities: ['chat:public'],
|
|
48
|
+
authenticated: false,
|
|
49
|
+
},
|
|
50
|
+
conversation: {
|
|
51
|
+
channel: 'public',
|
|
52
|
+
correlationId: 'corr-route-1',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
message: 'Hello structured context',
|
|
56
|
+
history: [],
|
|
57
|
+
});
|
|
58
|
+
expect(res.status).toBe(200);
|
|
59
|
+
expect(fakeRuntime.handleUserMessage).toHaveBeenCalledWith('Hello structured context', expect.objectContaining({
|
|
60
|
+
companionId: 'companion-a',
|
|
61
|
+
conversation: expect.objectContaining({ channel: 'public' }),
|
|
62
|
+
}), []);
|
|
63
|
+
});
|
|
64
|
+
test('rejects global primary_user subject with 400 and structured error', async () => {
|
|
65
|
+
const res = await (0, supertest_1.default)(app)
|
|
66
|
+
.post('/chat')
|
|
67
|
+
.send({
|
|
68
|
+
companionId: 'companion-a',
|
|
69
|
+
context: {
|
|
70
|
+
actor: {
|
|
71
|
+
actorId: 'actor-a',
|
|
72
|
+
sessionId: 'session-a',
|
|
73
|
+
authorizationRole: 'viewer',
|
|
74
|
+
capabilities: ['chat:public'],
|
|
75
|
+
authenticated: true,
|
|
76
|
+
},
|
|
77
|
+
conversation: {
|
|
78
|
+
channel: 'public',
|
|
79
|
+
correlationId: 'corr-err-2',
|
|
80
|
+
},
|
|
81
|
+
subject: {
|
|
82
|
+
subjectId: 'primary_user',
|
|
83
|
+
kind: 'actor',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
message: 'Forbidden primary user test',
|
|
87
|
+
});
|
|
88
|
+
expect(res.status).toBe(400);
|
|
89
|
+
expect(res.body.accepted).toBe(false);
|
|
90
|
+
expect(res.body.error.code).toBe('FORBIDDEN_CONTEXT');
|
|
91
|
+
expect(fakeRuntime.handleUserMessage).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
test('streams response chunks via POST /chat/stream', async () => {
|
|
94
|
+
fakeRuntime.mouth = {
|
|
95
|
+
stream: async function* () {
|
|
96
|
+
yield { utteranceId: 'utt-1', index: 1, deltaText: 'Hello', isComplete: false, medium: 'web' };
|
|
97
|
+
yield { utteranceId: 'utt-1', index: 2, deltaText: ' world', isComplete: false, medium: 'web' };
|
|
98
|
+
yield { utteranceId: 'utt-1', index: 3, deltaText: '', isComplete: true, medium: 'web' };
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const res = await (0, supertest_1.default)(app)
|
|
102
|
+
.post('/chat/stream')
|
|
103
|
+
.send({
|
|
104
|
+
id: 'companion-a',
|
|
105
|
+
message: 'Stream me',
|
|
106
|
+
});
|
|
107
|
+
expect(res.status).toBe(200);
|
|
108
|
+
expect(res.headers['content-type']).toContain('text/event-stream');
|
|
109
|
+
expect(res.text).toContain('event: staged');
|
|
110
|
+
expect(res.text).toContain('event: chunk');
|
|
111
|
+
expect(res.text).toContain('event: done');
|
|
112
|
+
});
|
|
113
|
+
test('handles barge-in interruption via POST /chat/interrupt', async () => {
|
|
114
|
+
fakeRuntime.mouth = { interrupt: jest.fn() };
|
|
115
|
+
const res = await (0, supertest_1.default)(app)
|
|
116
|
+
.post('/chat/interrupt')
|
|
117
|
+
.send({
|
|
118
|
+
companionId: 'companion-a',
|
|
119
|
+
reason: 'user_stop',
|
|
120
|
+
});
|
|
121
|
+
expect(res.status).toBe(200);
|
|
122
|
+
expect(res.body.interrupted).toBe(true);
|
|
123
|
+
expect(fakeRuntime.mouth.interrupt).toHaveBeenCalledWith('user_stop');
|
|
124
|
+
});
|
|
125
|
+
test('handles mouth interruption via POST /mouth/interrupt', async () => {
|
|
126
|
+
fakeRuntime.mouth = { interrupt: jest.fn() };
|
|
127
|
+
const res = await (0, supertest_1.default)(app)
|
|
128
|
+
.post('/mouth/interrupt')
|
|
129
|
+
.send({
|
|
130
|
+
companionId: 'companion-a',
|
|
131
|
+
reason: 'user_barge_in',
|
|
132
|
+
});
|
|
133
|
+
expect(res.status).toBe(200);
|
|
134
|
+
expect(res.body.interrupted).toBe(true);
|
|
135
|
+
expect(fakeRuntime.mouth.interrupt).toHaveBeenCalledWith('user_barge_in');
|
|
136
|
+
});
|
|
137
|
+
test('POST /chat passes explicit interaction mode (casual, teach, hybrid) to runtime', async () => {
|
|
138
|
+
const res = await (0, supertest_1.default)(app)
|
|
139
|
+
.post('/chat')
|
|
140
|
+
.send({
|
|
141
|
+
id: 'companion-a',
|
|
142
|
+
message: 'Casual banter',
|
|
143
|
+
mode: 'casual',
|
|
144
|
+
});
|
|
145
|
+
expect(res.status).toBe(200);
|
|
146
|
+
expect(fakeRuntime.handleUserMessage).toHaveBeenCalledWith('Casual banter', expect.objectContaining({
|
|
147
|
+
companionId: 'companion-a',
|
|
148
|
+
mode: 'casual',
|
|
149
|
+
}), []);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const supertest_1 = __importDefault(require("supertest"));
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const app_1 = require("./app");
|
|
10
|
+
const runtime_1 = require("./runtime");
|
|
11
|
+
const knowledge_1 = require("@sidurijs/knowledge");
|
|
12
|
+
describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
|
|
13
|
+
const testDbPath = node_path_1.default.resolve(__dirname, '../test-api-knowledge.sqlite');
|
|
14
|
+
let app;
|
|
15
|
+
let runtime;
|
|
16
|
+
let knowledge;
|
|
17
|
+
const mockAuthHeader = { 'Authorization': 'Bearer test-token' };
|
|
18
|
+
const cleanDb = () => {
|
|
19
|
+
for (const file of [testDbPath, `${testDbPath}-shm`, `${testDbPath}-wal`]) {
|
|
20
|
+
if (node_fs_1.default.existsSync(file)) {
|
|
21
|
+
try {
|
|
22
|
+
node_fs_1.default.unlinkSync(file);
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
process.env.AUTH_TOKEN = 'test-token';
|
|
30
|
+
cleanDb();
|
|
31
|
+
knowledge = new knowledge_1.UnifiedKnowledgeOrgan({
|
|
32
|
+
lifeDatabase: true,
|
|
33
|
+
dbPath: testDbPath,
|
|
34
|
+
});
|
|
35
|
+
const mockBrain = {
|
|
36
|
+
generatePlan: jest.fn().mockImplementation(async (ctx) => {
|
|
37
|
+
return {
|
|
38
|
+
speech: `I see context: ${ctx.contextPrompt || 'none'}`,
|
|
39
|
+
language: 'en',
|
|
40
|
+
};
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
const memory = {
|
|
44
|
+
initialize: async () => { },
|
|
45
|
+
proposeClaim: async (claim) => knowledge.lifeDb.db.proposeClaim(claim),
|
|
46
|
+
getClaims: async (limit) => knowledge.lifeDb.db.getAllClaims(undefined, limit || 500),
|
|
47
|
+
getPendingClaims: async (limit) => knowledge.lifeDb.db.getAllClaims(undefined, limit || 500).filter((c) => c.status === 'pending'),
|
|
48
|
+
approveClaim: async (id) => knowledge.lifeDb.db.approveClaim(id),
|
|
49
|
+
rejectClaim: async (id) => knowledge.lifeDb.db.rejectClaim(id),
|
|
50
|
+
searchClaims: async () => [],
|
|
51
|
+
getApprovedClaims: async () => [],
|
|
52
|
+
getDirectives: async () => [],
|
|
53
|
+
};
|
|
54
|
+
runtime = new runtime_1.SiduriRuntime('test-comp', { name: 'Test Companion', organs: { knowledge: { provider: 'unified', dbPath: testDbPath } } }, {
|
|
55
|
+
brain: mockBrain,
|
|
56
|
+
knowledge,
|
|
57
|
+
externalKnowledge: knowledge.eAdapter ?? knowledge,
|
|
58
|
+
memory,
|
|
59
|
+
});
|
|
60
|
+
await runtime.initialize();
|
|
61
|
+
const runtimes = new Map([['test-comp', runtime]]);
|
|
62
|
+
const instance = (0, app_1.createApp)(runtimes);
|
|
63
|
+
app = instance.app;
|
|
64
|
+
});
|
|
65
|
+
afterAll(async () => {
|
|
66
|
+
knowledge.close();
|
|
67
|
+
delete process.env.AUTH_TOKEN;
|
|
68
|
+
cleanDb();
|
|
69
|
+
});
|
|
70
|
+
test('seeds inventory item and queries via GET /knowledge/inventory', async () => {
|
|
71
|
+
await knowledge.inventory.saveItem({
|
|
72
|
+
id: 'inv-item-1',
|
|
73
|
+
companionId: 'test-comp',
|
|
74
|
+
entityName: 'Hydro Visor',
|
|
75
|
+
domain: 'hardware',
|
|
76
|
+
properties: { model: 'V1', resolution: '4K' },
|
|
77
|
+
updatedAt: new Date().toISOString(),
|
|
78
|
+
});
|
|
79
|
+
const res = await (0, supertest_1.default)(app)
|
|
80
|
+
.get('/knowledge/inventory?id=test-comp')
|
|
81
|
+
.set(mockAuthHeader);
|
|
82
|
+
expect(res.status).toBe(200);
|
|
83
|
+
expect(res.body.items).toHaveLength(1);
|
|
84
|
+
expect(res.body.items[0].entityName).toBe('Hydro Visor');
|
|
85
|
+
expect(res.body.items[0].domain).toBe('hardware');
|
|
86
|
+
});
|
|
87
|
+
test('seeds finance entry and queries via GET /knowledge/finance', async () => {
|
|
88
|
+
await knowledge.finance.addEntry({
|
|
89
|
+
id: 'fin-1',
|
|
90
|
+
companionId: 'test-comp',
|
|
91
|
+
category: 'subscription',
|
|
92
|
+
amount: -15.99,
|
|
93
|
+
currency: 'USD',
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
});
|
|
96
|
+
const res = await (0, supertest_1.default)(app)
|
|
97
|
+
.get('/knowledge/finance?id=test-comp')
|
|
98
|
+
.set(mockAuthHeader);
|
|
99
|
+
expect(res.status).toBe(200);
|
|
100
|
+
expect(res.body.entries).toHaveLength(1);
|
|
101
|
+
expect(res.body.entries[0].category).toBe('subscription');
|
|
102
|
+
expect(res.body.summary).toBeDefined();
|
|
103
|
+
expect(res.body.summary.totalExpenses).toBe(15.99);
|
|
104
|
+
});
|
|
105
|
+
test('queries life snapshot via GET /knowledge/life', async () => {
|
|
106
|
+
const res = await (0, supertest_1.default)(app)
|
|
107
|
+
.get('/knowledge/life?id=test-comp&q=Hydro')
|
|
108
|
+
.set(mockAuthHeader);
|
|
109
|
+
expect(res.status).toBe(200);
|
|
110
|
+
expect(res.body.matchedInventory).toHaveLength(1);
|
|
111
|
+
expect(res.body.matchedInventory[0].entityName).toBe('Hydro Visor');
|
|
112
|
+
expect(res.body.formattedContext).toContain('<life_context>');
|
|
113
|
+
});
|
|
114
|
+
test('chat request triggers Stream D and injects Life DB context into cognition prompt', async () => {
|
|
115
|
+
const res = await (0, supertest_1.default)(app)
|
|
116
|
+
.post('/chat')
|
|
117
|
+
.send({
|
|
118
|
+
id: 'test-comp',
|
|
119
|
+
message: 'Tell me about the Hydro Visor specs',
|
|
120
|
+
history: [],
|
|
121
|
+
});
|
|
122
|
+
expect(res.status).toBe(200);
|
|
123
|
+
expect(res.body.response.subtitle_en).toContain('Hydro Visor');
|
|
124
|
+
});
|
|
125
|
+
test('boot endpoint instantiates UnifiedKnowledgeOrgan with Life DB enabled', async () => {
|
|
126
|
+
const bootRes = await (0, supertest_1.default)(app)
|
|
127
|
+
.post('/boot')
|
|
128
|
+
.set(mockAuthHeader)
|
|
129
|
+
.send({
|
|
130
|
+
id: 'booted-comp',
|
|
131
|
+
config: {
|
|
132
|
+
name: 'Booted Companion',
|
|
133
|
+
organs: {
|
|
134
|
+
knowledge: {
|
|
135
|
+
provider: 'unified',
|
|
136
|
+
dbPath: testDbPath,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
expect(bootRes.status).toBe(200);
|
|
142
|
+
expect(bootRes.body.success).toBe(true);
|
|
143
|
+
// Verify the booted companion's knowledge organ is UnifiedKnowledgeOrgan with working Life DB
|
|
144
|
+
const lifeRes = await (0, supertest_1.default)(app)
|
|
145
|
+
.get('/knowledge/life?id=booted-comp')
|
|
146
|
+
.set(mockAuthHeader);
|
|
147
|
+
expect(lifeRes.status).toBe(200);
|
|
148
|
+
expect(lifeRes.body.matchedInventory).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
test('seeds and queries generic entities, events, and tasks', async () => {
|
|
151
|
+
// 1. Entities
|
|
152
|
+
await knowledge.entities.saveEntity({
|
|
153
|
+
id: 'ent-1',
|
|
154
|
+
companionId: 'test-comp',
|
|
155
|
+
name: 'Coffee Grinder',
|
|
156
|
+
entityType: 'item',
|
|
157
|
+
domain: 'kitchen',
|
|
158
|
+
properties: { burr: 'conical', setting: 14 },
|
|
159
|
+
});
|
|
160
|
+
const entRes = await (0, supertest_1.default)(app)
|
|
161
|
+
.get('/knowledge/entities?id=test-comp&domain=kitchen')
|
|
162
|
+
.set(mockAuthHeader);
|
|
163
|
+
expect(entRes.status).toBe(200);
|
|
164
|
+
expect(entRes.body.entities).toHaveLength(1);
|
|
165
|
+
expect(entRes.body.entities[0].name).toBe('Coffee Grinder');
|
|
166
|
+
expect(entRes.body.entities[0].properties.setting).toBe(14);
|
|
167
|
+
// 2. Events
|
|
168
|
+
await knowledge.events.addEvent({
|
|
169
|
+
id: 'evt-1',
|
|
170
|
+
companionId: 'test-comp',
|
|
171
|
+
stream: 'health:heartrate',
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
metricValue: 72,
|
|
174
|
+
metadata: { unit: 'bpm' },
|
|
175
|
+
});
|
|
176
|
+
const evtRes = await (0, supertest_1.default)(app)
|
|
177
|
+
.get('/knowledge/events?id=test-comp&stream=health:heartrate')
|
|
178
|
+
.set(mockAuthHeader);
|
|
179
|
+
expect(evtRes.status).toBe(200);
|
|
180
|
+
expect(evtRes.body.events).toHaveLength(1);
|
|
181
|
+
expect(evtRes.body.events[0].metricValue).toBe(72);
|
|
182
|
+
// 3. Tasks
|
|
183
|
+
await knowledge.tasks.saveTask({
|
|
184
|
+
id: 'task-1',
|
|
185
|
+
companionId: 'test-comp',
|
|
186
|
+
title: 'Order fresh espresso beans',
|
|
187
|
+
status: 'pending',
|
|
188
|
+
priority: 2,
|
|
189
|
+
});
|
|
190
|
+
const taskRes = await (0, supertest_1.default)(app)
|
|
191
|
+
.get('/knowledge/tasks?id=test-comp&status=pending')
|
|
192
|
+
.set(mockAuthHeader);
|
|
193
|
+
expect(taskRes.status).toBe(200);
|
|
194
|
+
expect(taskRes.body.tasks).toHaveLength(1);
|
|
195
|
+
expect(taskRes.body.tasks[0].title).toBe('Order fresh espresso beans');
|
|
196
|
+
});
|
|
197
|
+
test('Truth Gate candidate proposal approval commits Life DB mutations', async () => {
|
|
198
|
+
// Stage a candidate proposal targeting Life DB
|
|
199
|
+
const proposal = await knowledge.lifeDb.db.proposeClaim({
|
|
200
|
+
id: 'prop-task-gate',
|
|
201
|
+
companionId: 'test-comp',
|
|
202
|
+
subject: 'task:buy-filter',
|
|
203
|
+
predicate: 'is',
|
|
204
|
+
value: 'Buy paper filters',
|
|
205
|
+
status: 'pending',
|
|
206
|
+
evidence: {
|
|
207
|
+
type: 'task',
|
|
208
|
+
title: 'Buy paper filters',
|
|
209
|
+
status: 'todo',
|
|
210
|
+
priority: 1,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
expect(proposal.status).toBe('pending');
|
|
214
|
+
// Confirm it is not yet in tasks
|
|
215
|
+
const beforeRes = await (0, supertest_1.default)(app)
|
|
216
|
+
.get('/knowledge/tasks?id=test-comp')
|
|
217
|
+
.set(mockAuthHeader);
|
|
218
|
+
expect(beforeRes.body.tasks.some((t) => t.title === 'Buy paper filters')).toBe(false);
|
|
219
|
+
// Approve via Truth Gate endpoint
|
|
220
|
+
const approveRes = await (0, supertest_1.default)(app)
|
|
221
|
+
.post('/knowledge/proposals/approve')
|
|
222
|
+
.set(mockAuthHeader)
|
|
223
|
+
.send({ companionId: 'test-comp', id: 'prop-task-gate' });
|
|
224
|
+
expect(approveRes.status).toBe(200);
|
|
225
|
+
expect(approveRes.body.approved).toBe(true);
|
|
226
|
+
expect(approveRes.body.target).toBe('knowledge');
|
|
227
|
+
// Confirm it is committed to Life DB tasks
|
|
228
|
+
const afterRes = await (0, supertest_1.default)(app)
|
|
229
|
+
.get('/knowledge/tasks?id=test-comp')
|
|
230
|
+
.set(mockAuthHeader);
|
|
231
|
+
expect(afterRes.status).toBe(200);
|
|
232
|
+
const approvedTask = afterRes.body.tasks.find((t) => t.title === 'Buy paper filters');
|
|
233
|
+
expect(approvedTask).toBeDefined();
|
|
234
|
+
expect(approvedTask.status).toBe('todo');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@sidurijs/core';
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("@sidurijs/core"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|