freddie 0.0.47 → 0.0.49
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/AGENTS.md +12 -0
- package/CHANGELOG.md +26 -2
- package/package.json +3 -2
- package/plugins/ansi_strip/handler.js +7 -0
- package/plugins/ansi_strip/plugin.js +2 -0
- package/plugins/approval/handler.js +13 -0
- package/plugins/approval/plugin.js +2 -0
- package/plugins/bash/handler.js +33 -0
- package/plugins/bash/plugin.js +2 -0
- package/plugins/binary_extensions/handler.js +20 -0
- package/plugins/binary_extensions/plugin.js +2 -0
- package/plugins/browser/handler.js +46 -0
- package/plugins/browser/plugin.js +2 -0
- package/plugins/budget_config/handler.js +12 -0
- package/plugins/budget_config/plugin.js +2 -0
- package/plugins/checkpoint/handler.js +27 -0
- package/plugins/checkpoint/plugin.js +2 -0
- package/plugins/clarify/handler.js +13 -0
- package/plugins/clarify/plugin.js +2 -0
- package/plugins/code_execution/handler.js +25 -0
- package/plugins/code_execution/plugin.js +2 -0
- package/plugins/core-agent-machine/plugin.js +8 -0
- package/plugins/core-cli/plugin.js +83 -0
- package/plugins/core-commands/plugin.js +7 -0
- package/plugins/core-compressor/plugin.js +15 -0
- package/plugins/core-context-engine/plugin.js +7 -0
- package/plugins/core-cron/plugin.js +7 -0
- package/plugins/core-skills/plugin.js +7 -0
- package/plugins/credential_files/handler.js +14 -0
- package/plugins/credential_files/plugin.js +2 -0
- package/plugins/cronjob/handler.js +14 -0
- package/plugins/cronjob/plugin.js +2 -0
- package/plugins/debug_helpers/handler.js +8 -0
- package/plugins/debug_helpers/plugin.js +2 -0
- package/plugins/delegate/handler.js +27 -0
- package/plugins/delegate/plugin.js +2 -0
- package/plugins/discord_tool/handler.js +12 -0
- package/plugins/discord_tool/plugin.js +2 -0
- package/plugins/edit/handler.js +29 -0
- package/plugins/edit/plugin.js +2 -0
- package/plugins/env_passthrough/handler.js +14 -0
- package/plugins/env_passthrough/plugin.js +2 -0
- package/plugins/feishu_doc/handler.js +14 -0
- package/plugins/feishu_doc/plugin.js +2 -0
- package/plugins/feishu_drive/handler.js +13 -0
- package/plugins/feishu_drive/plugin.js +2 -0
- package/plugins/file_operations/handler.js +15 -0
- package/plugins/file_operations/plugin.js +2 -0
- package/plugins/file_state/handler.js +14 -0
- package/plugins/file_state/plugin.js +2 -0
- package/plugins/file_tools/handler.js +21 -0
- package/plugins/file_tools/plugin.js +2 -0
- package/plugins/fuzzy_match/handler.js +7 -0
- package/plugins/fuzzy_match/plugin.js +2 -0
- package/plugins/gm-cc/plugin.js +28 -0
- package/plugins/grep/handler.js +49 -0
- package/plugins/grep/plugin.js +2 -0
- package/plugins/gui-agents/plugin.js +26 -0
- package/plugins/gui-batch/plugin.js +11 -0
- package/plugins/gui-chat/plugin.js +21 -0
- package/plugins/gui-config/plugin.js +12 -0
- package/plugins/gui-cron/plugin.js +13 -0
- package/plugins/gui-debug/plugin.js +24 -0
- package/plugins/gui-env/plugin.js +7 -0
- package/plugins/gui-gateway/plugin.js +9 -0
- package/plugins/gui-profiles-commands-health/plugin.js +11 -0
- package/plugins/gui-sessions/plugin.js +9 -0
- package/plugins/gui-skills/plugin.js +8 -0
- package/plugins/gui-tools/plugin.js +7 -0
- package/plugins/homeassistant_tool/handler.js +14 -0
- package/plugins/homeassistant_tool/plugin.js +2 -0
- package/plugins/image_gen/handler.js +31 -0
- package/plugins/image_gen/plugin.js +2 -0
- package/plugins/interrupt/handler.js +16 -0
- package/plugins/interrupt/plugin.js +2 -0
- package/plugins/managed_tool_gateway/handler.js +9 -0
- package/plugins/managed_tool_gateway/plugin.js +2 -0
- package/plugins/mcp_oauth/handler.js +20 -0
- package/plugins/mcp_oauth/plugin.js +2 -0
- package/plugins/mcp_oauth_manager/handler.js +18 -0
- package/plugins/mcp_oauth_manager/plugin.js +2 -0
- package/plugins/mcp_tool/handler.js +34 -0
- package/plugins/mcp_tool/plugin.js +2 -0
- package/plugins/memory/handler.js +66 -0
- package/plugins/memory/plugin.js +2 -0
- package/plugins/memory-byterover/handler.js +25 -0
- package/plugins/memory-byterover/plugin.js +2 -0
- package/plugins/memory-hindsight/handler.js +25 -0
- package/plugins/memory-hindsight/plugin.js +2 -0
- package/plugins/memory-holographic/handler.js +31 -0
- package/plugins/memory-holographic/plugin.js +2 -0
- package/plugins/memory-honcho/handler.js +25 -0
- package/plugins/memory-honcho/plugin.js +2 -0
- package/plugins/memory-mem0/handler.js +25 -0
- package/plugins/memory-mem0/plugin.js +2 -0
- package/plugins/memory-openviking/handler.js +25 -0
- package/plugins/memory-openviking/plugin.js +2 -0
- package/plugins/memory-retaindb/handler.js +25 -0
- package/plugins/memory-retaindb/plugin.js +2 -0
- package/plugins/memory-supermemory/handler.js +25 -0
- package/plugins/memory-supermemory/plugin.js +2 -0
- package/plugins/mixture_of_agents/handler.js +13 -0
- package/plugins/mixture_of_agents/plugin.js +2 -0
- package/plugins/neutts_synth/handler.js +12 -0
- package/plugins/neutts_synth/plugin.js +2 -0
- package/plugins/openrouter_client/handler.js +12 -0
- package/plugins/openrouter_client/plugin.js +2 -0
- package/plugins/osv_check/handler.js +10 -0
- package/plugins/osv_check/plugin.js +2 -0
- package/plugins/patch_parser/handler.js +40 -0
- package/plugins/patch_parser/plugin.js +2 -0
- package/plugins/path_security/handler.js +14 -0
- package/plugins/path_security/plugin.js +2 -0
- package/plugins/platform-api_server/handler.js +21 -0
- package/plugins/platform-api_server/plugin.js +2 -0
- package/plugins/platform-bluebubbles/handler.js +32 -0
- package/plugins/platform-bluebubbles/plugin.js +2 -0
- package/plugins/platform-dingtalk/handler.js +32 -0
- package/plugins/platform-dingtalk/plugin.js +2 -0
- package/plugins/platform-discord/handler.js +24 -0
- package/plugins/platform-discord/plugin.js +2 -0
- package/plugins/platform-email/handler.js +51 -0
- package/plugins/platform-email/plugin.js +2 -0
- package/plugins/platform-feishu/handler.js +32 -0
- package/plugins/platform-feishu/plugin.js +2 -0
- package/plugins/platform-feishu_comment/handler.js +12 -0
- package/plugins/platform-feishu_comment/plugin.js +2 -0
- package/plugins/platform-feishu_comment_rules/handler.js +11 -0
- package/plugins/platform-feishu_comment_rules/plugin.js +2 -0
- package/plugins/platform-homeassistant/handler.js +32 -0
- package/plugins/platform-homeassistant/plugin.js +2 -0
- package/plugins/platform-matrix/handler.js +40 -0
- package/plugins/platform-matrix/plugin.js +2 -0
- package/plugins/platform-mattermost/handler.js +29 -0
- package/plugins/platform-mattermost/plugin.js +2 -0
- package/plugins/platform-qqbot/handler.js +32 -0
- package/plugins/platform-qqbot/plugin.js +2 -0
- package/plugins/platform-signal/handler.js +33 -0
- package/plugins/platform-signal/plugin.js +2 -0
- package/plugins/platform-slack/handler.js +34 -0
- package/plugins/platform-slack/plugin.js +2 -0
- package/plugins/platform-sms/handler.js +34 -0
- package/plugins/platform-sms/plugin.js +2 -0
- package/plugins/platform-telegram/handler.js +38 -0
- package/plugins/platform-telegram/plugin.js +2 -0
- package/plugins/platform-telegram_network/handler.js +17 -0
- package/plugins/platform-telegram_network/plugin.js +2 -0
- package/plugins/platform-webhook/handler.js +19 -0
- package/plugins/platform-webhook/plugin.js +2 -0
- package/plugins/platform-wecom/handler.js +32 -0
- package/plugins/platform-wecom/plugin.js +2 -0
- package/plugins/platform-wecom_callback/handler.js +15 -0
- package/plugins/platform-wecom_callback/plugin.js +2 -0
- package/plugins/platform-wecom_crypto/handler.js +16 -0
- package/plugins/platform-wecom_crypto/plugin.js +2 -0
- package/plugins/platform-weixin/handler.js +32 -0
- package/plugins/platform-weixin/plugin.js +2 -0
- package/plugins/platform-whatsapp/handler.js +40 -0
- package/plugins/platform-whatsapp/plugin.js +2 -0
- package/plugins/platform-yuanbao/handler.js +9 -0
- package/plugins/platform-yuanbao/plugin.js +2 -0
- package/plugins/platform-yuanbao_media/handler.js +5 -0
- package/plugins/platform-yuanbao_media/plugin.js +2 -0
- package/plugins/platform-yuanbao_proto/handler.js +9 -0
- package/plugins/platform-yuanbao_proto/plugin.js +2 -0
- package/plugins/platform-yuanbao_sticker/handler.js +6 -0
- package/plugins/platform-yuanbao_sticker/plugin.js +2 -0
- package/plugins/process_registry/handler.js +15 -0
- package/plugins/process_registry/plugin.js +2 -0
- package/plugins/read/handler.js +24 -0
- package/plugins/read/plugin.js +2 -0
- package/plugins/rl_training/handler.js +12 -0
- package/plugins/rl_training/plugin.js +2 -0
- package/plugins/schema_sanitizer/handler.js +17 -0
- package/plugins/schema_sanitizer/plugin.js +2 -0
- package/plugins/send_message/handler.js +30 -0
- package/plugins/send_message/plugin.js +2 -0
- package/plugins/session_search/handler.js +21 -0
- package/plugins/session_search/plugin.js +2 -0
- package/plugins/skill_manager/handler.js +16 -0
- package/plugins/skill_manager/plugin.js +2 -0
- package/plugins/skill_usage/handler.js +18 -0
- package/plugins/skill_usage/plugin.js +2 -0
- package/plugins/skills_guard/handler.js +16 -0
- package/plugins/skills_guard/plugin.js +2 -0
- package/plugins/skills_hub/handler.js +29 -0
- package/plugins/skills_hub/plugin.js +2 -0
- package/plugins/skills_index/handler.js +12 -0
- package/plugins/skills_index/plugin.js +2 -0
- package/plugins/skills_sync/handler.js +17 -0
- package/plugins/skills_sync/plugin.js +2 -0
- package/plugins/skills_tool/handler.js +9 -0
- package/plugins/skills_tool/plugin.js +2 -0
- package/plugins/slash_confirm/handler.js +14 -0
- package/plugins/slash_confirm/plugin.js +2 -0
- package/plugins/terminal/handler.js +27 -0
- package/plugins/terminal/plugin.js +2 -0
- package/plugins/tirith_security/handler.js +23 -0
- package/plugins/tirith_security/plugin.js +2 -0
- package/plugins/todo/handler.js +52 -0
- package/plugins/todo/plugin.js +2 -0
- package/plugins/tool_backend_helpers/handler.js +24 -0
- package/plugins/tool_backend_helpers/plugin.js +2 -0
- package/plugins/tool_output_limits/handler.js +14 -0
- package/plugins/tool_output_limits/plugin.js +2 -0
- package/plugins/tool_result_storage/handler.js +18 -0
- package/plugins/tool_result_storage/plugin.js +2 -0
- package/plugins/transcription/handler.js +18 -0
- package/plugins/transcription/plugin.js +2 -0
- package/plugins/tts/handler.js +18 -0
- package/plugins/tts/plugin.js +2 -0
- package/plugins/url_safety/handler.js +14 -0
- package/plugins/url_safety/plugin.js +2 -0
- package/plugins/vision/handler.js +17 -0
- package/plugins/vision/plugin.js +2 -0
- package/plugins/voice_mode/handler.js +9 -0
- package/plugins/voice_mode/plugin.js +2 -0
- package/plugins/web_search/handler.js +35 -0
- package/plugins/web_search/plugin.js +2 -0
- package/plugins/web_tools/handler.js +17 -0
- package/plugins/web_tools/plugin.js +2 -0
- package/plugins/website_policy/handler.js +13 -0
- package/plugins/website_policy/plugin.js +2 -0
- package/plugins/write/handler.js +23 -0
- package/plugins/write/plugin.js +2 -0
- package/plugins/xai_http/handler.js +12 -0
- package/plugins/xai_http/plugin.js +2 -0
- package/plugins/yuanbao_tools/handler.js +12 -0
- package/plugins/yuanbao_tools/plugin.js +2 -0
- package/src/sessions.js +2 -1
- package/src/web/app.js +17 -0
- package/src/web/index.html +7 -3
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { getFreddieHome } from '../../src/home.js'
|
|
5
|
+
function dir() { const d = path.join(getFreddieHome(), 'tool-results'); fs.mkdirSync(d, { recursive: true }); return d }
|
|
6
|
+
|
|
7
|
+
export const _tool = ({
|
|
8
|
+
name: 'tool_result_storage',
|
|
9
|
+
toolset: 'core',
|
|
10
|
+
schema: { name: 'tool_result_storage', description: 'Persist a large tool result to disk; return reference token. Actions: store, fetch, list, delete.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['store', 'fetch', 'list', 'delete'] }, content: { type: 'string' }, token: { type: 'string' } }, required: ['action'] } },
|
|
11
|
+
handler: async ({ action, content, token }) => {
|
|
12
|
+
if (action === 'store') { const t = crypto.randomBytes(8).toString('hex'); fs.writeFileSync(path.join(dir(), t + '.txt'), content || '', 'utf8'); return { token: t, bytes: (content || '').length } }
|
|
13
|
+
if (action === 'fetch') { const f = path.join(dir(), token + '.txt'); return fs.existsSync(f) ? { content: fs.readFileSync(f, 'utf8') } : { error: 'not found' } }
|
|
14
|
+
if (action === 'list') return { tokens: fs.readdirSync(dir()).filter(f => f.endsWith('.txt')).map(f => f.replace(/\.txt$/, '')) }
|
|
15
|
+
if (action === 'delete') { const f = path.join(dir(), token + '.txt'); if (fs.existsSync(f)) fs.unlinkSync(f); return { deleted: token } }
|
|
16
|
+
return { error: 'unknown action' }
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
export const _tool = ({
|
|
3
|
+
name: 'transcription',
|
|
4
|
+
toolset: 'creative',
|
|
5
|
+
schema: { name: 'transcription', description: 'Transcribe audio with OpenAI Whisper.', parameters: { type: 'object', properties: { file_path: { type: 'string' }, model: { type: 'string', default: 'whisper-1' } }, required: ['file_path'] } },
|
|
6
|
+
requiresEnv: ['OPENAI_API_KEY'],
|
|
7
|
+
checkFn: () => Boolean(process.env.OPENAI_API_KEY),
|
|
8
|
+
handler: async ({ file_path, model = 'whisper-1' }) => {
|
|
9
|
+
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
10
|
+
if (!fs.existsSync(file_path)) return { error: 'file not found: ' + file_path }
|
|
11
|
+
const blob = new Blob([fs.readFileSync(file_path)])
|
|
12
|
+
const fd = new FormData()
|
|
13
|
+
fd.append('file', blob, file_path.split(/[\\/]/).pop())
|
|
14
|
+
fd.append('model', model)
|
|
15
|
+
const r = await fetch('https://api.openai.com/v1/audio/transcriptions', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: fd })
|
|
16
|
+
return await r.json()
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const _tool = ({
|
|
2
|
+
name: 'tts',
|
|
3
|
+
toolset: 'creative',
|
|
4
|
+
schema: { name: 'tts', description: 'Synthesize speech (OpenAI tts-1 or ElevenLabs).', parameters: { type: 'object', properties: { text: { type: 'string' }, provider: { type: 'string', enum: ['openai', 'elevenlabs'], default: 'openai' }, voice: { type: 'string' } }, required: ['text'] } },
|
|
5
|
+
requiresEnv: ['OPENAI_API_KEY or ELEVENLABS_API_KEY'],
|
|
6
|
+
checkFn: () => Boolean(process.env.OPENAI_API_KEY || process.env.ELEVENLABS_API_KEY),
|
|
7
|
+
handler: async ({ text, provider = 'openai', voice = 'alloy' }) => {
|
|
8
|
+
if (provider === 'elevenlabs') {
|
|
9
|
+
if (!process.env.ELEVENLABS_API_KEY) return { error: 'ELEVENLABS_API_KEY required' }
|
|
10
|
+
const v = voice || '21m00Tcm4TlvDq8ikWAM'
|
|
11
|
+
const r = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${v}`, { method: 'POST', headers: { 'xi-api-key': process.env.ELEVENLABS_API_KEY, 'content-type': 'application/json' }, body: JSON.stringify({ text }) })
|
|
12
|
+
return { status: r.status, contentType: r.headers.get('content-type'), bytes: (await r.arrayBuffer()).byteLength }
|
|
13
|
+
}
|
|
14
|
+
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
15
|
+
const r = await fetch('https://api.openai.com/v1/audio/speech', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model: 'tts-1', input: text, voice }) })
|
|
16
|
+
return { status: r.status, bytes: (await r.arrayBuffer()).byteLength }
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const SUSPICIOUS = ['phish', 'malware', '.onion']
|
|
2
|
+
const PRIVATE_RANGES = [/^10\./, /^192\.168\./, /^172\.(1[6-9]|2\d|3[01])\./, /^127\./, /^0\./, /^169\.254\./]
|
|
3
|
+
export const _tool = ({
|
|
4
|
+
name: 'url_safety',
|
|
5
|
+
toolset: 'core',
|
|
6
|
+
schema: { name: 'url_safety', description: 'Heuristic URL safety check (private IPs, known-bad TLDs, scheme).', parameters: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] } },
|
|
7
|
+
handler: async ({ url }) => {
|
|
8
|
+
let u; try { u = new URL(url) } catch { return { safe: false, reason: 'invalid URL' } }
|
|
9
|
+
if (!['http:', 'https:'].includes(u.protocol)) return { safe: false, reason: 'unsupported scheme: ' + u.protocol }
|
|
10
|
+
if (PRIVATE_RANGES.some(re => re.test(u.hostname))) return { safe: false, reason: 'private IP host' }
|
|
11
|
+
for (const s of SUSPICIOUS) if (u.hostname.includes(s)) return { safe: false, reason: 'suspicious token: ' + s }
|
|
12
|
+
return { safe: true, host: u.hostname }
|
|
13
|
+
},
|
|
14
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const _tool = ({
|
|
2
|
+
name: 'vision',
|
|
3
|
+
toolset: 'creative',
|
|
4
|
+
schema: { name: 'vision', description: 'Describe an image (URL or base64) using a vision-capable LLM.', parameters: { type: 'object', properties: { image_url: { type: 'string' }, prompt: { type: 'string', default: 'Describe this image.' }, provider: { type: 'string', enum: ['openai', 'anthropic'], default: 'openai' } }, required: ['image_url'] } },
|
|
5
|
+
requiresEnv: ['OPENAI_API_KEY or ANTHROPIC_API_KEY'],
|
|
6
|
+
checkFn: () => Boolean(process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY),
|
|
7
|
+
handler: async ({ image_url, prompt = 'Describe this image.', provider = 'openai' }) => {
|
|
8
|
+
if (provider === 'anthropic') {
|
|
9
|
+
if (!process.env.ANTHROPIC_API_KEY) return { error: 'ANTHROPIC_API_KEY required' }
|
|
10
|
+
const r = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': process.env.ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01', 'content-type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-6', max_tokens: 1024, messages: [{ role: 'user', content: [{ type: 'image', source: { type: 'url', url: image_url } }, { type: 'text', text: prompt }] }] }) })
|
|
11
|
+
return await r.json()
|
|
12
|
+
}
|
|
13
|
+
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
14
|
+
const r = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: [{ type: 'text', text: prompt }, { type: 'image_url', image_url: { url: image_url } }] }] }) })
|
|
15
|
+
return await r.json()
|
|
16
|
+
},
|
|
17
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const _tool = ({
|
|
2
|
+
name: 'voice_mode',
|
|
3
|
+
toolset: 'creative',
|
|
4
|
+
schema: { name: 'voice_mode', description: 'Toggle full-duplex voice (transcription in + tts out) for the active session. Returns the new state.', parameters: { type: 'object', properties: { enabled: { type: 'boolean' } } } },
|
|
5
|
+
handler: async ({ enabled }, ctx = {}) => {
|
|
6
|
+
if (typeof ctx.setVoiceMode === 'function') return await ctx.setVoiceMode(Boolean(enabled))
|
|
7
|
+
return { enabled: Boolean(enabled), note: 'voice mode toggled in-process; bind ctx.setVoiceMode to wire to a real audio loop' }
|
|
8
|
+
},
|
|
9
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const _tool = ({
|
|
2
|
+
name: 'web_search',
|
|
3
|
+
toolset: 'browse',
|
|
4
|
+
schema: {
|
|
5
|
+
name: 'web_search',
|
|
6
|
+
description: 'Search the web (DuckDuckGo HTML or SerpAPI). Returns title/url/snippet list.',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
query: { type: 'string' },
|
|
11
|
+
limit: { type: 'number', default: 5 },
|
|
12
|
+
},
|
|
13
|
+
required: ['query'],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
checkFn: () => true,
|
|
17
|
+
requiresEnv: ['SERPAPI_KEY (optional, falls back to DDG)'],
|
|
18
|
+
handler: async ({ query, limit = 5 }) => {
|
|
19
|
+
if (process.env.SERPAPI_KEY) {
|
|
20
|
+
const url = `https://serpapi.com/search.json?q=${encodeURIComponent(query)}&api_key=${process.env.SERPAPI_KEY}`
|
|
21
|
+
const data = await fetch(url).then(r => r.json())
|
|
22
|
+
const results = (data.organic_results || []).slice(0, limit).map(r => ({ title: r.title, url: r.link, snippet: r.snippet }))
|
|
23
|
+
return { results }
|
|
24
|
+
}
|
|
25
|
+
const fetchFn = globalThis.__freddieFetch || fetch
|
|
26
|
+
const html = await fetchFn(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`).then(r => r.text())
|
|
27
|
+
const results = []
|
|
28
|
+
const re = /<a class="result__a"[^>]*href="([^"]+)"[^>]*>([^<]+)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([^<]+)<\/a>/g
|
|
29
|
+
let m
|
|
30
|
+
while ((m = re.exec(html)) && results.length < limit) {
|
|
31
|
+
results.push({ url: m[1], title: m[2].replace(/&/g, '&'), snippet: m[3].replace(/<\/?b>/g, '') })
|
|
32
|
+
}
|
|
33
|
+
return { results }
|
|
34
|
+
},
|
|
35
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const _tool0 = ({
|
|
2
|
+
name: 'web_fetch',
|
|
3
|
+
toolset: 'browse',
|
|
4
|
+
schema: { name: 'web_fetch', description: 'Fetch a URL and return text/json/headers.', parameters: { type: 'object', properties: { url: { type: 'string' }, method: { type: 'string', default: 'GET' }, headers: {}, body: { type: 'string' }, parse: { type: 'string', enum: ['text', 'json'] } }, required: ['url'] } },
|
|
5
|
+
handler: async ({ url, method = 'GET', headers = {}, body, parse = 'text' }) => {
|
|
6
|
+
const r = await fetch(url, { method, headers, body })
|
|
7
|
+
const ct = r.headers.get('content-type')
|
|
8
|
+
const out = parse === 'json' ? await r.json().catch(() => null) : await r.text()
|
|
9
|
+
return { status: r.status, contentType: ct, body: out }
|
|
10
|
+
},
|
|
11
|
+
})
|
|
12
|
+
export const _tool1 = ({
|
|
13
|
+
name: 'web_extract',
|
|
14
|
+
toolset: 'browse',
|
|
15
|
+
schema: { name: 'web_extract', description: 'Strip tags from HTML to plain text.', parameters: { type: 'object', properties: { html: { type: 'string' } }, required: ['html'] } },
|
|
16
|
+
handler: async ({ html }) => ({ text: String(html).replace(/<script[\s\S]*?<\/script>/gi, '').replace(/<style[\s\S]*?<\/style>/gi, '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim() }),
|
|
17
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getConfigValue } from '../../src/config.js'
|
|
2
|
+
export const _tool = ({
|
|
3
|
+
name: 'website_policy',
|
|
4
|
+
toolset: 'core',
|
|
5
|
+
schema: { name: 'website_policy', description: 'Per-host fetch policy (rate limit, allow/deny). Reads config.website_policy.', parameters: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] } },
|
|
6
|
+
handler: async ({ url }) => {
|
|
7
|
+
const policy = getConfigValue('website_policy', { allow: [], deny: [], ratelimit_ms: 1000 }) || {}
|
|
8
|
+
const u = new URL(url)
|
|
9
|
+
if (policy.deny?.some(d => u.hostname.includes(d))) return { decision: 'deny' }
|
|
10
|
+
if (policy.allow?.length && !policy.allow.some(a => u.hostname.includes(a))) return { decision: 'deny', reason: 'not in allow list' }
|
|
11
|
+
return { decision: 'allow', ratelimit_ms: policy.ratelimit_ms || 1000 }
|
|
12
|
+
},
|
|
13
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
export const _tool = ({
|
|
4
|
+
name: 'write',
|
|
5
|
+
toolset: 'core',
|
|
6
|
+
schema: {
|
|
7
|
+
name: 'write',
|
|
8
|
+
description: 'Write content to a file (overwrites).',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
path: { type: 'string' },
|
|
13
|
+
content: { type: 'string' },
|
|
14
|
+
},
|
|
15
|
+
required: ['path', 'content'],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
handler: async ({ path: p, content }) => {
|
|
19
|
+
fs.mkdirSync(path.dirname(p), { recursive: true })
|
|
20
|
+
fs.writeFileSync(p, content, 'utf8')
|
|
21
|
+
return { path: p, bytes: Buffer.byteLength(content, 'utf8') }
|
|
22
|
+
},
|
|
23
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const _tool = ({
|
|
2
|
+
name: 'xai_grok',
|
|
3
|
+
toolset: 'core',
|
|
4
|
+
schema: { name: 'xai_grok', description: 'Chat completion via xAI Grok.', parameters: { type: 'object', properties: { prompt: { type: 'string' }, model: { type: 'string', default: 'grok-3' } }, required: ['prompt'] } },
|
|
5
|
+
requiresEnv: ['XAI_API_KEY'],
|
|
6
|
+
checkFn: () => Boolean(process.env.XAI_API_KEY),
|
|
7
|
+
handler: async ({ prompt, model = 'grok-3' }) => {
|
|
8
|
+
if (!process.env.XAI_API_KEY) return { error: 'XAI_API_KEY required' }
|
|
9
|
+
const r = await fetch('https://api.x.ai/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.XAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }) })
|
|
10
|
+
return await r.json()
|
|
11
|
+
},
|
|
12
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const _tool = ({
|
|
2
|
+
name: 'yuanbao_tools',
|
|
3
|
+
toolset: 'core',
|
|
4
|
+
schema: { name: 'yuanbao_tools', description: 'Tencent Yuanbao chat completion (Hunyuan).', parameters: { type: 'object', properties: { prompt: { type: 'string' }, model: { type: 'string', default: 'hunyuan-pro' } }, required: ['prompt'] } },
|
|
5
|
+
requiresEnv: ['YUANBAO_API_KEY'],
|
|
6
|
+
checkFn: () => Boolean(process.env.YUANBAO_API_KEY),
|
|
7
|
+
handler: async ({ prompt, model = 'hunyuan-pro' }) => {
|
|
8
|
+
if (!process.env.YUANBAO_API_KEY) return { error: 'YUANBAO_API_KEY required' }
|
|
9
|
+
const r = await fetch('https://api.hunyuan.cloud.tencent.com/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.YUANBAO_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }) })
|
|
10
|
+
return await r.json()
|
|
11
|
+
},
|
|
12
|
+
})
|
package/src/sessions.js
CHANGED
|
@@ -69,6 +69,7 @@ export async function listSessions(limit = 50) {
|
|
|
69
69
|
|
|
70
70
|
export async function search(query, limit = 20) {
|
|
71
71
|
const d = await db()
|
|
72
|
+
const likePattern = `%${query}%`
|
|
72
73
|
// Try FTS5 if available (libsql, but not busybase since triggers can't be created)
|
|
73
74
|
try {
|
|
74
75
|
const ftsResult = await d.prepare(`SELECT m.id, m.session_id, m.content FROM messages_fts f JOIN messages m ON m.id = f.rowid WHERE messages_fts MATCH ? ORDER BY rank LIMIT ?`).all(query, limit)
|
|
@@ -77,7 +78,7 @@ export async function search(query, limit = 20) {
|
|
|
77
78
|
// FTS5 not available, fall through to LIKE
|
|
78
79
|
}
|
|
79
80
|
// Fallback to LIKE search
|
|
80
|
-
return await d.prepare(`SELECT id, session_id, content FROM messages WHERE content LIKE ? ORDER BY ts DESC LIMIT ?`).all(
|
|
81
|
+
return await d.prepare(`SELECT id, session_id, content FROM messages WHERE content LIKE ? ORDER BY ts DESC LIMIT ?`).all(likePattern, limit)
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
export function closeDb() { return closeDbImpl() }
|
package/src/web/app.js
CHANGED
|
@@ -6,6 +6,7 @@ await installStyles()
|
|
|
6
6
|
|
|
7
7
|
if (!window.__debug) { try { window.__debug = {} } catch { Object.defineProperty(window, '__debug', { value: {}, writable: true, configurable: true }) } }
|
|
8
8
|
window.__debug.dashboard = () => ({ booted: true, ts: Date.now(), framework: 'anentrypoint-design+webjsx', route: location.hash || '#/sessions' })
|
|
9
|
+
window.__debug.agents = () => ({ registered: true, active: AppState.agents?.active || null, count: AppState.agents?.count || 0 })
|
|
9
10
|
|
|
10
11
|
const j = async (u, opts) => { try { const r = await fetch(u, opts); if (!r.ok) throw new Error(r.status + ' ' + r.statusText); return await r.json() } catch (e) { return { __error: String(e) } } }
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@ const ROUTES = [
|
|
|
13
14
|
{ path: '#/home', label: 'Home', glyph: '⌂' },
|
|
14
15
|
{ path: '#/chat', label: 'Chat', glyph: '⌨' },
|
|
15
16
|
{ path: '#/sessions', label: 'Sessions', glyph: '✉' },
|
|
17
|
+
{ path: '#/agents', label: 'Agents', glyph: '◈' },
|
|
16
18
|
{ path: '#/analytics', label: 'Analytics', glyph: '◉' },
|
|
17
19
|
{ path: '#/models', label: 'Models', glyph: '◎' },
|
|
18
20
|
{ path: '#/logs', label: 'Logs', glyph: '☰' },
|
|
@@ -36,6 +38,7 @@ const AppState = {
|
|
|
36
38
|
sessionsFilter: '',
|
|
37
39
|
chat: { messages: [], draft: '', streaming: false },
|
|
38
40
|
batch: { results: null, running: false },
|
|
41
|
+
agents: { count: 0, active: null },
|
|
39
42
|
}
|
|
40
43
|
function applyTheme() { document.documentElement.setAttribute('data-theme', AppState.theme) }
|
|
41
44
|
applyTheme()
|
|
@@ -141,6 +144,20 @@ const PAGES = {
|
|
|
141
144
|
]
|
|
142
145
|
},
|
|
143
146
|
|
|
147
|
+
'#/agents': async () => {
|
|
148
|
+
const agents = await j('/api/agents')
|
|
149
|
+
const count = agents.__error ? 0 : (agents.count || 0)
|
|
150
|
+
const active = agents.active || null
|
|
151
|
+
return [
|
|
152
|
+
kpi([[count || 0, 'Total agents'], [active ? 1 : 0, 'Active']]),
|
|
153
|
+
Panel({ title: 'Agent overview', children: Receipt({ rows: [
|
|
154
|
+
['Total agent turns', String(agents.turns || 0)],
|
|
155
|
+
['Active agent', active || '(none)'],
|
|
156
|
+
['Last activity', agents.last_activity ? new Date(agents.last_activity).toLocaleString() : '—'],
|
|
157
|
+
]}) }),
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
|
|
144
161
|
'#/analytics': async () => {
|
|
145
162
|
const [sessions, tools, debug] = await Promise.all([j('/api/sessions'), j('/api/tools'), j('/api/debug')])
|
|
146
163
|
const all = Array.isArray(sessions) ? sessions : []
|
package/src/web/index.html
CHANGED
|
@@ -146,10 +146,12 @@ html[data-theme="dark"] .ds-247420 {
|
|
|
146
146
|
.ds-247420 .kpi-card .num { font-size: 1.75rem; color: var(--panel-text); font-weight: 400; font-family: 'JetBrains Mono', monospace; }
|
|
147
147
|
.ds-247420 .kpi-card .lbl { font-size: 0.75rem; color: var(--panel-text-2); font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; margin-top: 2px; }
|
|
148
148
|
|
|
149
|
-
.ds-247420 .row-form { display: flex; gap: 8px; flex-wrap: wrap; align-items:
|
|
150
|
-
.ds-247420 .row-form input { flex: 1; min-width: 140px; }
|
|
149
|
+
.ds-247420 .row-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
|
|
150
|
+
.ds-247420 .row-form input { flex: 1; min-width: 140px; padding: 10px 12px !important; }
|
|
151
151
|
|
|
152
|
-
.ds-247420 .chat
|
|
152
|
+
.ds-247420 .chat { display: flex; flex-direction: column; height: 100%; }
|
|
153
|
+
.ds-247420 .chat .chat-thread { flex: 1; max-height: calc(100vh - 320px); overflow-y: auto; }
|
|
154
|
+
.ds-247420 .chat .chat-composer { padding-top: 12px; border-top: 1px solid var(--panel-2); }
|
|
153
155
|
|
|
154
156
|
.ds-247420 .chip { border-radius: 6px; padding: 4px 10px; font-size: 12px; font-weight: 600; display: inline-block; }
|
|
155
157
|
.ds-247420 .chip.ok { background: var(--green); color: white; }
|
|
@@ -158,6 +160,8 @@ html[data-theme="dark"] .ds-247420 {
|
|
|
158
160
|
.ds-247420 .empty { color: var(--panel-text-3); padding: 12px 0; }
|
|
159
161
|
.ds-247420 a { color: var(--link); text-decoration: none; }
|
|
160
162
|
.ds-247420 a:hover { text-decoration: underline; }
|
|
163
|
+
|
|
164
|
+
#app { padding: 16px 20px; }
|
|
161
165
|
</style>
|
|
162
166
|
</head>
|
|
163
167
|
<body>
|