@turmind/halo 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/admin-out/404.html +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_buildManifest.js +1 -0
- package/admin-out/_next/static/IBP_mwBDf-cPwZSZZGpi8/_ssgManifest.js +1 -0
- package/admin-out/_next/static/chunks/1d2d5650-810a3ca1ae20cab5.js +18 -0
- package/admin-out/_next/static/chunks/200.3b8f8a74f005a134.js +1 -0
- package/admin-out/_next/static/chunks/220.3dc41cd6269b88e6.js +1 -0
- package/admin-out/_next/static/chunks/298-d6b224b569da4de0.js +1 -0
- package/admin-out/_next/static/chunks/304eda7d.b1ee7f523653e027.js +1 -0
- package/admin-out/_next/static/chunks/330.3b784cf0dcd69163.js +1 -0
- package/admin-out/_next/static/chunks/442.f4302087bddb1ec1.js +1 -0
- package/admin-out/_next/static/chunks/443.d1b289781793f8de.js +25 -0
- package/admin-out/_next/static/chunks/503-7c6d9935df6e0cea.js +1 -0
- package/admin-out/_next/static/chunks/533.5f2d4bfe3d076095.js +1 -0
- package/admin-out/_next/static/chunks/579.a2718ddd395a2f3a.js +1 -0
- package/admin-out/_next/static/chunks/635-3dd0043ed0520b9d.js +1 -0
- package/admin-out/_next/static/chunks/65.859665ba6717967a.js +1 -0
- package/admin-out/_next/static/chunks/661.a4fe93f3f2a5e871.js +1 -0
- package/admin-out/_next/static/chunks/688.4e014ac6cfc7e152.js +1 -0
- package/admin-out/_next/static/chunks/868.0c95dfe283400a00.js +1 -0
- package/admin-out/_next/static/chunks/app/_not-found/page-8e35399c8fd5016d.js +1 -0
- package/admin-out/_next/static/chunks/app/layout-58f773336e6fce61.js +1 -0
- package/admin-out/_next/static/chunks/app/page-cc8043335282d745.js +1 -0
- package/admin-out/_next/static/chunks/db534642.09d3c650ff587175.js +1 -0
- package/admin-out/_next/static/chunks/fb4ec4d6-3fa44f69484f91b9.js +1 -0
- package/admin-out/_next/static/chunks/framework-c3031ae54a63fe16.js +1 -0
- package/admin-out/_next/static/chunks/main-14b48e3c2588354f.js +1 -0
- package/admin-out/_next/static/chunks/main-app-483b436873410ddc.js +1 -0
- package/admin-out/_next/static/chunks/pages/_app-b2fc6e5f920d1b1a.js +1 -0
- package/admin-out/_next/static/chunks/pages/_error-f3696a8f3d67d150.js +1 -0
- package/admin-out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/admin-out/_next/static/chunks/webpack-8bd7ef88a6527feb.js +1 -0
- package/admin-out/_next/static/css/659eccb5db697b76.css +1 -0
- package/admin-out/_next/static/css/8fce4e1a54dac12a.css +3 -0
- package/admin-out/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- package/admin-out/_next/static/media/21350d82a1f187e9-s.woff2 +0 -0
- package/admin-out/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
- package/admin-out/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- package/admin-out/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- package/admin-out/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
- package/admin-out/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
- package/admin-out/favicon.ico +0 -0
- package/admin-out/icon.svg +18 -0
- package/admin-out/index.html +1 -0
- package/admin-out/index.txt +23 -0
- package/admin-out/monaco/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
- package/admin-out/monaco/vs/abap-D-t0cyap.js +1 -0
- package/admin-out/monaco/vs/apex-CcIm7xu6.js +1 -0
- package/admin-out/monaco/vs/assets/css.worker-HnVq6Ewq.js +93 -0
- package/admin-out/monaco/vs/assets/editor.worker-Be8ye1pW.js +26 -0
- package/admin-out/monaco/vs/assets/html.worker-B51mlPHg.js +470 -0
- package/admin-out/monaco/vs/assets/json.worker-DKiEKt88.js +58 -0
- package/admin-out/monaco/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
- package/admin-out/monaco/vs/azcli-BA0tQDCg.js +1 -0
- package/admin-out/monaco/vs/basic-languages/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/bat-C397hTD6.js +1 -0
- package/admin-out/monaco/vs/bicep-DF5aW17k.js +2 -0
- package/admin-out/monaco/vs/cameligo-plsz8qhj.js +1 -0
- package/admin-out/monaco/vs/clojure-Y2auQMzK.js +1 -0
- package/admin-out/monaco/vs/coffee-Bu45yuWE.js +1 -0
- package/admin-out/monaco/vs/cpp-CkKPQIni.js +1 -0
- package/admin-out/monaco/vs/csharp-CX28MZyh.js +1 -0
- package/admin-out/monaco/vs/csp-D8uWnyxW.js +1 -0
- package/admin-out/monaco/vs/css-CaeNmE3S.js +3 -0
- package/admin-out/monaco/vs/cssMode-CjiAH6dQ.js +1 -0
- package/admin-out/monaco/vs/cypher-DVThT8BS.js +1 -0
- package/admin-out/monaco/vs/dart-CmGfCvrO.js +1 -0
- package/admin-out/monaco/vs/dockerfile-CZqqYdch.js +1 -0
- package/admin-out/monaco/vs/ecl-30fUercY.js +1 -0
- package/admin-out/monaco/vs/editor/editor.main.css +1 -0
- package/admin-out/monaco/vs/editor/editor.main.js +5 -0
- package/admin-out/monaco/vs/editor.api-CalNCsUg.js +903 -0
- package/admin-out/monaco/vs/elixir-xjPaIfzF.js +1 -0
- package/admin-out/monaco/vs/flow9-DqtmStfK.js +1 -0
- package/admin-out/monaco/vs/freemarker2-Cz_sV6Md.js +3 -0
- package/admin-out/monaco/vs/fsharp-BOMdg4U1.js +1 -0
- package/admin-out/monaco/vs/go-D_hbi-Jt.js +1 -0
- package/admin-out/monaco/vs/graphql-CKUU4kLG.js +1 -0
- package/admin-out/monaco/vs/handlebars-OwglfO-1.js +1 -0
- package/admin-out/monaco/vs/hcl-DTaboeZW.js +1 -0
- package/admin-out/monaco/vs/html-Pa1xEWsY.js +1 -0
- package/admin-out/monaco/vs/htmlMode-Bz67EXwp.js +1 -0
- package/admin-out/monaco/vs/ini-CsNwO04R.js +1 -0
- package/admin-out/monaco/vs/java-CI4ZMsH9.js +1 -0
- package/admin-out/monaco/vs/javascript-PczUCGdz.js +1 -0
- package/admin-out/monaco/vs/jsonMode-DULH5oaX.js +7 -0
- package/admin-out/monaco/vs/julia-BwzEvaQw.js +1 -0
- package/admin-out/monaco/vs/kotlin-IUYPiTV8.js +1 -0
- package/admin-out/monaco/vs/language/css/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/html/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/json/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/language/typescript/monaco.contribution.js +1 -0
- package/admin-out/monaco/vs/less-C0eDYdqa.js +2 -0
- package/admin-out/monaco/vs/lexon-iON-Kj97.js +1 -0
- package/admin-out/monaco/vs/liquid-DqKjdPGy.js +1 -0
- package/admin-out/monaco/vs/loader.js +1368 -0
- package/admin-out/monaco/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
- package/admin-out/monaco/vs/lua-DtygF91M.js +1 -0
- package/admin-out/monaco/vs/m3-CsR4AuFi.js +1 -0
- package/admin-out/monaco/vs/markdown-C_rD0bIw.js +1 -0
- package/admin-out/monaco/vs/mdx-DEWtB1K5.js +1 -0
- package/admin-out/monaco/vs/mips-CiYP61RB.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-D2OdxNBt.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-DO3azKX8.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-EcChJV6a.js +1 -0
- package/admin-out/monaco/vs/monaco.contribution-qLAYrEOP.js +1 -0
- package/admin-out/monaco/vs/msdax-C38-sJlp.js +1 -0
- package/admin-out/monaco/vs/mysql-CdtbpvbG.js +1 -0
- package/admin-out/monaco/vs/nls.messages-loader.js +1 -0
- package/admin-out/monaco/vs/nls.messages.cs.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.de.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.es.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.fr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.it.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.ja.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.js.js +10 -0
- package/admin-out/monaco/vs/nls.messages.ko.js.js +25 -0
- package/admin-out/monaco/vs/nls.messages.pl.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.pt-br.js.js +6 -0
- package/admin-out/monaco/vs/nls.messages.ru.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.tr.js.js +15 -0
- package/admin-out/monaco/vs/nls.messages.zh-cn.js.js +17 -0
- package/admin-out/monaco/vs/nls.messages.zh-tw.js.js +15 -0
- package/admin-out/monaco/vs/objective-c-CntZFaHX.js +1 -0
- package/admin-out/monaco/vs/pascal-r6kuqfl_.js +1 -0
- package/admin-out/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
- package/admin-out/monaco/vs/perl-DABw_TcH.js +1 -0
- package/admin-out/monaco/vs/pgsql-me_jFXeX.js +1 -0
- package/admin-out/monaco/vs/php-D_kh-9LK.js +1 -0
- package/admin-out/monaco/vs/pla-VfZjczW0.js +1 -0
- package/admin-out/monaco/vs/postiats-BBSzz8Pk.js +1 -0
- package/admin-out/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
- package/admin-out/monaco/vs/powershell-B-7ap1zc.js +1 -0
- package/admin-out/monaco/vs/protobuf-BmtuEB1A.js +2 -0
- package/admin-out/monaco/vs/pug-BRpRNeEb.js +1 -0
- package/admin-out/monaco/vs/python-Cr0UkIbn.js +1 -0
- package/admin-out/monaco/vs/qsharp-BzsFaUU9.js +1 -0
- package/admin-out/monaco/vs/r-f8dDdrp4.js +1 -0
- package/admin-out/monaco/vs/razor-BYAHOTkz.js +1 -0
- package/admin-out/monaco/vs/redis-fvZQY4PI.js +1 -0
- package/admin-out/monaco/vs/redshift-45Et0LQi.js +1 -0
- package/admin-out/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/admin-out/monaco/vs/ruby-CZO8zYTz.js +1 -0
- package/admin-out/monaco/vs/rust-Bfetafyc.js +1 -0
- package/admin-out/monaco/vs/sb-3GYllVck.js +1 -0
- package/admin-out/monaco/vs/scala-foMgrKo1.js +1 -0
- package/admin-out/monaco/vs/scheme-CHdMtr7p.js +1 -0
- package/admin-out/monaco/vs/scss-C1cmLt9V.js +3 -0
- package/admin-out/monaco/vs/shell-ClXCKCEW.js +1 -0
- package/admin-out/monaco/vs/solidity-MZ6ExpPy.js +1 -0
- package/admin-out/monaco/vs/sophia-DWkuSsPQ.js +1 -0
- package/admin-out/monaco/vs/sparql-AUGFYSyk.js +1 -0
- package/admin-out/monaco/vs/sql-32GpJSV2.js +1 -0
- package/admin-out/monaco/vs/st-CuDFIVZ_.js +1 -0
- package/admin-out/monaco/vs/swift-n-2HociN.js +3 -0
- package/admin-out/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/admin-out/monaco/vs/tcl-D74tq1nH.js +1 -0
- package/admin-out/monaco/vs/tsMode-CZz1Umrk.js +11 -0
- package/admin-out/monaco/vs/twig-C6taOxMV.js +1 -0
- package/admin-out/monaco/vs/typescript-DfOrAzoV.js +1 -0
- package/admin-out/monaco/vs/typespec-D-PIh9Xw.js +1 -0
- package/admin-out/monaco/vs/vb-Dyb2648j.js +1 -0
- package/admin-out/monaco/vs/wgsl-BhLXMOR0.js +298 -0
- package/admin-out/monaco/vs/workers-DcJshg-q.js +1 -0
- package/admin-out/monaco/vs/xml-CdsdnY8S.js +1 -0
- package/admin-out/monaco/vs/yaml-DYGvmE88.js +1 -0
- package/bin/halo.js +9 -0
- package/bundled-docs/design/agent.md +145 -0
- package/bundled-docs/design/architecture.md +270 -0
- package/bundled-docs/design/background-dispatch.md +151 -0
- package/bundled-docs/design/command.md +58 -0
- package/bundled-docs/design/cron.md +88 -0
- package/bundled-docs/design/evolution.md +383 -0
- package/bundled-docs/design/express-self.md +145 -0
- package/bundled-docs/design/feishu.md +224 -0
- package/bundled-docs/design/halo-city.md +112 -0
- package/bundled-docs/design/prompt-system.md +411 -0
- package/bundled-docs/design/session.md +321 -0
- package/bundled-docs/design/slack.md +212 -0
- package/bundled-docs/design/storage.md +578 -0
- package/bundled-docs/design/telegram.md +161 -0
- package/bundled-docs/design/web.md +222 -0
- package/bundled-docs/design/wechat.md +146 -0
- package/bundled-docs/design/ws.md +144 -0
- package/bundled-docs/dev/acp-adapter.md +337 -0
- package/bundled-docs/dev/add-channel.md +376 -0
- package/bundled-docs/dev/add-model-provider.md +386 -0
- package/bundled-docs/dev/api.md +686 -0
- package/bundled-docs/dev/deploy.md +198 -0
- package/bundled-docs/dev/desktop-packaging.md +327 -0
- package/bundled-docs/dev/env.md +158 -0
- package/bundled-docs/dev/logging.md +85 -0
- package/bundled-docs/dev/previews.md +225 -0
- package/bundled-docs/dev/tools.md +331 -0
- package/bundled-docs/guide/README.md +29 -0
- package/bundled-docs/guide/agents.md +264 -0
- package/bundled-docs/guide/channels/README.md +62 -0
- package/bundled-docs/guide/channels/acp.md +150 -0
- package/bundled-docs/guide/channels/feishu.md +154 -0
- package/bundled-docs/guide/channels/slack.md +156 -0
- package/bundled-docs/guide/channels/telegram.md +112 -0
- package/bundled-docs/guide/channels/web.md +149 -0
- package/bundled-docs/guide/channels/wechat.md +110 -0
- package/bundled-docs/guide/chat.md +112 -0
- package/bundled-docs/guide/cli.md +197 -0
- package/bundled-docs/guide/getting-started.md +93 -0
- package/bundled-docs/guide/secrets-and-credentials.md +161 -0
- package/bundled-docs/guide/sessions.md +104 -0
- package/bundled-docs/guide/skills.md +302 -0
- package/bundled-docs/guide/testing-agents-and-skills.md +136 -0
- package/bundled-docs/guide/workspace.md +131 -0
- package/bundled-docs/plans/cron-tasks.md +362 -0
- package/bundled-docs/plans/design-suggestions.md +203 -0
- package/bundled-docs/plans/issue-session-deep-nesting.md +208 -0
- package/bundled-docs/plans/launch-plan.md +111 -0
- package/bundled-docs/plans/md-and-agent-refactor.md +197 -0
- package/bundled-docs/plans/remote-mac-worker.md +196 -0
- package/bundled-docs/plans/roadmap.md +40 -0
- package/bundled-docs/plans/self-evolution.md +1379 -0
- package/bundled-docs/plans/todo.md +32 -0
- package/bundled-docs/plans/weixin-channel.md +255 -0
- package/bundled-docs/requirements/agent.md +92 -0
- package/bundled-docs/requirements/chat.md +81 -0
- package/bundled-docs/requirements/command.md +130 -0
- package/bundled-docs/requirements/editor.md +82 -0
- package/bundled-docs/requirements/explorer.md +89 -0
- package/bundled-docs/requirements/overview.md +62 -0
- package/bundled-docs/requirements/session.md +73 -0
- package/bundled-docs/requirements/settings.md +224 -0
- package/bundled-docs/requirements/skill.md +125 -0
- package/bundled-docs/requirements/source-control.md +82 -0
- package/bundled-docs/requirements/terminal.md +118 -0
- package/bundled-docs/requirements/workspace.md +80 -0
- package/bundled-docs/test/docs-seeding.md +101 -0
- package/bundled-docs/test/session.md +374 -0
- package/dist/index.js +26750 -0
- package/package.json +67 -0
- package/templates/INSTRUCTIONS.md +25 -0
- package/templates/agents/__apply_agent__/AGENT.md +229 -0
- package/templates/agents/__apply_agent__/agent.yaml +30 -0
- package/templates/agents/__evo_agent__/AGENT.md +323 -0
- package/templates/agents/__evo_agent__/agent.yaml +29 -0
- package/templates/agents/__score__/AGENT.md +177 -0
- package/templates/agents/__score__/agent.yaml +29 -0
- package/templates/agents/deep-executor/AGENT.md +60 -0
- package/templates/agents/deep-executor/agent.yaml +25 -0
- package/templates/agents/default/AGENT.md +38 -0
- package/templates/agents/default/agent.yaml +51 -0
- package/templates/agents/executor/AGENT.md +38 -0
- package/templates/agents/executor/agent.yaml +25 -0
- package/templates/aliases.yaml +26 -0
- package/templates/canvas/self.html +660 -0
- package/templates/config.yaml +69 -0
- package/templates/models/anthropic.yaml +47 -0
- package/templates/models/aws-bedrock-claude-invoke.yaml +156 -0
- package/templates/models/aws-bedrock-mantle.yaml +89 -0
- package/templates/models/deepseek.yaml +62 -0
- package/templates/models/doubao.yaml +137 -0
- package/templates/models/hunyuan.yaml +56 -0
- package/templates/models/kimi.yaml +41 -0
- package/templates/models/mimo-token-plan-china.yaml +61 -0
- package/templates/models/minimax.yaml +80 -0
- package/templates/models/openai.yaml +51 -0
- package/templates/models/qwen.yaml +78 -0
- package/templates/optional-skills/tavily-web-search/SKILL.md +91 -0
- package/templates/optional-skills/tavily-web-search/config.yaml +10 -0
- package/templates/prompts/all/TOOL_GUIDELINES.md +35 -0
- package/templates/prompts/all/TOOL_SHELL.md +4 -0
- package/templates/prompts/all/TOOL_SHELL.windows.md +7 -0
- package/templates/prompts/bootstrap/BOOTSTRAP.md +26 -0
- package/templates/prompts/root/WORKSPACE_MEMORY.md +11 -0
- package/templates/schema.sql +30 -0
- package/templates/skills/acp/SKILL.md +236 -0
- package/templates/skills/acp/templates/SKILL.md.tmpl +74 -0
- package/templates/skills/acp/templates/ask.py +176 -0
- package/templates/skills/acp/templates/config.yaml.tmpl +33 -0
- package/templates/skills/agent/SKILL.md +128 -0
- package/templates/skills/aws-knowledge/SKILL.md +82 -0
- package/templates/skills/aws-knowledge/scripts/aws_kb.py +61 -0
- package/templates/skills/cron/SKILL.md +217 -0
- package/templates/skills/cron/manage-cron.py +434 -0
- package/templates/skills/halo/SKILL.md +98 -0
- package/templates/skills/nova-web-search/SKILL.md +65 -0
- package/templates/skills/self/SKILL.md +139 -0
- package/templates/skills/send-file/SKILL.md +28 -0
- package/templates/skills/send-file/feishu.md +16 -0
- package/templates/skills/send-file/slack.md +7 -0
- package/templates/skills/send-file/telegram.md +9 -0
- package/templates/skills/send-file/web.md +6 -0
- package/templates/skills/send-file/wechat.md +8 -0
- package/templates/skills/skill/SKILL.md +131 -0
- package/templates/skills/workspace/SKILL.md +28 -0
- package/templates/skills/workspace/setup-tidy.md +265 -0
- package/templates/skills/workspace/share.md +187 -0
- package/templates/skills/workspace/stage.py +487 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Feishu/Lark Channel — Design
|
|
2
|
+
|
|
3
|
+
Let the user talk to Halo from Feishu (Lark) on their phone, sharing the same workspace + sessions as the web side. All channels are peers — each one is a subscriber + caller against SessionManager.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌── ws/ (web channel) ─┐
|
|
9
|
+
├── channels/telegram/ │
|
|
10
|
+
Halo server (9527) ──┤├── channels/wechat/ ├── SessionManager
|
|
11
|
+
├── channels/slack/ │ (per workspace, via Registry)
|
|
12
|
+
└── channels/feishu/ ←─ long-connect (wss) ─┘
|
|
13
|
+
↕ Lark.WSClient
|
|
14
|
+
https://open.feishu.cn
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Feishu uses the official SDK's `Lark.WSClient` to maintain a persistent WebSocket connection for event delivery. Unlike Slack/Telegram (which use webhooks + polling respectively), Feishu's long-connect receives `im.message.receive_v1` events pushed by the server over wss.
|
|
18
|
+
|
|
19
|
+
## Data model
|
|
20
|
+
|
|
21
|
+
### Workspace ↔ Bot mapping
|
|
22
|
+
|
|
23
|
+
One Feishu app (= one appId + appSecret pair) is bound to one workspace. A workspace can bind multiple apps.
|
|
24
|
+
|
|
25
|
+
### Session strategy
|
|
26
|
+
|
|
27
|
+
- **One Feishu user → many sessions (one active at a time, per thread)**
|
|
28
|
+
- Session ID format: `feishu_<chatId>:<rootId>_<createdAtBase36>`
|
|
29
|
+
- In p2p (DM): rootId anchored to `dm` for a stable key across multiple messages
|
|
30
|
+
- In group: each thread is its own bounded session, keyed by the thread's root message id
|
|
31
|
+
- Sessions live under the bot's bound workspace and use the highest-priority agent (falls back to `default` only when none exists)
|
|
32
|
+
- Access level inherited from the account's `accessLevel` field (see below)
|
|
33
|
+
|
|
34
|
+
### Access level
|
|
35
|
+
|
|
36
|
+
Each account carries `accessLevel: 'full' | 'workspace' | 'readonly'` (default `readonly`):
|
|
37
|
+
- `full` — no restrictions (no sandbox)
|
|
38
|
+
- `workspace` — tool execution runs inside a bwrap sandbox with the workspace mounted read-write
|
|
39
|
+
- `readonly` — tool execution runs inside a bwrap sandbox with the workspace mounted read-only; readonly sessions only receive read-only tools
|
|
40
|
+
|
|
41
|
+
### Storage
|
|
42
|
+
|
|
43
|
+
Feishu bot accounts are stored in the unified channel DB: `~/.halo/secrets/channels/channels.db`, table `channel_accounts` with `channel_type = 'feishu'`. See [storage.md](storage.md#channel_accounts) for the full schema.
|
|
44
|
+
|
|
45
|
+
Feishu-specific config JSON fields: `appId`, `appSecret`, `verificationToken` (legacy), `encryptKey` (optional), `botOpenId`, `lastActiveChatId`.
|
|
46
|
+
|
|
47
|
+
- `lastActiveChatId` is a runtime cache of the most recent `<chatId>:<rootId>` pair. Written by `rememberLastActiveChat()` in `channels/shared/accounts.ts` on every inbound message (idempotent). Not consumed by cron dispatch (the dispatcher requires an explicit `chatId`); it is kept as a runtime cache for any future "reply to whoever talked last" feature.
|
|
48
|
+
|
|
49
|
+
### Authentication model
|
|
50
|
+
|
|
51
|
+
- `appId` + `appSecret` are long-lived credentials from the Feishu open platform
|
|
52
|
+
- Used to mint short-lived `tenant_access_token` (~2 hour lifetime) for all API calls
|
|
53
|
+
- `verificationToken` and `encryptKey` handle webhook security (for legacy webhook setups; not used with long-connect)
|
|
54
|
+
- `botOpenId` is the bot's own open_id, used to detect mentions in group chats
|
|
55
|
+
|
|
56
|
+
Token caching is in-memory per-app in `api.ts:tokenCache`; refresh happens on-demand 60s before expiry.
|
|
57
|
+
|
|
58
|
+
### Proactive sending (cron)
|
|
59
|
+
|
|
60
|
+
The feishu cron-dispatcher (`channels/feishu/cron-dispatcher.ts`) requires an explicit `chatId` — there is no fallback to `lastActiveChatId`. When a cron fires:
|
|
61
|
+
|
|
62
|
+
1. **Explicit `chatId` on the target** — set when the cron was created from inside a Feishu chat (e.g. cron created from a group thread auto-pins to that thread). Sends only to that chat (top-level message, not nested in the thread — Feishu's open API v1 doesn't support thread-targeting in sendMessage).
|
|
63
|
+
2. **Admin-UI cron without explicit target** — runs silently if no chatId is provided.
|
|
64
|
+
|
|
65
|
+
## Modules
|
|
66
|
+
|
|
67
|
+
Files: `packages/server/src/channels/feishu/`
|
|
68
|
+
|
|
69
|
+
- `types.ts` — FeishuAccount, FeishuEventEnvelope, FeishuMessageEvent interfaces
|
|
70
|
+
- `accounts.ts` — DAL (listAccounts / getAccount / insertAccount / updateAccount / deleteAccount / findAccountByAppId)
|
|
71
|
+
- `handler.ts` — Lark.WSClient setup, event dispatch, message handling, session routing, mention detection, media ingestion
|
|
72
|
+
- `event-adapter.ts` — AgentSessionEvent stream → coalesced Feishu message replies (buffer + flush at paragraph boundaries)
|
|
73
|
+
- `api.ts` — HTTP client (tenant token caching / getBotInfo / sendMessage / replyMessage / uploadImage / uploadFile / downloadResource / decryptWebhookBody / searchFeishuTargets / openLongConnection)
|
|
74
|
+
- `cron-dispatcher.ts` — registers the cron dispatcher, requires explicit chatId targets
|
|
75
|
+
- `descriptor.ts` — ServerChannelDescriptor entry point
|
|
76
|
+
|
|
77
|
+
Routes: `packages/server/src/routes/feishu.ts`
|
|
78
|
+
|
|
79
|
+
- `GET /api/feishu/accounts` — list bot accounts (admin)
|
|
80
|
+
- `POST /api/feishu/accounts` — register a bot (validates credentials via `getBotInfo`, auto-resolves botOpenId)
|
|
81
|
+
- `PATCH /api/feishu/accounts/:id` — update config / enable / disable
|
|
82
|
+
- `DELETE /api/feishu/accounts/:id` — remove + stop
|
|
83
|
+
- `GET /api/feishu/accounts/:id/search` — search chats by name (for cron target picker)
|
|
84
|
+
|
|
85
|
+
## Bot lifecycle
|
|
86
|
+
|
|
87
|
+
1. User registers a bot via the API (provides appId + appSecret + workspace)
|
|
88
|
+
2. Server calls `getBotInfo` to validate credentials and auto-resolve botOpenId; stores account; starts long-connect
|
|
89
|
+
3. Lark.WSClient handles the wss connection, ping/pong, URL rotation (~30 min), and automatic reconnection
|
|
90
|
+
4. On graceful shutdown, `wsClient.close()` is called for each active bot
|
|
91
|
+
|
|
92
|
+
## Long-connect frame protocol
|
|
93
|
+
|
|
94
|
+
On connect to `wss://…` (negotiated via `POST /callback/ws/endpoint`):
|
|
95
|
+
|
|
96
|
+
1. Client sends a `register` frame containing the `conn_id` received from the negotiation response
|
|
97
|
+
2. Server pushes event frames typed by `type`:
|
|
98
|
+
- `1` = `frame` (event payload — `im.message.receive_v1` event data we care about)
|
|
99
|
+
- `2` = `pong` (heartbeat reply)
|
|
100
|
+
- `8` = `disconnect` (server is rotating the URL)
|
|
101
|
+
3. Client periodically sends `ping` (type=0) to keep the connection alive
|
|
102
|
+
|
|
103
|
+
The SDK wraps protobuf marshalling; we just register an `EventDispatcher` callback for `im.message.receive_v1` events.
|
|
104
|
+
|
|
105
|
+
## Message handling flow
|
|
106
|
+
|
|
107
|
+
`im.message.receive_v1` event:
|
|
108
|
+
|
|
109
|
+
1. Extract sender (open_id or user_id), message content, chat_type (p2p or group), mentions array
|
|
110
|
+
2. Check if bot was mentioned (p2p: always yes; group: check mentions array for bot's open_id)
|
|
111
|
+
3. Drop non-user senders (bots, apps)
|
|
112
|
+
4. Determine session key: p2p → `(chatId, 'dm')`; group → `(chatId, rootId ?? messageId)` to anchor each thread
|
|
113
|
+
5. Parse content (text / image / file / post) — images extracted as base64 for vision, files noted as `[文件: name]` markers
|
|
114
|
+
6. Download inbound images, save to workspace, generate base64 payloads
|
|
115
|
+
7. Strip mention markup from text (Feishu inserts `<at user_id="ou_xxx">@bot</at>` or `@_user_123` tokens)
|
|
116
|
+
8. Slash command dispatch (p2p only — group threads don't benefit from `/session new`, `/session list`, etc. since each thread is already its own session)
|
|
117
|
+
9. Create or retrieve active session for this thread (with inherited access level)
|
|
118
|
+
10. If session is compacting → reply "⏳ 正在整理上下文,请稍后再发消息(通常 30 秒内完成)" and skip queueing
|
|
119
|
+
11. If session is busy → reply "已收到,会在当前轮结束后处理。" and enqueue
|
|
120
|
+
12. Register a `FeishuResponder` event listener (once per session) that coalesces outbound text
|
|
121
|
+
13. `sm.sendUserMessage(sessionId, agentInput, images?)` with channel + user + thread context
|
|
122
|
+
|
|
123
|
+
## Slash commands (native Feishu /commands)
|
|
124
|
+
|
|
125
|
+
Implemented via `channels/shared/commands.ts` (shared across Telegram, WeChat, Slack, Feishu):
|
|
126
|
+
|
|
127
|
+
| Command | Purpose |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `/start` | Welcome message (p2p only) |
|
|
130
|
+
| `/session new` | Create a new session (p2p only) |
|
|
131
|
+
| `/session list` | List recent sessions (p2p only) |
|
|
132
|
+
| `/session switch <index>` | Switch active session (p2p only) |
|
|
133
|
+
| `/session stop` | Abort running task |
|
|
134
|
+
| `/session compact` | Compress context |
|
|
135
|
+
| `/workspace info` | Show workspace; `/workspace switch <path>` changes it (full access only) |
|
|
136
|
+
| `/help` | List commands |
|
|
137
|
+
|
|
138
|
+
## Event coalescing (FeishuResponder)
|
|
139
|
+
|
|
140
|
+
Feishu's text message limit (~5000 chars), smaller than Slack. Strategy:
|
|
141
|
+
|
|
142
|
+
- Buffer streamed text until `complete` event
|
|
143
|
+
- Flush fires on **any** `complete` — the responder doesn't read its `batchBoundary` flag, so a multi-round queue drain ships each merged turn as its own message instead of one blob (see [session.md](session.md#message-queue-and-drain))
|
|
144
|
+
- If buffer exceeds 4500 chars, split at paragraph boundary (`\n\n`)
|
|
145
|
+
- Otherwise hard-cut at 4500 chars
|
|
146
|
+
- `system` and `error` events flush early so users always see something before the run ends
|
|
147
|
+
- Media markers (`MEDIA:<path>`) intercepted and sent via native media upload
|
|
148
|
+
- Sub-agent events dropped (visible in web UI only)
|
|
149
|
+
|
|
150
|
+
## Media support
|
|
151
|
+
|
|
152
|
+
**Inbound:** text, images (base64 attached to agent message), files (noted as `[文件: name]` markers), voice, video, rich posts.
|
|
153
|
+
|
|
154
|
+
**Outbound routing:**
|
|
155
|
+
- `.jpg/.png/.gif/.webp/.bmp` → `uploadImage` → `msg_type: 'image'`
|
|
156
|
+
- `.opus` → `uploadFile(fileType: 'opus')` → `msg_type: 'audio'` (non-opus voice files sent as generic files)
|
|
157
|
+
- `.mp4/.mov/.webm/.m4v/.avi` → `uploadFile(fileType: 'mp4')` → `msg_type: 'media'` (with optional cover frame)
|
|
158
|
+
- everything else → `uploadFile(fileType: 'stream')` → `msg_type: 'file'`
|
|
159
|
+
|
|
160
|
+
Inbound images are downloaded via `/im/v1/messages/{messageId}/resources/{imageKey}?type=image`, decrypted if needed, saved under `<workspace>/.halo/assets/feishu/inbound/<accountId>/<date>/`, and fed to vision.
|
|
161
|
+
|
|
162
|
+
## Message routing (thread vs p2p)
|
|
163
|
+
|
|
164
|
+
**p2p (DM):** Replies sent via `sendMessage` targeting the chat (Feishu p2p doesn't support threads).
|
|
165
|
+
|
|
166
|
+
**Group (chat):** Replies sent via `replyMessage` with `reply_in_thread: true` and the inbound message id. This auto-creates a thread rooted at the inbound message if it doesn't exist, or appends to the existing thread.
|
|
167
|
+
|
|
168
|
+
## Similarities vs Slack and Telegram
|
|
169
|
+
|
|
170
|
+
| Aspect | Feishu | Slack | Telegram |
|
|
171
|
+
|--------|--------|-------|----------|
|
|
172
|
+
| **Inbound delivery** | Long-connect (wss) | Webhook + slash commands | Long-polling |
|
|
173
|
+
| **Session model** | Per-thread in groups, p2p anchored | Per-thread + channels | Per-user (one active) |
|
|
174
|
+
| **Mention required** | Yes in groups, no in p2p | Yes in channels, no in DMs | No (all messages routed to agent) |
|
|
175
|
+
| **Cron target** | Explicit chatId only | Explicit chatId only (DM / channel / thread) | Explicit numeric chatId only |
|
|
176
|
+
| **Media upload** | Separate image + file endpoints | Unified File upload API | Bot API sendPhoto / sendVideo / sendDocument |
|
|
177
|
+
| **Text limit** | ~5000 chars | ~4000 chars | ~4000 chars |
|
|
178
|
+
| **Thread support** | Native (root_id in messages) | Native (thread_ts replies) | N/A (group chats not supported) |
|
|
179
|
+
|
|
180
|
+
## Configuration
|
|
181
|
+
|
|
182
|
+
### Feishu app setup
|
|
183
|
+
|
|
184
|
+
1. Go to https://open.feishu.cn/
|
|
185
|
+
2. Create a new app (register as a custom app in your org, or create ISV app)
|
|
186
|
+
3. Under Settings → Permissions, enable:
|
|
187
|
+
- `im:message:readonly` — receive message events
|
|
188
|
+
- `im:message` — send messages
|
|
189
|
+
- `im:resource` — upload images / files
|
|
190
|
+
- `im:chat:readonly` — search chats (for cron target picker)
|
|
191
|
+
4. Under Settings → Bot, enable long-connect: "Get Card / Bot / Message Callbacks" → subscribe to `im.message.receive_v1`
|
|
192
|
+
5. Copy `App ID` and `App Secret` (displayed on the settings page, or regenerate if lost)
|
|
193
|
+
|
|
194
|
+
### Register to Halo
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
curl -X POST http://localhost:9527/api/feishu/accounts \
|
|
198
|
+
-H 'Content-Type: application/json' \
|
|
199
|
+
-d '{
|
|
200
|
+
"appId": "cli_xxxxxxxxxxxxxxxx",
|
|
201
|
+
"appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
202
|
+
"workspacePath": "/home/user/my-workspace",
|
|
203
|
+
"accessLevel": "full",
|
|
204
|
+
"label": "My Feishu Bot"
|
|
205
|
+
}'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Server auto-resolves `botOpenId` from the credentials, validates them, and starts the long-connect immediately.
|
|
209
|
+
|
|
210
|
+
## Scope and out-of-scope
|
|
211
|
+
|
|
212
|
+
Supported: p2p text, group text with mention-required; threads; images (base64 attached to agent); audio, video, files (uploaded and referenced); slash commands (p2p only); per-session access level; media sending.
|
|
213
|
+
|
|
214
|
+
Not supported: group chat without mention (we require explicit @mention for groups to avoid noise); rich message composition in responses (text only, plus media as separate messages); reactions; bot callbacks for button clicks / form submissions.
|
|
215
|
+
|
|
216
|
+
## Key file references
|
|
217
|
+
|
|
218
|
+
- Long-connect: `packages/server/src/channels/feishu/handler.ts:connect()` (line 241)
|
|
219
|
+
- Token caching: `packages/server/src/channels/feishu/api.ts:getTenantAccessToken()` (line 39)
|
|
220
|
+
- Message parsing: `packages/server/src/channels/feishu/handler.ts:parseContent()` (line 135)
|
|
221
|
+
- Event dispatch: `packages/server/src/channels/feishu/handler.ts:handleInbound()` (line 301)
|
|
222
|
+
- Cron dispatch: `packages/server/src/channels/feishu/cron-dispatcher.ts:dispatch()` (line 27)
|
|
223
|
+
- Event coalescing: `packages/server/src/channels/feishu/event-adapter.ts:FeishuResponder` (line 24)
|
|
224
|
+
- REST routes: `packages/server/src/routes/feishu.ts` (all account CRUD + search)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Halo City · 像素街区可视化
|
|
2
|
+
|
|
3
|
+
Halo 服务器运行态的**街景式**像素可视化:侧视、剖面的像素城,实时天色下,
|
|
4
|
+
每个 **workspace 是一栋楼**,每个 **agent session 是一只 Q 版动物市民**,爬真楼梯
|
|
5
|
+
到真工位上班。点任何人打开他的**真实会话日志**(服务端流式拉取,和 admin 面板同源)。
|
|
6
|
+
|
|
7
|
+
只读、零构建、零贴图素材(每个像素都是代码画的)。网络流量:一个
|
|
8
|
+
`GET /api/show/state` 轮询,外加面板打开时的 `GET /api/show/session`。
|
|
9
|
+
|
|
10
|
+
> 接口契约见 [dev/api.md](../dev/api.md#show-world-snapshot)(`/api/show/state`、
|
|
11
|
+
> `/api/show/session` 的字段与鉴权)。本文只讲前端可视化设计。
|
|
12
|
+
|
|
13
|
+
## 街区结构
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
☀/☾ parallax skyline · clouds · stars ──────────────────────────
|
|
17
|
+
┌─[roof: water tank / AC / antenna / billboard]─┐
|
|
18
|
+
│ W3 desks ×3 · skill station · flavor prop │
|
|
19
|
+
├───────────────────────────────────────────────┤ ┌──────────┐
|
|
20
|
+
│ C 公共层: 冰箱·水吧·书架·沙发·街机/鱼缸/猫 ─[阳台]│ │ next │
|
|
21
|
+
├───────────────────────────────────────────────┤ │ building │
|
|
22
|
+
│ W2 / W1 work floors … │ │ │
|
|
23
|
+
├───────────────────────────────────────────────┤ │ │
|
|
24
|
+
│ 大堂: 前台 · 咖啡机 · 沙发 · 大门 │ │ │
|
|
25
|
+
└─⌐stairs──────────────────────────────awning───┘ └──────────┘
|
|
26
|
+
──┴── sidewalk · 餐车 · 野餐桌 · 篮筐 · 单车架 · 路灯 ──┴───────────
|
|
27
|
+
road (楼距加宽:巷子就是楼下放风的地方)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| 看到的 | 意思 |
|
|
31
|
+
|---|---|
|
|
32
|
+
| 🏬 一栋楼 | 一个 workspace。**每 3 个工作层插 1 个公共层**(楼够高时),楼层数**贴合当前 root 会话数**(每层对应一个真实会话,顶上不留空层;增减都跟,但保留 1 层缓冲——会话数在两次轮询间 ±1 抖动不会让楼顶反复抽搐);砖材/霓虹竖牌/楼顶装置由 id hash 决定 |
|
|
33
|
+
| 🐾 动物市民 | 一个 agent session。**制服**(衬衫+条纹)认 agent 名 → 在哪都认得出"researcher";**物种/毛色/花纹/配件**(10 物种 × 多毛色 × 眼镜/耳机/斑纹)认 session id → 同 agent 并发的 session 是穿同款衬衫的不同动物。会眨眼,说话动嘴,狗走路摇尾巴 |
|
|
34
|
+
| 🪜 楼梯间 | 大楼左侧,真的逐层爬上爬下(没有传送) |
|
|
35
|
+
| 💺 工作层 | 每层 3 张工位(按 agent 名固定)+ 1 个技能站 + 随机小设施(盆栽/小书架/白板/售货机);干活时屏幕滚代码 |
|
|
36
|
+
| 🍵 公共层 | 冰箱(偶尔开门漏光)、水吧(水壶冒汽)、书报架、沙发,外加街机/鱼缸/猫爬架三选一;右侧伸出**阳台**(烟灰桶+盆栽)——抽烟点 |
|
|
37
|
+
| 🚬 习惯 | 每个 session 由 id hash 派生 **3 个固定偏好**(抽烟/咖啡/泡茶/翻冰箱/看书/街机/看鱼/撸猫/打电话/下楼遛弯/沙发),摸鱼时偏好权重 ~4×——"这个会话就爱在阳台抽烟"是稳定人设,不是随机骰子 |
|
|
38
|
+
| 🧭 就近原则 | 市民**生在自己的归属层**(自己的工位层;子代理用父代理的工位层),不再从大堂爬上来。休息活动以**归属层**为锚点就近找公共层(每 3 层就有),不会为了倒杯茶爬通天,**也不会随时间往低层漂**——12 楼的人永远回 F11 休息,不会一路沉到大堂。高层会话不下大堂:`下楼遛弯` 只有 F5 及以下才真下街,F6+ 改在就近休息层放风 |
|
|
39
|
+
| 💡 灯光 | 有人在的楼层灯亮(running 或 idle 都算——摸鱼也开灯),只有彻底 stopped 的层才熄;天黑后街灯/霓虹/远景窗火全亮 |
|
|
40
|
+
| ↳ 小一号 | 子代理:生在并聚在父代理那层桌边,不占工位 |
|
|
41
|
+
|
|
42
|
+
进出有戏:新会话直接出现在自己的工位层(从本层楼梯口走到工位,不再从街上爬通天);结束的走出门沿街远去淡出。
|
|
43
|
+
|
|
44
|
+
## 点击 = 真实数据
|
|
45
|
+
|
|
46
|
+
点任何市民,面板里是**真东西**:
|
|
47
|
+
|
|
48
|
+
- **会话日志**:`/api/show/session` 拉来的最近 40 条消息流——用户输入、助手回复、
|
|
49
|
+
工具调用(带耗时),像 `tail -f` 一样贴底自动滚,面板开着每 6s 只刷日志框
|
|
50
|
+
(面板本体原地补丁,不再整页重建闪烁)
|
|
51
|
+
- **上下文仪表**:`134k / 200k (67%)` —— 分母是该会话的**真实** `maxContextTokens`
|
|
52
|
+
- **委派链**:父/子会话可点击跳转,每行带 token 数和消息数
|
|
53
|
+
- **点楼层**:该层的会话委派关系树(缩进、可点跳转)——工作层看"谁在这层干活、
|
|
54
|
+
带着哪些子代理",公共层看"谁在摸鱼"
|
|
55
|
+
- 点大楼:全员 roster + token 合计;点技能站:谁在用
|
|
56
|
+
|
|
57
|
+
## 街景与载具
|
|
58
|
+
|
|
59
|
+
街区不只是静态背景,会按**真实时间**自己演(`traffic.js`):
|
|
60
|
+
|
|
61
|
+
| 事件 | 节奏 | 说明 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| 🛸 UFO | 每天 0:00 | 午夜从街头飘过 |
|
|
64
|
+
| ✈️ 飞机 | 每半点(`:30`) | 高空掠过,方向随机 |
|
|
65
|
+
| 🚌 巴士 | 每 3 分钟 | 进站、开门、接走在站台等车的人,再开走 |
|
|
66
|
+
| 🚗 进出场载具 | 跟随会话 | 新会话打车/电动车/跑车送到门口;结束的会话走到站台**体面地等车离场**,不再凭空消失 |
|
|
67
|
+
| 🏗️ 盖楼 / 拆楼 | 跟随楼层增减 | 长楼层有施工动效;拆楼留 ~30s 缓冲(先让人撤离再慢慢消散,不穿帮) |
|
|
68
|
+
|
|
69
|
+
> 定时全用真实 `Date.now()` 计算(`?hour=` 只冻结天色、不冻结这些事件)。
|
|
70
|
+
|
|
71
|
+
## 交互与显示
|
|
72
|
+
|
|
73
|
+
操作:滚轮缩放(九档 `0.3×–6×`)· 拖拽平移 · 点击查看 · **双击**在 1×/3× 间快速切 ·
|
|
74
|
+
**F** 全街区 · **H**(或点左上角 logo)切纯净模式 · **Esc** 逐层退(先关面板,再退纯净)·
|
|
75
|
+
右上角 **中/EN** 一键切语言。闲置 10s 后镜头沿街慢慢巡游。
|
|
76
|
+
|
|
77
|
+
- **纯净模式(zen)**:淡出所有 HUD(统计/动态/图例/缩放条/面板),只留街景,适合大屏常驻展示
|
|
78
|
+
- **多语言(i18n)**:中文 / English 一键切;`?lang=en`/`?lang=zh` 直达,选择存 localStorage。
|
|
79
|
+
运行时文案走 `t()`,静态 DOM 走 `data-i18n` 属性填充
|
|
80
|
+
|
|
81
|
+
## 调试参数
|
|
82
|
+
|
|
83
|
+
- `?hour=22.5` — 把昼夜定格在某个时刻(支持小数;只冻结天色,不冻结街景事件)
|
|
84
|
+
- `?lang=en` / `?lang=zh` — 指定语言
|
|
85
|
+
- kiosk 直链:`/#api=…&token=…`(token 立即从地址栏抹掉)
|
|
86
|
+
|
|
87
|
+
## 文件结构
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
js/
|
|
91
|
+
main.js 入口:setup → world → 输入/轮询
|
|
92
|
+
api.js state 轮询 + session 详情客户端(content-type 防呆)
|
|
93
|
+
world.js 快照 → 城市/市民 diff,渲染循环,拾取
|
|
94
|
+
city.js 街区:楼(材质/楼层/霓虹/楼顶)、大堂、街道、视差天际线
|
|
95
|
+
citizen.js 市民:楼层×楼梯 2D 路径规划 + 行为 FSM
|
|
96
|
+
people.js 人物绘制:动物物种×毛色×配件池,眨眼/口型
|
|
97
|
+
props.js 家具:工位/技能站/咖啡机/街机/鱼缸/猫爬架/窗/灯
|
|
98
|
+
palette.js EDG32 + 建筑材质 ramp + 关键帧天空
|
|
99
|
+
camera.js 整数吸附镜头(九档缩放 0.3×–6×)
|
|
100
|
+
inspector.js 面板 + 实时会话日志查看器
|
|
101
|
+
ticker.js 街区动态 feed
|
|
102
|
+
traffic.js 街景事件:UFO(0点)/飞机(每半点)/进出场载具/每 3 分钟 bus/盖楼·拆楼
|
|
103
|
+
i18n.js 多语言词典(zh/en)+ 运行时 t() + 静态 data-i18n 填充
|
|
104
|
+
util.js hash/PRNG/格式化
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 服务端配套
|
|
108
|
+
|
|
109
|
+
- `GET /api/show/session?ws=&id=` — 只读会话详情:裁剪后的消息日志 + 真实
|
|
110
|
+
token 上限。鉴权同 `/api/show/state`(x-token;非 full 只能看自己 workspace)
|
|
111
|
+
- `/api/show/state` — 无活跃 UIState 的会话(idle/stopped/重启后)token
|
|
112
|
+
从会话文件头读取(mtime 缓存,不随轮询刷盘),不显示 0;含 `messageCount` 字段
|