@causyne/agent 0.0.0-stage → 1.10.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/LICENSE +102 -0
- package/README.md +31 -2
- package/SECURITY.md +24 -0
- package/bin/causyne.js +13 -0
- package/docs/ARCHITECTURE.md +63 -0
- package/docs/PROVENANCE.md +29 -0
- package/docs/SECURITY_REVIEW.md +89 -0
- package/docs/THIRD_PARTY_NOTICES.md +49 -0
- package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
- package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
- package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
- package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
- package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
- package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
- package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
- package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
- package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
- package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
- package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
- package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
- package/native-skills/android/play-store-release/SKILL.md +43 -0
- package/native-skills/android/room-and-persistence/SKILL.md +40 -0
- package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
- package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
- package/native-skills/backend/caching-strategy/SKILL.md +44 -0
- package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
- package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
- package/native-skills/backend/rest-api-design/SKILL.md +40 -0
- package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
- package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
- package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
- package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
- package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
- package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
- package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
- package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
- package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
- package/native-skills/data/metric-definition/SKILL.md +42 -0
- package/native-skills/data/notebook-to-production/SKILL.md +43 -0
- package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
- package/native-skills/database/connection-pooling/SKILL.md +42 -0
- package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
- package/native-skills/database/query-optimisation/SKILL.md +38 -0
- package/native-skills/database/safe-migrations/SKILL.md +47 -0
- package/native-skills/database/schema-design/SKILL.md +38 -0
- package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
- package/native-skills/design/colour-system-construction/SKILL.md +38 -0
- package/native-skills/design/design-direction-brief/SKILL.md +41 -0
- package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
- package/native-skills/design/responsive-strategy/SKILL.md +39 -0
- package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
- package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
- package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
- package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
- package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
- package/native-skills/devops/incident-triage/SKILL.md +38 -0
- package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
- package/native-skills/devops/observability-baseline/SKILL.md +38 -0
- package/native-skills/devops/secret-management/SKILL.md +40 -0
- package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
- package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
- package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
- package/native-skills/git/branch-strategy/SKILL.md +39 -0
- package/native-skills/git/commit-hygiene/SKILL.md +41 -0
- package/native-skills/git/history-recovery/SKILL.md +44 -0
- package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
- package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
- package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
- package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
- package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
- package/native-skills/mobile/deep-linking/SKILL.md +39 -0
- package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
- package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
- package/native-skills/mobile/push-notifications/SKILL.md +38 -0
- package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
- package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
- package/native-skills/performance/load-testing/SKILL.md +42 -0
- package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
- package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
- package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
- package/native-skills/python/async-correctness/SKILL.md +40 -0
- package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
- package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
- package/native-skills/python/environment-isolation/SKILL.md +41 -0
- package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
- package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
- package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
- package/native-skills/security/authorization-model/SKILL.md +42 -0
- package/native-skills/security/crypto-usage/SKILL.md +48 -0
- package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
- package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
- package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
- package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
- package/native-skills/security/threat-modelling/SKILL.md +46 -0
- package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
- package/native-skills/testing/property-based-testing/SKILL.md +46 -0
- package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
- package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
- package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
- package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
- package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
- package/native-skills/web/accessible-components/SKILL.md +45 -0
- package/native-skills/web/api-client-resilience/SKILL.md +39 -0
- package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
- package/native-skills/web/core-web-vitals/SKILL.md +40 -0
- package/native-skills/web/css-layout-systems/SKILL.md +41 -0
- package/native-skills/web/form-validation-ux/SKILL.md +39 -0
- package/native-skills/web/react-state-architecture/SKILL.md +41 -0
- package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
- package/package.json +68 -4
- package/src/agentLoop.js +2458 -0
- package/src/appChannel.js +109 -0
- package/src/approvalGrants.js +66 -0
- package/src/auditLog.js +52 -0
- package/src/autoUpdate.js +12 -0
- package/src/capabilities.js +36 -0
- package/src/checkpoints.js +1199 -0
- package/src/cliMode.js +638 -0
- package/src/commands.js +805 -0
- package/src/compiledSkills.js +540 -0
- package/src/config.js +130 -0
- package/src/contextManager.js +498 -0
- package/src/cortex/autoCondenser.js +192 -0
- package/src/cortex/provisioner.js +27 -0
- package/src/cortex/vectorDb.js +447 -0
- package/src/designPalettes.js +99 -0
- package/src/devMode.js +6 -0
- package/src/embodimentSession.js +180 -0
- package/src/entrypoint.js +194 -0
- package/src/execSafe.js +21 -0
- package/src/gitSnapshot.js +378 -0
- package/src/mcp/jsonRpc.js +165 -0
- package/src/mcp/mcpClient.js +136 -0
- package/src/mcp/mcpRegistry.js +207 -0
- package/src/memory.js +62 -0
- package/src/memoryManager.js +124 -0
- package/src/modelCatalogue.js +132 -0
- package/src/modelIds.js +42 -0
- package/src/modelRouter.js +145 -0
- package/src/outputStore.js +246 -0
- package/src/perception.js +124 -0
- package/src/planCache.js +437 -0
- package/src/procSignal.js +32 -0
- package/src/providers/anthropic.js +408 -0
- package/src/providers/base.js +328 -0
- package/src/providers/dashscope-byok.js +86 -0
- package/src/providers/deepseek.js +80 -0
- package/src/providers/factory.js +37 -0
- package/src/providers/gemini.js +346 -0
- package/src/providers/moonshot.js +73 -0
- package/src/providers/muse.js +169 -0
- package/src/providers/ollama.js +104 -0
- package/src/providers/openai-compatible.js +84 -0
- package/src/providers/openai.js +20 -0
- package/src/providers/qwen-byok.js +76 -0
- package/src/providers/xai.js +73 -0
- package/src/provisioning/capabilityConsent.js +24 -0
- package/src/provisioning/capabilityProvisioner.js +271 -0
- package/src/provisioning/capabilityTools.js +29 -0
- package/src/reporting.js +373 -0
- package/src/sandbox.js +529 -0
- package/src/sessionStore.js +279 -0
- package/src/skillsManager.js +288 -0
- package/src/slopDetector.js +307 -0
- package/src/taskBuffer.js +45 -0
- package/src/taskShape.js +241 -0
- package/src/textDiff.js +150 -0
- package/src/tokenTracker.js +560 -0
- package/src/tool-scripts/ax_snapshot.applescript +39 -0
- package/src/tool-scripts/ax_snapshot.ps1 +10 -0
- package/src/tool-scripts/desktop_act.applescript +58 -0
- package/src/tool-scripts/resolve_automator.py +119 -0
- package/src/toolPacks.js +524 -0
- package/src/tools/archive.js +63 -0
- package/src/tools/astryxOracle.js +78 -0
- package/src/tools/axSnapshot.js +165 -0
- package/src/tools/blenderAutomator.js +349 -0
- package/src/tools/browserAutomator.js +252 -0
- package/src/tools/checkSystemSpecs.js +64 -0
- package/src/tools/clipboard.js +36 -0
- package/src/tools/codeSearchAst.js +101 -0
- package/src/tools/codeSearcher.js +230 -0
- package/src/tools/codeTools.js +262 -0
- package/src/tools/codebaseSearch.js +75 -0
- package/src/tools/createSkill.js +129 -0
- package/src/tools/deferredMcpSearch.js +246 -0
- package/src/tools/desktopAct.js +208 -0
- package/src/tools/desktopOpen.js +34 -0
- package/src/tools/dynamicLoader.js +44 -0
- package/src/tools/executeProceduralMedia.js +93 -0
- package/src/tools/fsDelete.js +51 -0
- package/src/tools/fsEdit.js +105 -0
- package/src/tools/fsFind.js +128 -0
- package/src/tools/fsList.js +77 -0
- package/src/tools/fsMove.js +57 -0
- package/src/tools/fsRead.js +87 -0
- package/src/tools/fsSearch.js +198 -0
- package/src/tools/fsWrite.js +41 -0
- package/src/tools/gameAiBehaviorTree.js +208 -0
- package/src/tools/gameAnimationRig.js +194 -0
- package/src/tools/gameAssetFetcher.js +372 -0
- package/src/tools/gameAssetHarvester.js +210 -0
- package/src/tools/gameAudioSynth.js +267 -0
- package/src/tools/gameBuilder.js +886 -0
- package/src/tools/gameCanvasAudit.js +101 -0
- package/src/tools/gameCinematicSequencer.js +151 -0
- package/src/tools/gameDataForge.js +124 -0
- package/src/tools/gameDialogueTreeGen.js +166 -0
- package/src/tools/gameEconomyBalancer.js +161 -0
- package/src/tools/gameEntitySpawner.js +207 -0
- package/src/tools/gameLoreForge.js +153 -0
- package/src/tools/gameQuestArcBuilder.js +152 -0
- package/src/tools/gameSceneBuilder.js +158 -0
- package/src/tools/gameShaderForge.js +181 -0
- package/src/tools/gameTilemapGenerator.js +164 -0
- package/src/tools/gameUiHudBuilder.js +292 -0
- package/src/tools/gameVfxEmitterGen.js +108 -0
- package/src/tools/gameWebRuntimeInspector.js +91 -0
- package/src/tools/generateMediaViaApi.js +358 -0
- package/src/tools/gitExec.js +54 -0
- package/src/tools/godotAutomator.js +437 -0
- package/src/tools/manageLocalAiDependencies.js +220 -0
- package/src/tools/memoryWrite.js +99 -0
- package/src/tools/multiEdit.js +107 -0
- package/src/tools/palmierAutomator.js +104 -0
- package/src/tools/pdfReader.js +112 -0
- package/src/tools/persistentShell.js +148 -0
- package/src/tools/printer.js +316 -0
- package/src/tools/procTerm.js +91 -0
- package/src/tools/processList.js +52 -0
- package/src/tools/readNativeSkill.js +39 -0
- package/src/tools/registry.js +277 -0
- package/src/tools/resolveAutomator.js +102 -0
- package/src/tools/screenshot.js +75 -0
- package/src/tools/subagentOrchestrator.js +305 -0
- package/src/tools/sysRun.js +125 -0
- package/src/tools/todoWrite.js +75 -0
- package/src/tools/toolDispatch.js +127 -0
- package/src/tools/toolForge.js +124 -0
- package/src/tools/updatePlan.js +88 -0
- package/src/tools/viewImage.js +82 -0
- package/src/tools/visionInspector.js +154 -0
- package/src/tools/webFetch.js +74 -0
- package/src/tools/webSearch.js +82 -0
- package/src/tui/ask.js +164 -0
- package/src/tui/banner.js +158 -0
- package/src/tui/block.js +152 -0
- package/src/tui/chatRenderer.js +953 -0
- package/src/tui/completion.js +424 -0
- package/src/tui/dialogs.js +606 -0
- package/src/tui/diff.js +296 -0
- package/src/tui/history.js +135 -0
- package/src/tui/home.js +217 -0
- package/src/tui/invocation.js +105 -0
- package/src/tui/keys.js +443 -0
- package/src/tui/lineEditor.js +256 -0
- package/src/tui/overlay.js +256 -0
- package/src/tui/prompt.js +1258 -0
- package/src/tui/selectList.js +335 -0
- package/src/tui/stage.js +330 -0
- package/src/tui/stageSurface.js +609 -0
- package/src/tui/statusLine.js +254 -0
- package/src/tui/text.js +345 -0
- package/src/tui/theme.js +393 -0
- package/src/tui/toolDisplay.js +176 -0
- package/src/tui/transcript.js +235 -0
- package/src/tui/viewport.js +337 -0
- package/src/uncertainty.js +271 -0
- package/src/userSkills.js +265 -0
- package/src/utils/cli_ui.js +0 -0
- package/src/utils/inquirer.js +12 -0
- package/src/utils/paths.js +37 -0
- package/src/version.js +38 -0
- package/src/vfeTracker.js +220 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { causyneHome } from '../utils/paths.js';
|
|
5
|
+
|
|
6
|
+
const CUSTOM_TOOLS_DIR = path.join(causyneHome(), 'custom_tools');
|
|
7
|
+
|
|
8
|
+
export const toolForgeTool = {
|
|
9
|
+
schema: {
|
|
10
|
+
name: 'tool.forge',
|
|
11
|
+
description: 'Self-Evolution Engine: Write a new custom Javascript tool that will be permanently added to your capabilities. Use this when you lack a specific tool to accomplish a goal. The tool will be hot-reloaded instantly.',
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
toolName: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'CamelCase name of the tool (e.g. "imageResizer", "pdfExtractor"). Must be alphanumeric without spaces.',
|
|
18
|
+
},
|
|
19
|
+
toolDescription: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Detailed description of what the tool does, so you know how to use it later.',
|
|
22
|
+
},
|
|
23
|
+
parametersSchema: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Stringified JSON schema of the arguments the tool accepts. E.g. {"type":"object","properties":{"filePath":{"type":"string"}},"required":["filePath"]}',
|
|
26
|
+
},
|
|
27
|
+
javascriptCode: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'The actual Node.js code block for the execute function. MUST be an async function accepting (args, context). Must return an object like { success: true, data: ... }. DO NOT use `export` or `const toolName =`, just provide the raw function body or `async (args, context) => { ... }`.',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ['toolName', 'toolDescription', 'parametersSchema', 'javascriptCode'],
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
execute: async (args, context) => {
|
|
36
|
+
try {
|
|
37
|
+
const { toolName, toolDescription, parametersSchema, javascriptCode } = args;
|
|
38
|
+
|
|
39
|
+
// Validate name
|
|
40
|
+
if (!/^[a-zA-Z0-9_]+$/.test(toolName)) {
|
|
41
|
+
return { success: false, error: 'Invalid toolName. Use alphanumeric characters only.' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Ethical Safety Gate Layer 4: Block malicious tool names and descriptions
|
|
45
|
+
const BANNED_TOOL_NAMES = [
|
|
46
|
+
/keylog/i, /phish/i, /exploit/i, /malware/i, /trojan/i,
|
|
47
|
+
/ransomware/i, /rootkit/i, /backdoor/i, /ddos/i,
|
|
48
|
+
/credential.?harvest/i, /password.?steal/i, /brute.?force/i,
|
|
49
|
+
/crypto.?mine/i, /wallet.?drain/i, /spyware/i, /surveil/i
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
for (const pattern of BANNED_TOOL_NAMES) {
|
|
53
|
+
if (pattern.test(toolName) || pattern.test(toolDescription)) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: 'Safety violation: Tool name or description indicates malicious intent. Causyne exists to make the world better.'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
const SUBPROCESS_MODULE = "child_process";
|
|
63
|
+
const BANNED_TOOL_PATTERNS = [
|
|
64
|
+
/process\.kill\(/,
|
|
65
|
+
new RegExp(SUBPROCESS_MODULE),
|
|
66
|
+
new RegExp('require\\s*\\(\\s*[\'"]' + SUBPROCESS_MODULE + '[\'"]\\s*\\)'),
|
|
67
|
+
/fs\.(rm|rmdir)Sync.*recursive/i,
|
|
68
|
+
/fs\.writeFileSync\s*\(\s*['"]\/etc/,
|
|
69
|
+
/fs\.writeFileSync\s*\(\s*['"]C:\\\\Windows/i,
|
|
70
|
+
/stratum\+tcp/i,
|
|
71
|
+
/cryptonight|ethash|randomx/i,
|
|
72
|
+
/iohook|keypress|keylog/i,
|
|
73
|
+
/getUserMedia|MediaRecorder.*audio|MediaRecorder.*video/i,
|
|
74
|
+
/sqlmap|nikto|metasploit/i
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const pattern of BANNED_TOOL_PATTERNS) {
|
|
78
|
+
if (pattern.test(javascriptCode)) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: 'Safety violation: Tool code contains a banned pattern. Causyne cannot forge tools designed for harm.'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Ensure directory exists
|
|
87
|
+
if (!fs.existsSync(CUSTOM_TOOLS_DIR)) {
|
|
88
|
+
fs.mkdirSync(CUSTOM_TOOLS_DIR, { recursive: true, mode: 0o700 });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const filePath = path.join(CUSTOM_TOOLS_DIR, `${toolName}.js`);
|
|
92
|
+
|
|
93
|
+
// Construct the ES Module file
|
|
94
|
+
const fileContent = `
|
|
95
|
+
// AUTOGENERATED CUSTOM TOOL: ${toolName}
|
|
96
|
+
// Generated at: ${new Date().toISOString()}
|
|
97
|
+
|
|
98
|
+
export const ${toolName}Tool = {
|
|
99
|
+
name: '${toolName}',
|
|
100
|
+
description: ${JSON.stringify(toolDescription)},
|
|
101
|
+
parameters: ${parametersSchema},
|
|
102
|
+
execute: async (args, context) => {
|
|
103
|
+
// LLM Generated Code Start
|
|
104
|
+
const llmFunction = ${javascriptCode.trim().startsWith('async') ? javascriptCode : `async (args, context) => { ${javascriptCode} }`};
|
|
105
|
+
return await llmFunction(args, context);
|
|
106
|
+
// LLM Generated Code End
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
fs.writeFileSync(filePath, fileContent.trim());
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
message: `Successfully forged new tool '${toolName}'. It is saved to ${filePath}. It will be available on the next loop iteration.`
|
|
116
|
+
};
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: `Failed to forge tool: ${error.message}`
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export const updatePlan = {
|
|
5
|
+
schema: {
|
|
6
|
+
name: 'update_plan',
|
|
7
|
+
description: 'Self-Correcting Trajectory Engine: Update specific tasks in your plan using delta operations without rewriting everything.',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
action: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
enum: ['read', 'ADD_TASK', 'REMOVE_TASK', 'MARK_COMPLETED', 'MARK_FAILED']
|
|
14
|
+
},
|
|
15
|
+
taskId: { type: 'string', description: 'ID of the task to modify' },
|
|
16
|
+
taskContent: { type: 'string', description: 'Content of the new task (if ADD_TASK)' },
|
|
17
|
+
failureReason: { type: 'string', description: 'REQUIRED if action is MARK_FAILED' }
|
|
18
|
+
},
|
|
19
|
+
required: ['action']
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
execute: async (args) => {
|
|
24
|
+
try {
|
|
25
|
+
const todoPath = path.resolve(process.cwd(), '.causyne_todos.json');
|
|
26
|
+
const mdPath = path.resolve(process.cwd(), 'AGI_PLAN.md');
|
|
27
|
+
|
|
28
|
+
let todos = [];
|
|
29
|
+
if (fs.existsSync(todoPath)) {
|
|
30
|
+
todos = JSON.parse(fs.readFileSync(todoPath, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (args.action === 'read') {
|
|
34
|
+
if (!fs.existsSync(mdPath)) return "No plan exists.";
|
|
35
|
+
return fs.readFileSync(mdPath, 'utf8');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (args.action === 'MARK_FAILED' && !args.failureReason) {
|
|
39
|
+
return "Error: You MUST provide a failureReason when marking a task as failed.";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (args.action === 'ADD_TASK') {
|
|
43
|
+
if (!args.taskId || !args.taskContent) return "Missing taskId or taskContent";
|
|
44
|
+
todos.push({ id: args.taskId, content: args.taskContent, status: 'pending' });
|
|
45
|
+
}
|
|
46
|
+
else if (args.action === 'REMOVE_TASK') {
|
|
47
|
+
todos = todos.filter(t => t.id !== args.taskId);
|
|
48
|
+
}
|
|
49
|
+
else if (args.action === 'MARK_COMPLETED') {
|
|
50
|
+
const t = todos.find(t => t.id === args.taskId);
|
|
51
|
+
if (t) t.status = 'completed';
|
|
52
|
+
}
|
|
53
|
+
else if (args.action === 'MARK_FAILED') {
|
|
54
|
+
const t = todos.find(t => t.id === args.taskId);
|
|
55
|
+
if (t) {
|
|
56
|
+
t.status = 'pending'; // Reset it
|
|
57
|
+
// Phase 2 AGI Upgrade: Auto-Pivot
|
|
58
|
+
todos.push({
|
|
59
|
+
id: `investigate_${args.taskId}`,
|
|
60
|
+
content: `Investigate failure of ${args.taskId}: ${args.failureReason}`,
|
|
61
|
+
status: 'in_progress'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Sync JSON
|
|
67
|
+
fs.writeFileSync(todoPath, JSON.stringify(todos, null, 2), 'utf8');
|
|
68
|
+
|
|
69
|
+
// Sync MD
|
|
70
|
+
let mdContent = '# AGI Execution Plan\n\n';
|
|
71
|
+
for (const t of todos) {
|
|
72
|
+
let box = '[ ]';
|
|
73
|
+
if (t.status === 'in_progress') box = '[/]';
|
|
74
|
+
if (t.status === 'completed') box = '[x]';
|
|
75
|
+
mdContent += `- ${box} ${t.id}: ${t.content}\n`;
|
|
76
|
+
}
|
|
77
|
+
fs.writeFileSync(mdPath, mdContent, 'utf8');
|
|
78
|
+
|
|
79
|
+
if (args.action === 'MARK_FAILED') {
|
|
80
|
+
return `Task failed. Auto-pivot engaged. Added investigation task.`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return "Plan delta updated successfully.";
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return `Error updating plan: ${error.message}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { resolveUserPath } from '../utils/paths.js';
|
|
3
|
+
import { existsSync, statSync } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { ensureCapability, loadCapabilityPackage } from '../provisioning/capabilityProvisioner.js';
|
|
7
|
+
|
|
8
|
+
export const viewImage = {
|
|
9
|
+
schema: {
|
|
10
|
+
name: 'view_image',
|
|
11
|
+
description: 'Multi-Modal Pre-Processing Engine. View an image file from disk. Automatically compresses huge images to save tokens. For text-only LLMs, it autonomously extracts text via OCR.',
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
path: { type: 'string', description: 'Absolute or relative path to the image file' },
|
|
16
|
+
extractTextOnly: { type: 'boolean', description: 'If true, skips Vision APIs and uses local OCR to read text from the image.' }
|
|
17
|
+
},
|
|
18
|
+
required: ['path']
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
execute: async (args, sandbox) => {
|
|
23
|
+
try {
|
|
24
|
+
const targetPath = resolveUserPath(args.path);
|
|
25
|
+
if (!existsSync(targetPath)) {
|
|
26
|
+
return { success: false, error: `Image not found at ${targetPath}` };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const stat = statSync(targetPath);
|
|
30
|
+
if (stat.size > 20 * 1024 * 1024) {
|
|
31
|
+
return { success: false, error: 'Image too large. Maximum raw size is 20MB.' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Phase 6 AGI Upgrade: OCR Fallback for Text-Only LLMs
|
|
35
|
+
const forceOcr = args.extractTextOnly || (sandbox && sandbox.llmSupportsVision === false);
|
|
36
|
+
|
|
37
|
+
if (forceOcr) {
|
|
38
|
+
const ocr = await ensureCapability('ocr');
|
|
39
|
+
if (!ocr.ok) return { success: false, error: ocr.reason, requiresConsent: ocr.requiresConsent };
|
|
40
|
+
const Tesseract = await loadCapabilityPackage('tesseract.js');
|
|
41
|
+
|
|
42
|
+
const worker = await Tesseract.createWorker('eng');
|
|
43
|
+
const { data: { text } } = await worker.recognize(targetPath);
|
|
44
|
+
await worker.terminate();
|
|
45
|
+
|
|
46
|
+
return { success: true, output: `[OCR FALLBACK] Extracted Text from Image:\n\n${text}` };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Phase 6 AGI Upgrade: Dynamic Resizing & Format Normalization
|
|
50
|
+
let processedBuffer;
|
|
51
|
+
let mimeType = 'image/jpeg';
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// sharp already lives in the Cortex tree as a pinned override, so it is
|
|
55
|
+
// loaded from there rather than installed a second time.
|
|
56
|
+
const sharp = await loadCapabilityPackage('sharp');
|
|
57
|
+
processedBuffer = await sharp(targetPath)
|
|
58
|
+
.resize({ width: 1024, height: 1024, fit: 'inside', withoutEnlargement: true })
|
|
59
|
+
.jpeg({ quality: 85 })
|
|
60
|
+
.toBuffer();
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Fallback if sharp is missing (return raw buffer but warn)
|
|
63
|
+
processedBuffer = await fs.readFile(targetPath);
|
|
64
|
+
const ext = path.extname(targetPath).toLowerCase();
|
|
65
|
+
if (ext === '.png') mimeType = 'image/png';
|
|
66
|
+
else if (ext === '.webp') mimeType = 'image/webp';
|
|
67
|
+
else if (ext === '.gif') mimeType = 'image/gif';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const base64 = processedBuffer.toString('base64');
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
_is_image_marker: true,
|
|
74
|
+
mimeType,
|
|
75
|
+
base64,
|
|
76
|
+
filename: path.basename(targetPath)
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return { success: false, error: `Error viewing image: ${error.message}` };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import fsSync from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execFile } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
|
|
7
|
+
import { ensureCapability, loadCapabilityPackage } from '../provisioning/capabilityProvisioner.js';
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
|
|
11
|
+
// Helper: Sovereign Workspace Buffer to prevent /tmp/ boundary errors
|
|
12
|
+
function getWorkspaceTempDir() {
|
|
13
|
+
const dir = path.join(process.cwd(), '.tmp_causyne_3d');
|
|
14
|
+
if (!fsSync.existsSync(dir)) {
|
|
15
|
+
fsSync.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const visionInspector = {
|
|
21
|
+
schema: {
|
|
22
|
+
name: 'vision.inspect_ui',
|
|
23
|
+
description: 'Captures live desktop (macOS/Windows) or mobile (Android via ADB) screenshots directly into the local workspace buffer (.tmp_causyne_3d/) and extracts spatial bounding box coordinates for visual layout verification, LCP debugging, and UI automation.',
|
|
24
|
+
parameters: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
target: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
enum: ['desktop', 'android_adb'],
|
|
30
|
+
description: 'Target device screen to capture: desktop (OS GUI) or android_adb (connected mobile screen).'
|
|
31
|
+
},
|
|
32
|
+
prompt: { type: 'string', description: 'The verification prompt describing what UI element, visual bug, alignment, or LCP state is being audited.' },
|
|
33
|
+
deviceSerial: { type: 'string', description: 'Optional Android ADB device serial when target is android_adb.' }
|
|
34
|
+
},
|
|
35
|
+
required: ['target', 'prompt']
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async execute(args, context = {}) {
|
|
40
|
+
try {
|
|
41
|
+
const target = args.target || 'desktop';
|
|
42
|
+
const prompt = args.prompt || 'Inspect UI spatial layout';
|
|
43
|
+
const tempDir = getWorkspaceTempDir();
|
|
44
|
+
const timestamp = Date.now();
|
|
45
|
+
const destPath = path.join(tempDir, `ui_inspect_${target}_${timestamp}.png`);
|
|
46
|
+
|
|
47
|
+
if (target === 'android_adb') {
|
|
48
|
+
const adbArgs = [];
|
|
49
|
+
if (args.deviceSerial) adbArgs.push('-s', args.deviceSerial);
|
|
50
|
+
adbArgs.push('shell', 'screencap', '-p');
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = await execFileAsync('adb', adbArgs, { encoding: 'buffer', maxBuffer: 20 * 1024 * 1024 });
|
|
54
|
+
// Handle CRLF on Windows ADB pipes
|
|
55
|
+
let cleanBuffer = stdout;
|
|
56
|
+
if (process.platform === 'win32') {
|
|
57
|
+
cleanBuffer = Buffer.from(stdout.toString('binary').replace(/\r\n/g, '\n'), 'binary');
|
|
58
|
+
}
|
|
59
|
+
await fs.writeFile(destPath, cleanBuffer);
|
|
60
|
+
} catch (adbErr) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: `ADB Mobile Vision Capture Failed: ${adbErr.message}. Ensure device is connected via ADB USB/WiFi.`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// Desktop OS Screen Capture
|
|
68
|
+
try {
|
|
69
|
+
if (process.platform === 'darwin') {
|
|
70
|
+
await execFileAsync('screencapture', ['-x', destPath]);
|
|
71
|
+
} else if (process.platform === 'win32') {
|
|
72
|
+
const psCmd = `Add-Type -AssemblyName System.Windows.Forms,System.Drawing; $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds; $bitmap = New-Object System.Drawing.Bitmap $screen.Width, $screen.Height; $graphics = [System.Drawing.Graphics]::FromImage($bitmap); $graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size); $bitmap.Save('${destPath.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png); $graphics.Dispose(); $bitmap.Dispose();`;
|
|
73
|
+
await execFileAsync('powershell.exe', ['-NoLogo', '-NoProfile', '-Command', psCmd]);
|
|
74
|
+
} else {
|
|
75
|
+
// macOS and Windows capture natively above; only this branch needs
|
|
76
|
+
// the provisioned package.
|
|
77
|
+
const vision = await ensureCapability('vision');
|
|
78
|
+
if (!vision.ok) throw new Error(vision.reason);
|
|
79
|
+
const screenshotDesktop = await loadCapabilityPackage('screenshot-desktop');
|
|
80
|
+
await screenshotDesktop({ filename: destPath });
|
|
81
|
+
}
|
|
82
|
+
} catch (captureErr) {
|
|
83
|
+
|
|
84
|
+
if (captureErr.message && (captureErr.message.includes('could not create image from display') || captureErr.message.includes('No display') || captureErr.message.includes('xclip'))) {
|
|
85
|
+
// Check if any existing buffer image exists in tempDir to inspect
|
|
86
|
+
const existingFiles = fsSync.readdirSync(tempDir).filter(f => f.endsWith('.png'));
|
|
87
|
+
if (existingFiles.length > 0) {
|
|
88
|
+
const fallbackFile = path.join(tempDir, existingFiles[existingFiles.length - 1]);
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
output: JSON.stringify({
|
|
92
|
+
status: 'Headless environment detected: GUI capture restricted. Returned latest buffered workspace visual frame.',
|
|
93
|
+
target_device: target,
|
|
94
|
+
verification_prompt: prompt,
|
|
95
|
+
filepath: fallbackFile,
|
|
96
|
+
workspace_boundary_safe: true,
|
|
97
|
+
headless_notice: captureErr.message
|
|
98
|
+
}, null, 2)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
output: JSON.stringify({
|
|
104
|
+
status: 'Live GUI capture is unavailable in this desktop session. Connect an active display or use an ADB mobile target.',
|
|
105
|
+
target_device: target,
|
|
106
|
+
verification_prompt: prompt,
|
|
107
|
+
headless_notice: captureErr.message
|
|
108
|
+
}, null, 2)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
throw captureErr;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check spatial OCR / bounding box extraction
|
|
116
|
+
let textMap = '';
|
|
117
|
+
try {
|
|
118
|
+
const ocr = await ensureCapability('ocr');
|
|
119
|
+
if (!ocr.ok) throw new Error(ocr.reason);
|
|
120
|
+
const Tesseract = await loadCapabilityPackage('tesseract.js');
|
|
121
|
+
const worker = await Tesseract.createWorker('eng');
|
|
122
|
+
const { data: { words } } = await worker.recognize(destPath);
|
|
123
|
+
await worker.terminate();
|
|
124
|
+
|
|
125
|
+
const confidentWords = (words || []).filter(w => w.confidence > 75).slice(0, 30);
|
|
126
|
+
if (confidentWords.length > 0) {
|
|
127
|
+
textMap = confidentWords.map(w => {
|
|
128
|
+
const centerX = Math.floor((w.bbox.x0 + w.bbox.x1) / 2);
|
|
129
|
+
const centerY = Math.floor((w.bbox.y0 + w.bbox.y1) / 2);
|
|
130
|
+
return `- [${w.text}] X:${centerX}, Y:${centerY}`;
|
|
131
|
+
}).join('\n');
|
|
132
|
+
}
|
|
133
|
+
} catch (ocrErr) {
|
|
134
|
+
textMap = `[OCR Text Bounding extraction skipped: ${ocrErr.message}]`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Route notification to mobile app (`AgentScreen.kt`)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
output: JSON.stringify({
|
|
142
|
+
status: 'Visual inspection frame captured cleanly inside workspace buffer',
|
|
143
|
+
target_device: target,
|
|
144
|
+
verification_prompt: prompt,
|
|
145
|
+
filepath: destPath,
|
|
146
|
+
workspace_boundary_safe: true,
|
|
147
|
+
detected_spatial_elements: textMap || 'No confident OCR text tokens detected.'
|
|
148
|
+
}, null, 2)
|
|
149
|
+
};
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return { success: false, error: e.message };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
// Phase 3 AGI Upgrade: HTML Cleanup to Markdown
|
|
6
|
+
function htmlToText(html) {
|
|
7
|
+
// AGI Optimization: Aggressively strip scripts, styles, svg, and tags
|
|
8
|
+
let text = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
9
|
+
text = text.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
|
|
10
|
+
text = text.replace(/<svg\b[^<]*(?:(?!<\/svg>)<[^<]*)*<\/svg>/gi, '');
|
|
11
|
+
text = text.replace(/<[^>]+>/g, ' '); // Strip HTML tags
|
|
12
|
+
text = text.replace(/ /g, ' '); // Decode basic entities
|
|
13
|
+
text = text.replace(/\s{2,}/g, ' '); // Normalize spaces
|
|
14
|
+
text = text.replace(/\n\s*\n/g, '\n\n'); // Normalize line breaks
|
|
15
|
+
return text.trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const webFetch = {
|
|
19
|
+
schema: {
|
|
20
|
+
name: 'web.fetch',
|
|
21
|
+
description: 'Make HTTP requests. Automatically strips raw HTML into clean text to protect Context Window.',
|
|
22
|
+
parameters: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
url: { type: 'string' },
|
|
26
|
+
method: { type: 'string' },
|
|
27
|
+
headers: { type: 'object' },
|
|
28
|
+
body: { type: 'string' },
|
|
29
|
+
outputFile: { type: 'string', description: 'If provided, saves as a binary file instead of returning text' }
|
|
30
|
+
},
|
|
31
|
+
required: ['url']
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
async execute(args) {
|
|
35
|
+
try {
|
|
36
|
+
const options = {
|
|
37
|
+
method: args.method || 'GET',
|
|
38
|
+
headers: {
|
|
39
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/120.0.0.0 Safari/537.36',
|
|
40
|
+
...args.headers
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (args.body) options.body = args.body;
|
|
45
|
+
|
|
46
|
+
const response = await fetch(args.url, options);
|
|
47
|
+
|
|
48
|
+
if (args.outputFile) {
|
|
49
|
+
const target = args.outputFile.replace(/^~\//, os.homedir() + '/');
|
|
50
|
+
const buffer = await response.arrayBuffer();
|
|
51
|
+
await fs.writeFile(target, Buffer.from(buffer));
|
|
52
|
+
return { success: true, output: `Downloaded ${buffer.byteLength} bytes to ${args.outputFile}`, status: response.status };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const text = await response.text();
|
|
56
|
+
|
|
57
|
+
// Phase 3 AGI Upgrade: Clean HTML unless it's an API JSON response
|
|
58
|
+
let cleanOutput = text;
|
|
59
|
+
const contentType = response.headers.get('content-type') || '';
|
|
60
|
+
if (contentType.includes('text/html')) {
|
|
61
|
+
cleanOutput = htmlToText(text);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Strict Context Paging Protection
|
|
65
|
+
if (cleanOutput.length > 50000) {
|
|
66
|
+
cleanOutput = cleanOutput.substring(0, 50000) + '\n\n[... OUTPUT TRUNCATED TO 50KB ...]';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { success: true, output: cleanOutput, status: response.status };
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return { success: false, error: e.message };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const webSearch = {
|
|
2
|
+
schema: {
|
|
3
|
+
name: 'web_search',
|
|
4
|
+
description: 'High-Precision SERP Router. Searches the web and extracts top organic results.',
|
|
5
|
+
parameters: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
query: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
description: 'The search query.'
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
required: ['query']
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
execute: async (args) => {
|
|
18
|
+
try {
|
|
19
|
+
// Phase 7 AGI Upgrade: Zero-Dependency Native Fetch Scraper
|
|
20
|
+
// Skipping Google's aggressive 429 Blocks by utilizing Yahoo Search HTML parsing.
|
|
21
|
+
const url = 'https://search.yahoo.com/search?p=' + encodeURIComponent(args.query);
|
|
22
|
+
|
|
23
|
+
const response = await fetch(url, {
|
|
24
|
+
headers: {
|
|
25
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
|
26
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
|
27
|
+
'Accept-Language': 'en-US,en;q=0.5'
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`HTTP Error ${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const html = await response.text();
|
|
36
|
+
const results = [];
|
|
37
|
+
|
|
38
|
+
// Robust Regex to extract Yahoo organic results
|
|
39
|
+
const regex = /<div class=\"compTitle[^>]*>.*?<h3[^>]*>.*?<a[^>]*href=\"([^\"]+)\"[^>]*>(.*?)<\/a>.*?<\/h3>.*?<div class=\"compText[^>]*>(.*?)<\/div>/gs;
|
|
40
|
+
|
|
41
|
+
let match;
|
|
42
|
+
while ((match = regex.exec(html)) !== null && results.length < 8) {
|
|
43
|
+
let link = match[1];
|
|
44
|
+
|
|
45
|
+
// Yahoo heavily redirects via RU=..., decode it to get the raw clean URL
|
|
46
|
+
if (link.includes('RU=')) {
|
|
47
|
+
const ruMatch = link.match(/RU=([^/]+)\//);
|
|
48
|
+
if (ruMatch) link = decodeURIComponent(ruMatch[1]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Skip sponsored Bing/Yahoo Ads
|
|
52
|
+
if (link.includes('bing.com/aclick') || link.includes('yahoo.com/yhs/')) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
results.push({
|
|
57
|
+
url: link,
|
|
58
|
+
title: match[2].replace(/<[^>]+>/g, '').trim(),
|
|
59
|
+
snippet: match[3].replace(/<[^>]+>/g, '').trim()
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (results.length === 0) {
|
|
64
|
+
return { success: true, output: "No organic results found. Try using web.fetch on known direct domains." };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let outputString = `🔍 SERP Results for "${args.query}":\n\n`;
|
|
68
|
+
outputString += `[TOP WEB RESULTS]\n`;
|
|
69
|
+
results.forEach((r, idx) => {
|
|
70
|
+
outputString += `${idx + 1}. ${r.title}\n URL: ${r.url}\n Snippet: ${r.snippet}\n\n`;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return { success: true, output: outputString.trim() };
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: `Search Engine Blocked Request: ${error.message}. Do NOT guess random API URLs. Instead, use the web_fetch tool directly on known sites (e.g. finance.yahoo.com, coinmarketcap.com, wikipedia.org).`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|