@soleri/forge 5.10.0 → 5.12.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/dist/facades/forge.facade.js +3 -3
- package/dist/facades/forge.facade.js.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/scaffolder.js +137 -20
- package/dist/scaffolder.js.map +1 -1
- package/dist/templates/agents-md.d.ts +5 -0
- package/dist/templates/agents-md.js +33 -0
- package/dist/templates/agents-md.js.map +1 -0
- package/dist/templates/entry-point.js +15 -2
- package/dist/templates/entry-point.js.map +1 -1
- package/dist/templates/package-json.js +7 -0
- package/dist/templates/package-json.js.map +1 -1
- package/dist/templates/readme.js +80 -27
- package/dist/templates/readme.js.map +1 -1
- package/dist/templates/setup-script.d.ts +1 -1
- package/dist/templates/setup-script.js +135 -53
- package/dist/templates/setup-script.js.map +1 -1
- package/dist/templates/skills.d.ts +0 -7
- package/dist/templates/skills.js +1 -24
- package/dist/templates/skills.js.map +1 -1
- package/dist/templates/telegram-agent.d.ts +6 -0
- package/dist/templates/telegram-agent.js +212 -0
- package/dist/templates/telegram-agent.js.map +1 -0
- package/dist/templates/telegram-bot.d.ts +6 -0
- package/dist/templates/telegram-bot.js +450 -0
- package/dist/templates/telegram-bot.js.map +1 -0
- package/dist/templates/telegram-config.d.ts +6 -0
- package/dist/templates/telegram-config.js +81 -0
- package/dist/templates/telegram-config.js.map +1 -0
- package/dist/templates/telegram-supervisor.d.ts +6 -0
- package/dist/templates/telegram-supervisor.js +148 -0
- package/dist/templates/telegram-supervisor.js.map +1 -0
- package/dist/types.d.ts +13 -5
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/scaffolder.test.ts +62 -0
- package/src/facades/forge.facade.ts +3 -3
- package/src/lib.ts +2 -1
- package/src/scaffolder.ts +170 -28
- package/src/templates/agents-md.ts +35 -0
- package/src/templates/entry-point.ts +15 -2
- package/src/templates/package-json.ts +7 -0
- package/src/templates/readme.ts +89 -27
- package/src/templates/setup-script.ts +141 -54
- package/src/templates/skills.ts +0 -23
- package/src/templates/telegram-agent.ts +214 -0
- package/src/templates/telegram-bot.ts +452 -0
- package/src/templates/telegram-config.ts +83 -0
- package/src/templates/telegram-supervisor.ts +150 -0
- package/src/types.ts +9 -2
|
@@ -2,51 +2,24 @@ import type { AgentConfig } from '../types.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Generate a scripts/setup.sh for the scaffolded agent.
|
|
5
|
-
* Handles: Node.js check, build,
|
|
5
|
+
* Handles: Node.js check, build, and host-specific MCP registration.
|
|
6
6
|
*/
|
|
7
7
|
export function generateSetupScript(config: AgentConfig): string {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# Check Node.js
|
|
18
|
-
if ! command -v node &>/dev/null; then
|
|
19
|
-
echo "Error: Node.js is not installed. Install Node.js 18+ first."
|
|
20
|
-
exit 1
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
|
|
24
|
-
if [ "$NODE_VERSION" -lt 18 ]; then
|
|
25
|
-
echo "Error: Node.js 18+ required (found v$(node -v))."
|
|
26
|
-
exit 1
|
|
27
|
-
fi
|
|
28
|
-
echo "[ok] Node.js $(node -v)"
|
|
29
|
-
|
|
30
|
-
# Check if built
|
|
31
|
-
if [ ! -f "$AGENT_DIR/dist/index.js" ]; then
|
|
32
|
-
echo ""
|
|
33
|
-
echo "Building ${config.name}..."
|
|
34
|
-
cd "$AGENT_DIR"
|
|
35
|
-
npm install
|
|
36
|
-
npm run build
|
|
37
|
-
echo "[ok] Built successfully"
|
|
38
|
-
else
|
|
39
|
-
echo "[ok] Already built"
|
|
40
|
-
fi
|
|
41
|
-
|
|
8
|
+
const setupTarget = config.setupTarget ?? 'claude';
|
|
9
|
+
const claudeSetup = setupTarget === 'claude' || setupTarget === 'both';
|
|
10
|
+
const codexSetup = setupTarget === 'codex' || setupTarget === 'both';
|
|
11
|
+
const hostLabel =
|
|
12
|
+
claudeSetup && codexSetup ? 'Claude Code + Codex' : claudeSetup ? 'Claude Code' : 'Codex';
|
|
13
|
+
|
|
14
|
+
const claudeSection = claudeSetup
|
|
15
|
+
? `
|
|
42
16
|
# Check Claude Code
|
|
43
17
|
if ! command -v claude &>/dev/null; then
|
|
44
18
|
echo ""
|
|
45
19
|
echo "Warning: 'claude' command not found."
|
|
46
20
|
echo "Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code"
|
|
47
21
|
echo ""
|
|
48
|
-
echo "After installing,
|
|
49
|
-
echo "(see README.md for the JSON config)"
|
|
22
|
+
echo "After installing, re-run this setup script."
|
|
50
23
|
exit 1
|
|
51
24
|
fi
|
|
52
25
|
echo "[ok] Claude Code found"
|
|
@@ -54,14 +27,13 @@ echo "[ok] Claude Code found"
|
|
|
54
27
|
# Register MCP server with Claude Code
|
|
55
28
|
echo ""
|
|
56
29
|
echo "Registering ${config.name} with Claude Code..."
|
|
57
|
-
|
|
58
30
|
claude mcp add --scope user "$AGENT_NAME" -- node "$AGENT_DIR/dist/index.js"
|
|
59
|
-
echo "[ok] Registered ${config.name} as MCP server"
|
|
31
|
+
echo "[ok] Registered ${config.name} as MCP server (Claude Code)"
|
|
60
32
|
|
|
61
33
|
# Configure PreCompact hook for session capture
|
|
62
34
|
SETTINGS_FILE="$HOME/.claude/settings.json"
|
|
63
35
|
echo ""
|
|
64
|
-
echo "Configuring session capture hook..."
|
|
36
|
+
echo "Configuring Claude session capture hook..."
|
|
65
37
|
|
|
66
38
|
if [ ! -d "$HOME/.claude" ]; then
|
|
67
39
|
mkdir -p "$HOME/.claude"
|
|
@@ -85,7 +57,6 @@ else
|
|
|
85
57
|
if grep -q "PreCompact" "$SETTINGS_FILE" 2>/dev/null; then
|
|
86
58
|
echo "[ok] PreCompact hook already configured — skipping"
|
|
87
59
|
else
|
|
88
|
-
# Use node to safely merge hook into existing settings
|
|
89
60
|
node -e "
|
|
90
61
|
const fs = require('fs');
|
|
91
62
|
const settings = JSON.parse(fs.readFileSync('$SETTINGS_FILE', 'utf-8'));
|
|
@@ -107,7 +78,7 @@ COMMANDS_DIR="$HOME/.claude/commands"
|
|
|
107
78
|
|
|
108
79
|
if [ -d "$SKILLS_DIR" ]; then
|
|
109
80
|
echo ""
|
|
110
|
-
echo "Installing skills..."
|
|
81
|
+
echo "Installing skills for Claude Code..."
|
|
111
82
|
mkdir -p "$COMMANDS_DIR"
|
|
112
83
|
skill_installed=0
|
|
113
84
|
skill_skipped=0
|
|
@@ -124,12 +95,15 @@ if [ -d "$SKILLS_DIR" ]; then
|
|
|
124
95
|
skill_installed=$((skill_installed + 1))
|
|
125
96
|
fi
|
|
126
97
|
done
|
|
127
|
-
echo "[ok]
|
|
98
|
+
echo "[ok] Claude skills: $skill_installed installed, $skill_skipped already present"
|
|
128
99
|
fi
|
|
100
|
+
`
|
|
101
|
+
: '';
|
|
129
102
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
103
|
+
const hookPackSection =
|
|
104
|
+
claudeSetup && config.hookPacks?.length
|
|
105
|
+
? `
|
|
106
|
+
# Install hook packs to global ~/.claude/
|
|
133
107
|
AGENT_CLAUDE_DIR="$AGENT_DIR/.claude"
|
|
134
108
|
GLOBAL_CLAUDE_DIR="$HOME/.claude"
|
|
135
109
|
|
|
@@ -151,16 +125,129 @@ if [ -d "$AGENT_CLAUDE_DIR" ]; then
|
|
|
151
125
|
done
|
|
152
126
|
echo "[ok] Hooks: $installed installed, $skipped already present"
|
|
153
127
|
fi
|
|
154
|
-
|
|
155
128
|
`
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
129
|
+
: '';
|
|
130
|
+
|
|
131
|
+
const codexSection = codexSetup
|
|
132
|
+
? `
|
|
133
|
+
# Register MCP server with Codex
|
|
159
134
|
echo ""
|
|
160
|
-
echo "
|
|
161
|
-
|
|
162
|
-
|
|
135
|
+
echo "Registering ${config.name} with Codex..."
|
|
136
|
+
mkdir -p "$HOME/.codex"
|
|
137
|
+
CODEX_CONFIG="$HOME/.codex/config.toml"
|
|
138
|
+
AGENT_DIST="$AGENT_DIR/dist/index.js"
|
|
139
|
+
|
|
140
|
+
CODEX_CONFIG="$CODEX_CONFIG" AGENT_NAME="$AGENT_NAME" AGENT_DIST="$AGENT_DIST" node <<'NODE'
|
|
141
|
+
const fs = require('node:fs');
|
|
142
|
+
const path = process.env.CODEX_CONFIG;
|
|
143
|
+
const agentName = process.env.AGENT_NAME;
|
|
144
|
+
const distPath = process.env.AGENT_DIST;
|
|
145
|
+
|
|
146
|
+
let content = '';
|
|
147
|
+
if (fs.existsSync(path)) {
|
|
148
|
+
content = fs.readFileSync(path, 'utf-8');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const header = '[mcp_servers.' + agentName + ']';
|
|
152
|
+
const block = header + '\\ncommand = "node"\\nargs = ["' + distPath + '"]\\n';
|
|
153
|
+
const start = content.indexOf(header);
|
|
154
|
+
|
|
155
|
+
if (start === -1) {
|
|
156
|
+
const trimmed = content.trimEnd();
|
|
157
|
+
content = trimmed.length === 0 ? block + '\\n' : trimmed + '\\n\\n' + block + '\\n';
|
|
158
|
+
} else {
|
|
159
|
+
const afterHeader = start + header.length;
|
|
160
|
+
const tail = content.slice(afterHeader);
|
|
161
|
+
const nextSectionOffset = tail.search(/\\n\\[[^\\]]+\\]/);
|
|
162
|
+
const end = nextSectionOffset === -1 ? content.length : afterHeader + nextSectionOffset;
|
|
163
|
+
content = content.slice(0, start).trimEnd() + '\\n\\n' + block + '\\n' + content.slice(end).trimStart();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
content = content.replace(/\\n{3,}/g, '\\n\\n');
|
|
167
|
+
fs.writeFileSync(path, content, 'utf-8');
|
|
168
|
+
NODE
|
|
169
|
+
echo "[ok] Registered ${config.name} as MCP server (Codex)"
|
|
170
|
+
|
|
171
|
+
# Install skills to ~/.codex/skills/
|
|
172
|
+
SKILLS_DIR="$AGENT_DIR/skills"
|
|
173
|
+
CODEX_SKILLS_DIR="$HOME/.codex/skills"
|
|
174
|
+
|
|
175
|
+
if [ -d "$SKILLS_DIR" ]; then
|
|
176
|
+
echo ""
|
|
177
|
+
echo "Installing skills for Codex..."
|
|
178
|
+
mkdir -p "$CODEX_SKILLS_DIR"
|
|
179
|
+
skill_installed=0
|
|
180
|
+
skill_skipped=0
|
|
181
|
+
for skill_dir in "$SKILLS_DIR"/*/; do
|
|
182
|
+
[ -d "$skill_dir" ] || continue
|
|
183
|
+
skill_file="$skill_dir/SKILL.md"
|
|
184
|
+
[ -f "$skill_file" ] || continue
|
|
185
|
+
skill_name="$(basename "$skill_dir")"
|
|
186
|
+
dest_dir="$CODEX_SKILLS_DIR/$AGENT_NAME-$skill_name"
|
|
187
|
+
dest_file="$dest_dir/SKILL.md"
|
|
188
|
+
if [ -f "$dest_file" ]; then
|
|
189
|
+
skill_skipped=$((skill_skipped + 1))
|
|
190
|
+
else
|
|
191
|
+
mkdir -p "$dest_dir"
|
|
192
|
+
cp "$skill_file" "$dest_file"
|
|
193
|
+
skill_installed=$((skill_installed + 1))
|
|
194
|
+
fi
|
|
195
|
+
done
|
|
196
|
+
echo "[ok] Codex skills: $skill_installed installed, $skill_skipped already present"
|
|
197
|
+
fi
|
|
198
|
+
`
|
|
199
|
+
: '';
|
|
200
|
+
|
|
201
|
+
const nextSteps = [
|
|
202
|
+
'echo ""',
|
|
203
|
+
'echo "=== Setup Complete ==="',
|
|
204
|
+
'echo ""',
|
|
205
|
+
'echo "Next:"',
|
|
206
|
+
...(claudeSetup
|
|
207
|
+
? ['echo " - Start a new Claude Code session (or restart if one is open)"']
|
|
208
|
+
: []),
|
|
209
|
+
...(codexSetup ? ['echo " - Start a new Codex session (or restart if one is open)"'] : []),
|
|
210
|
+
`echo " - Say: \\"Hello, ${config.name}!\\""`,
|
|
211
|
+
'echo ""',
|
|
212
|
+
`echo "${config.name} is ready."`,
|
|
213
|
+
].join('\n');
|
|
214
|
+
|
|
215
|
+
return `#!/usr/bin/env bash
|
|
216
|
+
set -euo pipefail
|
|
217
|
+
|
|
218
|
+
AGENT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
219
|
+
AGENT_NAME="${config.id}"
|
|
220
|
+
|
|
221
|
+
echo "=== ${config.name} Setup (${hostLabel}) ==="
|
|
163
222
|
echo ""
|
|
164
|
-
|
|
223
|
+
|
|
224
|
+
# Check Node.js
|
|
225
|
+
if ! command -v node &>/dev/null; then
|
|
226
|
+
echo "Error: Node.js is not installed. Install Node.js 18+ first."
|
|
227
|
+
exit 1
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
|
|
231
|
+
if [ "$NODE_VERSION" -lt 18 ]; then
|
|
232
|
+
echo "Error: Node.js 18+ required (found v$(node -v))."
|
|
233
|
+
exit 1
|
|
234
|
+
fi
|
|
235
|
+
echo "[ok] Node.js $(node -v)"
|
|
236
|
+
|
|
237
|
+
# Check if built
|
|
238
|
+
if [ ! -f "$AGENT_DIR/dist/index.js" ]; then
|
|
239
|
+
echo ""
|
|
240
|
+
echo "Building ${config.name}..."
|
|
241
|
+
cd "$AGENT_DIR"
|
|
242
|
+
npm install
|
|
243
|
+
npm run build
|
|
244
|
+
echo "[ok] Built successfully"
|
|
245
|
+
else
|
|
246
|
+
echo "[ok] Already built"
|
|
247
|
+
fi
|
|
248
|
+
${claudeSection}
|
|
249
|
+
${hookPackSection}
|
|
250
|
+
${codexSection}
|
|
251
|
+
${nextSteps}
|
|
165
252
|
`;
|
|
166
253
|
}
|
package/src/templates/skills.ts
CHANGED
|
@@ -68,26 +68,3 @@ export function generateSkills(config: AgentConfig): Array<[string, string]> {
|
|
|
68
68
|
|
|
69
69
|
return files;
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* List all bundled skill names with their descriptions (from YAML frontmatter).
|
|
74
|
-
*/
|
|
75
|
-
export function listSkillDescriptions(): Array<{ name: string; description: string }> {
|
|
76
|
-
let skillFiles: string[];
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
skillFiles = readdirSync(SKILLS_DIR).filter((f) => f.endsWith('.md'));
|
|
80
|
-
} catch {
|
|
81
|
-
return [];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return skillFiles.map((file) => {
|
|
85
|
-
const content = readFileSync(join(SKILLS_DIR, file), 'utf-8');
|
|
86
|
-
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
87
|
-
const descMatch = content.match(/^description:\s*"?(.+?)"?\s*$/m);
|
|
88
|
-
return {
|
|
89
|
-
name: nameMatch?.[1]?.trim() ?? file.replace('.md', ''),
|
|
90
|
-
description: descMatch?.[1]?.trim() ?? '',
|
|
91
|
-
};
|
|
92
|
-
});
|
|
93
|
-
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template: Telegram agent loop — wires @soleri/core agent loop to MCP tools.
|
|
3
|
+
* Generated by @soleri/forge for agents with Telegram transport.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AgentConfig } from '../types.js';
|
|
7
|
+
|
|
8
|
+
export function generateTelegramAgent(config: AgentConfig): string {
|
|
9
|
+
return `/**
|
|
10
|
+
* ${config.name} — Telegram Agent Loop.
|
|
11
|
+
* Generated by @soleri/forge.
|
|
12
|
+
*
|
|
13
|
+
* Wires the @soleri/core agent loop to the agent's MCP tools.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
runAgentLoop,
|
|
18
|
+
McpToolBridge,
|
|
19
|
+
createOutputCompressor,
|
|
20
|
+
} from '@soleri/core';
|
|
21
|
+
import type {
|
|
22
|
+
ChatMessage,
|
|
23
|
+
AgentLoopResult,
|
|
24
|
+
AgentCallbacks,
|
|
25
|
+
McpToolRegistration,
|
|
26
|
+
} from '@soleri/core';
|
|
27
|
+
import type { TelegramConfig } from './telegram-config.js';
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
30
|
+
import { dirname } from 'node:path';
|
|
31
|
+
|
|
32
|
+
// ─── Core Tools ──────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const CORE_TOOLS: McpToolRegistration[] = [
|
|
35
|
+
{
|
|
36
|
+
name: 'bash',
|
|
37
|
+
description: 'Execute a shell command. Returns stdout/stderr.',
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
command: { type: 'string', description: 'Shell command to execute.' },
|
|
42
|
+
timeout: { type: 'number', description: 'Timeout in ms. Default: 120000.' },
|
|
43
|
+
},
|
|
44
|
+
required: ['command'],
|
|
45
|
+
},
|
|
46
|
+
handler: async (input) => {
|
|
47
|
+
try {
|
|
48
|
+
const timeout = (input.timeout as number) ?? 120_000;
|
|
49
|
+
const output = execFileSync('/bin/sh', ['-c', input.command as string], {
|
|
50
|
+
timeout,
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
maxBuffer: 1024 * 1024,
|
|
53
|
+
});
|
|
54
|
+
return output;
|
|
55
|
+
} catch (err: unknown) {
|
|
56
|
+
const error = err as { stdout?: string; stderr?: string; message?: string };
|
|
57
|
+
return \`Error: \${error.stderr ?? error.stdout ?? error.message ?? 'Unknown error'}\`;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'read_file',
|
|
63
|
+
description: 'Read a file from the filesystem.',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
path: { type: 'string', description: 'File path to read.' },
|
|
68
|
+
},
|
|
69
|
+
required: ['path'],
|
|
70
|
+
},
|
|
71
|
+
handler: async (input) => {
|
|
72
|
+
return readFileSync(input.path as string, 'utf-8');
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'write_file',
|
|
77
|
+
description: 'Write content to a file.',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
path: { type: 'string', description: 'File path to write.' },
|
|
82
|
+
content: { type: 'string', description: 'Content to write.' },
|
|
83
|
+
},
|
|
84
|
+
required: ['path', 'content'],
|
|
85
|
+
},
|
|
86
|
+
handler: async (input) => {
|
|
87
|
+
mkdirSync(dirname(input.path as string), { recursive: true });
|
|
88
|
+
writeFileSync(input.path as string, input.content as string, 'utf-8');
|
|
89
|
+
return \`Wrote \${(input.content as string).length} bytes to \${input.path}\`;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'glob',
|
|
94
|
+
description: 'Find files matching a glob pattern.',
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
pattern: { type: 'string', description: 'Glob pattern.' },
|
|
99
|
+
cwd: { type: 'string', description: 'Working directory.' },
|
|
100
|
+
},
|
|
101
|
+
required: ['pattern'],
|
|
102
|
+
},
|
|
103
|
+
handler: async (input) => {
|
|
104
|
+
const cwd = (input.cwd as string) ?? process.cwd();
|
|
105
|
+
const output = execFileSync('find', ['.', '-path', input.pattern as string, '-type', 'f'], {
|
|
106
|
+
cwd,
|
|
107
|
+
encoding: 'utf-8',
|
|
108
|
+
timeout: 10_000,
|
|
109
|
+
});
|
|
110
|
+
const lines = output.trim().split('\\n').filter(Boolean).slice(0, 100);
|
|
111
|
+
return lines.length > 0 ? lines.join('\\n') : 'No files found.';
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'grep',
|
|
116
|
+
description: 'Search file contents with regex.',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
pattern: { type: 'string', description: 'Regex pattern.' },
|
|
121
|
+
path: { type: 'string', description: 'File or directory to search.' },
|
|
122
|
+
type: { type: 'string', description: 'File type filter (e.g., ts, js).' },
|
|
123
|
+
},
|
|
124
|
+
required: ['pattern'],
|
|
125
|
+
},
|
|
126
|
+
handler: async (input) => {
|
|
127
|
+
const searchPath = (input.path as string) ?? '.';
|
|
128
|
+
const args = ['--no-heading', '-n', input.pattern as string, searchPath];
|
|
129
|
+
if (input.type) args.splice(2, 0, '--type', input.type as string);
|
|
130
|
+
try {
|
|
131
|
+
const output = execFileSync('rg', args, {
|
|
132
|
+
encoding: 'utf-8',
|
|
133
|
+
timeout: 10_000,
|
|
134
|
+
});
|
|
135
|
+
const lines = output.trim().split('\\n').filter(Boolean).slice(0, 50);
|
|
136
|
+
return lines.length > 0 ? lines.join('\\n') : 'No matches found.';
|
|
137
|
+
} catch {
|
|
138
|
+
return 'No matches found.';
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// ─── Agent Factory ───────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
export interface TelegramAgentRunner {
|
|
147
|
+
run(messages: ChatMessage[], callbacks?: AgentCallbacks): Promise<AgentLoopResult>;
|
|
148
|
+
registerTool(tool: McpToolRegistration): void;
|
|
149
|
+
getToolCount(): number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function createTelegramAgent(config: TelegramConfig): TelegramAgentRunner {
|
|
153
|
+
const bridge = new McpToolBridge({
|
|
154
|
+
compressor: createOutputCompressor({ maxLength: 4000 }),
|
|
155
|
+
maxOutput: 10_000,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Register core tools
|
|
159
|
+
bridge.registerAll(CORE_TOOLS);
|
|
160
|
+
|
|
161
|
+
const systemPrompt = buildSystemPrompt(config);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
run: async (messages: ChatMessage[], callbacks?: AgentCallbacks) => {
|
|
165
|
+
return runAgentLoop(messages, {
|
|
166
|
+
apiKey: config.apiKey,
|
|
167
|
+
model: config.model,
|
|
168
|
+
systemPrompt,
|
|
169
|
+
tools: bridge.getTools(),
|
|
170
|
+
executor: bridge.createExecutor(),
|
|
171
|
+
maxIterations: config.maxIterations,
|
|
172
|
+
maxTokens: config.maxTokens,
|
|
173
|
+
}, callbacks);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
registerTool: (tool: McpToolRegistration) => {
|
|
177
|
+
bridge.register(tool);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
getToolCount: () => bridge.size,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── System Prompt ───────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
function buildSystemPrompt(config: TelegramConfig): string {
|
|
187
|
+
const parts: string[] = [
|
|
188
|
+
'# ${config.name}',
|
|
189
|
+
'',
|
|
190
|
+
'${config.role}',
|
|
191
|
+
'',
|
|
192
|
+
'## Environment',
|
|
193
|
+
\`- Platform: \${process.platform}\`,
|
|
194
|
+
\`- Working directory: \${config.workingDirectory ?? process.cwd()}\`,
|
|
195
|
+
\`- Date: \${new Date().toISOString().split('T')[0]}\`,
|
|
196
|
+
'',
|
|
197
|
+
'## Telegram Formatting',
|
|
198
|
+
'- Keep responses concise (under 2000 chars when possible)',
|
|
199
|
+
'- Use Markdown: **bold**, *italic*, \\\`code\\\`, \\\`\\\`\\\`code blocks\\\`\\\`\\\`',
|
|
200
|
+
'- Use bullet points for lists',
|
|
201
|
+
'- No raw JSON in responses — format for human reading',
|
|
202
|
+
'',
|
|
203
|
+
'## Available Tools',
|
|
204
|
+
'- bash: Execute shell commands',
|
|
205
|
+
'- read_file: Read file contents',
|
|
206
|
+
'- write_file: Write to files',
|
|
207
|
+
'- glob: Find files by pattern',
|
|
208
|
+
'- grep: Search file contents',
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
return parts.join('\\n');
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
}
|