@agentprojectcontext/apx 1.31.2 → 1.32.2
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/package.json +6 -1
- package/skills/apc-context/SKILL.md +5 -2
- package/skills/apx/SKILL.md +3 -3
- package/skills/apx-agency-agents/SKILL.md +5 -5
- package/skills/apx-agent/SKILL.md +7 -7
- package/skills/apx-mcp/SKILL.md +6 -4
- package/skills/apx-mcp-builder/SKILL.md +4 -7
- package/skills/apx-project/SKILL.md +4 -5
- package/skills/apx-routine/SKILL.md +14 -12
- package/skills/apx-runtime/SKILL.md +5 -3
- package/skills/apx-sessions/SKILL.md +5 -5
- package/skills/apx-skill-builder/SKILL.md +10 -6
- package/skills/apx-task/SKILL.md +8 -8
- package/skills/apx-telegram/SKILL.md +23 -7
- package/skills/apx-voice/SKILL.md +8 -6
- package/src/core/{agent-system.js → agent/build-agent-system.js} +10 -12
- package/src/core/agent/constants.js +5 -0
- package/src/core/agent/index.js +0 -2
- package/src/core/{agent-memory.js → agent/memory.js} +2 -2
- package/src/core/agent/model-router.js +21 -43
- package/src/core/agent/prompt-builder.js +17 -63
- package/src/core/agent/prompts/action-discipline.md +17 -0
- package/src/core/agent/prompts/channels/code.md +8 -12
- package/src/core/agent/prompts/channels/desktop.md +6 -4
- package/src/core/agent/prompts/channels/routine.md +10 -1
- package/src/core/agent/prompts/channels/telegram.md +5 -0
- package/src/core/agent/prompts/channels/web_code.md +20 -0
- package/src/core/agent/prompts/modes/voice.md +2 -2
- package/src/core/agent/prompts/super-agent-base.md +2 -2
- package/src/core/agent/run-agent.js +66 -36
- package/src/core/agent/runtime-bridge.js +42 -0
- package/src/core/agent/self-memory.js +19 -9
- package/src/core/agent/skills/catalog.js +65 -0
- package/src/core/agent/skills/index.js +6 -0
- package/src/{host/daemon/skills-loader.js → core/agent/skills/loader.js} +3 -3
- package/src/core/agent/skills/rag.js +91 -0
- package/src/core/agent/skills/trigger.js +71 -0
- package/src/{host/daemon → core/agent}/super-agent.js +5 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/add-project.js +3 -4
- package/src/core/agent/tools/handlers/ask-questions.js +115 -0
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-agent.js +2 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-mcp.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-runtime.js +10 -11
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/create-task.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/discover-tools.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/edit-file.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/import-agent.js +4 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-skills.js +7 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-tasks.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-vault-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/load-skill.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-agent-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-self-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/remember.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/run-shell.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-sessions.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/send-telegram.js +0 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-identity.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-permission-mode.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/tail-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/transcribe-audio.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/write-file.js +1 -2
- package/src/core/agent/tools/helpers.js +74 -0
- package/src/{host/daemon/super-agent-tools → core/agent/tools}/registry-bridge.js +3 -3
- package/src/{host/daemon/super-agent-tools/index.js → core/agent/tools/registry.js} +31 -32
- package/src/core/apc/agents-vault.js +37 -0
- package/src/core/{scaffold.js → apc/scaffold.js} +4 -5
- package/src/core/{config.js → config/index.js} +21 -27
- package/src/core/config/paths.js +32 -0
- package/src/core/constants/actors.js +8 -0
- package/src/core/constants/channels.js +19 -0
- package/src/core/constants/index.js +5 -0
- package/src/core/constants/permissions.js +17 -0
- package/src/core/constants/roles.js +9 -0
- package/src/core/engines/_streaming.js +63 -0
- package/src/core/engines/anthropic.js +11 -22
- package/src/core/engines/ollama.js +7 -16
- package/src/core/identity/index.js +8 -0
- package/src/core/{identity.js → identity/self.js} +5 -5
- package/src/core/{telegram-identity.js → identity/telegram.js} +1 -1
- package/src/core/logging.js +1 -1
- package/src/core/mascot.js +1 -1
- package/src/core/memory/active-threads.js +10 -10
- package/src/core/memory/broker.js +9 -9
- package/src/core/memory/compactor.js +2 -2
- package/src/core/memory/index.js +2 -2
- package/src/core/memory/indexer.js +1 -1
- package/src/core/{code-sessions-store.js → stores/code-sessions.js} +3 -7
- package/src/core/{messages-store.js → stores/messages.js} +6 -4
- package/src/core/stores/routine-memory.js +71 -0
- package/src/core/{routines-store.js → stores/routines.js} +1 -3
- package/src/core/stores/runtime-sessions.js +99 -0
- package/src/core/{tasks-store.js → stores/tasks.js} +3 -8
- package/src/core/update-check.js +1 -1
- package/src/core/util/ids.js +14 -0
- package/src/core/util/index.js +2 -0
- package/src/core/util/text-similarity.js +52 -0
- package/src/core/util/time.js +9 -0
- package/src/core/voice/tts.js +1 -1
- package/src/host/daemon/api/admin-config.js +4 -3
- package/src/host/daemon/api/admin.js +1 -1
- package/src/host/daemon/api/agents.js +4 -25
- package/src/host/daemon/api/artifacts.js +118 -1
- package/src/host/daemon/api/code.js +60 -16
- package/src/host/daemon/api/confirm.js +1 -1
- package/src/host/daemon/api/connections.js +2 -2
- package/src/host/daemon/api/conversations.js +2 -2
- package/src/host/daemon/api/deck.js +1 -1
- package/src/host/daemon/api/desktop.js +1 -1
- package/src/host/daemon/api/embeddings.js +4 -4
- package/src/host/daemon/api/engines.js +2 -2
- package/src/host/daemon/api/exec.js +3 -3
- package/src/host/daemon/api/identity.js +1 -1
- package/src/host/daemon/api/mcps.js +1 -1
- package/src/host/daemon/api/messages.js +1 -1
- package/src/host/daemon/api/runtimes.js +9 -8
- package/src/host/daemon/api/sessions-search.js +1 -1
- package/src/host/daemon/api/sessions.js +2 -2
- package/src/host/daemon/api/shared.js +5 -4
- package/src/host/daemon/api/skills.js +30 -0
- package/src/host/daemon/api/super-agent.js +29 -9
- package/src/host/daemon/api/tasks.js +2 -2
- package/src/host/daemon/api/telegram.js +1 -1
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/tts.js +2 -2
- package/src/host/daemon/api/voice.js +14 -12
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/compact.js +1 -1
- package/src/host/daemon/db.js +4 -4
- package/src/host/daemon/desktop-ws.js +1 -1
- package/src/host/daemon/index.js +4 -4
- package/src/host/daemon/plugins/{desktop.js → desktop/index.js} +45 -6
- package/src/host/daemon/plugins/index.js +2 -2
- package/src/host/daemon/plugins/telegram/ask.js +309 -0
- package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +390 -191
- package/src/host/daemon/plugins/telegram/media.js +162 -0
- package/src/host/daemon/projects-helpers.js +54 -0
- package/src/host/daemon/routines.js +28 -12
- package/src/host/daemon/smoke.js +2 -2
- package/src/host/daemon/token-store.js +1 -1
- package/src/host/daemon/transcription.js +2 -2
- package/src/host/daemon/wakeup.js +2 -2
- package/src/interfaces/cli/commands/agent.js +3 -3
- package/src/interfaces/cli/commands/artifact.js +99 -0
- package/src/interfaces/cli/commands/command.js +1 -1
- package/src/interfaces/cli/commands/config.js +3 -2
- package/src/interfaces/cli/commands/desktop.js +1 -1
- package/src/interfaces/cli/commands/exec.js +2 -1
- package/src/interfaces/cli/commands/identity.js +2 -2
- package/src/interfaces/cli/commands/init.js +1 -1
- package/src/interfaces/cli/commands/mcp.js +1 -1
- package/src/interfaces/cli/commands/memory.js +2 -2
- package/src/interfaces/cli/commands/model.js +16 -6
- package/src/interfaces/cli/commands/project.js +1 -1
- package/src/interfaces/cli/commands/routine.js +58 -0
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/session.js +4 -4
- package/src/interfaces/cli/commands/setup.js +4 -3
- package/src/interfaces/cli/commands/skills.js +25 -4
- package/src/interfaces/cli/commands/status.js +1 -1
- package/src/interfaces/cli/commands/sys.js +11 -4
- package/src/interfaces/cli/commands/update.js +1 -1
- package/src/interfaces/cli/index.js +8 -4
- package/src/interfaces/cli/postinstall.js +2 -2
- package/src/interfaces/cli/terminal-chat/renderer.js +22 -2
- package/src/interfaces/mcp-server/index.js +1 -1
- package/src/interfaces/tui/component/prompt/index.tsx +3 -1
- package/src/interfaces/tui/context/sdk-apx.tsx +47 -7
- package/src/interfaces/tui/context/sync-apx.tsx +20 -2
- package/src/interfaces/tui/context/sync.tsx +2 -1
- package/src/interfaces/tui/routes/session/index.tsx +151 -136
- package/src/interfaces/tui/routes/session/sidebar-apx.tsx +37 -15
- package/src/interfaces/tui/run.ts +2 -0
- package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +1 -0
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js +570 -0
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +9 -9
- package/src/interfaces/web/src/App.tsx +51 -32
- package/src/interfaces/web/src/components/RobyBubble.tsx +12 -6
- package/src/interfaces/web/src/components/UiSelect.tsx +1 -1
- package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +72 -0
- package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +399 -0
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
- package/src/interfaces/web/src/components/chat/MessageList.tsx +2 -1
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +230 -0
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +40 -17
- package/src/interfaces/web/src/components/common/TabLayout.tsx +9 -5
- package/src/interfaces/web/src/components/common/TabNav.tsx +3 -3
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +4 -2
- package/src/interfaces/web/src/hooks/useChat.ts +47 -2
- package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +59 -0
- package/src/interfaces/web/src/hooks/usePersonaName.ts +11 -0
- package/src/interfaces/web/src/i18n/en.ts +27 -7
- package/src/interfaces/web/src/i18n/es.ts +27 -7
- package/src/interfaces/web/src/lib/api/artifacts.ts +47 -0
- package/src/interfaces/web/src/lib/api/skills.ts +25 -0
- package/src/interfaces/web/src/lib/api.ts +2 -0
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +41 -20
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +5 -18
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -8
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +39 -40
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +27 -9
- package/src/skills/apc-context/SKILL.md +159 -0
- package/src/core/agent/ghost-guard.js +0 -24
- package/src/core/agent/prompts/channels/terminal.md +0 -16
- package/src/host/daemon/apc-runtime-context.js +0 -124
- package/src/host/daemon/super-agent-tools/helpers.js +0 -124
- package/src/host/daemon/super-agent-tools/tools/ask-questions.js +0 -32
- package/src/host/daemon/tool-call-parser.js +0 -2
- package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +0 -1
- package/src/interfaces/web/dist/assets/index-BV615I9p.js +0 -548
- package/src/interfaces/web/dist/assets/index-BV615I9p.js.map +0 -1
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-files.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-mcps.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-projects.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-file.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-files.js +0 -0
- /package/src/core/agent/{pseudo-tools.js → tools/pseudo-tools.js} +0 -0
- /package/src/core/agent/{tool-call-parser.js → tools/tool-call-parser.js} +0 -0
- /package/src/core/{parser.js → apc/parser.js} +0 -0
- /package/src/core/{apc-skill-sync.js → apc/skill-sync.js} +0 -0
- /package/src/core/{artifacts-store.js → stores/artifacts.js} +0 -0
- /package/src/{host/daemon → core/stores}/engine-sessions.js +0 -0
- /package/src/core/{session-store.js → stores/sessions.js} +0 -0
|
@@ -29,22 +29,26 @@
|
|
|
29
29
|
|
|
30
30
|
import fs from "node:fs";
|
|
31
31
|
import path from "node:path";
|
|
32
|
-
import { TELEGRAM_STATE_PATH, APX_HOME } from "
|
|
33
|
-
import { callEngine } from "
|
|
34
|
-
import { runSuperAgent, isSuperAgentEnabled } from "
|
|
35
|
-
import { stripThinking } from "
|
|
36
|
-
import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "
|
|
37
|
-
import { compactChannelIfNeeded } from "
|
|
38
|
-
import { readAgents } from "
|
|
39
|
-
import { buildAgentSystem } from "
|
|
40
|
-
import { transcribe as transcribeAudioFile } from "
|
|
41
|
-
import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "
|
|
42
|
-
import { registerSender, resolveAllowedTools } from "
|
|
43
|
-
import { buildRelationshipBlock } from "
|
|
44
|
-
import { getConfirmationStore as getConfirmStore } from "
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
import { TELEGRAM_STATE_PATH, APX_HOME } from "#core/config/index.js";
|
|
33
|
+
import { callEngine } from "#core/engines/index.js";
|
|
34
|
+
import { runSuperAgent, isSuperAgentEnabled } from "#core/agent/super-agent.js";
|
|
35
|
+
import { stripThinking } from "../../thinking.js";
|
|
36
|
+
import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "#core/stores/messages.js";
|
|
37
|
+
import { compactChannelIfNeeded } from "#core/memory/index.js";
|
|
38
|
+
import { readAgents } from "#core/apc/parser.js";
|
|
39
|
+
import { buildAgentSystem } from "#core/agent/build-agent-system.js";
|
|
40
|
+
import { transcribe as transcribeAudioFile } from "../../transcription.js";
|
|
41
|
+
import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
|
|
42
|
+
import { registerSender, resolveAllowedTools } from "#core/identity/telegram.js";
|
|
43
|
+
import { buildRelationshipBlock } from "#core/agent/index.js";
|
|
44
|
+
import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pending-store.js";
|
|
45
|
+
import { CHANNELS } from "#core/constants/channels.js";
|
|
46
|
+
import { tryResolveSkillCommand } from "#core/agent/skills/trigger.js";
|
|
47
|
+
import { isLikelyDuplicate } from "#core/util/text-similarity.js";
|
|
48
|
+
import { createTelegramConfirmAdapter } from "#core/confirmation/adapters/telegram.js";
|
|
49
|
+
import * as askFlow from "./ask.js";
|
|
50
|
+
|
|
51
|
+
// API_BASE re-imported from ./media.js below
|
|
48
52
|
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
49
53
|
|
|
50
54
|
// Build the channelMeta passed to the super-agent loop. The prompt template at
|
|
@@ -84,159 +88,9 @@ function buildTelegramMeta({ channelName, author, chatId, target, routeToAgent }
|
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* Send a photo to a Telegram chat.
|
|
91
|
-
* @param {string} token Bot token
|
|
92
|
-
* @param {string|number} chatId Telegram chat_id
|
|
93
|
-
* @param {string|Buffer} photo Absolute file path OR Buffer of image data
|
|
94
|
-
* @param {object} [opts]
|
|
95
|
-
* @param {string} [opts.caption]
|
|
96
|
-
* @param {string} [opts.parse_mode] "HTML" | "Markdown" | "MarkdownV2"
|
|
97
|
-
*/
|
|
98
|
-
export async function sendPhoto(token, chatId, photo, { caption, parse_mode } = {}) {
|
|
99
|
-
const url = `${API_BASE}/bot${token}/sendPhoto`;
|
|
100
|
-
const form = new FormData();
|
|
101
|
-
form.append("chat_id", String(chatId));
|
|
102
|
-
if (caption) form.append("caption", caption);
|
|
103
|
-
if (parse_mode) form.append("parse_mode", parse_mode);
|
|
104
|
-
|
|
105
|
-
if (typeof photo === "string" && photo.startsWith("http")) {
|
|
106
|
-
// Public URL — send as string
|
|
107
|
-
form.append("photo", photo);
|
|
108
|
-
} else {
|
|
109
|
-
// Local file path or Buffer
|
|
110
|
-
const buf = Buffer.isBuffer(photo) ? photo : fs.readFileSync(photo);
|
|
111
|
-
const name = typeof photo === "string" ? path.basename(photo) : "photo.jpg";
|
|
112
|
-
const blob = new Blob([buf], { type: name.endsWith(".png") ? "image/png" : "image/jpeg" });
|
|
113
|
-
form.append("photo", blob, name);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const res = await fetch(url, { method: "POST", body: form });
|
|
117
|
-
const json = await res.json();
|
|
118
|
-
if (!json.ok) throw new Error(`sendPhoto failed: ${json.description || res.status}`);
|
|
119
|
-
return json.result;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Send a voice message (OGG/Opus preferred by Telegram).
|
|
124
|
-
* @param {string} token
|
|
125
|
-
* @param {string|number} chatId
|
|
126
|
-
* @param {string|Buffer} audio Path or Buffer
|
|
127
|
-
* @param {object} [opts]
|
|
128
|
-
* @param {string} [opts.caption]
|
|
129
|
-
* @param {number} [opts.duration]
|
|
130
|
-
*/
|
|
131
|
-
export async function sendVoice(token, chatId, audio, { caption, duration } = {}) {
|
|
132
|
-
const url = `${API_BASE}/bot${token}/sendVoice`;
|
|
133
|
-
const form = new FormData();
|
|
134
|
-
form.append("chat_id", String(chatId));
|
|
135
|
-
if (caption) form.append("caption", caption);
|
|
136
|
-
if (duration) form.append("duration", String(duration));
|
|
137
|
-
|
|
138
|
-
const buf = Buffer.isBuffer(audio) ? audio : fs.readFileSync(audio);
|
|
139
|
-
const name = typeof audio === "string" ? path.basename(audio) : "voice.ogg";
|
|
140
|
-
const blob = new Blob([buf], { type: "audio/ogg" });
|
|
141
|
-
form.append("voice", blob, name);
|
|
142
|
-
|
|
143
|
-
const res = await fetch(url, { method: "POST", body: form });
|
|
144
|
-
const json = await res.json();
|
|
145
|
-
if (!json.ok) throw new Error(`sendVoice failed: ${json.description || res.status}`);
|
|
146
|
-
return json.result;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Send an audio file (MP3, M4A, etc — shown in Telegram music player).
|
|
151
|
-
* @param {string} token
|
|
152
|
-
* @param {string|number} chatId
|
|
153
|
-
* @param {string|Buffer} audio Path or Buffer
|
|
154
|
-
* @param {object} [opts]
|
|
155
|
-
* @param {string} [opts.caption]
|
|
156
|
-
* @param {string} [opts.title]
|
|
157
|
-
* @param {string} [opts.performer]
|
|
158
|
-
*/
|
|
159
|
-
/**
|
|
160
|
-
* Send any file as a Telegram document (PDF, zip, txt, etc).
|
|
161
|
-
* @param {string} token
|
|
162
|
-
* @param {string|number} chatId
|
|
163
|
-
* @param {string|Buffer} document Path or Buffer of document data
|
|
164
|
-
* @param {object} [opts]
|
|
165
|
-
* @param {string} [opts.caption]
|
|
166
|
-
* @param {string} [opts.filename] override filename for Buffer input
|
|
167
|
-
* @param {string} [opts.mime_type]
|
|
168
|
-
*/
|
|
169
|
-
export async function sendDocument(token, chatId, document, { caption, filename, mime_type } = {}) {
|
|
170
|
-
const url = `${API_BASE}/bot${token}/sendDocument`;
|
|
171
|
-
const form = new FormData();
|
|
172
|
-
form.append("chat_id", String(chatId));
|
|
173
|
-
if (caption) form.append("caption", caption);
|
|
174
|
-
|
|
175
|
-
// URL string → let Telegram fetch it
|
|
176
|
-
if (typeof document === "string" && /^https?:\/\//.test(document)) {
|
|
177
|
-
form.append("document", document);
|
|
178
|
-
} else {
|
|
179
|
-
const buf = Buffer.isBuffer(document) ? document : fs.readFileSync(document);
|
|
180
|
-
const name =
|
|
181
|
-
filename ||
|
|
182
|
-
(typeof document === "string" ? path.basename(document) : "document.bin");
|
|
183
|
-
const blob = new Blob([buf], { type: mime_type || "application/octet-stream" });
|
|
184
|
-
form.append("document", blob, name);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const res = await fetch(url, { method: "POST", body: form });
|
|
188
|
-
const json = await res.json();
|
|
189
|
-
if (!json.ok) throw new Error(`sendDocument failed: ${json.description || res.status}`);
|
|
190
|
-
return json.result;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export async function sendAudio(token, chatId, audio, { caption, title, performer } = {}) {
|
|
194
|
-
const url = `${API_BASE}/bot${token}/sendAudio`;
|
|
195
|
-
const form = new FormData();
|
|
196
|
-
form.append("chat_id", String(chatId));
|
|
197
|
-
if (caption) form.append("caption", caption);
|
|
198
|
-
if (title) form.append("title", title);
|
|
199
|
-
if (performer) form.append("performer", performer);
|
|
200
|
-
|
|
201
|
-
const buf = Buffer.isBuffer(audio) ? audio : fs.readFileSync(audio);
|
|
202
|
-
const name = typeof audio === "string" ? path.basename(audio) : "audio.mp3";
|
|
203
|
-
const blob = new Blob([buf], { type: "audio/mpeg" });
|
|
204
|
-
form.append("audio", blob, name);
|
|
205
|
-
|
|
206
|
-
const res = await fetch(url, { method: "POST", body: form });
|
|
207
|
-
const json = await res.json();
|
|
208
|
-
if (!json.ok) throw new Error(`sendAudio failed: ${json.description || res.status}`);
|
|
209
|
-
return json.result;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Audio transcription is delegated to the central dispatcher
|
|
213
|
-
// (../transcription.js) which handles local (faster-whisper via Python) +
|
|
214
|
-
// OpenAI cloud fallback. See that module for config keys.
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Download a file from Telegram servers.
|
|
218
|
-
* Returns the local file path where it was saved.
|
|
219
|
-
*/
|
|
220
|
-
async function downloadTelegramFile(token, fileId, destDir) {
|
|
221
|
-
// Step 1: get file path from Telegram
|
|
222
|
-
const infoRes = await fetch(`${API_BASE}/bot${token}/getFile?file_id=${fileId}`);
|
|
223
|
-
const infoJson = await infoRes.json();
|
|
224
|
-
if (!infoJson.ok) throw new Error(`getFile failed: ${infoJson.description}`);
|
|
225
|
-
const filePath = infoJson.result.file_path; // e.g. "photos/file_123.jpg"
|
|
226
|
-
const ext = path.extname(filePath) || ".jpg";
|
|
227
|
-
const fileName = `tg_${fileId.slice(-8)}_${Date.now()}${ext}`;
|
|
228
|
-
const localPath = path.join(destDir, fileName);
|
|
229
|
-
|
|
230
|
-
// Step 2: download
|
|
231
|
-
const dlRes = await fetch(`${API_BASE}/file/bot${token}/${filePath}`);
|
|
232
|
-
if (!dlRes.ok) throw new Error(`download failed: ${dlRes.status}`);
|
|
233
|
-
const buf = Buffer.from(await dlRes.arrayBuffer());
|
|
234
|
-
fs.writeFileSync(localPath, buf);
|
|
235
|
-
return localPath;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// ---------- shared state ----------------------------------------------------
|
|
239
|
-
|
|
91
|
+
// Media sending helpers moved to ./media.js.
|
|
92
|
+
import { sendPhoto, sendVoice, sendDocument, sendAudio, downloadTelegramFile, API_BASE } from "./media.js";
|
|
93
|
+
export { sendPhoto, sendVoice, sendDocument, sendAudio };
|
|
240
94
|
function loadState() {
|
|
241
95
|
if (!fs.existsSync(TELEGRAM_STATE_PATH)) return { channels: {} };
|
|
242
96
|
try {
|
|
@@ -484,7 +338,7 @@ class ChannelPoller {
|
|
|
484
338
|
const localPath = await downloadTelegramFile(token, bestPhoto.file_id, mediaDir);
|
|
485
339
|
this.log(`telegram[${this.channel.name}] photo saved: ${localPath}`);
|
|
486
340
|
appendGlobalMessage({
|
|
487
|
-
channel:
|
|
341
|
+
channel: CHANNELS.TELEGRAM,
|
|
488
342
|
direction: "in",
|
|
489
343
|
type: "photo",
|
|
490
344
|
actor_id: msg.from?.id ? String(msg.from.id) : author,
|
|
@@ -550,7 +404,7 @@ class ChannelPoller {
|
|
|
550
404
|
: `[audio] (transcription unavailable${transcribeError ? ": " + transcribeError : ""})`;
|
|
551
405
|
|
|
552
406
|
appendGlobalMessage({
|
|
553
|
-
channel:
|
|
407
|
+
channel: CHANNELS.TELEGRAM,
|
|
554
408
|
direction: "in",
|
|
555
409
|
type: "audio",
|
|
556
410
|
actor_id: msg.from?.id ? String(msg.from.id) : author,
|
|
@@ -577,6 +431,30 @@ class ChannelPoller {
|
|
|
577
431
|
text = text ? `${audioBody}\n${text}` : audioBody;
|
|
578
432
|
}
|
|
579
433
|
|
|
434
|
+
// If there's a pending ask_questions flow for this chat AND the current
|
|
435
|
+
// question is free-text, treat this message as the answer rather than a
|
|
436
|
+
// brand-new turn. Returns true when the message was consumed.
|
|
437
|
+
if (chat_id && text && await this._maybeConsumeAskTextAnswer({ chat_id, text })) {
|
|
438
|
+
// Still log the inbound so the chat history records what the user said.
|
|
439
|
+
appendGlobalMessage({
|
|
440
|
+
channel: CHANNELS.TELEGRAM,
|
|
441
|
+
direction: "in",
|
|
442
|
+
type: "user",
|
|
443
|
+
actor_id: msg.from?.id ? String(msg.from.id) : author,
|
|
444
|
+
external_id: String(u.update_id),
|
|
445
|
+
author,
|
|
446
|
+
body: text,
|
|
447
|
+
meta: {
|
|
448
|
+
chat_id,
|
|
449
|
+
user_id: msg.from?.id || null,
|
|
450
|
+
message_id: msg.message_id,
|
|
451
|
+
tg_channel: this.channel.name,
|
|
452
|
+
ask_answer: true,
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
580
458
|
// /reset or /new wipes the rolling context for this chat. We just
|
|
581
459
|
// remember a marker timestamp; subsequent inbounds will only consider
|
|
582
460
|
// history newer than this. Implemented by writing a synthetic message
|
|
@@ -598,7 +476,7 @@ class ChannelPoller {
|
|
|
598
476
|
// the next turn reads a [RESUMEN COMPACTADO] instead of raw history. Never
|
|
599
477
|
// awaited — adds zero latency to this reply, degrades gracefully.
|
|
600
478
|
compactChannelIfNeeded({
|
|
601
|
-
channel:
|
|
479
|
+
channel: CHANNELS.TELEGRAM,
|
|
602
480
|
chat_id,
|
|
603
481
|
config: this.globalConfig,
|
|
604
482
|
log: this.log,
|
|
@@ -622,7 +500,7 @@ class ChannelPoller {
|
|
|
622
500
|
|
|
623
501
|
// Always log inbound to global store (~/.apx/messages/telegram/)
|
|
624
502
|
appendGlobalMessage({
|
|
625
|
-
channel:
|
|
503
|
+
channel: CHANNELS.TELEGRAM,
|
|
626
504
|
direction: "in",
|
|
627
505
|
type: "user",
|
|
628
506
|
actor_id: msg.from?.id ? String(msg.from.id) : author,
|
|
@@ -654,7 +532,7 @@ class ChannelPoller {
|
|
|
654
532
|
const ack = "Done, context cleared. Starting fresh. What do you need?";
|
|
655
533
|
await this._send({ chat_id, text: ack });
|
|
656
534
|
appendGlobalMessage({
|
|
657
|
-
channel:
|
|
535
|
+
channel: CHANNELS.TELEGRAM,
|
|
658
536
|
direction: "out",
|
|
659
537
|
type: "agent",
|
|
660
538
|
actor_id: SUPERAGENT_ACTOR_ID,
|
|
@@ -678,7 +556,7 @@ class ChannelPoller {
|
|
|
678
556
|
let replyActorId; // stable id: super_agent | agent slug
|
|
679
557
|
let replyKind; // actor_kind: superagent | agent
|
|
680
558
|
const projectCfg = target.config || this.globalConfig;
|
|
681
|
-
// Display name for the super-agent persona on this channel (
|
|
559
|
+
// Display name for the super-agent persona on this channel (from identity.json).
|
|
682
560
|
const agentDisplay = resolveAgentName(this.globalConfig);
|
|
683
561
|
|
|
684
562
|
// Try the project's chosen agent first (skipped if the legacy
|
|
@@ -752,7 +630,7 @@ class ChannelPoller {
|
|
|
752
630
|
const heads = headsUpPhrase();
|
|
753
631
|
await this._send({ chat_id, text: heads });
|
|
754
632
|
appendGlobalMessage({
|
|
755
|
-
channel:
|
|
633
|
+
channel: CHANNELS.TELEGRAM,
|
|
756
634
|
direction: "out",
|
|
757
635
|
type: "agent",
|
|
758
636
|
actor_id: SUPERAGENT_ACTOR_ID,
|
|
@@ -767,11 +645,17 @@ class ChannelPoller {
|
|
|
767
645
|
if (ev.type === "assistant_text" && ev.text) {
|
|
768
646
|
const piece = stripThinking(ev.text).trim();
|
|
769
647
|
if (!piece) return;
|
|
648
|
+
// Skip post-tool segments that just restate the pre-tool intro —
|
|
649
|
+
// weaker models (gemini-flash et al.) frequently paraphrase the
|
|
650
|
+
// same content twice within a single turn.
|
|
651
|
+
if (lastStreamedText && isLikelyDuplicate(piece, lastStreamedText)) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
770
654
|
await this._send({ chat_id, text: piece });
|
|
771
655
|
lastStreamedText = piece;
|
|
772
656
|
streamedCount += 1;
|
|
773
657
|
appendGlobalMessage({
|
|
774
|
-
channel:
|
|
658
|
+
channel: CHANNELS.TELEGRAM,
|
|
775
659
|
direction: "out",
|
|
776
660
|
type: "agent",
|
|
777
661
|
actor_id: SUPERAGENT_ACTOR_ID,
|
|
@@ -791,7 +675,7 @@ class ChannelPoller {
|
|
|
791
675
|
// Logged for the audit trail / other channels — NOT sent to Telegram.
|
|
792
676
|
const t = ev.trace;
|
|
793
677
|
appendGlobalMessage({
|
|
794
|
-
channel:
|
|
678
|
+
channel: CHANNELS.TELEGRAM,
|
|
795
679
|
direction: "out",
|
|
796
680
|
type: "tool",
|
|
797
681
|
actor_id: t.tool,
|
|
@@ -821,17 +705,24 @@ class ChannelPoller {
|
|
|
821
705
|
pendingStore: getConfirmStore(),
|
|
822
706
|
});
|
|
823
707
|
|
|
708
|
+
// `/slug ...` shortcut: load the matching skill body into contextNote
|
|
709
|
+
// and strip the prefix from the user prompt before sending to the loop.
|
|
710
|
+
const slashed = tryResolveSkillCommand(text, { projectPath: target?.path });
|
|
711
|
+
const slashedPrompt = slashed.handled ? slashed.prompt : text;
|
|
712
|
+
const slashedContextNote = slashed.handled ? slashed.contextNote : "";
|
|
713
|
+
|
|
824
714
|
try {
|
|
825
715
|
const sa = await runSuperAgent({
|
|
826
716
|
globalConfig: this.globalConfig,
|
|
827
717
|
projects: this.projects,
|
|
828
718
|
plugins: this.plugins,
|
|
829
719
|
registries: this.registries,
|
|
830
|
-
prompt:
|
|
720
|
+
prompt: slashedPrompt,
|
|
831
721
|
previousMessages,
|
|
832
|
-
channel:
|
|
722
|
+
channel: CHANNELS.TELEGRAM,
|
|
833
723
|
relationshipBlock,
|
|
834
724
|
allowedTools,
|
|
725
|
+
contextNote: slashedContextNote || undefined,
|
|
835
726
|
channelMeta: buildTelegramMeta({
|
|
836
727
|
channelName: this.channel.name,
|
|
837
728
|
author,
|
|
@@ -848,6 +739,35 @@ class ChannelPoller {
|
|
|
848
739
|
replyActorId = SUPERAGENT_ACTOR_ID;
|
|
849
740
|
replyKind = "superagent";
|
|
850
741
|
saUsage = sa.usage;
|
|
742
|
+
|
|
743
|
+
// ── ask_questions integration ────────────────────────────────────
|
|
744
|
+
// If the super-agent ended this turn by calling ask_questions, hand
|
|
745
|
+
// off to the inline-keyboard flow instead of sending the bare
|
|
746
|
+
// assistant text. The flow keeps state per chat_id and re-runs the
|
|
747
|
+
// super-agent once every answer is collected.
|
|
748
|
+
const askQuestions = askFlow.extractAskQuestionsFromTrace(sa.trace);
|
|
749
|
+
if (askQuestions && chat_id) {
|
|
750
|
+
if (chat_id) this.activeRequests.delete(chat_id);
|
|
751
|
+
stopTyping();
|
|
752
|
+
try {
|
|
753
|
+
await this._startAskFlow({
|
|
754
|
+
chat_id,
|
|
755
|
+
projectId: target?.id,
|
|
756
|
+
authorId: msg.from?.id,
|
|
757
|
+
questions: askQuestions,
|
|
758
|
+
author,
|
|
759
|
+
agentDisplay,
|
|
760
|
+
relationshipBlock,
|
|
761
|
+
allowedTools,
|
|
762
|
+
target,
|
|
763
|
+
sender,
|
|
764
|
+
update_id: u.update_id,
|
|
765
|
+
});
|
|
766
|
+
} catch (e) {
|
|
767
|
+
this.log(`telegram[${this.channel.name}] ask flow start failed: ${e.message}`);
|
|
768
|
+
}
|
|
769
|
+
return; // The reply for this turn IS the ask flow.
|
|
770
|
+
}
|
|
851
771
|
} catch (e) {
|
|
852
772
|
if (abortCtrl.signal.aborted) {
|
|
853
773
|
// A newer message superseded this one. Whatever streamed so far is
|
|
@@ -878,8 +798,13 @@ class ChannelPoller {
|
|
|
878
798
|
// turn isn't silently empty.
|
|
879
799
|
const finalClean = replyText ? stripThinking(replyText).trim() : "";
|
|
880
800
|
let toSend = "";
|
|
881
|
-
|
|
882
|
-
|
|
801
|
+
// Fuzzy dedupe against the last streamed segment: catches paraphrases
|
|
802
|
+
// (Jaccard ≥ 0.4 or short-mostly-inside-long), not just exact matches.
|
|
803
|
+
if (finalClean && !isLikelyDuplicate(finalClean, lastStreamedText) && finalClean !== lastStreamedText) {
|
|
804
|
+
toSend = finalClean;
|
|
805
|
+
} else if (!finalClean && streamedCount === 0) {
|
|
806
|
+
toSend = "Listo.";
|
|
807
|
+
}
|
|
883
808
|
|
|
884
809
|
stopTyping();
|
|
885
810
|
if (!toSend) return; // everything was already streamed — nothing left to send
|
|
@@ -895,7 +820,7 @@ class ChannelPoller {
|
|
|
895
820
|
if (replyText && stripThinking(replyText) !== replyText) meta.thinking_stripped = true;
|
|
896
821
|
if (saUsage) meta.usage = saUsage;
|
|
897
822
|
appendGlobalMessage({
|
|
898
|
-
channel:
|
|
823
|
+
channel: CHANNELS.TELEGRAM,
|
|
899
824
|
direction: "out",
|
|
900
825
|
type: "agent",
|
|
901
826
|
actor_id: replyActorId || SUPERAGENT_ACTOR_ID,
|
|
@@ -908,7 +833,7 @@ class ChannelPoller {
|
|
|
908
833
|
} catch (e) {
|
|
909
834
|
this.log(`telegram[${this.channel.name}] send-back error: ${e.message}`);
|
|
910
835
|
appendGlobalMessage({
|
|
911
|
-
channel:
|
|
836
|
+
channel: CHANNELS.TELEGRAM,
|
|
912
837
|
direction: "out",
|
|
913
838
|
type: "agent",
|
|
914
839
|
actor_id: replyActorId || SUPERAGENT_ACTOR_ID,
|
|
@@ -928,6 +853,14 @@ class ChannelPoller {
|
|
|
928
853
|
}
|
|
929
854
|
|
|
930
855
|
async _handleCallbackQuery(callbackQuery) {
|
|
856
|
+
// Route ask_questions button presses before the confirmation adapter —
|
|
857
|
+
// both use `apx:<verb>:...` namespacing but ask owns its own state.
|
|
858
|
+
const data = callbackQuery.data || "";
|
|
859
|
+
if (data.startsWith("apx:ask:")) {
|
|
860
|
+
await this._handleAskCallback(callbackQuery);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
931
864
|
const adapter = createTelegramConfirmAdapter({
|
|
932
865
|
token: resolveBotToken(this.channel),
|
|
933
866
|
chatId: callbackQuery.message?.chat?.id,
|
|
@@ -939,6 +872,230 @@ class ChannelPoller {
|
|
|
939
872
|
}
|
|
940
873
|
}
|
|
941
874
|
|
|
875
|
+
// ── ask_questions: state-machine helpers ───────────────────────────────
|
|
876
|
+
// The flow lives in telegram-ask.js; this class owns the I/O (sending
|
|
877
|
+
// messages, editing keyboards, re-entering the super-agent loop with the
|
|
878
|
+
// compiled answer once the flow finishes).
|
|
879
|
+
|
|
880
|
+
async _renderQuestion(state) {
|
|
881
|
+
const text = askFlow.formatQuestionText(state);
|
|
882
|
+
const reply_markup = askFlow.buildKeyboard(state);
|
|
883
|
+
// If we already have a message for the previous question, leave its
|
|
884
|
+
// keyboard wiped — we draw a fresh message per question for clearer
|
|
885
|
+
// history in the chat (the question text stays as a record).
|
|
886
|
+
if (state.messageId) {
|
|
887
|
+
try {
|
|
888
|
+
await this._editKeyboard({
|
|
889
|
+
chat_id: state.chatId,
|
|
890
|
+
message_id: state.messageId,
|
|
891
|
+
reply_markup: { inline_keyboard: [] },
|
|
892
|
+
});
|
|
893
|
+
} catch { /* best-effort */ }
|
|
894
|
+
}
|
|
895
|
+
const sent = await this._send({
|
|
896
|
+
chat_id: state.chatId,
|
|
897
|
+
text,
|
|
898
|
+
reply_markup,
|
|
899
|
+
parse_mode: "Markdown",
|
|
900
|
+
});
|
|
901
|
+
state.messageId = sent?.message_id || null;
|
|
902
|
+
askFlow.saveState(state.chatId, state);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Kick off a brand-new ask flow after the super-agent called ask_questions.
|
|
906
|
+
// The flow's `resume` callback captures the per-turn context (sender,
|
|
907
|
+
// relationship, project) so when the compiled answer arrives we can run
|
|
908
|
+
// another super-agent turn without retyping all the inputs.
|
|
909
|
+
async _startAskFlow(ctx) {
|
|
910
|
+
const state = askFlow.startFlow({
|
|
911
|
+
chatId: ctx.chat_id,
|
|
912
|
+
projectId: ctx.projectId,
|
|
913
|
+
authorId: ctx.authorId,
|
|
914
|
+
questions: ctx.questions,
|
|
915
|
+
resume: async (compiled) => {
|
|
916
|
+
await this._runResumedTurn({ ...ctx, compiled });
|
|
917
|
+
},
|
|
918
|
+
});
|
|
919
|
+
await this._renderQuestion(state);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Apply an inline-keyboard press, then react: redraw, advance, or finish.
|
|
923
|
+
async _handleAskCallback(callbackQuery) {
|
|
924
|
+
const chatId = callbackQuery.message?.chat?.id;
|
|
925
|
+
if (!chatId) return;
|
|
926
|
+
const result = askFlow.applyCallback(chatId, callbackQuery.data || "");
|
|
927
|
+
// Ack the press regardless — keeps the spinner from hanging client-side.
|
|
928
|
+
await this._answerCallback({ callback_query_id: callbackQuery.id });
|
|
929
|
+
if (!result) return; // stale or unknown — adapter already ack'd.
|
|
930
|
+
|
|
931
|
+
if (result.action === "redraw") {
|
|
932
|
+
// Multi-select toggle: just refresh the keyboard on the SAME message.
|
|
933
|
+
try {
|
|
934
|
+
await this._editKeyboard({
|
|
935
|
+
chat_id: chatId,
|
|
936
|
+
message_id: callbackQuery.message?.message_id,
|
|
937
|
+
reply_markup: askFlow.buildKeyboard(result.state),
|
|
938
|
+
});
|
|
939
|
+
} catch (e) {
|
|
940
|
+
this.log(`telegram[${this.channel.name}] redraw failed: ${e.message}`);
|
|
941
|
+
}
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (result.action === "advance") {
|
|
945
|
+
await this._renderQuestion(result.state);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (result.action === "cancel") {
|
|
949
|
+
try {
|
|
950
|
+
await this._editKeyboard({
|
|
951
|
+
chat_id: chatId,
|
|
952
|
+
message_id: callbackQuery.message?.message_id,
|
|
953
|
+
reply_markup: { inline_keyboard: [] },
|
|
954
|
+
});
|
|
955
|
+
await this._send({ chat_id: chatId, text: "Pregunta cancelada." });
|
|
956
|
+
} catch { /* best-effort */ }
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (result.action === "done") {
|
|
960
|
+
try {
|
|
961
|
+
await this._editKeyboard({
|
|
962
|
+
chat_id: chatId,
|
|
963
|
+
message_id: callbackQuery.message?.message_id,
|
|
964
|
+
reply_markup: { inline_keyboard: [] },
|
|
965
|
+
});
|
|
966
|
+
} catch { /* best-effort */ }
|
|
967
|
+
// Feed the compiled answer back as a synthetic user turn.
|
|
968
|
+
if (typeof result.state.resume === "function") {
|
|
969
|
+
await result.state.resume(result.compiled);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Apply a free-text user reply when there's a pending free-text question.
|
|
975
|
+
// Returns true iff the message was consumed by the ask flow (so the normal
|
|
976
|
+
// super-agent path should be skipped for this update).
|
|
977
|
+
async _maybeConsumeAskTextAnswer({ chat_id, text }) {
|
|
978
|
+
if (!chat_id || !text) return false;
|
|
979
|
+
if (!askFlow.hasPendingFreeText(chat_id)) return false;
|
|
980
|
+
const state = askFlow.applyTextAnswer(chat_id, text);
|
|
981
|
+
if (!state) return false;
|
|
982
|
+
// Advance: emit a synthetic "next" to move past this question.
|
|
983
|
+
const next = askFlow.applyCallback(
|
|
984
|
+
chat_id,
|
|
985
|
+
`apx:ask:${state.correlationId}:next`,
|
|
986
|
+
);
|
|
987
|
+
if (!next) return true;
|
|
988
|
+
if (next.action === "advance") {
|
|
989
|
+
await this._renderQuestion(next.state);
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
if (next.action === "done") {
|
|
993
|
+
if (typeof next.state.resume === "function") {
|
|
994
|
+
await next.state.resume(next.compiled);
|
|
995
|
+
}
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Run a follow-up super-agent turn with the compiled answers as the user
|
|
1002
|
+
// prompt. Mirrors the post-runSuperAgent reply path in _handleUpdate but
|
|
1003
|
+
// skipped of the photo/audio/reset preamble. Re-enters the ask flow if the
|
|
1004
|
+
// model decides to ask again.
|
|
1005
|
+
async _runResumedTurn(ctx) {
|
|
1006
|
+
const { chat_id, compiled, target, relationshipBlock, allowedTools, author, agentDisplay, update_id, sender, authorId } = ctx;
|
|
1007
|
+
if (!chat_id) return;
|
|
1008
|
+
// Log the synthetic user message so getRecentTelegramTurnsFromFs picks
|
|
1009
|
+
// it up on the NEXT inbound. Mirrors how a normal text reply would be
|
|
1010
|
+
// recorded.
|
|
1011
|
+
appendGlobalMessage({
|
|
1012
|
+
channel: CHANNELS.TELEGRAM,
|
|
1013
|
+
direction: "in",
|
|
1014
|
+
type: "user",
|
|
1015
|
+
actor_id: authorId ? String(authorId) : (author || "ask_flow"),
|
|
1016
|
+
external_id: `ask-${Date.now()}`,
|
|
1017
|
+
author: author || "user",
|
|
1018
|
+
body: compiled,
|
|
1019
|
+
meta: {
|
|
1020
|
+
chat_id,
|
|
1021
|
+
user_id: authorId || null,
|
|
1022
|
+
tg_channel: this.channel.name,
|
|
1023
|
+
ask_flow: true,
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
const previousMessages = getRecentTelegramTurnsFromFs({
|
|
1028
|
+
chat_id,
|
|
1029
|
+
keepRecent: 40,
|
|
1030
|
+
max_age_hours: 24,
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
const stopTyping = this._startTyping(chat_id);
|
|
1034
|
+
try {
|
|
1035
|
+
const sa = await runSuperAgent({
|
|
1036
|
+
globalConfig: this.globalConfig,
|
|
1037
|
+
projects: this.projects,
|
|
1038
|
+
plugins: this.plugins,
|
|
1039
|
+
registries: this.registries,
|
|
1040
|
+
prompt: compiled,
|
|
1041
|
+
previousMessages,
|
|
1042
|
+
channel: CHANNELS.TELEGRAM,
|
|
1043
|
+
relationshipBlock,
|
|
1044
|
+
allowedTools,
|
|
1045
|
+
channelMeta: { channel: CHANNELS.TELEGRAM, chat_id, author, route_to_agent: this.channel.route_to_agent },
|
|
1046
|
+
});
|
|
1047
|
+
stopTyping();
|
|
1048
|
+
|
|
1049
|
+
// Did the model ask again? Restart the flow instead of replying.
|
|
1050
|
+
const followupAsk = askFlow.extractAskQuestionsFromTrace(sa.trace);
|
|
1051
|
+
if (followupAsk) {
|
|
1052
|
+
await this._startAskFlow({
|
|
1053
|
+
chat_id,
|
|
1054
|
+
projectId: target?.id,
|
|
1055
|
+
authorId,
|
|
1056
|
+
questions: followupAsk,
|
|
1057
|
+
author,
|
|
1058
|
+
agentDisplay,
|
|
1059
|
+
relationshipBlock,
|
|
1060
|
+
allowedTools,
|
|
1061
|
+
target,
|
|
1062
|
+
sender,
|
|
1063
|
+
update_id,
|
|
1064
|
+
});
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const replyText = sa.text ? stripThinking(sa.text).trim() : "";
|
|
1069
|
+
if (replyText) {
|
|
1070
|
+
await this._send({ chat_id, text: replyText });
|
|
1071
|
+
appendGlobalMessage({
|
|
1072
|
+
channel: CHANNELS.TELEGRAM,
|
|
1073
|
+
direction: "out",
|
|
1074
|
+
type: "agent",
|
|
1075
|
+
actor_id: SUPERAGENT_ACTOR_ID,
|
|
1076
|
+
actor_kind: "superagent",
|
|
1077
|
+
agent_slug: SUPERAGENT_ACTOR_ID,
|
|
1078
|
+
author: sa.name || agentDisplay,
|
|
1079
|
+
body: replyText,
|
|
1080
|
+
meta: {
|
|
1081
|
+
chat_id,
|
|
1082
|
+
tg_channel: this.channel.name,
|
|
1083
|
+
in_reply_to: update_id,
|
|
1084
|
+
final: true,
|
|
1085
|
+
ask_resume: true,
|
|
1086
|
+
...(sa.usage ? { usage: sa.usage } : {}),
|
|
1087
|
+
},
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
} catch (e) {
|
|
1091
|
+
stopTyping();
|
|
1092
|
+
this.log(`telegram[${this.channel.name}] ask resume failed: ${e.message}`);
|
|
1093
|
+
try {
|
|
1094
|
+
await this._send({ chat_id, text: `⚠️ Error procesando tus respuestas (${e.message}).` });
|
|
1095
|
+
} catch { /* best-effort */ }
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
942
1099
|
// Show "typing..." indicator in the chat. Telegram clears it automatically
|
|
943
1100
|
// after 5 seconds, so call this every ~4s while a long operation is going.
|
|
944
1101
|
async _typing(chat_id) {
|
|
@@ -971,22 +1128,64 @@ class ChannelPoller {
|
|
|
971
1128
|
return () => { stopped = true; };
|
|
972
1129
|
}
|
|
973
1130
|
|
|
974
|
-
async _send({ chat_id, text }) {
|
|
1131
|
+
async _send({ chat_id, text, reply_markup, parse_mode }) {
|
|
975
1132
|
const token = resolveBotToken(this.channel);
|
|
976
1133
|
if (!token) throw new Error(`channel ${this.channel.name}: no bot_token`);
|
|
977
1134
|
const target = chat_id || resolveChatId(this.channel);
|
|
978
1135
|
if (!target) throw new Error(`channel ${this.channel.name}: no chat_id`);
|
|
979
1136
|
const url = `${API_BASE}/bot${token}/sendMessage`;
|
|
1137
|
+
const body = { chat_id: target, text };
|
|
1138
|
+
if (reply_markup) body.reply_markup = reply_markup;
|
|
1139
|
+
if (parse_mode) body.parse_mode = parse_mode;
|
|
980
1140
|
const res = await fetch(url, {
|
|
981
1141
|
method: "POST",
|
|
982
1142
|
headers: { "content-type": "application/json" },
|
|
983
|
-
body: JSON.stringify(
|
|
1143
|
+
body: JSON.stringify(body),
|
|
984
1144
|
});
|
|
985
1145
|
const json = await res.json();
|
|
986
1146
|
if (!json.ok) throw new Error(json.description || `send failed (${res.status})`);
|
|
987
1147
|
return json.result;
|
|
988
1148
|
}
|
|
989
1149
|
|
|
1150
|
+
// Replace just the inline keyboard on a previously-sent message (used to
|
|
1151
|
+
// refresh after a multi-select toggle, or to wipe buttons once the flow
|
|
1152
|
+
// has moved on). Best-effort: failures are logged but don't break the flow.
|
|
1153
|
+
async _editKeyboard({ chat_id, message_id, reply_markup }) {
|
|
1154
|
+
const token = resolveBotToken(this.channel);
|
|
1155
|
+
if (!token) return;
|
|
1156
|
+
try {
|
|
1157
|
+
const url = `${API_BASE}/bot${token}/editMessageReplyMarkup`;
|
|
1158
|
+
const body = { chat_id, message_id };
|
|
1159
|
+
if (reply_markup) body.reply_markup = reply_markup;
|
|
1160
|
+
await fetch(url, {
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
headers: { "content-type": "application/json" },
|
|
1163
|
+
body: JSON.stringify(body),
|
|
1164
|
+
});
|
|
1165
|
+
} catch (e) {
|
|
1166
|
+
this.log(`telegram[${this.channel.name}] editMessageReplyMarkup failed: ${e.message}`);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// Acknowledge a callback button press so the user's Telegram client clears
|
|
1171
|
+
// the spinner on the tapped button. Optional `text` shows a small toast.
|
|
1172
|
+
async _answerCallback({ callback_query_id, text }) {
|
|
1173
|
+
const token = resolveBotToken(this.channel);
|
|
1174
|
+
if (!token) return;
|
|
1175
|
+
try {
|
|
1176
|
+
const url = `${API_BASE}/bot${token}/answerCallbackQuery`;
|
|
1177
|
+
const body = { callback_query_id };
|
|
1178
|
+
if (text) body.text = text;
|
|
1179
|
+
await fetch(url, {
|
|
1180
|
+
method: "POST",
|
|
1181
|
+
headers: { "content-type": "application/json" },
|
|
1182
|
+
body: JSON.stringify(body),
|
|
1183
|
+
});
|
|
1184
|
+
} catch (e) {
|
|
1185
|
+
this.log(`telegram[${this.channel.name}] answerCallbackQuery failed: ${e.message}`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
990
1189
|
/** Send a photo via this channel */
|
|
991
1190
|
async _sendPhoto({ chat_id, photo, caption, parse_mode }) {
|
|
992
1191
|
const token = resolveBotToken(this.channel);
|
|
@@ -1073,7 +1272,7 @@ export default {
|
|
|
1073
1272
|
if (!p) throw new Error("no telegram channel available");
|
|
1074
1273
|
const result = await p._send({ chat_id, text });
|
|
1075
1274
|
appendGlobalMessage({
|
|
1076
|
-
channel:
|
|
1275
|
+
channel: CHANNELS.TELEGRAM,
|
|
1077
1276
|
direction: "out",
|
|
1078
1277
|
type: "agent",
|
|
1079
1278
|
actor_id: SUPERAGENT_ACTOR_ID,
|
|
@@ -1103,7 +1302,7 @@ export default {
|
|
|
1103
1302
|
if (!p) throw new Error("no telegram channel available");
|
|
1104
1303
|
const result = await p._sendPhoto({ chat_id, photo, caption, parse_mode });
|
|
1105
1304
|
appendGlobalMessage({
|
|
1106
|
-
channel:
|
|
1305
|
+
channel: CHANNELS.TELEGRAM,
|
|
1107
1306
|
direction: "out",
|
|
1108
1307
|
type: "photo",
|
|
1109
1308
|
actor_id: SUPERAGENT_ACTOR_ID,
|
|
@@ -1127,7 +1326,7 @@ export default {
|
|
|
1127
1326
|
if (!p) throw new Error("no telegram channel available");
|
|
1128
1327
|
const result = await p._sendVoice({ chat_id, audio, caption, duration });
|
|
1129
1328
|
appendGlobalMessage({
|
|
1130
|
-
channel:
|
|
1329
|
+
channel: CHANNELS.TELEGRAM,
|
|
1131
1330
|
direction: "out",
|
|
1132
1331
|
type: "voice",
|
|
1133
1332
|
actor_id: SUPERAGENT_ACTOR_ID,
|
|
@@ -1151,7 +1350,7 @@ export default {
|
|
|
1151
1350
|
if (!p) throw new Error("no telegram channel available");
|
|
1152
1351
|
const result = await p._sendDocument({ chat_id, document, caption, filename, mime_type });
|
|
1153
1352
|
appendGlobalMessage({
|
|
1154
|
-
channel:
|
|
1353
|
+
channel: CHANNELS.TELEGRAM,
|
|
1155
1354
|
direction: "out",
|
|
1156
1355
|
type: "document",
|
|
1157
1356
|
actor_id: SUPERAGENT_ACTOR_ID,
|
|
@@ -1175,7 +1374,7 @@ export default {
|
|
|
1175
1374
|
if (!p) throw new Error("no telegram channel available");
|
|
1176
1375
|
const result = await p._sendAudio({ chat_id, audio, caption, title, performer });
|
|
1177
1376
|
appendGlobalMessage({
|
|
1178
|
-
channel:
|
|
1377
|
+
channel: CHANNELS.TELEGRAM,
|
|
1179
1378
|
direction: "out",
|
|
1180
1379
|
type: "audio",
|
|
1181
1380
|
actor_id: SUPERAGENT_ACTOR_ID,
|