blockmine 1.23.4 → 1.24.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/.claude/settings.json +1 -1
- package/.claude/settings.local.json +21 -1
- package/.claude/skills/node-development/SKILL.md +317 -0
- package/.claude/skills/skill-rules.json +49 -0
- package/CHANGELOG.md +50 -32
- package/backend/src/ai/plugin-assistant-system-prompt.md +61 -1
- package/backend/src/api/routes/aiAssistant.js +562 -103
- package/backend/src/api/routes/botUsers.js +30 -10
- package/backend/src/api/routes/bots.js +99 -21
- package/backend/src/core/BotProcess.js +41 -5
- package/backend/src/core/GraphExecutionEngine.js +9 -2
- package/backend/src/core/PluginHooks.js +221 -0
- package/backend/src/core/PluginManager.js +10 -0
- package/backend/src/core/node-registries/container.js +162 -0
- package/backend/src/core/node-registries/furnace.js +143 -0
- package/backend/src/core/node-registries/inventory.js +181 -0
- package/backend/src/core/node-registries/navigation.js +111 -0
- package/backend/src/core/nodes/container/close.js +26 -0
- package/backend/src/core/nodes/container/deposit.js +88 -0
- package/backend/src/core/nodes/container/deposit_all.js +71 -0
- package/backend/src/core/nodes/container/find_item.js +77 -0
- package/backend/src/core/nodes/container/get_items.js +51 -0
- package/backend/src/core/nodes/container/open.js +111 -0
- package/backend/src/core/nodes/container/withdraw.js +91 -0
- package/backend/src/core/nodes/furnace/close.js +24 -0
- package/backend/src/core/nodes/furnace/get_status.js +74 -0
- package/backend/src/core/nodes/furnace/open.js +110 -0
- package/backend/src/core/nodes/furnace/put_fuel.js +64 -0
- package/backend/src/core/nodes/furnace/put_input.js +64 -0
- package/backend/src/core/nodes/furnace/take_output.js +69 -0
- package/backend/src/core/nodes/inventory/count_item.js +28 -0
- package/backend/src/core/nodes/inventory/drop.js +65 -0
- package/backend/src/core/nodes/inventory/equip.js +45 -0
- package/backend/src/core/nodes/inventory/find_item.js +55 -0
- package/backend/src/core/nodes/inventory/get_all.js +45 -0
- package/backend/src/core/nodes/inventory/get_held_item.js +63 -0
- package/backend/src/core/nodes/inventory/get_slot.js +46 -0
- package/backend/src/core/nodes/inventory/has_item.js +35 -0
- package/backend/src/core/nodes/inventory/select_slot.js +46 -0
- package/backend/src/core/nodes/navigation/follow.js +51 -0
- package/backend/src/core/nodes/navigation/go_to.js +53 -0
- package/backend/src/core/nodes/navigation/go_to_entity.js +69 -0
- package/backend/src/core/nodes/navigation/go_to_player.js +70 -0
- package/backend/src/core/nodes/navigation/stop.js +26 -0
- package/backend/src/core/services/MinecraftViewerService.js +2 -0
- package/frontend/dist/assets/index-BC-NbKXi.css +32 -0
- package/frontend/dist/assets/{index-DqzDkFsP.js → index-DqJXZMHY.js} +2024 -1968
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/minecraft-assets/items/acacia_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/acacia_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/acacia_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/acacia_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/acacia_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/amethyst_shard.png +0 -0
- package/frontend/dist/minecraft-assets/items/angler_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/apple.png +0 -0
- package/frontend/dist/minecraft-assets/items/archer_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/armor_stand.png +0 -0
- package/frontend/dist/minecraft-assets/items/arms_up_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/arrow.png +0 -0
- package/frontend/dist/minecraft-assets/items/axolotl_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/baked_potato.png +0 -0
- package/frontend/dist/minecraft-assets/items/bamboo.png +0 -0
- package/frontend/dist/minecraft-assets/items/bamboo_chest_raft.png +0 -0
- package/frontend/dist/minecraft-assets/items/bamboo_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/bamboo_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/bamboo_raft.png +0 -0
- package/frontend/dist/minecraft-assets/items/bamboo_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/barrier.png +0 -0
- package/frontend/dist/minecraft-assets/items/beef.png +0 -0
- package/frontend/dist/minecraft-assets/items/beetroot.png +0 -0
- package/frontend/dist/minecraft-assets/items/beetroot_seeds.png +0 -0
- package/frontend/dist/minecraft-assets/items/beetroot_soup.png +0 -0
- package/frontend/dist/minecraft-assets/items/bell.png +0 -0
- package/frontend/dist/minecraft-assets/items/birch_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/birch_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/birch_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/birch_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/birch_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/black_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/black_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/blade_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/blaze_powder.png +0 -0
- package/frontend/dist/minecraft-assets/items/blaze_rod.png +0 -0
- package/frontend/dist/minecraft-assets/items/blue_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/blue_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/bone.png +0 -0
- package/frontend/dist/minecraft-assets/items/bone_meal.png +0 -0
- package/frontend/dist/minecraft-assets/items/book.png +0 -0
- package/frontend/dist/minecraft-assets/items/bow.png +0 -0
- package/frontend/dist/minecraft-assets/items/bow_pulling_0.png +0 -0
- package/frontend/dist/minecraft-assets/items/bow_pulling_1.png +0 -0
- package/frontend/dist/minecraft-assets/items/bow_pulling_2.png +0 -0
- package/frontend/dist/minecraft-assets/items/bowl.png +0 -0
- package/frontend/dist/minecraft-assets/items/bread.png +0 -0
- package/frontend/dist/minecraft-assets/items/brewer_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/brewing_stand.png +0 -0
- package/frontend/dist/minecraft-assets/items/brick.png +0 -0
- package/frontend/dist/minecraft-assets/items/broken_elytra.png +0 -0
- package/frontend/dist/minecraft-assets/items/brown_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/brown_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/brush.png +0 -0
- package/frontend/dist/minecraft-assets/items/bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/bundle.png +0 -0
- package/frontend/dist/minecraft-assets/items/bundle_filled.png +0 -0
- package/frontend/dist/minecraft-assets/items/burn_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/cake.png +0 -0
- package/frontend/dist/minecraft-assets/items/campfire.png +0 -0
- package/frontend/dist/minecraft-assets/items/candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/carrot.png +0 -0
- package/frontend/dist/minecraft-assets/items/carrot_on_a_stick.png +0 -0
- package/frontend/dist/minecraft-assets/items/cauldron.png +0 -0
- package/frontend/dist/minecraft-assets/items/chain.png +0 -0
- package/frontend/dist/minecraft-assets/items/chainmail_boots.png +0 -0
- package/frontend/dist/minecraft-assets/items/chainmail_chestplate.png +0 -0
- package/frontend/dist/minecraft-assets/items/chainmail_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/chainmail_leggings.png +0 -0
- package/frontend/dist/minecraft-assets/items/charcoal.png +0 -0
- package/frontend/dist/minecraft-assets/items/cherry_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/cherry_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/cherry_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/cherry_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/cherry_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/chest_minecart.png +0 -0
- package/frontend/dist/minecraft-assets/items/chicken.png +0 -0
- package/frontend/dist/minecraft-assets/items/chorus_fruit.png +0 -0
- package/frontend/dist/minecraft-assets/items/clay_ball.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_00.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_01.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_02.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_03.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_04.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_05.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_06.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_07.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_08.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_09.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_10.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_11.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_12.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_13.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_14.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_15.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_16.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_17.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_18.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_19.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_20.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_21.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_22.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_23.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_24.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_25.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_26.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_27.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_28.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_29.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_30.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_31.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_32.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_33.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_34.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_35.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_36.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_37.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_38.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_39.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_40.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_41.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_42.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_43.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_44.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_45.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_46.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_47.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_48.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_49.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_50.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_51.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_52.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_53.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_54.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_55.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_56.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_57.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_58.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_59.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_60.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_61.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_62.png +0 -0
- package/frontend/dist/minecraft-assets/items/clock_63.png +0 -0
- package/frontend/dist/minecraft-assets/items/coal.png +0 -0
- package/frontend/dist/minecraft-assets/items/coast_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/cocoa_beans.png +0 -0
- package/frontend/dist/minecraft-assets/items/cod.png +0 -0
- package/frontend/dist/minecraft-assets/items/cod_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/command_block_minecart.png +0 -0
- package/frontend/dist/minecraft-assets/items/comparator.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_00.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_01.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_02.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_03.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_04.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_05.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_06.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_07.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_08.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_09.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_10.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_11.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_12.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_13.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_14.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_15.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_16.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_17.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_18.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_19.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_20.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_21.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_22.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_23.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_24.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_25.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_26.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_27.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_28.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_29.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_30.png +0 -0
- package/frontend/dist/minecraft-assets/items/compass_31.png +0 -0
- package/frontend/dist/minecraft-assets/items/cooked_beef.png +0 -0
- package/frontend/dist/minecraft-assets/items/cooked_chicken.png +0 -0
- package/frontend/dist/minecraft-assets/items/cooked_cod.png +0 -0
- package/frontend/dist/minecraft-assets/items/cooked_mutton.png +0 -0
- package/frontend/dist/minecraft-assets/items/cooked_porkchop.png +0 -0
- package/frontend/dist/minecraft-assets/items/cooked_rabbit.png +0 -0
- package/frontend/dist/minecraft-assets/items/cooked_salmon.png +0 -0
- package/frontend/dist/minecraft-assets/items/cookie.png +0 -0
- package/frontend/dist/minecraft-assets/items/copper_ingot.png +0 -0
- package/frontend/dist/minecraft-assets/items/creeper_banner_pattern.png +0 -0
- package/frontend/dist/minecraft-assets/items/crimson_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/crimson_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/crimson_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/crossbow_arrow.png +0 -0
- package/frontend/dist/minecraft-assets/items/crossbow_firework.png +0 -0
- package/frontend/dist/minecraft-assets/items/crossbow_pulling_0.png +0 -0
- package/frontend/dist/minecraft-assets/items/crossbow_pulling_1.png +0 -0
- package/frontend/dist/minecraft-assets/items/crossbow_pulling_2.png +0 -0
- package/frontend/dist/minecraft-assets/items/crossbow_standby.png +0 -0
- package/frontend/dist/minecraft-assets/items/cyan_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/cyan_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/danger_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/dark_oak_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/dark_oak_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/dark_oak_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/dark_oak_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/dark_oak_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_axe.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_boots.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_chestplate.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_hoe.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_horse_armor.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_leggings.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_pickaxe.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_shovel.png +0 -0
- package/frontend/dist/minecraft-assets/items/diamond_sword.png +0 -0
- package/frontend/dist/minecraft-assets/items/disc_fragment_5.png +0 -0
- package/frontend/dist/minecraft-assets/items/dragon_breath.png +0 -0
- package/frontend/dist/minecraft-assets/items/dried_kelp.png +0 -0
- package/frontend/dist/minecraft-assets/items/dune_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/echo_shard.png +0 -0
- package/frontend/dist/minecraft-assets/items/egg.png +0 -0
- package/frontend/dist/minecraft-assets/items/elytra.png +0 -0
- package/frontend/dist/minecraft-assets/items/emerald.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_armor_slot_boots.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_armor_slot_chestplate.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_armor_slot_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_armor_slot_leggings.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_armor_slot_shield.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_amethyst_shard.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_axe.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_diamond.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_emerald.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_hoe.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_ingot.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_lapis_lazuli.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_pickaxe.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_quartz.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_redstone_dust.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_shovel.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_smithing_template_armor_trim.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_smithing_template_netherite_upgrade.png +0 -0
- package/frontend/dist/minecraft-assets/items/empty_slot_sword.png +0 -0
- package/frontend/dist/minecraft-assets/items/enchanted_book.png +0 -0
- package/frontend/dist/minecraft-assets/items/end_crystal.png +0 -0
- package/frontend/dist/minecraft-assets/items/ender_eye.png +0 -0
- package/frontend/dist/minecraft-assets/items/ender_pearl.png +0 -0
- package/frontend/dist/minecraft-assets/items/experience_bottle.png +0 -0
- package/frontend/dist/minecraft-assets/items/explorer_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/eye_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/feather.png +0 -0
- package/frontend/dist/minecraft-assets/items/fermented_spider_eye.png +0 -0
- package/frontend/dist/minecraft-assets/items/filled_map.png +0 -0
- package/frontend/dist/minecraft-assets/items/filled_map_markings.png +0 -0
- package/frontend/dist/minecraft-assets/items/fire_charge.png +0 -0
- package/frontend/dist/minecraft-assets/items/firework_rocket.png +0 -0
- package/frontend/dist/minecraft-assets/items/firework_star.png +0 -0
- package/frontend/dist/minecraft-assets/items/firework_star_overlay.png +0 -0
- package/frontend/dist/minecraft-assets/items/fishing_rod.png +0 -0
- package/frontend/dist/minecraft-assets/items/fishing_rod_cast.png +0 -0
- package/frontend/dist/minecraft-assets/items/flint.png +0 -0
- package/frontend/dist/minecraft-assets/items/flint_and_steel.png +0 -0
- package/frontend/dist/minecraft-assets/items/flower_banner_pattern.png +0 -0
- package/frontend/dist/minecraft-assets/items/flower_pot.png +0 -0
- package/frontend/dist/minecraft-assets/items/friend_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/furnace_minecart.png +0 -0
- package/frontend/dist/minecraft-assets/items/ghast_tear.png +0 -0
- package/frontend/dist/minecraft-assets/items/glass_bottle.png +0 -0
- package/frontend/dist/minecraft-assets/items/glistering_melon_slice.png +0 -0
- package/frontend/dist/minecraft-assets/items/globe_banner_pattern.png +0 -0
- package/frontend/dist/minecraft-assets/items/glow_berries.png +0 -0
- package/frontend/dist/minecraft-assets/items/glow_ink_sac.png +0 -0
- package/frontend/dist/minecraft-assets/items/glow_item_frame.png +0 -0
- package/frontend/dist/minecraft-assets/items/glowstone_dust.png +0 -0
- package/frontend/dist/minecraft-assets/items/goat_horn.png +0 -0
- package/frontend/dist/minecraft-assets/items/gold_ingot.png +0 -0
- package/frontend/dist/minecraft-assets/items/gold_nugget.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_apple.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_axe.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_boots.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_carrot.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_chestplate.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_hoe.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_horse_armor.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_leggings.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_pickaxe.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_shovel.png +0 -0
- package/frontend/dist/minecraft-assets/items/golden_sword.png +0 -0
- package/frontend/dist/minecraft-assets/items/gray_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/gray_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/green_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/green_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/gunpowder.png +0 -0
- package/frontend/dist/minecraft-assets/items/heart_of_the_sea.png +0 -0
- package/frontend/dist/minecraft-assets/items/heart_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/heartbreak_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/honey_bottle.png +0 -0
- package/frontend/dist/minecraft-assets/items/honeycomb.png +0 -0
- package/frontend/dist/minecraft-assets/items/hopper.png +0 -0
- package/frontend/dist/minecraft-assets/items/hopper_minecart.png +0 -0
- package/frontend/dist/minecraft-assets/items/host_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/howl_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/ink_sac.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_axe.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_boots.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_chestplate.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_hoe.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_horse_armor.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_ingot.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_leggings.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_nugget.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_pickaxe.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_shovel.png +0 -0
- package/frontend/dist/minecraft-assets/items/iron_sword.png +0 -0
- package/frontend/dist/minecraft-assets/items/item_frame.png +0 -0
- package/frontend/dist/minecraft-assets/items/jungle_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/jungle_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/jungle_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/jungle_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/jungle_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/kelp.png +0 -0
- package/frontend/dist/minecraft-assets/items/knowledge_book.png +0 -0
- package/frontend/dist/minecraft-assets/items/lantern.png +0 -0
- package/frontend/dist/minecraft-assets/items/lapis_lazuli.png +0 -0
- package/frontend/dist/minecraft-assets/items/lava_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/lead.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_boots.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_boots_overlay.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_chestplate.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_chestplate_overlay.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_helmet_overlay.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_horse_armor.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_leggings.png +0 -0
- package/frontend/dist/minecraft-assets/items/leather_leggings_overlay.png +0 -0
- package/frontend/dist/minecraft-assets/items/light.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_00.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_01.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_02.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_03.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_04.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_05.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_06.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_07.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_08.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_09.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_10.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_11.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_12.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_13.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_14.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_15.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_blue_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_blue_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_gray_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/light_gray_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/lime_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/lime_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/lingering_potion.png +0 -0
- package/frontend/dist/minecraft-assets/items/magenta_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/magenta_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/magma_cream.png +0 -0
- package/frontend/dist/minecraft-assets/items/mangrove_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/mangrove_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/mangrove_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/mangrove_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/mangrove_propagule.png +0 -0
- package/frontend/dist/minecraft-assets/items/mangrove_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/map.png +0 -0
- package/frontend/dist/minecraft-assets/items/melon_seeds.png +0 -0
- package/frontend/dist/minecraft-assets/items/melon_slice.png +0 -0
- package/frontend/dist/minecraft-assets/items/milk_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/minecart.png +0 -0
- package/frontend/dist/minecraft-assets/items/miner_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/mojang_banner_pattern.png +0 -0
- package/frontend/dist/minecraft-assets/items/mourner_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/mushroom_stew.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_11.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_13.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_5.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_blocks.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_cat.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_chirp.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_far.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_mall.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_mellohi.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_otherside.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_pigstep.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_relic.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_stal.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_strad.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_wait.png +0 -0
- package/frontend/dist/minecraft-assets/items/music_disc_ward.png +0 -0
- package/frontend/dist/minecraft-assets/items/mutton.png +0 -0
- package/frontend/dist/minecraft-assets/items/name_tag.png +0 -0
- package/frontend/dist/minecraft-assets/items/nautilus_shell.png +0 -0
- package/frontend/dist/minecraft-assets/items/nether_brick.png +0 -0
- package/frontend/dist/minecraft-assets/items/nether_sprouts.png +0 -0
- package/frontend/dist/minecraft-assets/items/nether_star.png +0 -0
- package/frontend/dist/minecraft-assets/items/nether_wart.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_axe.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_boots.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_chestplate.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_hoe.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_ingot.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_leggings.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_pickaxe.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_scrap.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_shovel.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_sword.png +0 -0
- package/frontend/dist/minecraft-assets/items/netherite_upgrade_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/oak_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/oak_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/oak_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/oak_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/oak_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/orange_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/orange_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/painting.png +0 -0
- package/frontend/dist/minecraft-assets/items/paper.png +0 -0
- package/frontend/dist/minecraft-assets/items/phantom_membrane.png +0 -0
- package/frontend/dist/minecraft-assets/items/piglin_banner_pattern.png +0 -0
- package/frontend/dist/minecraft-assets/items/pink_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/pink_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/pink_petals.png +0 -0
- package/frontend/dist/minecraft-assets/items/pitcher_plant.png +0 -0
- package/frontend/dist/minecraft-assets/items/pitcher_pod.png +0 -0
- package/frontend/dist/minecraft-assets/items/plenty_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/pointed_dripstone.png +0 -0
- package/frontend/dist/minecraft-assets/items/poisonous_potato.png +0 -0
- package/frontend/dist/minecraft-assets/items/popped_chorus_fruit.png +0 -0
- package/frontend/dist/minecraft-assets/items/porkchop.png +0 -0
- package/frontend/dist/minecraft-assets/items/potato.png +0 -0
- package/frontend/dist/minecraft-assets/items/potion.png +0 -0
- package/frontend/dist/minecraft-assets/items/potion_overlay.png +0 -0
- package/frontend/dist/minecraft-assets/items/powder_snow_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/prismarine_crystals.png +0 -0
- package/frontend/dist/minecraft-assets/items/prismarine_shard.png +0 -0
- package/frontend/dist/minecraft-assets/items/prize_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/pufferfish.png +0 -0
- package/frontend/dist/minecraft-assets/items/pufferfish_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/pumpkin_pie.png +0 -0
- package/frontend/dist/minecraft-assets/items/pumpkin_seeds.png +0 -0
- package/frontend/dist/minecraft-assets/items/purple_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/purple_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/quartz.png +0 -0
- package/frontend/dist/minecraft-assets/items/rabbit.png +0 -0
- package/frontend/dist/minecraft-assets/items/rabbit_foot.png +0 -0
- package/frontend/dist/minecraft-assets/items/rabbit_hide.png +0 -0
- package/frontend/dist/minecraft-assets/items/rabbit_stew.png +0 -0
- package/frontend/dist/minecraft-assets/items/raiser_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/raw_copper.png +0 -0
- package/frontend/dist/minecraft-assets/items/raw_gold.png +0 -0
- package/frontend/dist/minecraft-assets/items/raw_iron.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_00.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_01.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_02.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_03.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_04.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_05.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_06.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_07.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_08.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_09.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_10.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_11.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_12.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_13.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_14.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_15.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_16.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_17.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_18.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_19.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_20.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_21.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_22.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_23.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_24.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_25.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_26.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_27.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_28.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_29.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_30.png +0 -0
- package/frontend/dist/minecraft-assets/items/recovery_compass_31.png +0 -0
- package/frontend/dist/minecraft-assets/items/red_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/red_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/redstone.png +0 -0
- package/frontend/dist/minecraft-assets/items/repeater.png +0 -0
- package/frontend/dist/minecraft-assets/items/rib_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/rotten_flesh.png +0 -0
- package/frontend/dist/minecraft-assets/items/saddle.png +0 -0
- package/frontend/dist/minecraft-assets/items/salmon.png +0 -0
- package/frontend/dist/minecraft-assets/items/salmon_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/scute.png +0 -0
- package/frontend/dist/minecraft-assets/items/sea_pickle.png +0 -0
- package/frontend/dist/minecraft-assets/items/seagrass.png +0 -0
- package/frontend/dist/minecraft-assets/items/sentry_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/shaper_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/sheaf_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/shears.png +0 -0
- package/frontend/dist/minecraft-assets/items/shelter_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/shulker_shell.png +0 -0
- package/frontend/dist/minecraft-assets/items/silence_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/skull_banner_pattern.png +0 -0
- package/frontend/dist/minecraft-assets/items/skull_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/slime_ball.png +0 -0
- package/frontend/dist/minecraft-assets/items/sniffer_egg.png +0 -0
- package/frontend/dist/minecraft-assets/items/snort_pottery_sherd.png +0 -0
- package/frontend/dist/minecraft-assets/items/snout_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/snowball.png +0 -0
- package/frontend/dist/minecraft-assets/items/soul_campfire.png +0 -0
- package/frontend/dist/minecraft-assets/items/soul_lantern.png +0 -0
- package/frontend/dist/minecraft-assets/items/spawn_egg.png +0 -0
- package/frontend/dist/minecraft-assets/items/spawn_egg_overlay.png +0 -0
- package/frontend/dist/minecraft-assets/items/spectral_arrow.png +0 -0
- package/frontend/dist/minecraft-assets/items/spider_eye.png +0 -0
- package/frontend/dist/minecraft-assets/items/spire_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/splash_potion.png +0 -0
- package/frontend/dist/minecraft-assets/items/spruce_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/spruce_chest_boat.png +0 -0
- package/frontend/dist/minecraft-assets/items/spruce_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/spruce_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/spruce_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/spyglass.png +0 -0
- package/frontend/dist/minecraft-assets/items/spyglass_model.png +0 -0
- package/frontend/dist/minecraft-assets/items/stick.png +0 -0
- package/frontend/dist/minecraft-assets/items/stone_axe.png +0 -0
- package/frontend/dist/minecraft-assets/items/stone_hoe.png +0 -0
- package/frontend/dist/minecraft-assets/items/stone_pickaxe.png +0 -0
- package/frontend/dist/minecraft-assets/items/stone_shovel.png +0 -0
- package/frontend/dist/minecraft-assets/items/stone_sword.png +0 -0
- package/frontend/dist/minecraft-assets/items/string.png +0 -0
- package/frontend/dist/minecraft-assets/items/structure_void.png +0 -0
- package/frontend/dist/minecraft-assets/items/sugar.png +0 -0
- package/frontend/dist/minecraft-assets/items/sugar_cane.png +0 -0
- package/frontend/dist/minecraft-assets/items/suspicious_stew.png +0 -0
- package/frontend/dist/minecraft-assets/items/sweet_berries.png +0 -0
- package/frontend/dist/minecraft-assets/items/tadpole_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/tide_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/tipped_arrow_base.png +0 -0
- package/frontend/dist/minecraft-assets/items/tipped_arrow_head.png +0 -0
- package/frontend/dist/minecraft-assets/items/tnt_minecart.png +0 -0
- package/frontend/dist/minecraft-assets/items/torchflower_seeds.png +0 -0
- package/frontend/dist/minecraft-assets/items/totem_of_undying.png +0 -0
- package/frontend/dist/minecraft-assets/items/trident.png +0 -0
- package/frontend/dist/minecraft-assets/items/tropical_fish.png +0 -0
- package/frontend/dist/minecraft-assets/items/tropical_fish_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/turtle_egg.png +0 -0
- package/frontend/dist/minecraft-assets/items/turtle_helmet.png +0 -0
- package/frontend/dist/minecraft-assets/items/vex_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/ward_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/warped_door.png +0 -0
- package/frontend/dist/minecraft-assets/items/warped_fungus_on_a_stick.png +0 -0
- package/frontend/dist/minecraft-assets/items/warped_hanging_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/warped_sign.png +0 -0
- package/frontend/dist/minecraft-assets/items/water_bucket.png +0 -0
- package/frontend/dist/minecraft-assets/items/wayfinder_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/wheat.png +0 -0
- package/frontend/dist/minecraft-assets/items/wheat_seeds.png +0 -0
- package/frontend/dist/minecraft-assets/items/white_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/white_dye.png +0 -0
- package/frontend/dist/minecraft-assets/items/wild_armor_trim_smithing_template.png +0 -0
- package/frontend/dist/minecraft-assets/items/wooden_axe.png +0 -0
- package/frontend/dist/minecraft-assets/items/wooden_hoe.png +0 -0
- package/frontend/dist/minecraft-assets/items/wooden_pickaxe.png +0 -0
- package/frontend/dist/minecraft-assets/items/wooden_shovel.png +0 -0
- package/frontend/dist/minecraft-assets/items/wooden_sword.png +0 -0
- package/frontend/dist/minecraft-assets/items/writable_book.png +0 -0
- package/frontend/dist/minecraft-assets/items/written_book.png +0 -0
- package/frontend/dist/minecraft-assets/items/yellow_candle.png +0 -0
- package/frontend/dist/minecraft-assets/items/yellow_dye.png +0 -0
- package/package.json +1 -1
- package/frontend/dist/assets/index-t6K1u4OV.css +0 -32
- package/screen/console.png +0 -0
- package/screen/dashboard.png +0 -0
- package/screen/graph_collabe.png +0 -0
- package/screen/graph_live_debug.png +0 -0
- package/screen/management_command.png +0 -0
- package/screen/node_debug_trace.png +0 -0
- package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
- package/screen/websocket.png +0 -0
- package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
- package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
|
@@ -13,6 +13,122 @@ const { botManager } = require('../../core/services');
|
|
|
13
13
|
// Хранилище истории чатов в памяти: Map<"botId_pluginName", messages[]>
|
|
14
14
|
const chatHistoryStore = new Map();
|
|
15
15
|
|
|
16
|
+
const MAX_HISTORY_MESSAGES = 300;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Обрезает историю сообщений до MAX_HISTORY_MESSAGES (защита от memory leak)
|
|
20
|
+
* @param {Array} history - Массив сообщений
|
|
21
|
+
* @param {string} chatKey - Ключ чата для логирования
|
|
22
|
+
*/
|
|
23
|
+
function trimHistory(history, chatKey) {
|
|
24
|
+
if (history.length > MAX_HISTORY_MESSAGES) {
|
|
25
|
+
history.splice(0, history.length - MAX_HISTORY_MESSAGES);
|
|
26
|
+
console.log(`[AI Chat] History trimmed to ${MAX_HISTORY_MESSAGES} messages for ${chatKey}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rateLimitStore = new Map();
|
|
31
|
+
const RATE_LIMIT_MAX_REQUESTS = 30;
|
|
32
|
+
const RATE_LIMIT_WINDOW_MS = 60 * 1000;
|
|
33
|
+
|
|
34
|
+
// Очистка устаревших записей каждые 5 минут (защита от утечки памяти)
|
|
35
|
+
setInterval(() => {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
for (const [key, record] of rateLimitStore.entries()) {
|
|
38
|
+
if (now > record.resetTime) {
|
|
39
|
+
rateLimitStore.delete(key);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}, 5 * 60 * 1000);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Проверка rate limit для AI запросов
|
|
46
|
+
* @param {string} key - Ключ (например, "botId_pluginName")
|
|
47
|
+
* @returns {boolean} - true если запрос разрешен, false если превышен лимит
|
|
48
|
+
*/
|
|
49
|
+
function checkRateLimit(key) {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const record = rateLimitStore.get(key);
|
|
52
|
+
|
|
53
|
+
if (!record || now > record.resetTime) {
|
|
54
|
+
rateLimitStore.set(key, {
|
|
55
|
+
count: 1,
|
|
56
|
+
resetTime: now + RATE_LIMIT_WINDOW_MS
|
|
57
|
+
});
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (record.count >= RATE_LIMIT_MAX_REQUESTS) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
record.count += 1;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Получить оставшееся время до сброса rate limit (в секундах)
|
|
71
|
+
*/
|
|
72
|
+
function getRateLimitResetTime(key) {
|
|
73
|
+
const record = rateLimitStore.get(key);
|
|
74
|
+
if (!record) return 0;
|
|
75
|
+
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const remaining = Math.max(0, record.resetTime - now);
|
|
78
|
+
return Math.ceil(remaining / 1000);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Валидация и нормализация AI параметров (temperature, maxTokens)
|
|
83
|
+
*/
|
|
84
|
+
function validateAIParams(temperature, maxTokens, maxTokenLimit = 4096) {
|
|
85
|
+
let effectiveTemperature = 0.7;
|
|
86
|
+
if (temperature !== undefined) {
|
|
87
|
+
const temp = Number(temperature);
|
|
88
|
+
if (!isFinite(temp) || temp < 0 || temp > 2) {
|
|
89
|
+
return { error: 'Temperature must be a number between 0 and 2.' };
|
|
90
|
+
}
|
|
91
|
+
effectiveTemperature = temp;
|
|
92
|
+
}
|
|
93
|
+
let effectiveMaxTokens = 4096;
|
|
94
|
+
if (maxTokens !== undefined) {
|
|
95
|
+
const tokens = Number(maxTokens);
|
|
96
|
+
if (!isFinite(tokens) || tokens < 256) {
|
|
97
|
+
return { error: 'maxTokens must be a number >= 256.' };
|
|
98
|
+
}
|
|
99
|
+
effectiveMaxTokens = Math.min(tokens, maxTokenLimit);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { effectiveTemperature, effectiveMaxTokens };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Простое форматирование JS кода
|
|
107
|
+
* - Нормализует line endings (CRLF -> LF)
|
|
108
|
+
* - Убирает trailing whitespace
|
|
109
|
+
* - Обеспечивает newline в конце файла
|
|
110
|
+
* - Убирает множественные пустые строки (оставляет максимум 2)
|
|
111
|
+
*/
|
|
112
|
+
function simpleFormatCode(content, filePath) {
|
|
113
|
+
// Только для JS файлов
|
|
114
|
+
if (!filePath.endsWith('.js') && !filePath.endsWith('.mjs') && !filePath.endsWith('.cjs')) {
|
|
115
|
+
return content;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let formatted = content;
|
|
119
|
+
formatted = formatted.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
120
|
+
|
|
121
|
+
formatted = formatted.split('\n').map(line => line.trimEnd()).join('\n');
|
|
122
|
+
|
|
123
|
+
formatted = formatted.replace(/\n{4,}/g, '\n\n\n');
|
|
124
|
+
|
|
125
|
+
if (!formatted.endsWith('\n')) {
|
|
126
|
+
formatted += '\n';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return formatted;
|
|
130
|
+
}
|
|
131
|
+
|
|
16
132
|
/**
|
|
17
133
|
* Конвертирует JSON Schema параметры в формат Gemini
|
|
18
134
|
*/
|
|
@@ -96,7 +212,7 @@ function parseProxyString(proxyString) {
|
|
|
96
212
|
|
|
97
213
|
return {
|
|
98
214
|
host: host,
|
|
99
|
-
port: parseInt(port),
|
|
215
|
+
port: parseInt(port, 10),
|
|
100
216
|
user: user,
|
|
101
217
|
pass: pass
|
|
102
218
|
};
|
|
@@ -106,7 +222,6 @@ function parseProxyString(proxyString) {
|
|
|
106
222
|
}
|
|
107
223
|
}
|
|
108
224
|
|
|
109
|
-
// Список игнорируемых файлов/папок для сканирования проекта
|
|
110
225
|
const IGNORE_LIST = [
|
|
111
226
|
'node_modules/',
|
|
112
227
|
'package-lock.json',
|
|
@@ -121,9 +236,41 @@ const IGNORE_LIST = [
|
|
|
121
236
|
'.claude/'
|
|
122
237
|
];
|
|
123
238
|
|
|
124
|
-
|
|
239
|
+
/**
|
|
240
|
+
* Строгая проверка безопасности пути (защита от path traversal)
|
|
241
|
+
* @param {string} basePath - Базовая директория (например, pluginPath)
|
|
242
|
+
* @param {string} userPath - Путь от пользователя
|
|
243
|
+
* @returns {string|null} - Безопасный абсолютный путь или null если небезопасно
|
|
244
|
+
*/
|
|
245
|
+
function validateSafePath(basePath, userPath) {
|
|
246
|
+
// Проверяем что userPath не пустой
|
|
247
|
+
if (!userPath || typeof userPath !== 'string') {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Проверяем на опасные символы
|
|
252
|
+
if (userPath.includes('\0') || userPath.includes('\x00')) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Нормализуем пути
|
|
257
|
+
const normalizedBase = path.normalize(basePath);
|
|
258
|
+
const resolvedPath = path.resolve(basePath, userPath);
|
|
259
|
+
const normalizedResolved = path.normalize(resolvedPath);
|
|
260
|
+
|
|
261
|
+
// Проверяем что путь начинается с базового пути
|
|
262
|
+
// Используем path.relative чтобы проверить что файл внутри базовой директории
|
|
263
|
+
const relativePath = path.relative(normalizedBase, normalizedResolved);
|
|
264
|
+
|
|
265
|
+
// Если relative path начинается с '..' или является абсолютным, значит файл вне базовой директории
|
|
266
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return normalizedResolved;
|
|
271
|
+
}
|
|
272
|
+
|
|
125
273
|
function shouldIgnore(filePath, ignoreList) {
|
|
126
|
-
// Нормализуем путь к forward slashes для кроссплатформенности
|
|
127
274
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
128
275
|
|
|
129
276
|
return ignoreList.some(pattern => {
|
|
@@ -143,7 +290,6 @@ function shouldIgnore(filePath, ignoreList) {
|
|
|
143
290
|
});
|
|
144
291
|
}
|
|
145
292
|
|
|
146
|
-
// Получить все файлы рекурсивно
|
|
147
293
|
function getAllFilesRecursive(dir, basePath = dir, fileList = [], ignoreList = []) {
|
|
148
294
|
const files = fse.readdirSync(dir);
|
|
149
295
|
|
|
@@ -165,7 +311,6 @@ function getAllFilesRecursive(dir, basePath = dir, fileList = [], ignoreList = [
|
|
|
165
311
|
return fileList;
|
|
166
312
|
}
|
|
167
313
|
|
|
168
|
-
// Получить древовидную структуру
|
|
169
314
|
function getTreeStructure(dir, basePath = dir, prefix = '', ignoreList = []) {
|
|
170
315
|
const files = fse.readdirSync(dir);
|
|
171
316
|
let result = '';
|
|
@@ -192,7 +337,6 @@ function getTreeStructure(dir, basePath = dir, prefix = '', ignoreList = []) {
|
|
|
192
337
|
return result;
|
|
193
338
|
}
|
|
194
339
|
|
|
195
|
-
// Debug middleware
|
|
196
340
|
router.use((req, res, next) => {
|
|
197
341
|
console.log('[AI Assistant Router] Request:', req.method, req.path, 'Params:', req.params);
|
|
198
342
|
next();
|
|
@@ -206,13 +350,24 @@ async function resolvePluginPath(req, res, next) {
|
|
|
206
350
|
console.log('resolvePluginPath - botId:', req.params.botId, 'pluginName:', req.params.pluginName);
|
|
207
351
|
const { botId, pluginName } = req.params;
|
|
208
352
|
|
|
353
|
+
// Валидация botId (защита от SQL Injection)
|
|
354
|
+
const botIdNum = parseInt(botId, 10);
|
|
355
|
+
if (isNaN(botIdNum) || botIdNum <= 0 || botIdNum.toString() !== botId) {
|
|
356
|
+
return res.status(400).json({ error: 'Invalid bot ID.' });
|
|
357
|
+
}
|
|
358
|
+
|
|
209
359
|
if (!pluginName) {
|
|
210
360
|
return res.status(400).json({ error: 'Имя плагина обязательно в пути.' });
|
|
211
361
|
}
|
|
212
362
|
|
|
363
|
+
// Валидация pluginName (защита от path traversal в имени)
|
|
364
|
+
if (pluginName.includes('..') || pluginName.includes('/') || pluginName.includes('\\')) {
|
|
365
|
+
return res.status(400).json({ error: 'Invalid plugin name.' });
|
|
366
|
+
}
|
|
367
|
+
|
|
213
368
|
const plugin = await prisma.installedPlugin.findFirst({
|
|
214
369
|
where: {
|
|
215
|
-
botId:
|
|
370
|
+
botId: botIdNum,
|
|
216
371
|
name: pluginName
|
|
217
372
|
}
|
|
218
373
|
});
|
|
@@ -232,6 +387,7 @@ async function resolvePluginPath(req, res, next) {
|
|
|
232
387
|
|
|
233
388
|
console.log('Plugin path found, proceeding to route handler');
|
|
234
389
|
req.pluginPath = pluginPath;
|
|
390
|
+
req.botIdNum = botIdNum; // Сохраняем валидированный botId
|
|
235
391
|
next();
|
|
236
392
|
} catch (error) {
|
|
237
393
|
console.error('Error in resolvePluginPath:', error);
|
|
@@ -239,10 +395,8 @@ async function resolvePluginPath(req, res, next) {
|
|
|
239
395
|
}
|
|
240
396
|
}
|
|
241
397
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return [
|
|
245
|
-
// Tool 1: Получить древовидную структуру проекта
|
|
398
|
+
function createPluginTools(pluginPath, res, botId, applyMode = 'immediate', autoFormat = false) {
|
|
399
|
+
const baseTools = [
|
|
246
400
|
{
|
|
247
401
|
type: 'function',
|
|
248
402
|
function: {
|
|
@@ -271,7 +425,6 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
271
425
|
}
|
|
272
426
|
},
|
|
273
427
|
|
|
274
|
-
// Tool 2: Получить полный контекст проекта
|
|
275
428
|
{
|
|
276
429
|
type: 'function',
|
|
277
430
|
function: {
|
|
@@ -294,9 +447,27 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
294
447
|
result += `Всего файлов: ${allFiles.length}\n\n`;
|
|
295
448
|
result += `Содержимое файлов:\n\n`;
|
|
296
449
|
|
|
450
|
+
// Ограничения для защиты от переполнения памяти
|
|
451
|
+
const MAX_FILE_SIZE = 100 * 1024; // 100KB на файл
|
|
452
|
+
const MAX_TOTAL_SIZE = 1024 * 1024; // 1MB общий размер
|
|
453
|
+
let totalSize = 0;
|
|
454
|
+
|
|
297
455
|
for (const file of allFiles) {
|
|
298
456
|
const fullPath = path.join(pluginPath, file);
|
|
457
|
+
|
|
458
|
+
const stats = await fse.stat(fullPath);
|
|
459
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
460
|
+
result += `=== Файл: ${file} === (пропущен: размер ${stats.size} байт превышает лимит ${MAX_FILE_SIZE})\n\n`;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (totalSize + stats.size > MAX_TOTAL_SIZE) {
|
|
465
|
+
result += `\n(достигнут лимит размера ответа ${MAX_TOTAL_SIZE} байт, остальные файлы пропущены)\n`;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
|
|
299
469
|
const fileContent = await fse.readFile(fullPath, 'utf8');
|
|
470
|
+
totalSize += fileContent.length;
|
|
300
471
|
result += `=== Файл: ${file} ===\n${fileContent}\n\n`;
|
|
301
472
|
}
|
|
302
473
|
|
|
@@ -307,7 +478,6 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
307
478
|
}
|
|
308
479
|
},
|
|
309
480
|
|
|
310
|
-
// Tool 2: Прочитать конкретный файл
|
|
311
481
|
{
|
|
312
482
|
type: 'function',
|
|
313
483
|
function: {
|
|
@@ -327,12 +497,10 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
327
497
|
execute: async (args) => {
|
|
328
498
|
console.log('readFile called with:', args.filePath);
|
|
329
499
|
try {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (!safePath.startsWith(normalizedPluginPath) && safePath !== pluginPath) {
|
|
335
|
-
return `Ошибка: Доступ запрещен. Файл находится за пределами плагина.`;
|
|
500
|
+
// Используем строгую проверку безопасности (защита от path traversal)
|
|
501
|
+
const safePath = validateSafePath(pluginPath, args.filePath);
|
|
502
|
+
if (!safePath) {
|
|
503
|
+
return `Ошибка: Доступ запрещен. Недопустимый путь к файлу.`;
|
|
336
504
|
}
|
|
337
505
|
|
|
338
506
|
if (!await fse.pathExists(safePath)) {
|
|
@@ -347,7 +515,6 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
347
515
|
}
|
|
348
516
|
},
|
|
349
517
|
|
|
350
|
-
// Tool 3: Обновить содержимое файла
|
|
351
518
|
{
|
|
352
519
|
type: 'function',
|
|
353
520
|
function: {
|
|
@@ -369,14 +536,19 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
369
536
|
}
|
|
370
537
|
},
|
|
371
538
|
execute: async (args, context) => {
|
|
372
|
-
console.log('updateFile called for:', args.filePath);
|
|
373
|
-
|
|
374
|
-
|
|
539
|
+
console.log('updateFile called for:', args.filePath, 'applyMode:', applyMode, 'autoFormat:', autoFormat);
|
|
540
|
+
|
|
541
|
+
let content = args.content;
|
|
542
|
+
if (autoFormat) {
|
|
543
|
+
content = simpleFormatCode(content, args.filePath);
|
|
544
|
+
console.log('Auto-formatted content for:', args.filePath);
|
|
545
|
+
}
|
|
375
546
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
547
|
+
try {
|
|
548
|
+
// Используем строгую проверку безопасности (защита от path traversal)
|
|
549
|
+
const safePath = validateSafePath(pluginPath, args.filePath);
|
|
550
|
+
if (!safePath) {
|
|
551
|
+
return `Ошибка: Доступ запрещен. Недопустимый путь к файлу.`;
|
|
380
552
|
}
|
|
381
553
|
|
|
382
554
|
// Читаем старое содержимое для подсчёта diff
|
|
@@ -390,34 +562,24 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
390
562
|
} else {
|
|
391
563
|
isNewFile = true;
|
|
392
564
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
await fse.ensureDir(path.dirname(safePath));
|
|
396
|
-
|
|
397
|
-
// Записываем новое содержимое
|
|
398
|
-
await fse.writeFile(safePath, args.content, 'utf8');
|
|
399
|
-
|
|
400
|
-
// Вычисляем реальные изменённые строки используя diff
|
|
401
|
-
const newLines = args.content.split('\n');
|
|
565
|
+
|
|
566
|
+
const newLines = content.split('\n');
|
|
402
567
|
let linesAdded = 0;
|
|
403
568
|
let linesRemoved = 0;
|
|
404
569
|
let changedLineRanges = []; // Массив объектов { start: number, end: number }
|
|
405
570
|
|
|
406
571
|
if (isNewFile) {
|
|
407
572
|
linesAdded = newLines.length;
|
|
408
|
-
// Все строки нового файла - это добавленные строки
|
|
409
573
|
changedLineRanges = [{ start: 1, end: newLines.length }];
|
|
410
574
|
} else {
|
|
411
|
-
|
|
412
|
-
const diffResult = Diff.diffLines(oldContent, args.content);
|
|
575
|
+
const diffResult = Diff.diffLines(oldContent, content);
|
|
413
576
|
|
|
414
|
-
let currentLine = 1;
|
|
577
|
+
let currentLine = 1;
|
|
415
578
|
|
|
416
579
|
diffResult.forEach(part => {
|
|
417
580
|
const lineCount = part.count || 0;
|
|
418
581
|
|
|
419
582
|
if (part.added) {
|
|
420
|
-
// Добавленные строки
|
|
421
583
|
linesAdded += lineCount;
|
|
422
584
|
changedLineRanges.push({
|
|
423
585
|
start: currentLine,
|
|
@@ -425,29 +587,58 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
425
587
|
});
|
|
426
588
|
currentLine += lineCount;
|
|
427
589
|
} else if (part.removed) {
|
|
428
|
-
// Удалённые строки (не увеличиваем currentLine)
|
|
429
590
|
linesRemoved += lineCount;
|
|
430
591
|
} else {
|
|
431
|
-
// Неизменённые строки
|
|
432
592
|
currentLine += lineCount;
|
|
433
593
|
}
|
|
434
594
|
});
|
|
435
595
|
}
|
|
436
596
|
|
|
437
|
-
|
|
597
|
+
if (applyMode === 'preview') {
|
|
598
|
+
const sseEvent = {
|
|
599
|
+
type: 'file_preview',
|
|
600
|
+
filePath: args.filePath,
|
|
601
|
+
newContent: content,
|
|
602
|
+
oldContent: oldContent,
|
|
603
|
+
linesAdded,
|
|
604
|
+
linesRemoved,
|
|
605
|
+
isNewFile,
|
|
606
|
+
changedLineRanges
|
|
607
|
+
};
|
|
608
|
+
console.log('Sending SSE event file_preview:', {
|
|
609
|
+
filePath: args.filePath,
|
|
610
|
+
contentLength: content.length,
|
|
611
|
+
linesAdded,
|
|
612
|
+
linesRemoved,
|
|
613
|
+
isNewFile
|
|
614
|
+
});
|
|
615
|
+
res.write(`data: ${JSON.stringify(sseEvent)}\n\n`);
|
|
616
|
+
|
|
617
|
+
if (isNewFile) {
|
|
618
|
+
return `[ОЖИДАЕТ ПОДТВЕРЖДЕНИЯ] Предложено создание файла "${args.filePath}" (${newLines.length} строк). Файл ЕЩЁ НЕ создан - пользователь должен нажать "Применить" чтобы подтвердить. В своём ответе пользователю скажи что ты ПРЕДЛАГАЕШЬ создать файл и жди его решения. НЕ говори что файл создан.`;
|
|
619
|
+
} else {
|
|
620
|
+
return `[ОЖИДАЕТ ПОДТВЕРЖДЕНИЯ] Предложено обновление файла "${args.filePath}" (+${linesAdded} -${linesRemoved} строк). Файл ЕЩЁ НЕ изменён - пользователь должен нажать "Применить" чтобы подтвердить. В своём ответе пользователю скажи что ты ПРЕДЛАГАЕШЬ изменения и жди его решения. НЕ говори что изменения применены.`;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Immediate mode - записываем файл сразу
|
|
625
|
+
await fse.ensureDir(path.dirname(safePath));
|
|
626
|
+
|
|
627
|
+
await fse.writeFile(safePath, content, 'utf8');
|
|
628
|
+
|
|
438
629
|
const sseEvent = {
|
|
439
630
|
type: 'file_updated',
|
|
440
631
|
filePath: args.filePath,
|
|
441
|
-
newContent:
|
|
632
|
+
newContent: content,
|
|
442
633
|
oldContent: oldContent,
|
|
443
634
|
linesAdded,
|
|
444
635
|
linesRemoved,
|
|
445
636
|
isNewFile,
|
|
446
|
-
changedLineRanges
|
|
637
|
+
changedLineRanges
|
|
447
638
|
};
|
|
448
639
|
console.log('Sending SSE event file_updated:', {
|
|
449
640
|
filePath: args.filePath,
|
|
450
|
-
contentLength:
|
|
641
|
+
contentLength: content.length,
|
|
451
642
|
linesAdded,
|
|
452
643
|
linesRemoved,
|
|
453
644
|
isNewFile,
|
|
@@ -457,7 +648,7 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
457
648
|
console.log('SSE event sent successfully');
|
|
458
649
|
|
|
459
650
|
if (isNewFile) {
|
|
460
|
-
return `Создан новый файл "${args.filePath}". Размер: ${
|
|
651
|
+
return `Создан новый файл "${args.filePath}". Размер: ${content.length} символов (${newLines.length} строк).`;
|
|
461
652
|
} else {
|
|
462
653
|
return `Успешно обновлен файл "${args.filePath}". +${linesAdded} -${linesRemoved} строк.`;
|
|
463
654
|
}
|
|
@@ -468,7 +659,6 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
468
659
|
}
|
|
469
660
|
},
|
|
470
661
|
|
|
471
|
-
// Tool 4: Прочитать логи бота (чат из игры и console.log)
|
|
472
662
|
{
|
|
473
663
|
type: 'function',
|
|
474
664
|
function: {
|
|
@@ -499,10 +689,8 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
499
689
|
return 'Логи бота пусты. Возможно бот ещё не запущен или не было активности.';
|
|
500
690
|
}
|
|
501
691
|
|
|
502
|
-
// Берём последние N записей
|
|
503
692
|
let filteredLogs = logs.slice(-limit);
|
|
504
693
|
|
|
505
|
-
// Применяем фильтр если указан
|
|
506
694
|
if (args.filter) {
|
|
507
695
|
const filterLower = args.filter.toLowerCase();
|
|
508
696
|
filteredLogs = filteredLogs.filter(log => {
|
|
@@ -515,7 +703,6 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
515
703
|
return `Логи не найдены${args.filter ? ` по фильтру "${args.filter}"` : ''}.`;
|
|
516
704
|
}
|
|
517
705
|
|
|
518
|
-
// Форматируем логи
|
|
519
706
|
const formattedLogs = filteredLogs.map(log => {
|
|
520
707
|
if (typeof log === 'string') {
|
|
521
708
|
return log;
|
|
@@ -536,7 +723,6 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
536
723
|
}
|
|
537
724
|
},
|
|
538
725
|
|
|
539
|
-
// Tool 5: Удалить файл
|
|
540
726
|
{
|
|
541
727
|
type: 'function',
|
|
542
728
|
function: {
|
|
@@ -556,12 +742,10 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
556
742
|
execute: async (args) => {
|
|
557
743
|
console.log('deleteFile called for:', args.filePath);
|
|
558
744
|
try {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (!safePath.startsWith(normalizedPluginPath) && safePath !== pluginPath) {
|
|
564
|
-
return `Ошибка: Доступ запрещен. Файл находится за пределами плагина.`;
|
|
745
|
+
// Используем строгую проверку безопасности (защита от path traversal)
|
|
746
|
+
const safePath = validateSafePath(pluginPath, args.filePath);
|
|
747
|
+
if (!safePath) {
|
|
748
|
+
return `Ошибка: Доступ запрещен. Недопустимый путь к файлу.`;
|
|
565
749
|
}
|
|
566
750
|
|
|
567
751
|
// Проверяем что путь существует
|
|
@@ -593,7 +777,122 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
593
777
|
}
|
|
594
778
|
},
|
|
595
779
|
|
|
596
|
-
|
|
780
|
+
{
|
|
781
|
+
type: 'function',
|
|
782
|
+
function: {
|
|
783
|
+
name: 'searchCode',
|
|
784
|
+
description: 'Ищет текст или регулярное выражение в файлах плагина. Полезно для поиска функций, переменных, использований и паттернов в коде.',
|
|
785
|
+
parameters: {
|
|
786
|
+
type: 'object',
|
|
787
|
+
properties: {
|
|
788
|
+
query: {
|
|
789
|
+
type: 'string',
|
|
790
|
+
description: 'Текст или регулярное выражение для поиска'
|
|
791
|
+
},
|
|
792
|
+
type: {
|
|
793
|
+
type: 'string',
|
|
794
|
+
enum: ['text', 'regex'],
|
|
795
|
+
description: 'Тип поиска: text (точное совпадение) или regex (регулярное выражение). По умолчанию text.'
|
|
796
|
+
},
|
|
797
|
+
filePattern: {
|
|
798
|
+
type: 'string',
|
|
799
|
+
description: 'Паттерн файлов для поиска, например "*.js" или "*.json". По умолчанию ищет во всех файлах.'
|
|
800
|
+
},
|
|
801
|
+
maxResults: {
|
|
802
|
+
type: 'number',
|
|
803
|
+
description: 'Максимальное количество результатов (по умолчанию 20, максимум 50)'
|
|
804
|
+
}
|
|
805
|
+
},
|
|
806
|
+
required: ['query']
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
execute: async (args) => {
|
|
810
|
+
console.log('searchCode called:', args.query, 'type:', args.type || 'text');
|
|
811
|
+
try {
|
|
812
|
+
const searchType = args.type || 'text';
|
|
813
|
+
const maxResults = Math.min(args.maxResults || 20, 50);
|
|
814
|
+
const filePattern = args.filePattern || '*';
|
|
815
|
+
|
|
816
|
+
const allFiles = getAllFilesRecursive(pluginPath, pluginPath, [], IGNORE_LIST);
|
|
817
|
+
|
|
818
|
+
let filesToSearch = allFiles;
|
|
819
|
+
if (filePattern !== '*') {
|
|
820
|
+
// Полное экранирование спецсимволов regex, затем конвертация glob '*' в '.*'
|
|
821
|
+
const escaped = filePattern
|
|
822
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
823
|
+
.replace(/\*/g, '.*');
|
|
824
|
+
const patternRegex = new RegExp('^' + escaped + '$');
|
|
825
|
+
filesToSearch = allFiles.filter(f => patternRegex.test(path.basename(f)));
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
let searchRegex;
|
|
829
|
+
try {
|
|
830
|
+
// Защита от ReDoS: ограничение длины regex паттерна
|
|
831
|
+
const MAX_REGEX_LENGTH = 100;
|
|
832
|
+
if (searchType === 'regex') {
|
|
833
|
+
if (args.query.length > MAX_REGEX_LENGTH) {
|
|
834
|
+
return `Ошибка: регулярное выражение слишком длинное (максимум ${MAX_REGEX_LENGTH} символов)`;
|
|
835
|
+
}
|
|
836
|
+
searchRegex = new RegExp(args.query, 'gi');
|
|
837
|
+
} else {
|
|
838
|
+
const escaped = args.query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
839
|
+
searchRegex = new RegExp(escaped, 'gi');
|
|
840
|
+
}
|
|
841
|
+
} catch (regexError) {
|
|
842
|
+
return `Ошибка в регулярном выражении: ${regexError.message}`;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const results = [];
|
|
846
|
+
|
|
847
|
+
for (const file of filesToSearch) {
|
|
848
|
+
if (results.length >= maxResults) break;
|
|
849
|
+
|
|
850
|
+
const fullPath = path.join(pluginPath, file);
|
|
851
|
+
|
|
852
|
+
try {
|
|
853
|
+
const content = await fse.readFile(fullPath, 'utf8');
|
|
854
|
+
const lines = content.split('\n');
|
|
855
|
+
|
|
856
|
+
lines.forEach((line, index) => {
|
|
857
|
+
if (results.length >= maxResults) return;
|
|
858
|
+
|
|
859
|
+
if (searchRegex.test(line)) {
|
|
860
|
+
searchRegex.lastIndex = 0;
|
|
861
|
+
|
|
862
|
+
results.push({
|
|
863
|
+
file: file,
|
|
864
|
+
line: index + 1,
|
|
865
|
+
content: line.trim().substring(0, 200)
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
} catch (readError) {
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (results.length === 0) {
|
|
875
|
+
return `Поиск "${args.query}" не дал результатов в ${filesToSearch.length} файлах.`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
let response = `Найдено ${results.length} совпадений для "${args.query}":\n\n`;
|
|
879
|
+
|
|
880
|
+
results.forEach((r, i) => {
|
|
881
|
+
response += `${i + 1}. ${r.file}:${r.line}\n ${r.content}\n\n`;
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
if (results.length === maxResults) {
|
|
885
|
+
response += `\n(показаны первые ${maxResults} результатов)`;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return response;
|
|
889
|
+
} catch (error) {
|
|
890
|
+
console.error('Error in searchCode:', error);
|
|
891
|
+
return `Ошибка при поиске: ${error.message}`;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
|
|
597
896
|
{
|
|
598
897
|
type: 'function',
|
|
599
898
|
function: {
|
|
@@ -613,38 +912,30 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
613
912
|
execute: async (args) => {
|
|
614
913
|
console.log('deleteFolder called for:', args.folderPath);
|
|
615
914
|
try {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (!safePath.startsWith(normalizedPluginPath) && safePath !== pluginPath) {
|
|
621
|
-
return `Ошибка: Доступ запрещен. Папка находится за пределами плагина.`;
|
|
915
|
+
// Используем строгую проверку безопасности (защита от path traversal)
|
|
916
|
+
const safePath = validateSafePath(pluginPath, args.folderPath);
|
|
917
|
+
if (!safePath) {
|
|
918
|
+
return `Ошибка: Доступ запрещен. Недопустимый путь к папке.`;
|
|
622
919
|
}
|
|
623
920
|
|
|
624
|
-
|
|
625
|
-
if (safePath === pluginPath) {
|
|
921
|
+
if (safePath === path.normalize(pluginPath)) {
|
|
626
922
|
return `Ошибка: Нельзя удалить корневую директорию плагина.`;
|
|
627
923
|
}
|
|
628
924
|
|
|
629
|
-
// Проверяем что путь существует
|
|
630
925
|
if (!await fse.pathExists(safePath)) {
|
|
631
926
|
return `Ошибка: Папка "${args.folderPath}" не найдена.`;
|
|
632
927
|
}
|
|
633
928
|
|
|
634
|
-
// Проверяем что это директория
|
|
635
929
|
const stats = await fse.stat(safePath);
|
|
636
930
|
if (!stats.isDirectory()) {
|
|
637
931
|
return `Ошибка: "${args.folderPath}" является файлом. Используйте deleteFile для удаления файлов.`;
|
|
638
932
|
}
|
|
639
933
|
|
|
640
|
-
// Подсчитываем количество файлов и папок внутри
|
|
641
934
|
const items = await fse.readdir(safePath);
|
|
642
935
|
const itemCount = items.length;
|
|
643
936
|
|
|
644
|
-
// Удаляем папку рекурсивно
|
|
645
937
|
await fse.remove(safePath);
|
|
646
938
|
|
|
647
|
-
// Отправляем событие на фронтенд
|
|
648
939
|
const sseEvent = {
|
|
649
940
|
type: 'folder_deleted',
|
|
650
941
|
folderPath: args.folderPath
|
|
@@ -659,6 +950,8 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
659
950
|
}
|
|
660
951
|
}
|
|
661
952
|
];
|
|
953
|
+
|
|
954
|
+
return baseTools;
|
|
662
955
|
}
|
|
663
956
|
|
|
664
957
|
/**
|
|
@@ -668,9 +961,32 @@ function createPluginTools(pluginPath, res, botId) {
|
|
|
668
961
|
router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
669
962
|
console.log('Route hit! botId:', req.params.botId, 'pluginName:', req.params.pluginName);
|
|
670
963
|
try {
|
|
671
|
-
const { message, provider, apiKey, apiEndpoint, model, history, includeFiles, proxy } = req.body;
|
|
964
|
+
const { message, provider, apiKey, apiEndpoint, model, history, includeFiles, proxy, applyMode, temperature, maxTokens, customSystemPrompt, aiMode, autoFormat } = req.body;
|
|
672
965
|
const { botId, pluginName } = req.params;
|
|
673
966
|
|
|
967
|
+
// Проверка rate limit (защита от злоупотребления)
|
|
968
|
+
const rateLimitKey = `${botId}_${pluginName}`;
|
|
969
|
+
if (!checkRateLimit(rateLimitKey)) {
|
|
970
|
+
const resetTime = getRateLimitResetTime(rateLimitKey);
|
|
971
|
+
console.warn(`[AI Chat] Rate limit exceeded for ${rateLimitKey}. Reset in ${resetTime}s`);
|
|
972
|
+
return res.status(429).json({
|
|
973
|
+
error: 'Превышен лимит запросов к AI. Попробуйте позже.',
|
|
974
|
+
retryAfter: resetTime
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const effectiveApplyMode = applyMode || 'immediate';
|
|
979
|
+
|
|
980
|
+
const effectiveAutoFormat = autoFormat === true;
|
|
981
|
+
|
|
982
|
+
const validation = validateAIParams(temperature, maxTokens, 4096);
|
|
983
|
+
if (validation.error) {
|
|
984
|
+
return res.status(400).json({ error: validation.error });
|
|
985
|
+
}
|
|
986
|
+
const { effectiveTemperature, effectiveMaxTokens } = validation;
|
|
987
|
+
|
|
988
|
+
console.log('Apply mode:', effectiveApplyMode);
|
|
989
|
+
|
|
674
990
|
const aiProvider = provider || 'openrouter';
|
|
675
991
|
console.log('AI Provider:', aiProvider);
|
|
676
992
|
console.log('Proxy config:', proxy);
|
|
@@ -690,6 +1006,10 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
690
1006
|
systemPrompt = await fse.readFile(systemPromptPath, 'utf8');
|
|
691
1007
|
}
|
|
692
1008
|
|
|
1009
|
+
if (customSystemPrompt && customSystemPrompt.trim()) {
|
|
1010
|
+
systemPrompt += `\n\n## Дополнительные инструкции пользователя:\n${customSystemPrompt.trim()}`;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
693
1013
|
let context = '';
|
|
694
1014
|
|
|
695
1015
|
const packageJsonPath = path.join(req.pluginPath, 'package.json');
|
|
@@ -698,12 +1018,15 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
698
1018
|
context += `\n\n## Package.json плагина:\n\`\`\`json\n${JSON.stringify(packageJson, null, 2)}\n\`\`\`\n`;
|
|
699
1019
|
}
|
|
700
1020
|
|
|
701
|
-
// Добавляем файлы если запрошено
|
|
702
1021
|
if (includeFiles && Array.isArray(includeFiles)) {
|
|
703
1022
|
for (const fileName of includeFiles) {
|
|
704
|
-
const
|
|
705
|
-
if (
|
|
706
|
-
|
|
1023
|
+
const safePath = validateSafePath(req.pluginPath, fileName);
|
|
1024
|
+
if (!safePath) {
|
|
1025
|
+
console.warn(`[AI Chat] Skipping unsafe path: ${fileName}`);
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
if (await fse.pathExists(safePath)) {
|
|
1029
|
+
const fileContent = await fse.readFile(safePath, 'utf8');
|
|
707
1030
|
context += `\n\n## Файл ${fileName}:\n\`\`\`javascript\n${fileContent}\n\`\`\`\n`;
|
|
708
1031
|
}
|
|
709
1032
|
}
|
|
@@ -729,35 +1052,34 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
729
1052
|
console.log('Created Google Gemini client');
|
|
730
1053
|
} else {
|
|
731
1054
|
// OpenRouter
|
|
1055
|
+
const maxToolIterations = 30;
|
|
1056
|
+
|
|
732
1057
|
const clientConfig = {
|
|
733
1058
|
apiKey: apiKey,
|
|
734
1059
|
model: model,
|
|
735
1060
|
historyAdapter: new MemoryHistoryStorage(),
|
|
736
|
-
|
|
1061
|
+
maxToolCalls: maxToolIterations,
|
|
1062
|
+
debug: false
|
|
737
1063
|
};
|
|
738
1064
|
|
|
739
|
-
// Добавляем кастомный endpoint если указан
|
|
740
1065
|
if (apiEndpoint && apiEndpoint !== 'https://openrouter.ai/api/v1') {
|
|
741
1066
|
clientConfig.apiEndpoint = apiEndpoint;
|
|
742
1067
|
}
|
|
743
1068
|
|
|
744
|
-
// Добавляем прокси если указан
|
|
745
1069
|
if (proxyConfig) {
|
|
746
1070
|
clientConfig.proxy = proxyConfig;
|
|
747
1071
|
}
|
|
748
1072
|
|
|
749
1073
|
client = new OpenRouterClient(clientConfig);
|
|
750
|
-
console.log('Created OpenRouter client');
|
|
1074
|
+
console.log('Created OpenRouter client with maxToolCalls:', maxToolIterations);
|
|
751
1075
|
}
|
|
752
1076
|
|
|
753
|
-
// Получаем или создаем историю для этого бота и плагина
|
|
754
1077
|
const chatKey = `${botId}_${pluginName}`;
|
|
755
1078
|
if (!chatHistoryStore.has(chatKey)) {
|
|
756
1079
|
chatHistoryStore.set(chatKey, []);
|
|
757
1080
|
}
|
|
758
1081
|
const storedHistory = chatHistoryStore.get(chatKey);
|
|
759
1082
|
|
|
760
|
-
// Формируем customMessages
|
|
761
1083
|
const customMessages = [
|
|
762
1084
|
{
|
|
763
1085
|
role: 'system',
|
|
@@ -765,35 +1087,38 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
765
1087
|
}
|
|
766
1088
|
];
|
|
767
1089
|
|
|
768
|
-
// Добавляем сохранённую историю
|
|
769
1090
|
customMessages.push(...storedHistory);
|
|
770
1091
|
|
|
771
|
-
// Добавляем текущее сообщение
|
|
772
1092
|
const userMessage = {
|
|
773
1093
|
role: 'user',
|
|
774
1094
|
content: message
|
|
775
1095
|
};
|
|
776
1096
|
customMessages.push(userMessage);
|
|
777
1097
|
|
|
778
|
-
// Сохраняем сообщение пользователя в историю
|
|
779
1098
|
storedHistory.push(userMessage);
|
|
780
1099
|
|
|
781
|
-
//
|
|
782
|
-
|
|
1100
|
+
// Ограничиваем размер истории (защита от memory leak)
|
|
1101
|
+
trimHistory(storedHistory, chatKey);
|
|
1102
|
+
|
|
1103
|
+
const pluginTools = createPluginTools(
|
|
1104
|
+
req.pluginPath,
|
|
1105
|
+
res,
|
|
1106
|
+
botId,
|
|
1107
|
+
effectiveApplyMode,
|
|
1108
|
+
effectiveAutoFormat
|
|
1109
|
+
);
|
|
1110
|
+
console.log('AutoFormat:', effectiveAutoFormat, 'Tools count:', pluginTools.length);
|
|
783
1111
|
|
|
784
1112
|
let fullResponse = '';
|
|
785
1113
|
let assistantMessage = { role: 'assistant', content: '' };
|
|
786
1114
|
|
|
787
|
-
// Google Gemini использует другой API
|
|
788
1115
|
if (aiProvider === 'google') {
|
|
789
|
-
// Устанавливаем SSE заголовки
|
|
790
1116
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
791
1117
|
res.setHeader('Cache-Control', 'no-cache');
|
|
792
1118
|
res.setHeader('Connection', 'keep-alive');
|
|
793
1119
|
|
|
794
1120
|
try {
|
|
795
1121
|
console.log('[Google] Converting history...');
|
|
796
|
-
// Конвертируем историю в формат Gemini
|
|
797
1122
|
const geminiHistory = [];
|
|
798
1123
|
storedHistory.forEach(msg => {
|
|
799
1124
|
if (msg.role === 'user') {
|
|
@@ -804,7 +1129,6 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
804
1129
|
});
|
|
805
1130
|
console.log('[Google] History length:', geminiHistory.length);
|
|
806
1131
|
|
|
807
|
-
// Конвертируем tools в формат Gemini с wrapper для SSE событий
|
|
808
1132
|
console.log('[Google] Converting tools...');
|
|
809
1133
|
const geminiTools = [{
|
|
810
1134
|
functionDeclarations: pluginTools.map(tool => ({
|
|
@@ -812,7 +1136,6 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
812
1136
|
description: tool.function.description,
|
|
813
1137
|
parameters: convertToGeminiParameters(tool.function.parameters),
|
|
814
1138
|
execute: async (args) => {
|
|
815
|
-
// Отправляем событие начала выполнения tool
|
|
816
1139
|
if (!res.writableEnded) {
|
|
817
1140
|
res.write(`data: ${JSON.stringify({
|
|
818
1141
|
type: 'tool_call',
|
|
@@ -837,12 +1160,13 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
837
1160
|
}];
|
|
838
1161
|
console.log('[Google] Converted', geminiTools[0].functionDeclarations.length, 'tools');
|
|
839
1162
|
|
|
840
|
-
|
|
1163
|
+
const maxToolIterations = 30;
|
|
1164
|
+
console.log('[Google] Creating chat... maxToolCalls:', maxToolIterations);
|
|
841
1165
|
const chat = client.chats.create({
|
|
842
1166
|
systemInstruction: systemPrompt + context,
|
|
843
1167
|
history: geminiHistory,
|
|
844
1168
|
tools: geminiTools,
|
|
845
|
-
maxToolCalls:
|
|
1169
|
+
maxToolCalls: maxToolIterations
|
|
846
1170
|
});
|
|
847
1171
|
|
|
848
1172
|
console.log('[Google] Sending message...');
|
|
@@ -859,6 +1183,9 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
859
1183
|
assistantMessage.content = content;
|
|
860
1184
|
storedHistory.push(assistantMessage);
|
|
861
1185
|
|
|
1186
|
+
// Ограничиваем размер истории (защита от memory leak)
|
|
1187
|
+
trimHistory(storedHistory, chatKey);
|
|
1188
|
+
|
|
862
1189
|
console.log('[Google] Closing SSE stream');
|
|
863
1190
|
res.end();
|
|
864
1191
|
return;
|
|
@@ -877,10 +1204,11 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
877
1204
|
res.setHeader('Connection', 'keep-alive');
|
|
878
1205
|
|
|
879
1206
|
try {
|
|
1207
|
+
console.log(`Using temperature: ${effectiveTemperature}, maxTokens: ${effectiveMaxTokens}`);
|
|
880
1208
|
await client.chatStream({
|
|
881
1209
|
customMessages: customMessages,
|
|
882
|
-
temperature:
|
|
883
|
-
maxTokens:
|
|
1210
|
+
temperature: effectiveTemperature,
|
|
1211
|
+
maxTokens: effectiveMaxTokens,
|
|
884
1212
|
tools: pluginTools,
|
|
885
1213
|
includeToolResultInReport: true,
|
|
886
1214
|
streamCallbacks: {
|
|
@@ -911,6 +1239,9 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
911
1239
|
|
|
912
1240
|
if (assistantMessage.content) {
|
|
913
1241
|
storedHistory.push(assistantMessage);
|
|
1242
|
+
|
|
1243
|
+
trimHistory(storedHistory, chatKey);
|
|
1244
|
+
|
|
914
1245
|
console.log(`Saved to history. Total messages: ${storedHistory.length}`);
|
|
915
1246
|
}
|
|
916
1247
|
|
|
@@ -930,22 +1261,151 @@ router.post('/chat', resolvePluginPath, async (req, res) => {
|
|
|
930
1261
|
} catch (error) {
|
|
931
1262
|
console.error('[AI Assistant Error]:', error);
|
|
932
1263
|
|
|
933
|
-
// Если заголовки еще не отправлены, отправляем JSON ошибку
|
|
934
1264
|
if (!res.headersSent) {
|
|
935
1265
|
res.status(500).json({
|
|
936
1266
|
error: error.message || 'Failed to process AI request.'
|
|
937
1267
|
});
|
|
938
1268
|
} else {
|
|
939
|
-
// Если стриминг уже начался, отправляем ошибку через SSE
|
|
940
1269
|
res.write(`data: ${JSON.stringify({ type: 'error', error: error.message })}\n\n`);
|
|
941
1270
|
res.end();
|
|
942
1271
|
}
|
|
943
1272
|
}
|
|
944
1273
|
});
|
|
945
1274
|
|
|
1275
|
+
/**
|
|
1276
|
+
* POST /api/bots/:botId/plugins/ide/:pluginName/ai/inline
|
|
1277
|
+
* Inline AI запрос (для командной палитры в редакторе)
|
|
1278
|
+
*/
|
|
1279
|
+
router.post('/inline', resolvePluginPath, async (req, res) => {
|
|
1280
|
+
try {
|
|
1281
|
+
const { prompt, systemInstruction, action, context, provider, apiKey, apiEndpoint, model, proxy, temperature, maxTokens } = req.body;
|
|
1282
|
+
const { botId, pluginName } = req.params;
|
|
1283
|
+
|
|
1284
|
+
const rateLimitKey = `${botId}_${pluginName}_inline`;
|
|
1285
|
+
if (!checkRateLimit(rateLimitKey)) {
|
|
1286
|
+
const resetTime = getRateLimitResetTime(rateLimitKey);
|
|
1287
|
+
console.warn(`[AI Inline] Rate limit exceeded for ${rateLimitKey}. Reset in ${resetTime}s`);
|
|
1288
|
+
return res.status(429).json({
|
|
1289
|
+
error: 'Превышен лимит запросов к AI. Попробуйте позже.',
|
|
1290
|
+
retryAfter: resetTime
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
if (!prompt) {
|
|
1295
|
+
return res.status(400).json({ error: 'Prompt is required' });
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
if (!apiKey) {
|
|
1299
|
+
return res.status(400).json({ error: 'API key is required' });
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const aiProvider = provider || 'openrouter';
|
|
1303
|
+
const proxyConfig = parseProxyString(proxy);
|
|
1304
|
+
|
|
1305
|
+
const validation = validateAIParams(temperature, maxTokens, 4096);
|
|
1306
|
+
if (validation.error) {
|
|
1307
|
+
return res.status(400).json({ error: validation.error });
|
|
1308
|
+
}
|
|
1309
|
+
const { effectiveTemperature, effectiveMaxTokens } = validation;
|
|
1310
|
+
|
|
1311
|
+
let client;
|
|
1312
|
+
if (aiProvider === 'google') {
|
|
1313
|
+
const googleConfig = {
|
|
1314
|
+
apiKeys: [apiKey],
|
|
1315
|
+
defaultModel: model
|
|
1316
|
+
};
|
|
1317
|
+
if (proxyConfig) {
|
|
1318
|
+
googleConfig.proxy = proxyConfig;
|
|
1319
|
+
}
|
|
1320
|
+
client = new GeminiClient(googleConfig);
|
|
1321
|
+
} else {
|
|
1322
|
+
const clientConfig = {
|
|
1323
|
+
apiKey: apiKey,
|
|
1324
|
+
model: model,
|
|
1325
|
+
historyAdapter: new MemoryHistoryStorage()
|
|
1326
|
+
};
|
|
1327
|
+
if (apiEndpoint && apiEndpoint !== 'https://openrouter.ai/api/v1') {
|
|
1328
|
+
clientConfig.apiEndpoint = apiEndpoint;
|
|
1329
|
+
}
|
|
1330
|
+
if (proxyConfig) {
|
|
1331
|
+
clientConfig.proxy = proxyConfig;
|
|
1332
|
+
}
|
|
1333
|
+
client = new OpenRouterClient(clientConfig);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
let result;
|
|
1337
|
+
|
|
1338
|
+
if (aiProvider === 'google') {
|
|
1339
|
+
const chat = client.chats.create({
|
|
1340
|
+
systemInstruction: systemInstruction || 'Ты - AI помощник для разработчиков. Отвечай кратко и по делу.'
|
|
1341
|
+
});
|
|
1342
|
+
const response = await chat.sendMessage(prompt);
|
|
1343
|
+
result = response.text();
|
|
1344
|
+
} else {
|
|
1345
|
+
const response = await client.chat({
|
|
1346
|
+
customMessages: [
|
|
1347
|
+
{ role: 'system', content: systemInstruction || 'Ты - AI помощник для разработчиков. Отвечай кратко и по делу.' },
|
|
1348
|
+
{ role: 'user', content: prompt }
|
|
1349
|
+
],
|
|
1350
|
+
temperature: effectiveTemperature,
|
|
1351
|
+
maxTokens: effectiveMaxTokens
|
|
1352
|
+
});
|
|
1353
|
+
result = response;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
console.log('[AI Inline] Response generated for action:', action);
|
|
1357
|
+
res.json({ result, action });
|
|
1358
|
+
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
console.error('[AI Inline Error]:', error);
|
|
1361
|
+
res.status(500).json({ error: error.message || 'Failed to process inline AI request' });
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* POST /api/bots/:botId/plugins/ide/:pluginName/ai/apply-change
|
|
1367
|
+
* Применяет preview изменение (записывает файл на диск)
|
|
1368
|
+
*/
|
|
1369
|
+
router.post('/apply-change', resolvePluginPath, async (req, res) => {
|
|
1370
|
+
try {
|
|
1371
|
+
const { filePath, content } = req.body;
|
|
1372
|
+
const { botId, pluginName } = req.params;
|
|
1373
|
+
|
|
1374
|
+
const rateLimitKey = `${botId}_${pluginName}_apply`;
|
|
1375
|
+
if (!checkRateLimit(rateLimitKey)) {
|
|
1376
|
+
const resetTime = getRateLimitResetTime(rateLimitKey);
|
|
1377
|
+
console.warn(`[AI Apply] Rate limit exceeded for ${rateLimitKey}. Reset in ${resetTime}s`);
|
|
1378
|
+
return res.status(429).json({
|
|
1379
|
+
error: 'Превышен лимит запросов к AI. Попробуйте позже.',
|
|
1380
|
+
retryAfter: resetTime
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (!filePath || content === undefined) {
|
|
1385
|
+
return res.status(400).json({ error: 'filePath and content are required' });
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Используем строгую проверку безопасности (защита от path traversal)
|
|
1389
|
+
const safePath = validateSafePath(req.pluginPath, filePath);
|
|
1390
|
+
if (!safePath) {
|
|
1391
|
+
return res.status(403).json({ error: 'Access denied. Invalid file path.' });
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
await fse.ensureDir(path.dirname(safePath));
|
|
1395
|
+
|
|
1396
|
+
await fse.writeFile(safePath, content, 'utf8');
|
|
1397
|
+
|
|
1398
|
+
console.log('[AI] Applied preview change:', filePath);
|
|
1399
|
+
|
|
1400
|
+
res.json({ success: true, filePath });
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
console.error('[AI] Error applying change:', error);
|
|
1403
|
+
res.status(500).json({ error: error.message });
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
|
|
946
1407
|
/**
|
|
947
1408
|
* GET /api/bots/:botId/plugins/ide/:pluginName/ai/chat
|
|
948
|
-
* Получает историю чата для конкретного бота и плагина
|
|
949
1409
|
*/
|
|
950
1410
|
router.get('/chat', resolvePluginPath, async (req, res) => {
|
|
951
1411
|
try {
|
|
@@ -967,7 +1427,6 @@ router.get('/chat', resolvePluginPath, async (req, res) => {
|
|
|
967
1427
|
|
|
968
1428
|
/**
|
|
969
1429
|
* DELETE /api/bots/:botId/plugins/ide/:pluginName/ai/chat
|
|
970
|
-
* Очищает историю чата для конкретного бота и плагина
|
|
971
1430
|
*/
|
|
972
1431
|
router.delete('/chat', resolvePluginPath, async (req, res) => {
|
|
973
1432
|
try {
|