@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.
@@ -1,4 +1,4 @@
1
-
2
- > @siduri-x/api@2.0.13 build /home/zagin/Projects/vxnus-studio/projects/siduri-x/apps/api
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
+
@@ -1,9 +1,9 @@
1
1
 
2
- > @siduri-x/api@2.0.13 test /home/zagin/Projects/vxnus-studio/projects/siduri-x/apps/api
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 (9.967 s)
6
- PASS src/runtime.test.ts (10.201 s)
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/t4-gating.test.ts (11.238 s)
28
- PASS src/knowledge.test.ts
29
- PASS src/t5-experience.test.ts (11.834 s)
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/teach-mode.test.ts (11.131 s)
34
- PASS src/boot.test.ts
35
- PASS src/index.test.ts (11.341 s)
36
- PASS src/t7-release.test.ts
37
- PASS src/t6-security.test.ts (11.765 s)
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
  
39
39
  Test Suites: 13 passed, 13 total
40
- Tests: 92 passed, 92 total
40
+ Tests: 94 passed, 94 total
41
41
  Snapshots: 0 total
42
- Time: 15.433 s
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;
@@ -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.13",
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/brain": "2.0.6",
17
- "@siduri-x/core": "2.0.10",
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.1",
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;
@@ -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
  });