agentshire 1.0.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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +437 -0
- package/README.zh-CN.md +441 -0
- package/ROADMAP.md +207 -0
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/VISION.md +101 -0
- package/index.ts +386 -0
- package/openclaw.plugin.json +27 -0
- package/package.json +100 -0
- package/src/bridge/AGENTS.md +136 -0
- package/src/bridge/ActivityStream.ts +184 -0
- package/src/bridge/CitizenManager.ts +280 -0
- package/src/bridge/DirectorBridge.ts +1076 -0
- package/src/bridge/EventTranslator.ts +142 -0
- package/src/bridge/NpcEventQueue.ts +61 -0
- package/src/bridge/ReconnectManager.ts +42 -0
- package/src/bridge/RouteManager.ts +270 -0
- package/src/bridge/StateTracker.ts +100 -0
- package/src/bridge/ToolVfxMapper.ts +94 -0
- package/src/bridge/__tests__/ActivityStream.test.ts +156 -0
- package/src/bridge/__tests__/CitizenManager.test.ts +174 -0
- package/src/bridge/__tests__/EventTranslator.test.ts +292 -0
- package/src/bridge/__tests__/NpcEventQueue.test.ts +92 -0
- package/src/bridge/__tests__/RouteManager.test.ts +106 -0
- package/src/bridge/data/route-config.json +36 -0
- package/src/bridge/data/route-config.ts +19 -0
- package/src/bridge/implicit-chat.ts +192 -0
- package/src/bridge/index.ts +12 -0
- package/src/contracts/agent-state.ts +87 -0
- package/src/contracts/agui.ts +212 -0
- package/src/contracts/chat.ts +67 -0
- package/src/contracts/events.ts +170 -0
- package/src/contracts/index.ts +71 -0
- package/src/contracts/media.ts +77 -0
- package/src/contracts/registry.ts +32 -0
- package/src/plugin/AGENTS.md +192 -0
- package/src/plugin/__tests__/editor-serve-megapack.test.ts +150 -0
- package/src/plugin/__tests__/hook-translator.test.ts +197 -0
- package/src/plugin/auto-config.ts +131 -0
- package/src/plugin/channel.ts +399 -0
- package/src/plugin/chat-asset-resolver.ts +75 -0
- package/src/plugin/chat-session-watcher.ts +98 -0
- package/src/plugin/citizen-agent-manager.ts +178 -0
- package/src/plugin/citizen-chat-router.ts +83 -0
- package/src/plugin/citizen-workshop-manager.ts +96 -0
- package/src/plugin/custom-asset-manager.ts +250 -0
- package/src/plugin/editor-serve.ts +1368 -0
- package/src/plugin/group-discussion.ts +223 -0
- package/src/plugin/hook-translator.ts +202 -0
- package/src/plugin/llm-agent-proxy.ts +193 -0
- package/src/plugin/outbound-adapter.ts +187 -0
- package/src/plugin/paths.ts +33 -0
- package/src/plugin/plan-manager.ts +594 -0
- package/src/plugin/runtime.ts +9 -0
- package/src/plugin/session-history.ts +838 -0
- package/src/plugin/session-log-watcher.ts +221 -0
- package/src/plugin/soul-prompt-template.ts +290 -0
- package/src/plugin/subagent-tracker.ts +205 -0
- package/src/plugin/tools.ts +493 -0
- package/src/plugin/town-session.ts +40 -0
- package/src/plugin/ws-server.ts +680 -0
- package/src/town-souls.ts +134 -0
- package/town-frontend/dist/assets/CustomAssetStore-oi8aIurt.js +2 -0
- package/town-frontend/dist/assets/GLTFLoader-BA5RqSME.js +3804 -0
- package/town-frontend/dist/assets/OrbitControls-ZmySp9sQ.js +2 -0
- package/town-frontend/dist/assets/SettingsPanel-BO52reJe.js +2 -0
- package/town-frontend/dist/assets/SkeletonUtils-BCVmgslc.js +2 -0
- package/town-frontend/dist/assets/SkillLearnCard-Dk38iDpy.js +6 -0
- package/town-frontend/dist/assets/TopicSetupPanel-DLaLHB_Z.js +2 -0
- package/town-frontend/dist/assets/Trap-Bold-CT0JBE39.woff2 +0 -0
- package/town-frontend/dist/assets/Trap-SemiBold-R4_-Ld0j.woff2 +0 -0
- package/town-frontend/dist/assets/WeatherSystem-Cb3BvHes.js +550 -0
- package/town-frontend/dist/assets/avatars/char-female-a.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-b.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-c.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-d.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-e.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-f.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-a.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-b.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-c.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-d.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-e.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-f.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-beaver.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-bee.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-bunny.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-cat.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-caterpillar.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-chick.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-cow.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-crab.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-deer.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-dog.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-elephant.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-fish.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-fox.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-giraffe.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-hog.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-koala.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-lion.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-monkey.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-panda.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-parrot.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-penguin.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-pig.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-polar.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-tiger.webp +0 -0
- package/town-frontend/dist/assets/character-catalog-DSmLtlNC.js +2 -0
- package/town-frontend/dist/assets/citizenEditor-DubGSJOQ.js +296 -0
- package/town-frontend/dist/assets/command-parser-BUd15Bmv.js +12 -0
- package/town-frontend/dist/assets/editor-B5QO0OtX.js +258 -0
- package/town-frontend/dist/assets/editor-Bk8g1NCD.css +1 -0
- package/town-frontend/dist/assets/index-BWfrufil.js +2 -0
- package/town-frontend/dist/assets/index-faS20RJk.js +6 -0
- package/town-frontend/dist/assets/logo-DJI6EtST.png +0 -0
- package/town-frontend/dist/assets/logo-title-AdKPZX5E.png +0 -0
- package/town-frontend/dist/assets/main-CqsN43aT.js +224 -0
- package/town-frontend/dist/assets/main-D7neuy3w.css +1 -0
- package/town-frontend/dist/assets/models/buildings/base.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/base.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/bench.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/bench.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/box_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/box_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/box_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/box_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_A_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_A_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_B_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_B_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_C.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_C.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_C_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_C_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_D.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_D.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_D_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_D_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_E.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_E.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_E_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_E_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_F.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_F.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_F_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_F_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_G.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_G.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_G_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_G_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_H.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_H.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_H_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_H_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/bush.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/bush.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/car_hatchback.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_hatchback.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_police.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_police.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_sedan.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_sedan.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_stationwagon.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_stationwagon.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_taxi.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_taxi.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/citybits_texture.png +0 -0
- package/town-frontend/dist/assets/models/buildings/dumpster.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/dumpster.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/firehydrant.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/firehydrant.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_corner.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_corner.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_corner_curved.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_corner_curved.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_junction.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_junction.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_straight.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_straight.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_straight_crossing.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_straight_crossing.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_tsplit.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_tsplit.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/streetlight.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/streetlight.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_C.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_C.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trash_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trash_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trash_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trash_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/watertower.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/watertower.gltf +136 -0
- package/town-frontend/dist/assets/models/characters/Textures/colormap.png +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-a.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-b.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-c.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-d.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-e.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-f.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-a.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-b.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-c.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-d.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-e.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-f.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-beaver.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-bee.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-bunny.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-cat.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-caterpillar.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-chick.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-cow.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-crab.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-deer.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-dog.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-elephant.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-fish.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-fox.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-giraffe.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-hog.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-koala.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-lion.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-monkey.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-panda.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-parrot.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-penguin.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-pig.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-polar.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-tiger.glb +0 -0
- package/town-frontend/dist/assets/models/characters/colormap.png +0 -0
- package/town-frontend/dist/assets/models/furniture/armchair.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/armchair.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/armchair_pillows.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/armchair_pillows.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/book_set.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/book_set.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/book_single.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/book_single.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_A_wood.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_A_wood.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_B_wood.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_B_wood.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_C.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_C.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool_wood.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool_wood.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/couch.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/couch.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/couch_pillows.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/couch_pillows.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/furniturebits_texture.png +0 -0
- package/town-frontend/dist/assets/models/furniture/lamp_standing.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/lamp_standing.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/lamp_table.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/lamp_table.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_medium.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_medium.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_C.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_C.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pillow_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pillow_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pillow_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pillow_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_big.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_big.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_small.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_low.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_low.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_medium.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_medium.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_medium_long.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_medium_long.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_small.gltf +136 -0
- package/town-frontend/dist/assets/models/props/bench.bin +0 -0
- package/town-frontend/dist/assets/models/props/bench.gltf +136 -0
- package/town-frontend/dist/assets/models/props/bush.bin +0 -0
- package/town-frontend/dist/assets/models/props/bush.gltf +136 -0
- package/town-frontend/dist/assets/models/props/capybara.glb +0 -0
- package/town-frontend/dist/assets/models/props/car_hatchback.bin +0 -0
- package/town-frontend/dist/assets/models/props/car_hatchback.gltf +442 -0
- package/town-frontend/dist/assets/models/props/car_sedan.bin +0 -0
- package/town-frontend/dist/assets/models/props/car_sedan.gltf +442 -0
- package/town-frontend/dist/assets/models/props/car_taxi.bin +0 -0
- package/town-frontend/dist/assets/models/props/car_taxi.gltf +442 -0
- package/town-frontend/dist/assets/models/props/citybits_texture.png +0 -0
- package/town-frontend/dist/assets/models/props/dumpster.bin +0 -0
- package/town-frontend/dist/assets/models/props/dumpster.gltf +136 -0
- package/town-frontend/dist/assets/models/props/firehydrant.bin +0 -0
- package/town-frontend/dist/assets/models/props/firehydrant.gltf +136 -0
- package/town-frontend/dist/assets/models/props/streetlight.bin +0 -0
- package/town-frontend/dist/assets/models/props/streetlight.gltf +136 -0
- package/town-frontend/dist/assets/models/props/trafficlight_A.bin +0 -0
- package/town-frontend/dist/assets/models/props/trafficlight_A.gltf +136 -0
- package/town-frontend/dist/assets/models/props/trash_A.bin +0 -0
- package/town-frontend/dist/assets/models/props/trash_A.gltf +136 -0
- package/town-frontend/dist/assets/models/props/watertower.bin +0 -0
- package/town-frontend/dist/assets/models/props/watertower.gltf +136 -0
- package/town-frontend/dist/assets/models/stage-deco/Flowers_1_D.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Flowers_2_B.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Grass_A_1.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Park_GrassHill_A.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Pebles_1_A_2.glb +0 -0
- package/town-frontend/dist/assets/music/bgm_day.mp3 +0 -0
- package/town-frontend/dist/assets/music/bgm_dusk.mp3 +0 -0
- package/town-frontend/dist/assets/music/bgm_night.mp3 +0 -0
- package/town-frontend/dist/assets/music/bgm_work.mp3 +0 -0
- package/town-frontend/dist/assets/office-whiteboard-idle-CyEwBrq_.webp +0 -0
- package/town-frontend/dist/assets/preview-B6hYEQij.js +2 -0
- package/town-frontend/dist/assets/town-BKesnERP.js +8888 -0
- package/town-frontend/dist/assets/town-BnswKsjF.css +1 -0
- package/town-frontend/dist/citizen-editor.html +166 -0
- package/town-frontend/dist/editor.html +227 -0
- package/town-frontend/dist/index.html +22 -0
- package/town-frontend/dist/preview.html +56 -0
- package/town-frontend/dist/town.html +165 -0
- package/town-frontend/dist/viewer.html +91 -0
- package/town-souls/CHEN.md +130 -0
- package/town-souls/CHENGZI.md +92 -0
- package/town-souls/CITIZEN_tpl.md +16 -0
- package/town-souls/DIANDIAN.md +92 -0
- package/town-souls/HAITANG.md +94 -0
- package/town-souls/QIQI.md +119 -0
- package/town-souls/SOUL.md +141 -0
- package/town-souls/SOUL_tpl.md +135 -0
- package/town-souls/XIAOLIE.md +107 -0
- package/town-souls/YAN.md +107 -0
- package/town-workspace/IDENTITY.md +5 -0
- package/town-workspace/SOUL.md +141 -0
- package/town-workspace/project-workflow.md +81 -0
- package/town-workspace/town-defaults.json +92 -0
- package/town-workspace/town-guide.md +45 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group discussion orchestrator.
|
|
3
|
+
* Manages a round-robin discussion between multiple citizen agents around a user-provided topic.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. User provides topic + list of npcIds
|
|
7
|
+
* 2. Orchestrator sends contextual message to first citizen via routeCitizenMessage
|
|
8
|
+
* 3. On citizen response (captured via hook), appends to shared history, sends to next citizen
|
|
9
|
+
* 4. User can "interject" at any time — message is queued and injected after current speaker
|
|
10
|
+
* 5. Continues until explicitly ended
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { routeCitizenMessage } from "./citizen-chat-router.js";
|
|
14
|
+
|
|
15
|
+
const TURN_TIMEOUT_MS = 30_000;
|
|
16
|
+
const MAX_TOTAL_TURNS = 30;
|
|
17
|
+
|
|
18
|
+
interface Participant {
|
|
19
|
+
npcId: string;
|
|
20
|
+
name: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface HistoryEntry {
|
|
24
|
+
speaker: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ActiveDiscussion {
|
|
29
|
+
participants: Participant[];
|
|
30
|
+
history: HistoryEntry[];
|
|
31
|
+
queue: number[];
|
|
32
|
+
currentIndex: number;
|
|
33
|
+
currentSpeakerNpcId: string | null;
|
|
34
|
+
turnTimer: ReturnType<typeof setTimeout> | null;
|
|
35
|
+
pendingUserMessages: string[];
|
|
36
|
+
totalTurns: number;
|
|
37
|
+
stopped: boolean;
|
|
38
|
+
townSessionId: string;
|
|
39
|
+
accountId: string;
|
|
40
|
+
cfg: Record<string, unknown>;
|
|
41
|
+
responseBuffer: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let activeDiscussion: ActiveDiscussion | null = null;
|
|
45
|
+
|
|
46
|
+
export function hasActiveDiscussion(): boolean {
|
|
47
|
+
return activeDiscussion !== null && !activeDiscussion.stopped;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getDiscussionSpeaker(): string | null {
|
|
51
|
+
return activeDiscussion?.currentSpeakerNpcId ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function startDiscussion(params: {
|
|
55
|
+
participants: Participant[];
|
|
56
|
+
townSessionId: string;
|
|
57
|
+
accountId: string;
|
|
58
|
+
cfg: Record<string, unknown>;
|
|
59
|
+
}): void {
|
|
60
|
+
if (activeDiscussion && !activeDiscussion.stopped) {
|
|
61
|
+
endDiscussion();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { participants, townSessionId, accountId, cfg } = params;
|
|
65
|
+
const queue = participants.map((_, i) => i);
|
|
66
|
+
|
|
67
|
+
activeDiscussion = {
|
|
68
|
+
participants,
|
|
69
|
+
history: [],
|
|
70
|
+
queue,
|
|
71
|
+
currentIndex: 0,
|
|
72
|
+
currentSpeakerNpcId: null,
|
|
73
|
+
turnTimer: null,
|
|
74
|
+
pendingUserMessages: [],
|
|
75
|
+
totalTurns: 0,
|
|
76
|
+
stopped: false,
|
|
77
|
+
townSessionId,
|
|
78
|
+
accountId,
|
|
79
|
+
cfg,
|
|
80
|
+
responseBuffer: "",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
console.log(`[group-discussion] Started with ${participants.length} participants: ${participants.map(p => p.name).join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function onUserMessage(message: string): void {
|
|
87
|
+
if (!activeDiscussion || activeDiscussion.stopped) return;
|
|
88
|
+
|
|
89
|
+
if (activeDiscussion.currentSpeakerNpcId) {
|
|
90
|
+
activeDiscussion.pendingUserMessages.push(message);
|
|
91
|
+
console.log(`[group-discussion] User message queued (speaker active): "${message.slice(0, 50)}"`);
|
|
92
|
+
} else {
|
|
93
|
+
activeDiscussion.history.push({ speaker: "镇长", text: message });
|
|
94
|
+
console.log(`[group-discussion] User message added to history: "${message.slice(0, 50)}"`);
|
|
95
|
+
sendToNextSpeaker();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function onCitizenResponse(agentId: string, text: string): void {
|
|
100
|
+
if (!activeDiscussion || activeDiscussion.stopped) return;
|
|
101
|
+
|
|
102
|
+
const participant = activeDiscussion.participants.find(p => {
|
|
103
|
+
const expectedPrefix = `citizen-${p.npcId.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
104
|
+
return agentId === expectedPrefix || agentId === p.npcId;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!participant) return;
|
|
108
|
+
if (participant.npcId !== activeDiscussion.currentSpeakerNpcId) return;
|
|
109
|
+
|
|
110
|
+
activeDiscussion.responseBuffer += text;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function onCitizenTurnEnd(agentId: string): void {
|
|
114
|
+
if (!activeDiscussion || activeDiscussion.stopped) return;
|
|
115
|
+
|
|
116
|
+
const participant = activeDiscussion.participants.find(p => {
|
|
117
|
+
const expectedPrefix = `citizen-${p.npcId.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
118
|
+
return agentId === expectedPrefix || agentId === p.npcId;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (!participant) return;
|
|
122
|
+
if (participant.npcId !== activeDiscussion.currentSpeakerNpcId) return;
|
|
123
|
+
|
|
124
|
+
if (activeDiscussion.turnTimer) {
|
|
125
|
+
clearTimeout(activeDiscussion.turnTimer);
|
|
126
|
+
activeDiscussion.turnTimer = null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const responseText = activeDiscussion.responseBuffer.trim();
|
|
130
|
+
activeDiscussion.responseBuffer = "";
|
|
131
|
+
activeDiscussion.currentSpeakerNpcId = null;
|
|
132
|
+
|
|
133
|
+
if (responseText) {
|
|
134
|
+
activeDiscussion.history.push({ speaker: participant.name, text: responseText });
|
|
135
|
+
console.log(`[group-discussion] ${participant.name} said: "${responseText.slice(0, 80)}"`);
|
|
136
|
+
} else {
|
|
137
|
+
console.log(`[group-discussion] ${participant.name} had empty response, skipping`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (activeDiscussion.stopped) return;
|
|
141
|
+
|
|
142
|
+
while (activeDiscussion.pendingUserMessages.length > 0) {
|
|
143
|
+
const userMsg = activeDiscussion.pendingUserMessages.shift()!;
|
|
144
|
+
activeDiscussion.history.push({ speaker: "镇长", text: userMsg });
|
|
145
|
+
console.log(`[group-discussion] Flushed queued user message: "${userMsg.slice(0, 50)}"`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
activeDiscussion.currentIndex = (activeDiscussion.currentIndex + 1) % activeDiscussion.participants.length;
|
|
149
|
+
|
|
150
|
+
sendToNextSpeaker();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function endDiscussion(): void {
|
|
154
|
+
if (!activeDiscussion) return;
|
|
155
|
+
|
|
156
|
+
console.log(`[group-discussion] Ending discussion (${activeDiscussion.totalTurns} total turns)`);
|
|
157
|
+
|
|
158
|
+
activeDiscussion.stopped = true;
|
|
159
|
+
if (activeDiscussion.turnTimer) {
|
|
160
|
+
clearTimeout(activeDiscussion.turnTimer);
|
|
161
|
+
activeDiscussion.turnTimer = null;
|
|
162
|
+
}
|
|
163
|
+
activeDiscussion = null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildContextMessage(discussion: ActiveDiscussion): string {
|
|
167
|
+
let context = "";
|
|
168
|
+
for (const entry of discussion.history) {
|
|
169
|
+
context += `${entry.speaker}:${entry.text}\n`;
|
|
170
|
+
}
|
|
171
|
+
context += `\n轮到你了,简短回复。`;
|
|
172
|
+
|
|
173
|
+
return context;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function sendToNextSpeaker(): void {
|
|
177
|
+
if (!activeDiscussion || activeDiscussion.stopped) return;
|
|
178
|
+
|
|
179
|
+
if (activeDiscussion.totalTurns >= MAX_TOTAL_TURNS) {
|
|
180
|
+
console.log(`[group-discussion] Max turns (${MAX_TOTAL_TURNS}) reached, pausing`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (activeDiscussion.history.length === 0) return;
|
|
185
|
+
|
|
186
|
+
const participant = activeDiscussion.participants[activeDiscussion.currentIndex];
|
|
187
|
+
const contextMessage = buildContextMessage(activeDiscussion);
|
|
188
|
+
|
|
189
|
+
activeDiscussion.currentSpeakerNpcId = participant.npcId;
|
|
190
|
+
activeDiscussion.responseBuffer = "";
|
|
191
|
+
activeDiscussion.totalTurns++;
|
|
192
|
+
|
|
193
|
+
console.log(`[group-discussion] Turn ${activeDiscussion.totalTurns}: sending to ${participant.name} (${participant.npcId})`);
|
|
194
|
+
|
|
195
|
+
activeDiscussion.turnTimer = setTimeout(() => {
|
|
196
|
+
if (!activeDiscussion || activeDiscussion.currentSpeakerNpcId !== participant.npcId) return;
|
|
197
|
+
console.log(`[group-discussion] ${participant.name} timed out after ${TURN_TIMEOUT_MS}ms, skipping`);
|
|
198
|
+
activeDiscussion.currentSpeakerNpcId = null;
|
|
199
|
+
activeDiscussion.responseBuffer = "";
|
|
200
|
+
activeDiscussion.currentIndex = (activeDiscussion.currentIndex + 1) % activeDiscussion.participants.length;
|
|
201
|
+
sendToNextSpeaker();
|
|
202
|
+
}, TURN_TIMEOUT_MS);
|
|
203
|
+
|
|
204
|
+
routeCitizenMessage({
|
|
205
|
+
npcId: participant.npcId,
|
|
206
|
+
label: participant.name,
|
|
207
|
+
message: contextMessage,
|
|
208
|
+
townSessionId: activeDiscussion.townSessionId,
|
|
209
|
+
accountId: activeDiscussion.accountId,
|
|
210
|
+
cfg: activeDiscussion.cfg,
|
|
211
|
+
}).catch((err) => {
|
|
212
|
+
console.error(`[group-discussion] Failed to route message to ${participant.name}:`, err);
|
|
213
|
+
if (!activeDiscussion || activeDiscussion.stopped) return;
|
|
214
|
+
if (activeDiscussion.turnTimer) {
|
|
215
|
+
clearTimeout(activeDiscussion.turnTimer);
|
|
216
|
+
activeDiscussion.turnTimer = null;
|
|
217
|
+
}
|
|
218
|
+
activeDiscussion.currentSpeakerNpcId = null;
|
|
219
|
+
activeDiscussion.responseBuffer = "";
|
|
220
|
+
activeDiscussion.currentIndex = (activeDiscussion.currentIndex + 1) % activeDiscussion.participants.length;
|
|
221
|
+
sendToNextSpeaker();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { AgentEvent } from "../contracts/events.js";
|
|
2
|
+
import type { ContentKind } from "../contracts/media.js";
|
|
3
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
function tryReadBase64(filePath: string): string | undefined {
|
|
6
|
+
try {
|
|
7
|
+
if (!filePath || !existsSync(filePath)) return undefined;
|
|
8
|
+
return readFileSync(filePath).toString('base64');
|
|
9
|
+
} catch { return undefined; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let sessionCounter = 0;
|
|
13
|
+
|
|
14
|
+
const MEDIA_EXTENSIONS = new Set([
|
|
15
|
+
"png", "jpg", "jpeg", "gif", "webp", "svg", "bmp",
|
|
16
|
+
"mp4", "webm", "mov", "avi", "mkv",
|
|
17
|
+
"mp3", "wav", "ogg", "m4a", "flac",
|
|
18
|
+
"pdf", "zip", "tar", "gz",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"]);
|
|
22
|
+
const VIDEO_EXTENSIONS = new Set(["mp4", "webm", "mov", "avi", "mkv"]);
|
|
23
|
+
const AUDIO_EXTENSIONS = new Set(["mp3", "wav", "ogg", "m4a", "flac"]);
|
|
24
|
+
|
|
25
|
+
function detectMediaKind(ext: string): ContentKind {
|
|
26
|
+
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
27
|
+
if (VIDEO_EXTENSIONS.has(ext)) return "video";
|
|
28
|
+
if (AUDIO_EXTENSIONS.has(ext)) return "audio";
|
|
29
|
+
return "file";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function extractMediaPath(toolName: string, params: any, result: any): string | null {
|
|
33
|
+
if (toolName === "image") {
|
|
34
|
+
const output = String(result ?? "");
|
|
35
|
+
const match = output.match(/(?:saved|wrote|created|generated)[^"]*?["']?([^\s"']+\.(?:png|jpg|jpeg|gif|webp))/i);
|
|
36
|
+
if (match) return match[1];
|
|
37
|
+
const pathMatch = output.match(/\/[^\s"']+\.(?:png|jpg|jpeg|gif|webp)/i);
|
|
38
|
+
if (pathMatch) return pathMatch[0];
|
|
39
|
+
return params?.path ?? params?.output_path ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (toolName === "exec" || toolName === "bash") {
|
|
43
|
+
const cmd = String(params?.command ?? "");
|
|
44
|
+
if (/screencapture|screenshot/i.test(cmd)) {
|
|
45
|
+
const pathMatch = cmd.match(/[\w/.-]+\.(?:png|jpg|jpeg|gif)/i);
|
|
46
|
+
if (pathMatch) return pathMatch[0];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const output = String(result ?? "");
|
|
51
|
+
const fileMatch = output.match(/\/[^\s"']+\.(\w+)/);
|
|
52
|
+
if (fileMatch && MEDIA_EXTENSIONS.has(fileMatch[1].toLowerCase())) {
|
|
53
|
+
return fileMatch[0];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function hookToAgentEvent(
|
|
60
|
+
hookName: string,
|
|
61
|
+
payload: Record<string, unknown>,
|
|
62
|
+
): AgentEvent | AgentEvent[] | null {
|
|
63
|
+
switch (hookName) {
|
|
64
|
+
case "before_agent_start":
|
|
65
|
+
return {
|
|
66
|
+
type: "system",
|
|
67
|
+
subtype: "init",
|
|
68
|
+
sessionId: String(payload.sessionId ?? `oc-session-${++sessionCounter}`),
|
|
69
|
+
model: String(payload.model ?? "unknown"),
|
|
70
|
+
persona: payload.persona as string | undefined,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
case "agent_end": {
|
|
74
|
+
const events: AgentEvent[] = [];
|
|
75
|
+
|
|
76
|
+
if (payload.success === false || payload.error) {
|
|
77
|
+
events.push({
|
|
78
|
+
type: "error",
|
|
79
|
+
message: String(payload.error ?? "Agent run failed"),
|
|
80
|
+
recoverable: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const usage = payload.usage as { inputTokens?: number; outputTokens?: number } | undefined;
|
|
85
|
+
events.push({
|
|
86
|
+
type: "turn_end",
|
|
87
|
+
usage: {
|
|
88
|
+
inputTokens: usage?.inputTokens ?? 0,
|
|
89
|
+
outputTokens: usage?.outputTokens ?? 0,
|
|
90
|
+
},
|
|
91
|
+
toolCalls: (payload.toolCalls as number) ?? 0,
|
|
92
|
+
durationMs: (payload.durationMs as number) ?? 0,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return events;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case "llm_input": {
|
|
99
|
+
const delta = String(payload.thinking ?? payload.reasoning ?? payload.thought ?? "");
|
|
100
|
+
return { type: "thinking_delta", delta };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "llm_output": {
|
|
104
|
+
const texts = payload.assistantTexts as string[] | undefined;
|
|
105
|
+
const text = texts?.[texts.length - 1] ?? String(payload.text ?? payload.content ?? "");
|
|
106
|
+
if (!text) return null;
|
|
107
|
+
return { type: "text", content: text };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case "before_tool_call":
|
|
111
|
+
return {
|
|
112
|
+
type: "tool_use",
|
|
113
|
+
toolUseId: String(payload.toolCallId ?? payload.toolUseId ?? `tool-${Date.now()}`),
|
|
114
|
+
name: String(payload.toolName ?? payload.name ?? "unknown"),
|
|
115
|
+
input: (payload.params ?? payload.input ?? {}) as Record<string, unknown>,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
case "after_tool_call": {
|
|
119
|
+
const toolName = String(payload.toolName ?? payload.name ?? "unknown");
|
|
120
|
+
const events: AgentEvent[] = [
|
|
121
|
+
{
|
|
122
|
+
type: "tool_result",
|
|
123
|
+
toolUseId: String(payload.toolCallId ?? payload.toolUseId ?? ""),
|
|
124
|
+
name: toolName,
|
|
125
|
+
output: String(payload.result ?? payload.output ?? ""),
|
|
126
|
+
meta: payload.meta as Record<string, unknown> | undefined,
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
const mediaPath = extractMediaPath(toolName, payload.params, payload.result);
|
|
131
|
+
if (mediaPath) {
|
|
132
|
+
const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
|
|
133
|
+
const mimeMap: Record<string, string> = {
|
|
134
|
+
png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg",
|
|
135
|
+
gif: "image/gif", webp: "image/webp", svg: "image/svg+xml",
|
|
136
|
+
mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime",
|
|
137
|
+
mp3: "audio/mpeg", wav: "audio/wav", ogg: "audio/ogg", m4a: "audio/mp4",
|
|
138
|
+
};
|
|
139
|
+
events.push({
|
|
140
|
+
type: "media_output",
|
|
141
|
+
kind: detectMediaKind(ext),
|
|
142
|
+
path: mediaPath,
|
|
143
|
+
mimeType: mimeMap[ext] ?? "application/octet-stream",
|
|
144
|
+
data: tryReadBase64(mediaPath),
|
|
145
|
+
role: "assistant",
|
|
146
|
+
meta: { filename: mediaPath.split("/").pop() },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return events;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case "subagent_spawned": {
|
|
154
|
+
const childKey = String(payload.childSessionKey ?? "");
|
|
155
|
+
const uniqueId = childKey || String(payload.runId ?? `sub-${Date.now()}`);
|
|
156
|
+
return {
|
|
157
|
+
type: "sub_agent",
|
|
158
|
+
subtype: "started",
|
|
159
|
+
agentId: uniqueId,
|
|
160
|
+
agentType: String(payload.agentType ?? "worker"),
|
|
161
|
+
parentToolUseId: String(payload.parentToolUseId ?? ""),
|
|
162
|
+
task: String(payload.task ?? payload.label ?? ""),
|
|
163
|
+
model: String(payload.model ?? "unknown"),
|
|
164
|
+
displayName: (payload.label ?? payload.displayName) as string | undefined,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case "subagent_ended": {
|
|
169
|
+
const targetKey = String(payload.targetSessionKey ?? "");
|
|
170
|
+
const endId = targetKey || String(payload.runId ?? payload.agentId ?? "");
|
|
171
|
+
const outcome = String(payload.outcome ?? "ok");
|
|
172
|
+
const statusMap: Record<string, string> = {
|
|
173
|
+
ok: "completed", error: "failed", timeout: "failed", killed: "killed", reset: "killed", deleted: "killed",
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
type: "sub_agent",
|
|
177
|
+
subtype: "done",
|
|
178
|
+
agentId: endId,
|
|
179
|
+
result: String(payload.result ?? payload.reason ?? ""),
|
|
180
|
+
toolCalls: (payload.toolCalls as number) ?? 0,
|
|
181
|
+
status: (statusMap[outcome] ?? "completed") as "completed" | "failed" | "killed",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
case "message_sending": {
|
|
186
|
+
const text = String(payload.content ?? payload.text ?? payload.body ?? "");
|
|
187
|
+
if (!text) return null;
|
|
188
|
+
return { type: "text", content: text };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case "session_end":
|
|
192
|
+
return {
|
|
193
|
+
type: "system",
|
|
194
|
+
subtype: "done",
|
|
195
|
+
result: "session_end",
|
|
196
|
+
sessionId: String(payload.sessionId ?? ""),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
default:
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM proxy for implicit NPC behaviors and soul generation.
|
|
3
|
+
* Reads provider config from OpenClaw runtime (rt.config.loadConfig()),
|
|
4
|
+
* resolves env-templated API keys from the config's env section,
|
|
5
|
+
* and makes direct HTTP calls. Falls back to process.env for QClaw compatibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getTownRuntime } from "./runtime.js";
|
|
9
|
+
|
|
10
|
+
export interface LLMChatRequest {
|
|
11
|
+
system: string;
|
|
12
|
+
user: string;
|
|
13
|
+
maxTokens: number;
|
|
14
|
+
temperature: number;
|
|
15
|
+
stop: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LLMChatResult {
|
|
19
|
+
text: string;
|
|
20
|
+
usage?: { input: number; output: number };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ProviderConfig {
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
apiKey: string;
|
|
26
|
+
model: string;
|
|
27
|
+
apiFormat: "anthropic-messages" | "openai";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const MAX_CONCURRENT = 2;
|
|
31
|
+
const MAX_QUEUE = 10;
|
|
32
|
+
|
|
33
|
+
function resolveEnvRef(value: string, env: Record<string, string>): string {
|
|
34
|
+
return value.replace(/\$\{(\w+)\}/g, (_, key) => env[key] ?? process.env[key] ?? "");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function loadProvider(): ProviderConfig | null {
|
|
38
|
+
try {
|
|
39
|
+
const rt = getTownRuntime();
|
|
40
|
+
const cfg = rt.config.loadConfig() as any;
|
|
41
|
+
const env: Record<string, string> = cfg?.env ?? {};
|
|
42
|
+
const providers = cfg?.models?.providers;
|
|
43
|
+
if (!providers || typeof providers !== "object") return null;
|
|
44
|
+
|
|
45
|
+
for (const [, provider] of Object.entries(providers) as [string, any][]) {
|
|
46
|
+
if (!provider.baseUrl || !provider.apiKey) continue;
|
|
47
|
+
const apiKey = resolveEnvRef(String(provider.apiKey), env);
|
|
48
|
+
if (!apiKey) continue;
|
|
49
|
+
|
|
50
|
+
const apiFormat = provider.api?.startsWith("openai") ? "openai" as const : "anthropic-messages" as const;
|
|
51
|
+
const models = Array.isArray(provider.models) ? provider.models : [];
|
|
52
|
+
const model = models[0]?.id ?? "default";
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
baseUrl: String(provider.baseUrl).replace(/\/+$/, ""),
|
|
56
|
+
apiKey,
|
|
57
|
+
model,
|
|
58
|
+
apiFormat,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.warn("[llm-agent-proxy] Failed to load provider:", (err as Error).message);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function callAnthropicMessages(config: ProviderConfig, req: LLMChatRequest): Promise<LLMChatResult> {
|
|
68
|
+
const body = {
|
|
69
|
+
model: config.model,
|
|
70
|
+
max_tokens: req.maxTokens,
|
|
71
|
+
temperature: req.temperature,
|
|
72
|
+
system: req.system,
|
|
73
|
+
messages: [{ role: "user", content: req.user }],
|
|
74
|
+
...(req.stop.length > 0 ? { stop_sequences: req.stop } : {}),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const url = `${config.baseUrl}/v1/messages`;
|
|
78
|
+
const resp = await fetch(url, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
"x-api-key": config.apiKey,
|
|
83
|
+
"anthropic-version": "2023-06-01",
|
|
84
|
+
},
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!resp.ok) {
|
|
89
|
+
const errText = await resp.text().catch(() => "");
|
|
90
|
+
throw new Error(`Anthropic API ${resp.status}: ${errText.slice(0, 200)}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = await resp.json() as {
|
|
94
|
+
content?: Array<{ type: string; text?: string }>;
|
|
95
|
+
usage?: { input_tokens?: number; output_tokens?: number };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const text = data.content?.find(b => b.type === "text")?.text ?? "";
|
|
99
|
+
return {
|
|
100
|
+
text,
|
|
101
|
+
usage: data.usage
|
|
102
|
+
? { input: data.usage.input_tokens ?? 0, output: data.usage.output_tokens ?? 0 }
|
|
103
|
+
: undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function callOpenAI(config: ProviderConfig, req: LLMChatRequest): Promise<LLMChatResult> {
|
|
108
|
+
const body = {
|
|
109
|
+
model: config.model,
|
|
110
|
+
max_tokens: req.maxTokens,
|
|
111
|
+
temperature: req.temperature,
|
|
112
|
+
messages: [
|
|
113
|
+
{ role: "system", content: req.system },
|
|
114
|
+
{ role: "user", content: req.user },
|
|
115
|
+
],
|
|
116
|
+
...(req.stop.length > 0 ? { stop: req.stop } : {}),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const url = `${config.baseUrl}/v1/chat/completions`;
|
|
120
|
+
const resp = await fetch(url, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify(body),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!resp.ok) {
|
|
130
|
+
const errText = await resp.text().catch(() => "");
|
|
131
|
+
throw new Error(`OpenAI API ${resp.status}: ${errText.slice(0, 200)}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = await resp.json() as {
|
|
135
|
+
choices?: Array<{ message?: { content?: string } }>;
|
|
136
|
+
usage?: { prompt_tokens?: number; completion_tokens?: number };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const text = data.choices?.[0]?.message?.content ?? "";
|
|
140
|
+
return {
|
|
141
|
+
text,
|
|
142
|
+
usage: data.usage
|
|
143
|
+
? { input: data.usage.prompt_tokens ?? 0, output: data.usage.completion_tokens ?? 0 }
|
|
144
|
+
: undefined,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let activeRequests = 0;
|
|
149
|
+
const requestQueue: Array<{
|
|
150
|
+
req: LLMChatRequest;
|
|
151
|
+
resolve: (r: LLMChatResult) => void;
|
|
152
|
+
reject: (e: Error) => void;
|
|
153
|
+
}> = [];
|
|
154
|
+
|
|
155
|
+
function drainQueue(): void {
|
|
156
|
+
if (requestQueue.length === 0 || activeRequests >= MAX_CONCURRENT) return;
|
|
157
|
+
const next = requestQueue.shift()!;
|
|
158
|
+
executeChat(next.req).then(next.resolve, next.reject);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function executeChat(req: LLMChatRequest): Promise<LLMChatResult> {
|
|
162
|
+
activeRequests++;
|
|
163
|
+
try {
|
|
164
|
+
const config = loadProvider();
|
|
165
|
+
if (!config) return { text: "" };
|
|
166
|
+
return config.apiFormat === "openai"
|
|
167
|
+
? await callOpenAI(config, req)
|
|
168
|
+
: await callAnthropicMessages(config, req);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.warn("[llm-agent-proxy] chat error:", (err as Error).message);
|
|
171
|
+
return { text: "" };
|
|
172
|
+
} finally {
|
|
173
|
+
activeRequests--;
|
|
174
|
+
drainQueue();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function chat(req: LLMChatRequest): Promise<LLMChatResult> {
|
|
179
|
+
if (activeRequests >= MAX_CONCURRENT) {
|
|
180
|
+
return new Promise<LLMChatResult>((resolve, reject) => {
|
|
181
|
+
if (requestQueue.length >= MAX_QUEUE) {
|
|
182
|
+
resolve({ text: "" });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
requestQueue.push({ req, resolve, reject });
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return executeChat(req);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function isAvailable(): boolean {
|
|
192
|
+
return loadProvider() !== null;
|
|
193
|
+
}
|