pi-coding-master 0.2.7
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/README.md +50 -0
- package/core/god.agent.capability/@ABANDONED.brain.prefrontal.monitor/gpu_monitor.py +80 -0
- package/core/god.agent.capability/@ABANDONED.brain.prefrontal.monitor/gpu_monitor.py.CHANGELOG +1 -0
- package/core/god.agent.capability/@ABANDONED.brain.prefrontal.monitor/gpu_monitor.py.SPEC +3 -0
- package/core/god.agent.capability/hands.dev.writeissue/issue.ts +209 -0
- package/core/god.agent.capability/hands.dev.writeissue/issue.ts.SPEC +26 -0
- package/core/god.agent.capability/hands.files.changewatcher/watcher.ts +44 -0
- package/core/god.agent.capability/hands.files.changewatcher/watcher.ts.SPEC +25 -0
- package/core/god.pi.mod/cli/pi-completion.zsh +21 -0
- package/core/god.pi.mod/cli/pi-completion.zsh.CHANGELOG +1 -0
- package/core/god.pi.mod/cli/pi-people.sh +264 -0
- package/core/god.pi.mod/cli/pi-people.sh.LESSON +10 -0
- package/core/god.pi.mod/cli/pi-people.sh.SPEC +31 -0
- package/core/god.pi.mod/paths.ts +47 -0
- package/core/god.pi.mod/tui.mods.blockrender/blockrender.js +90 -0
- package/core/god.pi.mod/tui.mods.blockrender/blockrender.js.SPEC +29 -0
- package/core/god.pi.mod/tui.mods.footer.budget/budget_guard.ts +154 -0
- package/core/god.pi.mod/tui.mods.footer.budget/budget_guard.ts.CHANGELOG +10 -0
- package/core/god.pi.mod/tui.mods.footer.budget/budget_guard.ts.LESSON +12 -0
- package/core/god.pi.mod/tui.mods.footer.budget/budget_guard.ts.SPEC +26 -0
- package/core/god.pi.mod/tui.mods.footer.budget/footer-cny.CHANGELOG +39 -0
- package/core/god.pi.mod/tui.mods.viewmode/view_mode-thinking.patch +55 -0
- package/core/god.pi.mod/tui.mods.viewmode/view_mode-tools.patch +19 -0
- package/core/god.pi.mod/tui.mods.viewmode/view_mode-tools.patch.CHANGELOG +1 -0
- package/core/god.pi.mod/tui.mods.viewmode/view_mode.ts +50 -0
- package/core/god.pi.mod/tui.mods.viewmode/view_mode.ts.SPEC +12 -0
- package/core/god.pi.mod/tui.variants.userterminal/user_terminal.CHANGELOG +10 -0
- package/core/god.pi.mod/tui.variants.userterminal/user_terminal.ts +66 -0
- package/core/god.pi.mod/tui.variants.userterminal/user_terminal.ts.SPEC +31 -0
- package/core/index.ts +3 -0
- package/core/individual.bio.gene/dna.coded/coded.dna +257 -0
- package/core/individual.bio.gene/dna.coded/coded.dna.CHANGELOG +12 -0
- package/core/individual.bio.gene/dna.coded/coded.dna.SPEC +11 -0
- package/core/individual.bio.gene/dna.coded/core.dna +110 -0
- package/core/individual.bio.gene/dna.promotor/promotor.dna +117 -0
- package/core/individual.bio.gene/dna.promotor/promotor.dna.CHANGELOG +4 -0
- package/core/individual.bio.gene/dna.promotor/promotor.dna.SPEC +7 -0
- package/core/individual.bio.gene/dna.transpiler/transpiler.ts +395 -0
- package/core/individual.bio.gene/dna.transpiler/transpiler.ts.CHANGELOG +7 -0
- package/core/individual.bio.gene/dna.transpiler/transpiler.ts.SPEC +28 -0
- package/core/individual.bio.gene/gene.README +19 -0
- package/core/individual.bio.gene/rna/rna.json +536 -0
- package/core/individual.bio.gene/rna/rna.json.CHANGELOG +2 -0
- package/core/individual.bio.gene/rna/rna.json.SPEC +8 -0
- package/core/individual.bio.organs/blood.runtime/messages.ts +236 -0
- package/core/individual.bio.organs/blood.runtime/runtime.ts +173 -0
- package/core/individual.bio.organs/blood.runtime/runtime.ts.CHANGELOG +5 -0
- package/core/individual.bio.organs/blood.runtime/runtime.ts.SPEC +32 -0
- package/core/individual.bio.organs/brain.amygdala/amygdala.ts +25 -0
- package/core/individual.bio.organs/brain.amygdala/amygdala.ts.COMMENT +3 -0
- package/core/individual.bio.organs/brain.amygdala/amygdala.ts.SPEC +9 -0
- package/core/individual.bio.organs/brain.hippocampus/hippocampus-launcher.sh.template +108 -0
- package/core/individual.bio.organs/brain.hippocampus/hippocampus.ts +166 -0
- package/core/individual.bio.organs/brain.hippocampus/hippocampus.ts.CHANGELOG +12 -0
- package/core/individual.bio.organs/brain.hippocampus/hippocampus.ts.LESSON +22 -0
- package/core/individual.bio.organs/brain.hippocampus/hippocampus.ts.SPEC +16 -0
- package/core/individual.bio.organs/brain.hippocampus/memory.ts +879 -0
- package/core/individual.bio.organs/brain.hippocampus/memory.ts.CHANGELOG +66 -0
- package/core/individual.bio.organs/brain.hippocampus/memory.ts.LESSON +25 -0
- package/core/individual.bio.organs/brain.hippocampus/memory.ts.SPEC +46 -0
- package/core/individual.bio.organs/brain.hippocampus/sleep.ts +139 -0
- package/core/individual.bio.organs/brain.hippocampus/sleep.ts.CHANGELOG +11 -0
- package/core/individual.bio.organs/brain.hippocampus/sleep.ts.SPEC +16 -0
- package/core/individual.bio.organs/brain.prefrontal.drafting/drafting.SPEC +44 -0
- package/core/individual.bio.organs/brain.prefrontal.drafting/drafting.ts +73 -0
- package/core/individual.bio.organs/brain.prefrontal.drafting/drafting.ts.CHANGELOG +3 -0
- package/core/individual.bio.organs/brain.prefrontal.drafting/drafting.ts.SPEC +24 -0
- package/core/individual.bio.organs/brain.prefrontal.drafting/index.ts.CHANGELOG +3 -0
- package/core/individual.bio.organs/brain.prefrontal.drafting/main.ts +13 -0
- package/core/individual.bio.organs/brain.prefrontal.drafting/main.ts.SPEC +17 -0
- package/core/individual.bio.organs/brain.senses.bioclock/bioclock.ts +94 -0
- package/core/individual.bio.organs/brain.senses.bioclock/bioclock.ts.LESSON +13 -0
- package/core/individual.bio.organs/brain.senses.bioclock/bioclock.ts.SPEC +20 -0
- package/core/individual.bio.organs/brain.senses.subconscious/feed-format.SPEC +56 -0
- package/core/individual.bio.organs/brain.senses.subconscious/index.ts.CHANGELOG +13 -0
- package/core/individual.bio.organs/brain.senses.subconscious/spawner.ts +130 -0
- package/core/individual.bio.organs/brain.senses.subconscious/spawner.ts.SPEC +13 -0
- package/core/individual.bio.organs/brain.senses.subconscious/subconscious.ts +280 -0
- package/core/individual.bio.organs/brain.senses.subconscious/subconscious.ts.CHANGELOG +7 -0
- package/core/individual.bio.organs/brain.senses.subconscious/tools.ts +180 -0
- package/core/individual.bio.organs/brain.senses.subconscious/tools.ts.SPEC +3 -0
- package/core/individual.bio.organs/ears.listen/config.json +9 -0
- package/core/individual.bio.organs/ears.listen/config.json.CHANGELOG +1 -0
- package/core/individual.bio.organs/ears.listen/config.json.SPEC +3 -0
- package/core/individual.bio.organs/ears.listen/ears.ts.CHANGELOG +48 -0
- package/core/individual.bio.organs/ears.listen/ears_recorder.py.CHANGELOG +6 -0
- package/core/individual.bio.organs/ears.listen/index.ts +1 -0
- package/core/individual.bio.organs/ears.listen/index.ts.SPEC +16 -0
- package/core/individual.bio.organs/ears.listen/listen.ts +208 -0
- package/core/individual.bio.organs/ears.listen/listen.ts.SPEC +3 -0
- package/core/individual.bio.organs/ears.listen/listen_recorder.py +445 -0
- package/core/individual.bio.organs/ears.listen/listen_recorder.py.SPEC +7 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/__init__.py +0 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/common/__init__.py +0 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/common/events_pb2.py +38 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/common/events_pb2_grpc.py +24 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/common/rpcmeta_pb2.py +42 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/common/rpcmeta_pb2_grpc.py +24 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/__init__.py +0 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/understanding/__init__.py +0 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/understanding/ast/__init__.py +0 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/understanding/ast/ast_service_pb2.py +45 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/understanding/ast/ast_service_pb2_grpc.py +97 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/understanding/base/__init__.py +0 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/understanding/base/au_base_pb2.py +80 -0
- package/core/individual.bio.organs/ears.listen/python_protogen/products/understanding/base/au_base_pb2_grpc.py +24 -0
- package/core/individual.bio.organs/hands.fileactions/authorize.TRUST +1 -0
- package/core/individual.bio.organs/hands.fileactions/authorize.ts +70 -0
- package/core/individual.bio.organs/hands.fileactions/authorize.ts.CHANGELOG +1 -0
- package/core/individual.bio.organs/hands.fileactions/authorize.ts.SPEC +3 -0
- package/core/individual.bio.organs/hands.fileactions/dir.README +13 -0
- package/core/individual.bio.organs/hands.fileactions/file_rules.json +23 -0
- package/core/individual.bio.organs/hands.fileactions/fileactions.README +12 -0
- package/core/individual.bio.organs/hands.fileactions/fileactions.ts +540 -0
- package/core/individual.bio.organs/hands.fileactions/fileactions.ts.CHANGELOG +25 -0
- package/core/individual.bio.organs/hands.fileactions/fileactions.ts.SPEC +30 -0
- package/core/individual.bio.organs/hands.fileactions/filewatch.ts +66 -0
- package/core/individual.bio.organs/hands.fileactions/filewatch.ts.SPEC +3 -0
- package/core/individual.bio.organs/hands.main/main.ts +18 -0
- package/core/individual.bio.organs/hands.main/main.ts.SPEC +20 -0
- package/core/individual.bio.organs/hands.sensitive/sensitive.ts +24 -0
- package/core/individual.bio.organs/hands.sensitive/sensitive.ts.CHANGELOG +2 -0
- package/core/individual.bio.organs/hands.sensitive/sensitive.ts.SPEC +3 -0
- package/core/individual.bio.organs/heart.interrupt/agent_start-loader-flicker.patch +13 -0
- package/core/individual.bio.organs/heart.interrupt/interactive-mode-loader.patch +11 -0
- package/core/individual.bio.organs/heart.interrupt/runner_esc.patch +10 -0
- package/core/individual.bio.organs/heart.interrupt/runner_esc.patch.SPEC +9 -0
- package/core/individual.bio.organs/heart.kernel/kernel.ts +253 -0
- package/core/individual.bio.organs/heart.kernel/kernel.ts.CHANGELOG +13 -0
- package/core/individual.bio.organs/heart.kernel/kernel.ts.SPEC +23 -0
- package/core/individual.bio.organs/heart.main/heart.main.CHANGELOG +43 -0
- package/core/individual.bio.organs/heart.main/heartbeat.ts +494 -0
- package/core/individual.bio.organs/heart.main/heartbeat.ts.LESSON +43 -0
- package/core/individual.bio.organs/heart.main/main.ts +8 -0
- package/core/individual.bio.organs/heart.main/main.ts.SPEC +19 -0
- package/core/individual.bio.organs/heart.main/process.ts +122 -0
- package/core/individual.bio.organs/heart.main/process.ts.CHANGELOG +2 -0
- package/core/individual.bio.organs/heart.main/process.ts.SPEC +24 -0
- package/core/individual.bio.organs/heart.main/remove_timeout.patch +110 -0
- package/core/individual.bio.organs/heart.main/stop.ts.CHANGELOG +3 -0
- package/core/individual.bio.organs/heart.main/stop.ts.SPEC +28 -0
- package/core/individual.bio.organs/mouth.speak/index.ts +1 -0
- package/core/individual.bio.organs/mouth.speak/index.ts.CHANGELOG +1 -0
- package/core/individual.bio.organs/mouth.speak/index.ts.SPEC +6 -0
- package/core/individual.bio.organs/mouth.speak/mouth.ts.CHANGELOG +15 -0
- package/core/individual.bio.organs/mouth.speak/mouth_recorder.py.CHANGELOG +1 -0
- package/core/individual.bio.organs/mouth.speak/speak.ts +180 -0
- package/core/individual.bio.organs/mouth.speak/speak.ts.SPEC +3 -0
- package/core/individual.bio.organs/mouth.speak/speak_recorder.py +35 -0
- package/core/individual.bio.organs/mouth.speak/speak_recorder.py.SPEC +3 -0
- package/core/individual.bio.organs/organs.README +110 -0
- package/core/package-lock.json +18 -0
- package/core/package.json +35 -0
- package/core/prompts/prompts.json +77 -0
- package/core/prompts/prompts.ts +44 -0
- package/core/prompts/prompts.ts.SPEC +9 -0
- package/core/society.world/.gitkeep +0 -0
- package/core/society.world/accessibility.claudecode/message-service.cjs +217 -0
- package/core/society.world/accessibility.claudecode/message-service.cjs.SPEC +7 -0
- package/core/society.world/accessibility.claudecode/send.ts +34 -0
- package/core/society.world/dollar.distribution.ubi/ubi.ts +55 -0
- package/core/society.world/dollar.main/dollar-service.cjs +185 -0
- package/core/society.world/dollar.main/main.ts +116 -0
- package/core/society.world/dollar.transaction/transaction.ts +71 -0
- package/core/society.world/space/space.ts +206 -0
- package/core/society.world/space/space.ts.SPEC +30 -0
- package/core/technology.laptop/#agent.macos.BLUEPRINT +278 -0
- package/core/technology.phone/apps.preinstalled/albums.FUTURE/albums.ts +69 -0
- package/core/technology.phone/apps.preinstalled/albums.FUTURE/albums.ts.SPEC +15 -0
- package/core/technology.phone/apps.preinstalled/calendar/calendar.ts +406 -0
- package/core/technology.phone/apps.preinstalled/calendar/calendar.ts.SPEC +22 -0
- package/core/technology.phone/apps.preinstalled/calendar/holiday-calendar.ts +529 -0
- package/core/technology.phone/apps.preinstalled/clock/clock.ts +132 -0
- package/core/technology.phone/apps.preinstalled/clock/clock.ts.SPEC +11 -0
- package/core/technology.phone/apps.preinstalled/contacts.FUTURE/contacts.ts +300 -0
- package/core/technology.phone/apps.preinstalled/contacts.FUTURE/contacts.ts.SPEC +22 -0
- package/core/technology.phone/apps.preinstalled/developer.FUTURE/developer.ts +22 -0
- package/core/technology.phone/apps.preinstalled/developer.FUTURE/developer.ts.SPEC +15 -0
- package/core/technology.phone/apps.preinstalled/notes/notes.ts +239 -0
- package/core/technology.phone/apps.preinstalled/notes/notes.ts.SPEC +21 -0
- package/core/technology.phone/apps.preinstalled/polymarket/polymarket.ts +261 -0
- package/core/technology.phone/apps.preinstalled/polymarket/polymarket.ts.SPEC +7 -0
- package/core/technology.phone/apps.preinstalled/reminder/reminder.ts +404 -0
- package/core/technology.phone/apps.preinstalled/reminder/reminder.ts.SPEC +25 -0
- package/core/technology.phone/apps.preinstalled/siri.FUTURE/siri.ts +22 -0
- package/core/technology.phone/apps.preinstalled/siri.FUTURE/siri.ts.SPEC +15 -0
- package/core/technology.phone/apps.preinstalled/spotlight/spotlight.ts +29 -0
- package/core/technology.phone/apps.preinstalled/spotlight/spotlight.ts.SPEC +7 -0
- package/core/technology.phone/apps.preinstalled/steam/chess.ts +230 -0
- package/core/technology.phone/apps.preinstalled/steam/snake.ts +100 -0
- package/core/technology.phone/apps.preinstalled/steam/snake.ts.SPEC +7 -0
- package/core/technology.phone/apps.preinstalled/steam/spy-cmd.ts +4 -0
- package/core/technology.phone/apps.preinstalled/steam/spy-tool.ts +56 -0
- package/core/technology.phone/apps.preinstalled/steam/spy.ts +302 -0
- package/core/technology.phone/apps.preinstalled/steam/steam.ts +299 -0
- package/core/technology.phone/apps.preinstalled/weather/weather.ts +50 -0
- package/core/technology.phone/apps.preinstalled/weather/weather.ts.SPEC +9 -0
- package/core/technology.phone/apps.preinstalled/wechat/imessage.ts +423 -0
- package/core/technology.phone/apps.preinstalled/wechat/imessage.ts.SPEC +24 -0
- package/core/technology.phone/apps.system/appstore/appstore.ts +22 -0
- package/core/technology.phone/apps.system/appstore/appstore.ts.SPEC +15 -0
- package/core/technology.phone/apps.system/finder.FUTURE/finder.ts +64 -0
- package/core/technology.phone/apps.system/finder.FUTURE/finder.ts.SPEC +8 -0
- package/core/technology.phone/apps.system/safari/safari-app.ts +146 -0
- package/core/technology.phone/apps.system/safari/safari.ts.SPEC +24 -0
- package/core/technology.phone/apps.system/settings/settings.ts +126 -0
- package/core/technology.phone/apps.system/settings/settings.ts.SPEC +17 -0
- package/core/technology.phone/apps.system/tips/tips.ts +22 -0
- package/core/technology.phone/apps.system/tips/tips.ts.SPEC +15 -0
- package/core/technology.phone/apps.thirdparty/alipay/alipay.ts +148 -0
- package/core/technology.phone/apps.thirdparty/alipay/alipay.ts.SPEC +7 -0
- package/core/technology.phone/apps.thirdparty/bilibili/bilibili-app.ts +33 -0
- package/core/technology.phone/apps.thirdparty/bilibili/bilibili.ts +142 -0
- package/core/technology.phone/apps.thirdparty/bilibili/bilibili.ts.SPEC +22 -0
- package/core/technology.phone/apps.thirdparty/wechatread/wechatread.ts +80 -0
- package/core/technology.phone/apps.thirdparty/wechatread/wechatread.ts.SPEC +15 -0
- package/core/technology.phone/index.ts +1 -0
- package/core/technology.phone/package.json +2 -0
- package/core/technology.phone/system.homepage/homepage.ts +247 -0
- package/core/technology.phone/system.homepage/homepage.ts.SPEC +22 -0
- package/core/technology.phone/system.kernel/kernel.ts +264 -0
- package/core/technology.phone/system.kernel/kernel.ts.SPEC +7 -0
- package/core/technology.phone/system.notifications/notifications.ts +87 -0
- package/core/technology.phone/system.notifications/notifications.ts.SPEC +7 -0
- package/core/technology.phone/system.share/share.ts +46 -0
- package/core/technology.phone/system.share/share.ts.SPEC +7 -0
- package/core/technology.server/browser-service.cjs +152 -0
- package/core/technology.server/data/cookies/arxiv.json +30 -0
- package/core/technology.server/data/cookies/bili.json +184 -0
- package/core/technology.server/data/cookies/default.json +30 -0
- package/core/technology.server/data/cookies/news.json +113 -0
- package/core/technology.server/data/cookies/s1.json +45 -0
- package/core/technology.server/data/cookies/safari.json +184 -0
- package/core/technology.server/data/cookies/safari2.json +1 -0
- package/core/technology.server/data/cookies/safaridbg.json +1 -0
- package/core/technology.server/data/cookies/search.json +45 -0
- package/core/technology.server/data/cookies/sw.json +30 -0
- package/core/technology.server/data/cookies/t1.json +1 -0
- package/core/technology.server/data/cookies/testread.json +1 -0
- package/core/technology.server/data/cookies/video1.json +113 -0
- package/core/technology.server/data/cookies/yt.json +170 -0
- package/core/technology.server/data/cookies/yt2.json +113 -0
- package/core/technology.server/pikipedia/#pikipedia.BLUEPRINT +106 -0
- package/core/technology.server/playleft.cjs +247 -0
- package/core/technology.server/search-proxy.py +76 -0
- package/core/technology.server/server.README +59 -0
- package/deploy/dist-overrides/cli/cli.js +18 -0
- package/deploy/dist-overrides/core/extensions/loader.js +518 -0
- package/deploy/dist-overrides/core/package-manager.js +2081 -0
- package/deploy/dist-overrides/core/system-prompt.js +109 -0
- package/deploy/dist-overrides/core/system-prompt.js.LESSON +17 -0
- package/deploy/dist-overrides/core/tools/bash.js +353 -0
- package/deploy/dist-overrides/core/tools/bash.js.CHANGELOG +2 -0
- package/deploy/dist-overrides/core/tools/edit-diff.js +345 -0
- package/deploy/dist-overrides/core/tools/edit.js +315 -0
- package/deploy/dist-overrides/core/tools/edit.js.CHANGELOG +1 -0
- package/deploy/dist-overrides/core/tools/file-mutation-queue.js +52 -0
- package/deploy/dist-overrides/core/tools/find.js +298 -0
- package/deploy/dist-overrides/core/tools/find.js.CHANGELOG +1 -0
- package/deploy/dist-overrides/core/tools/grep.js +305 -0
- package/deploy/dist-overrides/core/tools/grep.js.CHANGELOG +1 -0
- package/deploy/dist-overrides/core/tools/index.js +112 -0
- package/deploy/dist-overrides/core/tools/ls-guard.js +4 -0
- package/deploy/dist-overrides/core/tools/ls.js +170 -0
- package/deploy/dist-overrides/core/tools/ls.js.CHANGELOG +1 -0
- package/deploy/dist-overrides/core/tools/output-accumulator.js +184 -0
- package/deploy/dist-overrides/core/tools/path-utils.js +99 -0
- package/deploy/dist-overrides/core/tools/prompts-reader.js +53 -0
- package/deploy/dist-overrides/core/tools/read.js +392 -0
- package/deploy/dist-overrides/core/tools/read.js.CHANGELOG +1 -0
- package/deploy/dist-overrides/core/tools/render-utils.js +65 -0
- package/deploy/dist-overrides/core/tools/tool-definition-wrapper.js +34 -0
- package/deploy/dist-overrides/core/tools/truncate.js +215 -0
- package/deploy/dist-overrides/core/tools/write.js +203 -0
- package/deploy/dist-overrides/core/tools/write.js.CHANGELOG +1 -0
- package/deploy/dist-overrides/dist-overrides.README +18 -0
- package/deploy/dist-overrides/main.js +665 -0
- package/deploy/dist-overrides/modes/interactive/components/assistant-message.js +139 -0
- package/deploy/dist-overrides/modes/interactive/components/footer.js +326 -0
- package/deploy/dist-overrides/modes/interactive/components/model-selector.js +285 -0
- package/deploy/dist-overrides/modes/interactive/components/tool-execution.js +383 -0
- package/deploy/dist-overrides/modes/interactive/components/tool-execution.js.CHANGELOG +3 -0
- package/deploy/dist-overrides/modes/interactive/interactive-mode.js +4781 -0
- package/deploy/dist-overrides/pi-ai/providers/anthropic.js +931 -0
- package/deploy/dist-overrides/pi-ai/providers/openai-completions.js +1007 -0
- package/deploy/dist-overrides/pi-ai/providers/openai-completions.js.LESSON +15 -0
- package/deploy/dist-overrides/pi-tui/components/loader.js +69 -0
- package/deploy/dist-overrides/pi-tui/components/markdown.js +646 -0
- package/deploy/dist-overrides/pi-tui/components/text.js +92 -0
- package/deploy/dist-overrides/pi-tui/custom-message.js +75 -0
- package/deploy/dist-overrides/pi-tui/tui.js +1266 -0
- package/deploy/dist-overrides/pi-tui/utils.js +1060 -0
- package/deploy/dist-overrides/vendor.REMOVED/jiti/lib/jiti.mjs +3 -0
- package/deploy/install.sh +186 -0
- package/deploy/install.sh.CHANGELOG +6 -0
- package/deploy/lint/lint-naming.ts +202 -0
- package/deploy/scripts/apply.js +18 -0
- package/deploy/scripts/build-github.sh +219 -0
- package/deploy/scripts/build-phone.sh +24 -0
- package/deploy/scripts/check-deploy.sh +42 -0
- package/deploy/scripts/migrate-context.sh +72 -0
- package/deploy/scripts/patch-pi-dist.js +39 -0
- package/deploy/scripts/uninstall.sh +34 -0
- package/package.json +18 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// steam/spy.ts — Steam 游戏平台(谁是卧底 + 国际象棋)
|
|
2
|
+
|
|
3
|
+
import type { PhoneApp } from "../../phone.ts";
|
|
4
|
+
import { SpyGame } from "./engine.ts";
|
|
5
|
+
import { ChessGame } from "./chess.ts";
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
// ── LLM bot ──
|
|
11
|
+
|
|
12
|
+
function getApiKey(): string | null {
|
|
13
|
+
try {
|
|
14
|
+
const cfg = JSON.parse(readFileSync(join(homedir(), ".pi/agent/models.json"), "utf8"));
|
|
15
|
+
let k = cfg?.providers?.deepseek?.apiKey;
|
|
16
|
+
if (typeof k === "string" && k.startsWith("$")) k = process.env[k.slice(1)] ?? "";
|
|
17
|
+
if (typeof k === "string" && k.trim()) return k.trim();
|
|
18
|
+
} catch {}
|
|
19
|
+
return process.env.DEEPSEEK_API_KEY || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function llm(system: string, user: string): Promise<string> {
|
|
23
|
+
const key = getApiKey();
|
|
24
|
+
if (!key) return "";
|
|
25
|
+
try {
|
|
26
|
+
const res = await Promise.race([
|
|
27
|
+
fetch("https://api.deepseek.com/chat/completions", {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
30
|
+
body: JSON.stringify({
|
|
31
|
+
model: "deepseek-v4-flash",
|
|
32
|
+
messages: [{ role: "system", content: system }, { role: "user", content: user }],
|
|
33
|
+
max_tokens: 300, temperature: 0.9,
|
|
34
|
+
}),
|
|
35
|
+
signal: AbortSignal.timeout(3000),
|
|
36
|
+
}),
|
|
37
|
+
new Promise<null>((r) => setTimeout(() => r(null), 3000)),
|
|
38
|
+
]);
|
|
39
|
+
if (!res) return "";
|
|
40
|
+
const j = await res.json() as any;
|
|
41
|
+
return (j.choices?.[0]?.message?.content || "").trim().replace(/^["「]|["」]$/g, "");
|
|
42
|
+
} catch { return ""; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 本地 fallback:LLM 不可用时,根据词生成简单描述
|
|
46
|
+
const LOCAL_FALLBACKS: Record<string, string[]> = {
|
|
47
|
+
"薯条": ["炸的", "蘸番茄酱吃"], "薯片": ["袋装", "脆的零食"],
|
|
48
|
+
"眼镜": ["戴在脸上", "帮助看清东西"], "墨镜": ["遮阳", "夏天常戴"],
|
|
49
|
+
"牛奶": ["白色液体", "补钙"], "豆浆": ["豆制品", "早餐喝"],
|
|
50
|
+
"西瓜": ["绿色外皮", "夏天吃"], "哈密瓜": ["黄色果肉", "甜瓜"],
|
|
51
|
+
"口红": ["涂抹嘴唇", "美妆"], "唇膏": ["滋润", "防干裂"],
|
|
52
|
+
"高铁": ["速度快", "出远门坐"], "动车": ["也是快的火车", "短途多"],
|
|
53
|
+
"微信": ["聊天", "绿色图标"], "QQ": ["聊天软件", "企鹅图标"],
|
|
54
|
+
"面包": ["烤的", "西式主食"], "馒头": ["蒸的", "中式主食"],
|
|
55
|
+
"咖啡": ["苦的", "提神醒脑"], "奶茶": ["甜的", "年轻人爱喝"],
|
|
56
|
+
"蜡烛": ["能点燃", "照明用"], "火把": ["手持燃烧", "古代照明"],
|
|
57
|
+
"护照": ["出国用", "一本蓝色"], "身份证": ["国内用", "随身携带"],
|
|
58
|
+
"沙发": ["软的", "客厅家具"], "椅子": ["硬的", "可以坐"],
|
|
59
|
+
"钢笔": ["蘸墨水", "写字"], "毛笔": ["软笔头", "书法"],
|
|
60
|
+
"足球": ["用脚踢", "圆的"], "篮球": ["用手拍", "橙色的"],
|
|
61
|
+
"医生": ["看病", "白大褂"], "护士": ["护理", "白衣天使"],
|
|
62
|
+
"月亮": ["晚上看", "会变圆缺"], "太阳": ["白天", "发光发热"],
|
|
63
|
+
"空调": ["制冷", "挂在墙上"], "风扇": ["转的", "吹风"],
|
|
64
|
+
"地铁": ["地下跑", "通勤"], "公交": ["地上跑", "等车"],
|
|
65
|
+
"饺子": ["皮包馅", "过年吃"], "馄饨": ["汤里", "皮更薄"],
|
|
66
|
+
"雪碧": ["柠檬味", "绿色瓶"], "七喜": ["也是柠檬味", "白瓶"],
|
|
67
|
+
};
|
|
68
|
+
function localFallback(word: string): string {
|
|
69
|
+
const opts = LOCAL_FALLBACKS[word];
|
|
70
|
+
if (!opts) return "这个东西挺常见的";
|
|
71
|
+
return opts[Math.floor(Math.random() * opts.length)];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function botDescribe(name: string, word: string, isSpy: boolean, round: number, prevDescs: { player: string; text: string }[]): Promise<string> {
|
|
75
|
+
const history = prevDescs.length > 0 ? "前面的描述:\n" + prevDescs.map(d => `${d.player}: "${d.text}"`).join("\n") : "你是第一个。";
|
|
76
|
+
const sys = `你在玩谁是卧底。你的词是「${word}」。用一句话描述,不能说出词语本身或其中任何一个字。绝对不能抄袭或复述别人说过的描述,必须用完全不同的角度。${isSpy ? "你是卧底,描述要模糊但合理,让人觉得你拿到的词和大家一样。" : "你是平民,描述要有区分度但别太明显。"}只输出描述,限15字。`;
|
|
77
|
+
const llmResult = await llm(sys, `第${round}轮。${history}\n轮到${name}。`);
|
|
78
|
+
if (llmResult) return llmResult;
|
|
79
|
+
// 本地 fallback:取不一样的描述
|
|
80
|
+
const used = new Set(prevDescs.map(d => d.text));
|
|
81
|
+
const opts = LOCAL_FALLBACKS[word];
|
|
82
|
+
if (opts) {
|
|
83
|
+
for (const o of opts) if (!used.has(o)) return o;
|
|
84
|
+
return opts[0];
|
|
85
|
+
}
|
|
86
|
+
return "这个东西挺常见的";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function botVote(name: string, word: string, isSpy: boolean, alive: string[], descs: { player: string; text: string }[]): Promise<string> {
|
|
90
|
+
const candidates = alive.filter(id => id !== name);
|
|
91
|
+
if (candidates.length === 0) return "";
|
|
92
|
+
const descText = descs.map(d => `${d.player}: "${d.text}"`).join("\n");
|
|
93
|
+
const sys = `你在玩谁是卧底。你的词是「${word}」。${isSpy ? "你是卧底,投掉一个平民。" : "找描述最不一样的人。"}只回复一个名字。`;
|
|
94
|
+
const reply = await llm(sys, `描述:\n${descText}\n从 ${candidates.join("、")} 中选。`);
|
|
95
|
+
if (reply) return candidates.find(c => reply.includes(c)) || candidates[Math.floor(Math.random() * candidates.length)];
|
|
96
|
+
return candidates[Math.floor(Math.random() * candidates.length)];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── runBots:非阻塞,后台运行,完成时自动更新状态 ──
|
|
100
|
+
let botRunning = false;
|
|
101
|
+
async function runBots(game: SpyGame, agentId: string, onUpdate?: () => void): Promise<void> {
|
|
102
|
+
if (botRunning) return;
|
|
103
|
+
botRunning = true;
|
|
104
|
+
try {
|
|
105
|
+
// 描述阶段:串行(每个 bot 需要看到前面的描述)
|
|
106
|
+
while (game.phase === "describe") {
|
|
107
|
+
const current = game.getState().currentTurn;
|
|
108
|
+
if (!current || current === agentId) break;
|
|
109
|
+
const p = game.players.find(x => x.id === current);
|
|
110
|
+
if (!p) break;
|
|
111
|
+
game.describe(current, await botDescribe(current, p.word, p.isSpy, game.round, game.currentRoundDescs));
|
|
112
|
+
onUpdate?.();
|
|
113
|
+
}
|
|
114
|
+
// 投票阶段:并行
|
|
115
|
+
if (game.phase === "vote") {
|
|
116
|
+
const alive = game.getState().alivePlayers;
|
|
117
|
+
await Promise.all(
|
|
118
|
+
alive.filter(pid => pid !== agentId && !game.votes.find(v => v.voter === pid))
|
|
119
|
+
.map(async pid => {
|
|
120
|
+
const p = game.players.find(x => x.id === pid)!;
|
|
121
|
+
game.vote(pid, await botVote(pid, p.word, p.isSpy, alive, game.currentRoundDescs));
|
|
122
|
+
onUpdate?.();
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
onUpdate?.();
|
|
127
|
+
} finally {
|
|
128
|
+
botRunning = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Steam 状态 ──
|
|
133
|
+
|
|
134
|
+
// ── 游戏注册表 ──
|
|
135
|
+
|
|
136
|
+
interface SteamGameDef {
|
|
137
|
+
name: string;
|
|
138
|
+
desc: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const GAMES: SteamGameDef[] = [
|
|
142
|
+
{ name: "谁是卧底", desc: "和 AI 对战的推理游戏" },
|
|
143
|
+
{ name: "国际象棋", desc: "和 AI 对弈(minimax)" },
|
|
144
|
+
{ name: "贪吃蛇", desc: "经典贪吃蛇" },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
function menu(): string {
|
|
148
|
+
const lines = ["═══ Steam 游戏平台 ═══", ""];
|
|
149
|
+
for (const g of GAMES) lines.push(` ${g.name} — ${g.desc}`);
|
|
150
|
+
lines.push("", "输入游戏名,或「返回」回主屏幕");
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function matchGame(input: string): SteamGameDef | null {
|
|
155
|
+
return GAMES.find(g => input.includes(g.name)) || null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface SteamState {
|
|
159
|
+
cur: string | null;
|
|
160
|
+
spy: SpyGame | null;
|
|
161
|
+
chess: ChessGame | null;
|
|
162
|
+
snake: any;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function norm(raw: any): SteamState {
|
|
166
|
+
if (raw?.cur !== undefined) return raw;
|
|
167
|
+
if (raw?.game) return { cur: '谁是卧底', spy: raw.game, chess: null, snake: null };
|
|
168
|
+
return { cur: null, spy: null, chess: null, snake: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Tool 导出(manifest 用)──
|
|
172
|
+
// 游戏逻辑由 Steam 手机 app 管理;这里只提供入口引导
|
|
173
|
+
export async function spyCmd(args: any, _ctx: any, _personDir: string): Promise<{ content: any[]; details: any }> {
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: "谁是卧底\n\n请在手机 Steam 应用中打开「谁是卧底」开始游戏。\n手机 → Steam → 谁是卧底" }],
|
|
176
|
+
details: {},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const app: PhoneApp = {
|
|
181
|
+
name: "Steam",
|
|
182
|
+
icon: "",
|
|
183
|
+
messageDescription: "游戏平台",
|
|
184
|
+
|
|
185
|
+
onOpen(state: any, personDir: string) {
|
|
186
|
+
const s = norm(state);
|
|
187
|
+
if (s.cur === "谁是卧底" && s.spy && s.spy.phase !== "end") return { screen: s.spy.getPlayerView("你"), state: s };
|
|
188
|
+
if (s.cur === "国际象棋") {
|
|
189
|
+
if (!s.chess || s.chess.s.over) s.chess = loadPersisted(personDir) || new ChessGame();
|
|
190
|
+
return { screen: s.chess.screen(), state: s };
|
|
191
|
+
}
|
|
192
|
+
if (s.cur === "贪吃蛇" && s.snake && !s.snake.over) return { screen: s.snake.screen(), state: s };
|
|
193
|
+
s.cur = null;
|
|
194
|
+
return { screen: menu(), state: s };
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
async onAction(input: string, rawState: any, personDir: string) {
|
|
198
|
+
const s = norm(rawState);
|
|
199
|
+
const trimmed = input.trim();
|
|
200
|
+
const agentId = "你";
|
|
201
|
+
|
|
202
|
+
if (/^(菜单|menu|返回|back)$/i.test(trimmed)) { s.cur = null; s.spy = null; return { screen: menu(), state: s }; }
|
|
203
|
+
|
|
204
|
+
// ── 游戏选择 ──
|
|
205
|
+
if (!s.cur) {
|
|
206
|
+
const hit = matchGame(trimmed);
|
|
207
|
+
if (!hit) return { screen: menu() + "\n\n请选择游戏。", state: s };
|
|
208
|
+
s.cur = hit.name;
|
|
209
|
+
if (hit.name === "国际象棋") {
|
|
210
|
+
s.chess = loadPersisted(personDir) || new ChessGame();
|
|
211
|
+
return { screen: s.chess.screen(), state: s };
|
|
212
|
+
}
|
|
213
|
+
if (hit.name === "贪吃蛇") {
|
|
214
|
+
s.snake = await newSnake();
|
|
215
|
+
return { screen: s.snake.screen(), state: s };
|
|
216
|
+
}
|
|
217
|
+
if (hit.name === "谁是卧底") {
|
|
218
|
+
if (s.spy && s.spy.phase !== "end") return { screen: s.spy.getPlayerView(agentId), state: s };
|
|
219
|
+
return { screen: "═══ 谁是卧底 ═══\n\n输入「新游戏」或「新游戏 5」开始\n「菜单」回 Steam", state: s };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── 谁是卧底 ──
|
|
224
|
+
if (s.cur === "谁是卧底") {
|
|
225
|
+
const newMatch = trimmed.match(/^新游戏\s*(\d+)?$/) || trimmed.match(/^new\s*(\d+)?$/i);
|
|
226
|
+
if (newMatch) {
|
|
227
|
+
const count = Math.max(3, Math.min(7, parseInt(newMatch[1] || "5")));
|
|
228
|
+
const botNames = ["甲", "乙", "丙", "丁", "戊", "己"].slice(0, count - 1);
|
|
229
|
+
s.spy = new SpyGame([agentId, ...botNames]);
|
|
230
|
+
// 非阻塞:bot 后台运行,不卡 UI
|
|
231
|
+
runBots(s.spy, agentId);
|
|
232
|
+
return { screen: s.spy.getPlayerView(agentId) + "\nAI 玩家思考中...", state: s };
|
|
233
|
+
}
|
|
234
|
+
if (!s.spy || s.spy.phase === "end") {
|
|
235
|
+
if (s.spy?.phase === "end") return { screen: s.spy.getPlayerView(agentId) + "\n\n输入「新游戏」再来一局,或「菜单」回 Steam。", state: s };
|
|
236
|
+
return { screen: "没有进行中的游戏。输入「新游戏」开始。", state: s };
|
|
237
|
+
}
|
|
238
|
+
const game = s.spy;
|
|
239
|
+
if (game.phase === "vote") {
|
|
240
|
+
const alive = game.getState().alivePlayers.filter((id: string) => id !== agentId);
|
|
241
|
+
const target = alive.find((name: string) => trimmed.includes(name));
|
|
242
|
+
if (target) {
|
|
243
|
+
const result = game.vote(agentId, target);
|
|
244
|
+
if (!result.ok) return { screen: `${result.error}`, state: s };
|
|
245
|
+
runBots(game, agentId);
|
|
246
|
+
return { screen: `${result.message}\n\n${game.getPlayerView(agentId)}`, state: s };
|
|
247
|
+
}
|
|
248
|
+
return { screen: `请投票: ${alive.join(", ")}`, state: s };
|
|
249
|
+
}
|
|
250
|
+
if (game.phase === "describe" && game.getState().currentTurn === agentId) {
|
|
251
|
+
const desc = trimmed.replace(/^(describe|描述)\s*/i, "").trim();
|
|
252
|
+
if (!desc) return { screen: "描述不能为空。输入你对词语的描述。", state: s };
|
|
253
|
+
const result = game.describe(agentId, desc);
|
|
254
|
+
if (!result.ok) return { screen: `${result.error}`, state: s };
|
|
255
|
+
runBots(game, agentId);
|
|
256
|
+
return { screen: `${result.message}\n\n${game.getPlayerView(agentId)}`, state: s };
|
|
257
|
+
}
|
|
258
|
+
// 默认返回当前状态
|
|
259
|
+
let screen = game.getPlayerView(agentId);
|
|
260
|
+
if (botRunning) screen += "\nAI 玩家思考中...";
|
|
261
|
+
return { screen, state: s };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── 贪吃蛇 ──
|
|
265
|
+
if (s.cur === "贪吃蛇") {
|
|
266
|
+
if (!s.snake) s.snake = await newSnake();
|
|
267
|
+
if (/^(新局|new)$/i.test(trimmed)) { s.snake = await newSnake(); return { screen: s.snake.screen(), state: s }; }
|
|
268
|
+
const dirMap: Record<string, 'u'|'d'|'l'|'r'> = { w: 'u', s: 'd', a: 'l', d: 'r', up: 'u', down: 'd', left: 'l', right: 'r' };
|
|
269
|
+
const dir = dirMap[trimmed.toLowerCase()];
|
|
270
|
+
if (dir) s.snake.input(dir);
|
|
271
|
+
return { screen: s.snake.screen(), state: s };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── 国际象棋 ──
|
|
275
|
+
if (s.cur === "国际象棋") {
|
|
276
|
+
if (!s.chess) s.chess = loadPersisted(personDir) || new ChessGame();
|
|
277
|
+
if (/^(新局|new)$/i.test(trimmed)) { s.chess = new ChessGame(); clearPersisted(personDir); return { screen: s.chess.screen(), state: s }; }
|
|
278
|
+
if (/^(认输|resign)$/i.test(trimmed)) { s.chess.resign(); persist(s.chess, personDir); return { screen: s.chess.screen(), state: s }; }
|
|
279
|
+
const r = s.chess.move(trimmed);
|
|
280
|
+
if (!r.ok) return { screen: s.chess.screen() + `\n\n${r.error}`, state: s };
|
|
281
|
+
persist(s.chess, personDir);
|
|
282
|
+
return { screen: s.chess.screen() + (r.ai ? `\n\n对手走: ${r.ai}` : ""), state: s };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { screen: menu(), state: s };
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// ── 持久化 ──
|
|
290
|
+
function persist(game: ChessGame, personDir: string) {
|
|
291
|
+
try { mkdirSync(personDir, { recursive: true }); writeFileSync(join(personDir, "steam.json"), JSON.stringify({ chess: game.toJSON() })); } catch {}
|
|
292
|
+
}
|
|
293
|
+
function loadPersisted(personDir: string): ChessGame | null {
|
|
294
|
+
try { const raw = JSON.parse(readFileSync(join(personDir, "steam.json"), "utf8")); return raw?.chess ? ChessGame.fromJSON(raw.chess) : null; } catch { return null; }
|
|
295
|
+
}
|
|
296
|
+
function clearPersisted(personDir: string) { try { writeFileSync(join(personDir, "steam.json"), "{}"); } catch {} }
|
|
297
|
+
|
|
298
|
+
// ── 动态加载蛇(破 jiti 缓存)──
|
|
299
|
+
async function newSnake(): Promise<any> {
|
|
300
|
+
const { SnakeGame } = await import(`./snake.js?t=${Date.now()}`);
|
|
301
|
+
return new SnakeGame();
|
|
302
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// spy/engine.ts — 谁是卧底游戏引擎(纯逻辑,零 LLM,零 I/O)
|
|
2
|
+
// 所有状态变更通过方法调用,所有非法操作返回错误,不抛异常。
|
|
3
|
+
|
|
4
|
+
export interface WordPair { spy: string; civilian: string; hint?: string }
|
|
5
|
+
|
|
6
|
+
export interface Player {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
isSpy: boolean;
|
|
10
|
+
word: string;
|
|
11
|
+
alive: boolean;
|
|
12
|
+
messageDescriptions: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type Phase = "describe" | "vote" | "end";
|
|
16
|
+
export type Winner = "spy" | "civilian" | null;
|
|
17
|
+
|
|
18
|
+
export interface VoteRecord { voter: string; target: string }
|
|
19
|
+
export interface RoundRecord {
|
|
20
|
+
round: number;
|
|
21
|
+
messageDescriptions: { player: string; text: string }[];
|
|
22
|
+
votes: VoteRecord[];
|
|
23
|
+
eliminated: string | null;
|
|
24
|
+
tiebreak: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GameState {
|
|
28
|
+
phase: Phase;
|
|
29
|
+
round: number;
|
|
30
|
+
alivePlayers: string[];
|
|
31
|
+
currentTurn: string | null;
|
|
32
|
+
describeOrder: string[];
|
|
33
|
+
describedThisRound: string[];
|
|
34
|
+
votedThisRound: string[];
|
|
35
|
+
winner: Winner;
|
|
36
|
+
rounds: RoundRecord[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ActionResult {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
error?: string;
|
|
42
|
+
state: GameState;
|
|
43
|
+
message?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const BUILTIN_WORDS: WordPair[] = [
|
|
47
|
+
{ spy: "薯条", civilian: "薯片" },
|
|
48
|
+
{ spy: "眼镜", civilian: "墨镜" },
|
|
49
|
+
{ spy: "牛奶", civilian: "豆浆" },
|
|
50
|
+
{ spy: "西瓜", civilian: "哈密瓜" },
|
|
51
|
+
{ spy: "口红", civilian: "唇膏" },
|
|
52
|
+
{ spy: "高铁", civilian: "动车" },
|
|
53
|
+
{ spy: "微信", civilian: "QQ" },
|
|
54
|
+
{ spy: "面包", civilian: "馒头" },
|
|
55
|
+
{ spy: "咖啡", civilian: "奶茶" },
|
|
56
|
+
{ spy: "蜡烛", civilian: "火把" },
|
|
57
|
+
{ spy: "护照", civilian: "身份证" },
|
|
58
|
+
{ spy: "沙发", civilian: "椅子" },
|
|
59
|
+
{ spy: "钢笔", civilian: "毛笔" },
|
|
60
|
+
{ spy: "足球", civilian: "篮球" },
|
|
61
|
+
{ spy: "医生", civilian: "护士" },
|
|
62
|
+
{ spy: "月亮", civilian: "太阳" },
|
|
63
|
+
{ spy: "空调", civilian: "风扇" },
|
|
64
|
+
{ spy: "地铁", civilian: "公交" },
|
|
65
|
+
{ spy: "饺子", civilian: "馄饨" },
|
|
66
|
+
{ spy: "雪碧", civilian: "七喜" },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
function shuffle<T>(arr: T[]): T[] {
|
|
70
|
+
const a = [...arr];
|
|
71
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
72
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
73
|
+
[a[i], a[j]] = [a[j], a[i]];
|
|
74
|
+
}
|
|
75
|
+
return a;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class SpyGame {
|
|
79
|
+
players: Player[] = [];
|
|
80
|
+
phase: Phase = "describe";
|
|
81
|
+
round = 1;
|
|
82
|
+
describeOrder: string[] = [];
|
|
83
|
+
describeIdx = 0;
|
|
84
|
+
votes: VoteRecord[] = [];
|
|
85
|
+
rounds: RoundRecord[] = [];
|
|
86
|
+
currentRoundDescs: { player: string; text: string }[] = [];
|
|
87
|
+
winner: Winner = null;
|
|
88
|
+
wordPair: WordPair;
|
|
89
|
+
|
|
90
|
+
constructor(playerNames: string[], spyCount = 1, wordPair?: WordPair) {
|
|
91
|
+
if (playerNames.length < 3) throw new Error("至少 3 个玩家");
|
|
92
|
+
if (spyCount >= playerNames.length) throw new Error("卧底数量必须少于玩家数");
|
|
93
|
+
|
|
94
|
+
this.wordPair = wordPair ?? BUILTIN_WORDS[Math.floor(Math.random() * BUILTIN_WORDS.length)];
|
|
95
|
+
const shuffled = shuffle(playerNames);
|
|
96
|
+
const spyNames = new Set(shuffled.slice(0, spyCount));
|
|
97
|
+
|
|
98
|
+
this.players = playerNames.map(name => ({
|
|
99
|
+
id: name,
|
|
100
|
+
name,
|
|
101
|
+
isSpy: spyNames.has(name),
|
|
102
|
+
word: spyNames.has(name) ? this.wordPair.spy : this.wordPair.civilian,
|
|
103
|
+
alive: true,
|
|
104
|
+
messageDescriptions: [],
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
this.describeOrder = shuffle(this.alivePlayers().map(p => p.id));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private alivePlayers(): Player[] {
|
|
111
|
+
return this.players.filter(p => p.alive);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private player(id: string): Player | undefined {
|
|
115
|
+
return this.players.find(p => p.id === id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getState(): GameState {
|
|
119
|
+
return {
|
|
120
|
+
phase: this.phase,
|
|
121
|
+
round: this.round,
|
|
122
|
+
alivePlayers: this.alivePlayers().map(p => p.id),
|
|
123
|
+
currentTurn: this.phase === "describe" ? (this.describeOrder[this.describeIdx] ?? null) : null,
|
|
124
|
+
describeOrder: this.describeOrder,
|
|
125
|
+
describedThisRound: this.currentRoundDescs.map(d => d.player),
|
|
126
|
+
votedThisRound: this.votes.map(v => v.voter),
|
|
127
|
+
winner: this.winner,
|
|
128
|
+
rounds: this.rounds,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getPlayerView(playerId: string): string {
|
|
133
|
+
const p = this.player(playerId);
|
|
134
|
+
if (!p) return "错误:你不在这局游戏里。";
|
|
135
|
+
|
|
136
|
+
const lines: string[] = [];
|
|
137
|
+
lines.push(`═══ 谁是卧底 · 第 ${this.round} 轮 ═══`);
|
|
138
|
+
lines.push(`你是: ${p.name} | 你的词: 「${p.word}」`);
|
|
139
|
+
lines.push(`存活: ${this.alivePlayers().map(a => a.id).join(", ")} (${this.alivePlayers().length}人)`);
|
|
140
|
+
lines.push("");
|
|
141
|
+
|
|
142
|
+
if (this.phase === "describe") {
|
|
143
|
+
lines.push(`── 描述阶段 ──`);
|
|
144
|
+
if (this.currentRoundDescs.length > 0) {
|
|
145
|
+
for (const d of this.currentRoundDescs) {
|
|
146
|
+
lines.push(` ${d.player}: "${d.text}"`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const currentTurn = this.describeOrder[this.describeIdx];
|
|
150
|
+
if (currentTurn === playerId) {
|
|
151
|
+
lines.push("");
|
|
152
|
+
lines.push(`→ 轮到你描述。用你自己的话描述你的词,不要直接说出词语。`);
|
|
153
|
+
lines.push(` 操作: describe <你的描述>`);
|
|
154
|
+
} else if (currentTurn) {
|
|
155
|
+
lines.push(` 等待 ${currentTurn} 描述...`);
|
|
156
|
+
}
|
|
157
|
+
} else if (this.phase === "vote") {
|
|
158
|
+
lines.push(`── 投票阶段 ──`);
|
|
159
|
+
lines.push(`本轮描述回顾:`);
|
|
160
|
+
for (const d of this.currentRoundDescs) {
|
|
161
|
+
lines.push(` ${d.player}: "${d.text}"`);
|
|
162
|
+
}
|
|
163
|
+
lines.push("");
|
|
164
|
+
if (this.votes.find(v => v.voter === playerId)) {
|
|
165
|
+
lines.push(`你已投票。等待其他人...`);
|
|
166
|
+
lines.push(`已投: ${this.votes.map(v => v.voter).join(", ")}`);
|
|
167
|
+
const notYet = this.alivePlayers().filter(a => !this.votes.find(v => v.voter === a.id));
|
|
168
|
+
if (notYet.length > 0) lines.push(`未投: ${notYet.map(a => a.id).join(", ")}`);
|
|
169
|
+
} else {
|
|
170
|
+
const candidates = this.alivePlayers().filter(a => a.id !== playerId).map(a => a.id);
|
|
171
|
+
lines.push(`→ 投票淘汰一个你认为是卧底的人。`);
|
|
172
|
+
lines.push(` 候选: ${candidates.join(", ")}`);
|
|
173
|
+
lines.push(` 操作: vote <玩家名>`);
|
|
174
|
+
}
|
|
175
|
+
} else if (this.phase === "end") {
|
|
176
|
+
lines.push(`══ 游戏结束 ══`);
|
|
177
|
+
lines.push(`胜方: ${this.winner === "spy" ? "卧底" : "平民"}`);
|
|
178
|
+
lines.push(`卧底词: 「${this.wordPair.spy}」 | 平民词: 「${this.wordPair.civilian}」`);
|
|
179
|
+
lines.push(`卧底是: ${this.players.filter(p => p.isSpy).map(p => p.name).join(", ")}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.rounds.length > 0 && this.phase !== "end") {
|
|
183
|
+
lines.push("");
|
|
184
|
+
lines.push(`── 历史 ──`);
|
|
185
|
+
for (const r of this.rounds) {
|
|
186
|
+
lines.push(` 第${r.round}轮: 淘汰 ${r.eliminated ?? "无"}${r.tiebreak ? " (平票重投)" : ""}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return lines.join("\n");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
describe(playerId: string, text: string): ActionResult {
|
|
194
|
+
if (this.phase !== "describe") return { ok: false, error: "当前不是描述阶段。", state: this.getState() };
|
|
195
|
+
if (!this.player(playerId)?.alive) return { ok: false, error: "你已被淘汰。", state: this.getState() };
|
|
196
|
+
const currentTurn = this.describeOrder[this.describeIdx];
|
|
197
|
+
if (currentTurn !== playerId) return { ok: false, error: `还没轮到你,当前轮到 ${currentTurn}。`, state: this.getState() };
|
|
198
|
+
|
|
199
|
+
const trimmed = text.trim();
|
|
200
|
+
if (!trimmed) return { ok: false, error: "描述不能为空。", state: this.getState() };
|
|
201
|
+
if (trimmed.includes(this.wordPair.spy) || trimmed.includes(this.wordPair.civilian)) {
|
|
202
|
+
return { ok: false, error: "描述中不能包含任何玩家的词语!请重新描述。", state: this.getState() };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.currentRoundDescs.push({ player: playerId, text: trimmed });
|
|
206
|
+
this.player(playerId)!.messageDescriptions.push(trimmed);
|
|
207
|
+
this.describeIdx++;
|
|
208
|
+
|
|
209
|
+
if (this.describeIdx >= this.describeOrder.length) {
|
|
210
|
+
this.phase = "vote";
|
|
211
|
+
this.votes = [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { ok: true, state: this.getState(), message: `描述已记录。` };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
vote(voterId: string, targetId: string): ActionResult {
|
|
218
|
+
if (this.phase !== "vote") return { ok: false, error: "当前不是投票阶段。", state: this.getState() };
|
|
219
|
+
if (!this.player(voterId)?.alive) return { ok: false, error: "你已被淘汰。", state: this.getState() };
|
|
220
|
+
if (this.votes.find(v => v.voter === voterId)) return { ok: false, error: "你已经投过票了。", state: this.getState() };
|
|
221
|
+
if (voterId === targetId) return { ok: false, error: "不能投自己。", state: this.getState() };
|
|
222
|
+
const target = this.player(targetId);
|
|
223
|
+
if (!target || !target.alive) return { ok: false, error: `${targetId} 不存在或已被淘汰。`, state: this.getState() };
|
|
224
|
+
|
|
225
|
+
this.votes.push({ voter: voterId, target: targetId });
|
|
226
|
+
|
|
227
|
+
if (this.votes.length >= this.alivePlayers().length) {
|
|
228
|
+
return this.resolveVotes();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { ok: true, state: this.getState(), message: `投票已记录。等待其他玩家投票。` };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private resolveVotes(): ActionResult {
|
|
235
|
+
const counts: Record<string, number> = {};
|
|
236
|
+
for (const v of this.votes) counts[v.target] = (counts[v.target] || 0) + 1;
|
|
237
|
+
|
|
238
|
+
const maxVotes = Math.max(...Object.values(counts));
|
|
239
|
+
const topPlayers = Object.entries(counts).filter(([, c]) => c === maxVotes).map(([id]) => id);
|
|
240
|
+
|
|
241
|
+
let eliminated: string | null = null;
|
|
242
|
+
let tiebreak = false;
|
|
243
|
+
|
|
244
|
+
if (topPlayers.length === 1) {
|
|
245
|
+
eliminated = topPlayers[0];
|
|
246
|
+
} else {
|
|
247
|
+
// 平票:随机淘汰一个(简化规则,完整版可以加PK)
|
|
248
|
+
eliminated = topPlayers[Math.floor(Math.random() * topPlayers.length)];
|
|
249
|
+
tiebreak = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (eliminated) {
|
|
253
|
+
const p = this.player(eliminated);
|
|
254
|
+
if (p) p.alive = false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.rounds.push({
|
|
258
|
+
round: this.round,
|
|
259
|
+
messageDescriptions: [...this.currentRoundDescs],
|
|
260
|
+
votes: [...this.votes],
|
|
261
|
+
eliminated,
|
|
262
|
+
tiebreak,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const winner = this.checkWin();
|
|
266
|
+
if (winner) {
|
|
267
|
+
this.winner = winner;
|
|
268
|
+
this.phase = "end";
|
|
269
|
+
return {
|
|
270
|
+
ok: true,
|
|
271
|
+
state: this.getState(),
|
|
272
|
+
message: `${eliminated} 被淘汰!游戏结束,${winner === "spy" ? "卧底" : "平民"}获胜!`,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.round++;
|
|
277
|
+
this.currentRoundDescs = [];
|
|
278
|
+
this.votes = [];
|
|
279
|
+
this.describeOrder = shuffle(this.alivePlayers().map(p => p.id));
|
|
280
|
+
this.describeIdx = 0;
|
|
281
|
+
this.phase = "describe";
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
ok: true,
|
|
285
|
+
state: this.getState(),
|
|
286
|
+
message: `${eliminated} 被淘汰!${tiebreak ? "(平票随机)" : ""} 进入第 ${this.round} 轮。`,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private checkWin(): Winner {
|
|
291
|
+
const alive = this.alivePlayers();
|
|
292
|
+
const spiesAlive = alive.filter(p => p.isSpy).length;
|
|
293
|
+
const civiliansAlive = alive.filter(p => !p.isSpy).length;
|
|
294
|
+
|
|
295
|
+
if (spiesAlive === 0) return "civilian";
|
|
296
|
+
if (spiesAlive >= civiliansAlive) return "spy";
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// apps.preinstalled/weather/weather.ts — Weather PhoneApp
|
|
2
|
+
import type { PhoneApp } from "../../system.kernel/kernel.ts";
|
|
3
|
+
|
|
4
|
+
async function fetchWeather(city: string): Promise<string> {
|
|
5
|
+
const c = encodeURIComponent(city || "Beijing");
|
|
6
|
+
try {
|
|
7
|
+
const res = await fetch(`https://wttr.in/${c}?format=%C+%t+%h+%w&lang=zh`, {
|
|
8
|
+
signal: AbortSignal.timeout(3000),
|
|
9
|
+
headers: { "User-Agent": "curl/7.79" },
|
|
10
|
+
});
|
|
11
|
+
if (res.ok) return `${city}: ${(await res.text()).trim()}`;
|
|
12
|
+
} catch {}
|
|
13
|
+
// fallback: multi-line forecast
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch(`https://wttr.in/${c}?T&lang=zh`, {
|
|
16
|
+
signal: AbortSignal.timeout(5000),
|
|
17
|
+
headers: { "User-Agent": "curl/7.79" },
|
|
18
|
+
});
|
|
19
|
+
if (res.ok) {
|
|
20
|
+
const text = await res.text();
|
|
21
|
+
return text.split("\n").filter((l: string) => l.trim()).slice(0, 8).join("\n");
|
|
22
|
+
}
|
|
23
|
+
} catch {}
|
|
24
|
+
return "天气查询失败,检查城市名";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const app: PhoneApp = {
|
|
28
|
+
name: "天气",
|
|
29
|
+
icon: "天气",
|
|
30
|
+
messageDescription: "查询城市天气",
|
|
31
|
+
|
|
32
|
+
onOpen(state: any) {
|
|
33
|
+
return {
|
|
34
|
+
screen: [
|
|
35
|
+
"═══ 天气 ═══",
|
|
36
|
+
"",
|
|
37
|
+
"输入城市名查询天气",
|
|
38
|
+
"如:深圳、Tokyo、London",
|
|
39
|
+
"",
|
|
40
|
+
"「返回」退出",
|
|
41
|
+
].join("\n"),
|
|
42
|
+
state,
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async onAction(input: string, state: any) {
|
|
47
|
+
const weather = await fetchWeather(input.trim());
|
|
48
|
+
return { screen: weather + "\n\n输入其他城市继续查,或「返回」退出", state };
|
|
49
|
+
},
|
|
50
|
+
};
|