morpheus-cli 0.4.15 → 0.5.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.
Files changed (38) hide show
  1. package/README.md +275 -1116
  2. package/dist/channels/telegram.js +206 -74
  3. package/dist/cli/commands/doctor.js +34 -0
  4. package/dist/cli/commands/init.js +128 -0
  5. package/dist/cli/commands/restart.js +17 -0
  6. package/dist/cli/commands/start.js +15 -0
  7. package/dist/config/manager.js +51 -0
  8. package/dist/config/schemas.js +7 -0
  9. package/dist/devkit/tools/network.js +1 -1
  10. package/dist/http/api.js +177 -10
  11. package/dist/runtime/apoc.js +25 -17
  12. package/dist/runtime/memory/sati/repository.js +30 -2
  13. package/dist/runtime/memory/sati/service.js +46 -15
  14. package/dist/runtime/memory/sati/system-prompts.js +71 -29
  15. package/dist/runtime/memory/sqlite.js +24 -0
  16. package/dist/runtime/neo.js +134 -0
  17. package/dist/runtime/oracle.js +244 -205
  18. package/dist/runtime/providers/factory.js +1 -12
  19. package/dist/runtime/tasks/context.js +53 -0
  20. package/dist/runtime/tasks/dispatcher.js +70 -0
  21. package/dist/runtime/tasks/notifier.js +68 -0
  22. package/dist/runtime/tasks/repository.js +370 -0
  23. package/dist/runtime/tasks/types.js +1 -0
  24. package/dist/runtime/tasks/worker.js +96 -0
  25. package/dist/runtime/tools/apoc-tool.js +61 -8
  26. package/dist/runtime/tools/delegation-guard.js +29 -0
  27. package/dist/runtime/tools/index.js +1 -0
  28. package/dist/runtime/tools/neo-tool.js +99 -0
  29. package/dist/runtime/tools/task-query-tool.js +76 -0
  30. package/dist/runtime/webhooks/dispatcher.js +10 -19
  31. package/dist/types/config.js +10 -0
  32. package/dist/ui/assets/index-20lLB1sM.js +112 -0
  33. package/dist/ui/assets/index-BJ56bRfs.css +1 -0
  34. package/dist/ui/index.html +2 -2
  35. package/dist/ui/sw.js +1 -1
  36. package/package.json +1 -1
  37. package/dist/ui/assets/index-LemKVRjC.js +0 -112
  38. package/dist/ui/assets/index-TCQ7VNYO.css +0 -1
@@ -1,23 +1,116 @@
1
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
1
+ import { HumanMessage, SystemMessage, AIMessage, ToolMessage } from "@langchain/core/messages";
2
2
  import { ProviderFactory } from "./providers/factory.js";
3
- import { Construtor } from "./tools/factory.js";
4
3
  import { ConfigManager } from "../config/manager.js";
5
4
  import { ProviderError } from "./errors.js";
6
5
  import { DisplayManager } from "./display.js";
7
6
  import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
8
7
  import { SatiMemoryMiddleware } from "./memory/sati/index.js";
9
8
  import { Apoc } from "./apoc.js";
