daemora 1.0.3 → 1.0.5
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/LICENSE +663 -0
- package/README.md +69 -19
- package/SOUL.md +25 -24
- package/daemora-ui/README.md +11 -0
- package/package.json +12 -2
- package/skills/api-development.md +35 -0
- package/skills/artifacts-builder/SKILL.md +74 -0
- package/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
- package/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/skills/brand-guidelines.md +73 -0
- package/skills/browser.md +77 -0
- package/skills/changelog-generator.md +104 -0
- package/skills/coding.md +26 -10
- package/skills/content-research-writer.md +538 -0
- package/skills/data-analysis.md +27 -0
- package/skills/debugging.md +33 -0
- package/skills/devops.md +37 -0
- package/skills/document-docx.md +197 -0
- package/skills/document-pdf.md +294 -0
- package/skills/document-pptx.md +484 -0
- package/skills/document-xlsx.md +289 -0
- package/skills/domain-name-brainstormer.md +212 -0
- package/skills/file-organizer.md +433 -0
- package/skills/frontend-design.md +42 -0
- package/skills/image-enhancer.md +99 -0
- package/skills/invoice-organizer.md +446 -0
- package/skills/lead-research-assistant.md +199 -0
- package/skills/mcp-builder/SKILL.md +328 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +916 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +752 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/meeting-insights-analyzer.md +327 -0
- package/skills/orchestration.md +93 -0
- package/skills/raffle-winner-picker.md +159 -0
- package/skills/slack-gif-creator/SKILL.md +646 -0
- package/skills/slack-gif-creator/core/color_palettes.py +302 -0
- package/skills/slack-gif-creator/core/easing.py +230 -0
- package/skills/slack-gif-creator/core/frame_composer.py +469 -0
- package/skills/slack-gif-creator/core/gif_builder.py +246 -0
- package/skills/slack-gif-creator/core/typography.py +357 -0
- package/skills/slack-gif-creator/core/validators.py +264 -0
- package/skills/slack-gif-creator/core/visual_effects.py +494 -0
- package/skills/slack-gif-creator/requirements.txt +4 -0
- package/skills/slack-gif-creator/templates/bounce.py +106 -0
- package/skills/slack-gif-creator/templates/explode.py +331 -0
- package/skills/slack-gif-creator/templates/fade.py +329 -0
- package/skills/slack-gif-creator/templates/flip.py +291 -0
- package/skills/slack-gif-creator/templates/kaleidoscope.py +211 -0
- package/skills/slack-gif-creator/templates/morph.py +329 -0
- package/skills/slack-gif-creator/templates/move.py +293 -0
- package/skills/slack-gif-creator/templates/pulse.py +268 -0
- package/skills/slack-gif-creator/templates/shake.py +127 -0
- package/skills/slack-gif-creator/templates/slide.py +291 -0
- package/skills/slack-gif-creator/templates/spin.py +269 -0
- package/skills/slack-gif-creator/templates/wiggle.py +300 -0
- package/skills/slack-gif-creator/templates/zoom.py +312 -0
- package/skills/system-admin.md +44 -0
- package/skills/tailored-resume-generator.md +345 -0
- package/skills/theme-factory/SKILL.md +59 -0
- package/skills/theme-factory/theme-showcase.pdf +0 -0
- package/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/skills/theme-factory/themes/desert-rose.md +19 -0
- package/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/skills/theme-factory/themes/golden-hour.md +19 -0
- package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/skills/video-downloader.md +99 -0
- package/skills/web-development.md +32 -0
- package/skills/webapp-testing/SKILL.md +96 -0
- package/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/webapp-testing/scripts/with_server.py +106 -0
- package/src/agents/SubAgentManager.js +57 -12
- package/src/api/openai-compat.js +212 -0
- package/src/channels/TelegramChannel.js +5 -2
- package/src/channels/index.js +7 -10
- package/src/cli.js +129 -50
- package/src/config/agentProfiles.js +1 -0
- package/src/config/default.js +10 -0
- package/src/config/models.js +317 -71
- package/src/config/permissions.js +12 -0
- package/src/core/AgentLoop.js +70 -50
- package/src/core/Compaction.js +84 -2
- package/src/core/MessageQueue.js +90 -0
- package/src/core/Task.js +13 -0
- package/src/core/TaskQueue.js +1 -1
- package/src/core/TaskRunner.js +80 -5
- package/src/index.js +328 -48
- package/src/mcp/MCPAgentRunner.js +48 -11
- package/src/mcp/MCPManager.js +40 -2
- package/src/models/ModelRouter.js +67 -1
- package/src/safety/DockerSandbox.js +212 -0
- package/src/safety/ExecApproval.js +118 -0
- package/src/scheduler/Heartbeat.js +56 -21
- package/src/services/cleanup.js +106 -0
- package/src/services/sessions.js +39 -1
- package/src/setup/wizard.js +75 -4
- package/src/skills/SkillLoader.js +104 -17
- package/src/storage/TaskStore.js +19 -1
- package/src/systemPrompt.js +171 -328
- package/src/tools/browserAutomation.js +615 -104
- package/src/tools/executeCommand.js +19 -1
- package/src/tools/index.js +6 -0
- package/src/tools/manageAgents.js +55 -4
- package/src/tools/replyWithFile.js +62 -0
- package/src/tools/screenCapture.js +12 -1
- package/src/tools/taskManager.js +164 -0
- package/src/tools/useMCP.js +3 -1
- package/src/utils/Embeddings.js +157 -10
- package/src/webhooks/WebhookHandler.js +107 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Start one or more servers, wait for them to be ready, run a command, then clean up.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
# Single server
|
|
7
|
+
python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py
|
|
8
|
+
python scripts/with_server.py --server "npm start" --port 3000 -- python test.py
|
|
9
|
+
|
|
10
|
+
# Multiple servers
|
|
11
|
+
python scripts/with_server.py \
|
|
12
|
+
--server "cd backend && python server.py" --port 3000 \
|
|
13
|
+
--server "cd frontend && npm run dev" --port 5173 \
|
|
14
|
+
-- python test.py
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import subprocess
|
|
18
|
+
import socket
|
|
19
|
+
import time
|
|
20
|
+
import sys
|
|
21
|
+
import argparse
|
|
22
|
+
|
|
23
|
+
def is_server_ready(port, timeout=30):
|
|
24
|
+
"""Wait for server to be ready by polling the port."""
|
|
25
|
+
start_time = time.time()
|
|
26
|
+
while time.time() - start_time < timeout:
|
|
27
|
+
try:
|
|
28
|
+
with socket.create_connection(('localhost', port), timeout=1):
|
|
29
|
+
return True
|
|
30
|
+
except (socket.error, ConnectionRefusedError):
|
|
31
|
+
time.sleep(0.5)
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
parser = argparse.ArgumentParser(description='Run command with one or more servers')
|
|
37
|
+
parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)')
|
|
38
|
+
parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)')
|
|
39
|
+
parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)')
|
|
40
|
+
parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready')
|
|
41
|
+
|
|
42
|
+
args = parser.parse_args()
|
|
43
|
+
|
|
44
|
+
# Remove the '--' separator if present
|
|
45
|
+
if args.command and args.command[0] == '--':
|
|
46
|
+
args.command = args.command[1:]
|
|
47
|
+
|
|
48
|
+
if not args.command:
|
|
49
|
+
print("Error: No command specified to run")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
# Parse server configurations
|
|
53
|
+
if len(args.servers) != len(args.ports):
|
|
54
|
+
print("Error: Number of --server and --port arguments must match")
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
|
|
57
|
+
servers = []
|
|
58
|
+
for cmd, port in zip(args.servers, args.ports):
|
|
59
|
+
servers.append({'cmd': cmd, 'port': port})
|
|
60
|
+
|
|
61
|
+
server_processes = []
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# Start all servers
|
|
65
|
+
for i, server in enumerate(servers):
|
|
66
|
+
print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}")
|
|
67
|
+
|
|
68
|
+
# Use shell=True to support commands with cd and &&
|
|
69
|
+
process = subprocess.Popen(
|
|
70
|
+
server['cmd'],
|
|
71
|
+
shell=True,
|
|
72
|
+
stdout=subprocess.PIPE,
|
|
73
|
+
stderr=subprocess.PIPE
|
|
74
|
+
)
|
|
75
|
+
server_processes.append(process)
|
|
76
|
+
|
|
77
|
+
# Wait for this server to be ready
|
|
78
|
+
print(f"Waiting for server on port {server['port']}...")
|
|
79
|
+
if not is_server_ready(server['port'], timeout=args.timeout):
|
|
80
|
+
raise RuntimeError(f"Server failed to start on port {server['port']} within {args.timeout}s")
|
|
81
|
+
|
|
82
|
+
print(f"Server ready on port {server['port']}")
|
|
83
|
+
|
|
84
|
+
print(f"\nAll {len(servers)} server(s) ready")
|
|
85
|
+
|
|
86
|
+
# Run the command
|
|
87
|
+
print(f"Running: {' '.join(args.command)}\n")
|
|
88
|
+
result = subprocess.run(args.command)
|
|
89
|
+
sys.exit(result.returncode)
|
|
90
|
+
|
|
91
|
+
finally:
|
|
92
|
+
# Clean up all servers
|
|
93
|
+
print(f"\nStopping {len(server_processes)} server(s)...")
|
|
94
|
+
for i, process in enumerate(server_processes):
|
|
95
|
+
try:
|
|
96
|
+
process.terminate()
|
|
97
|
+
process.wait(timeout=5)
|
|
98
|
+
except subprocess.TimeoutExpired:
|
|
99
|
+
process.kill()
|
|
100
|
+
process.wait()
|
|
101
|
+
print(f"Server {i+1} stopped")
|
|
102
|
+
print("All servers stopped")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == '__main__':
|
|
106
|
+
main()
|
|
@@ -2,10 +2,12 @@ import { runAgentLoop } from "../core/AgentLoop.js";
|
|
|
2
2
|
import { buildSystemPrompt } from "../systemPrompt.js";
|
|
3
3
|
import { toolFunctions } from "../tools/index.js";
|
|
4
4
|
import { agentProfiles, defaultSubAgentTools } from "../config/agentProfiles.js";
|
|
5
|
+
import { config } from "../config/default.js";
|
|
5
6
|
import eventBus from "../core/EventBus.js";
|
|
6
7
|
import { v4 as uuidv4 } from "uuid";
|
|
7
8
|
import tenantContext from "../tenants/TenantContext.js";
|
|
8
9
|
import { resolveModelForProfile } from "../models/ModelRouter.js";
|
|
10
|
+
import { createSession, getSession, setMessages } from "../services/sessions.js";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Sub-Agent Manager - spawns, tracks, kills, and steers sub-agents.
|
|
@@ -94,12 +96,14 @@ export async function spawnSubAgent(taskDescription, options = {}) {
|
|
|
94
96
|
toolOverride = null, // exact tool functions - specialist agents only (e.g. MCP)
|
|
95
97
|
systemPromptOverride = null, // replace system prompt - specialist agents only
|
|
96
98
|
maxCost = 0.10,
|
|
97
|
-
timeout =
|
|
99
|
+
timeout = 300_000,
|
|
98
100
|
depth = 0,
|
|
99
101
|
parentTaskId = null,
|
|
100
102
|
parentContext = null,
|
|
101
103
|
approvalMode = "auto",
|
|
102
104
|
channelMeta = null,
|
|
105
|
+
historyMessages = [], // previous session messages to prepend (persistent sub-agent sessions)
|
|
106
|
+
returnFullResult = false, // return {text, messages, cost} instead of just text
|
|
103
107
|
} = options;
|
|
104
108
|
|
|
105
109
|
const maxDepth = 3;
|
|
@@ -114,11 +118,6 @@ export async function spawnSubAgent(taskDescription, options = {}) {
|
|
|
114
118
|
const agentId = uuidv4().slice(0, 8);
|
|
115
119
|
const taskId = `subagent-${agentId}`;
|
|
116
120
|
|
|
117
|
-
const profileLabel = profile ? ` [${profile}]` : "";
|
|
118
|
-
const modelLabel = resolvedModel ? ` ${C.dim}(${resolvedModel})${C.reset}` : "";
|
|
119
|
-
_agentLog(C.cyan + C.bold, "🤖 SPAWN", agentId, depth,
|
|
120
|
-
`${C.cyan}${C.bold}${profileLabel}${C.reset}${modelLabel} "${taskDescription.slice(0, 80)}${taskDescription.length > 80 ? "…" : ""}"`);
|
|
121
|
-
|
|
122
121
|
// ── Model resolution - priority: explicit > profile routing > parent > global default ───────
|
|
123
122
|
const store = tenantContext.getStore();
|
|
124
123
|
const resolvedModel = model
|
|
@@ -126,6 +125,12 @@ export async function spawnSubAgent(taskDescription, options = {}) {
|
|
|
126
125
|
|| store?.resolvedModel
|
|
127
126
|
|| config.defaultModel;
|
|
128
127
|
|
|
128
|
+
const profileLabel = profile ? ` [${profile}]` : "";
|
|
129
|
+
const modelLabel = resolvedModel ? ` ${C.dim}(${resolvedModel})${C.reset}` : "";
|
|
130
|
+
_agentLog(C.cyan + C.bold, "🤖 SPAWN", agentId, depth,
|
|
131
|
+
`${C.cyan}${C.bold}${profileLabel}${C.reset}${modelLabel} "${taskDescription.slice(0, 80)}${taskDescription.length > 80 ? "…" : ""}"`);
|
|
132
|
+
|
|
133
|
+
|
|
129
134
|
const apiKeys = store?.apiKeys || {};
|
|
130
135
|
|
|
131
136
|
// ── Tool set ──────────────────────────────────────────────────────────────
|
|
@@ -229,11 +234,25 @@ export async function spawnSubAgent(taskDescription, options = {}) {
|
|
|
229
234
|
taskDescription: taskDescription.slice(0, 100),
|
|
230
235
|
});
|
|
231
236
|
|
|
232
|
-
// ──
|
|
233
|
-
const
|
|
237
|
+
// ── Auto session load for regular sub-agents (not MCP — they manage their own) ──
|
|
238
|
+
const mainSessionId = store?.sessionId || null;
|
|
239
|
+
const shouldManageSession = !toolOverride && historyMessages.length === 0 && mainSessionId;
|
|
240
|
+
let subSessionId = null;
|
|
241
|
+
|
|
242
|
+
if (shouldManageSession) {
|
|
243
|
+
const sessionKey = profile || "general";
|
|
244
|
+
subSessionId = `${mainSessionId}--${sessionKey}`;
|
|
245
|
+
const subSession = getSession(subSessionId);
|
|
246
|
+
if (subSession && subSession.messages.length > 0) {
|
|
247
|
+
historyMessages = subSession.messages.map(m => ({ role: m.role, content: m.content }));
|
|
248
|
+
console.log(`[SubAgent:${agentId}] Loaded ${historyMessages.length} history messages from "${subSessionId}"`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Build initial messages (include history + optionally parent context) ──
|
|
253
|
+
const initialMessages = [...historyMessages];
|
|
234
254
|
|
|
235
255
|
if (parentContext) {
|
|
236
|
-
// Give the sub-agent a quick summary of what the parent knows
|
|
237
256
|
initialMessages.push({
|
|
238
257
|
role: "user",
|
|
239
258
|
content: `[Context from parent agent]:\n${parentContext}\n\n[Your task]:\n${taskDescription}`,
|
|
@@ -249,7 +268,11 @@ export async function spawnSubAgent(taskDescription, options = {}) {
|
|
|
249
268
|
const result = await Promise.race([
|
|
250
269
|
runAgentLoop({
|
|
251
270
|
messages: initialMessages,
|
|
252
|
-
systemPrompt: systemPromptOverride || await buildSystemPrompt(taskDescription
|
|
271
|
+
systemPrompt: systemPromptOverride || await buildSystemPrompt(taskDescription, "minimal", {
|
|
272
|
+
model: resolvedModel,
|
|
273
|
+
agentId,
|
|
274
|
+
taskDescription,
|
|
275
|
+
}),
|
|
253
276
|
tools: agentTools,
|
|
254
277
|
modelId: resolvedModel,
|
|
255
278
|
taskId,
|
|
@@ -268,10 +291,32 @@ export async function spawnSubAgent(taskDescription, options = {}) {
|
|
|
268
291
|
]);
|
|
269
292
|
|
|
270
293
|
const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
271
|
-
const
|
|
294
|
+
const costVal = typeof result.cost === "number" ? result.cost : result.cost?.estimatedCost;
|
|
295
|
+
const costStr = costVal ? ` $${costVal.toFixed(4)}` : "";
|
|
272
296
|
_agentLog(C.green + C.bold, "✅ DONE ", agentId, depth,
|
|
273
297
|
`${C.green}${C.bold}completed in ${elapsed}s${costStr}${C.reset}`);
|
|
274
|
-
eventBus.emitEvent("agent:finished", {
|
|
298
|
+
eventBus.emitEvent("agent:finished", {
|
|
299
|
+
agentId, taskId, parentTaskId, cost: result.cost,
|
|
300
|
+
toolCalls: (result.toolCalls || []).map(tc => ({ tool: tc.tool, duration: tc.duration })),
|
|
301
|
+
resultPreview: (result.text || "").slice(0, 200),
|
|
302
|
+
model: resolvedModel,
|
|
303
|
+
role: profile || "general",
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ── Auto session save for regular sub-agents ──────────────────────────
|
|
307
|
+
if (subSessionId && result.messages) {
|
|
308
|
+
let subSession = getSession(subSessionId);
|
|
309
|
+
if (!subSession) subSession = createSession(subSessionId);
|
|
310
|
+
const capped = result.messages.length > 100
|
|
311
|
+
? result.messages.slice(-100)
|
|
312
|
+
: result.messages;
|
|
313
|
+
setMessages(subSessionId, capped);
|
|
314
|
+
console.log(`[SubAgent:${agentId}] Saved ${capped.length} messages to "${subSessionId}"`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (returnFullResult) {
|
|
318
|
+
return { text: result.text, messages: result.messages, cost: result.cost };
|
|
319
|
+
}
|
|
275
320
|
return result.text;
|
|
276
321
|
|
|
277
322
|
} catch (error) {
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-Compatible API — /v1/chat/completions
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for OpenAI API. Routes through TaskRunner as a chat task.
|
|
5
|
+
* Supports both non-streaming and SSE streaming responses.
|
|
6
|
+
*
|
|
7
|
+
* Auth: Bearer token from OPENAI_COMPAT_TOKEN or WEBHOOK_TOKEN env var.
|
|
8
|
+
* Config: openaiCompat.enabled (default false, set OPENAI_COMPAT_ENABLED=true)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Router } from "express";
|
|
12
|
+
import { v4 as uuidv4 } from "uuid";
|
|
13
|
+
import taskQueue from "../core/TaskQueue.js";
|
|
14
|
+
import eventBus from "../core/EventBus.js";
|
|
15
|
+
import { loadTask } from "../storage/TaskStore.js";
|
|
16
|
+
|
|
17
|
+
const router = Router();
|
|
18
|
+
|
|
19
|
+
function checkAuth(req, res) {
|
|
20
|
+
const token = process.env.OPENAI_COMPAT_TOKEN || process.env.WEBHOOK_TOKEN;
|
|
21
|
+
if (!token) {
|
|
22
|
+
res.status(503).json({ error: { message: "OpenAI-compat API not configured. Set OPENAI_COMPAT_TOKEN env var.", type: "server_error" } });
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const authHeader = req.headers.authorization;
|
|
27
|
+
if (!authHeader || !authHeader.startsWith("Bearer ") || authHeader.slice(7) !== token) {
|
|
28
|
+
res.status(401).json({ error: { message: "Invalid API key.", type: "invalid_request_error" } });
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* POST /v1/chat/completions
|
|
36
|
+
*/
|
|
37
|
+
router.post("/chat/completions", async (req, res) => {
|
|
38
|
+
if (!checkAuth(req, res)) return;
|
|
39
|
+
|
|
40
|
+
const { messages, model, stream = false, max_tokens } = req.body;
|
|
41
|
+
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
42
|
+
return res.status(400).json({ error: { message: "messages array is required", type: "invalid_request_error" } });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Extract user message (last user message) and system prompt
|
|
46
|
+
let systemContent = "";
|
|
47
|
+
let userContent = "";
|
|
48
|
+
for (const msg of messages) {
|
|
49
|
+
if (msg.role === "system") systemContent += msg.content + "\n";
|
|
50
|
+
if (msg.role === "user") userContent = msg.content;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const taskInput = systemContent
|
|
54
|
+
? `[System instruction]: ${systemContent.trim()}\n\n${userContent}`
|
|
55
|
+
: userContent;
|
|
56
|
+
|
|
57
|
+
const taskId = uuidv4();
|
|
58
|
+
|
|
59
|
+
const task = taskQueue.enqueue({
|
|
60
|
+
input: taskInput,
|
|
61
|
+
channel: "openai-compat",
|
|
62
|
+
sessionId: `compat-${taskId.slice(0, 8)}`,
|
|
63
|
+
model: model || null,
|
|
64
|
+
priority: 5,
|
|
65
|
+
type: "chat",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (stream) {
|
|
69
|
+
// SSE streaming
|
|
70
|
+
res.writeHead(200, {
|
|
71
|
+
"Content-Type": "text/event-stream",
|
|
72
|
+
"Cache-Control": "no-cache",
|
|
73
|
+
Connection: "keep-alive",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const completionId = `chatcmpl-${task.id.slice(0, 12)}`;
|
|
77
|
+
|
|
78
|
+
const onComplete = (evt) => {
|
|
79
|
+
if (evt.taskId !== task.id) return;
|
|
80
|
+
const finalTask = loadTask(task.id);
|
|
81
|
+
const text = finalTask?.result || "";
|
|
82
|
+
|
|
83
|
+
// Send chunks
|
|
84
|
+
const chunk = {
|
|
85
|
+
id: completionId,
|
|
86
|
+
object: "chat.completion.chunk",
|
|
87
|
+
created: Math.floor(Date.now() / 1000),
|
|
88
|
+
model: model || "daemora",
|
|
89
|
+
choices: [{
|
|
90
|
+
index: 0,
|
|
91
|
+
delta: { role: "assistant", content: text },
|
|
92
|
+
finish_reason: "stop",
|
|
93
|
+
}],
|
|
94
|
+
};
|
|
95
|
+
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
96
|
+
res.write("data: [DONE]\n\n");
|
|
97
|
+
cleanup();
|
|
98
|
+
res.end();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onFail = (evt) => {
|
|
102
|
+
if (evt.taskId !== task.id) return;
|
|
103
|
+
const errChunk = {
|
|
104
|
+
id: completionId,
|
|
105
|
+
object: "chat.completion.chunk",
|
|
106
|
+
created: Math.floor(Date.now() / 1000),
|
|
107
|
+
model: model || "daemora",
|
|
108
|
+
choices: [{
|
|
109
|
+
index: 0,
|
|
110
|
+
delta: { content: `Error: ${evt.error || "Task failed"}` },
|
|
111
|
+
finish_reason: "stop",
|
|
112
|
+
}],
|
|
113
|
+
};
|
|
114
|
+
res.write(`data: ${JSON.stringify(errChunk)}\n\n`);
|
|
115
|
+
res.write("data: [DONE]\n\n");
|
|
116
|
+
cleanup();
|
|
117
|
+
res.end();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
eventBus.on("task:completed", onComplete);
|
|
121
|
+
eventBus.on("task:failed", onFail);
|
|
122
|
+
|
|
123
|
+
const cleanup = () => {
|
|
124
|
+
eventBus.removeListener("task:completed", onComplete);
|
|
125
|
+
eventBus.removeListener("task:failed", onFail);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
req.on("close", cleanup);
|
|
129
|
+
|
|
130
|
+
// Timeout after 5 minutes
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
cleanup();
|
|
133
|
+
if (!res.writableEnded) {
|
|
134
|
+
res.write(`data: ${JSON.stringify({ error: "Timeout" })}\n\n`);
|
|
135
|
+
res.write("data: [DONE]\n\n");
|
|
136
|
+
res.end();
|
|
137
|
+
}
|
|
138
|
+
}, 300_000);
|
|
139
|
+
|
|
140
|
+
} else {
|
|
141
|
+
// Non-streaming — wait for completion
|
|
142
|
+
try {
|
|
143
|
+
const result = await new Promise((resolve, reject) => {
|
|
144
|
+
const timeout = setTimeout(() => {
|
|
145
|
+
cleanup();
|
|
146
|
+
reject(new Error("Request timed out after 5 minutes"));
|
|
147
|
+
}, 300_000);
|
|
148
|
+
|
|
149
|
+
const onComplete = (evt) => {
|
|
150
|
+
if (evt.taskId !== task.id) return;
|
|
151
|
+
clearTimeout(timeout);
|
|
152
|
+
cleanup();
|
|
153
|
+
const finalTask = loadTask(task.id);
|
|
154
|
+
resolve(finalTask?.result || "");
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const onFail = (evt) => {
|
|
158
|
+
if (evt.taskId !== task.id) return;
|
|
159
|
+
clearTimeout(timeout);
|
|
160
|
+
cleanup();
|
|
161
|
+
reject(new Error(evt.error || "Task failed"));
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
eventBus.on("task:completed", onComplete);
|
|
165
|
+
eventBus.on("task:failed", onFail);
|
|
166
|
+
|
|
167
|
+
var cleanup = () => {
|
|
168
|
+
eventBus.removeListener("task:completed", onComplete);
|
|
169
|
+
eventBus.removeListener("task:failed", onFail);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
req.on("close", () => {
|
|
173
|
+
clearTimeout(timeout);
|
|
174
|
+
cleanup();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
res.json({
|
|
179
|
+
id: `chatcmpl-${task.id.slice(0, 12)}`,
|
|
180
|
+
object: "chat.completion",
|
|
181
|
+
created: Math.floor(Date.now() / 1000),
|
|
182
|
+
model: model || "daemora",
|
|
183
|
+
choices: [{
|
|
184
|
+
index: 0,
|
|
185
|
+
message: { role: "assistant", content: result },
|
|
186
|
+
finish_reason: "stop",
|
|
187
|
+
}],
|
|
188
|
+
usage: {
|
|
189
|
+
prompt_tokens: Math.ceil(taskInput.length / 4),
|
|
190
|
+
completion_tokens: Math.ceil(result.length / 4),
|
|
191
|
+
total_tokens: Math.ceil((taskInput.length + result.length) / 4),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
} catch (error) {
|
|
195
|
+
res.status(500).json({ error: { message: error.message, type: "server_error" } });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* GET /v1/models — list available models
|
|
202
|
+
*/
|
|
203
|
+
router.get("/models", (req, res) => {
|
|
204
|
+
res.json({
|
|
205
|
+
object: "list",
|
|
206
|
+
data: [
|
|
207
|
+
{ id: "daemora", object: "model", owned_by: "daemora", created: Math.floor(Date.now() / 1000) },
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export default router;
|
|
@@ -225,12 +225,15 @@ export class TelegramChannel extends BaseChannel {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
const failed = completedTask.status === "failed";
|
|
228
|
+
await this.sendReaction({ chatId, messageId }, failed ? "❌" : "✅");
|
|
229
|
+
|
|
230
|
+
// Skip text reply if agent already sent a file directly via replyWithFile
|
|
231
|
+
if (completedTask.directReplySent && !failed) return;
|
|
232
|
+
|
|
228
233
|
const response = failed
|
|
229
234
|
? `Sorry, I encountered an error: ${completedTask.error}`
|
|
230
235
|
: completedTask.result || "Done.";
|
|
231
236
|
|
|
232
|
-
await this.sendReaction({ chatId, messageId }, failed ? "❌" : "✅");
|
|
233
|
-
|
|
234
237
|
const chunks = splitMessage(response, 4096);
|
|
235
238
|
for (const chunk of chunks) {
|
|
236
239
|
await ctx.reply(chunk).catch(() => {});
|
package/src/channels/index.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
// Anyone on the network could send arbitrary tasks to the agent.
|
|
3
|
-
// Uncomment only if you've added your own auth middleware.
|
|
4
|
-
// import { HttpChannel } from "./HttpChannel.js";
|
|
1
|
+
import { HttpChannel } from "./HttpChannel.js";
|
|
5
2
|
|
|
6
3
|
// ─── Core channels ─────────────────────────────────────────────────────────────
|
|
7
4
|
import { TelegramChannel } from "./TelegramChannel.js";
|
|
@@ -42,10 +39,10 @@ class ChannelRegistry {
|
|
|
42
39
|
* Initialize and start all enabled channels.
|
|
43
40
|
*/
|
|
44
41
|
async startAll() {
|
|
45
|
-
// ─── HTTP channel
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
// ─── HTTP channel (Securely handled by Express routes in index.js) ────────
|
|
43
|
+
const http = new HttpChannel(config.channels.http);
|
|
44
|
+
this.channels.set("http", http);
|
|
45
|
+
await http.start();
|
|
49
46
|
// ─────────────────────────────────────────────────────────────────────────
|
|
50
47
|
|
|
51
48
|
// ── Core channels ─────────────────────────────────────────────────────────
|
|
@@ -351,11 +348,11 @@ class ChannelRegistry {
|
|
|
351
348
|
eventBus.on("task:reply:needed", async ({ task }) => {
|
|
352
349
|
const channel = this.channels.get(task.channel);
|
|
353
350
|
if (!channel?.running) {
|
|
354
|
-
console.log(`[ChannelRegistry] Cannot send recovered reply
|
|
351
|
+
console.log(`[ChannelRegistry] Cannot send recovered reply \u2014 channel "${task.channel}" not running`);
|
|
355
352
|
return;
|
|
356
353
|
}
|
|
357
354
|
try {
|
|
358
|
-
const reply = task.result || "(Task completed
|
|
355
|
+
const reply = task.result || "(Task completed \u2014 no output)";
|
|
359
356
|
await channel.sendReply(task.channelMeta, reply);
|
|
360
357
|
console.log(`[ChannelRegistry] Recovered reply sent via ${task.channel} for task ${task.id}`);
|
|
361
358
|
} catch (e) {
|