@siduri-x/api 2.0.2 → 2.0.4
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 +14 -14
- package/dist/app.js +35 -10
- package/dist/context-mapper.js +2 -0
- package/dist/context-mapper.test.js +35 -0
- package/dist/index.test.js +14 -0
- package/dist/teach-mode.test.js +67 -0
- package/package.json +8 -8
- package/siduri.sqlite +0 -0
- package/src/app.ts +37 -10
- package/src/context-mapper.test.ts +39 -0
- package/src/context-mapper.ts +2 -0
- package/src/index.test.ts +20 -0
- package/src/teach-mode.test.ts +77 -0
package/.turbo/turbo-build.log
CHANGED
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
|
|
2
|
-
> @siduri-x/api@2.0.
|
|
2
|
+
> @siduri-x/api@2.0.4 test /home/zagin/Projects/vxnus-studio/projects/siduri-x/apps/api
|
|
3
3
|
> jest --config jest.config.json
|
|
4
4
|
|
|
5
|
-
PASS src/
|
|
6
|
-
PASS src/index.test.ts (
|
|
7
|
-
PASS src/
|
|
8
|
-
PASS src/
|
|
9
|
-
PASS src/context-mapper.test.ts
|
|
10
|
-
PASS src/runtime.test.ts
|
|
5
|
+
PASS src/context-mapper.test.ts (9.405 s)
|
|
6
|
+
PASS src/index.test.ts (11.391 s)
|
|
7
|
+
PASS src/t5-experience.test.ts (11.412 s)
|
|
8
|
+
PASS src/runtime.test.ts (10.411 s)
|
|
11
9
|
● Console
|
|
12
10
|
|
|
13
11
|
console.error
|
|
@@ -28,16 +26,18 @@ PASS src/runtime.test.ts
|
|
|
28
26
|
at PerceptionPipeline.execute (../../packages/core/dist/perception-pipeline.js:21:34)
|
|
29
27
|
at Object.<anonymous> (src/runtime.test.ts:74:22)
|
|
30
28
|
|
|
29
|
+
PASS src/t4-gating.test.ts (10.904 s)
|
|
31
30
|
PASS src/auth.test.ts
|
|
32
31
|
PASS src/smoke.test.ts
|
|
33
|
-
PASS src/
|
|
34
|
-
PASS src/
|
|
35
|
-
PASS src/
|
|
36
|
-
PASS src/t6-security.test.ts (
|
|
37
|
-
PASS src/
|
|
32
|
+
PASS src/b0-b6.test.ts
|
|
33
|
+
PASS src/t7-release.test.ts
|
|
34
|
+
PASS src/knowledge.test.ts
|
|
35
|
+
PASS src/t6-security.test.ts (12.086 s)
|
|
36
|
+
PASS src/boot.test.ts
|
|
37
|
+
PASS src/teach-mode.test.ts (11.464 s)
|
|
38
38
|
[999D[K
|
|
39
39
|
Test Suites: 13 passed, 13 total
|
|
40
|
-
Tests:
|
|
40
|
+
Tests: 90 passed, 90 total
|
|
41
41
|
Snapshots: 0 total
|
|
42
|
-
Time:
|
|
42
|
+
Time: 14.772 s, estimated 16 s
|
|
43
43
|
Ran all test suites.
|
package/dist/app.js
CHANGED
|
@@ -114,12 +114,31 @@ function createApp(runtimes = new Map()) {
|
|
|
114
114
|
companionId,
|
|
115
115
|
name: manifest.identity.name,
|
|
116
116
|
archetype: manifest.identity.archetype,
|
|
117
|
+
origin: manifest.identity.origin,
|
|
118
|
+
ethos: manifest.identity.ethos,
|
|
117
119
|
version: manifest.version || '1.0.0',
|
|
118
120
|
updatedAt: new Date().toISOString(),
|
|
119
121
|
});
|
|
120
122
|
if (manifest.personality) {
|
|
121
123
|
await repo.setPersonality(companionId, manifest.personality);
|
|
122
124
|
}
|
|
125
|
+
if (Array.isArray(manifest.relationships)) {
|
|
126
|
+
for (const rel of manifest.relationships) {
|
|
127
|
+
if (rel && rel.entityId) {
|
|
128
|
+
await repo.updateRelationship(companionId, {
|
|
129
|
+
companionId,
|
|
130
|
+
entityId: rel.entityId,
|
|
131
|
+
entityType: 'human',
|
|
132
|
+
role: rel.role || 'user',
|
|
133
|
+
stance: rel.stance || 'neutral',
|
|
134
|
+
interactionConventions: rel.conventions || [],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(manifest.dialogueExamples) && repo.setExemplars) {
|
|
140
|
+
await repo.setExemplars(companionId, manifest.dialogueExamples);
|
|
141
|
+
}
|
|
123
142
|
if (directivesToCommit.length > 0) {
|
|
124
143
|
await repo.commitDirectives(companionId, directivesToCommit);
|
|
125
144
|
}
|
|
@@ -169,6 +188,7 @@ function createApp(runtimes = new Map()) {
|
|
|
169
188
|
context: mappingResult.context,
|
|
170
189
|
history,
|
|
171
190
|
...(req.body?.medium ? { medium: req.body.medium } : {}),
|
|
191
|
+
subtitleLanguage: req.body?.subtitleLanguage || req.body?.subtitle_language,
|
|
172
192
|
});
|
|
173
193
|
res.json(response);
|
|
174
194
|
}
|
|
@@ -224,9 +244,10 @@ function createApp(runtimes = new Map()) {
|
|
|
224
244
|
history,
|
|
225
245
|
medium: 'web',
|
|
226
246
|
signal: abortController.signal,
|
|
247
|
+
subtitleLanguage: req.body?.subtitleLanguage || req.body?.subtitle_language,
|
|
227
248
|
});
|
|
228
|
-
res.write(`event: staged\ndata: ${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status })}\n\n`);
|
|
229
|
-
const avatarEvent = response.metadata?.events?.find((e) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval === 'APPROVED' || !e.approval));
|
|
249
|
+
res.write(`event: staged\ndata: ${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status, mode: response.metadata?.mode })}\n\n`);
|
|
250
|
+
const avatarEvent = response.metadata?.events?.find((e) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval?.toLowerCase() === 'approved' || e.approval === 'APPROVED' || !e.approval));
|
|
230
251
|
if (avatarEvent) {
|
|
231
252
|
res.write(`event: avatar\ndata: ${JSON.stringify(avatarEvent)}\n\n`);
|
|
232
253
|
}
|
|
@@ -346,7 +367,9 @@ function createApp(runtimes = new Map()) {
|
|
|
346
367
|
if (!runtime.memory)
|
|
347
368
|
return res.json({ claims: [] });
|
|
348
369
|
try {
|
|
349
|
-
const claims =
|
|
370
|
+
const claims = typeof runtime.memory.getAllClaims === 'function'
|
|
371
|
+
? await runtime.memory.getAllClaims()
|
|
372
|
+
: await runtime.memory.getClaims();
|
|
350
373
|
res.json({ claims });
|
|
351
374
|
}
|
|
352
375
|
catch (e) {
|
|
@@ -361,7 +384,9 @@ function createApp(runtimes = new Map()) {
|
|
|
361
384
|
if (!runtime.memory)
|
|
362
385
|
return res.json({ directives: [] });
|
|
363
386
|
try {
|
|
364
|
-
const directives =
|
|
387
|
+
const directives = typeof runtime.memory.getAllDirectives === 'function'
|
|
388
|
+
? await runtime.memory.getAllDirectives()
|
|
389
|
+
: await runtime.memory.getDirectives();
|
|
365
390
|
res.json({ directives });
|
|
366
391
|
}
|
|
367
392
|
catch (e) {
|
|
@@ -486,7 +511,7 @@ function createApp(runtimes = new Map()) {
|
|
|
486
511
|
return res.status(400).json({ error: "Memory organ not configured" });
|
|
487
512
|
try {
|
|
488
513
|
await runtime.memory.approveClaim(req.body.id);
|
|
489
|
-
res.json({ approved: true });
|
|
514
|
+
res.json({ approved: true, status: 'approved' });
|
|
490
515
|
}
|
|
491
516
|
catch (e) {
|
|
492
517
|
res.status(500).json({ error: e.message });
|
|
@@ -501,7 +526,7 @@ function createApp(runtimes = new Map()) {
|
|
|
501
526
|
return res.status(400).json({ error: "Memory organ not configured" });
|
|
502
527
|
try {
|
|
503
528
|
await runtime.memory.rejectClaim(req.body.id);
|
|
504
|
-
res.json({ rejected: true });
|
|
529
|
+
res.json({ rejected: true, status: 'rejected' });
|
|
505
530
|
}
|
|
506
531
|
catch (e) {
|
|
507
532
|
res.status(500).json({ error: e.message });
|
|
@@ -517,7 +542,7 @@ function createApp(runtimes = new Map()) {
|
|
|
517
542
|
return res.status(400).json({ error: "Memory organ not configured" });
|
|
518
543
|
try {
|
|
519
544
|
await runtime.memory.approveDirective(req.body.id);
|
|
520
|
-
res.json({ approved: true });
|
|
545
|
+
res.json({ approved: true, status: 'active' });
|
|
521
546
|
}
|
|
522
547
|
catch (e) {
|
|
523
548
|
res.status(500).json({ error: e.message });
|
|
@@ -532,7 +557,7 @@ function createApp(runtimes = new Map()) {
|
|
|
532
557
|
return res.status(400).json({ error: "Memory organ not configured" });
|
|
533
558
|
try {
|
|
534
559
|
await runtime.memory.rejectDirective(req.body.id);
|
|
535
|
-
res.json({ rejected: true });
|
|
560
|
+
res.json({ rejected: true, status: 'rejected' });
|
|
536
561
|
}
|
|
537
562
|
catch (e) {
|
|
538
563
|
res.status(500).json({ error: e.message });
|
|
@@ -547,7 +572,7 @@ function createApp(runtimes = new Map()) {
|
|
|
547
572
|
return res.status(400).json({ error: "Memory organ not configured" });
|
|
548
573
|
try {
|
|
549
574
|
await runtime.memory.revokeDirective(req.body.id);
|
|
550
|
-
res.json({ revoked: true });
|
|
575
|
+
res.json({ revoked: true, status: 'revoked' });
|
|
551
576
|
}
|
|
552
577
|
catch (e) {
|
|
553
578
|
res.status(500).json({ error: e.message });
|
|
@@ -562,7 +587,7 @@ function createApp(runtimes = new Map()) {
|
|
|
562
587
|
return res.status(400).json({ error: "Memory organ not configured" });
|
|
563
588
|
try {
|
|
564
589
|
await runtime.memory.disableDirective(req.body.id);
|
|
565
|
-
res.json({ disabled: true });
|
|
590
|
+
res.json({ disabled: true, status: 'disabled' });
|
|
566
591
|
}
|
|
567
592
|
catch (e) {
|
|
568
593
|
res.status(500).json({ error: e.message });
|
package/dist/context-mapper.js
CHANGED
|
@@ -115,6 +115,7 @@ function mapRequestContext(input, options = {}) {
|
|
|
115
115
|
},
|
|
116
116
|
source: input.source || rawCtx.source || (isAuthenticated ? 'local' : 'external'),
|
|
117
117
|
subject: rawCtx.subject,
|
|
118
|
+
mode: rawCtx.mode || input.mode,
|
|
118
119
|
};
|
|
119
120
|
const validated = (0, core_1.validateRequestContext)(constructed);
|
|
120
121
|
if (!validated.accepted) {
|
|
@@ -218,6 +219,7 @@ function mapRequestContext(input, options = {}) {
|
|
|
218
219
|
},
|
|
219
220
|
source: input.source || (isAuthenticated ? 'local' : 'external'),
|
|
220
221
|
subject: input.subject,
|
|
222
|
+
mode: input.mode || input.conversation?.mode,
|
|
221
223
|
};
|
|
222
224
|
const validated = (0, core_1.validateRequestContext)(mappedContext);
|
|
223
225
|
if (!validated.accepted) {
|
|
@@ -249,4 +249,39 @@ describe('API Request Context Mapper (Single-Owner, Single-Machine)', () => {
|
|
|
249
249
|
expect(attenuatedResult.context?.actor.authorizationRole).toBe('viewer');
|
|
250
250
|
expect(attenuatedResult.diagnostics).not.toContain('role_escalation_attempt_suppressed');
|
|
251
251
|
});
|
|
252
|
+
describe('Mode Mapping (Casual, Teach, Hybrid)', () => {
|
|
253
|
+
test('maps explicit mode parameter to RequestContext', () => {
|
|
254
|
+
const casualResult = (0, context_mapper_1.mapRequestContext)({
|
|
255
|
+
companionId: 'comp-1',
|
|
256
|
+
mode: 'casual',
|
|
257
|
+
generateCorrelationId: true,
|
|
258
|
+
});
|
|
259
|
+
expect(casualResult.accepted).toBe(true);
|
|
260
|
+
expect(casualResult.context?.mode).toBe('casual');
|
|
261
|
+
const teachResult = (0, context_mapper_1.mapRequestContext)({
|
|
262
|
+
companionId: 'comp-1',
|
|
263
|
+
mode: 'teach',
|
|
264
|
+
generateCorrelationId: true,
|
|
265
|
+
});
|
|
266
|
+
expect(teachResult.accepted).toBe(true);
|
|
267
|
+
expect(teachResult.context?.mode).toBe('teach');
|
|
268
|
+
const hybridResult = (0, context_mapper_1.mapRequestContext)({
|
|
269
|
+
companionId: 'comp-1',
|
|
270
|
+
mode: 'hybrid',
|
|
271
|
+
generateCorrelationId: true,
|
|
272
|
+
});
|
|
273
|
+
expect(hybridResult.accepted).toBe(true);
|
|
274
|
+
expect(hybridResult.context?.mode).toBe('hybrid');
|
|
275
|
+
});
|
|
276
|
+
test('rejects invalid interaction mode', () => {
|
|
277
|
+
const invalidResult = (0, context_mapper_1.mapRequestContext)({
|
|
278
|
+
companionId: 'comp-1',
|
|
279
|
+
mode: 'super_turbo',
|
|
280
|
+
generateCorrelationId: true,
|
|
281
|
+
});
|
|
282
|
+
expect(invalidResult.accepted).toBe(false);
|
|
283
|
+
expect(invalidResult.error?.code).toBe('INVALID_CONTEXT');
|
|
284
|
+
expect(invalidResult.error?.field).toBe('mode');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
252
287
|
});
|
package/dist/index.test.js
CHANGED
|
@@ -134,4 +134,18 @@ describe('API Boundary Context Validation (P2 Route Integration)', () => {
|
|
|
134
134
|
expect(res.body.interrupted).toBe(true);
|
|
135
135
|
expect(fakeRuntime.mouth.interrupt).toHaveBeenCalledWith('user_barge_in');
|
|
136
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
|
+
});
|
|
137
151
|
});
|
package/dist/teach-mode.test.js
CHANGED
|
@@ -14,6 +14,8 @@ jest.mock('@siduri-x/self', () => {
|
|
|
14
14
|
SqliteSelfRepository: jest.fn().mockImplementation(() => ({
|
|
15
15
|
setIdentity: jest.fn().mockResolvedValue(undefined),
|
|
16
16
|
setPersonality: jest.fn().mockResolvedValue(undefined),
|
|
17
|
+
updateRelationship: jest.fn().mockResolvedValue(undefined),
|
|
18
|
+
setExemplars: jest.fn().mockResolvedValue(undefined),
|
|
17
19
|
commitDirectives: jest.fn().mockResolvedValue(undefined),
|
|
18
20
|
close: jest.fn(),
|
|
19
21
|
})),
|
|
@@ -133,4 +135,69 @@ kind: "other"
|
|
|
133
135
|
const MockRepo = self_1.SqliteSelfRepository;
|
|
134
136
|
expect(MockRepo.mock.instances.length).toBe(0);
|
|
135
137
|
});
|
|
138
|
+
it('POST /teach/install-self installs v2.0 manifest with ethos, relationships, and exemplars', async () => {
|
|
139
|
+
const v2Manifest = {
|
|
140
|
+
specVersion: '2.0.0',
|
|
141
|
+
identity: {
|
|
142
|
+
name: 'Siduri',
|
|
143
|
+
archetype: 'System Sentinel',
|
|
144
|
+
origin: 'Ancient mythos',
|
|
145
|
+
ethos: 'Protector of the realm and loyal companion to creator',
|
|
146
|
+
},
|
|
147
|
+
version: '2.0.0',
|
|
148
|
+
relationships: [
|
|
149
|
+
{
|
|
150
|
+
entityId: 'actor:zagin',
|
|
151
|
+
role: 'creator',
|
|
152
|
+
stance: 'familiar_loyal',
|
|
153
|
+
conventions: ['Direct communication', 'Highest administrative trust'],
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
dialogueExamples: [
|
|
157
|
+
{
|
|
158
|
+
user: 'Deploy current branch',
|
|
159
|
+
assistant: 'Deploying to staging now, boss.',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
directives: [
|
|
163
|
+
{ id: 'dir-rel-1', directive: 'Honor creator root privileges' },
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
const res = await (0, supertest_1.default)(app)
|
|
167
|
+
.post('/teach/install-self')
|
|
168
|
+
.set(mockAuthHeader)
|
|
169
|
+
.send({
|
|
170
|
+
companionId: 'comp-v2',
|
|
171
|
+
manifest: v2Manifest,
|
|
172
|
+
approvedDirectiveIds: ['dir-rel-1'],
|
|
173
|
+
});
|
|
174
|
+
expect(res.status).toBe(200);
|
|
175
|
+
expect(res.body.success).toBe(true);
|
|
176
|
+
const MockRepo = self_1.SqliteSelfRepository;
|
|
177
|
+
const repoInstance = MockRepo.mock.results[MockRepo.mock.results.length - 1].value;
|
|
178
|
+
expect(repoInstance.setIdentity).toHaveBeenCalledWith(expect.objectContaining({
|
|
179
|
+
companionId: 'comp-v2',
|
|
180
|
+
name: 'Siduri',
|
|
181
|
+
archetype: 'System Sentinel',
|
|
182
|
+
origin: 'Ancient mythos',
|
|
183
|
+
ethos: 'Protector of the realm and loyal companion to creator',
|
|
184
|
+
}));
|
|
185
|
+
expect(repoInstance.updateRelationship).toHaveBeenCalledWith('comp-v2', {
|
|
186
|
+
companionId: 'comp-v2',
|
|
187
|
+
entityId: 'actor:zagin',
|
|
188
|
+
entityType: 'human',
|
|
189
|
+
role: 'creator',
|
|
190
|
+
stance: 'familiar_loyal',
|
|
191
|
+
interactionConventions: ['Direct communication', 'Highest administrative trust'],
|
|
192
|
+
});
|
|
193
|
+
expect(repoInstance.setExemplars).toHaveBeenCalledWith('comp-v2', [
|
|
194
|
+
{
|
|
195
|
+
user: 'Deploy current branch',
|
|
196
|
+
assistant: 'Deploying to staging now, boss.',
|
|
197
|
+
},
|
|
198
|
+
]);
|
|
199
|
+
expect(repoInstance.commitDirectives).toHaveBeenCalledWith('comp-v2', [
|
|
200
|
+
{ id: 'dir-rel-1', directive: 'Honor creator root privileges' },
|
|
201
|
+
]);
|
|
202
|
+
});
|
|
136
203
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siduri-x/api",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
"dotenv": "^17.4.2",
|
|
14
14
|
"express": "^5.2.1",
|
|
15
15
|
"@siduri-x/body": "2.0.1",
|
|
16
|
-
"@siduri-x/brain": "2.0.
|
|
17
|
-
"@siduri-x/core": "2.0.2",
|
|
16
|
+
"@siduri-x/brain": "2.0.2",
|
|
18
17
|
"@siduri-x/ear": "2.0.1",
|
|
18
|
+
"@siduri-x/core": "2.0.4",
|
|
19
19
|
"@siduri-x/eknowledge": "2.0.1",
|
|
20
20
|
"@siduri-x/hands": "2.0.1",
|
|
21
|
-
"@siduri-x/knowledge": "2.0.
|
|
22
|
-
"@siduri-x/memory": "2.0.
|
|
23
|
-
"@siduri-x/mouth": "2.0.
|
|
21
|
+
"@siduri-x/knowledge": "2.0.3",
|
|
22
|
+
"@siduri-x/memory": "2.0.2",
|
|
23
|
+
"@siduri-x/mouth": "2.0.2",
|
|
24
24
|
"@siduri-x/observation": "2.0.1",
|
|
25
|
-
"@siduri-x/self": "2.0.
|
|
25
|
+
"@siduri-x/self": "2.0.2",
|
|
26
26
|
"@siduri-x/vision": "2.0.1",
|
|
27
|
-
"@siduri-x/voice": "2.0.
|
|
27
|
+
"@siduri-x/voice": "2.0.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/cors": "^2.8.19",
|
package/siduri.sqlite
ADDED
|
Binary file
|
package/src/app.ts
CHANGED
|
@@ -136,6 +136,8 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
136
136
|
companionId,
|
|
137
137
|
name: manifest.identity.name,
|
|
138
138
|
archetype: manifest.identity.archetype,
|
|
139
|
+
origin: manifest.identity.origin,
|
|
140
|
+
ethos: manifest.identity.ethos,
|
|
139
141
|
version: manifest.version || '1.0.0',
|
|
140
142
|
updatedAt: new Date().toISOString(),
|
|
141
143
|
});
|
|
@@ -144,6 +146,25 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
144
146
|
await repo.setPersonality(companionId, manifest.personality);
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
if (Array.isArray(manifest.relationships)) {
|
|
150
|
+
for (const rel of manifest.relationships) {
|
|
151
|
+
if (rel && rel.entityId) {
|
|
152
|
+
await repo.updateRelationship(companionId, {
|
|
153
|
+
companionId,
|
|
154
|
+
entityId: rel.entityId,
|
|
155
|
+
entityType: 'human',
|
|
156
|
+
role: rel.role || 'user',
|
|
157
|
+
stance: rel.stance || 'neutral',
|
|
158
|
+
interactionConventions: rel.conventions || [],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (Array.isArray(manifest.dialogueExamples) && repo.setExemplars) {
|
|
165
|
+
await repo.setExemplars(companionId, manifest.dialogueExamples);
|
|
166
|
+
}
|
|
167
|
+
|
|
147
168
|
if (directivesToCommit.length > 0) {
|
|
148
169
|
await repo.commitDirectives(companionId, directivesToCommit);
|
|
149
170
|
}
|
|
@@ -197,6 +218,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
197
218
|
context: mappingResult.context,
|
|
198
219
|
history,
|
|
199
220
|
...(req.body?.medium ? { medium: req.body.medium } : {}),
|
|
221
|
+
subtitleLanguage: req.body?.subtitleLanguage || req.body?.subtitle_language,
|
|
200
222
|
});
|
|
201
223
|
res.json(response);
|
|
202
224
|
} catch (e: any) {
|
|
@@ -258,12 +280,13 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
258
280
|
history,
|
|
259
281
|
medium: 'web',
|
|
260
282
|
signal: abortController.signal,
|
|
283
|
+
subtitleLanguage: req.body?.subtitleLanguage || req.body?.subtitle_language,
|
|
261
284
|
});
|
|
262
285
|
|
|
263
|
-
res.write(`event: staged\ndata: ${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status })}\n\n`);
|
|
286
|
+
res.write(`event: staged\ndata: ${JSON.stringify({ response_id: response.response_id, correlation_id: response.correlation_id, status: response.status, mode: response.metadata?.mode })}\n\n`);
|
|
264
287
|
|
|
265
288
|
const avatarEvent = response.metadata?.events?.find(
|
|
266
|
-
(e: any) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval === 'APPROVED' || !e.approval)
|
|
289
|
+
(e: any) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval?.toLowerCase() === 'approved' || e.approval === 'APPROVED' || !e.approval)
|
|
267
290
|
);
|
|
268
291
|
if (avatarEvent) {
|
|
269
292
|
res.write(`event: avatar\ndata: ${JSON.stringify(avatarEvent)}\n\n`);
|
|
@@ -384,7 +407,9 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
384
407
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
385
408
|
if (!runtime.memory) return res.json({ claims: [] });
|
|
386
409
|
try {
|
|
387
|
-
const claims =
|
|
410
|
+
const claims = typeof (runtime.memory as any).getAllClaims === 'function'
|
|
411
|
+
? await (runtime.memory as any).getAllClaims()
|
|
412
|
+
: await runtime.memory.getClaims();
|
|
388
413
|
res.json({ claims });
|
|
389
414
|
} catch (e: any) {
|
|
390
415
|
res.status(500).json({ error: e.message });
|
|
@@ -397,7 +422,9 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
397
422
|
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
398
423
|
if (!runtime.memory) return res.json({ directives: [] });
|
|
399
424
|
try {
|
|
400
|
-
const directives =
|
|
425
|
+
const directives = typeof (runtime.memory as any).getAllDirectives === 'function'
|
|
426
|
+
? await (runtime.memory as any).getAllDirectives()
|
|
427
|
+
: await runtime.memory.getDirectives();
|
|
401
428
|
res.json({ directives });
|
|
402
429
|
} catch (e: any) {
|
|
403
430
|
res.status(500).json({ error: e.message });
|
|
@@ -514,7 +541,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
514
541
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
515
542
|
try {
|
|
516
543
|
await runtime.memory.approveClaim(req.body.id);
|
|
517
|
-
res.json({ approved: true });
|
|
544
|
+
res.json({ approved: true, status: 'approved' });
|
|
518
545
|
} catch (e: any) {
|
|
519
546
|
res.status(500).json({ error: e.message });
|
|
520
547
|
}
|
|
@@ -527,7 +554,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
527
554
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
528
555
|
try {
|
|
529
556
|
await runtime.memory.rejectClaim(req.body.id);
|
|
530
|
-
res.json({ rejected: true });
|
|
557
|
+
res.json({ rejected: true, status: 'rejected' });
|
|
531
558
|
} catch (e: any) {
|
|
532
559
|
res.status(500).json({ error: e.message });
|
|
533
560
|
}
|
|
@@ -541,7 +568,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
541
568
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
542
569
|
try {
|
|
543
570
|
await runtime.memory.approveDirective(req.body.id);
|
|
544
|
-
res.json({ approved: true });
|
|
571
|
+
res.json({ approved: true, status: 'active' });
|
|
545
572
|
} catch (e: any) {
|
|
546
573
|
res.status(500).json({ error: e.message });
|
|
547
574
|
}
|
|
@@ -554,7 +581,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
554
581
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
555
582
|
try {
|
|
556
583
|
await runtime.memory.rejectDirective(req.body.id);
|
|
557
|
-
res.json({ rejected: true });
|
|
584
|
+
res.json({ rejected: true, status: 'rejected' });
|
|
558
585
|
} catch (e: any) {
|
|
559
586
|
res.status(500).json({ error: e.message });
|
|
560
587
|
}
|
|
@@ -567,7 +594,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
567
594
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
568
595
|
try {
|
|
569
596
|
await runtime.memory.revokeDirective(req.body.id);
|
|
570
|
-
res.json({ revoked: true });
|
|
597
|
+
res.json({ revoked: true, status: 'revoked' });
|
|
571
598
|
} catch (e: any) {
|
|
572
599
|
res.status(500).json({ error: e.message });
|
|
573
600
|
}
|
|
@@ -580,7 +607,7 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
580
607
|
if (!runtime.memory) return res.status(400).json({ error: "Memory organ not configured" });
|
|
581
608
|
try {
|
|
582
609
|
await runtime.memory.disableDirective(req.body.id);
|
|
583
|
-
res.json({ disabled: true });
|
|
610
|
+
res.json({ disabled: true, status: 'disabled' });
|
|
584
611
|
} catch (e: any) {
|
|
585
612
|
res.status(500).json({ error: e.message });
|
|
586
613
|
}
|
|
@@ -277,4 +277,43 @@ describe('API Request Context Mapper (Single-Owner, Single-Machine)', () => {
|
|
|
277
277
|
expect(attenuatedResult.context?.actor.authorizationRole).toBe('viewer');
|
|
278
278
|
expect(attenuatedResult.diagnostics).not.toContain('role_escalation_attempt_suppressed');
|
|
279
279
|
});
|
|
280
|
+
|
|
281
|
+
describe('Mode Mapping (Casual, Teach, Hybrid)', () => {
|
|
282
|
+
test('maps explicit mode parameter to RequestContext', () => {
|
|
283
|
+
const casualResult = mapRequestContext({
|
|
284
|
+
companionId: 'comp-1',
|
|
285
|
+
mode: 'casual',
|
|
286
|
+
generateCorrelationId: true,
|
|
287
|
+
});
|
|
288
|
+
expect(casualResult.accepted).toBe(true);
|
|
289
|
+
expect(casualResult.context?.mode).toBe('casual');
|
|
290
|
+
|
|
291
|
+
const teachResult = mapRequestContext({
|
|
292
|
+
companionId: 'comp-1',
|
|
293
|
+
mode: 'teach',
|
|
294
|
+
generateCorrelationId: true,
|
|
295
|
+
});
|
|
296
|
+
expect(teachResult.accepted).toBe(true);
|
|
297
|
+
expect(teachResult.context?.mode).toBe('teach');
|
|
298
|
+
|
|
299
|
+
const hybridResult = mapRequestContext({
|
|
300
|
+
companionId: 'comp-1',
|
|
301
|
+
mode: 'hybrid',
|
|
302
|
+
generateCorrelationId: true,
|
|
303
|
+
});
|
|
304
|
+
expect(hybridResult.accepted).toBe(true);
|
|
305
|
+
expect(hybridResult.context?.mode).toBe('hybrid');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('rejects invalid interaction mode', () => {
|
|
309
|
+
const invalidResult = mapRequestContext({
|
|
310
|
+
companionId: 'comp-1',
|
|
311
|
+
mode: 'super_turbo',
|
|
312
|
+
generateCorrelationId: true,
|
|
313
|
+
});
|
|
314
|
+
expect(invalidResult.accepted).toBe(false);
|
|
315
|
+
expect(invalidResult.error?.code).toBe('INVALID_CONTEXT');
|
|
316
|
+
expect(invalidResult.error?.field).toBe('mode');
|
|
317
|
+
});
|
|
318
|
+
});
|
|
280
319
|
});
|
package/src/context-mapper.ts
CHANGED
|
@@ -145,6 +145,7 @@ export function mapRequestContext(
|
|
|
145
145
|
},
|
|
146
146
|
source: input.source || rawCtx.source || (isAuthenticated ? 'local' : 'external'),
|
|
147
147
|
subject: rawCtx.subject,
|
|
148
|
+
mode: rawCtx.mode || input.mode,
|
|
148
149
|
};
|
|
149
150
|
|
|
150
151
|
const validated = validateRequestContext(constructed);
|
|
@@ -262,6 +263,7 @@ export function mapRequestContext(
|
|
|
262
263
|
},
|
|
263
264
|
source: input.source || (isAuthenticated ? 'local' : 'external'),
|
|
264
265
|
subject: input.subject,
|
|
266
|
+
mode: input.mode || input.conversation?.mode,
|
|
265
267
|
};
|
|
266
268
|
|
|
267
269
|
const validated = validateRequestContext(mappedContext);
|
package/src/index.test.ts
CHANGED
|
@@ -155,4 +155,24 @@ describe('API Boundary Context Validation (P2 Route Integration)', () => {
|
|
|
155
155
|
expect(res.body.interrupted).toBe(true);
|
|
156
156
|
expect(fakeRuntime.mouth.interrupt).toHaveBeenCalledWith('user_barge_in');
|
|
157
157
|
});
|
|
158
|
+
|
|
159
|
+
test('POST /chat passes explicit interaction mode (casual, teach, hybrid) to runtime', async () => {
|
|
160
|
+
const res = await request(app)
|
|
161
|
+
.post('/chat')
|
|
162
|
+
.send({
|
|
163
|
+
id: 'companion-a',
|
|
164
|
+
message: 'Casual banter',
|
|
165
|
+
mode: 'casual',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(res.status).toBe(200);
|
|
169
|
+
expect(fakeRuntime.handleUserMessage).toHaveBeenCalledWith(
|
|
170
|
+
'Casual banter',
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
companionId: 'companion-a',
|
|
173
|
+
mode: 'casual',
|
|
174
|
+
}),
|
|
175
|
+
[]
|
|
176
|
+
);
|
|
177
|
+
});
|
|
158
178
|
});
|
package/src/teach-mode.test.ts
CHANGED
|
@@ -10,6 +10,8 @@ jest.mock('@siduri-x/self', () => {
|
|
|
10
10
|
SqliteSelfRepository: jest.fn().mockImplementation(() => ({
|
|
11
11
|
setIdentity: jest.fn().mockResolvedValue(undefined),
|
|
12
12
|
setPersonality: jest.fn().mockResolvedValue(undefined),
|
|
13
|
+
updateRelationship: jest.fn().mockResolvedValue(undefined),
|
|
14
|
+
setExemplars: jest.fn().mockResolvedValue(undefined),
|
|
13
15
|
commitDirectives: jest.fn().mockResolvedValue(undefined),
|
|
14
16
|
close: jest.fn(),
|
|
15
17
|
})),
|
|
@@ -149,4 +151,79 @@ kind: "other"
|
|
|
149
151
|
const MockRepo = SqliteSelfRepository as jest.MockedClass<typeof SqliteSelfRepository>;
|
|
150
152
|
expect(MockRepo.mock.instances.length).toBe(0);
|
|
151
153
|
});
|
|
154
|
+
|
|
155
|
+
it('POST /teach/install-self installs v2.0 manifest with ethos, relationships, and exemplars', async () => {
|
|
156
|
+
const v2Manifest = {
|
|
157
|
+
specVersion: '2.0.0',
|
|
158
|
+
identity: {
|
|
159
|
+
name: 'Siduri',
|
|
160
|
+
archetype: 'System Sentinel',
|
|
161
|
+
origin: 'Ancient mythos',
|
|
162
|
+
ethos: 'Protector of the realm and loyal companion to creator',
|
|
163
|
+
},
|
|
164
|
+
version: '2.0.0',
|
|
165
|
+
relationships: [
|
|
166
|
+
{
|
|
167
|
+
entityId: 'actor:zagin',
|
|
168
|
+
role: 'creator',
|
|
169
|
+
stance: 'familiar_loyal',
|
|
170
|
+
conventions: ['Direct communication', 'Highest administrative trust'],
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
dialogueExamples: [
|
|
174
|
+
{
|
|
175
|
+
user: 'Deploy current branch',
|
|
176
|
+
assistant: 'Deploying to staging now, boss.',
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
directives: [
|
|
180
|
+
{ id: 'dir-rel-1', directive: 'Honor creator root privileges' },
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const res = await request(app)
|
|
185
|
+
.post('/teach/install-self')
|
|
186
|
+
.set(mockAuthHeader)
|
|
187
|
+
.send({
|
|
188
|
+
companionId: 'comp-v2',
|
|
189
|
+
manifest: v2Manifest,
|
|
190
|
+
approvedDirectiveIds: ['dir-rel-1'],
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(res.status).toBe(200);
|
|
194
|
+
expect(res.body.success).toBe(true);
|
|
195
|
+
|
|
196
|
+
const MockRepo = SqliteSelfRepository as jest.MockedClass<typeof SqliteSelfRepository>;
|
|
197
|
+
const repoInstance = MockRepo.mock.results[MockRepo.mock.results.length - 1].value;
|
|
198
|
+
|
|
199
|
+
expect(repoInstance.setIdentity).toHaveBeenCalledWith(
|
|
200
|
+
expect.objectContaining({
|
|
201
|
+
companionId: 'comp-v2',
|
|
202
|
+
name: 'Siduri',
|
|
203
|
+
archetype: 'System Sentinel',
|
|
204
|
+
origin: 'Ancient mythos',
|
|
205
|
+
ethos: 'Protector of the realm and loyal companion to creator',
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(repoInstance.updateRelationship).toHaveBeenCalledWith('comp-v2', {
|
|
210
|
+
companionId: 'comp-v2',
|
|
211
|
+
entityId: 'actor:zagin',
|
|
212
|
+
entityType: 'human',
|
|
213
|
+
role: 'creator',
|
|
214
|
+
stance: 'familiar_loyal',
|
|
215
|
+
interactionConventions: ['Direct communication', 'Highest administrative trust'],
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(repoInstance.setExemplars).toHaveBeenCalledWith('comp-v2', [
|
|
219
|
+
{
|
|
220
|
+
user: 'Deploy current branch',
|
|
221
|
+
assistant: 'Deploying to staging now, boss.',
|
|
222
|
+
},
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
expect(repoInstance.commitDirectives).toHaveBeenCalledWith('comp-v2', [
|
|
226
|
+
{ id: 'dir-rel-1', directive: 'Honor creator root privileges' },
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
152
229
|
});
|