sensorium-mcp 2.16.28 → 2.16.30
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/dist/config.d.ts +1 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -49
- package/dist/config.js.map +1 -1
- package/dist/dashboard/presets.d.ts +18 -0
- package/dist/dashboard/presets.d.ts.map +1 -0
- package/dist/dashboard/presets.js +78 -0
- package/dist/dashboard/presets.js.map +1 -0
- package/dist/dashboard/routes.d.ts +33 -0
- package/dist/dashboard/routes.d.ts.map +1 -0
- package/dist/dashboard/routes.js +283 -0
- package/dist/dashboard/routes.js.map +1 -0
- package/dist/dashboard.d.ts +6 -29
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/dashboard.js +6 -1158
- package/dist/dashboard.js.map +1 -1
- package/dist/data/file-storage.d.ts +19 -0
- package/dist/data/file-storage.d.ts.map +1 -0
- package/dist/data/file-storage.js +58 -0
- package/dist/data/file-storage.js.map +1 -0
- package/dist/data/memory/bootstrap.d.ts +40 -0
- package/dist/data/memory/bootstrap.d.ts.map +1 -0
- package/dist/data/memory/bootstrap.js +240 -0
- package/dist/data/memory/bootstrap.js.map +1 -0
- package/dist/data/memory/consolidation.d.ts +12 -0
- package/dist/data/memory/consolidation.d.ts.map +1 -0
- package/dist/data/memory/consolidation.js +248 -0
- package/dist/data/memory/consolidation.js.map +1 -0
- package/dist/data/memory/episodes.d.ts +34 -0
- package/dist/data/memory/episodes.d.ts.map +1 -0
- package/dist/data/memory/episodes.js +89 -0
- package/dist/data/memory/episodes.js.map +1 -0
- package/dist/data/memory/index.d.ts +14 -0
- package/dist/data/memory/index.d.ts.map +1 -0
- package/dist/data/memory/index.js +14 -0
- package/dist/data/memory/index.js.map +1 -0
- package/dist/data/memory/procedures.d.ts +42 -0
- package/dist/data/memory/procedures.d.ts.map +1 -0
- package/dist/data/memory/procedures.js +122 -0
- package/dist/data/memory/procedures.js.map +1 -0
- package/dist/data/memory/schema.d.ts +11 -0
- package/dist/data/memory/schema.d.ts.map +1 -0
- package/dist/data/memory/schema.js +327 -0
- package/dist/data/memory/schema.js.map +1 -0
- package/dist/data/memory/semantic.d.ts +94 -0
- package/dist/data/memory/semantic.d.ts.map +1 -0
- package/dist/data/memory/semantic.js +385 -0
- package/dist/data/memory/semantic.js.map +1 -0
- package/dist/data/memory/voice-sig.d.ts +33 -0
- package/dist/data/memory/voice-sig.d.ts.map +1 -0
- package/dist/data/memory/voice-sig.js +48 -0
- package/dist/data/memory/voice-sig.js.map +1 -0
- package/dist/data/templates.d.ts +19 -0
- package/dist/data/templates.d.ts.map +1 -0
- package/dist/data/templates.js +46 -0
- package/dist/data/templates.js.map +1 -0
- package/dist/dispatcher.d.ts +5 -97
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/dispatcher.js +5 -525
- package/dist/dispatcher.js.map +1 -1
- package/dist/drive.d.ts.map +1 -1
- package/dist/drive.js +3 -1
- package/dist/drive.js.map +1 -1
- package/dist/index.d.ts +4 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -289
- package/dist/index.js.map +1 -1
- package/dist/integrations/openai/chat.d.ts +29 -0
- package/dist/integrations/openai/chat.d.ts.map +1 -0
- package/dist/integrations/openai/chat.js +84 -0
- package/dist/integrations/openai/chat.js.map +1 -0
- package/dist/integrations/openai/index.d.ts +6 -0
- package/dist/integrations/openai/index.d.ts.map +1 -0
- package/dist/integrations/openai/index.js +6 -0
- package/dist/integrations/openai/index.js.map +1 -0
- package/dist/integrations/openai/speech.d.ts +21 -0
- package/dist/integrations/openai/speech.d.ts.map +1 -0
- package/dist/integrations/openai/speech.js +75 -0
- package/dist/integrations/openai/speech.js.map +1 -0
- package/dist/integrations/openai/video.d.ts +15 -0
- package/dist/integrations/openai/video.d.ts.map +1 -0
- package/dist/integrations/openai/video.js +131 -0
- package/dist/integrations/openai/video.js.map +1 -0
- package/dist/integrations/openai/vision.d.ts +23 -0
- package/dist/integrations/openai/vision.d.ts.map +1 -0
- package/dist/integrations/openai/vision.js +116 -0
- package/dist/integrations/openai/vision.js.map +1 -0
- package/dist/integrations/openai/voice-emotion.d.ts +41 -0
- package/dist/integrations/openai/voice-emotion.d.ts.map +1 -0
- package/dist/integrations/openai/voice-emotion.js +50 -0
- package/dist/integrations/openai/voice-emotion.js.map +1 -0
- package/dist/integrations/telegram/types.d.ts +112 -0
- package/dist/integrations/telegram/types.d.ts.map +1 -0
- package/dist/integrations/telegram/types.js +6 -0
- package/dist/integrations/telegram/types.js.map +1 -0
- package/dist/memory.d.ts +6 -205
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +6 -1357
- package/dist/memory.js.map +1 -1
- package/dist/openai.d.ts +11 -102
- package/dist/openai.d.ts.map +1 -1
- package/dist/openai.js +14 -421
- package/dist/openai.js.map +1 -1
- package/dist/response-builders.d.ts +1 -11
- package/dist/response-builders.d.ts.map +1 -1
- package/dist/response-builders.js +2 -38
- package/dist/response-builders.js.map +1 -1
- package/dist/server/factory.d.ts +17 -0
- package/dist/server/factory.d.ts.map +1 -0
- package/dist/server/factory.js +279 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/services/dispatcher/broker.d.ts +83 -0
- package/dist/services/dispatcher/broker.d.ts.map +1 -0
- package/dist/services/dispatcher/broker.js +175 -0
- package/dist/services/dispatcher/broker.js.map +1 -0
- package/dist/services/dispatcher/index.d.ts +7 -0
- package/dist/services/dispatcher/index.d.ts.map +1 -0
- package/dist/services/dispatcher/index.js +7 -0
- package/dist/services/dispatcher/index.js.map +1 -0
- package/dist/services/dispatcher/lock.d.ts +25 -0
- package/dist/services/dispatcher/lock.d.ts.map +1 -0
- package/dist/services/dispatcher/lock.js +111 -0
- package/dist/services/dispatcher/lock.js.map +1 -0
- package/dist/services/dispatcher/poller.d.ts +19 -0
- package/dist/services/dispatcher/poller.d.ts.map +1 -0
- package/dist/services/dispatcher/poller.js +269 -0
- package/dist/services/dispatcher/poller.js.map +1 -0
- package/dist/telegram.d.ts +2 -88
- package/dist/telegram.d.ts.map +1 -1
- package/dist/telegram.js +2 -0
- package/dist/telegram.js.map +1 -1
- package/dist/tool-definitions.d.ts +1 -14
- package/dist/tool-definitions.d.ts.map +1 -1
- package/dist/tool-definitions.js +1 -403
- package/dist/tool-definitions.js.map +1 -1
- package/dist/tools/definitions.d.ts +15 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +404 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/start-session-tool.d.ts.map +1 -1
- package/dist/tools/start-session-tool.js +2 -0
- package/dist/tools/start-session-tool.js.map +1 -1
- package/dist/tools/wait/drive-handler.d.ts +61 -0
- package/dist/tools/wait/drive-handler.d.ts.map +1 -0
- package/dist/tools/wait/drive-handler.js +138 -0
- package/dist/tools/wait/drive-handler.js.map +1 -0
- package/dist/tools/wait/index.d.ts +8 -0
- package/dist/tools/wait/index.d.ts.map +1 -0
- package/dist/tools/wait/index.js +8 -0
- package/dist/tools/wait/index.js.map +1 -0
- package/dist/tools/wait/media-processor.d.ts +52 -0
- package/dist/tools/wait/media-processor.d.ts.map +1 -0
- package/dist/tools/wait/media-processor.js +261 -0
- package/dist/tools/wait/media-processor.js.map +1 -0
- package/dist/tools/wait/message-delivery.d.ts +63 -0
- package/dist/tools/wait/message-delivery.d.ts.map +1 -0
- package/dist/tools/wait/message-delivery.js +281 -0
- package/dist/tools/wait/message-delivery.js.map +1 -0
- package/dist/tools/wait/poll-loop.d.ts +72 -0
- package/dist/tools/wait/poll-loop.d.ts.map +1 -0
- package/dist/tools/wait/poll-loop.js +280 -0
- package/dist/tools/wait/poll-loop.js.map +1 -0
- package/dist/tools/wait/reaction-handler.d.ts +49 -0
- package/dist/tools/wait/reaction-handler.d.ts.map +1 -0
- package/dist/tools/wait/reaction-handler.js +126 -0
- package/dist/tools/wait/reaction-handler.js.map +1 -0
- package/dist/tools/wait/task-handler.d.ts +40 -0
- package/dist/tools/wait/task-handler.d.ts.map +1 -0
- package/dist/tools/wait/task-handler.js +41 -0
- package/dist/tools/wait/task-handler.js.map +1 -0
- package/dist/tools/wait-tool.d.ts +3 -69
- package/dist/tools/wait-tool.d.ts.map +1 -1
- package/dist/tools/wait-tool.js +3 -876
- package/dist/tools/wait-tool.js.map +1 -1
- package/package.json +1 -1
- package/templates/daily-review.default.md +26 -0
- package/templates/drive-dispatcher.default.md +2 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message delivery logic extracted from wait-tool.ts (Phase 4).
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* - Simple message processing (photo, document, text, sticker)
|
|
6
|
+
* - Auto-ingesting episodes for messages not saved by media handlers
|
|
7
|
+
* - Smart context injection (embedding search + GPT-4o-mini filter)
|
|
8
|
+
* - Intent classification and final operator response assembly
|
|
9
|
+
*/
|
|
10
|
+
import { basename } from "node:path";
|
|
11
|
+
import { saveFileToDisk } from "../../config.js";
|
|
12
|
+
import { classifyIntent } from "../../intent.js";
|
|
13
|
+
import { log } from "../../logger.js";
|
|
14
|
+
import { saveEpisode, searchByEmbedding, searchSemanticNotesRanked, } from "../../memory.js";
|
|
15
|
+
import { chatCompletion, generateEmbedding, } from "../../openai.js";
|
|
16
|
+
import { extractSearchKeywords, getReminders, getMediumReminder } from "../../response-builders.js";
|
|
17
|
+
import { errorMessage, IMAGE_EXTENSIONS } from "../../utils.js";
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// processSimpleMessage
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Process non-media message types for a single StoredMessage.
|
|
23
|
+
* Handles: photo, document, text, sticker.
|
|
24
|
+
* Returns content blocks for the processed parts.
|
|
25
|
+
*/
|
|
26
|
+
export async function processSimpleMessage(msg, telegram) {
|
|
27
|
+
const blocks = [];
|
|
28
|
+
// Photos: download the largest size, persist to disk, and embed as base64.
|
|
29
|
+
if (msg.message.photo && msg.message.photo.length > 0) {
|
|
30
|
+
const largest = msg.message.photo[msg.message.photo.length - 1];
|
|
31
|
+
try {
|
|
32
|
+
const { buffer, filePath: telegramPath } = await telegram.downloadFileAsBuffer(largest.file_id);
|
|
33
|
+
const ext = telegramPath.split(".").pop()?.toLowerCase() ?? "jpg";
|
|
34
|
+
const mimeType = ext === "png" ? "image/png" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
35
|
+
const base64 = buffer.toString("base64");
|
|
36
|
+
const diskPath = saveFileToDisk(buffer, `photo.${ext}`);
|
|
37
|
+
blocks.push({ type: "image", data: base64, mimeType });
|
|
38
|
+
blocks.push({
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `[Photo saved to: ${diskPath}]` +
|
|
41
|
+
(msg.message.caption ? ` Caption: ${msg.message.caption}` : ""),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
blocks.push({
|
|
46
|
+
type: "text",
|
|
47
|
+
text: `[Photo received but could not be downloaded: ${errorMessage(err)}]`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Documents: download, persist to disk, and embed as base64.
|
|
52
|
+
if (msg.message.document) {
|
|
53
|
+
const doc = msg.message.document;
|
|
54
|
+
try {
|
|
55
|
+
const { buffer, filePath: telegramPath } = await telegram.downloadFileAsBuffer(doc.file_id);
|
|
56
|
+
const filename = doc.file_name ?? basename(telegramPath);
|
|
57
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
58
|
+
const mimeType = doc.mime_type ?? (IMAGE_EXTENSIONS.has(ext) ? `image/${ext === "jpg" ? "jpeg" : ext}` : "application/octet-stream");
|
|
59
|
+
const base64 = buffer.toString("base64");
|
|
60
|
+
const diskPath = saveFileToDisk(buffer, filename);
|
|
61
|
+
const isImage = mimeType.startsWith("image/");
|
|
62
|
+
if (isImage) {
|
|
63
|
+
blocks.push({ type: "image", data: base64, mimeType });
|
|
64
|
+
blocks.push({
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `[File saved to: ${diskPath}]` +
|
|
67
|
+
(msg.message.caption ? ` Caption: ${msg.message.caption}` : ""),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Non-image documents: provide the disk path instead of
|
|
72
|
+
// dumping potentially huge base64 into the LLM context.
|
|
73
|
+
blocks.push({
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `[Document: ${filename} (${mimeType}) — saved to: ${diskPath}]` +
|
|
76
|
+
(msg.message.caption ? ` Caption: ${msg.message.caption}` : ""),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
blocks.push({
|
|
82
|
+
type: "text",
|
|
83
|
+
text: `[Document "${doc.file_name ?? "file"}" received but could not be downloaded: ${errorMessage(err)}]`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Text messages.
|
|
88
|
+
if (msg.message.text) {
|
|
89
|
+
blocks.push({ type: "text", text: msg.message.text });
|
|
90
|
+
}
|
|
91
|
+
// Stickers: deliver as text with emoji, set name, and file_id (so agent can re-use it).
|
|
92
|
+
if (msg.message.sticker) {
|
|
93
|
+
const emoji = msg.message.sticker.emoji || "🏷️";
|
|
94
|
+
const setName = msg.message.sticker.set_name || "unknown";
|
|
95
|
+
const fileId = msg.message.sticker.file_id;
|
|
96
|
+
blocks.push({
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `(The operator sent a sticker: ${emoji} from pack "${setName}", file_id: "${fileId}")`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return blocks;
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// handleEmptyContent
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
/**
|
|
107
|
+
* If contentBlocks is empty after processing all messages, push an
|
|
108
|
+
* unsupported-type placeholder and log the message fields for debugging.
|
|
109
|
+
*/
|
|
110
|
+
export function handleEmptyContent(contentBlocks, stored) {
|
|
111
|
+
if (contentBlocks.length === 0) {
|
|
112
|
+
const msgKeys = stored.map(m => Object.keys(m.message).filter(k => m.message[k] != null).join(",")).join(" | ");
|
|
113
|
+
log.warn(`[wait] No content blocks from ${stored.length} messages. Fields: ${msgKeys}`);
|
|
114
|
+
contentBlocks.push({
|
|
115
|
+
type: "text",
|
|
116
|
+
text: "[Unsupported message type received — the operator sent a message type that cannot be processed (e.g., sticker, location, contact). Please ask them to resend as text, photo, document, or voice.]",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// autoIngestEpisodes
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
/**
|
|
124
|
+
* Auto-ingest episodes for messages that were not already saved by
|
|
125
|
+
* voice/video handlers (identified by savedEpisodeUpdateIds).
|
|
126
|
+
*/
|
|
127
|
+
export function autoIngestEpisodes(stored, savedEpisodeUpdateIds, ctx) {
|
|
128
|
+
try {
|
|
129
|
+
const db = ctx.getMemoryDb();
|
|
130
|
+
const sessionId = `session_${ctx.sessionStartedAt}`;
|
|
131
|
+
if (ctx.effectiveThreadId !== undefined) {
|
|
132
|
+
// Collect text from messages that didn't already get an episode
|
|
133
|
+
const unsavedMsgs = stored.filter(m => !savedEpisodeUpdateIds.has(m.update_id));
|
|
134
|
+
if (unsavedMsgs.length > 0) {
|
|
135
|
+
const textContent = unsavedMsgs
|
|
136
|
+
.map(m => m.message.text ?? m.message.caption ?? "")
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
.join("\n")
|
|
139
|
+
.slice(0, 2000);
|
|
140
|
+
if (textContent) {
|
|
141
|
+
saveEpisode(db, {
|
|
142
|
+
sessionId,
|
|
143
|
+
threadId: ctx.effectiveThreadId,
|
|
144
|
+
type: "operator_message",
|
|
145
|
+
modality: "text",
|
|
146
|
+
content: { text: textContent },
|
|
147
|
+
importance: 0.5,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (_) { /* memory write failures should never break the main flow */ }
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// buildSmartContext
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
/**
|
|
159
|
+
* Smart context injection: retrieves candidate memory notes via embedding
|
|
160
|
+
* search, then uses GPT-4o-mini to select ONLY the notes truly relevant
|
|
161
|
+
* to the operator's message. Falls back to keyword search if embedding
|
|
162
|
+
* fails, and to raw top-3 if the LLM filter fails.
|
|
163
|
+
*/
|
|
164
|
+
export async function buildSmartContext(operatorText, ctx) {
|
|
165
|
+
let autoMemoryContext = "";
|
|
166
|
+
try {
|
|
167
|
+
const db = ctx.getMemoryDb();
|
|
168
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
169
|
+
if (operatorText.length > 10 && apiKey) {
|
|
170
|
+
// Phase 1: Broad retrieval — get 10 candidates via embedding search
|
|
171
|
+
let candidates = [];
|
|
172
|
+
try {
|
|
173
|
+
const queryEmb = await generateEmbedding(operatorText, apiKey);
|
|
174
|
+
const embResults = searchByEmbedding(db, queryEmb, { maxResults: 10, minSimilarity: 0.25, skipAccessTracking: true, threadId: ctx.effectiveThreadId });
|
|
175
|
+
candidates = embResults.map(n => ({ type: n.type, content: n.content.slice(0, 200), confidence: n.confidence, similarity: n.similarity }));
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Fallback to keyword search
|
|
179
|
+
const searchQuery = extractSearchKeywords(operatorText);
|
|
180
|
+
if (searchQuery.trim().length > 0) {
|
|
181
|
+
const kwResults = searchSemanticNotesRanked(db, searchQuery, { maxResults: 10, skipAccessTracking: true, threadId: ctx.effectiveThreadId });
|
|
182
|
+
candidates = kwResults.map(n => ({ type: n.type, content: n.content.slice(0, 200), confidence: n.confidence }));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (candidates.length > 0) {
|
|
186
|
+
// Phase 2: GPT-4o-mini filters and compresses
|
|
187
|
+
try {
|
|
188
|
+
const noteList = candidates.map((c, i) => `[${i}] [${c.type}] ${c.content}`).join("\n");
|
|
189
|
+
const filterResponse = await chatCompletion([
|
|
190
|
+
{
|
|
191
|
+
role: "system",
|
|
192
|
+
content: "You are a context filter for an AI assistant. Given an operator's message and candidate memory notes, " +
|
|
193
|
+
"select ONLY the notes that are directly relevant to the operator's current instruction or question. " +
|
|
194
|
+
"Discard notes that are tangentially related, duplicates, or noise. " +
|
|
195
|
+
"Return a JSON array of objects: [{\"i\": <index>, \"s\": \"<compressed one-liner>\"}] " +
|
|
196
|
+
"where 'i' is the note index and 's' is a compressed summary (max 80 chars). " +
|
|
197
|
+
"Return [] if no notes are relevant. Return at most 3 notes. Be aggressive about filtering.",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
role: "user",
|
|
201
|
+
content: `Operator message: "${operatorText.slice(0, 300)}"\n\nCandidate notes:\n${noteList}`,
|
|
202
|
+
},
|
|
203
|
+
], apiKey, { maxTokens: 200, temperature: 0 });
|
|
204
|
+
// Parse the response — expect JSON array
|
|
205
|
+
const jsonMatch = filterResponse.match(/\[.*\]/s);
|
|
206
|
+
if (jsonMatch) {
|
|
207
|
+
const filtered = JSON.parse(jsonMatch[0]);
|
|
208
|
+
if (filtered.length > 0) {
|
|
209
|
+
const lines = filtered
|
|
210
|
+
.filter(f => f.i >= 0 && f.i < candidates.length)
|
|
211
|
+
.slice(0, 3)
|
|
212
|
+
.map(f => {
|
|
213
|
+
const c = candidates[f.i];
|
|
214
|
+
return `- **[${c.type}]** ${f.s} _(conf: ${c.confidence})_`;
|
|
215
|
+
});
|
|
216
|
+
if (lines.length > 0) {
|
|
217
|
+
autoMemoryContext = `\n\n## Relevant Memory (auto-injected)\n${lines.join("\n")}`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
log.verbose("memory", `Smart filter: ${candidates.length} candidates → ${(jsonMatch ? JSON.parse(jsonMatch[0]) : []).length} selected`);
|
|
222
|
+
}
|
|
223
|
+
catch (filterErr) {
|
|
224
|
+
// GPT-4o-mini filter failed — fall back to top-3 raw notes
|
|
225
|
+
log.warn(`[memory] Smart filter failed, using raw top-3: ${filterErr instanceof Error ? filterErr.message : String(filterErr)}`);
|
|
226
|
+
const lines = candidates.slice(0, 3).map(c => `- **[${c.type}]** ${c.content} _(conf: ${c.confidence})_`);
|
|
227
|
+
autoMemoryContext = `\n\n## Relevant Memory (auto-injected)\n${lines.join("\n")}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else if (operatorText.length > 10) {
|
|
232
|
+
// No API key — keyword search, raw top-3
|
|
233
|
+
const searchQuery = extractSearchKeywords(operatorText);
|
|
234
|
+
if (searchQuery.trim().length > 0) {
|
|
235
|
+
const kwResults = searchSemanticNotesRanked(db, searchQuery, { maxResults: 3, skipAccessTracking: true, threadId: ctx.effectiveThreadId });
|
|
236
|
+
if (kwResults.length > 0) {
|
|
237
|
+
const lines = kwResults.map(n => `- **[${n.type}]** ${n.content.slice(0, 200)} _(conf: ${n.confidence})_`);
|
|
238
|
+
autoMemoryContext = `\n\n## Relevant Memory (auto-injected)\n${lines.join("\n")}`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (_) { /* memory search failures should never break message delivery */ }
|
|
244
|
+
return autoMemoryContext;
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// assembleOperatorResponse
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
/**
|
|
250
|
+
* Build the final operator message response: intent classification,
|
|
251
|
+
* reminder selection, and the `<<< OPERATOR MESSAGE >>>` envelope.
|
|
252
|
+
*/
|
|
253
|
+
export function assembleOperatorResponse(contentBlocks, operatorText, hasVoiceMessages, autoMemoryContext, ctx) {
|
|
254
|
+
const intent = classifyIntent(operatorText);
|
|
255
|
+
log.verbose("intent", `Classified "${operatorText.substring(0, 50)}" as ${intent}`);
|
|
256
|
+
const reminder = intent === "conversational"
|
|
257
|
+
? getMediumReminder(ctx.effectiveThreadId, ctx.sessionStartedAt, ctx.autonomousMode)
|
|
258
|
+
: getReminders(ctx.effectiveThreadId, ctx.sessionStartedAt, ctx.autonomousMode);
|
|
259
|
+
return {
|
|
260
|
+
content: [
|
|
261
|
+
{
|
|
262
|
+
type: "text",
|
|
263
|
+
text: "Follow the operator's instructions below.",
|
|
264
|
+
},
|
|
265
|
+
{ type: "text", text: "<<< OPERATOR MESSAGE >>>" },
|
|
266
|
+
...contentBlocks,
|
|
267
|
+
...(hasVoiceMessages
|
|
268
|
+
? [{
|
|
269
|
+
type: "text",
|
|
270
|
+
text: "(Operator sent voice — respond with `send_voice`.)",
|
|
271
|
+
}]
|
|
272
|
+
: []),
|
|
273
|
+
{ type: "text", text: reminder },
|
|
274
|
+
{ type: "text", text: "<<< END OPERATOR MESSAGE >>>" },
|
|
275
|
+
...(autoMemoryContext
|
|
276
|
+
? [{ type: "text", text: autoMemoryContext }]
|
|
277
|
+
: []),
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=message-delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-delivery.js","sourceRoot":"","sources":["../../../src/tools/wait/message-delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core poll-loop orchestrator for remote_copilot_wait_for_instructions.
|
|
3
|
+
*
|
|
4
|
+
* This is the main long-polling loop that:
|
|
5
|
+
* - Polls the dispatcher for new operator messages every 2s
|
|
6
|
+
* - Processes all media types: text, photo, document, voice, video_note
|
|
7
|
+
* - Runs voice analysis (transcription + emotion via VANPY)
|
|
8
|
+
* - Auto-saves episodes to memory
|
|
9
|
+
* - Injects relevant memory context via GPT-4o-mini smart filter
|
|
10
|
+
* - Checks scheduled tasks during idle polling
|
|
11
|
+
* - Triggers auto-consolidation (idle, episode-count, time-based)
|
|
12
|
+
* - Sends SSE keepalive pings every 30s
|
|
13
|
+
* - Detects maintenance flags and instructs agent to wait externally
|
|
14
|
+
* - Activates the Dispatcher drive after extended operator silence
|
|
15
|
+
*/
|
|
16
|
+
import { type initMemoryDb } from "../../memory.js";
|
|
17
|
+
import type { TelegramClient } from "../../telegram.js";
|
|
18
|
+
import type { AppConfig } from "../../types.js";
|
|
19
|
+
type ContentBlock = {
|
|
20
|
+
type: string;
|
|
21
|
+
text: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: "image";
|
|
24
|
+
data: string;
|
|
25
|
+
mimeType: string;
|
|
26
|
+
};
|
|
27
|
+
type ToolResult = {
|
|
28
|
+
content: Array<ContentBlock>;
|
|
29
|
+
isError?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export interface WaitToolContext {
|
|
32
|
+
/** Mutable per-session state — the handler reads and writes directly. */
|
|
33
|
+
state: {
|
|
34
|
+
currentThreadId: number | undefined;
|
|
35
|
+
sessionStartedAt: number;
|
|
36
|
+
waitCallCount: number;
|
|
37
|
+
lastToolCallAt: number;
|
|
38
|
+
deadSessionAlerted: boolean;
|
|
39
|
+
toolCallsSinceLastDelivery: number;
|
|
40
|
+
lastOperatorMessageAt: number;
|
|
41
|
+
lastOperatorMessageText: string;
|
|
42
|
+
lastConsolidationAt: number;
|
|
43
|
+
previewedUpdateIds: Set<number>;
|
|
44
|
+
lastDriveAttemptAt: number;
|
|
45
|
+
drivePhase2Fired: boolean;
|
|
46
|
+
};
|
|
47
|
+
addPreviewedId: (id: number) => void;
|
|
48
|
+
generateDmnReflection: (threadId: number) => string;
|
|
49
|
+
resolveThreadId: (args: Record<string, unknown> | undefined) => number | undefined;
|
|
50
|
+
telegram: TelegramClient;
|
|
51
|
+
telegramChatId: string;
|
|
52
|
+
getMemoryDb: () => ReturnType<typeof initMemoryDb>;
|
|
53
|
+
config: AppConfig;
|
|
54
|
+
errorResult: (msg: string) => {
|
|
55
|
+
content: Array<{
|
|
56
|
+
type: string;
|
|
57
|
+
text: string;
|
|
58
|
+
}>;
|
|
59
|
+
isError: true;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export interface WaitToolExtra {
|
|
63
|
+
sendNotification: (notification: {
|
|
64
|
+
method: string;
|
|
65
|
+
params: Record<string, unknown>;
|
|
66
|
+
}) => Promise<void>;
|
|
67
|
+
signal: AbortSignal;
|
|
68
|
+
requestId?: string | number;
|
|
69
|
+
}
|
|
70
|
+
export declare function handleWaitForInstructions(args: Record<string, unknown>, ctx: WaitToolContext, extra: WaitToolExtra): Promise<ToolResult>;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=poll-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poll-loop.d.ts","sourceRoot":"","sources":["../../../src/tools/wait/poll-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAchD,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AACvG,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,KAAK,EAAE;QACL,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;QACpC,gBAAgB,EAAE,MAAM,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,0BAA0B,EAAE,MAAM,CAAC;QACnC,qBAAqB,EAAE,MAAM,CAAC;QAC9B,uBAAuB,EAAE,MAAM,CAAC;QAChC,mBAAmB,EAAE,MAAM,CAAC;QAC5B,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAChC,kBAAkB,EAAE,MAAM,CAAC;QAC3B,gBAAgB,EAAE,OAAO,CAAC;KAC3B,CAAC;IACF,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,qBAAqB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IACpD,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KAAK,MAAM,GAAG,SAAS,CAAC;IAGnF,QAAQ,EAAE,cAAc,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;IACnD,MAAM,EAAE,SAAS,CAAC;IAGlB,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,OAAO,EAAE,IAAI,CAAA;KAAE,CAAC;CACjG;AAED,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE,CAAC,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvG,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAMD,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,UAAU,CAAC,CA8RrB"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core poll-loop orchestrator for remote_copilot_wait_for_instructions.
|
|
3
|
+
*
|
|
4
|
+
* This is the main long-polling loop that:
|
|
5
|
+
* - Polls the dispatcher for new operator messages every 2s
|
|
6
|
+
* - Processes all media types: text, photo, document, voice, video_note
|
|
7
|
+
* - Runs voice analysis (transcription + emotion via VANPY)
|
|
8
|
+
* - Auto-saves episodes to memory
|
|
9
|
+
* - Injects relevant memory context via GPT-4o-mini smart filter
|
|
10
|
+
* - Checks scheduled tasks during idle polling
|
|
11
|
+
* - Triggers auto-consolidation (idle, episode-count, time-based)
|
|
12
|
+
* - Sends SSE keepalive pings every 30s
|
|
13
|
+
* - Detects maintenance flags and instructs agent to wait externally
|
|
14
|
+
* - Activates the Dispatcher drive after extended operator silence
|
|
15
|
+
*/
|
|
16
|
+
import { checkMaintenanceFlag } from "../../config.js";
|
|
17
|
+
import { peekThreadMessages, readThreadMessages } from "../../dispatcher.js";
|
|
18
|
+
import { assembleCompactRefresh, } from "../../memory.js";
|
|
19
|
+
import { listSchedules } from "../../scheduler.js";
|
|
20
|
+
import { log } from "../../logger.js";
|
|
21
|
+
import { getReminders, getShortReminder } from "../../response-builders.js";
|
|
22
|
+
import { processVoice, processAnimation, processVideoNote } from "./media-processor.js";
|
|
23
|
+
import { handleReactionWithMessages, handleReactionOnly } from "./reaction-handler.js";
|
|
24
|
+
import { checkForDueTasks } from "./task-handler.js";
|
|
25
|
+
import { runAutoConsolidation, checkDriveActivation } from "./drive-handler.js";
|
|
26
|
+
import { processSimpleMessage, handleEmptyContent, autoIngestEpisodes, buildSmartContext, assembleOperatorResponse } from "./message-delivery.js";
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Handler
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
export async function handleWaitForInstructions(args, ctx, extra) {
|
|
31
|
+
const { state, telegram, telegramChatId, config, getMemoryDb } = ctx;
|
|
32
|
+
const { OPENAI_API_KEY, VOICE_ANALYSIS_URL, WAIT_TIMEOUT_MINUTES, AUTONOMOUS_MODE } = config;
|
|
33
|
+
// Agent is actively polling — this is the primary health signal
|
|
34
|
+
state.deadSessionAlerted = false;
|
|
35
|
+
state.toolCallsSinceLastDelivery = 0;
|
|
36
|
+
const effectiveThreadId = ctx.resolveThreadId(args);
|
|
37
|
+
if (effectiveThreadId === undefined) {
|
|
38
|
+
return ctx.errorResult("Error: No active session. Call start_session first, then pass the returned threadId to this tool.");
|
|
39
|
+
}
|
|
40
|
+
const callNumber = ++state.waitCallCount;
|
|
41
|
+
const timeoutMs = WAIT_TIMEOUT_MINUTES * 60 * 1000;
|
|
42
|
+
const deadline = Date.now() + timeoutMs;
|
|
43
|
+
// Poll the dispatcher's per-thread file instead of calling getUpdates
|
|
44
|
+
// directly. This avoids 409 conflicts between concurrent instances.
|
|
45
|
+
const POLL_INTERVAL_MS = 2000;
|
|
46
|
+
const SSE_KEEPALIVE_INTERVAL_MS = 30_000;
|
|
47
|
+
let lastScheduleCheck = 0;
|
|
48
|
+
let lastKeepalive = Date.now();
|
|
49
|
+
let maintenanceNotified = false;
|
|
50
|
+
while (Date.now() < deadline) {
|
|
51
|
+
// Check for pending update — tell agent to wait externally via Desktop Commander
|
|
52
|
+
// CRITICAL: Do NOT tell agents to call hibernate or any MCP tool here — the server
|
|
53
|
+
// is about to die. Agents must use an external sleep (PowerShell Start-Sleep) instead.
|
|
54
|
+
const maintenanceInfo = checkMaintenanceFlag();
|
|
55
|
+
if (maintenanceInfo) {
|
|
56
|
+
log.info(`[wait] Maintenance flag detected: ${maintenanceInfo}`);
|
|
57
|
+
// Notify operator via Telegram once
|
|
58
|
+
if (!maintenanceNotified) {
|
|
59
|
+
maintenanceNotified = true;
|
|
60
|
+
let version = "unknown";
|
|
61
|
+
try {
|
|
62
|
+
version = JSON.parse(maintenanceInfo).version ?? version;
|
|
63
|
+
}
|
|
64
|
+
catch { /* not JSON or missing field */ }
|
|
65
|
+
telegram.sendMessage(telegramChatId, `\u26A0\uFE0F Server update: v${version} deploying. Agent sessions will reconnect after update.`, undefined, effectiveThreadId).catch(() => { });
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
content: [{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: `⚠️ **Server update pending** (${maintenanceInfo}). ` +
|
|
71
|
+
`The MCP server will restart shortly. Use Desktop Commander to run: ` +
|
|
72
|
+
`Start-Sleep -Seconds 600 — then call start_session with threadId=${effectiveThreadId} to reconnect.` +
|
|
73
|
+
getShortReminder(effectiveThreadId, state.sessionStartedAt),
|
|
74
|
+
}],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Peek first (non-destructive) to avoid consuming messages when the
|
|
78
|
+
// SSE connection may be dead.
|
|
79
|
+
const peeked = peekThreadMessages(effectiveThreadId);
|
|
80
|
+
if (peeked.length > 0) {
|
|
81
|
+
// Verify SSE connection is alive BEFORE consuming messages.
|
|
82
|
+
// This prevents the destructive readThreadMessages from eating
|
|
83
|
+
// messages that can never be delivered to a dead connection.
|
|
84
|
+
if (extra.signal.aborted) {
|
|
85
|
+
log.warn(`[wait] SSE connection aborted before consuming ${peeked.length} messages — leaving in queue.`);
|
|
86
|
+
return {
|
|
87
|
+
content: [{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: "The connection was interrupted. Messages are preserved for the next call.",
|
|
90
|
+
}],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Connection alive — now consume messages for real.
|
|
94
|
+
const stored = readThreadMessages(effectiveThreadId);
|
|
95
|
+
log.info(`[wait] Read ${stored.length} messages from thread ${effectiveThreadId}. Processing...`);
|
|
96
|
+
// Update the operator activity timestamp and last message text.
|
|
97
|
+
state.lastOperatorMessageAt = Date.now();
|
|
98
|
+
state.lastOperatorMessageText = stored
|
|
99
|
+
.map(m => m.message.text ?? m.message.caption ?? "")
|
|
100
|
+
.filter(Boolean)
|
|
101
|
+
.join("\n")
|
|
102
|
+
.slice(0, 2000) || "";
|
|
103
|
+
// Clear only the consumed IDs from the previewed set (scoped clear).
|
|
104
|
+
// This is safe because Node.js is single-threaded — no report_progress
|
|
105
|
+
// call can interleave between readThreadMessages and this cleanup.
|
|
106
|
+
for (const msg of stored) {
|
|
107
|
+
state.previewedUpdateIds.delete(msg.update_id);
|
|
108
|
+
}
|
|
109
|
+
// React with 👀 on each consumed message to signal "seen" to the operator.
|
|
110
|
+
for (const msg of stored) {
|
|
111
|
+
void telegram.setMessageReaction(telegramChatId, msg.message.message_id).catch(() => { });
|
|
112
|
+
}
|
|
113
|
+
const contentBlocks = [];
|
|
114
|
+
let hasVoiceMessages = false;
|
|
115
|
+
// Track which messages already had episodes saved (voice/video handlers)
|
|
116
|
+
const savedEpisodeUpdateIds = new Set();
|
|
117
|
+
for (const msg of stored) {
|
|
118
|
+
// Photos, documents, text, stickers — handled by message-delivery.
|
|
119
|
+
const simpleBlocks = await processSimpleMessage(msg, telegram);
|
|
120
|
+
contentBlocks.push(...simpleBlocks);
|
|
121
|
+
// Voice messages: transcribe using OpenAI Whisper.
|
|
122
|
+
if (msg.message.voice) {
|
|
123
|
+
hasVoiceMessages = true;
|
|
124
|
+
const mediaCtx = { telegram, openaiApiKey: OPENAI_API_KEY, voiceAnalysisUrl: VOICE_ANALYSIS_URL, effectiveThreadId: effectiveThreadId, sessionStartedAt: state.sessionStartedAt, getMemoryDb };
|
|
125
|
+
const result = await processVoice(msg, mediaCtx);
|
|
126
|
+
contentBlocks.push(...result.blocks);
|
|
127
|
+
if (result.episodeSaved)
|
|
128
|
+
savedEpisodeUpdateIds.add(msg.update_id);
|
|
129
|
+
}
|
|
130
|
+
// Animations / GIFs: download full file, extract frames, run multi-frame vision analysis
|
|
131
|
+
// (same pipeline as video_notes — uses extractVideoFrames + analyzeVideoFrames).
|
|
132
|
+
if (msg.message.animation) {
|
|
133
|
+
const mediaCtx = { telegram, openaiApiKey: OPENAI_API_KEY, voiceAnalysisUrl: VOICE_ANALYSIS_URL, effectiveThreadId: effectiveThreadId, sessionStartedAt: state.sessionStartedAt, getMemoryDb };
|
|
134
|
+
const animBlocks = await processAnimation(msg, mediaCtx);
|
|
135
|
+
contentBlocks.push(...animBlocks);
|
|
136
|
+
}
|
|
137
|
+
// Video notes (circle videos): extract frames, analyze with GPT-4.1 vision,
|
|
138
|
+
// optionally transcribe the audio track.
|
|
139
|
+
if (msg.message.video_note) {
|
|
140
|
+
hasVoiceMessages = true; // Video notes often contain speech
|
|
141
|
+
const mediaCtx = { telegram, openaiApiKey: OPENAI_API_KEY, voiceAnalysisUrl: VOICE_ANALYSIS_URL, effectiveThreadId: effectiveThreadId, sessionStartedAt: state.sessionStartedAt, getMemoryDb };
|
|
142
|
+
const result = await processVideoNote(msg, mediaCtx);
|
|
143
|
+
contentBlocks.push(...result.blocks);
|
|
144
|
+
if (result.episodeSaved)
|
|
145
|
+
savedEpisodeUpdateIds.add(msg.update_id);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
handleEmptyContent(contentBlocks, stored);
|
|
149
|
+
log.info(`[wait] ${contentBlocks.length} content blocks built. Saving episodes...`);
|
|
150
|
+
// Auto-ingest episodes for messages not already saved by voice/video handlers
|
|
151
|
+
autoIngestEpisodes(stored, savedEpisodeUpdateIds, { getMemoryDb, effectiveThreadId: effectiveThreadId, sessionStartedAt: state.sessionStartedAt });
|
|
152
|
+
// ── Check for pending operator reactions ─────────────────────────
|
|
153
|
+
await handleReactionWithMessages(contentBlocks, {
|
|
154
|
+
telegram,
|
|
155
|
+
getMemoryDb,
|
|
156
|
+
effectiveThreadId,
|
|
157
|
+
sessionStartedAt: state.sessionStartedAt,
|
|
158
|
+
});
|
|
159
|
+
log.info(`[wait] Episodes saved. Building auto-memory context...`);
|
|
160
|
+
// Extract operator text for memory search and intent classification.
|
|
161
|
+
const operatorText = stored
|
|
162
|
+
.map(m => m.message.text ?? m.message.caption ?? "")
|
|
163
|
+
.filter(Boolean)
|
|
164
|
+
.join(" ")
|
|
165
|
+
.slice(0, 500);
|
|
166
|
+
// Smart context injection (GPT-4o-mini preprocessor)
|
|
167
|
+
const autoMemoryContext = await buildSmartContext(operatorText, { getMemoryDb, effectiveThreadId });
|
|
168
|
+
log.info(`[wait] Returning response with ${contentBlocks.length} blocks to agent.`);
|
|
169
|
+
return assembleOperatorResponse(contentBlocks, operatorText, hasVoiceMessages, autoMemoryContext, { effectiveThreadId: effectiveThreadId, sessionStartedAt: state.sessionStartedAt, autonomousMode: AUTONOMOUS_MODE });
|
|
170
|
+
}
|
|
171
|
+
// ── Reaction-only wake-up ───────────────────────────────────────
|
|
172
|
+
{
|
|
173
|
+
const reactionResult = await handleReactionOnly({
|
|
174
|
+
telegram,
|
|
175
|
+
getMemoryDb,
|
|
176
|
+
effectiveThreadId,
|
|
177
|
+
sessionStartedAt: state.sessionStartedAt,
|
|
178
|
+
autonomousMode: AUTONOMOUS_MODE,
|
|
179
|
+
});
|
|
180
|
+
if (reactionResult)
|
|
181
|
+
return reactionResult;
|
|
182
|
+
}
|
|
183
|
+
// Check scheduled tasks every ~60s during idle polling.
|
|
184
|
+
if (effectiveThreadId !== undefined && Date.now() - lastScheduleCheck >= 60_000) {
|
|
185
|
+
lastScheduleCheck = Date.now();
|
|
186
|
+
const taskResult = checkForDueTasks(ctx, effectiveThreadId);
|
|
187
|
+
if (taskResult)
|
|
188
|
+
return taskResult;
|
|
189
|
+
}
|
|
190
|
+
// No messages yet — sleep briefly and check again.
|
|
191
|
+
// Send SSE keepalive to prevent silent connection death during long polls.
|
|
192
|
+
if (Date.now() - lastKeepalive >= SSE_KEEPALIVE_INTERVAL_MS) {
|
|
193
|
+
lastKeepalive = Date.now();
|
|
194
|
+
state.lastToolCallAt = Date.now();
|
|
195
|
+
try {
|
|
196
|
+
await extra.sendNotification({
|
|
197
|
+
method: "notifications/progress",
|
|
198
|
+
params: {
|
|
199
|
+
progressToken: extra.requestId,
|
|
200
|
+
progress: 0,
|
|
201
|
+
total: 0,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// If notification fails, the SSE stream is already dead.
|
|
207
|
+
// Return immediately so the agent can reconnect.
|
|
208
|
+
log.warn(`[wait] SSE keepalive failed — connection dead. Returning early.`);
|
|
209
|
+
state.lastToolCallAt = Date.now();
|
|
210
|
+
return {
|
|
211
|
+
content: [{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: "The connection was interrupted. Please call wait_for_instructions again immediately to resume polling.",
|
|
214
|
+
}],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
219
|
+
}
|
|
220
|
+
// Timeout elapsed with no actionable message.
|
|
221
|
+
const now = new Date().toISOString();
|
|
222
|
+
// Check for scheduled wake-up tasks.
|
|
223
|
+
if (effectiveThreadId !== undefined) {
|
|
224
|
+
const taskResult = checkForDueTasks(ctx, effectiveThreadId);
|
|
225
|
+
if (taskResult)
|
|
226
|
+
return taskResult;
|
|
227
|
+
}
|
|
228
|
+
const idleMinutes = Math.round((Date.now() - state.lastOperatorMessageAt) / 60000);
|
|
229
|
+
// Show pending scheduled tasks if any exist.
|
|
230
|
+
let scheduleHint = "";
|
|
231
|
+
if (effectiveThreadId !== undefined) {
|
|
232
|
+
const pending = listSchedules(effectiveThreadId);
|
|
233
|
+
if (pending.length > 0) {
|
|
234
|
+
const taskList = pending.map(t => {
|
|
235
|
+
let trigger = "";
|
|
236
|
+
if (t.runAt) {
|
|
237
|
+
trigger = `at ${new Date(t.runAt).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })}`;
|
|
238
|
+
}
|
|
239
|
+
else if (t.cron) {
|
|
240
|
+
trigger = `cron: ${t.cron}`;
|
|
241
|
+
}
|
|
242
|
+
else if (t.afterIdleMinutes) {
|
|
243
|
+
trigger = `after ${t.afterIdleMinutes}min idle`;
|
|
244
|
+
}
|
|
245
|
+
return ` • "${t.label}" (${trigger})`;
|
|
246
|
+
}).join("\n");
|
|
247
|
+
scheduleHint = `\n\n📋 **Pending scheduled tasks:**\n${taskList}`;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// ── Auto-consolidation during idle (fire-and-forget) ────────────────────
|
|
251
|
+
runAutoConsolidation({ state, effectiveThreadId, getMemoryDb, config, memoryRefresh: "", scheduleHint: "" });
|
|
252
|
+
// Periodic memory refresh — re-ground the agent every 10 polls (~5h)
|
|
253
|
+
// (reduced from 5 since auto-inject now handles per-message context)
|
|
254
|
+
let memoryRefresh = "";
|
|
255
|
+
if (callNumber % 10 === 0 && effectiveThreadId !== undefined) {
|
|
256
|
+
try {
|
|
257
|
+
const db = getMemoryDb();
|
|
258
|
+
const refresh = assembleCompactRefresh(db, effectiveThreadId);
|
|
259
|
+
if (refresh)
|
|
260
|
+
memoryRefresh = `\n\n${refresh}`;
|
|
261
|
+
}
|
|
262
|
+
catch (_) { /* non-fatal */ }
|
|
263
|
+
}
|
|
264
|
+
// ── 3-Phase Probabilistic Autonomous Drive ──────────────────────────────
|
|
265
|
+
const driveActivationResult = checkDriveActivation({ state, effectiveThreadId, getMemoryDb, config, memoryRefresh, scheduleHint });
|
|
266
|
+
if (driveActivationResult)
|
|
267
|
+
return driveActivationResult;
|
|
268
|
+
return {
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: `No new instructions. Call \`remote_copilot_wait_for_instructions\` again to keep listening.` +
|
|
273
|
+
memoryRefresh +
|
|
274
|
+
scheduleHint +
|
|
275
|
+
getReminders(effectiveThreadId, state.sessionStartedAt, AUTONOMOUS_MODE),
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=poll-loop.js.map
|