@sidurijs/api 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +78 -0
- package/dist/app.d.ts +11 -0
- package/dist/app.js +1190 -0
- package/dist/auth.d.ts +34 -0
- package/dist/auth.js +84 -0
- package/dist/auth.test.d.ts +1 -0
- package/dist/auth.test.js +74 -0
- package/dist/b0-b6.test.d.ts +1 -0
- package/dist/b0-b6.test.js +121 -0
- package/dist/boot.d.ts +91 -0
- package/dist/boot.js +154 -0
- package/dist/boot.test.d.ts +1 -0
- package/dist/boot.test.js +91 -0
- package/dist/context-mapper.d.ts +20 -0
- package/dist/context-mapper.js +233 -0
- package/dist/context-mapper.test.d.ts +1 -0
- package/dist/context-mapper.test.js +287 -0
- package/dist/cors.d.ts +3 -0
- package/dist/cors.js +42 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +116 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +151 -0
- package/dist/knowledge.test.d.ts +1 -0
- package/dist/knowledge.test.js +236 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +17 -0
- package/dist/runtime.test.d.ts +1 -0
- package/dist/runtime.test.js +282 -0
- package/dist/smoke.test.d.ts +0 -0
- package/dist/smoke.test.js +6 -0
- package/dist/t4-gating.test.d.ts +1 -0
- package/dist/t4-gating.test.js +193 -0
- package/dist/t5-experience.test.d.ts +1 -0
- package/dist/t5-experience.test.js +155 -0
- package/dist/t6-security.test.d.ts +1 -0
- package/dist/t6-security.test.js +535 -0
- package/dist/t7-release.test.d.ts +1 -0
- package/dist/t7-release.test.js +119 -0
- package/dist/teach-mode.test.d.ts +1 -0
- package/dist/teach-mode.test.js +277 -0
- package/jest.config.json +5 -0
- package/package.json +44 -0
- package/siduri.sqlite +0 -0
- package/src/app.ts +1209 -0
- package/src/auth.test.ts +90 -0
- package/src/auth.ts +101 -0
- package/src/b0-b6.test.ts +137 -0
- package/src/boot.test.ts +112 -0
- package/src/boot.ts +197 -0
- package/src/context-mapper.test.ts +319 -0
- package/src/context-mapper.ts +279 -0
- package/src/cors.ts +44 -0
- package/src/index.test.ts +178 -0
- package/src/index.ts +101 -0
- package/src/knowledge.test.ts +267 -0
- package/src/runtime.test.ts +336 -0
- package/src/runtime.ts +1 -0
- package/src/smoke.test.ts +5 -0
- package/src/t4-gating.test.ts +219 -0
- package/src/t5-experience.test.ts +174 -0
- package/src/t6-security.test.ts +589 -0
- package/src/t7-release.test.ts +131 -0
- package/src/teach-mode.test.ts +312 -0
- package/tsconfig.json +16 -0
package/dist/app.js
ADDED
|
@@ -0,0 +1,1190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.bootCompanion = void 0;
|
|
7
|
+
exports.createApp = createApp;
|
|
8
|
+
const express_1 = __importDefault(require("express"));
|
|
9
|
+
const cors_1 = __importDefault(require("cors"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const cors_2 = require("./cors");
|
|
13
|
+
const runtime_1 = require("./runtime");
|
|
14
|
+
const self_1 = require("@sidurijs/self");
|
|
15
|
+
const auth_1 = require("./auth");
|
|
16
|
+
const context_mapper_1 = require("./context-mapper");
|
|
17
|
+
const boot_1 = require("./boot");
|
|
18
|
+
Object.defineProperty(exports, "bootCompanion", { enumerable: true, get: function () { return boot_1.bootCompanion; } });
|
|
19
|
+
function createApp(runtimes = new Map()) {
|
|
20
|
+
const app = (0, express_1.default)();
|
|
21
|
+
app.use((0, cors_1.default)((0, cors_2.createCorsOptions)()));
|
|
22
|
+
app.use(express_1.default.json());
|
|
23
|
+
let observationOrgan;
|
|
24
|
+
app.post('/boot', auth_1.requireAuth, async (req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const { id, config } = req.body;
|
|
27
|
+
if (runtimes.has(id)) {
|
|
28
|
+
return res.status(400).json({ error: "Already booted" });
|
|
29
|
+
}
|
|
30
|
+
const runtime = await (0, boot_1.bootCompanion)(id, config, { observationOrgan });
|
|
31
|
+
runtimes.set(id, runtime);
|
|
32
|
+
res.json({ success: true, id });
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
res.status(500).json({ error: e.message });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// STATUS / HEALTH ENDPOINTS
|
|
39
|
+
app.get('/health', (req, res) => res.json({ status: "ok" }));
|
|
40
|
+
app.get('/version', (req, res) => res.json({ name: "siduri-x-api", version: "0.2.0-x" }));
|
|
41
|
+
app.get('/ready', (req, res) => {
|
|
42
|
+
const isReady = runtimes.size > 0;
|
|
43
|
+
res.status(isReady ? 200 : 503).json({
|
|
44
|
+
status: isReady ? "ready" : "not_ready",
|
|
45
|
+
companionCount: runtimes.size,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
app.get('/voice/health', (req, res) => {
|
|
49
|
+
const hasVoice = Array.from(runtimes.values()).some((r) => Boolean(r.voice));
|
|
50
|
+
res.json({ provider: "siduri-voice", configured: hasVoice });
|
|
51
|
+
});
|
|
52
|
+
app.get('/obs/health', (req, res) => {
|
|
53
|
+
const connected = Boolean(observationOrgan) || Array.from(runtimes.values()).some((r) => Boolean(r.observation));
|
|
54
|
+
res.json({ connected });
|
|
55
|
+
});
|
|
56
|
+
app.get('/mouth/health', (req, res) => {
|
|
57
|
+
const hasMouth = Array.from(runtimes.values()).some((r) => Boolean(r.mouth));
|
|
58
|
+
res.json({ provider: "siduri-mouth", configured: hasMouth });
|
|
59
|
+
});
|
|
60
|
+
app.get('/body/health', (req, res) => {
|
|
61
|
+
const hasBody = Array.from(runtimes.values()).some((r) => Boolean(r.body));
|
|
62
|
+
res.json({ provider: "siduri-body", configured: hasBody });
|
|
63
|
+
});
|
|
64
|
+
app.get('/body/snapshot', (req, res) => {
|
|
65
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
66
|
+
const runtime = runtimes.get(id);
|
|
67
|
+
if (!runtime || !runtime.body) {
|
|
68
|
+
return res.status(404).json({ error: "Body organ not configured for companion" });
|
|
69
|
+
}
|
|
70
|
+
const snapshot = typeof runtime.body.getSnapshot === 'function'
|
|
71
|
+
? runtime.body.getSnapshot()
|
|
72
|
+
: null;
|
|
73
|
+
res.json({ snapshot });
|
|
74
|
+
});
|
|
75
|
+
app.get('/mouth/channels', (req, res) => {
|
|
76
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
77
|
+
const runtime = runtimes.get(id);
|
|
78
|
+
if (!runtime || !runtime.mouth || typeof runtime.mouth.getRegisteredChannels !== 'function') {
|
|
79
|
+
return res.json({ channels: [] });
|
|
80
|
+
}
|
|
81
|
+
res.json({ channels: runtime.mouth.getRegisteredChannels() });
|
|
82
|
+
});
|
|
83
|
+
app.get('/me', auth_1.attachIdentity, (req, res) => {
|
|
84
|
+
const identity = req.identity;
|
|
85
|
+
res.json({
|
|
86
|
+
actorId: identity.actorId || (identity.authenticated ? 'owner-user' : 'anonymous-session'),
|
|
87
|
+
role: identity.role || (identity.authenticated ? 'OWNER' : 'VIEWER'),
|
|
88
|
+
authenticated: Boolean(identity.authenticated),
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
app.put('/me', auth_1.requireAuth, (req, res) => res.json({ success: true }));
|
|
92
|
+
// TEACH MODE ENDPOINTS
|
|
93
|
+
app.get('/teach/detected-self', auth_1.attachIdentity, async (req, res) => {
|
|
94
|
+
try {
|
|
95
|
+
const companionId = req.query.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
96
|
+
const runtime = runtimes.get(companionId);
|
|
97
|
+
const explicitPath = req.query.path || undefined;
|
|
98
|
+
const envPath = process.env.SIDURI_SELF_PATH || process.env.SELF_PATH;
|
|
99
|
+
const configPath = runtime?.config?.organs?.behavior?.selfPath ||
|
|
100
|
+
runtime?.config?.behavior?.selfPath;
|
|
101
|
+
const candidates = [];
|
|
102
|
+
if (explicitPath)
|
|
103
|
+
candidates.push(node_path_1.default.resolve(process.cwd(), explicitPath));
|
|
104
|
+
if (envPath)
|
|
105
|
+
candidates.push(node_path_1.default.resolve(process.cwd(), envPath));
|
|
106
|
+
if (configPath)
|
|
107
|
+
candidates.push(node_path_1.default.resolve(process.cwd(), configPath));
|
|
108
|
+
candidates.push(node_path_1.default.resolve(process.cwd(), 'assets', 'self', `${companionId}.self`));
|
|
109
|
+
candidates.push(node_path_1.default.resolve(process.cwd(), `${companionId}.self`));
|
|
110
|
+
const searchDirs = [
|
|
111
|
+
node_path_1.default.resolve(process.cwd(), 'assets', 'self'),
|
|
112
|
+
node_path_1.default.resolve(process.cwd(), 'assets'),
|
|
113
|
+
node_path_1.default.resolve(process.cwd()),
|
|
114
|
+
node_path_1.default.resolve(process.cwd(), '..', '..', 'assets', 'self'),
|
|
115
|
+
node_path_1.default.resolve(process.cwd(), '..', '..'),
|
|
116
|
+
];
|
|
117
|
+
let matchedFilePath;
|
|
118
|
+
for (const candidate of candidates) {
|
|
119
|
+
if (node_fs_1.default.existsSync(candidate)) {
|
|
120
|
+
try {
|
|
121
|
+
const stat = node_fs_1.default.statSync(candidate);
|
|
122
|
+
if (stat.isFile()) {
|
|
123
|
+
matchedFilePath = candidate;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!matchedFilePath) {
|
|
131
|
+
for (const dir of searchDirs) {
|
|
132
|
+
if (node_fs_1.default.existsSync(dir)) {
|
|
133
|
+
try {
|
|
134
|
+
const entries = node_fs_1.default.readdirSync(dir);
|
|
135
|
+
const selfFiles = entries.filter((f) => f.endsWith('.self'));
|
|
136
|
+
if (selfFiles.length > 0) {
|
|
137
|
+
const companionSelf = selfFiles.find((f) => f === `${companionId}.self`);
|
|
138
|
+
matchedFilePath = node_path_1.default.join(dir, companionSelf || selfFiles[0]);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch { }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!matchedFilePath) {
|
|
147
|
+
return res.json({ detected: false });
|
|
148
|
+
}
|
|
149
|
+
const content = await node_fs_1.default.promises.readFile(matchedFilePath, 'utf8');
|
|
150
|
+
const parsed = await (0, self_1.compilePersonaDocument)(content, {
|
|
151
|
+
brain: runtime?.brain,
|
|
152
|
+
companionId,
|
|
153
|
+
});
|
|
154
|
+
let alreadyInstalled = false;
|
|
155
|
+
const repo = new self_1.SqliteSelfRepository({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
|
|
156
|
+
try {
|
|
157
|
+
const existingIdentity = await repo.getIdentity(companionId);
|
|
158
|
+
const existingDirectives = await repo.getActiveDirectives(companionId);
|
|
159
|
+
if (existingIdentity &&
|
|
160
|
+
parsed.manifest?.identity?.name &&
|
|
161
|
+
existingIdentity.name.toLowerCase() === parsed.manifest.identity.name.toLowerCase() &&
|
|
162
|
+
existingDirectives.length > 0) {
|
|
163
|
+
alreadyInstalled = true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch { }
|
|
167
|
+
finally {
|
|
168
|
+
repo.close();
|
|
169
|
+
}
|
|
170
|
+
res.json({
|
|
171
|
+
detected: true,
|
|
172
|
+
filename: node_path_1.default.basename(matchedFilePath),
|
|
173
|
+
path: matchedFilePath,
|
|
174
|
+
content,
|
|
175
|
+
parsed,
|
|
176
|
+
alreadyInstalled,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
res.status(500).json({ error: e.message });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
app.post('/teach/upload-self', auth_1.requireAuth, async (req, res) => {
|
|
184
|
+
try {
|
|
185
|
+
const { content, companionId } = req.body;
|
|
186
|
+
if (!content)
|
|
187
|
+
return res.status(400).json({ error: "Missing content" });
|
|
188
|
+
const targetId = companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
189
|
+
const runtime = runtimes.get(targetId);
|
|
190
|
+
const parsed = await (0, self_1.compilePersonaDocument)(content, {
|
|
191
|
+
brain: runtime?.brain,
|
|
192
|
+
companionId: targetId,
|
|
193
|
+
});
|
|
194
|
+
res.json(parsed);
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
res.status(500).json({ error: e.message });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
app.post('/teach/install-self', auth_1.requireAuth, async (req, res) => {
|
|
201
|
+
try {
|
|
202
|
+
const { companionId, manifest, approvedDirectiveIds } = req.body;
|
|
203
|
+
if (!companionId || !manifest || !Array.isArray(approvedDirectiveIds)) {
|
|
204
|
+
return res.status(400).json({ error: "Missing required fields" });
|
|
205
|
+
}
|
|
206
|
+
if (!manifest.identity || !manifest.identity.name) {
|
|
207
|
+
return res.status(400).json({ error: "Invalid manifest: missing identity.name" });
|
|
208
|
+
}
|
|
209
|
+
const directivesToCommit = manifest.directives?.filter((d) => approvedDirectiveIds.includes(d.id)) || [];
|
|
210
|
+
for (const d of directivesToCommit) {
|
|
211
|
+
if (!d || typeof d.directive !== 'string') {
|
|
212
|
+
return res.status(400).json({ error: "Invalid directive entry: missing directive string" });
|
|
213
|
+
}
|
|
214
|
+
const scan = (0, self_1.scanDirective)(d.directive);
|
|
215
|
+
if (!scan.safe) {
|
|
216
|
+
return res.status(400).json({
|
|
217
|
+
error: `Safety check failed for directive: ${scan.reason}`,
|
|
218
|
+
directiveId: d.id,
|
|
219
|
+
reason: scan.reason,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const repo = new self_1.SqliteSelfRepository({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
|
|
224
|
+
try {
|
|
225
|
+
await repo.setIdentity({
|
|
226
|
+
companionId,
|
|
227
|
+
name: manifest.identity.name,
|
|
228
|
+
archetype: manifest.identity.archetype,
|
|
229
|
+
origin: manifest.identity.origin,
|
|
230
|
+
ethos: manifest.identity.ethos,
|
|
231
|
+
version: manifest.version || '1.0.0',
|
|
232
|
+
updatedAt: new Date().toISOString(),
|
|
233
|
+
});
|
|
234
|
+
if (manifest.personality) {
|
|
235
|
+
await repo.setPersonality(companionId, manifest.personality);
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(manifest.relationships)) {
|
|
238
|
+
for (const rel of manifest.relationships) {
|
|
239
|
+
if (rel && rel.entityId) {
|
|
240
|
+
await repo.updateRelationship(companionId, {
|
|
241
|
+
companionId,
|
|
242
|
+
entityId: rel.entityId,
|
|
243
|
+
entityType: 'human',
|
|
244
|
+
role: rel.role || 'user',
|
|
245
|
+
stance: rel.stance || 'neutral',
|
|
246
|
+
interactionConventions: rel.conventions || [],
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (Array.isArray(manifest.dialogueExamples) && repo.setExemplars) {
|
|
252
|
+
await repo.setExemplars(companionId, manifest.dialogueExamples);
|
|
253
|
+
}
|
|
254
|
+
if (directivesToCommit.length > 0) {
|
|
255
|
+
await repo.commitDirectives(companionId, directivesToCommit);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
repo.close();
|
|
260
|
+
}
|
|
261
|
+
res.json({ success: true });
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
res.status(500).json({ error: e.message });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
app.get('/teach/identity', auth_1.attachIdentity, async (req, res) => {
|
|
268
|
+
try {
|
|
269
|
+
const companionId = req.query.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
270
|
+
const runtime = runtimes.get(companionId);
|
|
271
|
+
let identity = null;
|
|
272
|
+
if (runtime?.self && typeof runtime.self.getIdentity === 'function') {
|
|
273
|
+
try {
|
|
274
|
+
identity = await runtime.self.getIdentity(companionId);
|
|
275
|
+
}
|
|
276
|
+
catch { }
|
|
277
|
+
}
|
|
278
|
+
if (!identity) {
|
|
279
|
+
const repo = new self_1.SqliteSelfRepository({ dbPath: process.env.STORAGE_PATH || process.env.SQLITE_DB_PATH || 'siduri.sqlite' });
|
|
280
|
+
try {
|
|
281
|
+
identity = await repo.getIdentity(companionId);
|
|
282
|
+
}
|
|
283
|
+
catch { }
|
|
284
|
+
finally {
|
|
285
|
+
repo.close();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const configuredName = runtime?.config?.name || 'Siduri';
|
|
289
|
+
res.json({
|
|
290
|
+
companionId,
|
|
291
|
+
name: identity?.name || configuredName,
|
|
292
|
+
archetype: identity?.archetype || identity?.role,
|
|
293
|
+
origin: identity?.origin,
|
|
294
|
+
ethos: identity?.ethos,
|
|
295
|
+
version: identity?.version,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
res.status(500).json({ error: e.message });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
// CHAT (API context boundary validation)
|
|
303
|
+
app.post('/chat', auth_1.attachIdentity, async (req, res) => {
|
|
304
|
+
const { id, message, history } = req.body;
|
|
305
|
+
const identity = req.identity;
|
|
306
|
+
const local = (0, auth_1.isLocalRequest)(req);
|
|
307
|
+
const hasConfiguredToken = Boolean(process.env.AUTH_TOKEN || process.env.OPERATOR_TOKEN || process.env.OWNER_TOKEN);
|
|
308
|
+
const isLocalDirectOwner = local && !hasConfiguredToken;
|
|
309
|
+
const authenticated = identity?.authenticated || isLocalDirectOwner;
|
|
310
|
+
const serverRole = identity?.authenticated ? identity.role : (isLocalDirectOwner ? 'OWNER' : 'VIEWER');
|
|
311
|
+
// Single-owner companion model: map request context directly with server identity enforcement
|
|
312
|
+
const mappingResult = (0, context_mapper_1.mapRequestContext)({
|
|
313
|
+
...req.body,
|
|
314
|
+
id: id || req.body.companionId,
|
|
315
|
+
authenticated,
|
|
316
|
+
serverRole,
|
|
317
|
+
source: identity?.source ?? (local ? 'local' : 'external'),
|
|
318
|
+
generateCorrelationId: true,
|
|
319
|
+
});
|
|
320
|
+
if (!mappingResult.accepted) {
|
|
321
|
+
return res.status(400).json({
|
|
322
|
+
accepted: false,
|
|
323
|
+
error: mappingResult.error,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const companionId = mappingResult.context.companionId;
|
|
327
|
+
const runtime = runtimes.get(companionId);
|
|
328
|
+
if (!runtime)
|
|
329
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
330
|
+
try {
|
|
331
|
+
const response = await (0, runtime_1.dispatchCompanionChat)(runtime, {
|
|
332
|
+
id: companionId,
|
|
333
|
+
companionId,
|
|
334
|
+
message,
|
|
335
|
+
context: mappingResult.context,
|
|
336
|
+
history,
|
|
337
|
+
...(req.body?.medium ? { medium: req.body.medium } : {}),
|
|
338
|
+
subtitleLanguage: req.body?.subtitleLanguage || req.body?.subtitle_language,
|
|
339
|
+
});
|
|
340
|
+
res.json(response);
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
res.status(500).json({ error: e.message });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
// REAL-TIME SSE STREAMING (Mouth transport)
|
|
347
|
+
app.post('/chat/stream', auth_1.attachIdentity, async (req, res) => {
|
|
348
|
+
const { id, message, history } = req.body;
|
|
349
|
+
const identity = req.identity;
|
|
350
|
+
const local = (0, auth_1.isLocalRequest)(req);
|
|
351
|
+
const hasConfiguredToken = Boolean(process.env.AUTH_TOKEN || process.env.OPERATOR_TOKEN || process.env.OWNER_TOKEN);
|
|
352
|
+
const isLocalDirectOwner = local && !hasConfiguredToken;
|
|
353
|
+
const authenticated = identity?.authenticated || isLocalDirectOwner;
|
|
354
|
+
const serverRole = identity?.authenticated ? identity.role : (isLocalDirectOwner ? 'OWNER' : 'VIEWER');
|
|
355
|
+
const mappingResult = (0, context_mapper_1.mapRequestContext)({
|
|
356
|
+
...req.body,
|
|
357
|
+
id: id || req.body.companionId,
|
|
358
|
+
authenticated,
|
|
359
|
+
serverRole,
|
|
360
|
+
source: identity?.source ?? (local ? 'local' : 'external'),
|
|
361
|
+
generateCorrelationId: true,
|
|
362
|
+
});
|
|
363
|
+
if (!mappingResult.accepted) {
|
|
364
|
+
return res.status(400).json({
|
|
365
|
+
accepted: false,
|
|
366
|
+
error: mappingResult.error,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const companionId = mappingResult.context.companionId;
|
|
370
|
+
const runtime = runtimes.get(companionId);
|
|
371
|
+
if (!runtime)
|
|
372
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
373
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
374
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
375
|
+
res.setHeader('Connection', 'keep-alive');
|
|
376
|
+
res.flushHeaders?.();
|
|
377
|
+
const abortController = new AbortController();
|
|
378
|
+
const onClose = () => {
|
|
379
|
+
if (!res.writableEnded) {
|
|
380
|
+
abortController.abort('client_disconnect');
|
|
381
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
382
|
+
runtime.mouth.interrupt('client_disconnect');
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
res.on('close', onClose);
|
|
387
|
+
try {
|
|
388
|
+
const response = await (0, runtime_1.dispatchCompanionChat)(runtime, {
|
|
389
|
+
id: companionId,
|
|
390
|
+
companionId,
|
|
391
|
+
message,
|
|
392
|
+
context: mappingResult.context,
|
|
393
|
+
history,
|
|
394
|
+
medium: 'web',
|
|
395
|
+
signal: abortController.signal,
|
|
396
|
+
subtitleLanguage: req.body?.subtitleLanguage || req.body?.subtitle_language,
|
|
397
|
+
});
|
|
398
|
+
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`);
|
|
399
|
+
const avatarEvent = response.metadata?.events?.find((e) => (e.kind === 'avatar' || e.kind === 'body') && (e.approval?.toLowerCase() === 'approved' || e.approval === 'APPROVED' || !e.approval));
|
|
400
|
+
if (avatarEvent) {
|
|
401
|
+
res.write(`event: avatar\ndata: ${JSON.stringify(avatarEvent)}\n\n`);
|
|
402
|
+
}
|
|
403
|
+
const maxStreamLen = Number(process.env.SIDURI_MAX_RESPONSE_CHARS || 8000);
|
|
404
|
+
const rawSpeechText = response.delivery?.text || response.response?.subtitle_en || response.response?.spoken_ja || '';
|
|
405
|
+
const speechText = rawSpeechText.slice(0, maxStreamLen);
|
|
406
|
+
const utterance = {
|
|
407
|
+
utteranceId: response.response_id || 'utt-stream',
|
|
408
|
+
companionId,
|
|
409
|
+
responseId: response.response_id,
|
|
410
|
+
correlationId: response.correlation_id,
|
|
411
|
+
text: speechText,
|
|
412
|
+
medium: 'web',
|
|
413
|
+
expression: avatarEvent?.expression,
|
|
414
|
+
action: avatarEvent?.action,
|
|
415
|
+
signal: abortController.signal,
|
|
416
|
+
};
|
|
417
|
+
if (runtime.mouth && typeof runtime.mouth.stream === 'function') {
|
|
418
|
+
for await (const chunk of runtime.mouth.stream(utterance)) {
|
|
419
|
+
if (abortController.signal.aborted) {
|
|
420
|
+
res.write(`event: chunk\ndata: ${JSON.stringify({ ...chunk, interrupted: true })}\n\n`);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
res.write(`event: chunk\ndata: ${JSON.stringify(chunk)}\n\n`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
res.write(`event: chunk\ndata: ${JSON.stringify({ utteranceId: utterance.utteranceId, index: 1, deltaText: speechText, isComplete: true, medium: 'web' })}\n\n`);
|
|
428
|
+
}
|
|
429
|
+
res.write(`event: done\ndata: ${JSON.stringify(response)}\n\n`);
|
|
430
|
+
res.end();
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
if (abortController.signal.aborted) {
|
|
434
|
+
res.write(`event: interrupted\ndata: ${JSON.stringify({ reason: abortController.signal.reason })}\n\n`);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
res.write(`event: error\ndata: ${JSON.stringify({ error: e.message })}\n\n`);
|
|
438
|
+
}
|
|
439
|
+
res.end();
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
res.removeListener('close', onClose);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
// INTERRUPTION / BARGE-IN
|
|
446
|
+
app.post('/chat/interrupt', auth_1.attachIdentity, (req, res) => {
|
|
447
|
+
const companionId = req.body?.companionId || req.body?.id || Array.from(runtimes.keys())[0];
|
|
448
|
+
const runtime = companionId ? runtimes.get(companionId) : undefined;
|
|
449
|
+
const reason = req.body?.reason || 'user_barge_in';
|
|
450
|
+
if (runtime) {
|
|
451
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
452
|
+
runtime.mouth.interrupt(reason);
|
|
453
|
+
}
|
|
454
|
+
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
455
|
+
}
|
|
456
|
+
for (const r of runtimes.values()) {
|
|
457
|
+
if (r.mouth && typeof r.mouth.interrupt === 'function') {
|
|
458
|
+
r.mouth.interrupt(reason);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return res.json({ success: true, interrupted: true, reason });
|
|
462
|
+
});
|
|
463
|
+
app.post('/mouth/interrupt', auth_1.attachIdentity, (req, res) => {
|
|
464
|
+
const companionId = req.body?.companionId || req.body?.id || Array.from(runtimes.keys())[0];
|
|
465
|
+
const runtime = companionId ? runtimes.get(companionId) : undefined;
|
|
466
|
+
const reason = req.body?.reason || 'user_barge_in';
|
|
467
|
+
if (runtime) {
|
|
468
|
+
if (runtime.mouth && typeof runtime.mouth.interrupt === 'function') {
|
|
469
|
+
runtime.mouth.interrupt(reason);
|
|
470
|
+
}
|
|
471
|
+
return res.json({ success: true, interrupted: true, companionId, reason });
|
|
472
|
+
}
|
|
473
|
+
for (const r of runtimes.values()) {
|
|
474
|
+
if (r.mouth && typeof r.mouth.interrupt === 'function') {
|
|
475
|
+
r.mouth.interrupt(reason);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return res.json({ success: true, interrupted: true, reason });
|
|
479
|
+
});
|
|
480
|
+
// MEMORY GETTERS
|
|
481
|
+
app.get('/memory/proposals', auth_1.requireAuth, async (req, res) => {
|
|
482
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
483
|
+
const runtime = runtimes.get(id);
|
|
484
|
+
if (!runtime)
|
|
485
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
486
|
+
if (!runtime.memory)
|
|
487
|
+
return res.json({ proposals: [] });
|
|
488
|
+
try {
|
|
489
|
+
const proposals = await runtime.memory.getPendingClaims();
|
|
490
|
+
res.json({ proposals });
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
res.status(500).json({ error: e.message });
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
app.get('/memory', auth_1.requireAuth, async (req, res) => {
|
|
497
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
498
|
+
const runtime = runtimes.get(id);
|
|
499
|
+
if (!runtime)
|
|
500
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
501
|
+
if (!runtime.memory)
|
|
502
|
+
return res.json({ items: [] });
|
|
503
|
+
try {
|
|
504
|
+
const items = await runtime.memory.getClaims();
|
|
505
|
+
res.json({ items });
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
res.status(500).json({ error: e.message });
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
app.get('/memory/claims', auth_1.requireAuth, async (req, res) => {
|
|
512
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
513
|
+
const runtime = runtimes.get(id);
|
|
514
|
+
if (!runtime)
|
|
515
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
516
|
+
if (!runtime.memory)
|
|
517
|
+
return res.json({ claims: [] });
|
|
518
|
+
try {
|
|
519
|
+
const claims = typeof runtime.memory.getAllClaims === 'function'
|
|
520
|
+
? await runtime.memory.getAllClaims()
|
|
521
|
+
: await runtime.memory.getClaims();
|
|
522
|
+
res.json({ claims });
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
res.status(500).json({ error: e.message });
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
app.get('/memory/behavioral', auth_1.requireAuth, async (req, res) => {
|
|
529
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
530
|
+
const runtime = runtimes.get(id);
|
|
531
|
+
if (!runtime)
|
|
532
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
533
|
+
if (!runtime.memory)
|
|
534
|
+
return res.json({ directives: [] });
|
|
535
|
+
try {
|
|
536
|
+
const directives = typeof runtime.memory.getAllDirectives === 'function'
|
|
537
|
+
? await runtime.memory.getAllDirectives()
|
|
538
|
+
: await runtime.memory.getDirectives();
|
|
539
|
+
res.json({ directives });
|
|
540
|
+
}
|
|
541
|
+
catch (e) {
|
|
542
|
+
res.status(500).json({ error: e.message });
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
// KNOWLEDGE / LIFE DB GETTERS
|
|
546
|
+
app.get('/knowledge/life', auth_1.requireAuth, async (req, res) => {
|
|
547
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
548
|
+
const runtime = runtimes.get(id);
|
|
549
|
+
if (!runtime)
|
|
550
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
551
|
+
const query = req.query.q || '';
|
|
552
|
+
if (!runtime.knowledge || typeof runtime.knowledge.queryLifeContext !== 'function') {
|
|
553
|
+
return res.json({ matchedInventory: [], recentFinances: [], upcomingSchedule: [], preferences: [], formattedContext: '' });
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const result = await runtime.knowledge.queryLifeContext(id, query);
|
|
557
|
+
res.json(result);
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
res.status(500).json({ error: e.message });
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
app.get('/knowledge/inventory', auth_1.requireAuth, async (req, res) => {
|
|
564
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
565
|
+
const runtime = runtimes.get(id);
|
|
566
|
+
if (!runtime)
|
|
567
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
568
|
+
const domain = req.query.domain;
|
|
569
|
+
if (!runtime.knowledge || !runtime.knowledge.inventory) {
|
|
570
|
+
return res.json({ items: [] });
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
const items = await runtime.knowledge.inventory.getItems(id, domain);
|
|
574
|
+
res.json({ items });
|
|
575
|
+
}
|
|
576
|
+
catch (e) {
|
|
577
|
+
res.status(500).json({ error: e.message });
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
app.get('/knowledge/finance', auth_1.requireAuth, async (req, res) => {
|
|
581
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
582
|
+
const runtime = runtimes.get(id);
|
|
583
|
+
if (!runtime)
|
|
584
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
585
|
+
if (!runtime.knowledge || !runtime.knowledge.finance) {
|
|
586
|
+
return res.json({ summary: null, entries: [] });
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
const limit = Number(req.query.limit || 20);
|
|
590
|
+
const [summary, entries] = await Promise.all([
|
|
591
|
+
runtime.knowledge.finance.getSummary(id),
|
|
592
|
+
runtime.knowledge.finance.getEntries(id, limit),
|
|
593
|
+
]);
|
|
594
|
+
res.json({ summary, entries });
|
|
595
|
+
}
|
|
596
|
+
catch (e) {
|
|
597
|
+
res.status(500).json({ error: e.message });
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
app.get('/knowledge/schedule', auth_1.requireAuth, async (req, res) => {
|
|
601
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
602
|
+
const runtime = runtimes.get(id);
|
|
603
|
+
if (!runtime)
|
|
604
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
605
|
+
if (!runtime.knowledge || !runtime.knowledge.schedule) {
|
|
606
|
+
return res.json({ items: [] });
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
const items = await runtime.knowledge.schedule.getUpcoming(id);
|
|
610
|
+
res.json({ items });
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
res.status(500).json({ error: e.message });
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
app.get('/knowledge/preferences', auth_1.requireAuth, async (req, res) => {
|
|
617
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
618
|
+
const runtime = runtimes.get(id);
|
|
619
|
+
if (!runtime)
|
|
620
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
621
|
+
const category = req.query.category;
|
|
622
|
+
if (!runtime.knowledge || !runtime.knowledge.preferences) {
|
|
623
|
+
return res.json({ preferences: [] });
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
const preferences = await runtime.knowledge.preferences.getPreferences(id, category);
|
|
627
|
+
res.json({ preferences });
|
|
628
|
+
}
|
|
629
|
+
catch (e) {
|
|
630
|
+
res.status(500).json({ error: e.message });
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
app.get('/knowledge/entities', auth_1.requireAuth, async (req, res) => {
|
|
634
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
635
|
+
const runtime = runtimes.get(id);
|
|
636
|
+
if (!runtime)
|
|
637
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
638
|
+
const entityType = req.query.type;
|
|
639
|
+
const domain = req.query.domain;
|
|
640
|
+
if (!runtime.knowledge || !runtime.knowledge.entities) {
|
|
641
|
+
return res.json({ entities: [] });
|
|
642
|
+
}
|
|
643
|
+
try {
|
|
644
|
+
const entities = await runtime.knowledge.entities.getEntities(id, entityType, domain);
|
|
645
|
+
res.json({ entities });
|
|
646
|
+
}
|
|
647
|
+
catch (e) {
|
|
648
|
+
res.status(500).json({ error: e.message });
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
app.get('/knowledge/events', auth_1.requireAuth, async (req, res) => {
|
|
652
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
653
|
+
const runtime = runtimes.get(id);
|
|
654
|
+
if (!runtime)
|
|
655
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
656
|
+
const stream = req.query.stream;
|
|
657
|
+
const limit = Number(req.query.limit || 50);
|
|
658
|
+
if (!runtime.knowledge || !runtime.knowledge.events) {
|
|
659
|
+
return res.json({ events: [] });
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const events = await runtime.knowledge.events.getEvents(id, stream, limit);
|
|
663
|
+
res.json({ events });
|
|
664
|
+
}
|
|
665
|
+
catch (e) {
|
|
666
|
+
res.status(500).json({ error: e.message });
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
app.get('/knowledge/tasks', auth_1.requireAuth, async (req, res) => {
|
|
670
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
671
|
+
const runtime = runtimes.get(id);
|
|
672
|
+
if (!runtime)
|
|
673
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
674
|
+
const status = req.query.status;
|
|
675
|
+
if (!runtime.knowledge || !runtime.knowledge.tasks) {
|
|
676
|
+
return res.json({ tasks: [] });
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
const tasks = await runtime.knowledge.tasks.getTasks(id, status);
|
|
680
|
+
res.json({ tasks });
|
|
681
|
+
}
|
|
682
|
+
catch (e) {
|
|
683
|
+
res.status(500).json({ error: e.message });
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
app.post('/knowledge/proposals/approve', auth_1.requireAuth, async (req, res) => {
|
|
687
|
+
const id = req.body.companionId || 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
|
+
try {
|
|
692
|
+
if (typeof runtime.approveProposal === 'function') {
|
|
693
|
+
const result = await runtime.approveProposal(req.body.id, { companionId: id });
|
|
694
|
+
res.json({ approved: true, target: result?.target || 'knowledge', status: 'approved' });
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
res.status(400).json({ error: 'Runtime does not support proposal approval' });
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
catch (e) {
|
|
701
|
+
res.status(500).json({ error: e.message });
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
app.post('/knowledge/entities', auth_1.requireAuth, async (req, res) => {
|
|
705
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
706
|
+
const runtime = runtimes.get(id);
|
|
707
|
+
if (!runtime)
|
|
708
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
709
|
+
if (!runtime.knowledge || !runtime.knowledge.entities) {
|
|
710
|
+
return res.status(400).json({ error: 'Knowledge organ does not support entities' });
|
|
711
|
+
}
|
|
712
|
+
try {
|
|
713
|
+
const entity = {
|
|
714
|
+
id: req.body.id || `ent-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
715
|
+
companionId: id,
|
|
716
|
+
name: req.body.name,
|
|
717
|
+
entityType: req.body.entityType || req.body.type || 'entity',
|
|
718
|
+
domain: req.body.domain || 'general',
|
|
719
|
+
properties: req.body.properties || {},
|
|
720
|
+
};
|
|
721
|
+
await runtime.knowledge.entities.saveEntity(entity);
|
|
722
|
+
res.json({ saved: true, entity });
|
|
723
|
+
}
|
|
724
|
+
catch (e) {
|
|
725
|
+
res.status(500).json({ error: e.message });
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
app.post('/knowledge/entities/delete', auth_1.requireAuth, async (req, res) => {
|
|
729
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
730
|
+
const runtime = runtimes.get(id);
|
|
731
|
+
if (!runtime)
|
|
732
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
733
|
+
if (!runtime.knowledge || !runtime.knowledge.entities) {
|
|
734
|
+
return res.status(400).json({ error: 'Knowledge organ does not support entities' });
|
|
735
|
+
}
|
|
736
|
+
try {
|
|
737
|
+
const success = await runtime.knowledge.entities.deleteEntity(req.body.id);
|
|
738
|
+
res.json({ deleted: success, id: req.body.id });
|
|
739
|
+
}
|
|
740
|
+
catch (e) {
|
|
741
|
+
res.status(500).json({ error: e.message });
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
app.post('/knowledge/tasks', auth_1.requireAuth, async (req, res) => {
|
|
745
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
746
|
+
const runtime = runtimes.get(id);
|
|
747
|
+
if (!runtime)
|
|
748
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
749
|
+
if (!runtime.knowledge || !runtime.knowledge.tasks) {
|
|
750
|
+
return res.status(400).json({ error: 'Knowledge organ does not support tasks' });
|
|
751
|
+
}
|
|
752
|
+
try {
|
|
753
|
+
const task = {
|
|
754
|
+
id: req.body.id || `task-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
755
|
+
companionId: id,
|
|
756
|
+
title: req.body.title,
|
|
757
|
+
status: req.body.status || 'todo',
|
|
758
|
+
priority: req.body.priority !== undefined ? Number(req.body.priority) : 1,
|
|
759
|
+
targetDate: req.body.targetDate || null,
|
|
760
|
+
metadata: req.body.metadata || {},
|
|
761
|
+
};
|
|
762
|
+
await runtime.knowledge.tasks.saveTask(task);
|
|
763
|
+
res.json({ saved: true, task });
|
|
764
|
+
}
|
|
765
|
+
catch (e) {
|
|
766
|
+
res.status(500).json({ error: e.message });
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
app.post('/knowledge/tasks/delete', auth_1.requireAuth, async (req, res) => {
|
|
770
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
771
|
+
const runtime = runtimes.get(id);
|
|
772
|
+
if (!runtime)
|
|
773
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
774
|
+
if (!runtime.knowledge || !runtime.knowledge.tasks) {
|
|
775
|
+
return res.status(400).json({ error: 'Knowledge organ does not support tasks' });
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
const success = await runtime.knowledge.tasks.deleteTask(req.body.id);
|
|
779
|
+
res.json({ deleted: success, id: req.body.id });
|
|
780
|
+
}
|
|
781
|
+
catch (e) {
|
|
782
|
+
res.status(500).json({ error: e.message });
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
app.post('/knowledge/events', auth_1.requireAuth, async (req, res) => {
|
|
786
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
787
|
+
const runtime = runtimes.get(id);
|
|
788
|
+
if (!runtime)
|
|
789
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
790
|
+
if (!runtime.knowledge || !runtime.knowledge.events) {
|
|
791
|
+
return res.status(400).json({ error: 'Knowledge organ does not support events' });
|
|
792
|
+
}
|
|
793
|
+
try {
|
|
794
|
+
const event = {
|
|
795
|
+
id: req.body.id || `evt-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
796
|
+
companionId: id,
|
|
797
|
+
stream: req.body.stream || 'default',
|
|
798
|
+
timestamp: req.body.timestamp || new Date().toISOString(),
|
|
799
|
+
metricValue: req.body.metricValue !== undefined && req.body.metricValue !== null ? Number(req.body.metricValue) : undefined,
|
|
800
|
+
metadata: req.body.metadata || {},
|
|
801
|
+
};
|
|
802
|
+
await runtime.knowledge.events.addEvent(event);
|
|
803
|
+
res.json({ saved: true, event });
|
|
804
|
+
}
|
|
805
|
+
catch (e) {
|
|
806
|
+
res.status(500).json({ error: e.message });
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
app.post('/knowledge/schedule', auth_1.requireAuth, async (req, res) => {
|
|
810
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
811
|
+
const runtime = runtimes.get(id);
|
|
812
|
+
if (!runtime)
|
|
813
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
814
|
+
if (!runtime.knowledge || !runtime.knowledge.schedule) {
|
|
815
|
+
return res.status(400).json({ error: 'Knowledge organ does not support schedule' });
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
const item = {
|
|
819
|
+
id: req.body.id || `sched-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
820
|
+
companionId: id,
|
|
821
|
+
title: req.body.title,
|
|
822
|
+
startTime: req.body.startTime,
|
|
823
|
+
endTime: req.body.endTime || null,
|
|
824
|
+
isRecurring: Boolean(req.body.isRecurring),
|
|
825
|
+
status: req.body.status || 'active',
|
|
826
|
+
};
|
|
827
|
+
await runtime.knowledge.schedule.saveItem(item);
|
|
828
|
+
res.json({ saved: true, item });
|
|
829
|
+
}
|
|
830
|
+
catch (e) {
|
|
831
|
+
res.status(500).json({ error: e.message });
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
app.post('/knowledge/schedule/delete', auth_1.requireAuth, async (req, res) => {
|
|
835
|
+
const id = req.body.companionId || req.query.id || Array.from(runtimes.keys())[0];
|
|
836
|
+
const runtime = runtimes.get(id);
|
|
837
|
+
if (!runtime)
|
|
838
|
+
return res.status(404).json({ error: 'Companion not found' });
|
|
839
|
+
if (!runtime.knowledge || !runtime.knowledge.schedule) {
|
|
840
|
+
return res.status(400).json({ error: 'Knowledge organ does not support schedule' });
|
|
841
|
+
}
|
|
842
|
+
try {
|
|
843
|
+
const success = typeof runtime.knowledge.schedule.deleteItem === 'function'
|
|
844
|
+
? await runtime.knowledge.schedule.deleteItem(req.body.id)
|
|
845
|
+
: false;
|
|
846
|
+
res.json({ deleted: success, id: req.body.id });
|
|
847
|
+
}
|
|
848
|
+
catch (e) {
|
|
849
|
+
res.status(500).json({ error: e.message });
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
// MEMORY MUTATIONS - PROPOSALS
|
|
853
|
+
app.post('/memory/proposals/update', auth_1.requireAuth, async (req, res) => {
|
|
854
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
855
|
+
const runtime = runtimes.get(id);
|
|
856
|
+
if (!runtime)
|
|
857
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
858
|
+
if (!runtime.memory || typeof runtime.memory.updateClaim !== 'function') {
|
|
859
|
+
return res.status(400).json({ error: "Memory organ does not support updating claims" });
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const claimId = req.body.id || req.body.claimId;
|
|
863
|
+
if (!claimId) {
|
|
864
|
+
return res.status(400).json({ error: "Missing required claim id" });
|
|
865
|
+
}
|
|
866
|
+
const updated = await runtime.memory.updateClaim(claimId, req.body.updates || req.body);
|
|
867
|
+
res.json({ success: true, claim: updated });
|
|
868
|
+
}
|
|
869
|
+
catch (e) {
|
|
870
|
+
res.status(500).json({ error: e.message });
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
app.post('/memory/proposals/approve', auth_1.requireAuth, async (req, res) => {
|
|
874
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
875
|
+
const runtime = runtimes.get(id);
|
|
876
|
+
if (!runtime)
|
|
877
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
878
|
+
if (!runtime.memory)
|
|
879
|
+
return res.status(400).json({ error: "Memory organ not configured" });
|
|
880
|
+
try {
|
|
881
|
+
let name;
|
|
882
|
+
if (typeof runtime.approveProposal === 'function') {
|
|
883
|
+
const pRes = await runtime.approveProposal(req.body.id, { companionId: id });
|
|
884
|
+
name = pRes?.name;
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
await runtime.memory.approveClaim(req.body.id);
|
|
888
|
+
}
|
|
889
|
+
if (!name && runtime.self && typeof runtime.self.getIdentity === 'function') {
|
|
890
|
+
try {
|
|
891
|
+
const ident = await runtime.self.getIdentity(id);
|
|
892
|
+
name = ident?.name;
|
|
893
|
+
}
|
|
894
|
+
catch { }
|
|
895
|
+
}
|
|
896
|
+
res.json({ approved: true, status: 'approved', name });
|
|
897
|
+
}
|
|
898
|
+
catch (e) {
|
|
899
|
+
res.status(500).json({ error: e.message });
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
app.post('/memory/proposals/reject', auth_1.requireAuth, async (req, res) => {
|
|
903
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
904
|
+
const runtime = runtimes.get(id);
|
|
905
|
+
if (!runtime)
|
|
906
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
907
|
+
if (!runtime.memory)
|
|
908
|
+
return res.status(400).json({ error: "Memory organ not configured" });
|
|
909
|
+
try {
|
|
910
|
+
if (typeof runtime.rejectProposal === 'function') {
|
|
911
|
+
await runtime.rejectProposal(req.body.id, { companionId: id });
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
await runtime.memory.rejectClaim(req.body.id);
|
|
915
|
+
}
|
|
916
|
+
res.json({ rejected: true, status: 'rejected' });
|
|
917
|
+
}
|
|
918
|
+
catch (e) {
|
|
919
|
+
res.status(500).json({ error: e.message });
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
// MEMORY MUTATIONS - BEHAVIORAL
|
|
923
|
+
app.post('/memory/behavioral/approve', auth_1.requireAuth, async (req, res) => {
|
|
924
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
925
|
+
const runtime = runtimes.get(id);
|
|
926
|
+
if (!runtime)
|
|
927
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
928
|
+
if (!runtime.memory)
|
|
929
|
+
return res.status(400).json({ error: "Memory organ not configured" });
|
|
930
|
+
try {
|
|
931
|
+
let name;
|
|
932
|
+
if (typeof runtime.approveDirective === 'function') {
|
|
933
|
+
const bRes = await runtime.approveDirective(req.body.id, { companionId: id });
|
|
934
|
+
name = bRes?.name;
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
await runtime.memory.approveDirective(req.body.id);
|
|
938
|
+
}
|
|
939
|
+
if (!name && runtime.self && typeof runtime.self.getIdentity === 'function') {
|
|
940
|
+
try {
|
|
941
|
+
const ident = await runtime.self.getIdentity(id);
|
|
942
|
+
name = ident?.name;
|
|
943
|
+
}
|
|
944
|
+
catch { }
|
|
945
|
+
}
|
|
946
|
+
res.json({ approved: true, status: 'active', name });
|
|
947
|
+
}
|
|
948
|
+
catch (e) {
|
|
949
|
+
res.status(500).json({ error: e.message });
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
app.post('/memory/behavioral/reject', 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
|
+
if (!runtime.memory)
|
|
958
|
+
return res.status(400).json({ error: "Memory organ not configured" });
|
|
959
|
+
try {
|
|
960
|
+
if (typeof runtime.rejectDirective === 'function') {
|
|
961
|
+
await runtime.rejectDirective(req.body.id, { companionId: id });
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
await runtime.memory.rejectDirective(req.body.id);
|
|
965
|
+
}
|
|
966
|
+
res.json({ rejected: true, status: 'rejected' });
|
|
967
|
+
}
|
|
968
|
+
catch (e) {
|
|
969
|
+
res.status(500).json({ error: e.message });
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
app.post('/memory/behavioral/revoke', auth_1.requireAuth, async (req, res) => {
|
|
973
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
974
|
+
const runtime = runtimes.get(id);
|
|
975
|
+
if (!runtime)
|
|
976
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
977
|
+
if (!runtime.memory)
|
|
978
|
+
return res.status(400).json({ error: "Memory organ not configured" });
|
|
979
|
+
try {
|
|
980
|
+
if (typeof runtime.revokeDirective === 'function') {
|
|
981
|
+
await runtime.revokeDirective(req.body.id, { companionId: id });
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
await runtime.memory.revokeDirective(req.body.id);
|
|
985
|
+
}
|
|
986
|
+
res.json({ revoked: true, status: 'revoked' });
|
|
987
|
+
}
|
|
988
|
+
catch (e) {
|
|
989
|
+
res.status(500).json({ error: e.message });
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
app.post('/memory/behavioral/disable', auth_1.requireAuth, async (req, res) => {
|
|
993
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
994
|
+
const runtime = runtimes.get(id);
|
|
995
|
+
if (!runtime)
|
|
996
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
997
|
+
if (!runtime.memory)
|
|
998
|
+
return res.status(400).json({ error: "Memory organ not configured" });
|
|
999
|
+
try {
|
|
1000
|
+
await runtime.memory.disableDirective(req.body.id);
|
|
1001
|
+
res.json({ disabled: true, status: 'disabled' });
|
|
1002
|
+
}
|
|
1003
|
+
catch (e) {
|
|
1004
|
+
res.status(500).json({ error: e.message });
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
// SYSTEM LOGS
|
|
1008
|
+
app.get('/system/logs', auth_1.requireAuth, async (req, res) => {
|
|
1009
|
+
const id = req.query.id || Array.from(runtimes.keys())[0];
|
|
1010
|
+
const runtime = runtimes.get(id);
|
|
1011
|
+
if (!runtime)
|
|
1012
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
1013
|
+
const level = req.query.level;
|
|
1014
|
+
const subsystem = req.query.subsystem;
|
|
1015
|
+
const q = req.query.q;
|
|
1016
|
+
const limit = parseInt(req.query.limit || '100', 10);
|
|
1017
|
+
const offset = parseInt(req.query.offset || '0', 10);
|
|
1018
|
+
try {
|
|
1019
|
+
const logs = typeof runtime.queryLogs === 'function'
|
|
1020
|
+
? runtime.queryLogs({ companionId: id, level, subsystem, q, limit, offset })
|
|
1021
|
+
: [];
|
|
1022
|
+
res.json({ logs });
|
|
1023
|
+
}
|
|
1024
|
+
catch (e) {
|
|
1025
|
+
res.status(500).json({ error: e.message, logs: [] });
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
app.post('/system/logs/clear', auth_1.requireAuth, async (req, res) => {
|
|
1029
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
1030
|
+
const runtime = runtimes.get(id);
|
|
1031
|
+
if (!runtime)
|
|
1032
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
1033
|
+
try {
|
|
1034
|
+
if (typeof runtime.clearLogs === 'function') {
|
|
1035
|
+
runtime.clearLogs(id);
|
|
1036
|
+
}
|
|
1037
|
+
res.json({ cleared: true });
|
|
1038
|
+
}
|
|
1039
|
+
catch (e) {
|
|
1040
|
+
res.status(500).json({ error: e.message });
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
const isDevMode = process.env.NODE_ENV !== 'production' || process.env.SIDURI_DEV_MODE === 'true';
|
|
1044
|
+
if (isDevMode) {
|
|
1045
|
+
app.post('/dev/memory/reset', auth_1.requireAuth, async (req, res) => {
|
|
1046
|
+
const id = req.body.companionId || Array.from(runtimes.keys())[0];
|
|
1047
|
+
const runtime = runtimes.get(id);
|
|
1048
|
+
if (!runtime)
|
|
1049
|
+
return res.status(404).json({ error: "Companion not found" });
|
|
1050
|
+
if (!runtime.memory || typeof runtime.memory.resetMemory !== 'function') {
|
|
1051
|
+
return res.status(400).json({ error: "Memory organ does not support reset" });
|
|
1052
|
+
}
|
|
1053
|
+
try {
|
|
1054
|
+
await runtime.memory.resetMemory();
|
|
1055
|
+
res.json({ reset: true });
|
|
1056
|
+
}
|
|
1057
|
+
catch (e) {
|
|
1058
|
+
res.status(500).json({ error: e.message });
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
app.post('/dev/mock-response', async (req, res) => {
|
|
1062
|
+
const companionId = req.body?.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
1063
|
+
const runtime = runtimes.get(companionId);
|
|
1064
|
+
if (!runtime)
|
|
1065
|
+
return res.status(404).json({ accepted: false, error: 'Companion not found' });
|
|
1066
|
+
const staged = runtime.gating.stageResponse({
|
|
1067
|
+
requestContext: {
|
|
1068
|
+
companionId,
|
|
1069
|
+
actor: {
|
|
1070
|
+
actorId: 'local-user',
|
|
1071
|
+
sessionId: 'sess-local',
|
|
1072
|
+
capabilities: ['chat', 'memory:approve'],
|
|
1073
|
+
authenticated: true,
|
|
1074
|
+
},
|
|
1075
|
+
conversation: {
|
|
1076
|
+
correlationId: req.body?.correlation_id || `corr-${Date.now()}`,
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
candidateSpeech: req.body?.speech || 'Mocked staged response for review',
|
|
1080
|
+
candidateLanguage: req.body?.language || 'en',
|
|
1081
|
+
requiresApproval: req.body?.requiresApproval ?? true,
|
|
1082
|
+
});
|
|
1083
|
+
res.json({
|
|
1084
|
+
accepted: true,
|
|
1085
|
+
staged: true,
|
|
1086
|
+
status: staged.status,
|
|
1087
|
+
response_id: staged.responseId,
|
|
1088
|
+
correlation_id: staged.correlationId,
|
|
1089
|
+
speech: staged.speech,
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
app.post('/dev/approve-response', async (req, res) => {
|
|
1093
|
+
const companionId = req.body?.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
1094
|
+
const runtime = runtimes.get(companionId);
|
|
1095
|
+
if (!runtime)
|
|
1096
|
+
return res.status(404).json({ approved: false, error: 'Companion not found' });
|
|
1097
|
+
let responseId = req.body?.responseId;
|
|
1098
|
+
let correlationId = req.body?.correlation_id;
|
|
1099
|
+
if (!responseId && correlationId) {
|
|
1100
|
+
const found = runtime.gating.findStagedPlanByCorrelation(companionId, correlationId);
|
|
1101
|
+
if (found) {
|
|
1102
|
+
responseId = found.responseId;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
else if (responseId && !correlationId) {
|
|
1106
|
+
const found = runtime.gating.getStagedPlan(responseId);
|
|
1107
|
+
if (found) {
|
|
1108
|
+
correlationId = found.correlationId;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (!responseId) {
|
|
1112
|
+
return res.status(400).json({ approved: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
1113
|
+
}
|
|
1114
|
+
const result = runtime.gating.approveResponse({
|
|
1115
|
+
responseId,
|
|
1116
|
+
companionId,
|
|
1117
|
+
correlationId: correlationId || '',
|
|
1118
|
+
});
|
|
1119
|
+
if (!result.success) {
|
|
1120
|
+
return res.status(400).json({ approved: false, error: result.reason });
|
|
1121
|
+
}
|
|
1122
|
+
// Now evaluated as approved
|
|
1123
|
+
const evaluation = runtime.gating.evaluateGate(result.plan);
|
|
1124
|
+
res.json({
|
|
1125
|
+
approved: true,
|
|
1126
|
+
status: evaluation.disposition,
|
|
1127
|
+
response_id: result.plan.responseId,
|
|
1128
|
+
speech: result.plan.speech,
|
|
1129
|
+
language: result.plan.language,
|
|
1130
|
+
});
|
|
1131
|
+
});
|
|
1132
|
+
app.post('/dev/reject-response', async (req, res) => {
|
|
1133
|
+
const companionId = req.body?.companionId || Array.from(runtimes.keys())[0] || 'default';
|
|
1134
|
+
const runtime = runtimes.get(companionId);
|
|
1135
|
+
if (!runtime)
|
|
1136
|
+
return res.status(404).json({ rejected: false, error: 'Companion not found' });
|
|
1137
|
+
let responseId = req.body?.responseId;
|
|
1138
|
+
let correlationId = req.body?.correlation_id;
|
|
1139
|
+
if (!responseId && correlationId) {
|
|
1140
|
+
const found = runtime.gating.findStagedPlanByCorrelation(companionId, correlationId);
|
|
1141
|
+
if (found) {
|
|
1142
|
+
responseId = found.responseId;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
else if (responseId && !correlationId) {
|
|
1146
|
+
const found = runtime.gating.getStagedPlan(responseId);
|
|
1147
|
+
if (found) {
|
|
1148
|
+
correlationId = found.correlationId;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (!responseId) {
|
|
1152
|
+
return res.status(400).json({ rejected: false, error: 'UNKNOWN_APPROVAL_ID' });
|
|
1153
|
+
}
|
|
1154
|
+
const result = runtime.gating.rejectResponse({
|
|
1155
|
+
responseId,
|
|
1156
|
+
companionId,
|
|
1157
|
+
correlationId: correlationId || '',
|
|
1158
|
+
reason: req.body?.reason,
|
|
1159
|
+
});
|
|
1160
|
+
if (!result.success) {
|
|
1161
|
+
return res.status(400).json({ rejected: false, error: result.reason });
|
|
1162
|
+
}
|
|
1163
|
+
res.json({
|
|
1164
|
+
rejected: true,
|
|
1165
|
+
status: result.plan.status,
|
|
1166
|
+
response_id: result.plan.responseId,
|
|
1167
|
+
});
|
|
1168
|
+
});
|
|
1169
|
+
app.post('/dev/mock-observation', async (req, res) => {
|
|
1170
|
+
const activeRuntime = Array.from(runtimes.values())[0];
|
|
1171
|
+
const targetObs = activeRuntime?.observation || observationOrgan;
|
|
1172
|
+
if (!targetObs)
|
|
1173
|
+
return res.status(503).json({ accepted: false, reason: 'observation_unavailable' });
|
|
1174
|
+
const result = await targetObs.ingest(new Uint8Array([115, 121, 110, 116, 104, 101, 116, 105, 99]), 'fixture-observation', 'configured-vision');
|
|
1175
|
+
if (!result.observation)
|
|
1176
|
+
return res.status(result.duplicate ? 200 : 409).json({ accepted: false, ...result });
|
|
1177
|
+
res.status(202).json({ accepted: true, observation: result.observation });
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
return {
|
|
1181
|
+
app,
|
|
1182
|
+
runtimes,
|
|
1183
|
+
setObservationOrgan: (org) => {
|
|
1184
|
+
observationOrgan = org;
|
|
1185
|
+
for (const r of runtimes.values()) {
|
|
1186
|
+
r.observation = org;
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
};
|
|
1190
|
+
}
|