titan-agent 2026.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +21 -0
- package/LICENSE +29 -0
- package/README.md +305 -0
- package/assets/titan-logo.png +0 -0
- package/dist/agent/agent.js +1458 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/generator.js +1078 -0
- package/dist/agent/generator.js.map +1 -0
- package/dist/cli/index.js +5064 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/gateway/server.js +4462 -0
- package/dist/gateway/server.js.map +1 -0
- package/dist/skills/builtin/apply_patch.js +368 -0
- package/dist/skills/builtin/apply_patch.js.map +1 -0
- package/dist/skills/builtin/auto_generate.js +1129 -0
- package/dist/skills/builtin/auto_generate.js.map +1 -0
- package/dist/skills/builtin/browser.js +357 -0
- package/dist/skills/builtin/browser.js.map +1 -0
- package/dist/skills/builtin/cron.js +410 -0
- package/dist/skills/builtin/cron.js.map +1 -0
- package/dist/skills/builtin/filesystem.js +386 -0
- package/dist/skills/builtin/filesystem.js.map +1 -0
- package/dist/skills/builtin/memory_skill.js +430 -0
- package/dist/skills/builtin/memory_skill.js.map +1 -0
- package/dist/skills/builtin/process.js +421 -0
- package/dist/skills/builtin/process.js.map +1 -0
- package/dist/skills/builtin/sessions.js +1729 -0
- package/dist/skills/builtin/sessions.js.map +1 -0
- package/dist/skills/builtin/shell.js +327 -0
- package/dist/skills/builtin/shell.js.map +1 -0
- package/dist/skills/builtin/vision.js +491 -0
- package/dist/skills/builtin/vision.js.map +1 -0
- package/dist/skills/builtin/voice.js +468 -0
- package/dist/skills/builtin/voice.js.map +1 -0
- package/dist/skills/builtin/web_fetch.js +331 -0
- package/dist/skills/builtin/web_fetch.js.map +1 -0
- package/dist/skills/builtin/web_search.js +317 -0
- package/dist/skills/builtin/web_search.js.map +1 -0
- package/dist/skills/builtin/webhook.js +323 -0
- package/dist/skills/builtin/webhook.js.map +1 -0
- package/dist/skills/registry.js +3369 -0
- package/dist/skills/registry.js.map +1 -0
- package/package.json +118 -0
|
@@ -0,0 +1,4462 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/utils/constants.ts
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
var TITAN_VERSION, TITAN_NAME, TITAN_ASCII_LOGO, TITAN_HOME, TITAN_CONFIG_PATH, TITAN_DB_PATH, TITAN_WORKSPACE, TITAN_SKILLS_DIR, TITAN_LOGS_DIR, TITAN_MEMORY_DIR, AGENTS_MD, SOUL_MD, TOOLS_MD, DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_PORT, DEFAULT_WEB_PORT, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, MAX_CONTEXT_MESSAGES, SESSION_TIMEOUT_MS, DEFAULT_SANDBOX_MODE, ALLOWED_TOOLS_DEFAULT;
|
|
16
|
+
var init_constants = __esm({
|
|
17
|
+
"src/utils/constants.ts"() {
|
|
18
|
+
"use strict";
|
|
19
|
+
TITAN_VERSION = "2026.4.0";
|
|
20
|
+
TITAN_NAME = "TITAN";
|
|
21
|
+
TITAN_ASCII_LOGO = `
|
|
22
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
23
|
+
\u2551 \u2551
|
|
24
|
+
\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2551
|
|
25
|
+
\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
|
|
26
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
|
|
27
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2551
|
|
28
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2551
|
|
29
|
+
\u2551 \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u2551
|
|
30
|
+
\u2551 \u2551
|
|
31
|
+
\u2551 The Intelligent Task Automation Network \u2551
|
|
32
|
+
\u2551 v${TITAN_VERSION} \u2551
|
|
33
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`;
|
|
34
|
+
TITAN_HOME = join(homedir(), ".titan");
|
|
35
|
+
TITAN_CONFIG_PATH = join(TITAN_HOME, "titan.json");
|
|
36
|
+
TITAN_DB_PATH = join(TITAN_HOME, "titan.db");
|
|
37
|
+
TITAN_WORKSPACE = join(TITAN_HOME, "workspace");
|
|
38
|
+
TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, "skills");
|
|
39
|
+
TITAN_LOGS_DIR = join(TITAN_HOME, "logs");
|
|
40
|
+
TITAN_MEMORY_DIR = join(TITAN_HOME, "memory");
|
|
41
|
+
AGENTS_MD = join(TITAN_WORKSPACE, "AGENTS.md");
|
|
42
|
+
SOUL_MD = join(TITAN_WORKSPACE, "SOUL.md");
|
|
43
|
+
TOOLS_MD = join(TITAN_WORKSPACE, "TOOLS.md");
|
|
44
|
+
DEFAULT_GATEWAY_HOST = "127.0.0.1";
|
|
45
|
+
DEFAULT_GATEWAY_PORT = 18789;
|
|
46
|
+
DEFAULT_WEB_PORT = 18790;
|
|
47
|
+
DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514";
|
|
48
|
+
DEFAULT_MAX_TOKENS = 8192;
|
|
49
|
+
DEFAULT_TEMPERATURE = 0.7;
|
|
50
|
+
MAX_CONTEXT_MESSAGES = 50;
|
|
51
|
+
SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
52
|
+
DEFAULT_SANDBOX_MODE = "host";
|
|
53
|
+
ALLOWED_TOOLS_DEFAULT = [
|
|
54
|
+
"shell",
|
|
55
|
+
"read_file",
|
|
56
|
+
"write_file",
|
|
57
|
+
"edit_file",
|
|
58
|
+
"list_dir",
|
|
59
|
+
"web_search",
|
|
60
|
+
"browser",
|
|
61
|
+
"cron",
|
|
62
|
+
"webhook",
|
|
63
|
+
"email",
|
|
64
|
+
"memory"
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// src/utils/helpers.ts
|
|
70
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
71
|
+
function ensureDir(dirPath) {
|
|
72
|
+
if (!existsSync(dirPath)) {
|
|
73
|
+
mkdirSync(dirPath, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function readJsonFile(filePath) {
|
|
77
|
+
try {
|
|
78
|
+
if (!existsSync(filePath)) return null;
|
|
79
|
+
const content = readFileSync(filePath, "utf-8");
|
|
80
|
+
return JSON.parse(content);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
var init_helpers = __esm({
|
|
86
|
+
"src/utils/helpers.ts"() {
|
|
87
|
+
"use strict";
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// src/config/schema.ts
|
|
92
|
+
import { z } from "zod";
|
|
93
|
+
var ProviderConfigSchema, ChannelConfigSchema, SecurityConfigSchema, GatewayConfigSchema, AgentConfigSchema, TitanConfigSchema;
|
|
94
|
+
var init_schema = __esm({
|
|
95
|
+
"src/config/schema.ts"() {
|
|
96
|
+
"use strict";
|
|
97
|
+
init_constants();
|
|
98
|
+
ProviderConfigSchema = z.object({
|
|
99
|
+
apiKey: z.string().optional(),
|
|
100
|
+
baseUrl: z.string().optional(),
|
|
101
|
+
model: z.string().optional(),
|
|
102
|
+
maxTokens: z.number().optional(),
|
|
103
|
+
temperature: z.number().min(0).max(2).optional()
|
|
104
|
+
});
|
|
105
|
+
ChannelConfigSchema = z.object({
|
|
106
|
+
enabled: z.boolean().default(false),
|
|
107
|
+
token: z.string().optional(),
|
|
108
|
+
apiKey: z.string().optional(),
|
|
109
|
+
allowFrom: z.array(z.string()).default([]),
|
|
110
|
+
dmPolicy: z.enum(["pairing", "open", "closed"]).default("pairing")
|
|
111
|
+
});
|
|
112
|
+
SecurityConfigSchema = z.object({
|
|
113
|
+
sandboxMode: z.enum(["host", "docker", "none"]).default(DEFAULT_SANDBOX_MODE),
|
|
114
|
+
allowedTools: z.array(z.string()).default(ALLOWED_TOOLS_DEFAULT),
|
|
115
|
+
deniedTools: z.array(z.string()).default([]),
|
|
116
|
+
maxConcurrentTasks: z.number().default(5),
|
|
117
|
+
commandTimeout: z.number().default(3e4),
|
|
118
|
+
fileSystemAllowlist: z.array(z.string()).default([]),
|
|
119
|
+
networkAllowlist: z.array(z.string()).default(["*"]),
|
|
120
|
+
shield: z.object({
|
|
121
|
+
enabled: z.boolean().default(true),
|
|
122
|
+
mode: z.enum(["standard", "strict"]).default("strict")
|
|
123
|
+
}).default({})
|
|
124
|
+
});
|
|
125
|
+
GatewayConfigSchema = z.object({
|
|
126
|
+
host: z.string().default(DEFAULT_GATEWAY_HOST),
|
|
127
|
+
port: z.number().default(DEFAULT_GATEWAY_PORT),
|
|
128
|
+
webPort: z.number().default(DEFAULT_WEB_PORT),
|
|
129
|
+
auth: z.object({
|
|
130
|
+
mode: z.enum(["none", "token", "password"]).default("token"),
|
|
131
|
+
token: z.string().optional(),
|
|
132
|
+
password: z.string().optional()
|
|
133
|
+
}).default({})
|
|
134
|
+
});
|
|
135
|
+
AgentConfigSchema = z.object({
|
|
136
|
+
model: z.string().default(DEFAULT_MODEL),
|
|
137
|
+
maxTokens: z.number().default(DEFAULT_MAX_TOKENS),
|
|
138
|
+
temperature: z.number().default(DEFAULT_TEMPERATURE),
|
|
139
|
+
systemPrompt: z.string().optional(),
|
|
140
|
+
workspace: z.string().optional(),
|
|
141
|
+
thinkingMode: z.enum(["off", "low", "medium", "high"]).default("medium")
|
|
142
|
+
});
|
|
143
|
+
TitanConfigSchema = z.object({
|
|
144
|
+
agent: AgentConfigSchema.default({}),
|
|
145
|
+
providers: z.object({
|
|
146
|
+
anthropic: ProviderConfigSchema.default({}),
|
|
147
|
+
openai: ProviderConfigSchema.default({}),
|
|
148
|
+
google: ProviderConfigSchema.default({}),
|
|
149
|
+
ollama: ProviderConfigSchema.default({})
|
|
150
|
+
}).default({}),
|
|
151
|
+
channels: z.object({
|
|
152
|
+
discord: ChannelConfigSchema.default({}),
|
|
153
|
+
telegram: ChannelConfigSchema.default({}),
|
|
154
|
+
slack: ChannelConfigSchema.default({}),
|
|
155
|
+
whatsapp: ChannelConfigSchema.default({}),
|
|
156
|
+
webchat: ChannelConfigSchema.default({}),
|
|
157
|
+
googlechat: ChannelConfigSchema.default({}),
|
|
158
|
+
matrix: ChannelConfigSchema.default({}),
|
|
159
|
+
signal: ChannelConfigSchema.default({}),
|
|
160
|
+
msteams: ChannelConfigSchema.default({}),
|
|
161
|
+
bluebubbles: ChannelConfigSchema.default({})
|
|
162
|
+
}).default({}),
|
|
163
|
+
gateway: GatewayConfigSchema.default({}),
|
|
164
|
+
security: SecurityConfigSchema.default({}),
|
|
165
|
+
memory: z.object({
|
|
166
|
+
enabled: z.boolean().default(true),
|
|
167
|
+
maxHistoryMessages: z.number().default(50),
|
|
168
|
+
vectorSearchEnabled: z.boolean().default(false)
|
|
169
|
+
}).default({}),
|
|
170
|
+
skills: z.object({
|
|
171
|
+
enabled: z.boolean().default(true),
|
|
172
|
+
autoDiscover: z.boolean().default(true),
|
|
173
|
+
marketplace: z.boolean().default(false)
|
|
174
|
+
}).default({}),
|
|
175
|
+
logging: z.object({
|
|
176
|
+
level: z.enum(["debug", "info", "warn", "error", "silent"]).default("info"),
|
|
177
|
+
file: z.boolean().default(true)
|
|
178
|
+
}).default({}),
|
|
179
|
+
autonomy: z.object({
|
|
180
|
+
/** autonomous = full auto, supervised = asks for dangerous ops, locked = asks for everything */
|
|
181
|
+
mode: z.enum(["autonomous", "supervised", "locked"]).default("supervised"),
|
|
182
|
+
/** Auto-approve moderate-risk tools in main session (cli/webchat) */
|
|
183
|
+
autoApproveMainSession: z.boolean().default(true),
|
|
184
|
+
/** Timeout for HITL approval requests (ms). Auto-deny after timeout. */
|
|
185
|
+
approvalTimeoutMs: z.number().default(6e4),
|
|
186
|
+
/** Notify user of auto-approved actions */
|
|
187
|
+
notifyOnAutoApprove: z.boolean().default(true)
|
|
188
|
+
}).default({})
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// src/utils/logger.ts
|
|
194
|
+
import chalk from "chalk";
|
|
195
|
+
function formatTimestamp() {
|
|
196
|
+
return chalk.gray((/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19));
|
|
197
|
+
}
|
|
198
|
+
function log(level, component, message, ...args) {
|
|
199
|
+
if (level < currentLevel) return;
|
|
200
|
+
const prefix = `${formatTimestamp()} ${LEVEL_LABELS[level]} ${chalk.blue(`[${component}]`)}`;
|
|
201
|
+
console.log(`${prefix} ${message}`, ...args);
|
|
202
|
+
}
|
|
203
|
+
var LEVEL_LABELS, currentLevel, logger, logger_default;
|
|
204
|
+
var init_logger = __esm({
|
|
205
|
+
"src/utils/logger.ts"() {
|
|
206
|
+
"use strict";
|
|
207
|
+
LEVEL_LABELS = {
|
|
208
|
+
[0 /* DEBUG */]: chalk.gray("DEBUG"),
|
|
209
|
+
[1 /* INFO */]: chalk.cyan("INFO "),
|
|
210
|
+
[2 /* WARN */]: chalk.yellow("WARN "),
|
|
211
|
+
[3 /* ERROR */]: chalk.red("ERROR"),
|
|
212
|
+
[4 /* SILENT */]: ""
|
|
213
|
+
};
|
|
214
|
+
currentLevel = 1 /* INFO */;
|
|
215
|
+
logger = {
|
|
216
|
+
debug: (component, msg, ...args) => log(0 /* DEBUG */, component, msg, ...args),
|
|
217
|
+
info: (component, msg, ...args) => log(1 /* INFO */, component, msg, ...args),
|
|
218
|
+
warn: (component, msg, ...args) => log(2 /* WARN */, component, msg, ...args),
|
|
219
|
+
error: (component, msg, ...args) => log(3 /* ERROR */, component, msg, ...args)
|
|
220
|
+
};
|
|
221
|
+
logger_default = logger;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// src/config/config.ts
|
|
226
|
+
import { existsSync as existsSync2 } from "fs";
|
|
227
|
+
function getDefaultConfig() {
|
|
228
|
+
return TitanConfigSchema.parse({});
|
|
229
|
+
}
|
|
230
|
+
function loadConfig() {
|
|
231
|
+
if (cachedConfig) return cachedConfig;
|
|
232
|
+
ensureDir(TITAN_HOME);
|
|
233
|
+
let rawConfig = {};
|
|
234
|
+
if (existsSync2(TITAN_CONFIG_PATH)) {
|
|
235
|
+
const loaded = readJsonFile(TITAN_CONFIG_PATH);
|
|
236
|
+
if (loaded) {
|
|
237
|
+
rawConfig = loaded;
|
|
238
|
+
logger_default.debug(COMPONENT, `Loaded config from ${TITAN_CONFIG_PATH}`);
|
|
239
|
+
} else {
|
|
240
|
+
logger_default.warn(COMPONENT, `Failed to parse config at ${TITAN_CONFIG_PATH}, using defaults`);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
logger_default.info(COMPONENT, "No config file found, using defaults");
|
|
244
|
+
}
|
|
245
|
+
applyEnvOverrides(rawConfig);
|
|
246
|
+
const result = TitanConfigSchema.safeParse(rawConfig);
|
|
247
|
+
if (!result.success) {
|
|
248
|
+
logger_default.warn(COMPONENT, "Config validation issues, using defaults for invalid fields");
|
|
249
|
+
logger_default.debug(COMPONENT, result.error.message);
|
|
250
|
+
cachedConfig = getDefaultConfig();
|
|
251
|
+
} else {
|
|
252
|
+
cachedConfig = result.data;
|
|
253
|
+
}
|
|
254
|
+
return cachedConfig;
|
|
255
|
+
}
|
|
256
|
+
function applyEnvOverrides(config) {
|
|
257
|
+
const envMap = {
|
|
258
|
+
TITAN_MODEL: (val) => setNested(config, "agent.model", val),
|
|
259
|
+
TITAN_GATEWAY_PORT: (val) => setNested(config, "gateway.port", parseInt(val, 10)),
|
|
260
|
+
TITAN_GATEWAY_HOST: (val) => setNested(config, "gateway.host", val),
|
|
261
|
+
TITAN_LOG_LEVEL: (val) => setNested(config, "logging.level", val),
|
|
262
|
+
ANTHROPIC_API_KEY: (val) => setNested(config, "providers.anthropic.apiKey", val),
|
|
263
|
+
OPENAI_API_KEY: (val) => setNested(config, "providers.openai.apiKey", val),
|
|
264
|
+
GOOGLE_API_KEY: (val) => setNested(config, "providers.google.apiKey", val),
|
|
265
|
+
OLLAMA_BASE_URL: (val) => setNested(config, "providers.ollama.baseUrl", val),
|
|
266
|
+
DISCORD_TOKEN: (val) => setNested(config, "channels.discord.token", val),
|
|
267
|
+
TELEGRAM_TOKEN: (val) => setNested(config, "channels.telegram.token", val),
|
|
268
|
+
SLACK_TOKEN: (val) => setNested(config, "channels.slack.token", val)
|
|
269
|
+
};
|
|
270
|
+
for (const [envKey, setter] of Object.entries(envMap)) {
|
|
271
|
+
const val = process.env[envKey];
|
|
272
|
+
if (val) {
|
|
273
|
+
setter(val);
|
|
274
|
+
logger_default.debug(COMPONENT, `Applied env override: ${envKey}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function setNested(obj, path2, value) {
|
|
279
|
+
const parts = path2.split(".");
|
|
280
|
+
let current = obj;
|
|
281
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
282
|
+
if (!current[parts[i]] || typeof current[parts[i]] !== "object") {
|
|
283
|
+
current[parts[i]] = {};
|
|
284
|
+
}
|
|
285
|
+
current = current[parts[i]];
|
|
286
|
+
}
|
|
287
|
+
current[parts[parts.length - 1]] = value;
|
|
288
|
+
}
|
|
289
|
+
var COMPONENT, cachedConfig;
|
|
290
|
+
var init_config = __esm({
|
|
291
|
+
"src/config/config.ts"() {
|
|
292
|
+
"use strict";
|
|
293
|
+
init_constants();
|
|
294
|
+
init_helpers();
|
|
295
|
+
init_schema();
|
|
296
|
+
init_logger();
|
|
297
|
+
COMPONENT = "Config";
|
|
298
|
+
cachedConfig = null;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// src/security/encryption.ts
|
|
303
|
+
var encryption_exports = {};
|
|
304
|
+
__export(encryption_exports, {
|
|
305
|
+
decrypt: () => decrypt,
|
|
306
|
+
encrypt: () => encrypt,
|
|
307
|
+
generateKey: () => generateKey
|
|
308
|
+
});
|
|
309
|
+
import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
|
|
310
|
+
function generateKey() {
|
|
311
|
+
return randomBytes(32);
|
|
312
|
+
}
|
|
313
|
+
function encrypt(text, key) {
|
|
314
|
+
try {
|
|
315
|
+
const keyBuffer = typeof key === "string" ? Buffer.from(key, "hex") : key;
|
|
316
|
+
if (keyBuffer.length !== 32) {
|
|
317
|
+
throw new Error("Encryption key must be exactly 32 bytes (256 bits).");
|
|
318
|
+
}
|
|
319
|
+
const iv = randomBytes(16);
|
|
320
|
+
const cipher = createCipheriv(ALGORITHM, keyBuffer, iv);
|
|
321
|
+
let encrypted = cipher.update(text, "utf8", "hex");
|
|
322
|
+
encrypted += cipher.final("hex");
|
|
323
|
+
const authTag = cipher.getAuthTag().toString("hex");
|
|
324
|
+
return {
|
|
325
|
+
iv: iv.toString("hex"),
|
|
326
|
+
authTag,
|
|
327
|
+
data: encrypted
|
|
328
|
+
};
|
|
329
|
+
} catch (e) {
|
|
330
|
+
logger_default.error(COMPONENT2, `Encryption failed: ${e.message}`);
|
|
331
|
+
throw new Error("Failed to encrypt session data.");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function decrypt(payload, key) {
|
|
335
|
+
try {
|
|
336
|
+
const keyBuffer = typeof key === "string" ? Buffer.from(key, "hex") : key;
|
|
337
|
+
if (keyBuffer.length !== 32) {
|
|
338
|
+
throw new Error("Decryption key must be exactly 32 bytes (256 bits).");
|
|
339
|
+
}
|
|
340
|
+
const decipher = createDecipheriv(ALGORITHM, keyBuffer, Buffer.from(payload.iv, "hex"));
|
|
341
|
+
decipher.setAuthTag(Buffer.from(payload.authTag, "hex"));
|
|
342
|
+
let decrypted = decipher.update(payload.data, "hex", "utf8");
|
|
343
|
+
decrypted += decipher.final("utf8");
|
|
344
|
+
return decrypted;
|
|
345
|
+
} catch (e) {
|
|
346
|
+
logger_default.error(COMPONENT2, `Decryption failed: ${e.message}`);
|
|
347
|
+
throw new Error("Failed to decrypt session data. Invalid key or corrupted payload.");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
var COMPONENT2, ALGORITHM;
|
|
351
|
+
var init_encryption = __esm({
|
|
352
|
+
"src/security/encryption.ts"() {
|
|
353
|
+
"use strict";
|
|
354
|
+
init_logger();
|
|
355
|
+
COMPONENT2 = "Encryption";
|
|
356
|
+
ALGORITHM = "aes-256-gcm";
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// src/memory/memory.ts
|
|
361
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
362
|
+
import { join as join2 } from "path";
|
|
363
|
+
function getDefaultStore() {
|
|
364
|
+
return {
|
|
365
|
+
conversations: [],
|
|
366
|
+
memories: [],
|
|
367
|
+
sessions: [],
|
|
368
|
+
usageStats: [],
|
|
369
|
+
cronJobs: [],
|
|
370
|
+
skillsInstalled: []
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function loadStore() {
|
|
374
|
+
if (store) return store;
|
|
375
|
+
ensureDir(TITAN_HOME);
|
|
376
|
+
if (existsSync3(DB_FILE)) {
|
|
377
|
+
try {
|
|
378
|
+
const raw = readFileSync2(DB_FILE, "utf-8");
|
|
379
|
+
store = JSON.parse(raw);
|
|
380
|
+
store.conversations = store.conversations || [];
|
|
381
|
+
store.memories = store.memories || [];
|
|
382
|
+
store.sessions = store.sessions || [];
|
|
383
|
+
store.usageStats = store.usageStats || [];
|
|
384
|
+
store.cronJobs = store.cronJobs || [];
|
|
385
|
+
store.skillsInstalled = store.skillsInstalled || [];
|
|
386
|
+
} catch {
|
|
387
|
+
logger_default.warn(COMPONENT3, "Could not load data store, creating fresh one");
|
|
388
|
+
store = getDefaultStore();
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
store = getDefaultStore();
|
|
392
|
+
}
|
|
393
|
+
return store;
|
|
394
|
+
}
|
|
395
|
+
function saveStore() {
|
|
396
|
+
if (!store) return;
|
|
397
|
+
ensureDir(TITAN_HOME);
|
|
398
|
+
try {
|
|
399
|
+
writeFileSync2(DB_FILE, JSON.stringify(store, null, 2), "utf-8");
|
|
400
|
+
} catch (e) {
|
|
401
|
+
logger_default.error(COMPONENT3, `Failed to save data: ${e.message}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function debouncedSave() {
|
|
405
|
+
if (saveTimeout) clearTimeout(saveTimeout);
|
|
406
|
+
saveTimeout = setTimeout(saveStore, 1e3);
|
|
407
|
+
}
|
|
408
|
+
function initMemory() {
|
|
409
|
+
loadStore();
|
|
410
|
+
logger_default.info(COMPONENT3, "Memory system initialized");
|
|
411
|
+
}
|
|
412
|
+
function getDb() {
|
|
413
|
+
return loadStore();
|
|
414
|
+
}
|
|
415
|
+
function saveMessage(message, e2eKey) {
|
|
416
|
+
const s = loadStore();
|
|
417
|
+
let content = message.content;
|
|
418
|
+
let isEncrypted = false;
|
|
419
|
+
if (e2eKey) {
|
|
420
|
+
try {
|
|
421
|
+
const payload = encrypt(message.content, Buffer.from(e2eKey, "base64"));
|
|
422
|
+
content = JSON.stringify(payload);
|
|
423
|
+
isEncrypted = true;
|
|
424
|
+
} catch (e) {
|
|
425
|
+
logger_default.error(COMPONENT3, `Failed to encrypt message for storage`);
|
|
426
|
+
content = "[ENCRYPTION FAILED] " + content;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
s.conversations.push({
|
|
430
|
+
...message,
|
|
431
|
+
content,
|
|
432
|
+
isEncrypted,
|
|
433
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
434
|
+
});
|
|
435
|
+
if (s.conversations.length > 5e3) {
|
|
436
|
+
s.conversations = s.conversations.slice(-5e3);
|
|
437
|
+
}
|
|
438
|
+
debouncedSave();
|
|
439
|
+
}
|
|
440
|
+
function getHistory(sessionId, limit = 50, e2eKey) {
|
|
441
|
+
const s = loadStore();
|
|
442
|
+
const rawHistory = s.conversations.filter((m) => m.sessionId === sessionId).slice(-limit);
|
|
443
|
+
if (!e2eKey) {
|
|
444
|
+
return rawHistory;
|
|
445
|
+
}
|
|
446
|
+
return rawHistory.map((m) => {
|
|
447
|
+
if (m.isEncrypted) {
|
|
448
|
+
try {
|
|
449
|
+
const payload = JSON.parse(m.content);
|
|
450
|
+
return {
|
|
451
|
+
...m,
|
|
452
|
+
content: decrypt(payload, Buffer.from(e2eKey, "base64"))
|
|
453
|
+
};
|
|
454
|
+
} catch (e) {
|
|
455
|
+
logger_default.error(COMPONENT3, `Failed to decrypt message ${m.id}`);
|
|
456
|
+
return { ...m, content: "[DECRYPTION FAILED]" };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return m;
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function rememberFact(category, key, value, metadata) {
|
|
463
|
+
const s = loadStore();
|
|
464
|
+
const id = `${category}:${key}`;
|
|
465
|
+
const existingIdx = s.memories.findIndex((m) => m.id === id);
|
|
466
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
467
|
+
if (existingIdx >= 0) {
|
|
468
|
+
s.memories[existingIdx].value = value;
|
|
469
|
+
s.memories[existingIdx].metadata = metadata ? JSON.stringify(metadata) : void 0;
|
|
470
|
+
s.memories[existingIdx].updatedAt = now;
|
|
471
|
+
} else {
|
|
472
|
+
s.memories.push({
|
|
473
|
+
id,
|
|
474
|
+
category,
|
|
475
|
+
key,
|
|
476
|
+
value,
|
|
477
|
+
metadata: metadata ? JSON.stringify(metadata) : void 0,
|
|
478
|
+
createdAt: now,
|
|
479
|
+
updatedAt: now
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
debouncedSave();
|
|
483
|
+
}
|
|
484
|
+
function recallFact(category, key) {
|
|
485
|
+
const s = loadStore();
|
|
486
|
+
const entry = s.memories.find((m) => m.category === category && m.key === key);
|
|
487
|
+
return entry?.value || null;
|
|
488
|
+
}
|
|
489
|
+
function searchMemories(category, query) {
|
|
490
|
+
const s = loadStore();
|
|
491
|
+
let results = s.memories;
|
|
492
|
+
if (category) {
|
|
493
|
+
results = results.filter((m) => m.category === category);
|
|
494
|
+
}
|
|
495
|
+
if (query) {
|
|
496
|
+
const q = query.toLowerCase();
|
|
497
|
+
results = results.filter(
|
|
498
|
+
(m) => m.key.toLowerCase().includes(q) || m.value.toLowerCase().includes(q)
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
return results.slice(-50).map((m) => ({ key: m.key, value: m.value, category: m.category }));
|
|
502
|
+
}
|
|
503
|
+
function recordUsage(sessionId, provider, model, promptTokens, completionTokens) {
|
|
504
|
+
const s = loadStore();
|
|
505
|
+
s.usageStats.push({
|
|
506
|
+
id: s.usageStats.length + 1,
|
|
507
|
+
session_id: sessionId,
|
|
508
|
+
provider,
|
|
509
|
+
model,
|
|
510
|
+
prompt_tokens: promptTokens,
|
|
511
|
+
completion_tokens: completionTokens,
|
|
512
|
+
total_tokens: promptTokens + completionTokens,
|
|
513
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
514
|
+
});
|
|
515
|
+
if (s.usageStats.length > 1e4) {
|
|
516
|
+
s.usageStats = s.usageStats.slice(-1e4);
|
|
517
|
+
}
|
|
518
|
+
debouncedSave();
|
|
519
|
+
}
|
|
520
|
+
function getUsageStats() {
|
|
521
|
+
const s = loadStore();
|
|
522
|
+
let totalTokens = 0;
|
|
523
|
+
const byProvider = {};
|
|
524
|
+
for (const rec of s.usageStats) {
|
|
525
|
+
totalTokens += rec.total_tokens;
|
|
526
|
+
byProvider[rec.provider] = (byProvider[rec.provider] || 0) + rec.total_tokens;
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
totalTokens,
|
|
530
|
+
totalRequests: s.usageStats.length,
|
|
531
|
+
byProvider
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
var COMPONENT3, DB_FILE, store, saveTimeout;
|
|
535
|
+
var init_memory = __esm({
|
|
536
|
+
"src/memory/memory.ts"() {
|
|
537
|
+
"use strict";
|
|
538
|
+
init_constants();
|
|
539
|
+
init_helpers();
|
|
540
|
+
init_logger();
|
|
541
|
+
init_encryption();
|
|
542
|
+
COMPONENT3 = "Memory";
|
|
543
|
+
DB_FILE = join2(TITAN_HOME, "titan-data.json");
|
|
544
|
+
store = null;
|
|
545
|
+
saveTimeout = null;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// src/agent/toolRunner.ts
|
|
550
|
+
function registerTool(handler3) {
|
|
551
|
+
toolRegistry.set(handler3.name, handler3);
|
|
552
|
+
logger_default.debug(COMPONENT4, `Registered tool: ${handler3.name}`);
|
|
553
|
+
}
|
|
554
|
+
function getRegisteredTools() {
|
|
555
|
+
return Array.from(toolRegistry.values());
|
|
556
|
+
}
|
|
557
|
+
function getToolDefinitions() {
|
|
558
|
+
const config = loadConfig();
|
|
559
|
+
const allowed = new Set(config.security.allowedTools);
|
|
560
|
+
const denied = new Set(config.security.deniedTools);
|
|
561
|
+
return Array.from(toolRegistry.values()).filter((tool) => {
|
|
562
|
+
if (denied.has(tool.name)) return false;
|
|
563
|
+
if (allowed.size > 0 && !allowed.has(tool.name)) return false;
|
|
564
|
+
return true;
|
|
565
|
+
}).map((tool) => ({
|
|
566
|
+
type: "function",
|
|
567
|
+
function: {
|
|
568
|
+
name: tool.name,
|
|
569
|
+
description: tool.description,
|
|
570
|
+
parameters: tool.parameters
|
|
571
|
+
}
|
|
572
|
+
}));
|
|
573
|
+
}
|
|
574
|
+
async function executeTool(toolCall) {
|
|
575
|
+
const config = loadConfig();
|
|
576
|
+
const startTime = Date.now();
|
|
577
|
+
const handler3 = toolRegistry.get(toolCall.function.name);
|
|
578
|
+
if (!handler3) {
|
|
579
|
+
return {
|
|
580
|
+
toolCallId: toolCall.id,
|
|
581
|
+
name: toolCall.function.name,
|
|
582
|
+
content: `Error: Unknown tool "${toolCall.function.name}"`,
|
|
583
|
+
success: false,
|
|
584
|
+
durationMs: Date.now() - startTime
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
if (config.security.deniedTools.includes(handler3.name)) {
|
|
588
|
+
return {
|
|
589
|
+
toolCallId: toolCall.id,
|
|
590
|
+
name: handler3.name,
|
|
591
|
+
content: `Error: Tool "${handler3.name}" is denied by security policy`,
|
|
592
|
+
success: false,
|
|
593
|
+
durationMs: Date.now() - startTime
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
let args = {};
|
|
598
|
+
try {
|
|
599
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
600
|
+
} catch {
|
|
601
|
+
args = {};
|
|
602
|
+
}
|
|
603
|
+
logger_default.info(COMPONENT4, `Executing tool: ${handler3.name}`);
|
|
604
|
+
const timeout = config.security.commandTimeout || 3e4;
|
|
605
|
+
const result = await Promise.race([
|
|
606
|
+
handler3.execute(args),
|
|
607
|
+
new Promise(
|
|
608
|
+
(_, reject) => setTimeout(() => reject(new Error(`Tool "${handler3.name}" timed out after ${timeout}ms`)), timeout)
|
|
609
|
+
)
|
|
610
|
+
]);
|
|
611
|
+
const durationMs = Date.now() - startTime;
|
|
612
|
+
logger_default.info(COMPONENT4, `Tool ${handler3.name} completed in ${durationMs}ms`);
|
|
613
|
+
return {
|
|
614
|
+
toolCallId: toolCall.id,
|
|
615
|
+
name: handler3.name,
|
|
616
|
+
content: result,
|
|
617
|
+
success: true,
|
|
618
|
+
durationMs
|
|
619
|
+
};
|
|
620
|
+
} catch (error) {
|
|
621
|
+
const durationMs = Date.now() - startTime;
|
|
622
|
+
const errorMsg = error.message;
|
|
623
|
+
logger_default.error(COMPONENT4, `Tool ${handler3.name} failed: ${errorMsg}`);
|
|
624
|
+
return {
|
|
625
|
+
toolCallId: toolCall.id,
|
|
626
|
+
name: handler3.name,
|
|
627
|
+
content: `Error: ${errorMsg}`,
|
|
628
|
+
success: false,
|
|
629
|
+
durationMs
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
async function executeTools(toolCalls) {
|
|
634
|
+
const config = loadConfig();
|
|
635
|
+
const maxConcurrent = config.security.maxConcurrentTasks || 5;
|
|
636
|
+
const results = [];
|
|
637
|
+
for (let i = 0; i < toolCalls.length; i += maxConcurrent) {
|
|
638
|
+
const batch = toolCalls.slice(i, i + maxConcurrent);
|
|
639
|
+
const batchResults = await Promise.all(batch.map(executeTool));
|
|
640
|
+
results.push(...batchResults);
|
|
641
|
+
}
|
|
642
|
+
return results;
|
|
643
|
+
}
|
|
644
|
+
var COMPONENT4, toolRegistry;
|
|
645
|
+
var init_toolRunner = __esm({
|
|
646
|
+
"src/agent/toolRunner.ts"() {
|
|
647
|
+
"use strict";
|
|
648
|
+
init_logger();
|
|
649
|
+
init_config();
|
|
650
|
+
COMPONENT4 = "ToolRunner";
|
|
651
|
+
toolRegistry = /* @__PURE__ */ new Map();
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// src/skills/builtin/shell.ts
|
|
656
|
+
var shell_exports = {};
|
|
657
|
+
__export(shell_exports, {
|
|
658
|
+
registerShellSkill: () => registerShellSkill
|
|
659
|
+
});
|
|
660
|
+
import { exec } from "child_process";
|
|
661
|
+
function executeCommand(command, cwd, timeout = 3e4) {
|
|
662
|
+
return new Promise((resolve2, reject) => {
|
|
663
|
+
const proc = exec(command, {
|
|
664
|
+
cwd: cwd || process.cwd(),
|
|
665
|
+
timeout,
|
|
666
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
667
|
+
// 10MB
|
|
668
|
+
shell: "/bin/bash"
|
|
669
|
+
}, (error, stdout, stderr) => {
|
|
670
|
+
if (error && error.killed) {
|
|
671
|
+
reject(new Error(`Command timed out after ${timeout}ms`));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
let output = "";
|
|
675
|
+
if (stdout) output += stdout;
|
|
676
|
+
if (stderr) output += (output ? "\n" : "") + `[stderr] ${stderr}`;
|
|
677
|
+
if (error) output += (output ? "\n" : "") + `[exit code: ${error.code}]`;
|
|
678
|
+
if (output.length > 5e4) {
|
|
679
|
+
output = output.slice(0, 25e3) + "\n\n... [output truncated] ...\n\n" + output.slice(-25e3);
|
|
680
|
+
}
|
|
681
|
+
resolve2(output || "(no output)");
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
function registerShellSkill() {
|
|
686
|
+
registerSkill(
|
|
687
|
+
{
|
|
688
|
+
name: "shell",
|
|
689
|
+
description: "Execute shell commands on the system",
|
|
690
|
+
version: "1.0.0",
|
|
691
|
+
source: "bundled",
|
|
692
|
+
enabled: true
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: "shell",
|
|
696
|
+
description: "Execute a shell command on the user's system. Use this for running scripts, installing packages, checking system status, compiling code, git operations, and any other command-line task.",
|
|
697
|
+
parameters: {
|
|
698
|
+
type: "object",
|
|
699
|
+
properties: {
|
|
700
|
+
command: {
|
|
701
|
+
type: "string",
|
|
702
|
+
description: "The shell command to execute"
|
|
703
|
+
},
|
|
704
|
+
cwd: {
|
|
705
|
+
type: "string",
|
|
706
|
+
description: "Working directory for the command (optional)"
|
|
707
|
+
},
|
|
708
|
+
timeout: {
|
|
709
|
+
type: "number",
|
|
710
|
+
description: "Timeout in milliseconds (default: 30000)"
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
required: ["command"]
|
|
714
|
+
},
|
|
715
|
+
execute: async (args) => {
|
|
716
|
+
const command = args.command;
|
|
717
|
+
const cwd = args.cwd;
|
|
718
|
+
const timeout = args.timeout || 3e4;
|
|
719
|
+
logger_default.info(COMPONENT5, `Executing: ${command}`);
|
|
720
|
+
return await executeCommand(command, cwd, timeout);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
var COMPONENT5;
|
|
726
|
+
var init_shell = __esm({
|
|
727
|
+
"src/skills/builtin/shell.ts"() {
|
|
728
|
+
"use strict";
|
|
729
|
+
init_registry();
|
|
730
|
+
init_logger();
|
|
731
|
+
COMPONENT5 = "Shell";
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// src/skills/builtin/filesystem.ts
|
|
736
|
+
var filesystem_exports = {};
|
|
737
|
+
__export(filesystem_exports, {
|
|
738
|
+
registerFilesystemSkill: () => registerFilesystemSkill
|
|
739
|
+
});
|
|
740
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, readdirSync, statSync, mkdirSync as mkdirSync2 } from "fs";
|
|
741
|
+
import { join as join3, resolve } from "path";
|
|
742
|
+
function registerFilesystemSkill() {
|
|
743
|
+
registerSkill(
|
|
744
|
+
{ name: "read_file", description: "Read file contents", version: "1.0.0", source: "bundled", enabled: true },
|
|
745
|
+
{
|
|
746
|
+
name: "read_file",
|
|
747
|
+
description: "Read the contents of a file. Returns the text content of the file.",
|
|
748
|
+
parameters: {
|
|
749
|
+
type: "object",
|
|
750
|
+
properties: {
|
|
751
|
+
path: { type: "string", description: "Absolute or relative path to the file" },
|
|
752
|
+
startLine: { type: "number", description: "Start line (1-indexed, optional)" },
|
|
753
|
+
endLine: { type: "number", description: "End line (1-indexed, optional)" }
|
|
754
|
+
},
|
|
755
|
+
required: ["path"]
|
|
756
|
+
},
|
|
757
|
+
execute: async (args) => {
|
|
758
|
+
const filePath = resolve(args.path);
|
|
759
|
+
if (!existsSync4(filePath)) return `Error: File not found: ${filePath}`;
|
|
760
|
+
try {
|
|
761
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
762
|
+
const lines = content.split("\n");
|
|
763
|
+
const start = args.startLine || 1;
|
|
764
|
+
const end = args.endLine || lines.length;
|
|
765
|
+
const selected = lines.slice(start - 1, end);
|
|
766
|
+
return `File: ${filePath} (${lines.length} lines)
|
|
767
|
+
---
|
|
768
|
+
${selected.map((l, i) => `${start + i}: ${l}`).join("\n")}`;
|
|
769
|
+
} catch (e) {
|
|
770
|
+
return `Error reading file: ${e.message}`;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
registerSkill(
|
|
776
|
+
{ name: "write_file", description: "Write/create a file", version: "1.0.0", source: "bundled", enabled: true },
|
|
777
|
+
{
|
|
778
|
+
name: "write_file",
|
|
779
|
+
description: "Write content to a file. Creates the file and parent directories if they don't exist. Overwrites existing content.",
|
|
780
|
+
parameters: {
|
|
781
|
+
type: "object",
|
|
782
|
+
properties: {
|
|
783
|
+
path: { type: "string", description: "Path to the file" },
|
|
784
|
+
content: { type: "string", description: "Content to write" }
|
|
785
|
+
},
|
|
786
|
+
required: ["path", "content"]
|
|
787
|
+
},
|
|
788
|
+
execute: async (args) => {
|
|
789
|
+
const filePath = resolve(args.path);
|
|
790
|
+
try {
|
|
791
|
+
const dir = join3(filePath, "..");
|
|
792
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
793
|
+
writeFileSync3(filePath, args.content, "utf-8");
|
|
794
|
+
return `Successfully wrote ${args.content.length} bytes to ${filePath}`;
|
|
795
|
+
} catch (e) {
|
|
796
|
+
return `Error writing file: ${e.message}`;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
registerSkill(
|
|
802
|
+
{ name: "edit_file", description: "Search and replace in a file", version: "1.0.0", source: "bundled", enabled: true },
|
|
803
|
+
{
|
|
804
|
+
name: "edit_file",
|
|
805
|
+
description: "Edit a file by replacing a target string with new content.",
|
|
806
|
+
parameters: {
|
|
807
|
+
type: "object",
|
|
808
|
+
properties: {
|
|
809
|
+
path: { type: "string", description: "Path to the file" },
|
|
810
|
+
target: { type: "string", description: "Exact string to find and replace" },
|
|
811
|
+
replacement: { type: "string", description: "Replacement content" }
|
|
812
|
+
},
|
|
813
|
+
required: ["path", "target", "replacement"]
|
|
814
|
+
},
|
|
815
|
+
execute: async (args) => {
|
|
816
|
+
const filePath = resolve(args.path);
|
|
817
|
+
if (!existsSync4(filePath)) return `Error: File not found: ${filePath}`;
|
|
818
|
+
try {
|
|
819
|
+
let content = readFileSync3(filePath, "utf-8");
|
|
820
|
+
const target = args.target;
|
|
821
|
+
if (!content.includes(target)) return `Error: Target string not found in ${filePath}`;
|
|
822
|
+
content = content.replace(target, args.replacement);
|
|
823
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
824
|
+
return `Successfully edited ${filePath}`;
|
|
825
|
+
} catch (e) {
|
|
826
|
+
return `Error editing file: ${e.message}`;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
);
|
|
831
|
+
registerSkill(
|
|
832
|
+
{ name: "list_dir", description: "List directory contents", version: "1.0.0", source: "bundled", enabled: true },
|
|
833
|
+
{
|
|
834
|
+
name: "list_dir",
|
|
835
|
+
description: "List the contents of a directory, showing files and subdirectories with sizes.",
|
|
836
|
+
parameters: {
|
|
837
|
+
type: "object",
|
|
838
|
+
properties: {
|
|
839
|
+
path: { type: "string", description: "Path to the directory" },
|
|
840
|
+
recursive: { type: "boolean", description: "List recursively (default: false)" }
|
|
841
|
+
},
|
|
842
|
+
required: ["path"]
|
|
843
|
+
},
|
|
844
|
+
execute: async (args) => {
|
|
845
|
+
const dirPath = resolve(args.path);
|
|
846
|
+
if (!existsSync4(dirPath)) return `Error: Directory not found: ${dirPath}`;
|
|
847
|
+
try {
|
|
848
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
849
|
+
const lines = entries.map((entry) => {
|
|
850
|
+
const fullPath = join3(dirPath, entry.name);
|
|
851
|
+
if (entry.isDirectory()) {
|
|
852
|
+
return `\u{1F4C1} ${entry.name}/`;
|
|
853
|
+
}
|
|
854
|
+
const stat = statSync(fullPath);
|
|
855
|
+
const size = stat.size < 1024 ? `${stat.size}B` : stat.size < 1048576 ? `${(stat.size / 1024).toFixed(1)}KB` : `${(stat.size / 1048576).toFixed(1)}MB`;
|
|
856
|
+
return `\u{1F4C4} ${entry.name} (${size})`;
|
|
857
|
+
});
|
|
858
|
+
return `Directory: ${dirPath}
|
|
859
|
+
${lines.join("\n")}`;
|
|
860
|
+
} catch (e) {
|
|
861
|
+
return `Error listing directory: ${e.message}`;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
var init_filesystem = __esm({
|
|
868
|
+
"src/skills/builtin/filesystem.ts"() {
|
|
869
|
+
"use strict";
|
|
870
|
+
init_registry();
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
// src/skills/builtin/web_search.ts
|
|
875
|
+
var web_search_exports = {};
|
|
876
|
+
__export(web_search_exports, {
|
|
877
|
+
registerWebSearchSkill: () => registerWebSearchSkill
|
|
878
|
+
});
|
|
879
|
+
function registerWebSearchSkill() {
|
|
880
|
+
registerSkill(
|
|
881
|
+
{ name: "web_search", description: "Search the web", version: "1.0.0", source: "bundled", enabled: true },
|
|
882
|
+
{
|
|
883
|
+
name: "web_search",
|
|
884
|
+
description: "Search the web for information. Returns search results with titles, URLs, and snippets. Useful for finding current information, documentation, tutorials, etc.",
|
|
885
|
+
parameters: {
|
|
886
|
+
type: "object",
|
|
887
|
+
properties: {
|
|
888
|
+
query: { type: "string", description: "The search query" },
|
|
889
|
+
maxResults: { type: "number", description: "Maximum results to return (default: 5)" }
|
|
890
|
+
},
|
|
891
|
+
required: ["query"]
|
|
892
|
+
},
|
|
893
|
+
execute: async (args) => {
|
|
894
|
+
const query = args.query;
|
|
895
|
+
const maxResults = args.maxResults || 5;
|
|
896
|
+
logger_default.info(COMPONENT6, `Searching: ${query}`);
|
|
897
|
+
try {
|
|
898
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
899
|
+
const response = await fetch(url, {
|
|
900
|
+
headers: {
|
|
901
|
+
"User-Agent": "TITAN/1.0 (Autonomous AI Agent)"
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
if (!response.ok) {
|
|
905
|
+
return `Search failed with status ${response.status}`;
|
|
906
|
+
}
|
|
907
|
+
const html = await response.text();
|
|
908
|
+
const results = [];
|
|
909
|
+
const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/gi;
|
|
910
|
+
let match;
|
|
911
|
+
while ((match = resultRegex.exec(html)) !== null && results.length < maxResults) {
|
|
912
|
+
const rawUrl = match[1];
|
|
913
|
+
const title = match[2].replace(/<[^>]*>/g, "").trim();
|
|
914
|
+
const snippet = match[3].replace(/<[^>]*>/g, "").trim();
|
|
915
|
+
const urlMatch = rawUrl.match(/uddg=([^&]*)/);
|
|
916
|
+
const decodedUrl = urlMatch ? decodeURIComponent(urlMatch[1]) : rawUrl;
|
|
917
|
+
if (title && decodedUrl) {
|
|
918
|
+
results.push({ title, url: decodedUrl, snippet });
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (results.length === 0) {
|
|
922
|
+
return `No results found for: "${query}"`;
|
|
923
|
+
}
|
|
924
|
+
return results.map((r, i) => `${i + 1}. **${r.title}**
|
|
925
|
+
${r.url}
|
|
926
|
+
${r.snippet}`).join("\n\n");
|
|
927
|
+
} catch (error) {
|
|
928
|
+
return `Search error: ${error.message}`;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
var COMPONENT6;
|
|
935
|
+
var init_web_search = __esm({
|
|
936
|
+
"src/skills/builtin/web_search.ts"() {
|
|
937
|
+
"use strict";
|
|
938
|
+
init_registry();
|
|
939
|
+
init_logger();
|
|
940
|
+
COMPONENT6 = "WebSearch";
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// src/skills/builtin/cron.ts
|
|
945
|
+
var cron_exports = {};
|
|
946
|
+
__export(cron_exports, {
|
|
947
|
+
registerCronSkill: () => registerCronSkill
|
|
948
|
+
});
|
|
949
|
+
import { v4 as uuid } from "uuid";
|
|
950
|
+
function registerCronSkill() {
|
|
951
|
+
registerSkill(
|
|
952
|
+
{ name: "cron", description: "Manage scheduled tasks", version: "1.0.0", source: "bundled", enabled: true },
|
|
953
|
+
{
|
|
954
|
+
name: "cron",
|
|
955
|
+
description: "Create, list, enable/disable, or delete scheduled cron jobs. Jobs run automatically at specified intervals.",
|
|
956
|
+
parameters: {
|
|
957
|
+
type: "object",
|
|
958
|
+
properties: {
|
|
959
|
+
action: {
|
|
960
|
+
type: "string",
|
|
961
|
+
enum: ["create", "list", "delete", "enable", "disable"],
|
|
962
|
+
description: "Action to perform"
|
|
963
|
+
},
|
|
964
|
+
name: { type: "string", description: "Name for the cron job" },
|
|
965
|
+
schedule: { type: "string", description: 'Cron schedule expression (e.g., "0 9 * * *" for daily at 9am)' },
|
|
966
|
+
command: { type: "string", description: "Command to execute when the job runs" },
|
|
967
|
+
jobId: { type: "string", description: "Job ID (for delete/enable/disable)" }
|
|
968
|
+
},
|
|
969
|
+
required: ["action"]
|
|
970
|
+
},
|
|
971
|
+
execute: async (args) => {
|
|
972
|
+
const action = args.action;
|
|
973
|
+
const store2 = getDb();
|
|
974
|
+
switch (action) {
|
|
975
|
+
case "create": {
|
|
976
|
+
const name = args.name;
|
|
977
|
+
const schedule = args.schedule;
|
|
978
|
+
const command = args.command;
|
|
979
|
+
if (!name || !schedule || !command) return "Error: name, schedule, and command are required";
|
|
980
|
+
const id = uuid();
|
|
981
|
+
store2.cronJobs.push({
|
|
982
|
+
id,
|
|
983
|
+
name,
|
|
984
|
+
schedule,
|
|
985
|
+
command,
|
|
986
|
+
enabled: true,
|
|
987
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
988
|
+
});
|
|
989
|
+
logger_default.info(COMPONENT7, `Created cron job: ${name} (${schedule})`);
|
|
990
|
+
return `Created cron job "${name}" (ID: ${id})
|
|
991
|
+
Schedule: ${schedule}
|
|
992
|
+
Command: ${command}`;
|
|
993
|
+
}
|
|
994
|
+
case "list": {
|
|
995
|
+
if (store2.cronJobs.length === 0) return "No cron jobs configured.";
|
|
996
|
+
return store2.cronJobs.map(
|
|
997
|
+
(j) => `\u2022 ${j.name} [${j.enabled ? "\u2705 enabled" : "\u274C disabled"}]
|
|
998
|
+
ID: ${j.id}
|
|
999
|
+
Schedule: ${j.schedule}
|
|
1000
|
+
Command: ${j.command}`
|
|
1001
|
+
).join("\n\n");
|
|
1002
|
+
}
|
|
1003
|
+
case "delete": {
|
|
1004
|
+
const jobId = args.jobId;
|
|
1005
|
+
if (!jobId) return "Error: jobId is required";
|
|
1006
|
+
store2.cronJobs = store2.cronJobs.filter((j) => j.id !== jobId);
|
|
1007
|
+
return `Deleted cron job: ${jobId}`;
|
|
1008
|
+
}
|
|
1009
|
+
case "enable": {
|
|
1010
|
+
const eId = args.jobId;
|
|
1011
|
+
if (!eId) return "Error: jobId is required";
|
|
1012
|
+
const job = store2.cronJobs.find((j) => j.id === eId);
|
|
1013
|
+
if (job) job.enabled = true;
|
|
1014
|
+
return `Enabled cron job: ${eId}`;
|
|
1015
|
+
}
|
|
1016
|
+
case "disable": {
|
|
1017
|
+
const dId = args.jobId;
|
|
1018
|
+
if (!dId) return "Error: jobId is required";
|
|
1019
|
+
const job = store2.cronJobs.find((j) => j.id === dId);
|
|
1020
|
+
if (job) job.enabled = false;
|
|
1021
|
+
return `Disabled cron job: ${dId}`;
|
|
1022
|
+
}
|
|
1023
|
+
default:
|
|
1024
|
+
return `Unknown action: ${action}`;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
var COMPONENT7;
|
|
1031
|
+
var init_cron = __esm({
|
|
1032
|
+
"src/skills/builtin/cron.ts"() {
|
|
1033
|
+
"use strict";
|
|
1034
|
+
init_registry();
|
|
1035
|
+
init_memory();
|
|
1036
|
+
init_logger();
|
|
1037
|
+
COMPONENT7 = "Cron";
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
// src/skills/builtin/webhook.ts
|
|
1042
|
+
var webhook_exports = {};
|
|
1043
|
+
__export(webhook_exports, {
|
|
1044
|
+
getActiveWebhooks: () => getActiveWebhooks,
|
|
1045
|
+
registerWebhookSkill: () => registerWebhookSkill
|
|
1046
|
+
});
|
|
1047
|
+
import { v4 as uuid2 } from "uuid";
|
|
1048
|
+
function getActiveWebhooks() {
|
|
1049
|
+
return activeWebhooks;
|
|
1050
|
+
}
|
|
1051
|
+
function registerWebhookSkill() {
|
|
1052
|
+
registerSkill(
|
|
1053
|
+
{ name: "webhook", description: "Manage webhook endpoints", version: "1.0.0", source: "bundled", enabled: true },
|
|
1054
|
+
{
|
|
1055
|
+
name: "webhook",
|
|
1056
|
+
description: "Create, list, or delete HTTP webhook endpoints that trigger actions when called.",
|
|
1057
|
+
parameters: {
|
|
1058
|
+
type: "object",
|
|
1059
|
+
properties: {
|
|
1060
|
+
action: { type: "string", enum: ["create", "list", "delete"], description: "Action" },
|
|
1061
|
+
name: { type: "string", description: "Webhook name" },
|
|
1062
|
+
path: { type: "string", description: "URL path (e.g., /my-hook)" },
|
|
1063
|
+
method: { type: "string", description: "HTTP method (GET/POST, default: POST)" },
|
|
1064
|
+
handler: { type: "string", description: "Command to run when webhook is triggered" },
|
|
1065
|
+
webhookId: { type: "string", description: "Webhook ID (for delete)" }
|
|
1066
|
+
},
|
|
1067
|
+
required: ["action"]
|
|
1068
|
+
},
|
|
1069
|
+
execute: async (args) => {
|
|
1070
|
+
const action = args.action;
|
|
1071
|
+
switch (action) {
|
|
1072
|
+
case "create": {
|
|
1073
|
+
const id = uuid2();
|
|
1074
|
+
const name = args.name || `webhook-${id.slice(0, 8)}`;
|
|
1075
|
+
const path2 = args.path || `/webhook/${id.slice(0, 8)}`;
|
|
1076
|
+
const method = args.method || "POST";
|
|
1077
|
+
const handler3 = args.handler || "";
|
|
1078
|
+
activeWebhooks.set(id, { id, path: path2, name, method, handler: handler3 });
|
|
1079
|
+
logger_default.info(COMPONENT8, `Created webhook: ${name} at ${path2}`);
|
|
1080
|
+
return `Created webhook "${name}"
|
|
1081
|
+
ID: ${id}
|
|
1082
|
+
Path: ${path2}
|
|
1083
|
+
Method: ${method}
|
|
1084
|
+
Handler: ${handler3}`;
|
|
1085
|
+
}
|
|
1086
|
+
case "list": {
|
|
1087
|
+
if (activeWebhooks.size === 0) return "No active webhooks.";
|
|
1088
|
+
return Array.from(activeWebhooks.values()).map((w) => `\u2022 ${w.name}
|
|
1089
|
+
ID: ${w.id}
|
|
1090
|
+
${w.method} ${w.path}
|
|
1091
|
+
Handler: ${w.handler}`).join("\n\n");
|
|
1092
|
+
}
|
|
1093
|
+
case "delete": {
|
|
1094
|
+
const wId = args.webhookId;
|
|
1095
|
+
if (!wId) return "Error: webhookId is required";
|
|
1096
|
+
activeWebhooks.delete(wId);
|
|
1097
|
+
return `Deleted webhook: ${wId}`;
|
|
1098
|
+
}
|
|
1099
|
+
default:
|
|
1100
|
+
return `Unknown action: ${action}`;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
var COMPONENT8, activeWebhooks;
|
|
1107
|
+
var init_webhook = __esm({
|
|
1108
|
+
"src/skills/builtin/webhook.ts"() {
|
|
1109
|
+
"use strict";
|
|
1110
|
+
init_registry();
|
|
1111
|
+
init_logger();
|
|
1112
|
+
COMPONENT8 = "Webhook";
|
|
1113
|
+
activeWebhooks = /* @__PURE__ */ new Map();
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// src/skills/builtin/memory_skill.ts
|
|
1118
|
+
var memory_skill_exports = {};
|
|
1119
|
+
__export(memory_skill_exports, {
|
|
1120
|
+
registerMemorySkill: () => registerMemorySkill
|
|
1121
|
+
});
|
|
1122
|
+
function registerMemorySkill() {
|
|
1123
|
+
registerSkill(
|
|
1124
|
+
{ name: "memory", description: "Persistent memory management", version: "1.0.0", source: "bundled", enabled: true },
|
|
1125
|
+
{
|
|
1126
|
+
name: "memory",
|
|
1127
|
+
description: "Store and recall persistent memories. Use this to remember user preferences, important facts, project details, and anything that should persist across conversations.",
|
|
1128
|
+
parameters: {
|
|
1129
|
+
type: "object",
|
|
1130
|
+
properties: {
|
|
1131
|
+
action: { type: "string", enum: ["remember", "recall", "search", "list"], description: "Action" },
|
|
1132
|
+
category: { type: "string", description: 'Memory category (e.g., "preference", "fact", "project")' },
|
|
1133
|
+
key: { type: "string", description: "Memory key/name" },
|
|
1134
|
+
value: { type: "string", description: "Value to remember" },
|
|
1135
|
+
query: { type: "string", description: "Search query (for search action)" }
|
|
1136
|
+
},
|
|
1137
|
+
required: ["action"]
|
|
1138
|
+
},
|
|
1139
|
+
execute: async (args) => {
|
|
1140
|
+
const action = args.action;
|
|
1141
|
+
const category = args.category || "general";
|
|
1142
|
+
switch (action) {
|
|
1143
|
+
case "remember": {
|
|
1144
|
+
const key = args.key;
|
|
1145
|
+
const value = args.value;
|
|
1146
|
+
if (!key || !value) return "Error: key and value are required";
|
|
1147
|
+
rememberFact(category, key, value);
|
|
1148
|
+
return `Remembered: [${category}] ${key} = ${value}`;
|
|
1149
|
+
}
|
|
1150
|
+
case "recall": {
|
|
1151
|
+
const rKey = args.key;
|
|
1152
|
+
if (!rKey) return "Error: key is required";
|
|
1153
|
+
const value = recallFact(category, rKey);
|
|
1154
|
+
return value ? `[${category}] ${rKey} = ${value}` : `No memory found for [${category}] ${rKey}`;
|
|
1155
|
+
}
|
|
1156
|
+
case "search": {
|
|
1157
|
+
const query = args.query;
|
|
1158
|
+
const results = searchMemories(category !== "general" ? category : void 0, query);
|
|
1159
|
+
if (results.length === 0) return "No matching memories found.";
|
|
1160
|
+
return results.map((m) => `\u2022 [${m.category}] ${m.key}: ${m.value}`).join("\n");
|
|
1161
|
+
}
|
|
1162
|
+
case "list": {
|
|
1163
|
+
const all = searchMemories(category !== "general" ? category : void 0);
|
|
1164
|
+
if (all.length === 0) return "No memories stored yet.";
|
|
1165
|
+
return all.map((m) => `\u2022 [${m.category}] ${m.key}: ${m.value}`).join("\n");
|
|
1166
|
+
}
|
|
1167
|
+
default:
|
|
1168
|
+
return `Unknown action: ${action}`;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
var init_memory_skill = __esm({
|
|
1175
|
+
"src/skills/builtin/memory_skill.ts"() {
|
|
1176
|
+
"use strict";
|
|
1177
|
+
init_registry();
|
|
1178
|
+
init_memory();
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
// src/skills/builtin/browser.ts
|
|
1183
|
+
var browser_exports = {};
|
|
1184
|
+
__export(browser_exports, {
|
|
1185
|
+
registerBrowserSkill: () => registerBrowserSkill
|
|
1186
|
+
});
|
|
1187
|
+
import { exec as exec2 } from "child_process";
|
|
1188
|
+
function registerBrowserSkill() {
|
|
1189
|
+
registerSkill(
|
|
1190
|
+
{ name: "browser", description: "Browser control and web automation", version: "1.0.0", source: "bundled", enabled: true },
|
|
1191
|
+
{
|
|
1192
|
+
name: "browser",
|
|
1193
|
+
description: "Control a Chromium-based browser to navigate websites, take snapshots, click elements, type text, evaluate JavaScript, and extract page content. Uses Chrome DevTools Protocol (CDP).",
|
|
1194
|
+
parameters: {
|
|
1195
|
+
type: "object",
|
|
1196
|
+
properties: {
|
|
1197
|
+
action: {
|
|
1198
|
+
type: "string",
|
|
1199
|
+
enum: ["navigate", "snapshot", "click", "type", "evaluate", "extract", "screenshot"],
|
|
1200
|
+
description: "Browser action to perform"
|
|
1201
|
+
},
|
|
1202
|
+
url: { type: "string", description: "URL to navigate to (for navigate action)" },
|
|
1203
|
+
selector: { type: "string", description: "CSS selector for click/type actions" },
|
|
1204
|
+
text: { type: "string", description: "Text to type (for type action)" },
|
|
1205
|
+
script: { type: "string", description: "JavaScript code to evaluate (for evaluate action)" }
|
|
1206
|
+
},
|
|
1207
|
+
required: ["action"]
|
|
1208
|
+
},
|
|
1209
|
+
execute: async (args) => {
|
|
1210
|
+
const action = args.action;
|
|
1211
|
+
switch (action) {
|
|
1212
|
+
case "navigate": {
|
|
1213
|
+
const url = args.url;
|
|
1214
|
+
if (!url) return "Error: url is required";
|
|
1215
|
+
logger_default.info(COMPONENT9, `Navigating to: ${url}`);
|
|
1216
|
+
return new Promise((resolve2) => {
|
|
1217
|
+
exec2(`curl -sL --max-time 15 "${url}" | head -c 50000`, { timeout: 2e4 }, (err, stdout) => {
|
|
1218
|
+
if (err) {
|
|
1219
|
+
resolve2(`Error fetching ${url}: ${err.message}`);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const text = stdout.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
1223
|
+
resolve2(`Page content from ${url}:
|
|
1224
|
+
${text.slice(0, 2e4)}`);
|
|
1225
|
+
});
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
case "snapshot":
|
|
1229
|
+
case "extract": {
|
|
1230
|
+
const url = args.url;
|
|
1231
|
+
if (!url) return "Error: url is required for snapshot/extract";
|
|
1232
|
+
return new Promise((resolve2) => {
|
|
1233
|
+
exec2(`curl -sL --max-time 15 "${url}" | head -c 50000`, { timeout: 2e4 }, (err, stdout) => {
|
|
1234
|
+
if (err) {
|
|
1235
|
+
resolve2(`Error: ${err.message}`);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
const title = stdout.match(/<title[^>]*>(.*?)<\/title>/i)?.[1] || "No title";
|
|
1239
|
+
const desc = stdout.match(/<meta[^>]*name="description"[^>]*content="([^"]*)"[^>]*>/i)?.[1] || "";
|
|
1240
|
+
const links = [];
|
|
1241
|
+
const linkRegex = /<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
|
|
1242
|
+
let m;
|
|
1243
|
+
while ((m = linkRegex.exec(stdout)) !== null && links.length < 20) {
|
|
1244
|
+
const text = m[2].replace(/<[^>]*>/g, "").trim();
|
|
1245
|
+
if (text && m[1] && !m[1].startsWith("#")) links.push(` ${text}: ${m[1]}`);
|
|
1246
|
+
}
|
|
1247
|
+
resolve2(`Page: ${title}
|
|
1248
|
+
Description: ${desc}
|
|
1249
|
+
Links:
|
|
1250
|
+
${links.join("\n")}`);
|
|
1251
|
+
});
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
case "evaluate": {
|
|
1255
|
+
const script = args.script;
|
|
1256
|
+
if (!script) return "Error: script is required";
|
|
1257
|
+
return `Note: Full CDP browser evaluation requires a running browser session. Script queued: ${script.slice(0, 200)}`;
|
|
1258
|
+
}
|
|
1259
|
+
case "screenshot": {
|
|
1260
|
+
const url = args.url;
|
|
1261
|
+
if (!url) return "Error: url is required";
|
|
1262
|
+
return `Screenshot capture requires CDP connection to a running Chromium instance. Target: ${url}`;
|
|
1263
|
+
}
|
|
1264
|
+
case "click": {
|
|
1265
|
+
const selector = args.selector;
|
|
1266
|
+
if (!selector) return "Error: selector is required";
|
|
1267
|
+
return `Click action requires CDP connection. Selector: ${selector}`;
|
|
1268
|
+
}
|
|
1269
|
+
case "type": {
|
|
1270
|
+
const selector = args.selector;
|
|
1271
|
+
const text = args.text;
|
|
1272
|
+
if (!selector || !text) return "Error: selector and text are required";
|
|
1273
|
+
return `Type action requires CDP connection. Selector: ${selector}, Text: ${text}`;
|
|
1274
|
+
}
|
|
1275
|
+
default:
|
|
1276
|
+
return `Unknown browser action: ${action}`;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
var COMPONENT9;
|
|
1283
|
+
var init_browser = __esm({
|
|
1284
|
+
"src/skills/builtin/browser.ts"() {
|
|
1285
|
+
"use strict";
|
|
1286
|
+
init_registry();
|
|
1287
|
+
init_logger();
|
|
1288
|
+
COMPONENT9 = "Browser";
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
// src/agent/session.ts
|
|
1293
|
+
import { v4 as uuid3 } from "uuid";
|
|
1294
|
+
function getOrCreateSession(channel, userId, agentId = "default", isEncrypted = false) {
|
|
1295
|
+
const sessionKey = `${channel}:${userId}:${agentId}`;
|
|
1296
|
+
const cached = activeSessions.get(sessionKey);
|
|
1297
|
+
if (cached && cached.status === "active") {
|
|
1298
|
+
return cached;
|
|
1299
|
+
}
|
|
1300
|
+
const store2 = getDb();
|
|
1301
|
+
const existing = store2.sessions.find(
|
|
1302
|
+
(s) => s.channel === channel && s.user_id === userId && s.agent_id === agentId && s.status === "active"
|
|
1303
|
+
);
|
|
1304
|
+
if (existing) {
|
|
1305
|
+
const lastActive = new Date(existing.last_active || existing.created_at).getTime();
|
|
1306
|
+
if (Date.now() - lastActive > SESSION_TIMEOUT_MS) {
|
|
1307
|
+
existing.status = "idle";
|
|
1308
|
+
logger_default.debug(COMPONENT10, `Session ${existing.id} timed out, creating new one`);
|
|
1309
|
+
} else {
|
|
1310
|
+
const session2 = {
|
|
1311
|
+
id: existing.id,
|
|
1312
|
+
channel: existing.channel,
|
|
1313
|
+
userId: existing.user_id,
|
|
1314
|
+
agentId: existing.agent_id,
|
|
1315
|
+
status: existing.status,
|
|
1316
|
+
messageCount: existing.message_count,
|
|
1317
|
+
createdAt: existing.created_at,
|
|
1318
|
+
lastActive: existing.last_active,
|
|
1319
|
+
// Note: If a session was encrypted but dropped from memory, we cannot recover the key
|
|
1320
|
+
// A robust implementation would involve key exchange, but for now we warn:
|
|
1321
|
+
e2eKey: void 0
|
|
1322
|
+
};
|
|
1323
|
+
if (isEncrypted) {
|
|
1324
|
+
logger_default.warn(COMPONENT10, `Recovered session ${existing.id}, but E2E key was lost from memory.`);
|
|
1325
|
+
}
|
|
1326
|
+
activeSessions.set(sessionKey, session2);
|
|
1327
|
+
return session2;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
const session = {
|
|
1331
|
+
id: uuid3(),
|
|
1332
|
+
channel,
|
|
1333
|
+
userId,
|
|
1334
|
+
agentId,
|
|
1335
|
+
status: "active",
|
|
1336
|
+
messageCount: 0,
|
|
1337
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1338
|
+
lastActive: (/* @__PURE__ */ new Date()).toISOString()
|
|
1339
|
+
};
|
|
1340
|
+
if (isEncrypted) {
|
|
1341
|
+
Promise.resolve().then(() => (init_encryption(), encryption_exports)).then(({ generateKey: generateKey2 }) => {
|
|
1342
|
+
session.e2eKey = generateKey2().toString("base64");
|
|
1343
|
+
logger_default.info(COMPONENT10, `Generated E2E key for session ${session.id}`);
|
|
1344
|
+
}).catch((err) => {
|
|
1345
|
+
logger_default.error(COMPONENT10, `Failed to load encryption module: ${err}`);
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
store2.sessions.push({
|
|
1349
|
+
id: session.id,
|
|
1350
|
+
channel,
|
|
1351
|
+
user_id: userId,
|
|
1352
|
+
agent_id: agentId,
|
|
1353
|
+
status: "active",
|
|
1354
|
+
message_count: 0,
|
|
1355
|
+
created_at: session.createdAt,
|
|
1356
|
+
last_active: session.lastActive
|
|
1357
|
+
});
|
|
1358
|
+
activeSessions.set(sessionKey, session);
|
|
1359
|
+
logger_default.info(COMPONENT10, `Created new session: ${session.id} (${channel}/${userId})`);
|
|
1360
|
+
return session;
|
|
1361
|
+
}
|
|
1362
|
+
function addMessage(session, role, content, extra) {
|
|
1363
|
+
const messageId = uuid3();
|
|
1364
|
+
saveMessage({
|
|
1365
|
+
id: messageId,
|
|
1366
|
+
sessionId: session.id,
|
|
1367
|
+
role,
|
|
1368
|
+
content,
|
|
1369
|
+
toolCalls: extra?.toolCalls,
|
|
1370
|
+
toolCallId: extra?.toolCallId,
|
|
1371
|
+
model: extra?.model,
|
|
1372
|
+
tokenCount: extra?.tokenCount || 0
|
|
1373
|
+
}, session.e2eKey);
|
|
1374
|
+
session.messageCount++;
|
|
1375
|
+
session.lastActive = (/* @__PURE__ */ new Date()).toISOString();
|
|
1376
|
+
const store2 = getDb();
|
|
1377
|
+
const sessionRec = store2.sessions.find((s) => s.id === session.id);
|
|
1378
|
+
if (sessionRec) {
|
|
1379
|
+
sessionRec.message_count = session.messageCount;
|
|
1380
|
+
sessionRec.last_active = session.lastActive;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
function getContextMessages(session, maxMessages = MAX_CONTEXT_MESSAGES) {
|
|
1384
|
+
const history = getHistory(session.id, maxMessages, session.e2eKey);
|
|
1385
|
+
return history.map((msg) => ({
|
|
1386
|
+
role: msg.role,
|
|
1387
|
+
content: msg.content,
|
|
1388
|
+
toolCallId: msg.toolCallId || void 0,
|
|
1389
|
+
toolCalls: msg.toolCalls ? JSON.parse(msg.toolCalls) : void 0
|
|
1390
|
+
}));
|
|
1391
|
+
}
|
|
1392
|
+
function listSessions() {
|
|
1393
|
+
const store2 = getDb();
|
|
1394
|
+
return store2.sessions.filter((s) => s.status === "active").sort((a, b) => b.last_active.localeCompare(a.last_active)).map((s) => ({
|
|
1395
|
+
id: s.id,
|
|
1396
|
+
channel: s.channel,
|
|
1397
|
+
userId: s.user_id,
|
|
1398
|
+
agentId: s.agent_id,
|
|
1399
|
+
status: s.status,
|
|
1400
|
+
messageCount: s.message_count,
|
|
1401
|
+
createdAt: s.created_at,
|
|
1402
|
+
lastActive: s.last_active
|
|
1403
|
+
}));
|
|
1404
|
+
}
|
|
1405
|
+
function closeSession(sessionId) {
|
|
1406
|
+
const store2 = getDb();
|
|
1407
|
+
const sessionRec = store2.sessions.find((s) => s.id === sessionId);
|
|
1408
|
+
if (sessionRec) {
|
|
1409
|
+
sessionRec.status = "closed";
|
|
1410
|
+
}
|
|
1411
|
+
for (const [key, session] of activeSessions) {
|
|
1412
|
+
if (session.id === sessionId) {
|
|
1413
|
+
activeSessions.delete(key);
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
logger_default.info(COMPONENT10, `Closed session: ${sessionId}`);
|
|
1418
|
+
}
|
|
1419
|
+
var COMPONENT10, activeSessions;
|
|
1420
|
+
var init_session = __esm({
|
|
1421
|
+
"src/agent/session.ts"() {
|
|
1422
|
+
"use strict";
|
|
1423
|
+
init_memory();
|
|
1424
|
+
init_constants();
|
|
1425
|
+
init_logger();
|
|
1426
|
+
COMPONENT10 = "Session";
|
|
1427
|
+
activeSessions = /* @__PURE__ */ new Map();
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
// src/providers/base.ts
|
|
1432
|
+
var LLMProvider;
|
|
1433
|
+
var init_base = __esm({
|
|
1434
|
+
"src/providers/base.ts"() {
|
|
1435
|
+
"use strict";
|
|
1436
|
+
LLMProvider = class {
|
|
1437
|
+
/** Get the provider identifier from a model string like "anthropic/claude-3" */
|
|
1438
|
+
static parseModelId(modelId) {
|
|
1439
|
+
const parts = modelId.split("/");
|
|
1440
|
+
if (parts.length >= 2) {
|
|
1441
|
+
return { provider: parts[0], model: parts.slice(1).join("/") };
|
|
1442
|
+
}
|
|
1443
|
+
return { provider: "anthropic", model: modelId };
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
// src/providers/anthropic.ts
|
|
1450
|
+
import { v4 as uuid4 } from "uuid";
|
|
1451
|
+
var COMPONENT11, AnthropicProvider;
|
|
1452
|
+
var init_anthropic = __esm({
|
|
1453
|
+
"src/providers/anthropic.ts"() {
|
|
1454
|
+
"use strict";
|
|
1455
|
+
init_base();
|
|
1456
|
+
init_config();
|
|
1457
|
+
init_logger();
|
|
1458
|
+
COMPONENT11 = "Anthropic";
|
|
1459
|
+
AnthropicProvider = class extends LLMProvider {
|
|
1460
|
+
name = "anthropic";
|
|
1461
|
+
displayName = "Anthropic (Claude)";
|
|
1462
|
+
get apiKey() {
|
|
1463
|
+
const config = loadConfig();
|
|
1464
|
+
return config.providers.anthropic.apiKey || process.env.ANTHROPIC_API_KEY || "";
|
|
1465
|
+
}
|
|
1466
|
+
get baseUrl() {
|
|
1467
|
+
const config = loadConfig();
|
|
1468
|
+
return config.providers.anthropic.baseUrl || "https://api.anthropic.com";
|
|
1469
|
+
}
|
|
1470
|
+
async chat(options) {
|
|
1471
|
+
const model = options.model || "claude-sonnet-4-20250514";
|
|
1472
|
+
const apiKey = this.apiKey;
|
|
1473
|
+
if (!apiKey) throw new Error("Anthropic API key not configured");
|
|
1474
|
+
logger_default.debug(COMPONENT11, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
1475
|
+
const systemMessage = options.messages.find((m) => m.role === "system");
|
|
1476
|
+
const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
|
|
1477
|
+
const body = {
|
|
1478
|
+
model: model.replace("anthropic/", ""),
|
|
1479
|
+
max_tokens: options.maxTokens || 8192,
|
|
1480
|
+
messages: nonSystemMessages.map((m) => ({
|
|
1481
|
+
role: m.role === "tool" ? "user" : m.role,
|
|
1482
|
+
content: m.role === "tool" ? [{ type: "tool_result", tool_use_id: m.toolCallId, content: m.content }] : m.content
|
|
1483
|
+
}))
|
|
1484
|
+
};
|
|
1485
|
+
if (systemMessage) {
|
|
1486
|
+
body.system = systemMessage.content;
|
|
1487
|
+
}
|
|
1488
|
+
if (options.tools && options.tools.length > 0) {
|
|
1489
|
+
body.tools = options.tools.map((t) => ({
|
|
1490
|
+
name: t.function.name,
|
|
1491
|
+
description: t.function.description,
|
|
1492
|
+
input_schema: t.function.parameters
|
|
1493
|
+
}));
|
|
1494
|
+
}
|
|
1495
|
+
if (options.temperature !== void 0) {
|
|
1496
|
+
body.temperature = options.temperature;
|
|
1497
|
+
}
|
|
1498
|
+
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
1499
|
+
method: "POST",
|
|
1500
|
+
headers: {
|
|
1501
|
+
"Content-Type": "application/json",
|
|
1502
|
+
"x-api-key": apiKey,
|
|
1503
|
+
"anthropic-version": "2023-06-01"
|
|
1504
|
+
},
|
|
1505
|
+
body: JSON.stringify(body)
|
|
1506
|
+
});
|
|
1507
|
+
if (!response.ok) {
|
|
1508
|
+
const errorText = await response.text();
|
|
1509
|
+
throw new Error(`Anthropic API error (${response.status}): ${errorText}`);
|
|
1510
|
+
}
|
|
1511
|
+
const data = await response.json();
|
|
1512
|
+
const content = data.content;
|
|
1513
|
+
let textContent = "";
|
|
1514
|
+
const toolCalls = [];
|
|
1515
|
+
for (const block of content) {
|
|
1516
|
+
if (block.type === "text") {
|
|
1517
|
+
textContent += block.text;
|
|
1518
|
+
} else if (block.type === "tool_use") {
|
|
1519
|
+
toolCalls.push({
|
|
1520
|
+
id: block.id,
|
|
1521
|
+
type: "function",
|
|
1522
|
+
function: {
|
|
1523
|
+
name: block.name,
|
|
1524
|
+
arguments: JSON.stringify(block.input)
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
const usage = data.usage;
|
|
1530
|
+
return {
|
|
1531
|
+
id: data.id || uuid4(),
|
|
1532
|
+
content: textContent,
|
|
1533
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1534
|
+
usage: usage ? {
|
|
1535
|
+
promptTokens: usage.input_tokens,
|
|
1536
|
+
completionTokens: usage.output_tokens,
|
|
1537
|
+
totalTokens: usage.input_tokens + usage.output_tokens
|
|
1538
|
+
} : void 0,
|
|
1539
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
|
|
1540
|
+
model
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
async *chatStream(options) {
|
|
1544
|
+
try {
|
|
1545
|
+
const response = await this.chat(options);
|
|
1546
|
+
if (response.content) {
|
|
1547
|
+
yield { type: "text", content: response.content };
|
|
1548
|
+
}
|
|
1549
|
+
if (response.toolCalls) {
|
|
1550
|
+
for (const tc of response.toolCalls) {
|
|
1551
|
+
yield { type: "tool_call", toolCall: tc };
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
yield { type: "done" };
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
yield { type: "error", error: error.message };
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
async listModels() {
|
|
1560
|
+
return [
|
|
1561
|
+
"claude-opus-4-0",
|
|
1562
|
+
"claude-sonnet-4-20250514",
|
|
1563
|
+
"claude-haiku-4-20250414",
|
|
1564
|
+
"claude-3-5-sonnet-20241022",
|
|
1565
|
+
"claude-3-5-haiku-20241022"
|
|
1566
|
+
];
|
|
1567
|
+
}
|
|
1568
|
+
async healthCheck() {
|
|
1569
|
+
try {
|
|
1570
|
+
if (!this.apiKey) return false;
|
|
1571
|
+
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
1572
|
+
method: "POST",
|
|
1573
|
+
headers: {
|
|
1574
|
+
"Content-Type": "application/json",
|
|
1575
|
+
"x-api-key": this.apiKey,
|
|
1576
|
+
"anthropic-version": "2023-06-01"
|
|
1577
|
+
},
|
|
1578
|
+
body: JSON.stringify({
|
|
1579
|
+
model: "claude-haiku-4-20250414",
|
|
1580
|
+
max_tokens: 1,
|
|
1581
|
+
messages: [{ role: "user", content: "ping" }]
|
|
1582
|
+
})
|
|
1583
|
+
});
|
|
1584
|
+
return response.ok || response.status === 400;
|
|
1585
|
+
} catch {
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
// src/providers/openai.ts
|
|
1594
|
+
import { v4 as uuid5 } from "uuid";
|
|
1595
|
+
var COMPONENT12, OpenAIProvider;
|
|
1596
|
+
var init_openai = __esm({
|
|
1597
|
+
"src/providers/openai.ts"() {
|
|
1598
|
+
"use strict";
|
|
1599
|
+
init_base();
|
|
1600
|
+
init_config();
|
|
1601
|
+
init_logger();
|
|
1602
|
+
COMPONENT12 = "OpenAI";
|
|
1603
|
+
OpenAIProvider = class extends LLMProvider {
|
|
1604
|
+
name = "openai";
|
|
1605
|
+
displayName = "OpenAI (GPT)";
|
|
1606
|
+
get apiKey() {
|
|
1607
|
+
const config = loadConfig();
|
|
1608
|
+
return config.providers.openai.apiKey || process.env.OPENAI_API_KEY || "";
|
|
1609
|
+
}
|
|
1610
|
+
get baseUrl() {
|
|
1611
|
+
const config = loadConfig();
|
|
1612
|
+
return config.providers.openai.baseUrl || "https://api.openai.com";
|
|
1613
|
+
}
|
|
1614
|
+
async chat(options) {
|
|
1615
|
+
const model = options.model || "gpt-4o";
|
|
1616
|
+
const apiKey = this.apiKey;
|
|
1617
|
+
if (!apiKey) throw new Error("OpenAI API key not configured");
|
|
1618
|
+
logger_default.debug(COMPONENT12, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
1619
|
+
const body = {
|
|
1620
|
+
model: model.replace("openai/", ""),
|
|
1621
|
+
messages: options.messages.map((m) => {
|
|
1622
|
+
if (m.role === "tool") {
|
|
1623
|
+
return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
|
|
1624
|
+
}
|
|
1625
|
+
if (m.role === "assistant" && m.toolCalls) {
|
|
1626
|
+
return {
|
|
1627
|
+
role: "assistant",
|
|
1628
|
+
content: m.content || null,
|
|
1629
|
+
tool_calls: m.toolCalls.map((tc) => ({
|
|
1630
|
+
id: tc.id,
|
|
1631
|
+
type: "function",
|
|
1632
|
+
function: { name: tc.function.name, arguments: tc.function.arguments }
|
|
1633
|
+
}))
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
return { role: m.role, content: m.content };
|
|
1637
|
+
}),
|
|
1638
|
+
max_tokens: options.maxTokens || 8192
|
|
1639
|
+
};
|
|
1640
|
+
if (options.tools && options.tools.length > 0) {
|
|
1641
|
+
body.tools = options.tools;
|
|
1642
|
+
}
|
|
1643
|
+
if (options.temperature !== void 0) {
|
|
1644
|
+
body.temperature = options.temperature;
|
|
1645
|
+
}
|
|
1646
|
+
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
1647
|
+
method: "POST",
|
|
1648
|
+
headers: {
|
|
1649
|
+
"Content-Type": "application/json",
|
|
1650
|
+
Authorization: `Bearer ${apiKey}`
|
|
1651
|
+
},
|
|
1652
|
+
body: JSON.stringify(body)
|
|
1653
|
+
});
|
|
1654
|
+
if (!response.ok) {
|
|
1655
|
+
const errorText = await response.text();
|
|
1656
|
+
throw new Error(`OpenAI API error (${response.status}): ${errorText}`);
|
|
1657
|
+
}
|
|
1658
|
+
const data = await response.json();
|
|
1659
|
+
const choices = data.choices;
|
|
1660
|
+
const choice = choices[0];
|
|
1661
|
+
const message = choice.message;
|
|
1662
|
+
const toolCalls = [];
|
|
1663
|
+
if (message.tool_calls) {
|
|
1664
|
+
for (const tc of message.tool_calls) {
|
|
1665
|
+
const fn = tc.function;
|
|
1666
|
+
toolCalls.push({
|
|
1667
|
+
id: tc.id,
|
|
1668
|
+
type: "function",
|
|
1669
|
+
function: { name: fn.name, arguments: fn.arguments }
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
const usage = data.usage;
|
|
1674
|
+
return {
|
|
1675
|
+
id: data.id || uuid5(),
|
|
1676
|
+
content: message.content || "",
|
|
1677
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1678
|
+
usage: usage ? {
|
|
1679
|
+
promptTokens: usage.prompt_tokens,
|
|
1680
|
+
completionTokens: usage.completion_tokens,
|
|
1681
|
+
totalTokens: usage.total_tokens
|
|
1682
|
+
} : void 0,
|
|
1683
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : choice.finish_reason || "stop",
|
|
1684
|
+
model
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
async *chatStream(options) {
|
|
1688
|
+
try {
|
|
1689
|
+
const response = await this.chat(options);
|
|
1690
|
+
if (response.content) {
|
|
1691
|
+
yield { type: "text", content: response.content };
|
|
1692
|
+
}
|
|
1693
|
+
if (response.toolCalls) {
|
|
1694
|
+
for (const tc of response.toolCalls) {
|
|
1695
|
+
yield { type: "tool_call", toolCall: tc };
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
yield { type: "done" };
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
yield { type: "error", error: error.message };
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
async listModels() {
|
|
1704
|
+
return ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1", "o1-mini", "o3-mini"];
|
|
1705
|
+
}
|
|
1706
|
+
async healthCheck() {
|
|
1707
|
+
try {
|
|
1708
|
+
if (!this.apiKey) return false;
|
|
1709
|
+
const response = await fetch(`${this.baseUrl}/v1/models`, {
|
|
1710
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
1711
|
+
});
|
|
1712
|
+
return response.ok;
|
|
1713
|
+
} catch {
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1721
|
+
// src/providers/google.ts
|
|
1722
|
+
import { v4 as uuid6 } from "uuid";
|
|
1723
|
+
var COMPONENT13, GoogleProvider;
|
|
1724
|
+
var init_google = __esm({
|
|
1725
|
+
"src/providers/google.ts"() {
|
|
1726
|
+
"use strict";
|
|
1727
|
+
init_base();
|
|
1728
|
+
init_config();
|
|
1729
|
+
init_logger();
|
|
1730
|
+
COMPONENT13 = "Google";
|
|
1731
|
+
GoogleProvider = class extends LLMProvider {
|
|
1732
|
+
name = "google";
|
|
1733
|
+
displayName = "Google (Gemini)";
|
|
1734
|
+
get apiKey() {
|
|
1735
|
+
const config = loadConfig();
|
|
1736
|
+
return config.providers.google.apiKey || process.env.GOOGLE_API_KEY || "";
|
|
1737
|
+
}
|
|
1738
|
+
async chat(options) {
|
|
1739
|
+
const model = (options.model || "gemini-2.0-flash").replace("google/", "");
|
|
1740
|
+
const apiKey = this.apiKey;
|
|
1741
|
+
if (!apiKey) throw new Error("Google API key not configured");
|
|
1742
|
+
logger_default.debug(COMPONENT13, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
1743
|
+
const systemInstruction = options.messages.find((m) => m.role === "system")?.content;
|
|
1744
|
+
const contents = options.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
1745
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
1746
|
+
parts: [{ text: m.content }]
|
|
1747
|
+
}));
|
|
1748
|
+
const body = {
|
|
1749
|
+
contents,
|
|
1750
|
+
generationConfig: {
|
|
1751
|
+
maxOutputTokens: options.maxTokens || 8192,
|
|
1752
|
+
temperature: options.temperature ?? 0.7
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
if (systemInstruction) {
|
|
1756
|
+
body.systemInstruction = { parts: [{ text: systemInstruction }] };
|
|
1757
|
+
}
|
|
1758
|
+
if (options.tools && options.tools.length > 0) {
|
|
1759
|
+
body.tools = [{
|
|
1760
|
+
functionDeclarations: options.tools.map((t) => ({
|
|
1761
|
+
name: t.function.name,
|
|
1762
|
+
description: t.function.description,
|
|
1763
|
+
parameters: t.function.parameters
|
|
1764
|
+
}))
|
|
1765
|
+
}];
|
|
1766
|
+
}
|
|
1767
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
|
|
1768
|
+
const response = await fetch(url, {
|
|
1769
|
+
method: "POST",
|
|
1770
|
+
headers: { "Content-Type": "application/json" },
|
|
1771
|
+
body: JSON.stringify(body)
|
|
1772
|
+
});
|
|
1773
|
+
if (!response.ok) {
|
|
1774
|
+
const errorText = await response.text();
|
|
1775
|
+
throw new Error(`Google API error (${response.status}): ${errorText}`);
|
|
1776
|
+
}
|
|
1777
|
+
const data = await response.json();
|
|
1778
|
+
const candidates = data.candidates;
|
|
1779
|
+
let textContent = "";
|
|
1780
|
+
const toolCalls = [];
|
|
1781
|
+
if (candidates && candidates.length > 0) {
|
|
1782
|
+
const parts = candidates[0].content?.parts || [];
|
|
1783
|
+
for (const part of parts) {
|
|
1784
|
+
if (part.text) {
|
|
1785
|
+
textContent += part.text;
|
|
1786
|
+
}
|
|
1787
|
+
if (part.functionCall) {
|
|
1788
|
+
const fc = part.functionCall;
|
|
1789
|
+
toolCalls.push({
|
|
1790
|
+
id: uuid6(),
|
|
1791
|
+
type: "function",
|
|
1792
|
+
function: {
|
|
1793
|
+
name: fc.name,
|
|
1794
|
+
arguments: JSON.stringify(fc.args)
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
const usageMeta = data.usageMetadata;
|
|
1801
|
+
return {
|
|
1802
|
+
id: uuid6(),
|
|
1803
|
+
content: textContent,
|
|
1804
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1805
|
+
usage: usageMeta ? {
|
|
1806
|
+
promptTokens: usageMeta.promptTokenCount || 0,
|
|
1807
|
+
completionTokens: usageMeta.candidatesTokenCount || 0,
|
|
1808
|
+
totalTokens: usageMeta.totalTokenCount || 0
|
|
1809
|
+
} : void 0,
|
|
1810
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
|
|
1811
|
+
model: `google/${model}`
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
async *chatStream(options) {
|
|
1815
|
+
try {
|
|
1816
|
+
const response = await this.chat(options);
|
|
1817
|
+
if (response.content) yield { type: "text", content: response.content };
|
|
1818
|
+
if (response.toolCalls) {
|
|
1819
|
+
for (const tc of response.toolCalls) yield { type: "tool_call", toolCall: tc };
|
|
1820
|
+
}
|
|
1821
|
+
yield { type: "done" };
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
yield { type: "error", error: error.message };
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
async listModels() {
|
|
1827
|
+
return ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"];
|
|
1828
|
+
}
|
|
1829
|
+
async healthCheck() {
|
|
1830
|
+
try {
|
|
1831
|
+
if (!this.apiKey) return false;
|
|
1832
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;
|
|
1833
|
+
const response = await fetch(url);
|
|
1834
|
+
return response.ok;
|
|
1835
|
+
} catch {
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
// src/providers/ollama.ts
|
|
1844
|
+
import { v4 as uuid7 } from "uuid";
|
|
1845
|
+
var COMPONENT14, OllamaProvider;
|
|
1846
|
+
var init_ollama = __esm({
|
|
1847
|
+
"src/providers/ollama.ts"() {
|
|
1848
|
+
"use strict";
|
|
1849
|
+
init_base();
|
|
1850
|
+
init_config();
|
|
1851
|
+
init_logger();
|
|
1852
|
+
COMPONENT14 = "Ollama";
|
|
1853
|
+
OllamaProvider = class extends LLMProvider {
|
|
1854
|
+
name = "ollama";
|
|
1855
|
+
displayName = "Ollama (Local)";
|
|
1856
|
+
get baseUrl() {
|
|
1857
|
+
const config = loadConfig();
|
|
1858
|
+
return config.providers.ollama.baseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
|
|
1859
|
+
}
|
|
1860
|
+
async chat(options) {
|
|
1861
|
+
const model = (options.model || "llama3.1").replace("ollama/", "");
|
|
1862
|
+
logger_default.debug(COMPONENT14, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
1863
|
+
const body = {
|
|
1864
|
+
model,
|
|
1865
|
+
messages: options.messages.map((m) => ({
|
|
1866
|
+
role: m.role,
|
|
1867
|
+
content: m.content
|
|
1868
|
+
})),
|
|
1869
|
+
stream: false,
|
|
1870
|
+
options: {
|
|
1871
|
+
num_predict: options.maxTokens || 8192,
|
|
1872
|
+
temperature: options.temperature ?? 0.7
|
|
1873
|
+
}
|
|
1874
|
+
};
|
|
1875
|
+
if (options.tools && options.tools.length > 0) {
|
|
1876
|
+
body.tools = options.tools.map((t) => ({
|
|
1877
|
+
type: "function",
|
|
1878
|
+
function: {
|
|
1879
|
+
name: t.function.name,
|
|
1880
|
+
description: t.function.description,
|
|
1881
|
+
parameters: t.function.parameters
|
|
1882
|
+
}
|
|
1883
|
+
}));
|
|
1884
|
+
}
|
|
1885
|
+
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
1886
|
+
method: "POST",
|
|
1887
|
+
headers: { "Content-Type": "application/json" },
|
|
1888
|
+
body: JSON.stringify(body)
|
|
1889
|
+
});
|
|
1890
|
+
if (!response.ok) {
|
|
1891
|
+
const errorText = await response.text();
|
|
1892
|
+
throw new Error(`Ollama error (${response.status}): ${errorText}`);
|
|
1893
|
+
}
|
|
1894
|
+
const data = await response.json();
|
|
1895
|
+
const message = data.message;
|
|
1896
|
+
const toolCalls = [];
|
|
1897
|
+
if (message.tool_calls) {
|
|
1898
|
+
for (const tc of message.tool_calls) {
|
|
1899
|
+
const fn = tc.function;
|
|
1900
|
+
toolCalls.push({
|
|
1901
|
+
id: uuid7(),
|
|
1902
|
+
type: "function",
|
|
1903
|
+
function: {
|
|
1904
|
+
name: fn.name,
|
|
1905
|
+
arguments: JSON.stringify(fn.arguments)
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
id: uuid7(),
|
|
1912
|
+
content: message.content || "",
|
|
1913
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
1914
|
+
usage: {
|
|
1915
|
+
promptTokens: data.prompt_eval_count || 0,
|
|
1916
|
+
completionTokens: data.eval_count || 0,
|
|
1917
|
+
totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)
|
|
1918
|
+
},
|
|
1919
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
|
|
1920
|
+
model: `ollama/${model}`
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
async *chatStream(options) {
|
|
1924
|
+
try {
|
|
1925
|
+
const response = await this.chat(options);
|
|
1926
|
+
if (response.content) yield { type: "text", content: response.content };
|
|
1927
|
+
if (response.toolCalls) {
|
|
1928
|
+
for (const tc of response.toolCalls) yield { type: "tool_call", toolCall: tc };
|
|
1929
|
+
}
|
|
1930
|
+
yield { type: "done" };
|
|
1931
|
+
} catch (error) {
|
|
1932
|
+
yield { type: "error", error: error.message };
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
async listModels() {
|
|
1936
|
+
try {
|
|
1937
|
+
const response = await fetch(`${this.baseUrl}/api/tags`);
|
|
1938
|
+
if (!response.ok) return [];
|
|
1939
|
+
const data = await response.json();
|
|
1940
|
+
return (data.models || []).map((m) => m.name);
|
|
1941
|
+
} catch {
|
|
1942
|
+
return [];
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
async healthCheck() {
|
|
1946
|
+
try {
|
|
1947
|
+
const response = await fetch(`${this.baseUrl}/api/tags`);
|
|
1948
|
+
return response.ok;
|
|
1949
|
+
} catch {
|
|
1950
|
+
return false;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
// src/providers/router.ts
|
|
1958
|
+
function initProviders() {
|
|
1959
|
+
if (initialized) return;
|
|
1960
|
+
providers.set("anthropic", new AnthropicProvider());
|
|
1961
|
+
providers.set("openai", new OpenAIProvider());
|
|
1962
|
+
providers.set("google", new GoogleProvider());
|
|
1963
|
+
providers.set("ollama", new OllamaProvider());
|
|
1964
|
+
initialized = true;
|
|
1965
|
+
}
|
|
1966
|
+
function resolveModel(modelId) {
|
|
1967
|
+
initProviders();
|
|
1968
|
+
const { provider: providerName, model } = LLMProvider.parseModelId(modelId);
|
|
1969
|
+
const provider = providers.get(providerName);
|
|
1970
|
+
if (!provider) {
|
|
1971
|
+
throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(", ")}`);
|
|
1972
|
+
}
|
|
1973
|
+
return { provider, model };
|
|
1974
|
+
}
|
|
1975
|
+
async function chat(options) {
|
|
1976
|
+
const modelId = options.model || "anthropic/claude-sonnet-4-20250514";
|
|
1977
|
+
const { provider, model } = resolveModel(modelId);
|
|
1978
|
+
logger_default.info(COMPONENT15, `Routing to ${provider.displayName} (model: ${model})`);
|
|
1979
|
+
try {
|
|
1980
|
+
return await provider.chat({ ...options, model });
|
|
1981
|
+
} catch (error) {
|
|
1982
|
+
logger_default.error(COMPONENT15, `Provider ${provider.name} failed: ${error.message}`);
|
|
1983
|
+
const failoverOrder = ["anthropic", "openai", "google", "ollama"];
|
|
1984
|
+
for (const fallbackName of failoverOrder) {
|
|
1985
|
+
if (fallbackName === provider.name) continue;
|
|
1986
|
+
const fallback = providers.get(fallbackName);
|
|
1987
|
+
if (!fallback) continue;
|
|
1988
|
+
try {
|
|
1989
|
+
const healthy = await fallback.healthCheck();
|
|
1990
|
+
if (!healthy) continue;
|
|
1991
|
+
const models = await fallback.listModels();
|
|
1992
|
+
if (models.length === 0) continue;
|
|
1993
|
+
logger_default.warn(COMPONENT15, `Failing over to ${fallback.displayName} (model: ${models[0]})`);
|
|
1994
|
+
return await fallback.chat({ ...options, model: models[0] });
|
|
1995
|
+
} catch {
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
throw error;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
async function healthCheckAll() {
|
|
2003
|
+
initProviders();
|
|
2004
|
+
const results = {};
|
|
2005
|
+
for (const [name, provider] of providers) {
|
|
2006
|
+
try {
|
|
2007
|
+
results[name] = await provider.healthCheck();
|
|
2008
|
+
} catch {
|
|
2009
|
+
results[name] = false;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return results;
|
|
2013
|
+
}
|
|
2014
|
+
var COMPONENT15, providers, initialized;
|
|
2015
|
+
var init_router = __esm({
|
|
2016
|
+
"src/providers/router.ts"() {
|
|
2017
|
+
"use strict";
|
|
2018
|
+
init_base();
|
|
2019
|
+
init_anthropic();
|
|
2020
|
+
init_openai();
|
|
2021
|
+
init_google();
|
|
2022
|
+
init_ollama();
|
|
2023
|
+
init_logger();
|
|
2024
|
+
COMPONENT15 = "Router";
|
|
2025
|
+
providers = /* @__PURE__ */ new Map();
|
|
2026
|
+
initialized = false;
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
// src/memory/learning.ts
|
|
2031
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2032
|
+
import { join as join4 } from "path";
|
|
2033
|
+
function loadKnowledgeBase() {
|
|
2034
|
+
if (kb) return kb;
|
|
2035
|
+
ensureDir(TITAN_HOME);
|
|
2036
|
+
if (existsSync5(KNOWLEDGE_FILE)) {
|
|
2037
|
+
try {
|
|
2038
|
+
kb = JSON.parse(readFileSync4(KNOWLEDGE_FILE, "utf-8"));
|
|
2039
|
+
kb.entries = kb.entries || [];
|
|
2040
|
+
kb.toolSuccessRates = kb.toolSuccessRates || {};
|
|
2041
|
+
kb.errorPatterns = kb.errorPatterns || {};
|
|
2042
|
+
kb.userCorrections = kb.userCorrections || [];
|
|
2043
|
+
kb.conversationInsights = kb.conversationInsights || [];
|
|
2044
|
+
} catch {
|
|
2045
|
+
kb = createEmptyKB();
|
|
2046
|
+
}
|
|
2047
|
+
} else {
|
|
2048
|
+
kb = createEmptyKB();
|
|
2049
|
+
}
|
|
2050
|
+
return kb;
|
|
2051
|
+
}
|
|
2052
|
+
function createEmptyKB() {
|
|
2053
|
+
return {
|
|
2054
|
+
entries: [],
|
|
2055
|
+
toolSuccessRates: {},
|
|
2056
|
+
errorPatterns: {},
|
|
2057
|
+
userCorrections: [],
|
|
2058
|
+
conversationInsights: []
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function debouncedSave2() {
|
|
2062
|
+
if (saveTimeout2) clearTimeout(saveTimeout2);
|
|
2063
|
+
saveTimeout2 = setTimeout(() => {
|
|
2064
|
+
if (!kb) return;
|
|
2065
|
+
ensureDir(TITAN_HOME);
|
|
2066
|
+
writeFileSync4(KNOWLEDGE_FILE, JSON.stringify(kb, null, 2), "utf-8");
|
|
2067
|
+
}, 2e3);
|
|
2068
|
+
}
|
|
2069
|
+
function recordToolResult(toolName, success, context, error) {
|
|
2070
|
+
const k = loadKnowledgeBase();
|
|
2071
|
+
if (!k.toolSuccessRates[toolName]) {
|
|
2072
|
+
k.toolSuccessRates[toolName] = { success: 0, fail: 0, total: 0 };
|
|
2073
|
+
}
|
|
2074
|
+
k.toolSuccessRates[toolName].total++;
|
|
2075
|
+
if (success) {
|
|
2076
|
+
k.toolSuccessRates[toolName].success++;
|
|
2077
|
+
} else {
|
|
2078
|
+
k.toolSuccessRates[toolName].fail++;
|
|
2079
|
+
if (error) {
|
|
2080
|
+
const pattern = error.slice(0, 200);
|
|
2081
|
+
if (!k.errorPatterns[pattern]) {
|
|
2082
|
+
k.errorPatterns[pattern] = { count: 0, lastSeen: "" };
|
|
2083
|
+
}
|
|
2084
|
+
k.errorPatterns[pattern].count++;
|
|
2085
|
+
k.errorPatterns[pattern].lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
debouncedSave2();
|
|
2089
|
+
}
|
|
2090
|
+
function getToolRecommendations() {
|
|
2091
|
+
const k = loadKnowledgeBase();
|
|
2092
|
+
const recommendations = {};
|
|
2093
|
+
for (const [tool, stats] of Object.entries(k.toolSuccessRates)) {
|
|
2094
|
+
if (stats.total > 0) {
|
|
2095
|
+
recommendations[tool] = stats.success / stats.total;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
return recommendations;
|
|
2099
|
+
}
|
|
2100
|
+
function getLearningContext() {
|
|
2101
|
+
const k = loadKnowledgeBase();
|
|
2102
|
+
const parts = [];
|
|
2103
|
+
const topEntries = k.entries.filter((e) => e.score > 0.6).sort((a, b) => b.score - a.score).slice(0, 10);
|
|
2104
|
+
if (topEntries.length > 0) {
|
|
2105
|
+
parts.push("Key learned facts:");
|
|
2106
|
+
for (const e of topEntries) {
|
|
2107
|
+
parts.push(`- [${e.category}] ${e.content}`);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
const toolRecs = getToolRecommendations();
|
|
2111
|
+
const bestTools = Object.entries(toolRecs).filter(([_, rate]) => rate > 0.8).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
2112
|
+
if (bestTools.length > 0) {
|
|
2113
|
+
parts.push("\nMost reliable tools:");
|
|
2114
|
+
for (const [tool, rate] of bestTools) {
|
|
2115
|
+
parts.push(`- ${tool}: ${Math.round(rate * 100)}% success rate`);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
const frequentErrors = Object.entries(k.errorPatterns).filter(([_, info]) => info.count > 2).sort((a, b) => b[1].count - a[1].count).slice(0, 3);
|
|
2119
|
+
if (frequentErrors.length > 0) {
|
|
2120
|
+
parts.push("\nCommon errors to avoid:");
|
|
2121
|
+
for (const [pattern, info] of frequentErrors) {
|
|
2122
|
+
parts.push(`- ${pattern.slice(0, 100)} (seen ${info.count}x)${info.resolution ? ` \u2192 Fix: ${info.resolution}` : ""}`);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return parts.join("\n");
|
|
2126
|
+
}
|
|
2127
|
+
var KNOWLEDGE_FILE, kb, saveTimeout2;
|
|
2128
|
+
var init_learning = __esm({
|
|
2129
|
+
"src/memory/learning.ts"() {
|
|
2130
|
+
"use strict";
|
|
2131
|
+
init_constants();
|
|
2132
|
+
init_helpers();
|
|
2133
|
+
init_logger();
|
|
2134
|
+
KNOWLEDGE_FILE = join4(TITAN_HOME, "knowledge.json");
|
|
2135
|
+
kb = null;
|
|
2136
|
+
saveTimeout2 = null;
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2139
|
+
|
|
2140
|
+
// src/agent/agent.ts
|
|
2141
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
2142
|
+
function readPromptFile(path2) {
|
|
2143
|
+
try {
|
|
2144
|
+
if (existsSync6(path2)) return readFileSync5(path2, "utf-8");
|
|
2145
|
+
} catch {
|
|
2146
|
+
}
|
|
2147
|
+
return "";
|
|
2148
|
+
}
|
|
2149
|
+
function buildSystemPrompt(config) {
|
|
2150
|
+
const customPrompt = config.agent.systemPrompt || "";
|
|
2151
|
+
const memories = searchMemories("preference");
|
|
2152
|
+
const memoryContext = memories.length > 0 ? `
|
|
2153
|
+
|
|
2154
|
+
User preferences I remember:
|
|
2155
|
+
${memories.map((m) => `- ${m.key}: ${m.value}`).join("\n")}` : "";
|
|
2156
|
+
const agentsMd = readPromptFile(AGENTS_MD);
|
|
2157
|
+
const soulMd = readPromptFile(SOUL_MD);
|
|
2158
|
+
const toolsMd = readPromptFile(TOOLS_MD);
|
|
2159
|
+
const workspaceContext = [
|
|
2160
|
+
agentsMd ? `
|
|
2161
|
+
## Agent Instructions (AGENTS.md)
|
|
2162
|
+
${agentsMd}` : "",
|
|
2163
|
+
soulMd ? `
|
|
2164
|
+
## Personality (SOUL.md)
|
|
2165
|
+
${soulMd}` : "",
|
|
2166
|
+
toolsMd ? `
|
|
2167
|
+
## Tool Notes (TOOLS.md)
|
|
2168
|
+
${toolsMd}` : ""
|
|
2169
|
+
].filter(Boolean).join("\n");
|
|
2170
|
+
const learningContext = getLearningContext();
|
|
2171
|
+
return `You are ${TITAN_NAME}, The Intelligent Task Automation Network \u2014 a powerful personal AI assistant.
|
|
2172
|
+
|
|
2173
|
+
## Core Capabilities
|
|
2174
|
+
- Execute shell commands and scripts on the user's system
|
|
2175
|
+
- Read, write, edit, and manage files
|
|
2176
|
+
- Browse the web and extract information (browser control via CDP)
|
|
2177
|
+
- Schedule automated tasks with cron
|
|
2178
|
+
- Set up webhook endpoints
|
|
2179
|
+
- Search the web for current information
|
|
2180
|
+
- Control browser sessions (navigate, snapshot, evaluate)
|
|
2181
|
+
- Manage agent sessions (list, history, send, close)
|
|
2182
|
+
- Remember facts and user preferences persistently
|
|
2183
|
+
|
|
2184
|
+
## Behavior Guidelines
|
|
2185
|
+
- Be proactive: if a task implies follow-up actions, suggest or perform them
|
|
2186
|
+
- Be concise but thorough in responses
|
|
2187
|
+
- When executing commands, always explain what you're doing and why
|
|
2188
|
+
- If a task could be destructive (deleting files, etc.), confirm with the user first
|
|
2189
|
+
- Use tools when they would be helpful \u2014 don't just describe what could be done
|
|
2190
|
+
- Remember important information about the user for future conversations
|
|
2191
|
+
- If you encounter an error, try alternative approaches before reporting failure
|
|
2192
|
+
|
|
2193
|
+
## Security
|
|
2194
|
+
- Never expose API keys, passwords, or other secrets
|
|
2195
|
+
- Don't execute commands that could compromise system security without explicit approval
|
|
2196
|
+
- Respect file system boundaries set in the configuration
|
|
2197
|
+
|
|
2198
|
+
## Continuous Learning
|
|
2199
|
+
You get smarter with every interaction. Below is your accumulated knowledge:
|
|
2200
|
+
${learningContext}
|
|
2201
|
+
${customPrompt ? `
|
|
2202
|
+
## Custom Instructions
|
|
2203
|
+
${customPrompt}` : ""}${workspaceContext}${memoryContext}`;
|
|
2204
|
+
}
|
|
2205
|
+
async function processMessage(message, channel = "cli", userId = "default") {
|
|
2206
|
+
const startTime = Date.now();
|
|
2207
|
+
const config = loadConfig();
|
|
2208
|
+
const session = getOrCreateSession(channel, userId);
|
|
2209
|
+
logger_default.info(COMPONENT16, `Processing message in session ${session.id} (${channel}/${userId})`);
|
|
2210
|
+
addMessage(session, "user", message);
|
|
2211
|
+
const systemPrompt = buildSystemPrompt(config);
|
|
2212
|
+
const historyMessages = getContextMessages(session);
|
|
2213
|
+
const tools = getToolDefinitions();
|
|
2214
|
+
const messages = [
|
|
2215
|
+
{ role: "system", content: systemPrompt },
|
|
2216
|
+
...historyMessages
|
|
2217
|
+
];
|
|
2218
|
+
let totalPromptTokens = 0;
|
|
2219
|
+
let totalCompletionTokens = 0;
|
|
2220
|
+
const toolsUsed = [];
|
|
2221
|
+
let finalContent = "";
|
|
2222
|
+
let modelUsed = config.agent.model;
|
|
2223
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
2224
|
+
logger_default.debug(COMPONENT16, `Round ${round + 1}: ${messages.length} messages, ${tools.length} tools`);
|
|
2225
|
+
const response = await chat({
|
|
2226
|
+
model: config.agent.model,
|
|
2227
|
+
messages,
|
|
2228
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
2229
|
+
maxTokens: config.agent.maxTokens,
|
|
2230
|
+
temperature: config.agent.temperature
|
|
2231
|
+
});
|
|
2232
|
+
modelUsed = response.model;
|
|
2233
|
+
totalPromptTokens += response.usage?.promptTokens || 0;
|
|
2234
|
+
totalCompletionTokens += response.usage?.completionTokens || 0;
|
|
2235
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
2236
|
+
finalContent = response.content;
|
|
2237
|
+
break;
|
|
2238
|
+
}
|
|
2239
|
+
logger_default.info(COMPONENT16, `LLM requested ${response.toolCalls.length} tool call(s)`);
|
|
2240
|
+
messages.push({
|
|
2241
|
+
role: "assistant",
|
|
2242
|
+
content: response.content || "",
|
|
2243
|
+
toolCalls: response.toolCalls
|
|
2244
|
+
});
|
|
2245
|
+
const toolResults = await executeTools(response.toolCalls);
|
|
2246
|
+
for (const result of toolResults) {
|
|
2247
|
+
toolsUsed.push(result.name);
|
|
2248
|
+
messages.push({
|
|
2249
|
+
role: "tool",
|
|
2250
|
+
content: result.content,
|
|
2251
|
+
toolCallId: result.toolCallId
|
|
2252
|
+
});
|
|
2253
|
+
const success = !result.content.toLowerCase().includes("error:");
|
|
2254
|
+
recordToolResult(result.name, success, void 0, success ? void 0 : result.content.slice(0, 200));
|
|
2255
|
+
}
|
|
2256
|
+
if (round === MAX_TOOL_ROUNDS - 1) {
|
|
2257
|
+
finalContent = response.content || "I completed the tool operations. Let me know if you need anything else.";
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
addMessage(session, "assistant", finalContent, {
|
|
2261
|
+
model: modelUsed,
|
|
2262
|
+
tokenCount: totalCompletionTokens
|
|
2263
|
+
});
|
|
2264
|
+
const { provider: providerName } = { provider: modelUsed.split("/")[0] || "unknown" };
|
|
2265
|
+
recordUsage(session.id, providerName, modelUsed, totalPromptTokens, totalCompletionTokens);
|
|
2266
|
+
const durationMs = Date.now() - startTime;
|
|
2267
|
+
logger_default.info(COMPONENT16, `Response generated in ${durationMs}ms (${totalPromptTokens + totalCompletionTokens} tokens)`);
|
|
2268
|
+
return {
|
|
2269
|
+
content: finalContent,
|
|
2270
|
+
sessionId: session.id,
|
|
2271
|
+
toolsUsed: [...new Set(toolsUsed)],
|
|
2272
|
+
tokenUsage: {
|
|
2273
|
+
prompt: totalPromptTokens,
|
|
2274
|
+
completion: totalCompletionTokens,
|
|
2275
|
+
total: totalPromptTokens + totalCompletionTokens
|
|
2276
|
+
},
|
|
2277
|
+
model: modelUsed,
|
|
2278
|
+
durationMs
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
var COMPONENT16, MAX_TOOL_ROUNDS;
|
|
2282
|
+
var init_agent = __esm({
|
|
2283
|
+
"src/agent/agent.ts"() {
|
|
2284
|
+
"use strict";
|
|
2285
|
+
init_router();
|
|
2286
|
+
init_config();
|
|
2287
|
+
init_session();
|
|
2288
|
+
init_toolRunner();
|
|
2289
|
+
init_memory();
|
|
2290
|
+
init_learning();
|
|
2291
|
+
init_logger();
|
|
2292
|
+
init_constants();
|
|
2293
|
+
COMPONENT16 = "Agent";
|
|
2294
|
+
MAX_TOOL_ROUNDS = 10;
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2297
|
+
|
|
2298
|
+
// src/skills/builtin/sessions.ts
|
|
2299
|
+
var sessions_exports = {};
|
|
2300
|
+
__export(sessions_exports, {
|
|
2301
|
+
registerSessionsSkill: () => registerSessionsSkill
|
|
2302
|
+
});
|
|
2303
|
+
function registerSessionsSkill() {
|
|
2304
|
+
registerSkill(
|
|
2305
|
+
{ name: "sessions_list", description: "List active sessions", version: "1.0.0", source: "bundled", enabled: true },
|
|
2306
|
+
{
|
|
2307
|
+
name: "sessions_list",
|
|
2308
|
+
description: "List all active agent sessions with their IDs, channels, users, and message counts.",
|
|
2309
|
+
parameters: {
|
|
2310
|
+
type: "object",
|
|
2311
|
+
properties: {}
|
|
2312
|
+
},
|
|
2313
|
+
execute: async () => {
|
|
2314
|
+
const sessions = listSessions();
|
|
2315
|
+
if (sessions.length === 0) return "No active sessions.";
|
|
2316
|
+
return sessions.map(
|
|
2317
|
+
(s) => `\u2022 ${s.id.slice(0, 8)} | ${s.channel} | user: ${s.userId} | msgs: ${s.messageCount} | last active: ${s.lastActive}`
|
|
2318
|
+
).join("\n");
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
);
|
|
2322
|
+
registerSkill(
|
|
2323
|
+
{ name: "sessions_history", description: "Get session message history", version: "1.0.0", source: "bundled", enabled: true },
|
|
2324
|
+
{
|
|
2325
|
+
name: "sessions_history",
|
|
2326
|
+
description: "Retrieve the recent message history for a specific session.",
|
|
2327
|
+
parameters: {
|
|
2328
|
+
type: "object",
|
|
2329
|
+
properties: {
|
|
2330
|
+
sessionChannel: { type: "string", description: "Channel of the session" },
|
|
2331
|
+
sessionUserId: { type: "string", description: "User ID of the session" },
|
|
2332
|
+
limit: { type: "number", description: "Max messages to return (default: 20)" }
|
|
2333
|
+
},
|
|
2334
|
+
required: ["sessionChannel", "sessionUserId"]
|
|
2335
|
+
},
|
|
2336
|
+
execute: async (args) => {
|
|
2337
|
+
const channel = args.sessionChannel;
|
|
2338
|
+
const userId = args.sessionUserId;
|
|
2339
|
+
const limit = args.limit || 20;
|
|
2340
|
+
const session = getOrCreateSession(channel, userId);
|
|
2341
|
+
const messages = getContextMessages(session, limit);
|
|
2342
|
+
if (messages.length === 0) return `No messages in session ${channel}/${userId}.`;
|
|
2343
|
+
return messages.map(
|
|
2344
|
+
(m) => `[${m.role}] ${m.content.slice(0, 500)}`
|
|
2345
|
+
).join("\n---\n");
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
);
|
|
2349
|
+
registerSkill(
|
|
2350
|
+
{ name: "sessions_send", description: "Send a message to another session", version: "1.0.0", source: "bundled", enabled: true },
|
|
2351
|
+
{
|
|
2352
|
+
name: "sessions_send",
|
|
2353
|
+
description: "Send a message to a specific user's session on a channel, triggering agent processing.",
|
|
2354
|
+
parameters: {
|
|
2355
|
+
type: "object",
|
|
2356
|
+
properties: {
|
|
2357
|
+
targetChannel: { type: "string", description: 'Target channel (e.g., "discord", "telegram")' },
|
|
2358
|
+
targetUserId: { type: "string", description: "Target user ID" },
|
|
2359
|
+
message: { type: "string", description: "Message to send" }
|
|
2360
|
+
},
|
|
2361
|
+
required: ["targetChannel", "targetUserId", "message"]
|
|
2362
|
+
},
|
|
2363
|
+
execute: async (args) => {
|
|
2364
|
+
const channel = args.targetChannel;
|
|
2365
|
+
const userId = args.targetUserId;
|
|
2366
|
+
const message = args.message;
|
|
2367
|
+
logger_default.info(COMPONENT17, `Sending inter-session message to ${channel}/${userId}`);
|
|
2368
|
+
try {
|
|
2369
|
+
const response = await processMessage(message, channel, userId);
|
|
2370
|
+
return `Message delivered to ${channel}/${userId}. Response: ${response.content.slice(0, 500)}`;
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
return `Error sending to ${channel}/${userId}: ${error.message}`;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
);
|
|
2377
|
+
registerSkill(
|
|
2378
|
+
{ name: "sessions_close", description: "Close a session", version: "1.0.0", source: "bundled", enabled: true },
|
|
2379
|
+
{
|
|
2380
|
+
name: "sessions_close",
|
|
2381
|
+
description: "Close a specific session by its session ID.",
|
|
2382
|
+
parameters: {
|
|
2383
|
+
type: "object",
|
|
2384
|
+
properties: {
|
|
2385
|
+
sessionId: { type: "string", description: "Session ID to close" }
|
|
2386
|
+
},
|
|
2387
|
+
required: ["sessionId"]
|
|
2388
|
+
},
|
|
2389
|
+
execute: async (args) => {
|
|
2390
|
+
const sessionId = args.sessionId;
|
|
2391
|
+
closeSession(sessionId);
|
|
2392
|
+
return `Session ${sessionId} closed.`;
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
var COMPONENT17;
|
|
2398
|
+
var init_sessions = __esm({
|
|
2399
|
+
"src/skills/builtin/sessions.ts"() {
|
|
2400
|
+
"use strict";
|
|
2401
|
+
init_registry();
|
|
2402
|
+
init_session();
|
|
2403
|
+
init_agent();
|
|
2404
|
+
init_logger();
|
|
2405
|
+
COMPONENT17 = "Sessions";
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
|
|
2409
|
+
// src/skills/builtin/process.ts
|
|
2410
|
+
var process_exports = {};
|
|
2411
|
+
__export(process_exports, {
|
|
2412
|
+
registerProcessSkill: () => registerProcessSkill
|
|
2413
|
+
});
|
|
2414
|
+
import { exec as exec3, spawn } from "child_process";
|
|
2415
|
+
import { v4 as uuid8 } from "uuid";
|
|
2416
|
+
function registerProcessSkill() {
|
|
2417
|
+
registerSkill(
|
|
2418
|
+
{ name: "exec", description: "Execute commands with background/timeout support", version: "1.0.0", source: "bundled", enabled: true },
|
|
2419
|
+
{
|
|
2420
|
+
name: "exec",
|
|
2421
|
+
description: "Execute a shell command. Supports background execution (returns immediately with sessionId), timeouts, and PTY mode. Use the process tool to poll/kill background processes.",
|
|
2422
|
+
parameters: {
|
|
2423
|
+
type: "object",
|
|
2424
|
+
properties: {
|
|
2425
|
+
command: { type: "string", description: "Shell command to execute" },
|
|
2426
|
+
background: { type: "boolean", description: "Run in background (default: false)" },
|
|
2427
|
+
timeout: { type: "number", description: "Timeout in seconds (default: 30, max: 1800)" },
|
|
2428
|
+
cwd: { type: "string", description: "Working directory" }
|
|
2429
|
+
},
|
|
2430
|
+
required: ["command"]
|
|
2431
|
+
},
|
|
2432
|
+
execute: async (args) => {
|
|
2433
|
+
const command = args.command;
|
|
2434
|
+
const background = args.background || false;
|
|
2435
|
+
const timeout = Math.min(args.timeout || 30, 1800) * 1e3;
|
|
2436
|
+
const cwd = args.cwd || process.cwd();
|
|
2437
|
+
if (background) {
|
|
2438
|
+
const id = uuid8().slice(0, 8);
|
|
2439
|
+
const child = spawn("bash", ["-c", command], { cwd, stdio: "pipe" });
|
|
2440
|
+
const managed = {
|
|
2441
|
+
id,
|
|
2442
|
+
command,
|
|
2443
|
+
pid: child.pid || 0,
|
|
2444
|
+
status: "running",
|
|
2445
|
+
exitCode: null,
|
|
2446
|
+
output: [],
|
|
2447
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2448
|
+
process: child
|
|
2449
|
+
};
|
|
2450
|
+
child.stdout?.on("data", (data) => {
|
|
2451
|
+
const lines = data.toString().split("\n");
|
|
2452
|
+
managed.output.push(...lines);
|
|
2453
|
+
if (managed.output.length > MAX_OUTPUT_LINES) {
|
|
2454
|
+
managed.output = managed.output.slice(-MAX_OUTPUT_LINES);
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
child.stderr?.on("data", (data) => {
|
|
2458
|
+
managed.output.push(...data.toString().split("\n"));
|
|
2459
|
+
});
|
|
2460
|
+
child.on("exit", (code) => {
|
|
2461
|
+
managed.status = "exited";
|
|
2462
|
+
managed.exitCode = code;
|
|
2463
|
+
managed.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2464
|
+
});
|
|
2465
|
+
processes.set(id, managed);
|
|
2466
|
+
logger_default.info(COMPONENT18, `Background process started: ${id} (PID: ${managed.pid})`);
|
|
2467
|
+
return `Process started in background.
|
|
2468
|
+
Session ID: ${id}
|
|
2469
|
+
PID: ${managed.pid}
|
|
2470
|
+
Use the "process" tool to poll, log, or kill this process.`;
|
|
2471
|
+
}
|
|
2472
|
+
return new Promise((resolve2) => {
|
|
2473
|
+
exec3(command, { cwd, timeout, maxBuffer: 1024 * 1024 * 5 }, (err, stdout, stderr) => {
|
|
2474
|
+
if (err && err.killed) {
|
|
2475
|
+
resolve2(`Command timed out after ${timeout / 1e3}s.
|
|
2476
|
+
|
|
2477
|
+
Partial output:
|
|
2478
|
+
${stdout.slice(0, 5e3)}`);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const output = (stdout + (stderr ? `
|
|
2482
|
+
STDERR:
|
|
2483
|
+
${stderr}` : "")).trim();
|
|
2484
|
+
if (err) {
|
|
2485
|
+
resolve2(`Exit code: ${err.code}
|
|
2486
|
+
${output.slice(0, 1e4)}`);
|
|
2487
|
+
} else {
|
|
2488
|
+
resolve2(output.slice(0, 1e4) || "(no output)");
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
);
|
|
2495
|
+
registerSkill(
|
|
2496
|
+
{ name: "process", description: "Manage background processes", version: "1.0.0", source: "bundled", enabled: true },
|
|
2497
|
+
{
|
|
2498
|
+
name: "process",
|
|
2499
|
+
description: "Manage background processes: list running processes, poll for output, view logs, write to stdin, kill, or clear completed processes.",
|
|
2500
|
+
parameters: {
|
|
2501
|
+
type: "object",
|
|
2502
|
+
properties: {
|
|
2503
|
+
action: {
|
|
2504
|
+
type: "string",
|
|
2505
|
+
enum: ["list", "poll", "log", "write", "kill", "clear", "remove"],
|
|
2506
|
+
description: "Action to perform"
|
|
2507
|
+
},
|
|
2508
|
+
sessionId: { type: "string", description: "Process session ID (for poll/log/write/kill/remove)" },
|
|
2509
|
+
input: { type: "string", description: "Input to write to process stdin (for write action)" },
|
|
2510
|
+
limit: { type: "number", description: "Number of log lines to return (for log action, default: 50)" }
|
|
2511
|
+
},
|
|
2512
|
+
required: ["action"]
|
|
2513
|
+
},
|
|
2514
|
+
execute: async (args) => {
|
|
2515
|
+
const action = args.action;
|
|
2516
|
+
switch (action) {
|
|
2517
|
+
case "list": {
|
|
2518
|
+
const procs = Array.from(processes.values());
|
|
2519
|
+
if (procs.length === 0) return "No managed processes.";
|
|
2520
|
+
return procs.map(
|
|
2521
|
+
(p) => `\u2022 ${p.id} | PID: ${p.pid} | ${p.status} | cmd: ${p.command.slice(0, 80)} | started: ${p.startedAt}${p.exitCode !== null ? ` | exit: ${p.exitCode}` : ""}`
|
|
2522
|
+
).join("\n");
|
|
2523
|
+
}
|
|
2524
|
+
case "poll": {
|
|
2525
|
+
const proc = processes.get(args.sessionId);
|
|
2526
|
+
if (!proc) return `Process ${args.sessionId} not found.`;
|
|
2527
|
+
const lastLines = proc.output.slice(-20).join("\n");
|
|
2528
|
+
return `Status: ${proc.status}${proc.exitCode !== null ? ` (exit: ${proc.exitCode})` : ""}
|
|
2529
|
+
Output (last 20 lines):
|
|
2530
|
+
${lastLines || "(no output yet)"}`;
|
|
2531
|
+
}
|
|
2532
|
+
case "log": {
|
|
2533
|
+
const proc = processes.get(args.sessionId);
|
|
2534
|
+
if (!proc) return `Process ${args.sessionId} not found.`;
|
|
2535
|
+
const limit = args.limit || 50;
|
|
2536
|
+
return proc.output.slice(-limit).join("\n") || "(no output)";
|
|
2537
|
+
}
|
|
2538
|
+
case "write": {
|
|
2539
|
+
const proc = processes.get(args.sessionId);
|
|
2540
|
+
if (!proc || proc.status !== "running") return `Process ${args.sessionId} not running.`;
|
|
2541
|
+
const input = args.input;
|
|
2542
|
+
if (!input) return "No input provided.";
|
|
2543
|
+
proc.process.stdin?.write(input + "\n");
|
|
2544
|
+
return `Wrote to stdin: ${input}`;
|
|
2545
|
+
}
|
|
2546
|
+
case "kill": {
|
|
2547
|
+
const proc = processes.get(args.sessionId);
|
|
2548
|
+
if (!proc) return `Process ${args.sessionId} not found.`;
|
|
2549
|
+
proc.process.kill("SIGTERM");
|
|
2550
|
+
setTimeout(() => proc.process.kill("SIGKILL"), 5e3);
|
|
2551
|
+
return `Sent SIGTERM to process ${proc.id} (PID: ${proc.pid})`;
|
|
2552
|
+
}
|
|
2553
|
+
case "clear": {
|
|
2554
|
+
const cleared = Array.from(processes.values()).filter((p) => p.status === "exited");
|
|
2555
|
+
for (const p of cleared) processes.delete(p.id);
|
|
2556
|
+
return `Cleared ${cleared.length} completed processes.`;
|
|
2557
|
+
}
|
|
2558
|
+
case "remove": {
|
|
2559
|
+
if (processes.delete(args.sessionId)) {
|
|
2560
|
+
return `Removed process ${args.sessionId}.`;
|
|
2561
|
+
}
|
|
2562
|
+
return `Process ${args.sessionId} not found.`;
|
|
2563
|
+
}
|
|
2564
|
+
default:
|
|
2565
|
+
return `Unknown action: ${action}`;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
var COMPONENT18, processes, MAX_OUTPUT_LINES;
|
|
2572
|
+
var init_process = __esm({
|
|
2573
|
+
"src/skills/builtin/process.ts"() {
|
|
2574
|
+
"use strict";
|
|
2575
|
+
init_registry();
|
|
2576
|
+
init_logger();
|
|
2577
|
+
COMPONENT18 = "Process";
|
|
2578
|
+
processes = /* @__PURE__ */ new Map();
|
|
2579
|
+
MAX_OUTPUT_LINES = 500;
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
// src/skills/builtin/web_fetch.ts
|
|
2584
|
+
var web_fetch_exports = {};
|
|
2585
|
+
__export(web_fetch_exports, {
|
|
2586
|
+
registerWebFetchSkill: () => registerWebFetchSkill
|
|
2587
|
+
});
|
|
2588
|
+
import { exec as exec4 } from "child_process";
|
|
2589
|
+
function htmlToText(html) {
|
|
2590
|
+
return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/div>/gi, "\n").replace(/<\/h[1-6]>/gi, "\n\n").replace(/<li>/gi, "\u2022 ").replace(/<\/li>/gi, "\n").replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\n{3,}/g, "\n\n").trim();
|
|
2591
|
+
}
|
|
2592
|
+
function htmlToMarkdown(html) {
|
|
2593
|
+
let md = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "");
|
|
2594
|
+
md = md.replace(/<h1[^>]*>(.*?)<\/h1>/gi, "# $1\n");
|
|
2595
|
+
md = md.replace(/<h2[^>]*>(.*?)<\/h2>/gi, "## $1\n");
|
|
2596
|
+
md = md.replace(/<h3[^>]*>(.*?)<\/h3>/gi, "### $1\n");
|
|
2597
|
+
md = md.replace(/<h4[^>]*>(.*?)<\/h4>/gi, "#### $1\n");
|
|
2598
|
+
md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)");
|
|
2599
|
+
md = md.replace(/<(strong|b)>(.*?)<\/\1>/gi, "**$2**");
|
|
2600
|
+
md = md.replace(/<(em|i)>(.*?)<\/\1>/gi, "*$2*");
|
|
2601
|
+
md = md.replace(/<code>(.*?)<\/code>/gi, "`$1`");
|
|
2602
|
+
md = md.replace(/<pre>([\s\S]*?)<\/pre>/gi, "```\n$1\n```\n");
|
|
2603
|
+
md = md.replace(/<li>/gi, "- ");
|
|
2604
|
+
md = md.replace(/<\/li>/gi, "\n");
|
|
2605
|
+
md = md.replace(/<img[^>]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*>/gi, "");
|
|
2606
|
+
md = md.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*>/gi, "");
|
|
2607
|
+
md = md.replace(/<br\s*\/?>/gi, "\n");
|
|
2608
|
+
md = md.replace(/<\/p>/gi, "\n\n");
|
|
2609
|
+
md = md.replace(/<[^>]*>/g, "");
|
|
2610
|
+
md = md.replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2611
|
+
md = md.replace(/\n{3,}/g, "\n\n");
|
|
2612
|
+
return md.trim();
|
|
2613
|
+
}
|
|
2614
|
+
function registerWebFetchSkill() {
|
|
2615
|
+
registerSkill(
|
|
2616
|
+
{ name: "web_fetch", description: "Fetch URL content", version: "1.0.0", source: "bundled", enabled: true },
|
|
2617
|
+
{
|
|
2618
|
+
name: "web_fetch",
|
|
2619
|
+
description: "Fetch a URL and extract its content as markdown or plain text. Good for reading documentation, articles, and web pages. For JS-heavy sites, prefer the browser tool.",
|
|
2620
|
+
parameters: {
|
|
2621
|
+
type: "object",
|
|
2622
|
+
properties: {
|
|
2623
|
+
url: { type: "string", description: "URL to fetch" },
|
|
2624
|
+
extractMode: { type: "string", enum: ["markdown", "text"], description: "Output format (default: markdown)" },
|
|
2625
|
+
maxChars: { type: "number", description: "Max characters to return (default: 50000)" }
|
|
2626
|
+
},
|
|
2627
|
+
required: ["url"]
|
|
2628
|
+
},
|
|
2629
|
+
execute: async (args) => {
|
|
2630
|
+
const url = args.url;
|
|
2631
|
+
const mode = args.extractMode || "markdown";
|
|
2632
|
+
const maxChars = Math.min(args.maxChars || 5e4, 1e5);
|
|
2633
|
+
return new Promise((resolve2) => {
|
|
2634
|
+
exec4(
|
|
2635
|
+
`curl -sL --max-time 20 -A "Mozilla/5.0 (compatible; TITAN/1.0)" "${url}" | head -c 200000`,
|
|
2636
|
+
{ timeout: 25e3, maxBuffer: 1024 * 1024 * 5 },
|
|
2637
|
+
(err, stdout) => {
|
|
2638
|
+
if (err) {
|
|
2639
|
+
resolve2(`Error fetching ${url}: ${err.message}`);
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
if (!stdout.trim()) {
|
|
2643
|
+
resolve2(`Empty response from ${url}`);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
const title = stdout.match(/<title[^>]*>(.*?)<\/title>/i)?.[1] || "Untitled";
|
|
2647
|
+
const content = mode === "markdown" ? htmlToMarkdown(stdout) : htmlToText(stdout);
|
|
2648
|
+
resolve2(`# ${title}
|
|
2649
|
+
|
|
2650
|
+
Source: ${url}
|
|
2651
|
+
|
|
2652
|
+
${content.slice(0, maxChars)}`);
|
|
2653
|
+
}
|
|
2654
|
+
);
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
);
|
|
2659
|
+
}
|
|
2660
|
+
var init_web_fetch = __esm({
|
|
2661
|
+
"src/skills/builtin/web_fetch.ts"() {
|
|
2662
|
+
"use strict";
|
|
2663
|
+
init_registry();
|
|
2664
|
+
}
|
|
2665
|
+
});
|
|
2666
|
+
|
|
2667
|
+
// src/skills/builtin/apply_patch.ts
|
|
2668
|
+
var apply_patch_exports = {};
|
|
2669
|
+
__export(apply_patch_exports, {
|
|
2670
|
+
registerApplyPatchSkill: () => registerApplyPatchSkill
|
|
2671
|
+
});
|
|
2672
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
|
|
2673
|
+
import { dirname as dirname2 } from "path";
|
|
2674
|
+
function registerApplyPatchSkill() {
|
|
2675
|
+
registerSkill(
|
|
2676
|
+
{ name: "apply_patch", description: "Apply unified diff patches to files", version: "1.0.0", source: "bundled", enabled: true },
|
|
2677
|
+
{
|
|
2678
|
+
name: "apply_patch",
|
|
2679
|
+
description: "Apply a unified diff patch to one or more files. The patch should be in unified diff format (like git diff output). Creates new files if they don't exist.",
|
|
2680
|
+
parameters: {
|
|
2681
|
+
type: "object",
|
|
2682
|
+
properties: {
|
|
2683
|
+
patch: { type: "string", description: "Unified diff patch content" },
|
|
2684
|
+
cwd: { type: "string", description: "Working directory for relative paths" }
|
|
2685
|
+
},
|
|
2686
|
+
required: ["patch"]
|
|
2687
|
+
},
|
|
2688
|
+
execute: async (args) => {
|
|
2689
|
+
const patch = args.patch;
|
|
2690
|
+
const cwd = args.cwd || process.cwd();
|
|
2691
|
+
const results = [];
|
|
2692
|
+
const filePatches = patch.split(/^diff --git/m).filter(Boolean);
|
|
2693
|
+
if (filePatches.length === 0) {
|
|
2694
|
+
return applySimplePatch(patch, cwd);
|
|
2695
|
+
}
|
|
2696
|
+
for (const filePatch of filePatches) {
|
|
2697
|
+
try {
|
|
2698
|
+
const oldFile = filePatch.match(/^--- a\/(.+)$/m)?.[1];
|
|
2699
|
+
const newFile = filePatch.match(/^\+\+\+ b\/(.+)$/m)?.[1];
|
|
2700
|
+
const targetFile = newFile || oldFile;
|
|
2701
|
+
if (!targetFile) {
|
|
2702
|
+
results.push(`\u26A0\uFE0F Could not determine target file`);
|
|
2703
|
+
continue;
|
|
2704
|
+
}
|
|
2705
|
+
const fullPath = targetFile.startsWith("/") ? targetFile : `${cwd}/${targetFile}`;
|
|
2706
|
+
const isNewFile = oldFile === "/dev/null" || !existsSync7(fullPath);
|
|
2707
|
+
if (isNewFile) {
|
|
2708
|
+
const addedLines = filePatch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)).join("\n");
|
|
2709
|
+
ensureDir(dirname2(fullPath));
|
|
2710
|
+
writeFileSync5(fullPath, addedLines, "utf-8");
|
|
2711
|
+
results.push(`\u2705 Created: ${targetFile}`);
|
|
2712
|
+
} else {
|
|
2713
|
+
let content = readFileSync6(fullPath, "utf-8");
|
|
2714
|
+
const hunks = filePatch.match(/@@ .+ @@[\s\S]*?(?=@@ |$)/g) || [];
|
|
2715
|
+
for (const hunk of hunks) {
|
|
2716
|
+
const lines = hunk.split("\n").slice(1);
|
|
2717
|
+
const removeLines = lines.filter((l) => l.startsWith("-")).map((l) => l.slice(1));
|
|
2718
|
+
const addLines = lines.filter((l) => l.startsWith("+")).map((l) => l.slice(1));
|
|
2719
|
+
for (const removeLine of removeLines) {
|
|
2720
|
+
if (removeLine.trim()) {
|
|
2721
|
+
content = content.replace(removeLine, "");
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
if (addLines.length > 0) {
|
|
2725
|
+
const contextLine = lines.find((l) => !l.startsWith("+") && !l.startsWith("-"))?.trim();
|
|
2726
|
+
if (contextLine) {
|
|
2727
|
+
const idx = content.indexOf(contextLine);
|
|
2728
|
+
if (idx >= 0) {
|
|
2729
|
+
content = content.slice(0, idx) + addLines.join("\n") + "\n" + content.slice(idx);
|
|
2730
|
+
} else {
|
|
2731
|
+
content += "\n" + addLines.join("\n");
|
|
2732
|
+
}
|
|
2733
|
+
} else {
|
|
2734
|
+
content += "\n" + addLines.join("\n");
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
writeFileSync5(fullPath, content, "utf-8");
|
|
2739
|
+
results.push(`\u2705 Patched: ${targetFile} (${hunks.length} hunk(s))`);
|
|
2740
|
+
}
|
|
2741
|
+
} catch (error) {
|
|
2742
|
+
results.push(`\u274C Error: ${error.message}`);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
return results.join("\n") || "No changes applied.";
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2750
|
+
function applySimplePatch(patch, cwd) {
|
|
2751
|
+
const oldFileMatch = patch.match(/^--- (.+)$/m);
|
|
2752
|
+
const newFileMatch = patch.match(/^\+\+\+ (.+)$/m);
|
|
2753
|
+
if (!newFileMatch) return "Could not parse patch: no +++ line found.";
|
|
2754
|
+
let targetPath = newFileMatch[1].replace(/^b\//, "");
|
|
2755
|
+
if (!targetPath.startsWith("/")) targetPath = `${cwd}/${targetPath}`;
|
|
2756
|
+
const addedLines = patch.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1));
|
|
2757
|
+
const removedLines = patch.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).map((l) => l.slice(1));
|
|
2758
|
+
if (existsSync7(targetPath)) {
|
|
2759
|
+
let content = readFileSync6(targetPath, "utf-8");
|
|
2760
|
+
for (const line of removedLines) {
|
|
2761
|
+
content = content.replace(line + "\n", "");
|
|
2762
|
+
}
|
|
2763
|
+
if (addedLines.length > 0) {
|
|
2764
|
+
content += addedLines.join("\n") + "\n";
|
|
2765
|
+
}
|
|
2766
|
+
writeFileSync5(targetPath, content, "utf-8");
|
|
2767
|
+
return `\u2705 Patched: ${targetPath}`;
|
|
2768
|
+
} else {
|
|
2769
|
+
ensureDir(dirname2(targetPath));
|
|
2770
|
+
writeFileSync5(targetPath, addedLines.join("\n") + "\n", "utf-8");
|
|
2771
|
+
return `\u2705 Created: ${targetPath}`;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
var init_apply_patch = __esm({
|
|
2775
|
+
"src/skills/builtin/apply_patch.ts"() {
|
|
2776
|
+
"use strict";
|
|
2777
|
+
init_registry();
|
|
2778
|
+
init_helpers();
|
|
2779
|
+
}
|
|
2780
|
+
});
|
|
2781
|
+
|
|
2782
|
+
// src/agent/generator.ts
|
|
2783
|
+
import path from "path";
|
|
2784
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
2785
|
+
import { execSync } from "child_process";
|
|
2786
|
+
async function generateAndInstallSkill(description, requestedName) {
|
|
2787
|
+
logger_default.info(COMPONENT19, `Initiating auto-generation for skill: "${requestedName}"`);
|
|
2788
|
+
if (!existsSync8(AUTO_SKILLS_DIR)) {
|
|
2789
|
+
mkdirSync3(AUTO_SKILLS_DIR, { recursive: true });
|
|
2790
|
+
}
|
|
2791
|
+
try {
|
|
2792
|
+
const config = loadConfig();
|
|
2793
|
+
const model = config.agent.model;
|
|
2794
|
+
logger_default.debug(COMPONENT19, `Prompting ${model} to generate code...`);
|
|
2795
|
+
const messages = [
|
|
2796
|
+
{ role: "user", content: GENERATOR_PROMPT + `
|
|
2797
|
+
Requested Capability: ${description}
|
|
2798
|
+
Requested Name: ${requestedName}` }
|
|
2799
|
+
];
|
|
2800
|
+
const response = await chat({ messages, model });
|
|
2801
|
+
let code = response.content;
|
|
2802
|
+
if (code.startsWith("```typescript")) {
|
|
2803
|
+
code = code.replace(/^\`\`\`typescript\n/, "").replace(/\n\`\`\`$/, "");
|
|
2804
|
+
} else if (code.startsWith("```")) {
|
|
2805
|
+
code = code.replace(/^\`\`\`\n/, "").replace(/\n\`\`\`$/, "");
|
|
2806
|
+
}
|
|
2807
|
+
if (code.includes("process.exit") || code.includes("rm -rf /*") || code.includes("fs.rmSync('/'")) {
|
|
2808
|
+
return { success: false, error: "Generated code failed safety static analysis." };
|
|
2809
|
+
}
|
|
2810
|
+
const nameMatch = code.match(/name:\s*['"]([a-z0-9_]+)['"]/);
|
|
2811
|
+
const finalName = nameMatch ? nameMatch[1] : requestedName.replace(/[^a-z0-9_]/g, "_");
|
|
2812
|
+
const tsFilePath = path.join(AUTO_SKILLS_DIR, `${finalName}.ts`);
|
|
2813
|
+
const jsFilePath = path.join(AUTO_SKILLS_DIR, `${finalName}.js`);
|
|
2814
|
+
writeFileSync6(tsFilePath, code, "utf-8");
|
|
2815
|
+
logger_default.info(COMPONENT19, `Wrote generated source to ${tsFilePath}`);
|
|
2816
|
+
try {
|
|
2817
|
+
execSync(`npx tsc ${tsFilePath} --module NodeNext --moduleResolution NodeNext --target ES2022`, { stdio: "pipe" });
|
|
2818
|
+
logger_default.info(COMPONENT19, `Compiled ${finalName}.ts successfully.`);
|
|
2819
|
+
} catch (compileError) {
|
|
2820
|
+
logger_default.error(COMPONENT19, `Compilation failed for ${finalName}`);
|
|
2821
|
+
return { success: false, error: `Compilation failed: ${compileError.message || "Unknown error"}` };
|
|
2822
|
+
}
|
|
2823
|
+
if (!existsSync8(jsFilePath)) {
|
|
2824
|
+
return { success: false, error: "Compilation did not produce a .js file." };
|
|
2825
|
+
}
|
|
2826
|
+
await loadAutoSkills();
|
|
2827
|
+
logger_default.info(COMPONENT19, `\u2728 Successfully generated and installed new skill: ${finalName}`);
|
|
2828
|
+
return {
|
|
2829
|
+
success: true,
|
|
2830
|
+
skillName: finalName,
|
|
2831
|
+
filePath: tsFilePath
|
|
2832
|
+
};
|
|
2833
|
+
} catch (e) {
|
|
2834
|
+
logger_default.error(COMPONENT19, `Auto-generation failed: ${e.message}`);
|
|
2835
|
+
return { success: false, error: e.message };
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
var COMPONENT19, AUTO_SKILLS_DIR, GENERATOR_PROMPT;
|
|
2839
|
+
var init_generator = __esm({
|
|
2840
|
+
"src/agent/generator.ts"() {
|
|
2841
|
+
"use strict";
|
|
2842
|
+
init_config();
|
|
2843
|
+
init_router();
|
|
2844
|
+
init_constants();
|
|
2845
|
+
init_registry();
|
|
2846
|
+
init_logger();
|
|
2847
|
+
COMPONENT19 = "SkillGenerator";
|
|
2848
|
+
AUTO_SKILLS_DIR = path.join(TITAN_HOME, "skills", "auto");
|
|
2849
|
+
GENERATOR_PROMPT = `You are the core intelligence of TITAN, an elite autonomous AI agent framework.
|
|
2850
|
+
Your task is to generate a new, fully functional TypeScript tool (skill) based on the user's request.
|
|
2851
|
+
|
|
2852
|
+
CRITICAL REQUIREMENTS FOR THE CODE:
|
|
2853
|
+
1. It MUST export a "default" object that implements the "ToolConfig" interface.
|
|
2854
|
+
2. It MUST use ESM imports (e.g., "import { ... } from '...'").
|
|
2855
|
+
3. It CAN ONLY use built-in Node.js modules (fs, path, crypto, child_process, os, http, https, url) unless you are absolutely certain a specific npm package is guaranteed to be installed globally. Prefer built-ins.
|
|
2856
|
+
4. It MUST handle errors gracefully and return descriptive string messages.
|
|
2857
|
+
5. Do NOT wrap the output in markdown code blocks. Output ONLY the raw TypeScript code.
|
|
2858
|
+
|
|
2859
|
+
Here is the exact ToolConfig interface you must implement:
|
|
2860
|
+
export interface ToolConfig {
|
|
2861
|
+
name: string; // lowercase, alphanumeric, underscores only (e.g. 'my_custom_tool')
|
|
2862
|
+
description: string; // concise explanation of what the tool does
|
|
2863
|
+
parameters: any; // Zod schema converted to JSON schema format for LLM function calling
|
|
2864
|
+
execute: (args: any, config: any) => Promise<string>; // the async execution function
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
Example JSON schema for parameters (must follow this exact structure):
|
|
2868
|
+
{
|
|
2869
|
+
"type": "object",
|
|
2870
|
+
"properties": {
|
|
2871
|
+
"filePath": {
|
|
2872
|
+
"type": "string",
|
|
2873
|
+
"description": "Absolute path to the file"
|
|
2874
|
+
}
|
|
2875
|
+
},
|
|
2876
|
+
"required": ["filePath"]
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
Example implementation format:
|
|
2880
|
+
import fs from 'fs';
|
|
2881
|
+
|
|
2882
|
+
export default {
|
|
2883
|
+
name: "count_lines",
|
|
2884
|
+
description: "Counts the number of lines in a given text file.",
|
|
2885
|
+
parameters: {
|
|
2886
|
+
type: "object",
|
|
2887
|
+
properties: {
|
|
2888
|
+
filePath: { type: "string" }
|
|
2889
|
+
},
|
|
2890
|
+
required: ["filePath"]
|
|
2891
|
+
},
|
|
2892
|
+
execute: async (args, config) => {
|
|
2893
|
+
try {
|
|
2894
|
+
const content = fs.readFileSync(args.filePath, 'utf-8');
|
|
2895
|
+
return \`File has \${content.split('\\n').length} lines.\`;
|
|
2896
|
+
} catch (e) {
|
|
2897
|
+
return \`Error: \${e.message}\`;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
Now, generate the TypeScript code for the following requested tool capability:
|
|
2903
|
+
`;
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
// src/skills/builtin/auto_generate.ts
|
|
2908
|
+
var auto_generate_exports = {};
|
|
2909
|
+
__export(auto_generate_exports, {
|
|
2910
|
+
registerAutoGenerateSkill: () => registerAutoGenerateSkill
|
|
2911
|
+
});
|
|
2912
|
+
function registerAutoGenerateSkill() {
|
|
2913
|
+
registerSkill(meta, handler);
|
|
2914
|
+
}
|
|
2915
|
+
var meta, handler;
|
|
2916
|
+
var init_auto_generate = __esm({
|
|
2917
|
+
"src/skills/builtin/auto_generate.ts"() {
|
|
2918
|
+
"use strict";
|
|
2919
|
+
init_registry();
|
|
2920
|
+
init_generator();
|
|
2921
|
+
meta = {
|
|
2922
|
+
name: "auto_generate_skill",
|
|
2923
|
+
description: "Auto-generates, compiles, and installs a new TypeScript tool/skill when TITAN lacks a necessary capability.",
|
|
2924
|
+
version: "1.0.0",
|
|
2925
|
+
source: "bundled",
|
|
2926
|
+
enabled: true
|
|
2927
|
+
};
|
|
2928
|
+
handler = {
|
|
2929
|
+
name: "auto_generate_skill",
|
|
2930
|
+
description: "Use this tool to write a NEW skill/tool when you realize you cannot complete the user request with your standard tools. Generates real TypeScript code, compiles it, and hot-reloads it into your capabilities.",
|
|
2931
|
+
parameters: {
|
|
2932
|
+
type: "object",
|
|
2933
|
+
properties: {
|
|
2934
|
+
capability_description: {
|
|
2935
|
+
type: "string",
|
|
2936
|
+
description: "A detailed explanation of exactly what this new tool should do, what arguments it should take, and what it should return."
|
|
2937
|
+
},
|
|
2938
|
+
suggested_name: {
|
|
2939
|
+
type: "string",
|
|
2940
|
+
description: 'A short, lowercase name with underscores for the new tool (e.g., "parse_csv", "resize_image").'
|
|
2941
|
+
}
|
|
2942
|
+
},
|
|
2943
|
+
required: ["capability_description", "suggested_name"]
|
|
2944
|
+
},
|
|
2945
|
+
execute: async (args) => {
|
|
2946
|
+
const capability = args.capability_description;
|
|
2947
|
+
const name = args.suggested_name;
|
|
2948
|
+
if (!capability || !name) {
|
|
2949
|
+
return "Error: missing required arguments capability_description or suggested_name.";
|
|
2950
|
+
}
|
|
2951
|
+
const result = await generateAndInstallSkill(capability, name);
|
|
2952
|
+
if (result.success) {
|
|
2953
|
+
return `\u2728 SUCCESS! New skill '${result.skillName}' has been generated, compiled, and hot-loaded into your tools. You can now use this tool natively!`;
|
|
2954
|
+
} else {
|
|
2955
|
+
return `Failed to generate skill: ${result.error}`;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
});
|
|
2961
|
+
|
|
2962
|
+
// src/skills/builtin/vision.ts
|
|
2963
|
+
var vision_exports = {};
|
|
2964
|
+
__export(vision_exports, {
|
|
2965
|
+
registerVisionSkill: () => registerVisionSkill
|
|
2966
|
+
});
|
|
2967
|
+
import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
|
|
2968
|
+
import { extname } from "path";
|
|
2969
|
+
function getMediaType(filePath) {
|
|
2970
|
+
const ext = extname(filePath).toLowerCase();
|
|
2971
|
+
switch (ext) {
|
|
2972
|
+
case ".png":
|
|
2973
|
+
return "image/png";
|
|
2974
|
+
case ".jpg":
|
|
2975
|
+
case ".jpeg":
|
|
2976
|
+
return "image/jpeg";
|
|
2977
|
+
case ".webp":
|
|
2978
|
+
return "image/webp";
|
|
2979
|
+
case ".gif":
|
|
2980
|
+
return "image/gif";
|
|
2981
|
+
default:
|
|
2982
|
+
return "image/jpeg";
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
async function analyzeWithAnthropic(base64Data, mediaType, prompt, apiKey) {
|
|
2986
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
2987
|
+
method: "POST",
|
|
2988
|
+
headers: {
|
|
2989
|
+
"Content-Type": "application/json",
|
|
2990
|
+
"x-api-key": apiKey,
|
|
2991
|
+
"anthropic-version": "2023-06-01"
|
|
2992
|
+
},
|
|
2993
|
+
body: JSON.stringify({
|
|
2994
|
+
model: "claude-3-5-sonnet-20241022",
|
|
2995
|
+
max_tokens: 1024,
|
|
2996
|
+
messages: [
|
|
2997
|
+
{
|
|
2998
|
+
role: "user",
|
|
2999
|
+
content: [
|
|
3000
|
+
{
|
|
3001
|
+
type: "image",
|
|
3002
|
+
source: {
|
|
3003
|
+
type: "base64",
|
|
3004
|
+
media_type: mediaType,
|
|
3005
|
+
data: base64Data
|
|
3006
|
+
}
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
type: "text",
|
|
3010
|
+
text: prompt
|
|
3011
|
+
}
|
|
3012
|
+
]
|
|
3013
|
+
}
|
|
3014
|
+
]
|
|
3015
|
+
})
|
|
3016
|
+
});
|
|
3017
|
+
if (!response.ok) {
|
|
3018
|
+
throw new Error(`Anthropic Vision API error: ${await response.text()}`);
|
|
3019
|
+
}
|
|
3020
|
+
const data = await response.json();
|
|
3021
|
+
return data.content[0].text;
|
|
3022
|
+
}
|
|
3023
|
+
async function analyzeWithOpenAI(base64Data, mediaType, prompt, apiKey) {
|
|
3024
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
3025
|
+
method: "POST",
|
|
3026
|
+
headers: {
|
|
3027
|
+
"Content-Type": "application/json",
|
|
3028
|
+
"Authorization": `Bearer ${apiKey}`
|
|
3029
|
+
},
|
|
3030
|
+
body: JSON.stringify({
|
|
3031
|
+
model: "gpt-4o",
|
|
3032
|
+
max_tokens: 1024,
|
|
3033
|
+
messages: [
|
|
3034
|
+
{
|
|
3035
|
+
role: "user",
|
|
3036
|
+
content: [
|
|
3037
|
+
{
|
|
3038
|
+
type: "text",
|
|
3039
|
+
text: prompt
|
|
3040
|
+
},
|
|
3041
|
+
{
|
|
3042
|
+
type: "image_url",
|
|
3043
|
+
image_url: {
|
|
3044
|
+
url: `data:${mediaType};base64,${base64Data}`
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
]
|
|
3048
|
+
}
|
|
3049
|
+
]
|
|
3050
|
+
})
|
|
3051
|
+
});
|
|
3052
|
+
if (!response.ok) {
|
|
3053
|
+
throw new Error(`OpenAI Vision API error: ${await response.text()}`);
|
|
3054
|
+
}
|
|
3055
|
+
const data = await response.json();
|
|
3056
|
+
return data.choices[0].message.content;
|
|
3057
|
+
}
|
|
3058
|
+
function registerVisionSkill() {
|
|
3059
|
+
registerSkill(meta2, handler2);
|
|
3060
|
+
}
|
|
3061
|
+
var COMPONENT20, meta2, handler2;
|
|
3062
|
+
var init_vision = __esm({
|
|
3063
|
+
"src/skills/builtin/vision.ts"() {
|
|
3064
|
+
"use strict";
|
|
3065
|
+
init_registry();
|
|
3066
|
+
init_config();
|
|
3067
|
+
init_logger();
|
|
3068
|
+
COMPONENT20 = "VisionTool";
|
|
3069
|
+
meta2 = {
|
|
3070
|
+
name: "analyze_image",
|
|
3071
|
+
description: "Analyzes an image file from the local filesystem to answer a question or describe it.",
|
|
3072
|
+
version: "1.0.0",
|
|
3073
|
+
source: "bundled",
|
|
3074
|
+
enabled: true
|
|
3075
|
+
};
|
|
3076
|
+
handler2 = {
|
|
3077
|
+
name: "analyze_image",
|
|
3078
|
+
description: 'Use this tool to "look" at an image. Pass the absolute filepath to the image and a specific question or prompt about what you want to know.',
|
|
3079
|
+
parameters: {
|
|
3080
|
+
type: "object",
|
|
3081
|
+
properties: {
|
|
3082
|
+
filePath: {
|
|
3083
|
+
type: "string",
|
|
3084
|
+
description: "The absolute local path to the image file (png, jpg, webp, gif)."
|
|
3085
|
+
},
|
|
3086
|
+
prompt: {
|
|
3087
|
+
type: "string",
|
|
3088
|
+
description: 'What you want to know about the image (e.g. "Describe this image in detail", "What text is in this image?", "Is there a cat here?").'
|
|
3089
|
+
}
|
|
3090
|
+
},
|
|
3091
|
+
required: ["filePath", "prompt"]
|
|
3092
|
+
},
|
|
3093
|
+
execute: async (args) => {
|
|
3094
|
+
const filePath = args.filePath;
|
|
3095
|
+
const prompt = args.prompt;
|
|
3096
|
+
if (!filePath || !prompt) {
|
|
3097
|
+
return "Error: missing required arguments filePath or prompt.";
|
|
3098
|
+
}
|
|
3099
|
+
if (!existsSync9(filePath)) {
|
|
3100
|
+
return `Error: File not found at ${filePath}`;
|
|
3101
|
+
}
|
|
3102
|
+
try {
|
|
3103
|
+
logger_default.info(COMPONENT20, `Reading image ${filePath} for analysis...`);
|
|
3104
|
+
const fileBuffer = readFileSync7(filePath);
|
|
3105
|
+
const base64Data = fileBuffer.toString("base64");
|
|
3106
|
+
const mediaType = getMediaType(filePath);
|
|
3107
|
+
const config = loadConfig();
|
|
3108
|
+
if (config.providers.anthropic && config.providers.anthropic.apiKey) {
|
|
3109
|
+
logger_default.debug(COMPONENT20, "Using Anthropic Claude 3.5 Sonnet for vision");
|
|
3110
|
+
return await analyzeWithAnthropic(base64Data, mediaType, prompt, config.providers.anthropic.apiKey);
|
|
3111
|
+
} else if (config.providers.openai && config.providers.openai.apiKey) {
|
|
3112
|
+
logger_default.debug(COMPONENT20, "Using OpenAI GPT-4o for vision");
|
|
3113
|
+
return await analyzeWithOpenAI(base64Data, mediaType, prompt, config.providers.openai.apiKey);
|
|
3114
|
+
} else {
|
|
3115
|
+
return "Error: Vision tool requires either an Anthropic or OpenAI API key configured in TITAN.";
|
|
3116
|
+
}
|
|
3117
|
+
} catch (e) {
|
|
3118
|
+
return `Vision Analysis Failed: ${e.message}`;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
});
|
|
3124
|
+
|
|
3125
|
+
// src/skills/builtin/voice.ts
|
|
3126
|
+
var voice_exports = {};
|
|
3127
|
+
__export(voice_exports, {
|
|
3128
|
+
registerVoiceSkills: () => registerVoiceSkills
|
|
3129
|
+
});
|
|
3130
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
|
|
3131
|
+
import { join as join5 } from "path";
|
|
3132
|
+
import { tmpdir } from "os";
|
|
3133
|
+
import { v4 as uuid9 } from "uuid";
|
|
3134
|
+
function registerVoiceSkills() {
|
|
3135
|
+
registerSkill(metaSTT, sttHandler);
|
|
3136
|
+
registerSkill(metaTTS, ttsHandler);
|
|
3137
|
+
}
|
|
3138
|
+
var COMPONENT21, metaSTT, metaTTS, sttHandler, ttsHandler;
|
|
3139
|
+
var init_voice = __esm({
|
|
3140
|
+
"src/skills/builtin/voice.ts"() {
|
|
3141
|
+
"use strict";
|
|
3142
|
+
init_registry();
|
|
3143
|
+
init_config();
|
|
3144
|
+
init_logger();
|
|
3145
|
+
COMPONENT21 = "VoiceTool";
|
|
3146
|
+
metaSTT = {
|
|
3147
|
+
name: "transcribe_audio",
|
|
3148
|
+
description: "Transcribes an audio file (mp3, wav, ogg, m4a) into text using OpenAI Whisper.",
|
|
3149
|
+
version: "1.0.0",
|
|
3150
|
+
source: "bundled",
|
|
3151
|
+
enabled: true
|
|
3152
|
+
};
|
|
3153
|
+
metaTTS = {
|
|
3154
|
+
name: "generate_speech",
|
|
3155
|
+
description: "Converts text into spoken audio (mp3) using OpenAI TTS.",
|
|
3156
|
+
version: "1.0.0",
|
|
3157
|
+
source: "bundled",
|
|
3158
|
+
enabled: true
|
|
3159
|
+
};
|
|
3160
|
+
sttHandler = {
|
|
3161
|
+
name: "transcribe_audio",
|
|
3162
|
+
description: "Reads an audio file from the filesystem and transcribes the speech into text. Extremely useful if a user sends a voice note.",
|
|
3163
|
+
parameters: {
|
|
3164
|
+
type: "object",
|
|
3165
|
+
properties: {
|
|
3166
|
+
filePath: {
|
|
3167
|
+
type: "string",
|
|
3168
|
+
description: "The absolute path to the audio file (supported formats: mp3, mp4, mpeg, mpga, m4a, wav, webm, ogg)."
|
|
3169
|
+
}
|
|
3170
|
+
},
|
|
3171
|
+
required: ["filePath"]
|
|
3172
|
+
},
|
|
3173
|
+
execute: async (args) => {
|
|
3174
|
+
const filePath = args.filePath;
|
|
3175
|
+
if (!filePath || !existsSync10(filePath)) {
|
|
3176
|
+
return `Error: Audio file not found at ${filePath}`;
|
|
3177
|
+
}
|
|
3178
|
+
const config = loadConfig();
|
|
3179
|
+
const apiKey = config.providers.openai?.apiKey;
|
|
3180
|
+
if (!apiKey) {
|
|
3181
|
+
return "Error: OpenAI API key is missing. Voice transcription requires OpenAI.";
|
|
3182
|
+
}
|
|
3183
|
+
try {
|
|
3184
|
+
logger_default.info(COMPONENT21, `Transcribing audio file: ${filePath}`);
|
|
3185
|
+
const fileBuffer = readFileSync8(filePath);
|
|
3186
|
+
const blob = new Blob([fileBuffer]);
|
|
3187
|
+
const formData = new FormData();
|
|
3188
|
+
formData.append("file", blob, "audio.ogg");
|
|
3189
|
+
formData.append("model", "whisper-1");
|
|
3190
|
+
const response = await fetch("https://api.openai.com/v1/audio/transcriptions", {
|
|
3191
|
+
method: "POST",
|
|
3192
|
+
headers: {
|
|
3193
|
+
"Authorization": `Bearer ${apiKey}`
|
|
3194
|
+
},
|
|
3195
|
+
body: formData
|
|
3196
|
+
});
|
|
3197
|
+
if (!response.ok) {
|
|
3198
|
+
throw new Error(`OpenAI STT error: ${await response.text()}`);
|
|
3199
|
+
}
|
|
3200
|
+
const data = await response.json();
|
|
3201
|
+
return `Transcript: "${data.text}"`;
|
|
3202
|
+
} catch (e) {
|
|
3203
|
+
logger_default.error(COMPONENT21, `Transcription failed: ${e.message}`);
|
|
3204
|
+
return `Error transcribing audio: ${e.message}`;
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
};
|
|
3208
|
+
ttsHandler = {
|
|
3209
|
+
name: "generate_speech",
|
|
3210
|
+
description: "Generates spoken audio from text and saves it as an MP3 file. Useful if the user asks you to read something out loud or send a voice message.",
|
|
3211
|
+
parameters: {
|
|
3212
|
+
type: "object",
|
|
3213
|
+
properties: {
|
|
3214
|
+
text: {
|
|
3215
|
+
type: "string",
|
|
3216
|
+
description: "The text you want to convert to speech."
|
|
3217
|
+
},
|
|
3218
|
+
voice: {
|
|
3219
|
+
type: "string",
|
|
3220
|
+
description: "The voice to use. Options: alloy, echo, fable, onyx, nova, shimmer. Default is alloy."
|
|
3221
|
+
}
|
|
3222
|
+
},
|
|
3223
|
+
required: ["text"]
|
|
3224
|
+
},
|
|
3225
|
+
execute: async (args) => {
|
|
3226
|
+
const text = args.text;
|
|
3227
|
+
const voice = args.voice || "alloy";
|
|
3228
|
+
if (!text) return "Error: You must provide text to generate speech.";
|
|
3229
|
+
const config = loadConfig();
|
|
3230
|
+
const apiKey = config.providers.openai?.apiKey;
|
|
3231
|
+
if (!apiKey) {
|
|
3232
|
+
return "Error: OpenAI API key is missing. TTS requires OpenAI.";
|
|
3233
|
+
}
|
|
3234
|
+
try {
|
|
3235
|
+
logger_default.info(COMPONENT21, `Generating speech with voice ${voice}`);
|
|
3236
|
+
const response = await fetch("https://api.openai.com/v1/audio/speech", {
|
|
3237
|
+
method: "POST",
|
|
3238
|
+
headers: {
|
|
3239
|
+
"Content-Type": "application/json",
|
|
3240
|
+
"Authorization": `Bearer ${apiKey}`
|
|
3241
|
+
},
|
|
3242
|
+
body: JSON.stringify({
|
|
3243
|
+
model: "tts-1",
|
|
3244
|
+
input: text,
|
|
3245
|
+
voice
|
|
3246
|
+
})
|
|
3247
|
+
});
|
|
3248
|
+
if (!response.ok) {
|
|
3249
|
+
throw new Error(`OpenAI TTS error: ${await response.text()}`);
|
|
3250
|
+
}
|
|
3251
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
3252
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
3253
|
+
const tmpPath = join5(tmpdir(), `titan_voice_${uuid9().substring(0, 8)}.mp3`);
|
|
3254
|
+
writeFileSync7(tmpPath, buffer);
|
|
3255
|
+
return `Success. Generated MP3 audio file saved to: ${tmpPath}`;
|
|
3256
|
+
} catch (e) {
|
|
3257
|
+
logger_default.error(COMPONENT21, `TTS failed: ${e.message}`);
|
|
3258
|
+
return `Error generating speech: ${e.message}`;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
});
|
|
3264
|
+
|
|
3265
|
+
// src/skills/registry.ts
|
|
3266
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
3267
|
+
import { join as join6 } from "path";
|
|
3268
|
+
function registerSkill(meta3, handler3) {
|
|
3269
|
+
registeredSkills.set(meta3.name, meta3);
|
|
3270
|
+
registerTool(handler3);
|
|
3271
|
+
logger_default.debug(COMPONENT22, `Registered skill: ${meta3.name} (${meta3.source})`);
|
|
3272
|
+
}
|
|
3273
|
+
function getSkills() {
|
|
3274
|
+
return Array.from(registeredSkills.values());
|
|
3275
|
+
}
|
|
3276
|
+
async function initBuiltinSkills() {
|
|
3277
|
+
logger_default.info(COMPONENT22, "Loading built-in skills...");
|
|
3278
|
+
const { registerShellSkill: registerShellSkill2 } = await Promise.resolve().then(() => (init_shell(), shell_exports));
|
|
3279
|
+
const { registerFilesystemSkill: registerFilesystemSkill2 } = await Promise.resolve().then(() => (init_filesystem(), filesystem_exports));
|
|
3280
|
+
const { registerWebSearchSkill: registerWebSearchSkill2 } = await Promise.resolve().then(() => (init_web_search(), web_search_exports));
|
|
3281
|
+
const { registerCronSkill: registerCronSkill2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
|
|
3282
|
+
const { registerWebhookSkill: registerWebhookSkill2 } = await Promise.resolve().then(() => (init_webhook(), webhook_exports));
|
|
3283
|
+
const { registerMemorySkill: registerMemorySkill2 } = await Promise.resolve().then(() => (init_memory_skill(), memory_skill_exports));
|
|
3284
|
+
const { registerBrowserSkill: registerBrowserSkill2 } = await Promise.resolve().then(() => (init_browser(), browser_exports));
|
|
3285
|
+
const { registerSessionsSkill: registerSessionsSkill2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
|
|
3286
|
+
const { registerProcessSkill: registerProcessSkill2 } = await Promise.resolve().then(() => (init_process(), process_exports));
|
|
3287
|
+
const { registerWebFetchSkill: registerWebFetchSkill2 } = await Promise.resolve().then(() => (init_web_fetch(), web_fetch_exports));
|
|
3288
|
+
const { registerApplyPatchSkill: registerApplyPatchSkill2 } = await Promise.resolve().then(() => (init_apply_patch(), apply_patch_exports));
|
|
3289
|
+
const { registerAutoGenerateSkill: registerAutoGenerateSkill2 } = await Promise.resolve().then(() => (init_auto_generate(), auto_generate_exports));
|
|
3290
|
+
const { registerVisionSkill: registerVisionSkill2 } = await Promise.resolve().then(() => (init_vision(), vision_exports));
|
|
3291
|
+
const { registerVoiceSkills: registerVoiceSkills2 } = await Promise.resolve().then(() => (init_voice(), voice_exports));
|
|
3292
|
+
registerShellSkill2();
|
|
3293
|
+
registerFilesystemSkill2();
|
|
3294
|
+
registerWebSearchSkill2();
|
|
3295
|
+
registerCronSkill2();
|
|
3296
|
+
registerWebhookSkill2();
|
|
3297
|
+
registerMemorySkill2();
|
|
3298
|
+
registerBrowserSkill2();
|
|
3299
|
+
registerSessionsSkill2();
|
|
3300
|
+
registerProcessSkill2();
|
|
3301
|
+
registerWebFetchSkill2();
|
|
3302
|
+
registerApplyPatchSkill2();
|
|
3303
|
+
registerAutoGenerateSkill2();
|
|
3304
|
+
registerVisionSkill2();
|
|
3305
|
+
registerVoiceSkills2();
|
|
3306
|
+
logger_default.info(COMPONENT22, `Loaded ${registeredSkills.size} built-in skills`);
|
|
3307
|
+
}
|
|
3308
|
+
async function loadAutoSkills() {
|
|
3309
|
+
const autoDir = join6(TITAN_HOME, "skills", "auto");
|
|
3310
|
+
if (!existsSync11(autoDir)) return;
|
|
3311
|
+
logger_default.info(COMPONENT22, "Checking for auto-generated skills...");
|
|
3312
|
+
const files = readdirSync2(autoDir).filter((f) => f.endsWith(".js"));
|
|
3313
|
+
let loadedCount = 0;
|
|
3314
|
+
for (const file of files) {
|
|
3315
|
+
try {
|
|
3316
|
+
const skillPath = join6(autoDir, file);
|
|
3317
|
+
const modulePath = `file://${skillPath}?t=${Date.now()}`;
|
|
3318
|
+
const mod = await import(modulePath);
|
|
3319
|
+
if (mod.default && mod.default.name && mod.default.execute) {
|
|
3320
|
+
const handler3 = mod.default;
|
|
3321
|
+
const meta3 = {
|
|
3322
|
+
name: handler3.name,
|
|
3323
|
+
description: handler3.description || "Auto-generated skill",
|
|
3324
|
+
version: "1.0.0",
|
|
3325
|
+
source: "workspace",
|
|
3326
|
+
// Consider it part of user workspace
|
|
3327
|
+
enabled: true
|
|
3328
|
+
};
|
|
3329
|
+
registerSkill(meta3, handler3);
|
|
3330
|
+
loadedCount++;
|
|
3331
|
+
}
|
|
3332
|
+
} catch (e) {
|
|
3333
|
+
logger_default.error(COMPONENT22, `Failed to load auto skill ${file}: ${e.message}`);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
if (loadedCount > 0) {
|
|
3337
|
+
logger_default.info(COMPONENT22, `Successfully loaded ${loadedCount} auto-generated skills`);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
var COMPONENT22, registeredSkills;
|
|
3341
|
+
var init_registry = __esm({
|
|
3342
|
+
"src/skills/registry.ts"() {
|
|
3343
|
+
"use strict";
|
|
3344
|
+
init_constants();
|
|
3345
|
+
init_toolRunner();
|
|
3346
|
+
init_helpers();
|
|
3347
|
+
init_logger();
|
|
3348
|
+
COMPONENT22 = "Skills";
|
|
3349
|
+
registeredSkills = /* @__PURE__ */ new Map();
|
|
3350
|
+
}
|
|
3351
|
+
});
|
|
3352
|
+
|
|
3353
|
+
// src/gateway/server.ts
|
|
3354
|
+
init_config();
|
|
3355
|
+
init_memory();
|
|
3356
|
+
init_registry();
|
|
3357
|
+
init_toolRunner();
|
|
3358
|
+
init_session();
|
|
3359
|
+
init_router();
|
|
3360
|
+
import express from "express";
|
|
3361
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
3362
|
+
import { createServer } from "http";
|
|
3363
|
+
|
|
3364
|
+
// src/security/sandbox.ts
|
|
3365
|
+
init_config();
|
|
3366
|
+
init_logger();
|
|
3367
|
+
function auditSecurity() {
|
|
3368
|
+
const config = loadConfig();
|
|
3369
|
+
const issues = [];
|
|
3370
|
+
if (config.security.sandboxMode === "none") {
|
|
3371
|
+
issues.push({ level: "warn", message: "Sandbox mode is disabled. Non-main sessions will have full host access." });
|
|
3372
|
+
}
|
|
3373
|
+
if (config.security.deniedTools.length === 0) {
|
|
3374
|
+
issues.push({ level: "info", message: "No tools are explicitly denied." });
|
|
3375
|
+
}
|
|
3376
|
+
if (config.security.networkAllowlist.includes("*")) {
|
|
3377
|
+
issues.push({ level: "info", message: "Network access is unrestricted (allowlist: *)." });
|
|
3378
|
+
}
|
|
3379
|
+
if (config.security.commandTimeout > 6e4) {
|
|
3380
|
+
issues.push({ level: "warn", message: `Command timeout is very high: ${config.security.commandTimeout}ms` });
|
|
3381
|
+
}
|
|
3382
|
+
for (const [name, channel] of Object.entries(config.channels)) {
|
|
3383
|
+
if (channel.enabled && channel.dmPolicy === "open") {
|
|
3384
|
+
issues.push({ level: "warn", message: `Channel "${name}" has open DM policy \u2014 any user can message the bot.` });
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
return issues;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// src/channels/base.ts
|
|
3391
|
+
import { EventEmitter } from "events";
|
|
3392
|
+
var ChannelAdapter = class extends EventEmitter {
|
|
3393
|
+
};
|
|
3394
|
+
|
|
3395
|
+
// src/channels/webchat.ts
|
|
3396
|
+
init_logger();
|
|
3397
|
+
var COMPONENT23 = "WebChat";
|
|
3398
|
+
var outboundQueue = [];
|
|
3399
|
+
var WebChatChannel = class extends ChannelAdapter {
|
|
3400
|
+
name = "webchat";
|
|
3401
|
+
displayName = "WebChat";
|
|
3402
|
+
connected = false;
|
|
3403
|
+
async connect() {
|
|
3404
|
+
this.connected = true;
|
|
3405
|
+
logger_default.info(COMPONENT23, "WebChat channel ready");
|
|
3406
|
+
}
|
|
3407
|
+
async disconnect() {
|
|
3408
|
+
this.connected = false;
|
|
3409
|
+
}
|
|
3410
|
+
/** Handle an inbound WebSocket message */
|
|
3411
|
+
handleWebSocketMessage(userId, content) {
|
|
3412
|
+
const inbound = {
|
|
3413
|
+
id: `wc-${Date.now()}`,
|
|
3414
|
+
channel: "webchat",
|
|
3415
|
+
userId,
|
|
3416
|
+
content,
|
|
3417
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
3418
|
+
};
|
|
3419
|
+
this.emit("message", inbound);
|
|
3420
|
+
}
|
|
3421
|
+
async send(message) {
|
|
3422
|
+
outboundQueue.push(message);
|
|
3423
|
+
}
|
|
3424
|
+
getStatus() {
|
|
3425
|
+
return { name: this.displayName, connected: this.connected };
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3428
|
+
|
|
3429
|
+
// src/channels/discord.ts
|
|
3430
|
+
init_config();
|
|
3431
|
+
init_logger();
|
|
3432
|
+
var COMPONENT24 = "Discord";
|
|
3433
|
+
var DiscordChannel = class extends ChannelAdapter {
|
|
3434
|
+
name = "discord";
|
|
3435
|
+
displayName = "Discord";
|
|
3436
|
+
connected = false;
|
|
3437
|
+
client = null;
|
|
3438
|
+
async connect() {
|
|
3439
|
+
const config = loadConfig();
|
|
3440
|
+
const channelConfig = config.channels.discord;
|
|
3441
|
+
if (!channelConfig.enabled) {
|
|
3442
|
+
logger_default.info(COMPONENT24, "Discord channel is disabled");
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
const token = channelConfig.token;
|
|
3446
|
+
if (!token) {
|
|
3447
|
+
logger_default.warn(COMPONENT24, "Discord token not configured");
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
try {
|
|
3451
|
+
const { Client, GatewayIntentBits, Events } = await import("discord.js");
|
|
3452
|
+
const client = new Client({
|
|
3453
|
+
intents: [
|
|
3454
|
+
GatewayIntentBits.Guilds,
|
|
3455
|
+
GatewayIntentBits.GuildMessages,
|
|
3456
|
+
GatewayIntentBits.DirectMessages,
|
|
3457
|
+
GatewayIntentBits.MessageContent
|
|
3458
|
+
]
|
|
3459
|
+
});
|
|
3460
|
+
client.on(Events.MessageCreate, (message) => {
|
|
3461
|
+
if (message.author.bot) return;
|
|
3462
|
+
const inbound = {
|
|
3463
|
+
id: message.id,
|
|
3464
|
+
channel: "discord",
|
|
3465
|
+
userId: message.author.id,
|
|
3466
|
+
userName: message.author.username,
|
|
3467
|
+
content: message.content,
|
|
3468
|
+
groupId: message.guild?.id,
|
|
3469
|
+
timestamp: message.createdAt,
|
|
3470
|
+
raw: message
|
|
3471
|
+
};
|
|
3472
|
+
this.emit("message", inbound);
|
|
3473
|
+
});
|
|
3474
|
+
client.on(Events.ClientReady, () => {
|
|
3475
|
+
this.connected = true;
|
|
3476
|
+
logger_default.info(COMPONENT24, `Connected as ${client.user?.tag}`);
|
|
3477
|
+
});
|
|
3478
|
+
await client.login(token);
|
|
3479
|
+
this.client = client;
|
|
3480
|
+
} catch (error) {
|
|
3481
|
+
logger_default.error(COMPONENT24, `Failed to connect: ${error.message}`);
|
|
3482
|
+
logger_default.info(COMPONENT24, "Install discord.js with: npm install discord.js");
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
async disconnect() {
|
|
3486
|
+
if (this.client) {
|
|
3487
|
+
this.client.destroy();
|
|
3488
|
+
this.connected = false;
|
|
3489
|
+
logger_default.info(COMPONENT24, "Disconnected");
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
async send(message) {
|
|
3493
|
+
if (!this.client || !this.connected) {
|
|
3494
|
+
logger_default.warn(COMPONENT24, "Not connected, cannot send message");
|
|
3495
|
+
return;
|
|
3496
|
+
}
|
|
3497
|
+
try {
|
|
3498
|
+
const client = this.client;
|
|
3499
|
+
if (message.userId) {
|
|
3500
|
+
const user = await client.users.fetch(message.userId);
|
|
3501
|
+
const dm = await user.createDM();
|
|
3502
|
+
await dm.send(message.content);
|
|
3503
|
+
} else if (message.groupId) {
|
|
3504
|
+
const channel = await client.channels.fetch(message.groupId);
|
|
3505
|
+
if (channel?.isTextBased()) {
|
|
3506
|
+
await channel.send(message.content);
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
} catch (error) {
|
|
3510
|
+
logger_default.error(COMPONENT24, `Send failed: ${error.message}`);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
getStatus() {
|
|
3514
|
+
return {
|
|
3515
|
+
name: this.displayName,
|
|
3516
|
+
connected: this.connected
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
};
|
|
3520
|
+
|
|
3521
|
+
// src/channels/telegram.ts
|
|
3522
|
+
init_config();
|
|
3523
|
+
init_logger();
|
|
3524
|
+
var COMPONENT25 = "Telegram";
|
|
3525
|
+
var TelegramChannel = class extends ChannelAdapter {
|
|
3526
|
+
name = "telegram";
|
|
3527
|
+
displayName = "Telegram";
|
|
3528
|
+
connected = false;
|
|
3529
|
+
bot = null;
|
|
3530
|
+
async connect() {
|
|
3531
|
+
const config = loadConfig();
|
|
3532
|
+
const channelConfig = config.channels.telegram;
|
|
3533
|
+
if (!channelConfig.enabled) {
|
|
3534
|
+
logger_default.info(COMPONENT25, "Telegram channel is disabled");
|
|
3535
|
+
return;
|
|
3536
|
+
}
|
|
3537
|
+
const token = channelConfig.token;
|
|
3538
|
+
if (!token) {
|
|
3539
|
+
logger_default.warn(COMPONENT25, "Telegram token not configured");
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
try {
|
|
3543
|
+
const { Bot } = await import("grammy");
|
|
3544
|
+
const bot = new Bot(token);
|
|
3545
|
+
bot.on("message:text", (ctx) => {
|
|
3546
|
+
const inbound = {
|
|
3547
|
+
id: String(ctx.message.message_id),
|
|
3548
|
+
channel: "telegram",
|
|
3549
|
+
userId: String(ctx.from.id),
|
|
3550
|
+
userName: ctx.from.username || ctx.from.first_name,
|
|
3551
|
+
content: ctx.message.text,
|
|
3552
|
+
groupId: ctx.chat.type !== "private" ? String(ctx.chat.id) : void 0,
|
|
3553
|
+
timestamp: new Date(ctx.message.date * 1e3),
|
|
3554
|
+
raw: ctx
|
|
3555
|
+
};
|
|
3556
|
+
this.emit("message", inbound);
|
|
3557
|
+
});
|
|
3558
|
+
bot.start();
|
|
3559
|
+
this.bot = bot;
|
|
3560
|
+
this.connected = true;
|
|
3561
|
+
logger_default.info(COMPONENT25, "Connected to Telegram");
|
|
3562
|
+
} catch (error) {
|
|
3563
|
+
logger_default.error(COMPONENT25, `Failed to connect: ${error.message}`);
|
|
3564
|
+
logger_default.info(COMPONENT25, "Install grammy with: npm install grammy");
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
async disconnect() {
|
|
3568
|
+
if (this.bot) {
|
|
3569
|
+
this.bot.stop();
|
|
3570
|
+
this.connected = false;
|
|
3571
|
+
logger_default.info(COMPONENT25, "Disconnected");
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
async send(message) {
|
|
3575
|
+
if (!this.bot || !this.connected) return;
|
|
3576
|
+
try {
|
|
3577
|
+
const chatId = message.userId || message.groupId;
|
|
3578
|
+
if (chatId) {
|
|
3579
|
+
await this.bot.api.sendMessage(chatId, message.content, { parse_mode: "Markdown" });
|
|
3580
|
+
}
|
|
3581
|
+
} catch (error) {
|
|
3582
|
+
logger_default.error(COMPONENT25, `Send failed: ${error.message}`);
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
getStatus() {
|
|
3586
|
+
return { name: this.displayName, connected: this.connected };
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
|
|
3590
|
+
// src/channels/slack.ts
|
|
3591
|
+
init_config();
|
|
3592
|
+
init_logger();
|
|
3593
|
+
var COMPONENT26 = "Slack";
|
|
3594
|
+
var SlackChannel = class extends ChannelAdapter {
|
|
3595
|
+
name = "slack";
|
|
3596
|
+
displayName = "Slack";
|
|
3597
|
+
connected = false;
|
|
3598
|
+
async connect() {
|
|
3599
|
+
const config = loadConfig();
|
|
3600
|
+
const channelConfig = config.channels.slack;
|
|
3601
|
+
if (!channelConfig.enabled) {
|
|
3602
|
+
logger_default.info(COMPONENT26, "Slack channel is disabled");
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
if (!channelConfig.token) {
|
|
3606
|
+
logger_default.warn(COMPONENT26, "Slack token not configured");
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
try {
|
|
3610
|
+
const { App } = await import("@slack/bolt");
|
|
3611
|
+
const app = new App({
|
|
3612
|
+
token: channelConfig.token,
|
|
3613
|
+
signingSecret: channelConfig.apiKey || "",
|
|
3614
|
+
socketMode: true,
|
|
3615
|
+
appToken: channelConfig.apiKey || ""
|
|
3616
|
+
});
|
|
3617
|
+
app.message(async ({ message, say }) => {
|
|
3618
|
+
if (message.subtype) return;
|
|
3619
|
+
const inbound = {
|
|
3620
|
+
id: message.ts,
|
|
3621
|
+
channel: "slack",
|
|
3622
|
+
userId: message.user,
|
|
3623
|
+
content: message.text || "",
|
|
3624
|
+
groupId: message.channel,
|
|
3625
|
+
timestamp: new Date(parseFloat(message.ts) * 1e3),
|
|
3626
|
+
raw: message
|
|
3627
|
+
};
|
|
3628
|
+
this.emit("message", inbound);
|
|
3629
|
+
});
|
|
3630
|
+
await app.start();
|
|
3631
|
+
this.connected = true;
|
|
3632
|
+
logger_default.info(COMPONENT26, "Connected to Slack");
|
|
3633
|
+
} catch (error) {
|
|
3634
|
+
logger_default.error(COMPONENT26, `Failed to connect: ${error.message}`);
|
|
3635
|
+
logger_default.info(COMPONENT26, "Install Bolt with: npm install @slack/bolt");
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
async disconnect() {
|
|
3639
|
+
this.connected = false;
|
|
3640
|
+
}
|
|
3641
|
+
async send(message) {
|
|
3642
|
+
logger_default.debug(COMPONENT26, `Would send to ${message.userId || message.groupId}: ${message.content.slice(0, 100)}`);
|
|
3643
|
+
}
|
|
3644
|
+
getStatus() {
|
|
3645
|
+
return { name: this.displayName, connected: this.connected };
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
|
|
3649
|
+
// src/channels/googlechat.ts
|
|
3650
|
+
init_config();
|
|
3651
|
+
init_logger();
|
|
3652
|
+
var COMPONENT27 = "GoogleChat";
|
|
3653
|
+
var GoogleChatChannel = class extends ChannelAdapter {
|
|
3654
|
+
name = "googlechat";
|
|
3655
|
+
displayName = "Google Chat";
|
|
3656
|
+
connected = false;
|
|
3657
|
+
async connect() {
|
|
3658
|
+
const config = loadConfig();
|
|
3659
|
+
const channelConfig = config.channels.googlechat;
|
|
3660
|
+
if (!channelConfig?.enabled) {
|
|
3661
|
+
logger_default.info(COMPONENT27, "Google Chat channel is disabled");
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
logger_default.info(COMPONENT27, "Google Chat adapter ready (webhook mode)");
|
|
3665
|
+
this.connected = true;
|
|
3666
|
+
}
|
|
3667
|
+
async disconnect() {
|
|
3668
|
+
this.connected = false;
|
|
3669
|
+
}
|
|
3670
|
+
async send(message) {
|
|
3671
|
+
if (!this.connected) return;
|
|
3672
|
+
logger_default.debug(COMPONENT27, `Would send to ${message.userId || message.groupId}: ${message.content.slice(0, 100)}`);
|
|
3673
|
+
}
|
|
3674
|
+
getStatus() {
|
|
3675
|
+
return { name: this.displayName, connected: this.connected };
|
|
3676
|
+
}
|
|
3677
|
+
};
|
|
3678
|
+
|
|
3679
|
+
// src/agent/multiAgent.ts
|
|
3680
|
+
init_config();
|
|
3681
|
+
init_agent();
|
|
3682
|
+
import { v4 as uuid10 } from "uuid";
|
|
3683
|
+
|
|
3684
|
+
// src/security/shield.ts
|
|
3685
|
+
init_logger();
|
|
3686
|
+
init_config();
|
|
3687
|
+
var COMPONENT28 = "Shield";
|
|
3688
|
+
var INJECTION_PATTERNS = [
|
|
3689
|
+
// Ignore previous instructions
|
|
3690
|
+
/(?:ignore|disregard|forget)(?: all)? (?:previous|prior) (?:instructions|directions|prompts|rules)/i,
|
|
3691
|
+
/ignore the above/i,
|
|
3692
|
+
// Roleplay / Jailbreak
|
|
3693
|
+
/(?:you are now|pretend you are|act as) (?:a|an) (?:unrestricted|unbound|developer mode|DAN|do anything now)/i,
|
|
3694
|
+
/system override|override authorization/i,
|
|
3695
|
+
// Prompt extraction
|
|
3696
|
+
/(?:repeat|print|output|show|tell me) (?:the|all|your) (?:instructions|system prompt|initial prompt|rules|directives)/i,
|
|
3697
|
+
// Base64 disguised injections
|
|
3698
|
+
/(?:decode|translate) (?:this|the following) base64/i,
|
|
3699
|
+
// Markdown link exploits
|
|
3700
|
+
/\[.*\]\(javascript:/i
|
|
3701
|
+
];
|
|
3702
|
+
var STRICT_KEYWORDS = [
|
|
3703
|
+
"system prompt",
|
|
3704
|
+
"ignore previous",
|
|
3705
|
+
"developer mode",
|
|
3706
|
+
"unrestricted",
|
|
3707
|
+
"bypass",
|
|
3708
|
+
"DAN"
|
|
3709
|
+
];
|
|
3710
|
+
function checkPromptInjection(text) {
|
|
3711
|
+
const config = loadConfig();
|
|
3712
|
+
const shieldConfig = config.security?.shield || { enabled: true, mode: "strict" };
|
|
3713
|
+
if (!shieldConfig.enabled) {
|
|
3714
|
+
return { safe: true };
|
|
3715
|
+
}
|
|
3716
|
+
const normalized = text.toLowerCase().trim();
|
|
3717
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
3718
|
+
if (pattern.test(normalized)) {
|
|
3719
|
+
logger_default.warn(COMPONENT28, `\u{1F6E1}\uFE0F Blocked prompt injection attempt! Pattern: ${pattern}`);
|
|
3720
|
+
return {
|
|
3721
|
+
safe: false,
|
|
3722
|
+
reason: "Input matches known prompt injection signatures.",
|
|
3723
|
+
matchedPattern: pattern.toString()
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
if (shieldConfig.mode === "strict") {
|
|
3728
|
+
let flagCount = 0;
|
|
3729
|
+
for (const kw of STRICT_KEYWORDS) {
|
|
3730
|
+
if (normalized.includes(kw)) {
|
|
3731
|
+
flagCount++;
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
if (flagCount >= 2) {
|
|
3735
|
+
logger_default.warn(COMPONENT28, `\u{1F6E1}\uFE0F Blocked suspicious request! (Strict mode keyword density)`);
|
|
3736
|
+
return {
|
|
3737
|
+
safe: false,
|
|
3738
|
+
reason: "Input contains too many high-risk keywords (strict mode)."
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
if (normalized.length > 5e3) {
|
|
3742
|
+
const tail = normalized.slice(-500);
|
|
3743
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
3744
|
+
if (pattern.test(tail)) {
|
|
3745
|
+
logger_default.warn(COMPONENT28, `\u{1F6E1}\uFE0F Blocked prompt injection in payload tail.`);
|
|
3746
|
+
return { safe: false, reason: "Injection pattern found at end of large payload." };
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
return { safe: true };
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
// src/agent/multiAgent.ts
|
|
3755
|
+
init_logger();
|
|
3756
|
+
var COMPONENT29 = "MultiAgent";
|
|
3757
|
+
var MAX_AGENTS = 5;
|
|
3758
|
+
var agents = /* @__PURE__ */ new Map();
|
|
3759
|
+
var defaultAgentId = null;
|
|
3760
|
+
function initAgents() {
|
|
3761
|
+
if (agents.size > 0) return;
|
|
3762
|
+
const config = loadConfig();
|
|
3763
|
+
const defaultAgent = {
|
|
3764
|
+
id: "default",
|
|
3765
|
+
name: "TITAN Primary",
|
|
3766
|
+
model: config.agent.model,
|
|
3767
|
+
systemPrompt: config.agent.systemPrompt,
|
|
3768
|
+
status: "running",
|
|
3769
|
+
channelBindings: [{ channel: "*", pattern: "*" }],
|
|
3770
|
+
messageCount: 0,
|
|
3771
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3772
|
+
lastActive: (/* @__PURE__ */ new Date()).toISOString()
|
|
3773
|
+
};
|
|
3774
|
+
agents.set("default", defaultAgent);
|
|
3775
|
+
defaultAgentId = "default";
|
|
3776
|
+
logger_default.info(COMPONENT29, "Default agent initialized");
|
|
3777
|
+
}
|
|
3778
|
+
function spawnAgent(options) {
|
|
3779
|
+
initAgents();
|
|
3780
|
+
if (agents.size >= MAX_AGENTS) {
|
|
3781
|
+
return { success: false, error: `Maximum ${MAX_AGENTS} agents reached. Stop an existing agent to spawn a new one.` };
|
|
3782
|
+
}
|
|
3783
|
+
const config = loadConfig();
|
|
3784
|
+
const id = uuid10().slice(0, 8);
|
|
3785
|
+
const agent = {
|
|
3786
|
+
id,
|
|
3787
|
+
name: options.name,
|
|
3788
|
+
model: options.model || config.agent.model,
|
|
3789
|
+
systemPrompt: options.systemPrompt,
|
|
3790
|
+
workspace: options.workspace,
|
|
3791
|
+
status: "running",
|
|
3792
|
+
channelBindings: options.channelBindings || [],
|
|
3793
|
+
messageCount: 0,
|
|
3794
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3795
|
+
lastActive: (/* @__PURE__ */ new Date()).toISOString()
|
|
3796
|
+
};
|
|
3797
|
+
agents.set(id, agent);
|
|
3798
|
+
logger_default.info(COMPONENT29, `Spawned agent "${agent.name}" (${id}) \u2014 model: ${agent.model} [${agents.size}/${MAX_AGENTS}]`);
|
|
3799
|
+
return { success: true, agent };
|
|
3800
|
+
}
|
|
3801
|
+
function stopAgent(agentId) {
|
|
3802
|
+
if (agentId === "default") {
|
|
3803
|
+
return { success: false, error: "Cannot stop the default agent." };
|
|
3804
|
+
}
|
|
3805
|
+
const agent = agents.get(agentId);
|
|
3806
|
+
if (!agent) {
|
|
3807
|
+
return { success: false, error: `Agent "${agentId}" not found.` };
|
|
3808
|
+
}
|
|
3809
|
+
agent.status = "stopped";
|
|
3810
|
+
agents.delete(agentId);
|
|
3811
|
+
logger_default.info(COMPONENT29, `Stopped agent "${agent.name}" (${agentId}) [${agents.size}/${MAX_AGENTS}]`);
|
|
3812
|
+
return { success: true };
|
|
3813
|
+
}
|
|
3814
|
+
function listAgents() {
|
|
3815
|
+
initAgents();
|
|
3816
|
+
return Array.from(agents.values());
|
|
3817
|
+
}
|
|
3818
|
+
function resolveAgent(channel, userId) {
|
|
3819
|
+
initAgents();
|
|
3820
|
+
const agentList = Array.from(agents.values()).reverse();
|
|
3821
|
+
for (const agent of agentList) {
|
|
3822
|
+
if (agent.status !== "running") continue;
|
|
3823
|
+
for (const binding of agent.channelBindings) {
|
|
3824
|
+
if (binding.channel === "*" || binding.channel === channel) {
|
|
3825
|
+
if (binding.pattern === "*" || binding.pattern === userId) {
|
|
3826
|
+
return agent;
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
return agents.get("default");
|
|
3832
|
+
}
|
|
3833
|
+
async function routeMessage(message, channel, userId) {
|
|
3834
|
+
const agent = resolveAgent(channel, userId);
|
|
3835
|
+
agent.messageCount++;
|
|
3836
|
+
agent.lastActive = (/* @__PURE__ */ new Date()).toISOString();
|
|
3837
|
+
logger_default.info(COMPONENT29, `Routing to agent "${agent.name}" (${agent.id}) for ${channel}/${userId}`);
|
|
3838
|
+
const shieldResult = checkPromptInjection(message);
|
|
3839
|
+
if (!shieldResult.safe) {
|
|
3840
|
+
logger_default.warn(COMPONENT29, `Message rejected by Shield: ${shieldResult.reason}`);
|
|
3841
|
+
return {
|
|
3842
|
+
content: `\u{1F6E1}\uFE0F Message rejected by TITAN Security Shield: ${shieldResult.reason}`,
|
|
3843
|
+
sessionId: channel + ":" + userId,
|
|
3844
|
+
toolsUsed: [],
|
|
3845
|
+
tokenUsage: { prompt: 0, completion: 0, total: 0 },
|
|
3846
|
+
model: "shield-interceptor",
|
|
3847
|
+
durationMs: 0,
|
|
3848
|
+
agentId: agent.id,
|
|
3849
|
+
agentName: agent.name
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
const response = await processMessage(message, channel, userId);
|
|
3853
|
+
return {
|
|
3854
|
+
...response,
|
|
3855
|
+
agentId: agent.id,
|
|
3856
|
+
agentName: agent.name
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
function getAgentCapacity() {
|
|
3860
|
+
initAgents();
|
|
3861
|
+
return {
|
|
3862
|
+
current: agents.size,
|
|
3863
|
+
max: MAX_AGENTS,
|
|
3864
|
+
available: MAX_AGENTS - agents.size
|
|
3865
|
+
};
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
// src/gateway/server.ts
|
|
3869
|
+
init_logger();
|
|
3870
|
+
init_constants();
|
|
3871
|
+
|
|
3872
|
+
// src/gateway/dashboard.ts
|
|
3873
|
+
function getMissionControlHTML() {
|
|
3874
|
+
return `<!DOCTYPE html>
|
|
3875
|
+
<html lang="en">
|
|
3876
|
+
<head>
|
|
3877
|
+
<meta charset="utf-8"/>
|
|
3878
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
3879
|
+
<title>TITAN Mission Control</title>
|
|
3880
|
+
<style>
|
|
3881
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
3882
|
+
:root{
|
|
3883
|
+
--bg:#0a0e1a;--bg2:#111827;--bg3:#1a1f36;--bg4:#252b48;
|
|
3884
|
+
--accent:#06b6d4;--accent2:#8b5cf6;--accent3:#10b981;--accent4:#f59e0b;
|
|
3885
|
+
--error:#ef4444;--warn:#f59e0b;
|
|
3886
|
+
--text:#e2e8f0;--text-dim:#94a3b8;--text-bright:#f8fafc;
|
|
3887
|
+
--border:#2a3050;--glow:0 0 20px rgba(6,182,212,0.15);
|
|
3888
|
+
--radius:12px;--radius-sm:8px;
|
|
3889
|
+
}
|
|
3890
|
+
body{font-family:'Inter','Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text);display:flex;height:100vh;overflow:hidden}
|
|
3891
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
3892
|
+
|
|
3893
|
+
/* Sidebar */
|
|
3894
|
+
.sidebar{width:240px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
|
|
3895
|
+
.logo-area{padding:20px;border-bottom:1px solid var(--border);text-align:center}
|
|
3896
|
+
.logo-area h1{font-size:22px;font-weight:700;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:2px}
|
|
3897
|
+
.logo-area .version{font-size:11px;color:var(--text-dim);margin-top:4px;font-family:'JetBrains Mono',monospace}
|
|
3898
|
+
.nav{flex:1;padding:12px 8px;overflow-y:auto}
|
|
3899
|
+
.nav-section{font-size:10px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:1.5px;padding:12px 12px 6px;margin-top:8px}
|
|
3900
|
+
.nav-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:var(--radius-sm);cursor:pointer;color:var(--text-dim);font-size:13px;font-weight:500;transition:all .15s}
|
|
3901
|
+
.nav-item:hover{background:var(--bg3);color:var(--text)}
|
|
3902
|
+
.nav-item.active{background:linear-gradient(135deg,rgba(6,182,212,.15),rgba(139,92,246,.1));color:var(--accent);border:1px solid rgba(6,182,212,.2)}
|
|
3903
|
+
.nav-item .icon{font-size:16px;width:20px;text-align:center}
|
|
3904
|
+
.sidebar-footer{padding:12px;border-top:1px solid var(--border);font-size:11px;color:var(--text-dim);text-align:center}
|
|
3905
|
+
|
|
3906
|
+
/* Main */
|
|
3907
|
+
.main{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
|
3908
|
+
.topbar{height:56px;background:var(--bg2);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 24px;flex-shrink:0}
|
|
3909
|
+
.topbar h2{font-size:16px;font-weight:600}
|
|
3910
|
+
.topbar .status{display:flex;gap:16px;align-items:center}
|
|
3911
|
+
.status-dot{width:8px;height:8px;border-radius:50%;background:var(--accent3);box-shadow:0 0 8px var(--accent3);animation:pulse 2s infinite}
|
|
3912
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
|
3913
|
+
.status-label{font-size:12px;color:var(--text-dim)}
|
|
3914
|
+
|
|
3915
|
+
.content{flex:1;overflow-y:auto;padding:24px;display:grid;gap:20px}
|
|
3916
|
+
.panel{display:none}
|
|
3917
|
+
.panel.active{display:block}
|
|
3918
|
+
|
|
3919
|
+
/* Cards */
|
|
3920
|
+
.card{background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);padding:20px;box-shadow:var(--glow)}
|
|
3921
|
+
.card h3{font-size:14px;font-weight:600;margin-bottom:12px;color:var(--text-bright)}
|
|
3922
|
+
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}
|
|
3923
|
+
|
|
3924
|
+
/* Stat cards */
|
|
3925
|
+
.stat-card{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:16px;position:relative;overflow:hidden}
|
|
3926
|
+
.stat-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;border-radius:var(--radius) var(--radius) 0 0}
|
|
3927
|
+
.stat-card.cyan::before{background:linear-gradient(90deg,var(--accent),transparent)}
|
|
3928
|
+
.stat-card.purple::before{background:linear-gradient(90deg,var(--accent2),transparent)}
|
|
3929
|
+
.stat-card.green::before{background:linear-gradient(90deg,var(--accent3),transparent)}
|
|
3930
|
+
.stat-card.amber::before{background:linear-gradient(90deg,var(--accent4),transparent)}
|
|
3931
|
+
.stat-label{font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px}
|
|
3932
|
+
.stat-value{font-size:28px;font-weight:700;margin-top:4px;font-family:'JetBrains Mono',monospace}
|
|
3933
|
+
.stat-sub{font-size:11px;color:var(--text-dim);margin-top:4px}
|
|
3934
|
+
|
|
3935
|
+
/* Tables */
|
|
3936
|
+
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
3937
|
+
th{text-align:left;padding:10px 12px;border-bottom:2px solid var(--border);color:var(--text-dim);font-size:11px;text-transform:uppercase;letter-spacing:1px;font-weight:600}
|
|
3938
|
+
td{padding:10px 12px;border-bottom:1px solid var(--border)}
|
|
3939
|
+
tr:hover{background:rgba(6,182,212,.03)}
|
|
3940
|
+
|
|
3941
|
+
/* Badges */
|
|
3942
|
+
.badge{display:inline-block;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:600}
|
|
3943
|
+
.badge.active{background:rgba(16,185,129,.15);color:var(--accent3)}
|
|
3944
|
+
.badge.idle{background:rgba(148,163,184,.1);color:var(--text-dim)}
|
|
3945
|
+
.badge.error{background:rgba(239,68,68,.15);color:var(--error)}
|
|
3946
|
+
.badge.warn{background:rgba(245,158,11,.15);color:var(--warn)}
|
|
3947
|
+
.badge.info{background:rgba(6,182,212,.15);color:var(--accent)}
|
|
3948
|
+
|
|
3949
|
+
/* Chat */
|
|
3950
|
+
.chat-container{display:flex;flex-direction:column;height:calc(100vh - 140px)}
|
|
3951
|
+
.chat-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}
|
|
3952
|
+
.msg{max-width:75%;padding:12px 16px;border-radius:16px;font-size:14px;line-height:1.5;animation:fadeIn .2s}
|
|
3953
|
+
@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
3954
|
+
.msg.user{align-self:flex-end;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;border-bottom-right-radius:4px}
|
|
3955
|
+
.msg.assistant{align-self:flex-start;background:var(--bg3);border:1px solid var(--border);border-bottom-left-radius:4px}
|
|
3956
|
+
.chat-input-area{padding:16px;border-top:1px solid var(--border);display:flex;gap:12px}
|
|
3957
|
+
.chat-input-area input{flex:1;background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius);padding:12px 16px;color:var(--text);font-size:14px;outline:none;transition:border .2s}
|
|
3958
|
+
.chat-input-area input:focus{border-color:var(--accent)}
|
|
3959
|
+
.chat-input-area button{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;border:none;padding:12px 24px;border-radius:var(--radius);font-weight:600;cursor:pointer;font-size:14px;transition:transform .1s}
|
|
3960
|
+
.chat-input-area button:hover{transform:scale(1.02)}
|
|
3961
|
+
.chat-input-area button:active{transform:scale(0.98)}
|
|
3962
|
+
|
|
3963
|
+
/* Buttons */
|
|
3964
|
+
.btn{padding:8px 16px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg3);color:var(--text);font-size:12px;font-weight:500;cursor:pointer;transition:all .15s}
|
|
3965
|
+
.btn:hover{background:var(--bg4);border-color:var(--accent)}
|
|
3966
|
+
.btn.primary{background:linear-gradient(135deg,var(--accent),var(--accent2));border:none;color:#fff}
|
|
3967
|
+
.btn.danger{background:rgba(239,68,68,.1);border-color:var(--error);color:var(--error)}
|
|
3968
|
+
.btn.danger:hover{background:rgba(239,68,68,.2)}
|
|
3969
|
+
|
|
3970
|
+
/* Form inputs */
|
|
3971
|
+
.form-row{display:flex;gap:12px;margin-bottom:12px;align-items:center}
|
|
3972
|
+
.form-row label{font-size:12px;color:var(--text-dim);min-width:80px}
|
|
3973
|
+
.form-row input,.form-row select{background:var(--bg3);border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;color:var(--text);font-size:13px;flex:1;outline:none}
|
|
3974
|
+
.form-row input:focus,.form-row select:focus{border-color:var(--accent)}
|
|
3975
|
+
|
|
3976
|
+
/* Log entries */
|
|
3977
|
+
.log-entry{padding:6px 12px;font-family:'JetBrains Mono',monospace;font-size:12px;border-radius:4px;margin-bottom:4px}
|
|
3978
|
+
.log-entry.error{background:rgba(239,68,68,.1);color:#fca5a5}
|
|
3979
|
+
.log-entry.warn{background:rgba(245,158,11,.1);color:#fcd34d}
|
|
3980
|
+
.log-entry.info{background:rgba(6,182,212,.05);color:var(--text-dim)}
|
|
3981
|
+
|
|
3982
|
+
/* Scrollbar */
|
|
3983
|
+
::-webkit-scrollbar{width:6px}
|
|
3984
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
3985
|
+
::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:3px}
|
|
3986
|
+
::-webkit-scrollbar-thumb:hover{background:var(--border)}
|
|
3987
|
+
|
|
3988
|
+
/* Progress bar */
|
|
3989
|
+
.progress-bar{height:6px;background:var(--bg4);border-radius:3px;overflow:hidden;margin-top:8px}
|
|
3990
|
+
.progress-bar .fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:3px;transition:width .3s}
|
|
3991
|
+
|
|
3992
|
+
/* Empty state */
|
|
3993
|
+
.empty-state{text-align:center;padding:40px;color:var(--text-dim)}
|
|
3994
|
+
.empty-state .icon{font-size:48px;margin-bottom:12px}
|
|
3995
|
+
.empty-state p{font-size:14px}
|
|
3996
|
+
</style>
|
|
3997
|
+
</head>
|
|
3998
|
+
<body>
|
|
3999
|
+
<aside class="sidebar">
|
|
4000
|
+
<div class="logo-area">
|
|
4001
|
+
<h1>\u26A1 TITAN</h1>
|
|
4002
|
+
<div class="version" id="version">Mission Control</div>
|
|
4003
|
+
</div>
|
|
4004
|
+
<nav class="nav">
|
|
4005
|
+
<div class="nav-section">Core</div>
|
|
4006
|
+
<div class="nav-item active" onclick="showPanel('overview')"><span class="icon">\u{1F4CA}</span>Overview</div>
|
|
4007
|
+
<div class="nav-item" onclick="showPanel('chat')"><span class="icon">\u{1F4AC}</span>WebChat</div>
|
|
4008
|
+
<div class="nav-item" onclick="showPanel('agents')"><span class="icon">\u{1F916}</span>Agents</div>
|
|
4009
|
+
<div class="nav-section">Tools</div>
|
|
4010
|
+
<div class="nav-item" onclick="showPanel('skills')"><span class="icon">\u{1F9E9}</span>Skills</div>
|
|
4011
|
+
<div class="nav-item" onclick="showPanel('processes')"><span class="icon">\u2699\uFE0F</span>Processes</div>
|
|
4012
|
+
<div class="nav-item" onclick="showPanel('plans')"><span class="icon">\u{1F4CB}</span>Plans</div>
|
|
4013
|
+
<div class="nav-section">System</div>
|
|
4014
|
+
<div class="nav-item" onclick="showPanel('sessions')"><span class="icon">\u{1F517}</span>Sessions</div>
|
|
4015
|
+
<div class="nav-item" onclick="showPanel('channels')"><span class="icon">\u{1F4E1}</span>Channels</div>
|
|
4016
|
+
<div class="nav-item" onclick="showPanel('security')"><span class="icon">\u{1F512}</span>Security</div>
|
|
4017
|
+
<div class="nav-item" onclick="showPanel('learning')"><span class="icon">\u{1F9E0}</span>Learning</div>
|
|
4018
|
+
<div class="nav-item" onclick="showPanel('logs')"><span class="icon">\u{1F4DC}</span>Logs</div>
|
|
4019
|
+
</nav>
|
|
4020
|
+
<div class="sidebar-footer">TITAN \u2014 Mission Control<br/>The Intelligent Task Automation Network</div>
|
|
4021
|
+
</aside>
|
|
4022
|
+
|
|
4023
|
+
<main class="main">
|
|
4024
|
+
<header class="topbar">
|
|
4025
|
+
<h2 id="panel-title">\u{1F4CA} Overview</h2>
|
|
4026
|
+
<div class="status">
|
|
4027
|
+
<div class="status-dot"></div>
|
|
4028
|
+
<span class="status-label" id="uptime">Online</span>
|
|
4029
|
+
<span class="status-label" id="agent-count">0 agents</span>
|
|
4030
|
+
</div>
|
|
4031
|
+
</header>
|
|
4032
|
+
|
|
4033
|
+
<div class="content">
|
|
4034
|
+
<!-- Overview Panel -->
|
|
4035
|
+
<div id="panel-overview" class="panel active">
|
|
4036
|
+
<div class="card-grid" id="stats-grid">
|
|
4037
|
+
<div class="stat-card cyan"><div class="stat-label">Active Agents</div><div class="stat-value" id="s-agents">0</div><div class="stat-sub">of 5 max</div></div>
|
|
4038
|
+
<div class="stat-card purple"><div class="stat-label">Sessions</div><div class="stat-value" id="s-sessions">0</div><div class="stat-sub">active</div></div>
|
|
4039
|
+
<div class="stat-card green"><div class="stat-label">Tools Available</div><div class="stat-value" id="s-tools">0</div><div class="stat-sub">17+ built-in</div></div>
|
|
4040
|
+
<div class="stat-card amber"><div class="stat-label">Knowledge</div><div class="stat-value" id="s-knowledge">0</div><div class="stat-sub">learned facts</div></div>
|
|
4041
|
+
</div>
|
|
4042
|
+
<div class="card" style="margin-top:20px">
|
|
4043
|
+
<h3>System Health</h3>
|
|
4044
|
+
<div id="health-info">Loading...</div>
|
|
4045
|
+
</div>
|
|
4046
|
+
<div class="card" style="margin-top:20px">
|
|
4047
|
+
<h3>Recent Activity</h3>
|
|
4048
|
+
<div id="activity-feed">Loading...</div>
|
|
4049
|
+
</div>
|
|
4050
|
+
</div>
|
|
4051
|
+
|
|
4052
|
+
<!-- Chat Panel -->
|
|
4053
|
+
<div id="panel-chat" class="panel">
|
|
4054
|
+
<div class="card" style="height:calc(100vh - 140px);display:flex;flex-direction:column;padding:0">
|
|
4055
|
+
<div class="chat-messages" id="chat-messages">
|
|
4056
|
+
<div class="msg assistant">\u{1F44B} Hello! I'm TITAN. How can I help you today?</div>
|
|
4057
|
+
</div>
|
|
4058
|
+
<div class="chat-input-area">
|
|
4059
|
+
<input type="text" id="chat-input" placeholder="Type a message..." onkeydown="if(event.key==='Enter')sendChat()"/>
|
|
4060
|
+
<button onclick="sendChat()">Send \u26A1</button>
|
|
4061
|
+
</div>
|
|
4062
|
+
</div>
|
|
4063
|
+
</div>
|
|
4064
|
+
|
|
4065
|
+
<!-- Agents Panel -->
|
|
4066
|
+
<div id="panel-agents" class="panel">
|
|
4067
|
+
<div class="card">
|
|
4068
|
+
<h3>Spawn New Agent</h3>
|
|
4069
|
+
<div class="form-row"><label>Name</label><input type="text" id="agent-name" placeholder="e.g. Code Reviewer"/></div>
|
|
4070
|
+
<div class="form-row"><label>Model</label><input type="text" id="agent-model" placeholder="e.g. anthropic/claude-sonnet-4-20250514"/></div>
|
|
4071
|
+
<div class="form-row"><label></label><button class="btn primary" onclick="spawnAgent()">Spawn Agent</button></div>
|
|
4072
|
+
</div>
|
|
4073
|
+
<div class="card" style="margin-top:16px">
|
|
4074
|
+
<h3>Agent Instances (max 5)</h3>
|
|
4075
|
+
<div id="agents-list"><div class="empty-state"><div class="icon">\u{1F916}</div><p>No agents running. Spawn one above.</p></div></div>
|
|
4076
|
+
</div>
|
|
4077
|
+
</div>
|
|
4078
|
+
|
|
4079
|
+
<!-- Skills Panel -->
|
|
4080
|
+
<div id="panel-skills" class="panel">
|
|
4081
|
+
<div class="card"><h3>Installed Skills</h3><div id="skills-list">Loading...</div></div>
|
|
4082
|
+
</div>
|
|
4083
|
+
|
|
4084
|
+
<!-- Processes Panel -->
|
|
4085
|
+
<div id="panel-processes" class="panel">
|
|
4086
|
+
<div class="card"><h3>Background Processes</h3><div id="processes-list"><div class="empty-state"><div class="icon">\u2699\uFE0F</div><p>No background processes.</p></div></div></div>
|
|
4087
|
+
</div>
|
|
4088
|
+
|
|
4089
|
+
<!-- Plans Panel -->
|
|
4090
|
+
<div id="panel-plans" class="panel">
|
|
4091
|
+
<div class="card"><h3>Task Plans</h3><div id="plans-list"><div class="empty-state"><div class="icon">\u{1F4CB}</div><p>No active plans. Create one via chat.</p></div></div></div>
|
|
4092
|
+
</div>
|
|
4093
|
+
|
|
4094
|
+
<!-- Sessions Panel -->
|
|
4095
|
+
<div id="panel-sessions" class="panel">
|
|
4096
|
+
<div class="card"><h3>Active Sessions</h3><div id="sessions-list">Loading...</div></div>
|
|
4097
|
+
</div>
|
|
4098
|
+
|
|
4099
|
+
<!-- Channels Panel -->
|
|
4100
|
+
<div id="panel-channels" class="panel">
|
|
4101
|
+
<div class="card"><h3>Channel Status</h3><div id="channels-list">Loading...</div></div>
|
|
4102
|
+
</div>
|
|
4103
|
+
|
|
4104
|
+
<!-- Security Panel -->
|
|
4105
|
+
<div id="panel-security" class="panel">
|
|
4106
|
+
<div class="card"><h3>Security Audit</h3><div id="security-audit">Loading...</div></div>
|
|
4107
|
+
</div>
|
|
4108
|
+
|
|
4109
|
+
<!-- Learning Panel -->
|
|
4110
|
+
<div id="panel-learning" class="panel">
|
|
4111
|
+
<div class="card">
|
|
4112
|
+
<h3>Learning Engine Status</h3>
|
|
4113
|
+
<div id="learning-stats">Loading...</div>
|
|
4114
|
+
</div>
|
|
4115
|
+
<div class="card" style="margin-top:16px">
|
|
4116
|
+
<h3>Tool Success Rates</h3>
|
|
4117
|
+
<div id="tool-rates">Loading...</div>
|
|
4118
|
+
</div>
|
|
4119
|
+
</div>
|
|
4120
|
+
|
|
4121
|
+
<!-- Logs Panel -->
|
|
4122
|
+
<div id="panel-logs" class="panel">
|
|
4123
|
+
<div class="card"><h3>System Logs</h3><div id="logs-container" style="max-height:calc(100vh - 220px);overflow-y:auto">Loading...</div></div>
|
|
4124
|
+
</div>
|
|
4125
|
+
</div>
|
|
4126
|
+
</main>
|
|
4127
|
+
|
|
4128
|
+
<script>
|
|
4129
|
+
const panelTitles = {
|
|
4130
|
+
overview:'\u{1F4CA} Overview', chat:'\u{1F4AC} WebChat', agents:'\u{1F916} Agents',
|
|
4131
|
+
skills:'\u{1F9E9} Skills', processes:'\u2699\uFE0F Processes', plans:'\u{1F4CB} Plans',
|
|
4132
|
+
sessions:'\u{1F517} Sessions', channels:'\u{1F4E1} Channels', security:'\u{1F512} Security',
|
|
4133
|
+
learning:'\u{1F9E0} Learning', logs:'\u{1F4DC} Logs'
|
|
4134
|
+
};
|
|
4135
|
+
|
|
4136
|
+
function showPanel(name) {
|
|
4137
|
+
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
|
4138
|
+
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
4139
|
+
const panel = document.getElementById('panel-' + name);
|
|
4140
|
+
if (panel) panel.classList.add('active');
|
|
4141
|
+
event.target.closest('.nav-item')?.classList.add('active');
|
|
4142
|
+
document.getElementById('panel-title').textContent = panelTitles[name] || name;
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4145
|
+
// WebSocket
|
|
4146
|
+
let ws;
|
|
4147
|
+
function connectWS() {
|
|
4148
|
+
ws = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host);
|
|
4149
|
+
ws.onmessage = (e) => {
|
|
4150
|
+
try {
|
|
4151
|
+
const data = JSON.parse(e.data);
|
|
4152
|
+
if (data.type === 'response') {
|
|
4153
|
+
const messages = document.getElementById('chat-messages');
|
|
4154
|
+
const msg = document.createElement('div');
|
|
4155
|
+
msg.className = 'msg assistant';
|
|
4156
|
+
msg.textContent = data.content;
|
|
4157
|
+
messages.appendChild(msg);
|
|
4158
|
+
messages.scrollTop = messages.scrollHeight;
|
|
4159
|
+
}
|
|
4160
|
+
} catch(err) {}
|
|
4161
|
+
};
|
|
4162
|
+
ws.onclose = () => setTimeout(connectWS, 3000);
|
|
4163
|
+
}
|
|
4164
|
+
connectWS();
|
|
4165
|
+
|
|
4166
|
+
function sendChat() {
|
|
4167
|
+
const input = document.getElementById('chat-input');
|
|
4168
|
+
const text = input.value.trim();
|
|
4169
|
+
if (!text) return;
|
|
4170
|
+
const messages = document.getElementById('chat-messages');
|
|
4171
|
+
const msg = document.createElement('div');
|
|
4172
|
+
msg.className = 'msg user';
|
|
4173
|
+
msg.textContent = text;
|
|
4174
|
+
messages.appendChild(msg);
|
|
4175
|
+
messages.scrollTop = messages.scrollHeight;
|
|
4176
|
+
input.value = '';
|
|
4177
|
+
if (ws && ws.readyState === 1) {
|
|
4178
|
+
ws.send(JSON.stringify({type:'message',content:text,channel:'webchat',userId:'dashboard'}));
|
|
4179
|
+
} else {
|
|
4180
|
+
fetch('/api/message',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({content:text})})
|
|
4181
|
+
.then(r=>r.json())
|
|
4182
|
+
.then(data=>{const m=document.createElement('div');m.className='msg assistant';m.textContent=data.content||'No response';messages.appendChild(m);messages.scrollTop=messages.scrollHeight});
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
async function spawnAgent() {
|
|
4187
|
+
const name = document.getElementById('agent-name').value.trim();
|
|
4188
|
+
const model = document.getElementById('agent-model').value.trim();
|
|
4189
|
+
if (!name) return alert('Agent name required');
|
|
4190
|
+
const r = await fetch('/api/agents/spawn',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,model:model||undefined})});
|
|
4191
|
+
const data = await r.json();
|
|
4192
|
+
if (data.error) alert(data.error);
|
|
4193
|
+
else { document.getElementById('agent-name').value=''; document.getElementById('agent-model').value=''; fetchData(); }
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
async function stopAgent(id) {
|
|
4197
|
+
await fetch('/api/agents/stop',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({agentId:id})});
|
|
4198
|
+
fetchData();
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
async function fetchData() {
|
|
4202
|
+
try {
|
|
4203
|
+
const [stats, sessions, skills, channels, security, agents] = await Promise.all([
|
|
4204
|
+
fetch('/api/stats').then(r=>r.json()).catch(()=>({})),
|
|
4205
|
+
fetch('/api/sessions').then(r=>r.json()).catch(()=>[]),
|
|
4206
|
+
fetch('/api/skills').then(r=>r.json()).catch(()=>[]),
|
|
4207
|
+
fetch('/api/channels').then(r=>r.json()).catch(()=>{}),
|
|
4208
|
+
fetch('/api/security').then(r=>r.json()).catch(()=>({})),
|
|
4209
|
+
fetch('/api/agents').then(r=>r.json()).catch(()=>({agents:[],capacity:{current:0,max:5}})),
|
|
4210
|
+
]);
|
|
4211
|
+
|
|
4212
|
+
// Stats
|
|
4213
|
+
document.getElementById('s-agents').textContent = agents.capacity?.current || 0;
|
|
4214
|
+
document.getElementById('s-sessions').textContent = Array.isArray(sessions) ? sessions.length : 0;
|
|
4215
|
+
document.getElementById('s-tools').textContent = Array.isArray(skills) ? skills.length : 0;
|
|
4216
|
+
document.getElementById('s-knowledge').textContent = stats.knowledge || 0;
|
|
4217
|
+
document.getElementById('agent-count').textContent = (agents.capacity?.current||0) + ' agents';
|
|
4218
|
+
|
|
4219
|
+
// Health
|
|
4220
|
+
const upSec = stats.uptime || 0;
|
|
4221
|
+
const upStr = upSec > 3600 ? Math.floor(upSec/3600)+'h '+Math.floor((upSec%3600)/60)+'m' : Math.floor(upSec/60)+'m '+Math.floor(upSec%60)+'s';
|
|
4222
|
+
document.getElementById('uptime').textContent = 'Up ' + upStr;
|
|
4223
|
+
document.getElementById('health-info').innerHTML =
|
|
4224
|
+
'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:8px">'+
|
|
4225
|
+
'<div><span style="color:var(--text-dim)">Uptime</span><br><strong>'+upStr+'</strong></div>'+
|
|
4226
|
+
'<div><span style="color:var(--text-dim)">Memory</span><br><strong>'+(stats.memoryMB||'\u2014')+' MB</strong></div>'+
|
|
4227
|
+
'<div><span style="color:var(--text-dim)">Version</span><br><strong>'+(stats.version||'\u2014')+'</strong></div></div>';
|
|
4228
|
+
|
|
4229
|
+
// Agents
|
|
4230
|
+
if (agents.agents && agents.agents.length > 0) {
|
|
4231
|
+
document.getElementById('agents-list').innerHTML =
|
|
4232
|
+
'<table><tr><th>Name</th><th>ID</th><th>Model</th><th>Messages</th><th>Status</th><th>Action</th></tr>' +
|
|
4233
|
+
agents.agents.map(a=>'<tr><td><strong>'+a.name+'</strong></td><td style="font-family:JetBrains Mono;font-size:12px;color:var(--text-dim)">'+a.id+'</td><td>'+a.model+'</td><td>'+a.messageCount+'</td><td><span class="badge '+(a.status==='running'?'active':'idle')+'">'+a.status+'</span></td><td><button class="btn danger" onclick="stopAgent(\\''+a.id+'\\')">Stop</button></td></tr>').join('')+'</table>';
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
// Sessions
|
|
4237
|
+
if (Array.isArray(sessions) && sessions.length > 0) {
|
|
4238
|
+
document.getElementById('sessions-list').innerHTML =
|
|
4239
|
+
'<table><tr><th>ID</th><th>Channel</th><th>Messages</th><th>Last Active</th></tr>' +
|
|
4240
|
+
sessions.map(s=>'<tr><td style="font-family:JetBrains Mono;font-size:12px">'+s.id.slice(0,8)+'</td><td>'+s.channel+'</td><td>'+(s.messageCount||0)+'</td><td>'+(s.lastActive||'\u2014')+'</td></tr>').join('')+'</table>';
|
|
4241
|
+
} else {
|
|
4242
|
+
document.getElementById('sessions-list').innerHTML = '<div class="empty-state"><p>No active sessions</p></div>';
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
// Skills
|
|
4246
|
+
if (Array.isArray(skills) && skills.length > 0) {
|
|
4247
|
+
document.getElementById('skills-list').innerHTML =
|
|
4248
|
+
'<table><tr><th>Skill</th><th>Version</th><th>Source</th><th>Status</th></tr>' +
|
|
4249
|
+
skills.map(s=>'<tr><td><strong>'+s.name+'</strong></td><td>'+s.version+'</td><td><span class="badge info">'+s.source+'</span></td><td><span class="badge active">enabled</span></td></tr>').join('')+'</table>';
|
|
4250
|
+
}
|
|
4251
|
+
|
|
4252
|
+
// Channels
|
|
4253
|
+
if (channels && typeof channels === 'object') {
|
|
4254
|
+
const entries = Object.entries(channels);
|
|
4255
|
+
document.getElementById('channels-list').innerHTML =
|
|
4256
|
+
'<table><tr><th>Channel</th><th>Status</th></tr>' +
|
|
4257
|
+
entries.map(([name,info])=>'<tr><td><strong>'+name+'</strong></td><td><span class="badge '+((info as any).status==='connected'?'active':'idle')+'">'+(info as any).status+'</span></td></tr>').join('')+'</table>';
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
// Security
|
|
4261
|
+
document.getElementById('security-audit').innerHTML = security ? '<pre style="font-family:JetBrains Mono;font-size:12px;color:var(--text-dim);white-space:pre-wrap">'+JSON.stringify(security,null,2)+'</pre>' : 'No audit data';
|
|
4262
|
+
|
|
4263
|
+
} catch(e) { console.error('Fetch error:', e); }
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4266
|
+
fetchData();
|
|
4267
|
+
setInterval(fetchData, 15000);
|
|
4268
|
+
</script>
|
|
4269
|
+
</body>
|
|
4270
|
+
</html>`;
|
|
4271
|
+
}
|
|
4272
|
+
|
|
4273
|
+
// src/gateway/server.ts
|
|
4274
|
+
var COMPONENT30 = "Gateway";
|
|
4275
|
+
var channels = /* @__PURE__ */ new Map();
|
|
4276
|
+
var wsClients = /* @__PURE__ */ new Set();
|
|
4277
|
+
var webChatChannel = null;
|
|
4278
|
+
function broadcast(data) {
|
|
4279
|
+
const json = JSON.stringify(data);
|
|
4280
|
+
for (const client of wsClients) {
|
|
4281
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
4282
|
+
client.send(json);
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
async function handleInboundMessage(msg) {
|
|
4287
|
+
logger_default.info(COMPONENT30, `[${msg.channel}] ${msg.userName || msg.userId}: ${msg.content.slice(0, 100)}`);
|
|
4288
|
+
broadcast({
|
|
4289
|
+
type: "message",
|
|
4290
|
+
direction: "inbound",
|
|
4291
|
+
channel: msg.channel,
|
|
4292
|
+
userId: msg.userId,
|
|
4293
|
+
userName: msg.userName,
|
|
4294
|
+
content: msg.content,
|
|
4295
|
+
timestamp: msg.timestamp.toISOString()
|
|
4296
|
+
});
|
|
4297
|
+
try {
|
|
4298
|
+
const response = await routeMessage(msg.content, msg.channel, msg.userId);
|
|
4299
|
+
const channel = channels.get(msg.channel);
|
|
4300
|
+
if (channel) {
|
|
4301
|
+
await channel.send({
|
|
4302
|
+
channel: msg.channel,
|
|
4303
|
+
userId: msg.userId,
|
|
4304
|
+
groupId: msg.groupId,
|
|
4305
|
+
content: response.content,
|
|
4306
|
+
replyTo: msg.id
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
broadcast({
|
|
4310
|
+
type: "message",
|
|
4311
|
+
direction: "outbound",
|
|
4312
|
+
channel: msg.channel,
|
|
4313
|
+
userId: msg.userId,
|
|
4314
|
+
content: response.content,
|
|
4315
|
+
toolsUsed: response.toolsUsed,
|
|
4316
|
+
tokenUsage: response.tokenUsage,
|
|
4317
|
+
durationMs: response.durationMs,
|
|
4318
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4319
|
+
});
|
|
4320
|
+
} catch (error) {
|
|
4321
|
+
logger_default.error(COMPONENT30, `Error processing message: ${error.message}`);
|
|
4322
|
+
broadcast({ type: "error", message: error.message });
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
async function startGateway(options) {
|
|
4326
|
+
const config = loadConfig();
|
|
4327
|
+
const port = options?.port || config.gateway.port;
|
|
4328
|
+
const host = options?.host || config.gateway.host;
|
|
4329
|
+
logger_default.info(COMPONENT30, `Starting ${TITAN_NAME} Gateway v${TITAN_VERSION}`);
|
|
4330
|
+
initMemory();
|
|
4331
|
+
await initBuiltinSkills();
|
|
4332
|
+
initAgents();
|
|
4333
|
+
const app = express();
|
|
4334
|
+
app.use(express.json());
|
|
4335
|
+
app.get("/", (_req, res) => {
|
|
4336
|
+
res.setHeader("Content-Type", "text/html");
|
|
4337
|
+
res.send(getMissionControlHTML());
|
|
4338
|
+
});
|
|
4339
|
+
app.get("/api/stats", (_req, res) => {
|
|
4340
|
+
const stats = getUsageStats();
|
|
4341
|
+
res.json(stats);
|
|
4342
|
+
});
|
|
4343
|
+
app.get("/api/sessions", (_req, res) => {
|
|
4344
|
+
const sessions = listSessions();
|
|
4345
|
+
res.json(sessions);
|
|
4346
|
+
});
|
|
4347
|
+
app.get("/api/skills", (_req, res) => {
|
|
4348
|
+
const skills = getSkills();
|
|
4349
|
+
res.json(skills);
|
|
4350
|
+
});
|
|
4351
|
+
app.get("/api/tools", (_req, res) => {
|
|
4352
|
+
const tools = getRegisteredTools().map((t) => ({
|
|
4353
|
+
name: t.name,
|
|
4354
|
+
description: t.description
|
|
4355
|
+
}));
|
|
4356
|
+
res.json(tools);
|
|
4357
|
+
});
|
|
4358
|
+
app.get("/api/channels", (_req, res) => {
|
|
4359
|
+
const statuses = Array.from(channels.values()).map((ch) => ch.getStatus());
|
|
4360
|
+
res.json(statuses);
|
|
4361
|
+
});
|
|
4362
|
+
app.get("/api/security", (_req, res) => {
|
|
4363
|
+
const audit = auditSecurity();
|
|
4364
|
+
res.json(audit);
|
|
4365
|
+
});
|
|
4366
|
+
app.get("/api/providers", async (_req, res) => {
|
|
4367
|
+
const health = await healthCheckAll();
|
|
4368
|
+
res.json(health);
|
|
4369
|
+
});
|
|
4370
|
+
app.get("/api/health", (_req, res) => {
|
|
4371
|
+
res.json({ status: "ok", version: TITAN_VERSION, uptime: process.uptime() });
|
|
4372
|
+
});
|
|
4373
|
+
app.get("/api/agents", (_req, res) => {
|
|
4374
|
+
res.json({ agents: listAgents(), capacity: getAgentCapacity() });
|
|
4375
|
+
});
|
|
4376
|
+
app.post("/api/agents/spawn", (req, res) => {
|
|
4377
|
+
const { name, model, systemPrompt } = req.body;
|
|
4378
|
+
if (!name) {
|
|
4379
|
+
res.status(400).json({ error: "name is required" });
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
const result = spawnAgent({ name, model, systemPrompt });
|
|
4383
|
+
res.json(result);
|
|
4384
|
+
});
|
|
4385
|
+
app.post("/api/agents/stop", (req, res) => {
|
|
4386
|
+
const { agentId } = req.body;
|
|
4387
|
+
if (!agentId) {
|
|
4388
|
+
res.status(400).json({ error: "agentId is required" });
|
|
4389
|
+
return;
|
|
4390
|
+
}
|
|
4391
|
+
const result = stopAgent(agentId);
|
|
4392
|
+
res.json(result);
|
|
4393
|
+
});
|
|
4394
|
+
app.post("/api/message", async (req, res) => {
|
|
4395
|
+
const { content, channel = "api", userId = "api-user" } = req.body;
|
|
4396
|
+
if (!content) {
|
|
4397
|
+
res.status(400).json({ error: "content is required" });
|
|
4398
|
+
return;
|
|
4399
|
+
}
|
|
4400
|
+
try {
|
|
4401
|
+
const response = await routeMessage(content, channel, userId);
|
|
4402
|
+
res.json(response);
|
|
4403
|
+
} catch (error) {
|
|
4404
|
+
res.status(500).json({ error: error.message });
|
|
4405
|
+
}
|
|
4406
|
+
});
|
|
4407
|
+
const server = createServer(app);
|
|
4408
|
+
const wss = new WebSocketServer({ server });
|
|
4409
|
+
wss.on("connection", (ws) => {
|
|
4410
|
+
wsClients.add(ws);
|
|
4411
|
+
logger_default.info(COMPONENT30, `WebSocket client connected (${wsClients.size} total)`);
|
|
4412
|
+
ws.on("message", async (rawData) => {
|
|
4413
|
+
try {
|
|
4414
|
+
const data = JSON.parse(rawData.toString());
|
|
4415
|
+
if (data.type === "chat" && data.content) {
|
|
4416
|
+
if (webChatChannel) {
|
|
4417
|
+
webChatChannel.handleWebSocketMessage(data.userId || "webchat-user", data.content);
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
} catch (error) {
|
|
4421
|
+
logger_default.error(COMPONENT30, `WebSocket error: ${error.message}`);
|
|
4422
|
+
}
|
|
4423
|
+
});
|
|
4424
|
+
ws.on("close", () => {
|
|
4425
|
+
wsClients.delete(ws);
|
|
4426
|
+
logger_default.debug(COMPONENT30, `WebSocket client disconnected (${wsClients.size} total)`);
|
|
4427
|
+
});
|
|
4428
|
+
});
|
|
4429
|
+
webChatChannel = new WebChatChannel();
|
|
4430
|
+
channels.set("webchat", webChatChannel);
|
|
4431
|
+
await webChatChannel.connect();
|
|
4432
|
+
webChatChannel.on("message", handleInboundMessage);
|
|
4433
|
+
const channelAdapters = [
|
|
4434
|
+
["discord", new DiscordChannel()],
|
|
4435
|
+
["telegram", new TelegramChannel()],
|
|
4436
|
+
["slack", new SlackChannel()],
|
|
4437
|
+
["googlechat", new GoogleChatChannel()]
|
|
4438
|
+
];
|
|
4439
|
+
for (const [name, adapter] of channelAdapters) {
|
|
4440
|
+
channels.set(name, adapter);
|
|
4441
|
+
adapter.on("message", handleInboundMessage);
|
|
4442
|
+
try {
|
|
4443
|
+
await adapter.connect();
|
|
4444
|
+
} catch (error) {
|
|
4445
|
+
logger_default.debug(COMPONENT30, `Channel ${name} not available: ${error.message}`);
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
server.listen(port, host, () => {
|
|
4449
|
+
logger_default.info(COMPONENT30, `Gateway listening on http://${host}:${port}`);
|
|
4450
|
+
logger_default.info(COMPONENT30, `Dashboard: http://${host}:${port}`);
|
|
4451
|
+
logger_default.info(COMPONENT30, `WebSocket: ws://${host}:${port}`);
|
|
4452
|
+
logger_default.info(COMPONENT30, `API: http://${host}:${port}/api/health`);
|
|
4453
|
+
logger_default.info(COMPONENT30, `
|
|
4454
|
+
Channels: ${Array.from(channels.values()).map((c) => `${c.displayName} (${c.getStatus().connected ? "\u2705" : "\u274C"})`).join(", ")}`);
|
|
4455
|
+
logger_default.info(COMPONENT30, `Skills: ${getSkills().length} loaded`);
|
|
4456
|
+
logger_default.info(COMPONENT30, `Tools: ${getRegisteredTools().length} registered`);
|
|
4457
|
+
});
|
|
4458
|
+
}
|
|
4459
|
+
export {
|
|
4460
|
+
startGateway
|
|
4461
|
+
};
|
|
4462
|
+
//# sourceMappingURL=server.js.map
|