@siduri-x/api 2.0.13 → 2.0.14
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 -4
- package/.turbo/turbo-test.log +13 -13
- package/dist/app.js +255 -0
- package/dist/boot.js +1 -1
- package/dist/knowledge.test.js +98 -0
- package/package.json +5 -5
- package/siduri.sqlite +0 -0
- package/src/app.ts +241 -0
- package/src/boot.ts +1 -1
- package/src/knowledge.test.ts +111 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
> @siduri-x/api@2.0.
|
|
3
|
-
> tsc
|
|
4
|
-
|
|
1
|
+
|
|
2
|
+
> @siduri-x/api@2.0.14 build /home/zagin/Projects/vxnus-studio/projects/siduri-x/apps/api
|
|
3
|
+
> tsc
|
|
4
|
+
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @siduri-x/api@2.0.
|
|
2
|
+
> @siduri-x/api@2.0.14 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 (13.757 s)
|
|
6
|
+
PASS src/runtime.test.ts (13.709 s)
|
|
7
7
|
● Console
|
|
8
8
|
|
|
9
9
|
console.error
|
|
@@ -24,20 +24,20 @@ PASS src/runtime.test.ts (10.201 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/
|
|
27
|
+
PASS src/t5-experience.test.ts (14.225 s)
|
|
28
|
+
PASS src/t7-release.test.ts
|
|
29
|
+
PASS src/boot.test.ts
|
|
30
30
|
PASS src/auth.test.ts
|
|
31
31
|
PASS src/b0-b6.test.ts
|
|
32
32
|
PASS src/smoke.test.ts
|
|
33
|
-
PASS src/
|
|
34
|
-
PASS src/
|
|
35
|
-
PASS src/
|
|
36
|
-
PASS src/
|
|
37
|
-
PASS src/t6-security.test.ts (
|
|
33
|
+
PASS src/index.test.ts
|
|
34
|
+
PASS src/knowledge.test.ts (16.426 s)
|
|
35
|
+
PASS src/t4-gating.test.ts (17.057 s)
|
|
36
|
+
PASS src/teach-mode.test.ts (17.48 s)
|
|
37
|
+
PASS src/t6-security.test.ts (17.495 s)
|
|
38
38
|
[999D[K
|
|
39
39
|
Test Suites: 13 passed, 13 total
|
|
40
|
-
Tests:
|
|
40
|
+
Tests: 94 passed, 94 total
|
|
41
41
|
Snapshots: 0 total
|
|
42
|
-
Time:
|
|
42
|
+
Time: 21.275 s
|
|
43
43
|
Ran all test suites.
|
package/dist/app.js
CHANGED
|
@@ -572,6 +572,225 @@ function createApp(runtimes = new Map()) {
|
|
|
572
572
|
res.status(500).json({ error: e.message });
|
|
573
573
|
}
|
|
574
574
|
});
|
|
575
|
+
app.get('/knowledge/entities', auth_1.requireAuth, async (req, res) => {
|
|
576
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
577
|
+
const runtime = runtimes.get(id);
|
|
578
|
+
if (!runtime)
|
|
579
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
580
|
+
const entityType = req.query.type;
|
|
581
|
+
const domain = req.query.domain;
|
|
582
|
+
if (!runtime.knowledge || !runtime.knowledge.entities) {
|
|
583
|
+
return res.json({ entities: [] });
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
const entities = await runtime.knowledge.entities.getEntities(id, entityType, domain);
|
|
587
|
+
res.json({ entities });
|
|
588
|
+
}
|
|
589
|
+
catch (e) {
|
|
590
|
+
res.status(500).json({ error: e.message });
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
app.get('/knowledge/events', auth_1.requireAuth, async (req, res) => {
|
|
594
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
595
|
+
const runtime = runtimes.get(id);
|
|
596
|
+
if (!runtime)
|
|
597
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
598
|
+
const stream = req.query.stream;
|
|
599
|
+
const limit = Number(req.query.limit || 50);
|
|
600
|
+
if (!runtime.knowledge || !runtime.knowledge.events) {
|
|
601
|
+
return res.json({ events: [] });
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const events = await runtime.knowledge.events.getEvents(id, stream, limit);
|
|
605
|
+
res.json({ events });
|
|
606
|
+
}
|
|
607
|
+
catch (e) {
|
|
608
|
+
res.status(500).json({ error: e.message });
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
app.get('/knowledge/tasks', auth_1.requireAuth, async (req, res) => {
|
|
612
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
613
|
+
const runtime = runtimes.get(id);
|
|
614
|
+
if (!runtime)
|
|
615
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
616
|
+
const status = req.query.status;
|
|
617
|
+
if (!runtime.knowledge || !runtime.knowledge.tasks) {
|
|
618
|
+
return res.json({ tasks: [] });
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
const tasks = await runtime.knowledge.tasks.getTasks(id, status);
|
|
622
|
+
res.json({ tasks });
|
|
623
|
+
}
|
|
624
|
+
catch (e) {
|
|
625
|
+
res.status(500).json({ error: e.message });
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
app.post('/knowledge/proposals/approve', auth_1.requireAuth, async (req, res) => {
|
|
629
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
630
|
+
const runtime = runtimes.get(id);
|
|
631
|
+
if (!runtime)
|
|
632
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
633
|
+
try {
|
|
634
|
+
if (typeof runtime.approveProposal === 'function') {
|
|
635
|
+
const result = await runtime.approveProposal(req.body.id, { companionId: id });
|
|
636
|
+
res.json({ approved: true, target: result?.target || 'knowledge', status: 'approved' });
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
res.status(400).json({ error: 'Runtime does not support proposal approval' });
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
catch (e) {
|
|
643
|
+
res.status(500).json({ error: e.message });
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
app.post('/knowledge/entities', auth_1.requireAuth, async (req, res) => {
|
|
647
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
648
|
+
const runtime = runtimes.get(id);
|
|
649
|
+
if (!runtime)
|
|
650
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
651
|
+
if (!runtime.knowledge || !runtime.knowledge.entities) {
|
|
652
|
+
return res.status(400).json({ error: 'Knowledge organ does not support entities' });
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
const entity = {
|
|
656
|
+
id: req.body.id || `ent-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
657
|
+
companionId: id,
|
|
658
|
+
name: req.body.name,
|
|
659
|
+
entityType: req.body.entityType || req.body.type || 'entity',
|
|
660
|
+
domain: req.body.domain || 'general',
|
|
661
|
+
properties: req.body.properties || {},
|
|
662
|
+
};
|
|
663
|
+
await runtime.knowledge.entities.saveEntity(entity);
|
|
664
|
+
res.json({ saved: true, entity });
|
|
665
|
+
}
|
|
666
|
+
catch (e) {
|
|
667
|
+
res.status(500).json({ error: e.message });
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
app.post('/knowledge/entities/delete', auth_1.requireAuth, async (req, res) => {
|
|
671
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
672
|
+
const runtime = runtimes.get(id);
|
|
673
|
+
if (!runtime)
|
|
674
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
675
|
+
if (!runtime.knowledge || !runtime.knowledge.entities) {
|
|
676
|
+
return res.status(400).json({ error: 'Knowledge organ does not support entities' });
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
const success = await runtime.knowledge.entities.deleteEntity(req.body.id);
|
|
680
|
+
res.json({ deleted: success, id: req.body.id });
|
|
681
|
+
}
|
|
682
|
+
catch (e) {
|
|
683
|
+
res.status(500).json({ error: e.message });
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
app.post('/knowledge/tasks', auth_1.requireAuth, async (req, res) => {
|
|
687
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
688
|
+
const runtime = runtimes.get(id);
|
|
689
|
+
if (!runtime)
|
|
690
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
691
|
+
if (!runtime.knowledge || !runtime.knowledge.tasks) {
|
|
692
|
+
return res.status(400).json({ error: 'Knowledge organ does not support tasks' });
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
const task = {
|
|
696
|
+
id: req.body.id || `task-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
697
|
+
companionId: id,
|
|
698
|
+
title: req.body.title,
|
|
699
|
+
status: req.body.status || 'todo',
|
|
700
|
+
priority: req.body.priority !== undefined ? Number(req.body.priority) : 1,
|
|
701
|
+
targetDate: req.body.targetDate || null,
|
|
702
|
+
metadata: req.body.metadata || {},
|
|
703
|
+
};
|
|
704
|
+
await runtime.knowledge.tasks.saveTask(task);
|
|
705
|
+
res.json({ saved: true, task });
|
|
706
|
+
}
|
|
707
|
+
catch (e) {
|
|
708
|
+
res.status(500).json({ error: e.message });
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
app.post('/knowledge/tasks/delete', auth_1.requireAuth, async (req, res) => {
|
|
712
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
713
|
+
const runtime = runtimes.get(id);
|
|
714
|
+
if (!runtime)
|
|
715
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
716
|
+
if (!runtime.knowledge || !runtime.knowledge.tasks) {
|
|
717
|
+
return res.status(400).json({ error: 'Knowledge organ does not support tasks' });
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
const success = await runtime.knowledge.tasks.deleteTask(req.body.id);
|
|
721
|
+
res.json({ deleted: success, id: req.body.id });
|
|
722
|
+
}
|
|
723
|
+
catch (e) {
|
|
724
|
+
res.status(500).json({ error: e.message });
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
app.post('/knowledge/events', auth_1.requireAuth, async (req, res) => {
|
|
728
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
729
|
+
const runtime = runtimes.get(id);
|
|
730
|
+
if (!runtime)
|
|
731
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
732
|
+
if (!runtime.knowledge || !runtime.knowledge.events) {
|
|
733
|
+
return res.status(400).json({ error: 'Knowledge organ does not support events' });
|
|
734
|
+
}
|
|
735
|
+
try {
|
|
736
|
+
const event = {
|
|
737
|
+
id: req.body.id || `evt-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
738
|
+
companionId: id,
|
|
739
|
+
stream: req.body.stream || 'default',
|
|
740
|
+
timestamp: req.body.timestamp || new Date().toISOString(),
|
|
741
|
+
metricValue: req.body.metricValue !== undefined && req.body.metricValue !== null ? Number(req.body.metricValue) : undefined,
|
|
742
|
+
metadata: req.body.metadata || {},
|
|
743
|
+
};
|
|
744
|
+
await runtime.knowledge.events.addEvent(event);
|
|
745
|
+
res.json({ saved: true, event });
|
|
746
|
+
}
|
|
747
|
+
catch (e) {
|
|
748
|
+
res.status(500).json({ error: e.message });
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
app.post('/knowledge/schedule', auth_1.requireAuth, async (req, res) => {
|
|
752
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
753
|
+
const runtime = runtimes.get(id);
|
|
754
|
+
if (!runtime)
|
|
755
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
756
|
+
if (!runtime.knowledge || !runtime.knowledge.schedule) {
|
|
757
|
+
return res.status(400).json({ error: 'Knowledge organ does not support schedule' });
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
const item = {
|
|
761
|
+
id: req.body.id || `sched-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
762
|
+
companionId: id,
|
|
763
|
+
title: req.body.title,
|
|
764
|
+
startTime: req.body.startTime,
|
|
765
|
+
endTime: req.body.endTime || null,
|
|
766
|
+
isRecurring: Boolean(req.body.isRecurring),
|
|
767
|
+
status: req.body.status || 'active',
|
|
768
|
+
};
|
|
769
|
+
await runtime.knowledge.schedule.saveItem(item);
|
|
770
|
+
res.json({ saved: true, item });
|
|
771
|
+
}
|
|
772
|
+
catch (e) {
|
|
773
|
+
res.status(500).json({ error: e.message });
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
app.post('/knowledge/schedule/delete', auth_1.requireAuth, async (req, res) => {
|
|
777
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
778
|
+
const runtime = runtimes.get(id);
|
|
779
|
+
if (!runtime)
|
|
780
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
781
|
+
if (!runtime.knowledge || !runtime.knowledge.schedule) {
|
|
782
|
+
return res.status(400).json({ error: 'Knowledge organ does not support schedule' });
|
|
783
|
+
}
|
|
784
|
+
try {
|
|
785
|
+
const success = typeof runtime.knowledge.schedule.deleteItem === 'function'
|
|
786
|
+
? await runtime.knowledge.schedule.deleteItem(req.body.id)
|
|
787
|
+
: false;
|
|
788
|
+
res.json({ deleted: success, id: req.body.id });
|
|
789
|
+
}
|
|
790
|
+
catch (e) {
|
|
791
|
+
res.status(500).json({ error: e.message });
|
|
792
|
+
}
|
|
793
|
+
});
|
|
575
794
|
// MEMORY MUTATIONS - PROPOSALS
|
|
576
795
|
app.post('/memory/proposals/update', auth_1.requireAuth, async (req, res) => {
|
|
577
796
|
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
@@ -709,6 +928,42 @@ function createApp(runtimes = new Map()) {
|
|
|
709
928
|
res.status(500).json({ error: e.message });
|
|
710
929
|
}
|
|
711
930
|
});
|
|
931
|
+
// SYSTEM LOGS
|
|
932
|
+
app.get('/system/logs', auth_1.requireAuth, async (req, res) => {
|
|
933
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
934
|
+
const runtime = runtimes.get(id);
|
|
935
|
+
if (!runtime)
|
|
936
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
937
|
+
const level = req.query.level;
|
|
938
|
+
const subsystem = req.query.subsystem;
|
|
939
|
+
const q = req.query.q;
|
|
940
|
+
const limit = parseInt(req.query.limit || '100', 10);
|
|
941
|
+
const offset = parseInt(req.query.offset || '0', 10);
|
|
942
|
+
try {
|
|
943
|
+
const logs = typeof runtime.queryLogs === 'function'
|
|
944
|
+
? runtime.queryLogs({ companionId: id, level, subsystem, q, limit, offset })
|
|
945
|
+
: [];
|
|
946
|
+
res.json({ logs });
|
|
947
|
+
}
|
|
948
|
+
catch (e) {
|
|
949
|
+
res.status(500).json({ error: e.message, logs: [] });
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
app.post('/system/logs/clear', auth_1.requireAuth, async (req, res) => {
|
|
953
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
954
|
+
const runtime = runtimes.get(id);
|
|
955
|
+
if (!runtime)
|
|
956
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
957
|
+
try {
|
|
958
|
+
if (typeof runtime.clearLogs === 'function') {
|
|
959
|
+
runtime.clearLogs(id);
|
|
960
|
+
}
|
|
961
|
+
res.json({ cleared: true });
|
|
962
|
+
}
|
|
963
|
+
catch (e) {
|
|
964
|
+
res.status(500).json({ error: e.message });
|
|
965
|
+
}
|
|
966
|
+
});
|
|
712
967
|
const isDevMode = process.env.NODE_ENV !== 'production' || process.env.SIDURI_DEV_MODE === 'true';
|
|
713
968
|
if (isDevMode) {
|
|
714
969
|
app.post('/dev/memory/reset', auth_1.requireAuth, async (req, res) => {
|
package/dist/boot.js
CHANGED
|
@@ -119,7 +119,7 @@ async function bootCompanion(id, config, options) {
|
|
|
119
119
|
const vision = createVision(organs.vision || config?.vision);
|
|
120
120
|
const behavior = createBehavior(organs.behavior || config?.behavior);
|
|
121
121
|
const body = createBody(organs.body || config?.body);
|
|
122
|
-
const hands = createHands(organs.hands || config?.hands);
|
|
122
|
+
const hands = createHands({ ...(organs.hands || config?.hands), knowledge });
|
|
123
123
|
const ear = createEar(organs.ear || config?.ear);
|
|
124
124
|
const mouth = createMouth(organs.mouth || config?.mouth, voice);
|
|
125
125
|
const observation = options?.observationOrgan;
|
package/dist/knowledge.test.js
CHANGED
|
@@ -40,10 +40,22 @@ describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
|
|
|
40
40
|
};
|
|
41
41
|
}),
|
|
42
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
|
+
};
|
|
43
54
|
runtime = new runtime_1.SiduriRuntime('test-comp', { name: 'Test Companion', organs: { knowledge: { provider: 'unified', dbPath: testDbPath } } }, {
|
|
44
55
|
brain: mockBrain,
|
|
45
56
|
knowledge,
|
|
46
57
|
externalKnowledge: knowledge.eAdapter ?? knowledge,
|
|
58
|
+
memory,
|
|
47
59
|
});
|
|
48
60
|
await runtime.initialize();
|
|
49
61
|
const runtimes = new Map([['test-comp', runtime]]);
|
|
@@ -135,4 +147,90 @@ describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
|
|
|
135
147
|
expect(lifeRes.status).toBe(200);
|
|
136
148
|
expect(lifeRes.body.matchedInventory).toEqual([]);
|
|
137
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
|
+
});
|
|
138
236
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siduri-x/api",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"dotenv": "^17.4.2",
|
|
14
14
|
"express": "^5.2.1",
|
|
15
15
|
"@siduri-x/body": "2.0.1",
|
|
16
|
-
"@siduri-x/
|
|
17
|
-
"@siduri-x/
|
|
16
|
+
"@siduri-x/core": "2.0.11",
|
|
17
|
+
"@siduri-x/brain": "2.0.7",
|
|
18
18
|
"@siduri-x/ear": "2.0.3",
|
|
19
19
|
"@siduri-x/eknowledge": "2.0.1",
|
|
20
|
-
"@siduri-x/hands": "2.0.
|
|
20
|
+
"@siduri-x/hands": "2.0.2",
|
|
21
|
+
"@siduri-x/knowledge": "2.0.4",
|
|
21
22
|
"@siduri-x/memory": "2.0.5",
|
|
22
|
-
"@siduri-x/knowledge": "2.0.3",
|
|
23
23
|
"@siduri-x/mouth": "2.0.2",
|
|
24
24
|
"@siduri-x/observation": "2.0.1",
|
|
25
25
|
"@siduri-x/self": "2.0.7",
|
package/siduri.sqlite
CHANGED
|
Binary file
|
package/src/app.ts
CHANGED
|
@@ -611,6 +611,213 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
611
611
|
}
|
|
612
612
|
});
|
|
613
613
|
|
|
614
|
+
app.get('/knowledge/entities', requireAuth, async (req, res) => {
|
|
615
|
+
const id = (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
616
|
+
const runtime = runtimes.get(id);
|
|
617
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
618
|
+
const entityType = req.query.type as string | undefined;
|
|
619
|
+
const domain = req.query.domain as string | undefined;
|
|
620
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).entities) {
|
|
621
|
+
return res.json({ entities: [] });
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
const entities = await (runtime.knowledge as any).entities.getEntities(id, entityType, domain);
|
|
625
|
+
res.json({ entities });
|
|
626
|
+
} catch (e: any) {
|
|
627
|
+
res.status(500).json({ error: e.message });
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
app.get('/knowledge/events', requireAuth, async (req, res) => {
|
|
632
|
+
const id = (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
633
|
+
const runtime = runtimes.get(id);
|
|
634
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
635
|
+
const stream = req.query.stream as string | undefined;
|
|
636
|
+
const limit = Number(req.query.limit || 50);
|
|
637
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).events) {
|
|
638
|
+
return res.json({ events: [] });
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
const events = await (runtime.knowledge as any).events.getEvents(id, stream, limit);
|
|
642
|
+
res.json({ events });
|
|
643
|
+
} catch (e: any) {
|
|
644
|
+
res.status(500).json({ error: e.message });
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
app.get('/knowledge/tasks', requireAuth, async (req, res) => {
|
|
649
|
+
const id = (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
650
|
+
const runtime = runtimes.get(id);
|
|
651
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
652
|
+
const status = req.query.status as string | undefined;
|
|
653
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).tasks) {
|
|
654
|
+
return res.json({ tasks: [] });
|
|
655
|
+
}
|
|
656
|
+
try {
|
|
657
|
+
const tasks = await (runtime.knowledge as any).tasks.getTasks(id, status);
|
|
658
|
+
res.json({ tasks });
|
|
659
|
+
} catch (e: any) {
|
|
660
|
+
res.status(500).json({ error: e.message });
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
app.post('/knowledge/proposals/approve', requireAuth, async (req, res) => {
|
|
665
|
+
const id = (req.body.companionId as string) || Array.from(runtimes.keys())[0];
|
|
666
|
+
const runtime = runtimes.get(id);
|
|
667
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
668
|
+
try {
|
|
669
|
+
if (typeof (runtime as any).approveProposal === 'function') {
|
|
670
|
+
const result = await (runtime as any).approveProposal(req.body.id, { companionId: id });
|
|
671
|
+
res.json({ approved: true, target: result?.target || 'knowledge', status: 'approved' });
|
|
672
|
+
} else {
|
|
673
|
+
res.status(400).json({ error: 'Runtime does not support proposal approval' });
|
|
674
|
+
}
|
|
675
|
+
} catch (e: any) {
|
|
676
|
+
res.status(500).json({ error: e.message });
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
app.post('/knowledge/entities', requireAuth, async (req, res) => {
|
|
681
|
+
const id = (req.body.companionId as string) || (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
682
|
+
const runtime = runtimes.get(id);
|
|
683
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
684
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).entities) {
|
|
685
|
+
return res.status(400).json({ error: 'Knowledge organ does not support entities' });
|
|
686
|
+
}
|
|
687
|
+
try {
|
|
688
|
+
const entity = {
|
|
689
|
+
id: req.body.id || `ent-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
690
|
+
companionId: id,
|
|
691
|
+
name: req.body.name,
|
|
692
|
+
entityType: req.body.entityType || req.body.type || 'entity',
|
|
693
|
+
domain: req.body.domain || 'general',
|
|
694
|
+
properties: req.body.properties || {},
|
|
695
|
+
};
|
|
696
|
+
await (runtime.knowledge as any).entities.saveEntity(entity);
|
|
697
|
+
res.json({ saved: true, entity });
|
|
698
|
+
} catch (e: any) {
|
|
699
|
+
res.status(500).json({ error: e.message });
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
app.post('/knowledge/entities/delete', requireAuth, async (req, res) => {
|
|
704
|
+
const id = (req.body.companionId as string) || (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
705
|
+
const runtime = runtimes.get(id);
|
|
706
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
707
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).entities) {
|
|
708
|
+
return res.status(400).json({ error: 'Knowledge organ does not support entities' });
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
const success = await (runtime.knowledge as any).entities.deleteEntity(req.body.id);
|
|
712
|
+
res.json({ deleted: success, id: req.body.id });
|
|
713
|
+
} catch (e: any) {
|
|
714
|
+
res.status(500).json({ error: e.message });
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
app.post('/knowledge/tasks', requireAuth, async (req, res) => {
|
|
719
|
+
const id = (req.body.companionId as string) || (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
720
|
+
const runtime = runtimes.get(id);
|
|
721
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
722
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).tasks) {
|
|
723
|
+
return res.status(400).json({ error: 'Knowledge organ does not support tasks' });
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
const task = {
|
|
727
|
+
id: req.body.id || `task-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
728
|
+
companionId: id,
|
|
729
|
+
title: req.body.title,
|
|
730
|
+
status: req.body.status || 'todo',
|
|
731
|
+
priority: req.body.priority !== undefined ? Number(req.body.priority) : 1,
|
|
732
|
+
targetDate: req.body.targetDate || null,
|
|
733
|
+
metadata: req.body.metadata || {},
|
|
734
|
+
};
|
|
735
|
+
await (runtime.knowledge as any).tasks.saveTask(task);
|
|
736
|
+
res.json({ saved: true, task });
|
|
737
|
+
} catch (e: any) {
|
|
738
|
+
res.status(500).json({ error: e.message });
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
app.post('/knowledge/tasks/delete', requireAuth, async (req, res) => {
|
|
743
|
+
const id = (req.body.companionId as string) || (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
744
|
+
const runtime = runtimes.get(id);
|
|
745
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
746
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).tasks) {
|
|
747
|
+
return res.status(400).json({ error: 'Knowledge organ does not support tasks' });
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
const success = await (runtime.knowledge as any).tasks.deleteTask(req.body.id);
|
|
751
|
+
res.json({ deleted: success, id: req.body.id });
|
|
752
|
+
} catch (e: any) {
|
|
753
|
+
res.status(500).json({ error: e.message });
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
app.post('/knowledge/events', requireAuth, async (req, res) => {
|
|
758
|
+
const id = (req.body.companionId as string) || (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
759
|
+
const runtime = runtimes.get(id);
|
|
760
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
761
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).events) {
|
|
762
|
+
return res.status(400).json({ error: 'Knowledge organ does not support events' });
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
const event = {
|
|
766
|
+
id: req.body.id || `evt-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
767
|
+
companionId: id,
|
|
768
|
+
stream: req.body.stream || 'default',
|
|
769
|
+
timestamp: req.body.timestamp || new Date().toISOString(),
|
|
770
|
+
metricValue: req.body.metricValue !== undefined && req.body.metricValue !== null ? Number(req.body.metricValue) : undefined,
|
|
771
|
+
metadata: req.body.metadata || {},
|
|
772
|
+
};
|
|
773
|
+
await (runtime.knowledge as any).events.addEvent(event);
|
|
774
|
+
res.json({ saved: true, event });
|
|
775
|
+
} catch (e: any) {
|
|
776
|
+
res.status(500).json({ error: e.message });
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
app.post('/knowledge/schedule', requireAuth, async (req, res) => {
|
|
781
|
+
const id = (req.body.companionId as string) || (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
782
|
+
const runtime = runtimes.get(id);
|
|
783
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
784
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).schedule) {
|
|
785
|
+
return res.status(400).json({ error: 'Knowledge organ does not support schedule' });
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
const item = {
|
|
789
|
+
id: req.body.id || `sched-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
790
|
+
companionId: id,
|
|
791
|
+
title: req.body.title,
|
|
792
|
+
startTime: req.body.startTime,
|
|
793
|
+
endTime: req.body.endTime || null,
|
|
794
|
+
isRecurring: Boolean(req.body.isRecurring),
|
|
795
|
+
status: req.body.status || 'active',
|
|
796
|
+
};
|
|
797
|
+
await (runtime.knowledge as any).schedule.saveItem(item);
|
|
798
|
+
res.json({ saved: true, item });
|
|
799
|
+
} catch (e: any) {
|
|
800
|
+
res.status(500).json({ error: e.message });
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
app.post('/knowledge/schedule/delete', requireAuth, async (req, res) => {
|
|
805
|
+
const id = (req.body.companionId as string) || (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
806
|
+
const runtime = runtimes.get(id);
|
|
807
|
+
if (!runtime) return res.status(404).json({ error: 'Companion not found' });
|
|
808
|
+
if (!runtime.knowledge || !(runtime.knowledge as any).schedule) {
|
|
809
|
+
return res.status(400).json({ error: 'Knowledge organ does not support schedule' });
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
const success = typeof (runtime.knowledge as any).schedule.deleteItem === 'function'
|
|
813
|
+
? await (runtime.knowledge as any).schedule.deleteItem(req.body.id)
|
|
814
|
+
: false;
|
|
815
|
+
res.json({ deleted: success, id: req.body.id });
|
|
816
|
+
} catch (e: any) {
|
|
817
|
+
res.status(500).json({ error: e.message });
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
|
|
614
821
|
// MEMORY MUTATIONS - PROPOSALS
|
|
615
822
|
app.post('/memory/proposals/update', requireAuth, async (req, res) => {
|
|
616
823
|
const id = req.body.companionId as string || Array.from(runtimes.keys())[0];
|
|
@@ -730,6 +937,40 @@ export function createApp(runtimes: Map<string, SiduriRuntime> = new Map()): App
|
|
|
730
937
|
}
|
|
731
938
|
});
|
|
732
939
|
|
|
940
|
+
// SYSTEM LOGS
|
|
941
|
+
app.get('/system/logs', requireAuth, async (req, res) => {
|
|
942
|
+
const id = (req.query.id as string) || Array.from(runtimes.keys())[0];
|
|
943
|
+
const runtime = runtimes.get(id);
|
|
944
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
945
|
+
const level = req.query.level as string | undefined;
|
|
946
|
+
const subsystem = req.query.subsystem as string | undefined;
|
|
947
|
+
const q = req.query.q as string | undefined;
|
|
948
|
+
const limit = parseInt((req.query.limit as string) || '100', 10);
|
|
949
|
+
const offset = parseInt((req.query.offset as string) || '0', 10);
|
|
950
|
+
try {
|
|
951
|
+
const logs = typeof runtime.queryLogs === 'function'
|
|
952
|
+
? runtime.queryLogs({ companionId: id, level, subsystem, q, limit, offset })
|
|
953
|
+
: [];
|
|
954
|
+
res.json({ logs });
|
|
955
|
+
} catch (e: any) {
|
|
956
|
+
res.status(500).json({ error: e.message, logs: [] });
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
app.post('/system/logs/clear', requireAuth, async (req, res) => {
|
|
961
|
+
const id = (req.body.companionId as string) || Array.from(runtimes.keys())[0];
|
|
962
|
+
const runtime = runtimes.get(id);
|
|
963
|
+
if (!runtime) return res.status(404).json({ error: "Companion not found" });
|
|
964
|
+
try {
|
|
965
|
+
if (typeof runtime.clearLogs === 'function') {
|
|
966
|
+
runtime.clearLogs(id);
|
|
967
|
+
}
|
|
968
|
+
res.json({ cleared: true });
|
|
969
|
+
} catch (e: any) {
|
|
970
|
+
res.status(500).json({ error: e.message });
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
733
974
|
const isDevMode = process.env.NODE_ENV !== 'production' || process.env.SIDURI_DEV_MODE === 'true';
|
|
734
975
|
if (isDevMode) {
|
|
735
976
|
app.post('/dev/memory/reset', requireAuth, async (req, res) => {
|
package/src/boot.ts
CHANGED
|
@@ -160,7 +160,7 @@ export async function bootCompanion(
|
|
|
160
160
|
const vision = createVision(organs.vision || config?.vision);
|
|
161
161
|
const behavior = createBehavior(organs.behavior || config?.behavior);
|
|
162
162
|
const body = createBody(organs.body || config?.body);
|
|
163
|
-
const hands = createHands(organs.hands || config?.hands);
|
|
163
|
+
const hands = createHands({ ...((organs.hands || config?.hands) as any), knowledge });
|
|
164
164
|
const ear = createEar(organs.ear || config?.ear);
|
|
165
165
|
const mouth = createMouth(organs.mouth || config?.mouth, voice);
|
|
166
166
|
const observation = options?.observationOrgan;
|
package/src/knowledge.test.ts
CHANGED
|
@@ -38,6 +38,19 @@ describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
|
|
|
38
38
|
}),
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
const memory: any = {
|
|
42
|
+
initialize: async () => {},
|
|
43
|
+
proposeClaim: async (claim: any) => (knowledge.lifeDb as any).db.proposeClaim(claim),
|
|
44
|
+
getClaims: async (limit?: number) => (knowledge.lifeDb as any).db.getAllClaims(undefined, limit || 500),
|
|
45
|
+
getPendingClaims: async (limit?: number) =>
|
|
46
|
+
(knowledge.lifeDb as any).db.getAllClaims(undefined, limit || 500).filter((c: any) => c.status === 'pending'),
|
|
47
|
+
approveClaim: async (id: string) => (knowledge.lifeDb as any).db.approveClaim(id),
|
|
48
|
+
rejectClaim: async (id: string) => (knowledge.lifeDb as any).db.rejectClaim(id),
|
|
49
|
+
searchClaims: async () => [],
|
|
50
|
+
getApprovedClaims: async () => [],
|
|
51
|
+
getDirectives: async () => [],
|
|
52
|
+
};
|
|
53
|
+
|
|
41
54
|
runtime = new SiduriRuntime(
|
|
42
55
|
'test-comp',
|
|
43
56
|
{ name: 'Test Companion', organs: { knowledge: { provider: 'unified', dbPath: testDbPath } } } as any,
|
|
@@ -45,6 +58,7 @@ describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
|
|
|
45
58
|
brain: mockBrain,
|
|
46
59
|
knowledge,
|
|
47
60
|
externalKnowledge: knowledge.eAdapter ?? knowledge,
|
|
61
|
+
memory,
|
|
48
62
|
}
|
|
49
63
|
);
|
|
50
64
|
await runtime.initialize();
|
|
@@ -153,4 +167,101 @@ describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
|
|
|
153
167
|
expect(lifeRes.status).toBe(200);
|
|
154
168
|
expect(lifeRes.body.matchedInventory).toEqual([]);
|
|
155
169
|
});
|
|
170
|
+
|
|
171
|
+
test('seeds and queries generic entities, events, and tasks', async () => {
|
|
172
|
+
// 1. Entities
|
|
173
|
+
await knowledge.entities.saveEntity({
|
|
174
|
+
id: 'ent-1',
|
|
175
|
+
companionId: 'test-comp',
|
|
176
|
+
name: 'Coffee Grinder',
|
|
177
|
+
entityType: 'item',
|
|
178
|
+
domain: 'kitchen',
|
|
179
|
+
properties: { burr: 'conical', setting: 14 },
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const entRes = await request(app)
|
|
183
|
+
.get('/knowledge/entities?id=test-comp&domain=kitchen')
|
|
184
|
+
.set(mockAuthHeader);
|
|
185
|
+
expect(entRes.status).toBe(200);
|
|
186
|
+
expect(entRes.body.entities).toHaveLength(1);
|
|
187
|
+
expect(entRes.body.entities[0].name).toBe('Coffee Grinder');
|
|
188
|
+
expect(entRes.body.entities[0].properties.setting).toBe(14);
|
|
189
|
+
|
|
190
|
+
// 2. Events
|
|
191
|
+
await knowledge.events.addEvent({
|
|
192
|
+
id: 'evt-1',
|
|
193
|
+
companionId: 'test-comp',
|
|
194
|
+
stream: 'health:heartrate',
|
|
195
|
+
timestamp: new Date().toISOString(),
|
|
196
|
+
metricValue: 72,
|
|
197
|
+
metadata: { unit: 'bpm' },
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const evtRes = await request(app)
|
|
201
|
+
.get('/knowledge/events?id=test-comp&stream=health:heartrate')
|
|
202
|
+
.set(mockAuthHeader);
|
|
203
|
+
expect(evtRes.status).toBe(200);
|
|
204
|
+
expect(evtRes.body.events).toHaveLength(1);
|
|
205
|
+
expect(evtRes.body.events[0].metricValue).toBe(72);
|
|
206
|
+
|
|
207
|
+
// 3. Tasks
|
|
208
|
+
await knowledge.tasks.saveTask({
|
|
209
|
+
id: 'task-1',
|
|
210
|
+
companionId: 'test-comp',
|
|
211
|
+
title: 'Order fresh espresso beans',
|
|
212
|
+
status: 'pending',
|
|
213
|
+
priority: 2,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const taskRes = await request(app)
|
|
217
|
+
.get('/knowledge/tasks?id=test-comp&status=pending')
|
|
218
|
+
.set(mockAuthHeader);
|
|
219
|
+
expect(taskRes.status).toBe(200);
|
|
220
|
+
expect(taskRes.body.tasks).toHaveLength(1);
|
|
221
|
+
expect(taskRes.body.tasks[0].title).toBe('Order fresh espresso beans');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('Truth Gate candidate proposal approval commits Life DB mutations', async () => {
|
|
225
|
+
// Stage a candidate proposal targeting Life DB
|
|
226
|
+
const proposal = await (knowledge.lifeDb as any).db.proposeClaim({
|
|
227
|
+
id: 'prop-task-gate',
|
|
228
|
+
companionId: 'test-comp',
|
|
229
|
+
subject: 'task:buy-filter',
|
|
230
|
+
predicate: 'is',
|
|
231
|
+
value: 'Buy paper filters',
|
|
232
|
+
status: 'pending',
|
|
233
|
+
evidence: {
|
|
234
|
+
type: 'task',
|
|
235
|
+
title: 'Buy paper filters',
|
|
236
|
+
status: 'todo',
|
|
237
|
+
priority: 1,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
expect(proposal.status).toBe('pending');
|
|
241
|
+
|
|
242
|
+
// Confirm it is not yet in tasks
|
|
243
|
+
const beforeRes = await request(app)
|
|
244
|
+
.get('/knowledge/tasks?id=test-comp')
|
|
245
|
+
.set(mockAuthHeader);
|
|
246
|
+
expect(beforeRes.body.tasks.some((t: any) => t.title === 'Buy paper filters')).toBe(false);
|
|
247
|
+
|
|
248
|
+
// Approve via Truth Gate endpoint
|
|
249
|
+
const approveRes = await request(app)
|
|
250
|
+
.post('/knowledge/proposals/approve')
|
|
251
|
+
.set(mockAuthHeader)
|
|
252
|
+
.send({ companionId: 'test-comp', id: 'prop-task-gate' });
|
|
253
|
+
|
|
254
|
+
expect(approveRes.status).toBe(200);
|
|
255
|
+
expect(approveRes.body.approved).toBe(true);
|
|
256
|
+
expect(approveRes.body.target).toBe('knowledge');
|
|
257
|
+
|
|
258
|
+
// Confirm it is committed to Life DB tasks
|
|
259
|
+
const afterRes = await request(app)
|
|
260
|
+
.get('/knowledge/tasks?id=test-comp')
|
|
261
|
+
.set(mockAuthHeader);
|
|
262
|
+
expect(afterRes.status).toBe(200);
|
|
263
|
+
const approvedTask = afterRes.body.tasks.find((t: any) => t.title === 'Buy paper filters');
|
|
264
|
+
expect(approvedTask).toBeDefined();
|
|
265
|
+
expect(approvedTask.status).toBe('todo');
|
|
266
|
+
});
|
|
156
267
|
});
|