9
+ import { TaskRequestContext } from "./tasks/context.js";
10
+ import { TaskRepository } from "./tasks/repository.js";
11
+ import { Neo } from "./neo.js";
12
+ import { NeoDelegateTool } from "./tools/neo-tool.js";
13
+ import { ApocDelegateTool } from "./tools/apoc-tool.js";
14
+ import { TaskQueryTool } from "./tools/task-query-tool.js";
10
15
  export class Oracle {
11
16
  provider;
12
17
  config;
13
18
  history;
14
19
  display = DisplayManager.getInstance();
20
+ taskRepository = TaskRepository.getInstance();
15
21
  databasePath;
16
22
  satiMiddleware = SatiMemoryMiddleware.getInstance();
17
23
  constructor(config, overrides) {
18
24
  this.config = config || ConfigManager.getInstance().get();
19
25
  this.databasePath = overrides?.databasePath;
20
26
  }
27
+ buildDelegationFailureResponse() {
28
+ return "Task enqueue could not be confirmed in the database. No task was created. Please retry.";
29
+ }
30
+ looksLikeSyntheticDelegationAck(text) {
31
+ const raw = (text || "").trim();
32
+ if (!raw)
33
+ return false;
34
+ // Detect the structured ack format that Oracle itself generates.
35
+ // LLMs can learn to reproduce this format from conversation history without calling any tool.
36
+ const hasAckTaskLine = /Task\s+`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}/i.test(raw);
37
+ const hasAckAgentLine = /Agent:\s*`(APOC|NEO|apoc|neo)/i.test(raw);
38
+ const hasAckStatusLine = /Status:\s*`(QUEUED|PENDING|RUNNING|COMPLETED|FAILED)/i.test(raw);
39
+ if (hasAckTaskLine && hasAckAgentLine && hasAckStatusLine)
40
+ return true;
41
+ const hasCreationClaim = /(as\s+tarefas?\s+foram\s+criadas|tarefa\s+criada|nova\s+tarefa\s+criada|deleguei|delegado|delegada|tasks?\s+created|task\s+created|queued\s+for|agendei|agendado|agendada|foi\s+agendad)/i.test(raw);
42
+ if (!hasCreationClaim)
43
+ return false;
44
+ const hasAgentMention = /\b(apoc|neo|trinit)\b/i.test(raw);
45
+ const hasUuid = /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\b/.test(raw);
46
+ const hasAgentListLine = /(?:\*|-)?.{0,8}(apoc|neo|trinit)\s*[::]/i.test(raw);
47
+ return hasCreationClaim && (hasAgentMention || hasUuid || hasAgentListLine);
48
+ }
49
+ buildDelegationAck(acks) {
50
+ const truncate = (s, max = 72) => s.length > max ? s.slice(0, max).trimEnd() + '…' : s;
51
+ if (acks.length === 1) {
52
+ const { task_id, agent } = acks[0];
53
+ const task = this.taskRepository.getTaskById(task_id);
54
+ const taskLine = task?.input ? `\n${truncate(task.input)}` : '';
55
+ return `✅\ Task \`${task_id.toUpperCase()}\`\nAgent: \`${agent.toUpperCase()}\`\nStatus: \`QUEUED\`${taskLine}`;
56
+ }
57
+ const lines = acks.map((a) => {
58
+ const task = this.taskRepository.getTaskById(a.task_id);
59
+ const label = task?.input ? ` — ${truncate(task.input, 50)}` : '';
60
+ return `• ${a.agent.toUpperCase()}: \`${a.task_id}\`${label}`;
61
+ }).join('\n');
62
+ return `Tasks:\n${lines}\n\nRunning...`;
63
+ }
64
+ buildDelegationAckResult(acks) {
65
+ return { content: this.buildDelegationAck(acks) };
66
+ }
67
+ extractDelegationAcksFromMessages(messages) {
68
+ const acks = [];
69
+ const regex = /Task\s+([0-9a-fA-F-]{36})\s+(?:queued|already queued)\s+for\s+(Apoc|Neo|apoc|neo)\s+execution/i;
70
+ for (const msg of messages) {
71
+ if (!(msg instanceof ToolMessage))
72
+ continue;
73
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
74
+ const match = regex.exec(content);
75
+ if (!match)
76
+ continue;
77
+ acks.push({ task_id: match[1], agent: match[2].toLowerCase() });
78
+ }
79
+ return acks;
80
+ }
81
+ validateDelegationAcks(acks, requestMessage) {
82
+ const deduped = new Map();
83
+ for (const ack of acks) {
84
+ deduped.set(`${ack.agent}:${ack.task_id}`, { task_id: ack.task_id, agent: ack.agent });
85
+ }
86
+ const valid = [];
87
+ for (const ack of deduped.values()) {
88
+ const task = this.taskRepository.getTaskById(ack.task_id);
89
+ if (!task) {
90
+ this.display.log(`Discarded delegation ack with unknown task id: ${ack.task_id}`, { source: "Oracle", level: "warning", meta: { requestMessage, agent: ack.agent } });
91
+ continue;
92
+ }
93
+ if (task.agent !== ack.agent) {
94
+ this.display.log(`Discarded delegation ack with agent mismatch for task ${ack.task_id}: ack=${ack.agent}, db=${task.agent}`, { source: "Oracle", level: "warning", meta: { requestMessage } });
95
+ continue;
96
+ }
97
+ valid.push(ack);
98
+ }
99
+ return valid;
100
+ }
101
+ hasDelegationToolCall(messages) {
102
+ for (const msg of messages) {
103
+ if (!(msg instanceof AIMessage))
104
+ continue;
105
+ const toolCalls = msg.tool_calls ?? [];
106
+ if (!Array.isArray(toolCalls))
107
+ continue;
108
+ if (toolCalls.some((tc) => tc?.name === "apoc_delegate" || tc?.name === "neo_delegate")) {
109
+ return true;
110
+ }
111
+ }
112
+ return false;
113
+ }
21
114
  async initialize() {
22
115
  if (!this.config.llm) {
23
116
  throw new Error("LLM configuration missing in config object.");
@@ -29,8 +122,10 @@ export class Oracle {
29
122
  // Note: API Key validation is delegated to ProviderFactory or the Provider itself
30
123
  // to allow for Environment Variable fallback supported by LangChain.
31
124
  try {
32
- const tools = await Construtor.create();
33
- this.provider = await ProviderFactory.create(this.config.llm, tools);
125
+ // Refresh Neo tool catalog so neo_delegate description contains runtime tools list.
126
+ // Fail-open: Oracle can still initialize even if catalog refresh fails.
127
+ await Neo.refreshDelegateCatalog().catch(() => { });
128
+ this.provider = await ProviderFactory.create(this.config.llm, [TaskQueryTool, NeoDelegateTool, ApocDelegateTool]);
34
129
  if (!this.provider) {
35
130
  throw new Error("Provider factory returned undefined");
36
131
  }
@@ -50,7 +145,7 @@ export class Oracle {
50
145
  throw new ProviderError(this.config.llm.provider || 'unknown', err, "Oracle initialization failed");
51
146
  }
52
147
  }
53
- async chat(message, extraUsage, isTelephonist) {
148
+ async chat(message, extraUsage, isTelephonist, taskContext) {
54
149
  if (!this.provider) {
55
150
  throw new Error("Oracle not initialized. Call initialize() first.");
56
151
  }
@@ -70,194 +165,54 @@ export class Oracle {
70
165
  userMessage.usage_metadata = extraUsage;
71
166
  }
72
167
  const systemMessage = new SystemMessage(`
73
- You are ${this.config.agent.name}, ${this.config.agent.personality}, the Oracle.
74
-
75
- Your role is to orchestrate tools, MCPs, and language models to accurately fulfill the Architect’s request.
76
-
77
- You are an operator, not a guesser.
78
- Accuracy, verification, and task completion are more important than speed.
79
-
80
- --------------------------------------------------
81
- CORE OPERATING PRINCIPLES
82
- --------------------------------------------------
83
-
84
- 1. TOOL EVALUATION FIRST
85
-
86
- Before generating any final answer, evaluate whether an available tool or MCP can provide a more accurate, up-to-date, or authoritative result.
87
-
88
- If a tool can provide the answer, you MUST call the tool.
89
-
90
- Never generate speculative values when a tool can verify them.
91
-
92
-
93
- 2. ACTIVE INTENT TRACKING (CRITICAL)
94
-
95
- You must always maintain the current active user intent until it is fully resolved.
96
-
97
- If you ask a clarification question, the original intent remains ACTIVE.
98
-
99
- When the user responds to a clarification, you MUST:
100
-
101
- - Combine the new information with the original request
102
- - Resume the same task
103
- - Continue the tool evaluation process
104
- - Complete the original objective
105
-
106
- You MUST NOT:
107
- - Treat clarification answers as new unrelated requests
108
- - Drop the original task
109
- - Change subject unexpectedly
110
-
111
- Clarifications are part of the same execution chain.
112
-
113
-
114
- 3. NO HISTORICAL ASSUMPTIONS FOR DYNAMIC DATA
115
-
116
- If the user asks something that:
117
-
118
- - may change over time
119
- - depends on system state
120
- - depends on filesystem
121
- - depends on external APIs
122
- - was previously asked in the conversation
123
-
124
- You MUST NOT reuse previous outputs as final truth.
125
-
126
- You MUST:
127
- - Re-evaluate available tools
128
- - Re-execute relevant tools
129
- - Provide a fresh result
130
-
131
- Repeated queries require fresh verification.
132
-
133
-
134
- 4. HISTORY IS CONTEXT, NOT SOURCE OF TRUTH
135
-
136
- Conversation history provides context, not verified data.
137
-
138
- Never assume:
139
- - System state
140
- - File contents
141
- - Database values
142
- - API responses
143
-
144
- based only on previous messages.
145
-
146
-
147
- 5. TASK RESOLUTION LOOP
148
-
149
- You must operate in this loop:
150
-
151
- - Identify intent
152
- - Determine missing information (if any)
153
- - Ask clarification ONLY if necessary
154
- - When clarification is received, resume original task
155
- - Evaluate tools
156
- - Execute tools if applicable
157
- - Deliver verified answer
158
-
159
- Do not break this loop.
160
-
161
-
162
- 6. TOOL PRIORITY OVER LANGUAGE GUESSING
163
-
164
- If a tool can compute, fetch, inspect, or verify something, prefer tool usage.
165
-
166
- Never hallucinate values retrievable via tools.
167
-
168
- --------------------------------------------------
169
- DELEGATION DECISION PROTOCOL
170
- --------------------------------------------------
171
-
172
- Before responding, classify the request into one of the following categories:
173
-
174
- CATEGORY A — Execution / System / External State
175
- If the request involves:
176
- - Filesystem access
177
- - Code execution
178
- - Git operations
179
- - Package management
180
- - Process inspection
181
- - Networking
182
- - Environment state
183
- - Browser automation
184
- - Web navigation
185
- - Web research
186
- - Fact verification
187
- - Current or time-sensitive data
188
-
189
- You MUST delegate to the appropriate tool (e.g., apoc_delegate).
190
-
191
- CATEGORY B — Factual Question Requiring Verification
192
- If the question involves:
193
- - Rankings
194
- - Results
195
- - Versions
196
- - News
197
- - Public figures
198
- - Statistics
199
- - Events
200
- - Anything that may have changed over time
201
-
202
- You MUST delegate to Apoc for verification.
203
-
204
- CATEGORY C — Pure Reasoning / Conceptual
205
- If the request can be fully answered through reasoning alone without external validation,
206
- you may answer directly without delegating.
207
-
208
- If uncertainty exists about whether verification is required,
209
- DELEGATE.
210
-
211
- --------------------------------------------------
212
- APOC DELEGATION STANDARD
213
- --------------------------------------------------
214
-
215
- When delegating to Apoc:
216
-
217
- - Provide a clear objective.
218
- - Specify verification requirements if factual.
219
- - Define expected output structure if needed.
220
- - Pass relevant context from the conversation.
221
- - Never send vague tasks.
222
-
223
- Weak delegation example (forbidden):
224
- "Search who won the championship."
225
-
226
- Correct delegation example:
227
- "Find who won the 2023 NBA Championship. Verify using at least 3 reliable sources. Provide URLs and confidence level."
228
-
229
- --------------------------------------------------
230
- UNCERTAINTY RULE
231
- --------------------------------------------------
232
-
233
- If the answer cannot be produced with high confidence without external verification,
234
- you MUST delegate.
235
-
236
- Never fabricate certainty.
237
- Never simulate tool results.
238
- When in doubt, delegate.
239
-
240
-
241
- 7. FINAL ANSWER POLICY
242
-
243
- Provide a natural language answer only if:
244
-
245
- - No tool is relevant
246
- - Tools are unavailable
247
- - The request is purely conceptual
248
-
249
- Otherwise, use tools first.
250
-
251
- --------------------------------------------------
252
-
253
- You are a deterministic orchestration layer.
254
- You do not drift.
255
- You do not abandon tasks.
256
- You do not speculate when verification is possible.
257
-
258
- You maintain intent until resolution.
259
-
260
- `);
168
+ You are ${this.config.agent.name}, ${this.config.agent.personality}, the Oracle.
169
+
170
+ You are an orchestrator and task router.
171
+
172
+
173
+ Rules:
174
+ 1. For conversation-only requests (greetings, conceptual explanation, memory follow-up, statements of fact, sharing personal information), answer directly. DO NOT create tasks or delegate for simple statements like "I have two cats" or "My name is John". Sati will automatically memorize facts in the background ( **ALWAYS** use SATI Memories to review or retrieve these facts if needed).
175
+ **NEVER** Create data, use SATI memories to response on informal conversation or say that dont know abaout the awsor if the answer is in the memories. Always use the memories as source of truth for user facts, preferences, stable context and informal conversation. Use tools only for execution, verification or when external/system state is required.*
176
+ 2. For requests that require execution, verification, external/system state, or non-trivial operations, evaluate the available tools and choose the best one.
177
+ 3. For task status/check questions (for example: "consultou?", "status da task", "andamento"), use task_query directly and do not delegate.
178
+ 4. Prefer delegation tools when execution should be asynchronous, and return the task acknowledgement clearly.
179
+ 5. If the user asked for multiple independent actions in the same message, enqueue one delegated task per action. Each task must be atomic (single objective).
180
+ 6. If the user asked for a single action, do not create additional delegated tasks.
181
+ 7. Never fabricate execution results for delegated tasks.
182
+ 8. Keep responses concise and objective.
183
+ 9. Avoid duplicate delegations to the same tool or agent.
184
+ 10. After enqueuing all required delegated tasks for the current message, stop calling tools and return a concise acknowledgement.
185
+ 11. If a delegation is rejected as "not atomic", immediately split into smaller delegations and retry.
186
+
187
+ Delegation quality:
188
+ - Write delegation input in the same language requested by the user.
189
+ - Include clear objective and constraints.
190
+ - Include OS-aware guidance for network checks when relevant.
191
+ - Use Sati memories only as context to complement the task, never as source of truth for dynamic data.
192
+ - Use Sati memories to fill missing stable context fields (for example: city, timezone, language, currency, preferred units).
193
+ - If Sati memory is conflicting or uncertain for a required field, ask one short clarification before delegating.
194
+ - When completing missing fields from Sati, include explicit assumptions in delegation context using the format: "Assumption from Sati: key=value".
195
+ - Never infer sensitive data from Sati memories (credentials, legal identifiers, health details, financial account data).
196
+ - When assumptions were used, mention them briefly in the user-facing response and allow correction.
197
+ - break the request into multiple delegations if it contains multiple independent actions.
198
+ - Set a single task per delegation tool call. Do not combine multiple actions into one delegation, as it complicates execution and error handling.
199
+ - If user requested N independent actions, produce N delegated tasks (or direct answers), each one singular and tool-scoped.
200
+ - If use a delegation dont use the sati or messages history to answer directly in the same response. Just response with the delegations.
201
+ Example 1:
202
+ ask: "Tell me my account balance and do a ping on google.com"
203
+ good:
204
+ - delegate to "neo_delegate" with task "Check account balance using morpheus analytics MCP and return the result."
205
+ - delegate to "apoc_delegate" with task "Ping google.com using the network diagnostics MCP and return reachability status. Use '-n' flag for Windows and '-c' for Linux/macOS."
206
+ bad:
207
+ - delegate to "neo_delegate" with task "Check account balance using morpheus analytics MCP and ping google.com using the network diagnostics MCP, then return both results." (combines two independent actions into one delegation, which is not atomic and complicates execution and error handling)
208
+
209
+ Example 2:
210
+ ask: "I have two cats" or "My name is John"
211
+ good:
212
+ - Answer directly acknowledging the fact. Do NOT delegate.
213
+ bad:
214
+ - delegate to "neo_delegate" or "apoc_delegate" to save the fact. (Sati handles this automatically in the background)
215
+ `);
261
216
  // Load existing history from database in reverse order (most recent first)
262
217
  let previousMessages = await this.history.getMessages();
263
218
  previousMessages = previousMessages.reverse();
@@ -277,7 +232,15 @@ You maintain intent until resolution.
277
232
  systemMessage
278
233
  ];
279
234
  if (memoryMessage) {
280
- messages.push(memoryMessage);
235
+ // messages.push(memoryMessage);
236
+ systemMessage.content += `
237
+
238
+ ## Retrieved SATI Memory:
239
+ ${memoryMessage.content}
240
+
241
+ This memory may be relevant to the user's request.
242
+ Use this to complemento the informal conversatrion.
243
+ Use it to inform your response and tool selection (if needed), but do not assume it is 100% accurate or complete. Always validate against current inputs and tools.`;
281
244
  }
282
245
  messages.push(...previousMessages);
283
246
  messages.push(userMessage);
@@ -286,7 +249,19 @@ You maintain intent until resolution.
286
249
  ? this.history.currentSessionId
287
250
  : undefined;
288
251
  Apoc.setSessionId(currentSessionId);
289
- const response = await this.provider.invoke({ messages });
252
+ Neo.setSessionId(currentSessionId);
253
+ const invokeContext = {
254
+ origin_channel: taskContext?.origin_channel ?? "api",
255
+ session_id: taskContext?.session_id ?? currentSessionId ?? "default",
256
+ origin_message_id: taskContext?.origin_message_id,
257
+ origin_user_id: taskContext?.origin_user_id,
258
+ };
259
+ let contextDelegationAcks = [];
260
+ const response = await TaskRequestContext.run(invokeContext, async () => {
261
+ const agentResponse = await this.provider.invoke({ messages });
262
+ contextDelegationAcks = TaskRequestContext.getDelegationAcks();
263
+ return agentResponse;
264
+ });
290
265
  // Identify new messages generated during the interaction
291
266
  // The `messages` array passed to invoke had length `messages.length`
292
267
  // The `response.messages` contains the full state.
@@ -301,14 +276,77 @@ You maintain intent until resolution.
301
276
  model: this.config.llm.model
302
277
  };
303
278
  }
304
- // Persist user message + all generated messages in a single transaction
305
- await this.history.addMessages([userMessage, ...newGeneratedMessages]);
279
+ let responseContent;
280
+ const toolDelegationAcks = this.extractDelegationAcksFromMessages(newGeneratedMessages);
281
+ const hadDelegationToolCall = this.hasDelegationToolCall(newGeneratedMessages);
282
+ const mergedDelegationAcks = [
283
+ ...contextDelegationAcks.map((ack) => ({ task_id: ack.task_id, agent: ack.agent })),
284
+ ...toolDelegationAcks,
285
+ ];
286
+ const validDelegationAcks = this.validateDelegationAcks(mergedDelegationAcks, message);
287
+ if (mergedDelegationAcks.length > 0) {
288
+ this.display.log(`Delegation trace: context=${contextDelegationAcks.length}, tool_messages=${toolDelegationAcks.length}, valid=${validDelegationAcks.length}`, { source: "Oracle", level: "info" });
289
+ }
290
+ const delegatedThisTurn = validDelegationAcks.length > 0;
291
+ let blockedSyntheticDelegationAck = false;
292
+ if (delegatedThisTurn) {
293
+ const ackResult = this.buildDelegationAckResult(validDelegationAcks);
294
+ responseContent = ackResult.content;
295
+ const ackMessage = new AIMessage(responseContent);
296
+ ackMessage.provider_metadata = {
297
+ provider: this.config.llm.provider,
298
+ model: this.config.llm.model,
299
+ };
300
+ if (ackResult.usage_metadata) {
301
+ ackMessage.usage_metadata = ackResult.usage_metadata;
302
+ }
303
+ // Persist with addMessage so ack-provider usage is tracked per message row.
304
+ await this.history.addMessage(userMessage);
305
+ await this.history.addMessage(ackMessage);
306
+ }
307
+ else if (mergedDelegationAcks.length > 0 || hadDelegationToolCall) {
308
+ this.display.log(`Delegation attempted but no valid task id was confirmed (context=${contextDelegationAcks.length}, tool_messages=${toolDelegationAcks.length}, had_tool_call=${hadDelegationToolCall}).`, { source: "Oracle", level: "error" });
309
+ // Delegation was attempted but no valid task id could be confirmed in DB.
310
+ responseContent = this.buildDelegationFailureResponse();
311
+ const failureMessage = new AIMessage(responseContent);
312
+ failureMessage.provider_metadata = {
313
+ provider: this.config.llm.provider,
314
+ model: this.config.llm.model,
315
+ };
316
+ await this.history.addMessages([userMessage, failureMessage]);
317
+ }
318
+ else {
319
+ const lastMessage = response.messages[response.messages.length - 1];
320
+ responseContent = (typeof lastMessage.content === 'string') ? lastMessage.content : JSON.stringify(lastMessage.content);
321
+ if (this.looksLikeSyntheticDelegationAck(responseContent)) {
322
+ blockedSyntheticDelegationAck = true;
323
+ this.display.log("Blocked synthetic delegation acknowledgement without validated task creation.", { source: "Oracle", level: "error", meta: { preview: responseContent.slice(0, 200) } });
324
+ const usage = lastMessage.usage_metadata
325
+ ?? lastMessage.response_metadata?.usage
326
+ ?? lastMessage.response_metadata?.tokenUsage
327
+ ?? lastMessage.usage;
328
+ responseContent = this.buildDelegationFailureResponse();
329
+ const failureMessage = new AIMessage(responseContent);
330
+ failureMessage.provider_metadata = {
331
+ provider: this.config.llm.provider,
332
+ model: this.config.llm.model,
333
+ };
334
+ if (usage) {
335
+ failureMessage.usage_metadata = usage;
336
+ }
337
+ await this.history.addMessages([userMessage, failureMessage]);
338
+ }
339
+ else {
340
+ // Persist user message + all generated messages in a single transaction
341
+ await this.history.addMessages([userMessage, ...newGeneratedMessages]);
342
+ }
343
+ }
306
344
  this.display.log('Response generated.', { source: 'Oracle' });
307
- const lastMessage = response.messages[response.messages.length - 1];
308
- const responseContent = (typeof lastMessage.content === 'string') ? lastMessage.content : JSON.stringify(lastMessage.content);
309
- // Sati Middleware: Evaluation (Fire and forget)
310
- this.satiMiddleware.afterAgent(responseContent, [...previousMessages, userMessage], currentSessionId)
311
- .catch((e) => this.display.log(`Sati memory evaluation failed: ${e.message}`, { source: 'Sati' }));
345
+ // Sati Middleware: skip memory evaluation for delegation-only acknowledgements.
346
+ if (!delegatedThisTurn && !blockedSyntheticDelegationAck) {
347
+ this.satiMiddleware.afterAgent(responseContent, [...previousMessages, userMessage], currentSessionId)
348
+ .catch((e) => this.display.log(`Sati memory evaluation failed: ${e.message}`, { source: 'Sati' }));
349
+ }
312
350
  return responseContent;
313
351
  }
314
352
  catch (err) {
@@ -401,8 +439,9 @@ You maintain intent until resolution.
401
439
  if (!this.provider) {
402
440
  throw new Error("Oracle not initialized. Call initialize() first.");
403
441
  }
404
- const tools = await Construtor.create();
405
- this.provider = await ProviderFactory.create(this.config.llm, tools);
406
- this.display.log(`MCP tools reloaded (${tools.length} tools)`, { source: 'Oracle' });
442
+ await Neo.refreshDelegateCatalog().catch(() => { });
443
+ this.provider = await ProviderFactory.create(this.config.llm, [TaskQueryTool, NeoDelegateTool, ApocDelegateTool]);
444
+ await Neo.getInstance().reload();
445
+ this.display.log(`Oracle and Neo tools reloaded`, { source: 'Oracle' });
407
446
  }
408
447
  }
@@ -5,7 +5,6 @@ import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
5
5
  import { ProviderError } from "../errors.js";
6
6
  import { createAgent, createMiddleware } from "langchain";
7
7
  import { DisplayManager } from "../display.js";
8
- import { ConfigQueryTool, ConfigUpdateTool, DiagnosticTool, MessageCountTool, TokenUsageTool, ProviderModelUsageTool, ApocDelegateTool } from "../tools/index.js";
9
8
  export class ProviderFactory {
10
9
  static buildMonitoringMiddleware() {
11
10
  const display = DisplayManager.getInstance();
@@ -103,17 +102,7 @@ export class ProviderFactory {
103
102
  try {
104
103
  const model = ProviderFactory.buildModel(config);
105
104
  const middleware = ProviderFactory.buildMonitoringMiddleware();
106
- const toolsForAgent = [
107
- ...tools,
108
- ConfigQueryTool,
109
- ConfigUpdateTool,
110
- DiagnosticTool,
111
- MessageCountTool,
112
- TokenUsageTool,
113
- ProviderModelUsageTool,
114
- ApocDelegateTool
115
- ];
116
- return createAgent({ model, tools: toolsForAgent, middleware: [middleware] });
105
+ return createAgent({ model, tools, middleware: [middleware] });
117
106
  }
118
107
  catch (error) {
119
108
  ProviderFactory.handleProviderError(config, error);
@@ -0,0 +1,53 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const storage = new AsyncLocalStorage();
3
+ export class TaskRequestContext {
4
+ static MAX_DELEGATIONS_PER_TURN = 6;
5
+ static run(ctx, fn) {
6
+ return storage.run({ ...ctx }, fn);
7
+ }
8
+ static get() {
9
+ return storage.getStore();
10
+ }
11
+ static getDelegationAck() {
12
+ const acks = storage.getStore()?.delegation_acks ?? [];
13
+ return acks[0];
14
+ }
15
+ static setDelegationAck(ack) {
16
+ const current = storage.getStore();
17
+ if (!current)
18
+ return;
19
+ if (!current.delegation_acks) {
20
+ current.delegation_acks = [];
21
+ }
22
+ current.delegation_acks.push(ack);
23
+ }
24
+ static getDelegationAcks() {
25
+ return storage.getStore()?.delegation_acks ?? [];
26
+ }
27
+ static canEnqueueDelegation() {
28
+ return this.getDelegationAcks().length < this.MAX_DELEGATIONS_PER_TURN;
29
+ }
30
+ static findDuplicateDelegation(agent, task) {
31
+ const acks = this.getDelegationAcks();
32
+ if (acks.length === 0)
33
+ return undefined;
34
+ const normalized = this.normalizeTask(task);
35
+ for (const ack of acks) {
36
+ if (ack.agent !== agent) {
37
+ continue;
38
+ }
39
+ const existing = this.normalizeTask(ack.task);
40
+ if (existing === normalized) {
41
+ return ack;
42
+ }
43
+ }
44
+ return undefined;
45
+ }
46
+ static normalizeTask(task) {
47
+ return task
48
+ .toLowerCase()
49
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
50
+ .replace(/\s+/g, " ")
51
+ .trim();
52
+ }
53
+ }