morpheus-cli 0.9.0 → 0.9.3
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/README.md +18 -4
- package/dist/channels/discord.js +133 -6
- package/dist/channels/telegram.js +23 -17
- package/dist/config/manager.js +11 -0
- package/dist/config/schemas.js +5 -0
- package/dist/http/api.js +5 -3
- package/dist/http/routers/danger.js +137 -0
- package/dist/runtime/apoc.js +1 -1
- package/dist/runtime/audit/repository.js +2 -0
- package/dist/runtime/keymaker.js +1 -1
- package/dist/runtime/memory/sati/index.js +1 -1
- package/dist/runtime/memory/sati/service.js +28 -1
- package/dist/runtime/memory/session-embedding-worker.js +43 -36
- package/dist/runtime/memory/sqlite.js +31 -124
- package/dist/runtime/neo.js +1 -1
- package/dist/runtime/oracle.js +55 -54
- package/dist/runtime/setup/__tests__/repository.test.js +115 -0
- package/dist/runtime/setup/repository.js +87 -0
- package/dist/runtime/smiths/delegator.js +1 -1
- package/dist/runtime/tools/setup-tool.js +57 -0
- package/dist/runtime/trinity.js +1 -1
- package/dist/ui/assets/AuditDashboard-C1f6Hbdw.js +1 -0
- package/dist/ui/assets/Chat-5AeRYuRj.js +41 -0
- package/dist/ui/assets/{Chronos-BAjeLobF.js → Chronos-BrKldYVw.js} +1 -1
- package/dist/ui/assets/{ConfirmationModal-fvgnOWTY.js → ConfirmationModal-DsbS3XkJ.js} +1 -1
- package/dist/ui/assets/{Dashboard-Ca5mSefz.js → Dashboard-DvrTXLdo.js} +1 -1
- package/dist/ui/assets/{DeleteConfirmationModal-A8EmnHoa.js → DeleteConfirmationModal-BfSjv04R.js} +1 -1
- package/dist/ui/assets/{Logs-CYu7se7R.js → Logs-B0ZYWs5x.js} +1 -1
- package/dist/ui/assets/MCPManager-BwHGTeNs.js +1 -0
- package/dist/ui/assets/{ModelPricing-DnSm_Nh-.js → ModelPricing-CYhGRQr8.js} +1 -1
- package/dist/ui/assets/{Notifications-CiljQzvM.js → Notifications-BYMAtVMq.js} +1 -1
- package/dist/ui/assets/{Pagination-JsiwxVNQ.js → Pagination-oTGieBLM.js} +1 -1
- package/dist/ui/assets/SatiMemories-I1vsYtP2.js +1 -0
- package/dist/ui/assets/SessionAudit-BCecQWde.js +9 -0
- package/dist/ui/assets/Settings-Cu4D-7tb.js +47 -0
- package/dist/ui/assets/Skills-lGU3I5DO.js +7 -0
- package/dist/ui/assets/Smiths-DnEH3nID.js +1 -0
- package/dist/ui/assets/Tasks-Bz92GPWK.js +1 -0
- package/dist/ui/assets/{TrinityDatabases-BzYfecKI.js → TrinityDatabases-BUY-3j7Q.js} +1 -1
- package/dist/ui/assets/{UsageStats-CBo2vW2n.js → UsageStats-Dr5eSgJc.js} +1 -1
- package/dist/ui/assets/{WebhookManager-0tDFkfHd.js → WebhookManager-DIASAC-1.js} +1 -1
- package/dist/ui/assets/{audit-B-F8XPLi.js → audit-CcAEDbZh.js} +1 -1
- package/dist/ui/assets/{chronos-BvMxfBQH.js → chronos-2Z9E96_1.js} +1 -1
- package/dist/ui/assets/{config-DteVgNGR.js → config-DdfK4DX6.js} +1 -1
- package/dist/ui/assets/index-D4fzIKy1.css +1 -0
- package/dist/ui/assets/{index-Cwqr-n0Y.js → index-Dpd1Mkgp.js} +5 -5
- package/dist/ui/assets/{mcp-DxzodOdH.js → mcp-BWMt8aY7.js} +1 -1
- package/dist/ui/assets/{skills--hAyQnmG.js → skills-D7JjK7JH.js} +1 -1
- package/dist/ui/assets/{stats-Cibaisqd.js → stats-DoIhtLot.js} +1 -1
- package/dist/ui/assets/{vendor-icons-BVuQI-6R.js → vendor-icons-DMd9RGvJ.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/sw.js +1 -1
- package/package.json +5 -4
- package/dist/ui/assets/AuditDashboard-5sA8Sd8S.js +0 -1
- package/dist/ui/assets/Chat-CjxeAQmd.js +0 -41
- package/dist/ui/assets/MCPManager-DsDA_ZVT.js +0 -1
- package/dist/ui/assets/SatiMemories-rnO2b0LG.js +0 -1
- package/dist/ui/assets/SessionAudit-Dfvhge3Z.js +0 -9
- package/dist/ui/assets/Settings-OQlHAJoy.js +0 -41
- package/dist/ui/assets/Skills-Crsybug0.js +0 -7
- package/dist/ui/assets/Smiths-wm90jRDT.js +0 -1
- package/dist/ui/assets/Tasks-C5FMu_Yu.js +0 -1
- package/dist/ui/assets/index-DcfyUdLI.css +0 -1
|
@@ -97,7 +97,7 @@ export class SatiService {
|
|
|
97
97
|
console.warn('[SatiService] Failed to persist input log:', e);
|
|
98
98
|
}
|
|
99
99
|
const satiStartMs = Date.now();
|
|
100
|
-
const response = await agent.invoke({ messages }, { recursionLimit:
|
|
100
|
+
const response = await agent.invoke({ messages }, { recursionLimit: 50 });
|
|
101
101
|
const satiDurationMs = Date.now() - satiStartMs;
|
|
102
102
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
103
103
|
let content = lastMessage.content.toString();
|
|
@@ -209,6 +209,33 @@ export class SatiService {
|
|
|
209
209
|
display.log(`Deletion skipped — memory not found: ${deletion.id}`, { source: 'Sati', level: 'warning' });
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
+
// Emit audit event for memory persistence results
|
|
213
|
+
const inclusionsCount = (result.inclusions ?? []).filter(i => i.summary && i.category && i.importance).length;
|
|
214
|
+
const editsCount = (result.edits ?? []).filter(e => !!e.id).length;
|
|
215
|
+
const deletionsCount = (result.deletions ?? []).filter(d => !!d.id).length;
|
|
216
|
+
const totalOps = inclusionsCount + editsCount + deletionsCount;
|
|
217
|
+
if (totalOps > 0) {
|
|
218
|
+
try {
|
|
219
|
+
AuditRepository.getInstance().insert({
|
|
220
|
+
session_id: userSessionId ?? 'sati-persist',
|
|
221
|
+
event_type: 'memory_persist',
|
|
222
|
+
agent: 'sati',
|
|
223
|
+
duration_ms: Date.now() - satiStartMs,
|
|
224
|
+
status: 'success',
|
|
225
|
+
metadata: {
|
|
226
|
+
inclusions_count: inclusionsCount,
|
|
227
|
+
edits_count: editsCount,
|
|
228
|
+
deletions_count: deletionsCount,
|
|
229
|
+
inclusions: (result.inclusions ?? []).filter(i => i.summary && i.category && i.importance).map(i => ({ category: i.category, importance: i.importance, summary: i.summary })),
|
|
230
|
+
edits: (result.edits ?? []).filter(e => !!e.id).map(e => ({ id: e.id, summary: e.summary, reason: e.reason })),
|
|
231
|
+
deletions: (result.deletions ?? []).filter(d => !!d.id).map(d => ({ id: d.id, reason: d.reason })),
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
console.warn('[SatiService] Failed to log memory persistence audit event');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
212
239
|
}
|
|
213
240
|
catch (error) {
|
|
214
241
|
console.error('[SatiService] Evaluation failed:', error);
|
|
@@ -18,77 +18,84 @@ export async function runSessionEmbeddingWorker() {
|
|
|
18
18
|
// 🔥 importante: carregar vec0 no DB onde existe a tabela vetorial
|
|
19
19
|
loadVecExtension(satiDb);
|
|
20
20
|
const embeddingService = await EmbeddingService.getInstance();
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
try {
|
|
22
|
+
while (true) {
|
|
23
|
+
const sessions = shortDb.prepare(`
|
|
23
24
|
SELECT id
|
|
24
25
|
FROM sessions
|
|
25
26
|
WHERE ended_at IS NOT NULL
|
|
26
27
|
AND embedding_status = 'pending'
|
|
27
28
|
LIMIT ?
|
|
28
29
|
`).all(BATCH_LIMIT);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
if (sessions.length === 0) {
|
|
31
|
+
// display.log('✅ Nenhuma sessão pendente.', { level: 'debug', source: 'SessionEmbeddingWorker' });
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
for (const session of sessions) {
|
|
35
|
+
const sessionId = session.id;
|
|
36
|
+
display.log(`🧠 Processando sessão ${sessionId}...`, { source: 'SessionEmbeddingWorker' });
|
|
37
|
+
try {
|
|
38
|
+
// Skip setting 'processing' as it violates CHECK constraint
|
|
39
|
+
// active_processing.add(sessionId); // If we needed concurrency control
|
|
40
|
+
const chunks = satiDb.prepare(`
|
|
40
41
|
SELECT id, content
|
|
41
42
|
FROM session_chunks
|
|
42
43
|
WHERE session_id = ?
|
|
43
44
|
ORDER BY chunk_index
|
|
44
45
|
`).all(sessionId);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
if (chunks.length === 0) {
|
|
47
|
+
display.log(`⚠️ Sessão ${sessionId} não possui chunks.`, { source: 'SessionEmbeddingWorker' });
|
|
48
|
+
shortDb.prepare(`
|
|
48
49
|
UPDATE sessions
|
|
49
50
|
SET embedding_status = 'embedded',
|
|
50
51
|
embedded = 1
|
|
51
52
|
WHERE id = ?
|
|
52
53
|
`).run(sessionId);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const insertVec = satiDb.prepare(`
|
|
56
57
|
INSERT INTO session_vec (embedding)
|
|
57
58
|
VALUES (?)
|
|
58
59
|
`);
|
|
59
|
-
|
|
60
|
+
const insertMap = satiDb.prepare(`
|
|
60
61
|
INSERT OR REPLACE INTO session_embedding_map
|
|
61
62
|
(session_chunk_id, vec_rowid)
|
|
62
63
|
VALUES (?, ?)
|
|
63
64
|
`);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
for (const chunk of chunks) {
|
|
66
|
+
display.log(` ↳ Embedding chunk ${chunk.id}`, { source: 'SessionEmbeddingWorker' });
|
|
67
|
+
const embedding = await embeddingService.generate(chunk.content);
|
|
68
|
+
if (!embedding || embedding.length !== EMBEDDING_DIM) {
|
|
69
|
+
throw new Error(`Embedding inválido. Esperado ${EMBEDDING_DIM}, recebido ${embedding?.length}`);
|
|
70
|
+
}
|
|
71
|
+
const result = insertVec.run(new Float32Array(embedding));
|
|
72
|
+
const vecRowId = result.lastInsertRowid;
|
|
73
|
+
insertMap.run(chunk.id, vecRowId);
|
|
69
74
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
insertMap.run(chunk.id, vecRowId);
|
|
73
|
-
}
|
|
74
|
-
// ✅ finalizar sessão
|
|
75
|
-
shortDb.prepare(`
|
|
75
|
+
// ✅ finalizar sessão
|
|
76
|
+
shortDb.prepare(`
|
|
76
77
|
UPDATE sessions
|
|
77
78
|
SET embedding_status = 'embedded',
|
|
78
79
|
embedded = 1
|
|
79
80
|
WHERE id = ?
|
|
80
81
|
`).run(sessionId);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
display.log(`✅ Sessão ${sessionId} embedada com sucesso.`, { source: 'SessionEmbeddingWorker' });
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
display.log(`❌ Erro na sessão ${sessionId}: ${err}`, { source: 'SessionEmbeddingWorker' });
|
|
86
|
+
shortDb.prepare(`
|
|
86
87
|
UPDATE sessions
|
|
87
88
|
SET embedding_status = 'failed'
|
|
88
89
|
WHERE id = ?
|
|
89
90
|
`).run(sessionId);
|
|
91
|
+
}
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
}
|
|
95
|
+
finally {
|
|
96
|
+
// Always close connections when done
|
|
97
|
+
shortDb.close();
|
|
98
|
+
satiDb.close();
|
|
99
|
+
}
|
|
93
100
|
// display.log('🏁 Worker finalizado.', { source: 'SessionEmbeddingWorker' });
|
|
94
101
|
}
|
|
@@ -716,36 +716,19 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
716
716
|
}
|
|
717
717
|
async createNewSession() {
|
|
718
718
|
const now = Date.now();
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
`).run(activeSession.id);
|
|
733
|
-
}
|
|
734
|
-
// Criar uma nova sessão ativa
|
|
735
|
-
const newId = randomUUID();
|
|
736
|
-
this.db.prepare(`
|
|
737
|
-
INSERT INTO sessions (
|
|
738
|
-
id,
|
|
739
|
-
started_at,
|
|
740
|
-
status
|
|
741
|
-
) VALUES (?, ?, 'active')
|
|
742
|
-
`).run(newId, now);
|
|
743
|
-
// Atualizar o ID da sessão atual desta instância
|
|
744
|
-
this.sessionId = newId;
|
|
745
|
-
this.titleSet = false; // reset cache for new session
|
|
746
|
-
});
|
|
747
|
-
tx(); // Executar a transação
|
|
748
|
-
this.display.log('✅ Nova sessão iniciada e sessão anterior pausada', { source: 'Sati' });
|
|
719
|
+
const newId = randomUUID();
|
|
720
|
+
this.db.prepare(`
|
|
721
|
+
INSERT INTO sessions (
|
|
722
|
+
id,
|
|
723
|
+
started_at,
|
|
724
|
+
status
|
|
725
|
+
) VALUES (?, ?, 'active')
|
|
726
|
+
`).run(newId, now);
|
|
727
|
+
// Update this instance to point to the new session
|
|
728
|
+
this.sessionId = newId;
|
|
729
|
+
this.titleSet = false;
|
|
730
|
+
this.display.log('✅ New session created', { source: 'Sati' });
|
|
731
|
+
return newId;
|
|
749
732
|
}
|
|
750
733
|
chunkText(text, chunkSize = 500, overlap = 50) {
|
|
751
734
|
if (!text || text.length === 0)
|
|
@@ -890,27 +873,6 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
890
873
|
`).run(now, sessionId);
|
|
891
874
|
});
|
|
892
875
|
tx(); // Executar a transação
|
|
893
|
-
// Se a sessão era active, verificar se há outra para ativar
|
|
894
|
-
if (session.status === 'active') {
|
|
895
|
-
const nextSession = this.db.prepare(`
|
|
896
|
-
SELECT id FROM sessions
|
|
897
|
-
WHERE status = 'paused'
|
|
898
|
-
ORDER BY started_at DESC
|
|
899
|
-
LIMIT 1
|
|
900
|
-
`).get();
|
|
901
|
-
if (nextSession) {
|
|
902
|
-
// Promover a próxima sessão a ativa
|
|
903
|
-
this.db.prepare(`
|
|
904
|
-
UPDATE sessions
|
|
905
|
-
SET status = 'active'
|
|
906
|
-
WHERE id = ?
|
|
907
|
-
`).run(nextSession.id);
|
|
908
|
-
}
|
|
909
|
-
else {
|
|
910
|
-
// Nenhuma outra sessão, criar nova
|
|
911
|
-
this.createFreshSession();
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
876
|
}
|
|
915
877
|
/**
|
|
916
878
|
* Renomear uma sessão ativa ou pausada.
|
|
@@ -941,101 +903,46 @@ export class SQLiteChatMessageHistory extends BaseListChatMessageHistory {
|
|
|
941
903
|
tx(); // Executar a transação
|
|
942
904
|
}
|
|
943
905
|
/**
|
|
944
|
-
*
|
|
945
|
-
* Validar sessão alvo: existe e status ∈ (paused, active).
|
|
946
|
-
* Se já for active, não faz nada.
|
|
947
|
-
* Transação: sessão atual active → paused, sessão alvo → active.
|
|
948
|
-
*/
|
|
949
|
-
/**
|
|
950
|
-
* Creates a session row with status 'paused' if it doesn't already exist.
|
|
906
|
+
* Creates a session row with status 'active' if it doesn't already exist.
|
|
951
907
|
* Safe to call multiple times — idempotent.
|
|
952
908
|
*/
|
|
953
909
|
ensureSession(sessionId) {
|
|
954
910
|
const existing = this.db.prepare('SELECT id FROM sessions WHERE id = ?').get(sessionId);
|
|
955
911
|
if (!existing) {
|
|
956
|
-
this.db.prepare("INSERT INTO sessions (id, started_at, status) VALUES (?, ?, '
|
|
912
|
+
this.db.prepare("INSERT INTO sessions (id, started_at, status) VALUES (?, ?, 'active')").run(sessionId, Date.now());
|
|
957
913
|
}
|
|
958
914
|
}
|
|
915
|
+
/**
|
|
916
|
+
* Validates that the target session exists and is usable (not archived/deleted).
|
|
917
|
+
* No longer swaps active↔paused — sessions are independently usable from any channel.
|
|
918
|
+
*/
|
|
959
919
|
async switchSession(targetSessionId) {
|
|
960
|
-
// Validar sessão alvo: existe e status ∈ (paused, active)
|
|
961
920
|
const targetSession = this.db.prepare(`
|
|
962
921
|
SELECT id, status FROM sessions
|
|
963
922
|
WHERE id = ?
|
|
964
923
|
`).get(targetSessionId);
|
|
965
924
|
if (!targetSession) {
|
|
966
|
-
throw new Error(`
|
|
925
|
+
throw new Error(`Session with ID ${targetSessionId} not found.`);
|
|
967
926
|
}
|
|
968
|
-
if (targetSession.status
|
|
969
|
-
throw new Error(`
|
|
927
|
+
if (targetSession.status === 'archived' || targetSession.status === 'deleted') {
|
|
928
|
+
throw new Error(`Session ${targetSessionId} is ${targetSession.status} and cannot be used.`);
|
|
970
929
|
}
|
|
971
|
-
// Se já for active, não faz nada
|
|
972
|
-
if (targetSession.status === 'active') {
|
|
973
|
-
return; // A sessão alvo já está ativa, não precisa fazer nada
|
|
974
|
-
}
|
|
975
|
-
// Transação: sessão atual active → paused, sessão alvo → active
|
|
976
|
-
const tx = this.db.transaction(() => {
|
|
977
|
-
// Pegar a sessão atualmente ativa
|
|
978
|
-
const currentActiveSession = this.db.prepare(`
|
|
979
|
-
SELECT id FROM sessions
|
|
980
|
-
WHERE status = 'active'
|
|
981
|
-
`).get();
|
|
982
|
-
// Se houver uma sessão ativa, mudar seu status para 'paused'
|
|
983
|
-
if (currentActiveSession) {
|
|
984
|
-
this.db.prepare(`
|
|
985
|
-
UPDATE sessions
|
|
986
|
-
SET status = 'paused'
|
|
987
|
-
WHERE id = ?
|
|
988
|
-
`).run(currentActiveSession.id);
|
|
989
|
-
}
|
|
990
|
-
// Mudar o status da sessão alvo para 'active'
|
|
991
|
-
this.db.prepare(`
|
|
992
|
-
UPDATE sessions
|
|
993
|
-
SET status = 'active'
|
|
994
|
-
WHERE id = ?
|
|
995
|
-
`).run(targetSessionId);
|
|
996
|
-
});
|
|
997
|
-
tx(); // Executar a transação
|
|
998
930
|
}
|
|
999
931
|
/**
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1002
|
-
* ou criar nova sessão (createFreshSession) e retornar o novo id.
|
|
932
|
+
* Returns the most recently created usable session, or creates one if none exist.
|
|
933
|
+
* A session is usable if its status is 'active' or 'paused' (both are equivalent post-refactor).
|
|
1003
934
|
*/
|
|
1004
935
|
async getCurrentSessionOrCreate() {
|
|
1005
|
-
|
|
1006
|
-
const activeSession = this.db.prepare(`
|
|
1007
|
-
SELECT id FROM sessions
|
|
1008
|
-
WHERE status = 'active'
|
|
1009
|
-
`).get();
|
|
1010
|
-
if (activeSession) {
|
|
1011
|
-
// Se existir, retornar seu id
|
|
1012
|
-
return activeSession.id;
|
|
1013
|
-
}
|
|
1014
|
-
else {
|
|
1015
|
-
// Se não existir, criar nova sessão (createFreshSession) e retornar o novo id
|
|
1016
|
-
const newId = await this.createFreshSession();
|
|
1017
|
-
return newId;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
async createFreshSession() {
|
|
1021
|
-
// Validar que não existe sessão 'active'
|
|
1022
|
-
const activeSession = this.db.prepare(`
|
|
936
|
+
const session = this.db.prepare(`
|
|
1023
937
|
SELECT id FROM sessions
|
|
1024
|
-
WHERE status
|
|
938
|
+
WHERE status IN ('active', 'paused')
|
|
939
|
+
ORDER BY started_at DESC
|
|
940
|
+
LIMIT 1
|
|
1025
941
|
`).get();
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
942
|
+
if (session) {
|
|
943
|
+
return session.id;
|
|
1028
944
|
}
|
|
1029
|
-
|
|
1030
|
-
const newId = randomUUID();
|
|
1031
|
-
this.db.prepare(`
|
|
1032
|
-
INSERT INTO sessions (
|
|
1033
|
-
id,
|
|
1034
|
-
started_at,
|
|
1035
|
-
status
|
|
1036
|
-
) VALUES (?, ?, 'active')
|
|
1037
|
-
`).run(newId, now);
|
|
1038
|
-
return newId;
|
|
945
|
+
return this.createNewSession();
|
|
1039
946
|
}
|
|
1040
947
|
/**
|
|
1041
948
|
* Lists all active and paused sessions with their basic information.
|
package/dist/runtime/neo.js
CHANGED
|
@@ -137,7 +137,7 @@ ${context ? `Context:\n${context}` : ""}
|
|
|
137
137
|
};
|
|
138
138
|
const inputCount = messages.length;
|
|
139
139
|
const startMs = Date.now();
|
|
140
|
-
const response = await TaskRequestContext.run(invokeContext, () => this.agent.invoke({ messages }, { recursionLimit:
|
|
140
|
+
const response = await TaskRequestContext.run(invokeContext, () => this.agent.invoke({ messages }, { recursionLimit: 50 }));
|
|
141
141
|
const durationMs = Date.now() - startMs;
|
|
142
142
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
143
143
|
const content = typeof lastMessage.content === "string"
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -17,6 +17,8 @@ import { MCPManager } from "../config/mcp-manager.js";
|
|
|
17
17
|
import { SkillRegistry, SkillExecuteTool, SkillDelegateTool, updateSkillToolDescriptions } from "./skills/index.js";
|
|
18
18
|
import { SmithRegistry } from "./smiths/registry.js";
|
|
19
19
|
import { AuditRepository } from "./audit/repository.js";
|
|
20
|
+
import { SetupRepository } from './setup/repository.js';
|
|
21
|
+
import { buildSetupTool } from './tools/setup-tool.js';
|
|
20
22
|
import { emitToolAuditEvents } from "./subagent-utils.js";
|
|
21
23
|
const ORACLE_DELEGATION_TOOLS = new Set([
|
|
22
24
|
'apoc_delegate', 'neo_delegate', 'trinity_delegate', 'smith_delegate',
|
|
@@ -154,7 +156,10 @@ export class Oracle {
|
|
|
154
156
|
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
155
157
|
updateSkillToolDescriptions();
|
|
156
158
|
// Build tool list — conditionally include SmithDelegateTool based on config
|
|
159
|
+
// Initialize setup repository (creates table if needed)
|
|
160
|
+
SetupRepository.getInstance();
|
|
157
161
|
const coreTools = [
|
|
162
|
+
buildSetupTool(),
|
|
158
163
|
TaskQueryTool,
|
|
159
164
|
Neo.getInstance().createDelegateTool(),
|
|
160
165
|
Apoc.getInstance().createDelegateTool(),
|
|
@@ -208,6 +213,8 @@ export class Oracle {
|
|
|
208
213
|
if (!this.history) {
|
|
209
214
|
throw new Error("Message history not initialized. Call initialize() first.");
|
|
210
215
|
}
|
|
216
|
+
// Per-call scoped history — declared outside try so finally can close it.
|
|
217
|
+
let callHistory;
|
|
211
218
|
try {
|
|
212
219
|
this.display.log('Processing message...', { source: 'Oracle' });
|
|
213
220
|
const userMessage = new HumanMessage(message);
|
|
@@ -220,8 +227,25 @@ export class Oracle {
|
|
|
220
227
|
if (extraUsage) {
|
|
221
228
|
userMessage.usage_metadata = extraUsage;
|
|
222
229
|
}
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
// Build first-time setup block if setup is not yet completed
|
|
231
|
+
const setupRepo = SetupRepository.getInstance();
|
|
232
|
+
let setupBlock = '';
|
|
233
|
+
if (!setupRepo.isCompleted()) {
|
|
234
|
+
const missingFields = setupRepo.getMissingFields();
|
|
235
|
+
if (missingFields.length > 0) {
|
|
236
|
+
setupBlock = `## [FIRST-TIME SETUP — ACTIVE]
|
|
237
|
+
Before responding to any other request, you MUST collect the user's basic information.
|
|
238
|
+
Ask for the following fields conversationally (one or two at a time — do NOT list them all at once):
|
|
239
|
+
${missingFields.map((f) => `- ${f}`).join('\n')}
|
|
240
|
+
|
|
241
|
+
Once the user provides a value, immediately call \`setup_save\` with the collected fields.
|
|
242
|
+
Do NOT proceed with other tasks until all required fields have been collected and saved.
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const systemMessage = new SystemMessage(`${setupBlock}You are ${this.config.agent.name}, ${this.config.agent.personality}, the Oracle.
|
|
225
249
|
|
|
226
250
|
You are an orchestrator and task router.
|
|
227
251
|
|
|
@@ -347,13 +371,20 @@ bad:
|
|
|
347
371
|
${SkillRegistry.getInstance().getSystemPromptSection()}
|
|
348
372
|
${SmithRegistry.getInstance().getSystemPromptSection()}
|
|
349
373
|
`);
|
|
374
|
+
// Resolve the authoritative session ID for this call.
|
|
375
|
+
// Priority: explicit taskContext > current history instance > fallback.
|
|
376
|
+
const currentSessionId = taskContext?.session_id
|
|
377
|
+
?? ((this.history instanceof SQLiteChatMessageHistory) ? this.history.currentSessionId : undefined);
|
|
378
|
+
// Create a per-call scoped history so concurrent chat() calls for
|
|
379
|
+
// different sessions never interfere with each other.
|
|
380
|
+
callHistory = new SQLiteChatMessageHistory({
|
|
381
|
+
sessionId: currentSessionId ?? 'default',
|
|
382
|
+
databasePath: this.databasePath,
|
|
383
|
+
limit: this.config.llm?.context_window ?? 100,
|
|
384
|
+
});
|
|
350
385
|
// Load existing history from database in reverse order (most recent first)
|
|
351
|
-
let previousMessages = await
|
|
386
|
+
let previousMessages = await callHistory.getMessages();
|
|
352
387
|
previousMessages = previousMessages.reverse();
|
|
353
|
-
// Propagate current session to Apoc so its token usage lands in the right session
|
|
354
|
-
const currentSessionId = (this.history instanceof SQLiteChatMessageHistory)
|
|
355
|
-
? this.history.currentSessionId
|
|
356
|
-
: undefined;
|
|
357
388
|
// Sati Middleware: Retrieval
|
|
358
389
|
let memoryMessage = null;
|
|
359
390
|
try {
|
|
@@ -395,7 +426,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
395
426
|
let syncDelegationCount = 0;
|
|
396
427
|
const oracleStartMs = Date.now();
|
|
397
428
|
const response = await TaskRequestContext.run(invokeContext, async () => {
|
|
398
|
-
const agentResponse = await this.provider.invoke({ messages }, { recursionLimit:
|
|
429
|
+
const agentResponse = await this.provider.invoke({ messages }, { recursionLimit: 50 });
|
|
399
430
|
contextDelegationAcks = TaskRequestContext.getDelegationAcks();
|
|
400
431
|
syncDelegationCount = TaskRequestContext.getSyncDelegationCount();
|
|
401
432
|
return agentResponse;
|
|
@@ -474,8 +505,8 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
474
505
|
ackMessage.usage_metadata = ackResult.usage_metadata;
|
|
475
506
|
}
|
|
476
507
|
// Persist with addMessage so ack-provider usage is tracked per message row.
|
|
477
|
-
await
|
|
478
|
-
await
|
|
508
|
+
await callHistory.addMessage(userMessage);
|
|
509
|
+
await callHistory.addMessage(ackMessage);
|
|
479
510
|
// Unblock tasks for execution: the ack message is now persisted and will be
|
|
480
511
|
// returned to the caller (Telegram / UI) immediately after this point.
|
|
481
512
|
this.taskRepository.markAckSent(validDelegationAcks.map(a => a.task_id));
|
|
@@ -489,7 +520,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
489
520
|
provider: this.config.llm.provider,
|
|
490
521
|
model: this.config.llm.model,
|
|
491
522
|
};
|
|
492
|
-
await
|
|
523
|
+
await callHistory.addMessages([userMessage, failureMessage]);
|
|
493
524
|
}
|
|
494
525
|
else {
|
|
495
526
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
@@ -510,11 +541,11 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
510
541
|
if (usage) {
|
|
511
542
|
failureMessage.usage_metadata = usage;
|
|
512
543
|
}
|
|
513
|
-
await
|
|
544
|
+
await callHistory.addMessages([userMessage, failureMessage]);
|
|
514
545
|
}
|
|
515
546
|
else {
|
|
516
547
|
// Persist user message + all generated messages in a single transaction
|
|
517
|
-
await
|
|
548
|
+
await callHistory.addMessages([userMessage, ...newGeneratedMessages]);
|
|
518
549
|
}
|
|
519
550
|
}
|
|
520
551
|
this.display.log('Response generated.', { source: 'Oracle' });
|
|
@@ -539,6 +570,9 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
539
570
|
catch (err) {
|
|
540
571
|
throw new ProviderError(this.config.llm.provider, err, "Chat request failed");
|
|
541
572
|
}
|
|
573
|
+
finally {
|
|
574
|
+
callHistory?.close();
|
|
575
|
+
}
|
|
542
576
|
}
|
|
543
577
|
async getHistory() {
|
|
544
578
|
if (!this.history) {
|
|
@@ -558,53 +592,19 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
558
592
|
throw new Error("Current history provider does not support session rollover.");
|
|
559
593
|
}
|
|
560
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* Updates the internal history pointer to the given session.
|
|
597
|
+
* No longer mutates DB session status — sessions are independently usable from any channel.
|
|
598
|
+
* Note: chat() uses per-call callHistory scoped to taskContext.session_id,
|
|
599
|
+
* so this method is only a fallback for callers that don't pass session in taskContext.
|
|
600
|
+
*/
|
|
561
601
|
async setSessionId(sessionId) {
|
|
562
602
|
if (!this.history) {
|
|
563
603
|
throw new Error("Message history not initialized. Call initialize() first.");
|
|
564
604
|
}
|
|
565
|
-
// Check if the history provider supports switching sessions
|
|
566
|
-
// SQLiteChatMessageHistory does support it via constructor (new instance) or maybe we can add a method there too?
|
|
567
|
-
// Actually SQLiteChatMessageHistory has `switchSession(targetSessionId)` but that one logic is "pause current, activate target".
|
|
568
|
-
// For API usage, we might just want to *target* a session without necessarily changing the global "active" state regarding the Daemon?
|
|
569
|
-
//
|
|
570
|
-
// However, the user request implies this is "the" chat.
|
|
571
|
-
// If we use `switchSession` it pauses others. That seems correct for a single-user agent model.
|
|
572
|
-
//
|
|
573
|
-
// But `SQLiteChatMessageHistory` properties are `sessionId`.
|
|
574
|
-
// It seems `switchSession` in `sqlite.ts` updates the DB state.
|
|
575
|
-
// We also need to update the `sessionId` property of the `SQLiteChatMessageHistory` instance held by Oracle.
|
|
576
|
-
//
|
|
577
|
-
// Let's check `SQLiteChatMessageHistory` again.
|
|
578
|
-
// It has `sessionId` property.
|
|
579
|
-
// It does NOT have a method to just update `sessionId` property without DB side effects?
|
|
580
|
-
//
|
|
581
|
-
// Use `switchSession` from `sqlite.ts` is good for "Active/Paused" state management.
|
|
582
|
-
// But we also need the `history` instance to know it is now pointing to `sessionId`.
|
|
583
605
|
if (this.history instanceof SQLiteChatMessageHistory) {
|
|
584
|
-
//
|
|
585
|
-
// 1. If currently active session is different, switch.
|
|
586
|
-
// 2. Update internal sessionId.
|
|
587
|
-
// Actually `switchSession` in `sqlite.ts` takes `targetSessionId`.
|
|
588
|
-
// It updates the DB status.
|
|
589
|
-
// It DOES NOT seem to update `this.sessionId` of the instance?
|
|
590
|
-
// Wait, let me check `sqlite.ts` content from memory or view it again alongside.
|
|
591
|
-
//
|
|
592
|
-
// In `sqlite.ts`:
|
|
593
|
-
// public async switchSession(targetSessionId: string): Promise<void> { ... }
|
|
594
|
-
// It updates DB.
|
|
595
|
-
// It DOES NOT update `this.sessionId`.
|
|
596
|
-
//
|
|
597
|
-
// So we need to ensure `this.history` points to the new session.
|
|
598
|
-
// Since `SQLiteChatMessageHistory` might not allow changing `sessionId` publicly if it's protected/private...
|
|
599
|
-
// It is `private sessionId: string;`.
|
|
600
|
-
//
|
|
601
|
-
// So simple fix: Re-instantiate `this.history`?
|
|
602
|
-
// `this.history = new SQLiteChatMessageHistory({ sessionId: sessionId, ... })`
|
|
603
|
-
//
|
|
604
|
-
// This is safe and clean.
|
|
605
|
-
// Ensure the target session exists before switching (creates as 'paused' if not found).
|
|
606
|
+
// Ensure the target session exists in DB
|
|
606
607
|
this.history.ensureSession(sessionId);
|
|
607
|
-
await this.history.switchSession(sessionId);
|
|
608
608
|
// Close previous connection before re-instantiating to avoid file handle leaks
|
|
609
609
|
this.history.close();
|
|
610
610
|
// Re-instantiate to point to new session
|
|
@@ -645,6 +645,7 @@ Use it to inform your response and tool selection (if needed), but do not assume
|
|
|
645
645
|
await Trinity.refreshDelegateCatalog().catch(() => { });
|
|
646
646
|
updateSkillToolDescriptions();
|
|
647
647
|
this.provider = await ProviderFactory.create(this.config.llm, [
|
|
648
|
+
buildSetupTool(),
|
|
648
649
|
TaskQueryTool,
|
|
649
650
|
Neo.getInstance().createDelegateTool(),
|
|
650
651
|
Apoc.getInstance().createDelegateTool(),
|