@researai/deepscientist 1.5.0 → 1.5.2

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.
Files changed (168) hide show
  1. package/AGENTS.md +26 -0
  2. package/README.md +47 -161
  3. package/assets/connectors/lingzhu/openclaw-bridge/README.md +124 -0
  4. package/assets/connectors/lingzhu/openclaw-bridge/index.ts +162 -0
  5. package/assets/connectors/lingzhu/openclaw-bridge/openclaw.plugin.json +145 -0
  6. package/assets/connectors/lingzhu/openclaw-bridge/package.json +35 -0
  7. package/assets/connectors/lingzhu/openclaw-bridge/src/cli.ts +180 -0
  8. package/assets/connectors/lingzhu/openclaw-bridge/src/config.ts +196 -0
  9. package/assets/connectors/lingzhu/openclaw-bridge/src/debug-log.ts +111 -0
  10. package/assets/connectors/lingzhu/openclaw-bridge/src/events.ts +4 -0
  11. package/assets/connectors/lingzhu/openclaw-bridge/src/http-handler.ts +1133 -0
  12. package/assets/connectors/lingzhu/openclaw-bridge/src/image-cache.ts +75 -0
  13. package/assets/connectors/lingzhu/openclaw-bridge/src/lingzhu-tools.ts +246 -0
  14. package/assets/connectors/lingzhu/openclaw-bridge/src/transform.ts +541 -0
  15. package/assets/connectors/lingzhu/openclaw-bridge/src/types.ts +131 -0
  16. package/assets/connectors/lingzhu/openclaw-bridge/tsconfig.json +14 -0
  17. package/assets/connectors/lingzhu/openclaw.lingzhu.config.template.json +39 -0
  18. package/bin/ds.js +2048 -166
  19. package/docs/en/00_QUICK_START.md +152 -0
  20. package/docs/en/01_SETTINGS_REFERENCE.md +1104 -0
  21. package/docs/en/02_START_RESEARCH_GUIDE.md +404 -0
  22. package/docs/en/03_QQ_CONNECTOR_GUIDE.md +325 -0
  23. package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +216 -0
  24. package/docs/en/05_TUI_GUIDE.md +141 -0
  25. package/docs/en/06_RUNTIME_AND_CANVAS.md +679 -0
  26. package/docs/en/07_MEMORY_AND_MCP.md +253 -0
  27. package/docs/en/08_FIGURE_STYLE_GUIDE.md +97 -0
  28. package/docs/en/09_DOCTOR.md +152 -0
  29. package/docs/en/90_ARCHITECTURE.md +247 -0
  30. package/docs/en/91_DEVELOPMENT.md +195 -0
  31. package/docs/en/99_ACKNOWLEDGEMENTS.md +29 -0
  32. package/docs/zh/00_QUICK_START.md +152 -0
  33. package/docs/zh/01_SETTINGS_REFERENCE.md +1137 -0
  34. package/docs/zh/02_START_RESEARCH_GUIDE.md +414 -0
  35. package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +324 -0
  36. package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +230 -0
  37. package/docs/zh/05_TUI_GUIDE.md +128 -0
  38. package/docs/zh/06_RUNTIME_AND_CANVAS.md +271 -0
  39. package/docs/zh/07_MEMORY_AND_MCP.md +235 -0
  40. package/docs/zh/08_FIGURE_STYLE_GUIDE.md +97 -0
  41. package/docs/zh/09_DOCTOR.md +154 -0
  42. package/docs/zh/99_ACKNOWLEDGEMENTS.md +29 -0
  43. package/install.sh +41 -16
  44. package/package.json +5 -2
  45. package/pyproject.toml +1 -1
  46. package/src/deepscientist/__init__.py +6 -1
  47. package/src/deepscientist/artifact/guidance.py +9 -2
  48. package/src/deepscientist/artifact/service.py +1026 -39
  49. package/src/deepscientist/bash_exec/monitor.py +27 -5
  50. package/src/deepscientist/bash_exec/runtime.py +639 -0
  51. package/src/deepscientist/bash_exec/service.py +99 -16
  52. package/src/deepscientist/bridges/base.py +3 -0
  53. package/src/deepscientist/bridges/connectors.py +292 -13
  54. package/src/deepscientist/channels/qq.py +19 -2
  55. package/src/deepscientist/channels/relay.py +1 -0
  56. package/src/deepscientist/cli.py +32 -25
  57. package/src/deepscientist/config/models.py +28 -2
  58. package/src/deepscientist/config/service.py +202 -7
  59. package/src/deepscientist/connector_runtime.py +2 -0
  60. package/src/deepscientist/daemon/api/handlers.py +68 -6
  61. package/src/deepscientist/daemon/api/router.py +3 -0
  62. package/src/deepscientist/daemon/app.py +531 -15
  63. package/src/deepscientist/doctor.py +511 -0
  64. package/src/deepscientist/gitops/diff.py +3 -0
  65. package/src/deepscientist/home.py +26 -2
  66. package/src/deepscientist/latex_runtime.py +17 -4
  67. package/src/deepscientist/lingzhu_support.py +182 -0
  68. package/src/deepscientist/mcp/context.py +3 -1
  69. package/src/deepscientist/mcp/server.py +55 -2
  70. package/src/deepscientist/prompts/builder.py +222 -58
  71. package/src/deepscientist/quest/layout.py +2 -0
  72. package/src/deepscientist/quest/service.py +133 -14
  73. package/src/deepscientist/quest/stage_views.py +65 -1
  74. package/src/deepscientist/runners/codex.py +2 -0
  75. package/src/deepscientist/runtime_tools/__init__.py +16 -0
  76. package/src/deepscientist/runtime_tools/builtins.py +19 -0
  77. package/src/deepscientist/runtime_tools/models.py +29 -0
  78. package/src/deepscientist/runtime_tools/registry.py +40 -0
  79. package/src/deepscientist/runtime_tools/service.py +59 -0
  80. package/src/deepscientist/runtime_tools/tinytex.py +25 -0
  81. package/src/deepscientist/shared.py +44 -17
  82. package/src/deepscientist/tinytex.py +276 -0
  83. package/src/prompts/connectors/lingzhu.md +15 -0
  84. package/src/prompts/connectors/qq.md +121 -0
  85. package/src/prompts/system.md +214 -37
  86. package/src/skills/analysis-campaign/SKILL.md +46 -7
  87. package/src/skills/baseline/SKILL.md +12 -5
  88. package/src/skills/decision/SKILL.md +7 -5
  89. package/src/skills/experiment/SKILL.md +22 -5
  90. package/src/skills/finalize/SKILL.md +9 -5
  91. package/src/skills/idea/SKILL.md +6 -5
  92. package/src/skills/intake-audit/SKILL.md +277 -0
  93. package/src/skills/intake-audit/references/state-audit-template.md +41 -0
  94. package/src/skills/rebuttal/SKILL.md +409 -0
  95. package/src/skills/rebuttal/references/action-plan-template.md +63 -0
  96. package/src/skills/rebuttal/references/evidence-update-template.md +30 -0
  97. package/src/skills/rebuttal/references/response-letter-template.md +113 -0
  98. package/src/skills/rebuttal/references/review-matrix-template.md +55 -0
  99. package/src/skills/review/SKILL.md +295 -0
  100. package/src/skills/review/references/experiment-todo-template.md +29 -0
  101. package/src/skills/review/references/review-report-template.md +83 -0
  102. package/src/skills/review/references/revision-log-template.md +40 -0
  103. package/src/skills/scout/SKILL.md +6 -5
  104. package/src/skills/write/SKILL.md +8 -4
  105. package/src/tui/dist/components/WelcomePanel.js +17 -43
  106. package/src/tui/dist/components/messages/BashExecOperationMessage.js +3 -2
  107. package/src/tui/package.json +1 -1
  108. package/src/ui/dist/assets/{AiManusChatView-7v-dHngU.js → AiManusChatView-CZpg376x.js} +127 -597
  109. package/src/ui/dist/assets/{AnalysisPlugin-B_Xmz-KE.js → AnalysisPlugin-CtHA22g3.js} +1 -1
  110. package/src/ui/dist/assets/{AutoFigurePlugin-Cko-0tm1.js → AutoFigurePlugin-BSWmLMmF.js} +63 -8
  111. package/src/ui/dist/assets/{CliPlugin-BsU0ht7q.js → CliPlugin-CJ7jdm_s.js} +43 -609
  112. package/src/ui/dist/assets/{CodeEditorPlugin-DcMMP0Rt.js → CodeEditorPlugin-DhInVGFf.js} +8 -8
  113. package/src/ui/dist/assets/{CodeViewerPlugin-BqoQ5QyY.js → CodeViewerPlugin-D1n8S9r5.js} +5 -5
  114. package/src/ui/dist/assets/{DocViewerPlugin-D7eHNhU6.js → DocViewerPlugin-C4XM_kqk.js} +3 -3
  115. package/src/ui/dist/assets/{GitDiffViewerPlugin-DLJN42T5.js → GitDiffViewerPlugin-W6kS9r6v.js} +1 -1
  116. package/src/ui/dist/assets/{ImageViewerPlugin-gJMV7MOu.js → ImageViewerPlugin-DPeUx_Oz.js} +5 -6
  117. package/src/ui/dist/assets/{LabCopilotPanel-B857sfxP.js → LabCopilotPanel-eAelUaub.js} +12 -15
  118. package/src/ui/dist/assets/LabPlugin-BbOrBxKY.js +2676 -0
  119. package/src/ui/dist/assets/{LatexPlugin-DWKEo-Wj.js → LatexPlugin-C-HhkVXY.js} +16 -16
  120. package/src/ui/dist/assets/{MarkdownViewerPlugin-DBzoEmhv.js → MarkdownViewerPlugin-BDIzIBfh.js} +4 -4
  121. package/src/ui/dist/assets/{MarketplacePlugin-DoHc-8vo.js → MarketplacePlugin-DAOJphwr.js} +3 -3
  122. package/src/ui/dist/assets/{NotebookEditor-CKjKH-yS.js → NotebookEditor-BsoMvDoU.js} +3 -3
  123. package/src/ui/dist/assets/{PdfLoader-zFoL0VPo.js → PdfLoader-fiC7RtHf.js} +1 -1
  124. package/src/ui/dist/assets/{PdfMarkdownPlugin-DXPaL9Nt.js → PdfMarkdownPlugin-C5OxZBFK.js} +3 -3
  125. package/src/ui/dist/assets/{PdfViewerPlugin-DhK8qCFp.js → PdfViewerPlugin-CAbxQebk.js} +10 -10
  126. package/src/ui/dist/assets/{SearchPlugin-CdSi6krf.js → SearchPlugin-SE33Lb9B.js} +1 -1
  127. package/src/ui/dist/assets/{Stepper-V-WiDQJl.js → Stepper-0Av7GfV7.js} +1 -1
  128. package/src/ui/dist/assets/{TextViewerPlugin-hIs1Efiu.js → TextViewerPlugin-Daf2gJDI.js} +4 -4
  129. package/src/ui/dist/assets/{VNCViewer-DG8b0q2X.js → VNCViewer-BKrMUIOX.js} +9 -10
  130. package/src/ui/dist/assets/{bibtex-HDac6fVW.js → bibtex-JBdOEe45.js} +1 -1
  131. package/src/ui/dist/assets/{code-BnBeNxBc.js → code-B0TDFCZz.js} +1 -1
  132. package/src/ui/dist/assets/{file-content-IRQ3jHb8.js → file-content-3YtrSacz.js} +1 -1
  133. package/src/ui/dist/assets/{file-diff-panel-DZoQ9I6r.js → file-diff-panel-CJEg5OG1.js} +1 -1
  134. package/src/ui/dist/assets/{file-socket-BMCdLc-P.js → file-socket-CYQYdmB1.js} +1 -1
  135. package/src/ui/dist/assets/{file-utils-CltILB3w.js → file-utils-Cd1C9Ppl.js} +1 -1
  136. package/src/ui/dist/assets/{image-Boe6ffhu.js → image-B33ctrvC.js} +1 -1
  137. package/src/ui/dist/assets/{index-2Zf65FZt.js → index-9CLPVeZh.js} +1 -1
  138. package/src/ui/dist/assets/{index-DZqJ-qAM.js → index-BNQWqmJ2.js} +60 -2154
  139. package/src/ui/dist/assets/{index-DO43pFZP.js → index-BVXsmS7V.js} +84086 -84365
  140. package/src/ui/dist/assets/{index-BlplpvE1.js → index-Buw_N1VQ.js} +2 -2
  141. package/src/ui/dist/assets/{index-Bq2bvfkl.css → index-SwmFAld3.css} +2622 -2619
  142. package/src/ui/dist/assets/{message-square-mUHn_Ssb.js → message-square-D0cUJ9yU.js} +1 -1
  143. package/src/ui/dist/assets/{monaco-fe0arNEU.js → monaco-UZLYkp2n.js} +1 -1
  144. package/src/ui/dist/assets/{popover-D_7i19qU.js → popover-CTeiY-dK.js} +1 -1
  145. package/src/ui/dist/assets/{project-sync-DyVGrU7H.js → project-sync-Dbs01Xky.js} +2 -8
  146. package/src/ui/dist/assets/{sigma-BzazRyxQ.js → sigma-CM08S-xT.js} +1 -1
  147. package/src/ui/dist/assets/{tooltip-DN_yjHFH.js → tooltip-pDtzvU9p.js} +1 -1
  148. package/src/ui/dist/assets/trash-YvPCP-da.js +32 -0
  149. package/src/ui/dist/assets/{useCliAccess-DV2L2Qxy.js → useCliAccess-Bavi74Ac.js} +12 -42
  150. package/src/ui/dist/assets/{useFileDiffOverlay-DyTj-p_V.js → useFileDiffOverlay-CVXY6oeg.js} +1 -1
  151. package/src/ui/dist/assets/{wrap-text-ozYHtUwq.js → wrap-text-Cf4flRW7.js} +1 -1
  152. package/src/ui/dist/assets/{zoom-out-BN9MUyCQ.js → zoom-out-Hb0Z1YpT.js} +1 -1
  153. package/src/ui/dist/index.html +2 -2
  154. package/uv.lock +1155 -0
  155. package/assets/fonts/Inter-Variable.ttf +0 -0
  156. package/assets/fonts/NotoSerifSC-Regular-C94HN_ZN.ttf +0 -0
  157. package/assets/fonts/NunitoSans-Variable.ttf +0 -0
  158. package/assets/fonts/Satoshi-Medium-ByP-Zb-9.woff2 +0 -0
  159. package/assets/fonts/SourceSans3-Variable.ttf +0 -0
  160. package/assets/fonts/ds-fonts.css +0 -83
  161. package/src/ui/dist/assets/Inter-Variable-VF2RPR_K.ttf +0 -0
  162. package/src/ui/dist/assets/LabPlugin-bL7rpic8.js +0 -43
  163. package/src/ui/dist/assets/NotoSerifSC-Regular-C94HN_ZN-C94HN_ZN.ttf +0 -0
  164. package/src/ui/dist/assets/NunitoSans-Variable-B_ZymHAd.ttf +0 -0
  165. package/src/ui/dist/assets/Satoshi-Medium-ByP-Zb-9-GkA34YXu.woff2 +0 -0
  166. package/src/ui/dist/assets/SourceSans3-Variable-CD-WOsSK.ttf +0 -0
  167. package/src/ui/dist/assets/info-CcsK_htA.js +0 -18
  168. package/src/ui/dist/assets/user-plus-BusDx-hF.js +0 -79
@@ -0,0 +1,145 @@
1
+ {
2
+ "id": "lingzhu",
3
+ "uiHints": {
4
+ "enabled": {
5
+ "label": "启用灵珠接入"
6
+ },
7
+ "authAk": {
8
+ "label": "鉴权密钥 (AK)",
9
+ "sensitive": true,
10
+ "help": "灵珠平台调用时携带的 Bearer Token,留空则自动生成"
11
+ },
12
+ "agentId": {
13
+ "label": "智能体 ID",
14
+ "help": "使用的 OpenClaw 智能体 ID,默认 main"
15
+ },
16
+ "includeMetadata": {
17
+ "label": "透传设备元信息",
18
+ "help": "是否将 metadata 中的时间、位置、电量等信息传给 OpenClaw"
19
+ },
20
+ "requestTimeoutMs": {
21
+ "label": "上游请求超时 (ms)",
22
+ "help": "调用 OpenClaw /v1/chat/completions 的超时时间,范围 5000~300000"
23
+ },
24
+ "systemPrompt": {
25
+ "label": "自定义系统提示词",
26
+ "help": "用于补充业务约束,帮助模型更稳定地选择设备工具"
27
+ },
28
+ "defaultNavigationMode": {
29
+ "label": "默认导航方式",
30
+ "help": "0=驾车,1=步行,2=骑行"
31
+ },
32
+ "enableFollowUp": {
33
+ "label": "启用 follow_up 建议",
34
+ "help": "是否在普通文本回答后生成 follow_up 建议"
35
+ },
36
+ "followUpMaxCount": {
37
+ "label": "follow_up 上限",
38
+ "help": "最多返回多少条 follow_up 建议"
39
+ },
40
+ "maxImageBytes": {
41
+ "label": "图片大小上限 (bytes)",
42
+ "help": "下载或解码图片时允许的最大体积"
43
+ },
44
+ "sessionMode": {
45
+ "label": "会话策略",
46
+ "help": "per_user / shared_agent / per_message"
47
+ },
48
+ "sessionNamespace": {
49
+ "label": "会话命名空间",
50
+ "help": "构造 session key 时使用的前缀"
51
+ },
52
+ "debugLogging": {
53
+ "label": "文件调试日志",
54
+ "help": "启用后将桥接链路写入日志文件"
55
+ },
56
+ "debugLogPayloads": {
57
+ "label": "记录完整载荷",
58
+ "help": "启用后日志中包含完整请求和响应 JSON,仅建议联调时开启"
59
+ },
60
+ "debugLogDir": {
61
+ "label": "调试日志目录",
62
+ "help": "留空则写入插件目录下的 logs/"
63
+ },
64
+ "enableExperimentalNativeActions": {
65
+ "label": "实验性原生动作",
66
+ "help": "启用通知、Toast、TTS、录像和自定义页面桥接"
67
+ }
68
+ },
69
+ "configSchema": {
70
+ "type": "object",
71
+ "additionalProperties": false,
72
+ "properties": {
73
+ "enabled": {
74
+ "type": "boolean",
75
+ "default": true
76
+ },
77
+ "authAk": {
78
+ "type": "string"
79
+ },
80
+ "agentId": {
81
+ "type": "string",
82
+ "default": "main"
83
+ },
84
+ "includeMetadata": {
85
+ "type": "boolean",
86
+ "default": true
87
+ },
88
+ "requestTimeoutMs": {
89
+ "type": "number",
90
+ "minimum": 5000,
91
+ "maximum": 300000,
92
+ "default": 60000
93
+ },
94
+ "systemPrompt": {
95
+ "type": "string"
96
+ },
97
+ "defaultNavigationMode": {
98
+ "type": "string",
99
+ "enum": ["0", "1", "2"],
100
+ "default": "0"
101
+ },
102
+ "enableFollowUp": {
103
+ "type": "boolean",
104
+ "default": true
105
+ },
106
+ "followUpMaxCount": {
107
+ "type": "number",
108
+ "minimum": 0,
109
+ "maximum": 8,
110
+ "default": 3
111
+ },
112
+ "maxImageBytes": {
113
+ "type": "number",
114
+ "minimum": 262144,
115
+ "maximum": 20971520,
116
+ "default": 5242880
117
+ },
118
+ "sessionMode": {
119
+ "type": "string",
120
+ "enum": ["per_user", "shared_agent", "per_message"],
121
+ "default": "per_user"
122
+ },
123
+ "sessionNamespace": {
124
+ "type": "string",
125
+ "default": "lingzhu"
126
+ },
127
+ "debugLogging": {
128
+ "type": "boolean",
129
+ "default": false
130
+ },
131
+ "debugLogPayloads": {
132
+ "type": "boolean",
133
+ "default": false
134
+ },
135
+ "debugLogDir": {
136
+ "type": "string",
137
+ "default": ""
138
+ },
139
+ "enableExperimentalNativeActions": {
140
+ "type": "boolean",
141
+ "default": false
142
+ }
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@openclaw/lingzhu",
3
+ "version": "1.0.0",
4
+ "description": "灵珠平台接入 OpenClaw 的桥接插件",
5
+ "type": "module",
6
+ "scripts": {
7
+ "typecheck": "tsc --noEmit",
8
+ "check": "npm run typecheck"
9
+ },
10
+ "openclaw": {
11
+ "extensions": [
12
+ "./index.ts"
13
+ ]
14
+ },
15
+ "moltbot": {
16
+ "extensions": [
17
+ "./index.ts"
18
+ ]
19
+ },
20
+ "dependencies": {
21
+ "commander": "^13.1.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.13.0",
25
+ "typescript": "^5.3.0"
26
+ },
27
+ "keywords": [
28
+ "lingzhu",
29
+ "openclaw",
30
+ "moltbot",
31
+ "plugin",
32
+ "sse"
33
+ ],
34
+ "license": "MIT"
35
+ }
@@ -0,0 +1,180 @@
1
+ import type { Command } from "commander";
2
+ import type { LingzhuConfig } from "./types.js";
3
+ import { getDebugLogFilePath } from "./debug-log.js";
4
+ import { cleanupImageCache, summarizeImageCache } from "./image-cache.js";
5
+
6
+ interface CliContext {
7
+ program: Command;
8
+ }
9
+
10
+ interface LingzhuState {
11
+ config: LingzhuConfig;
12
+ authAk: string;
13
+ gatewayPort: number;
14
+ chatCompletionsEnabled?: boolean;
15
+ }
16
+
17
+ function maskSecret(secret: string): string {
18
+ if (!secret) {
19
+ return "(empty)";
20
+ }
21
+
22
+ if (secret.length <= 8) {
23
+ return "*".repeat(secret.length);
24
+ }
25
+
26
+ return `${secret.slice(0, 4)}${"*".repeat(secret.length - 8)}${secret.slice(-4)}`;
27
+ }
28
+
29
+ export function registerLingzhuCli(
30
+ ctx: CliContext,
31
+ getState: () => LingzhuState
32
+ ) {
33
+ const { program } = ctx;
34
+
35
+ const lingzhuCmd = program
36
+ .command("lingzhu")
37
+ .description("灵珠平台接入管理");
38
+
39
+ lingzhuCmd
40
+ .command("info")
41
+ .description("显示灵珠接入信息")
42
+ .action(() => {
43
+ const state = getState();
44
+ const url = `http://127.0.0.1:${state.gatewayPort}/metis/agent/api/sse`;
45
+ const debugLogState = `${state.config.debugLogging ? "ON" : "OFF"} ${getDebugLogFilePath(state.config)}`;
46
+
47
+ console.log("");
48
+ console.log("Lingzhu Bridge");
49
+ console.log(` SSE 接口: ${url}`);
50
+ console.log(` 鉴权 AK: ${maskSecret(state.authAk)}`);
51
+ console.log(` 智能体 ID: ${state.config.agentId || "main"}`);
52
+ console.log(` 会话策略: ${state.config.sessionMode || "per_user"}`);
53
+ console.log(` 调试日志: ${debugLogState}`);
54
+ console.log(` 状态: ${state.config.enabled !== false ? "已启用" : "已禁用"}`);
55
+ console.log("");
56
+ console.log("如需复制完整 Bearer Token 调试示例,请运行:");
57
+ console.log(" openclaw lingzhu curl");
58
+ console.log("");
59
+ });
60
+
61
+ lingzhuCmd
62
+ .command("status")
63
+ .description("检查灵珠接入状态")
64
+ .action(() => {
65
+ const state = getState();
66
+ console.log(state.config.enabled !== false ? "已启用" : "已禁用");
67
+ });
68
+
69
+ lingzhuCmd
70
+ .command("curl")
71
+ .description("输出可直接复制的本地联调 curl 命令")
72
+ .action(() => {
73
+ const state = getState();
74
+ const url = `http://127.0.0.1:${state.gatewayPort}/metis/agent/api/sse`;
75
+ const agentId = state.config.agentId || "main";
76
+
77
+ console.log(`curl -X POST '${url}' \\`);
78
+ console.log(`--header 'Authorization: Bearer ${state.authAk}' \\`);
79
+ console.log("--header 'Content-Type: application/json' \\");
80
+ console.log("--data '{");
81
+ console.log(' "message_id": "test_local_01",');
82
+ console.log(` "agent_id": "${agentId}",`);
83
+ console.log(' "message": [');
84
+ console.log(' {"role": "user", "type": "text", "text": "你好"}');
85
+ console.log(" ]");
86
+ console.log("}'");
87
+ });
88
+
89
+ lingzhuCmd
90
+ .command("capabilities")
91
+ .description("显示当前桥接支持的眼镜能力")
92
+ .action(() => {
93
+ const state = getState();
94
+ const experimentalEnabled = state.config.enableExperimentalNativeActions === true;
95
+
96
+ console.log("支持的眼镜能力:");
97
+ console.log(" - take_photo: 拍照");
98
+ console.log(" - take_navigation: 导航");
99
+ console.log(" - control_calendar: 日程提醒");
100
+ console.log(" - notify_agent_off: 退出智能体");
101
+
102
+ if (experimentalEnabled) {
103
+ console.log(" - send_notification: 实验性通知");
104
+ console.log(" - send_toast: 实验性提示");
105
+ console.log(" - speak_tts: 实验性播报");
106
+ console.log(" - start_video_record / stop_video_record: 实验性录像");
107
+ console.log(" - open_custom_view: 实验性自定义页面");
108
+ } else {
109
+ console.log(" - 实验性原生动作: 未启用");
110
+ console.log(" 需设置 enableExperimentalNativeActions=true 后可用");
111
+ }
112
+
113
+ console.log("");
114
+ console.log("桥接增强能力:");
115
+ console.log(" - 多模态图片预处理(受信 file URL / data URL / 远程图片)");
116
+ console.log(" - Follow-up 建议生成");
117
+ console.log(" - 可配置会话策略");
118
+ console.log(" - 健康检查与联调 curl");
119
+ console.log(" - 文件调试日志与载荷脱敏");
120
+ });
121
+
122
+ lingzhuCmd
123
+ .command("logpath")
124
+ .description("显示桥接文件日志路径")
125
+ .action(() => {
126
+ const state = getState();
127
+ console.log(getDebugLogFilePath(state.config));
128
+ });
129
+
130
+ lingzhuCmd
131
+ .command("doctor")
132
+ .description("输出桥接自检结果")
133
+ .action(async () => {
134
+ const state = getState();
135
+ const cache = await summarizeImageCache();
136
+ const issues: string[] = [];
137
+
138
+ if (state.config.enabled === false) {
139
+ issues.push("插件当前处于禁用状态");
140
+ }
141
+ if (!state.authAk) {
142
+ issues.push("当前没有可用 AK");
143
+ }
144
+ if (!state.config.agentId) {
145
+ issues.push("未显式配置 agentId,将回退到 main");
146
+ }
147
+ if (state.chatCompletionsEnabled !== true) {
148
+ issues.push("gateway.http.endpoints.chatCompletions.enabled 未开启");
149
+ }
150
+
151
+ console.log("Lingzhu Doctor");
152
+ console.log(` 插件状态: ${state.config.enabled !== false ? "已启用" : "已禁用"}`);
153
+ console.log(` 智能体 ID: ${state.config.agentId || "main"}`);
154
+ console.log(` 请求超时: ${state.config.requestTimeoutMs || 60000} ms`);
155
+ console.log(` 会话策略: ${state.config.sessionMode || "per_user"}`);
156
+ console.log(` Follow-up: ${state.config.enableFollowUp !== false ? "ON" : "OFF"}`);
157
+ console.log(` 实验动作: ${state.config.enableExperimentalNativeActions === true ? "ON" : "OFF"}`);
158
+ console.log(` 调试日志: ${state.config.debugLogging === true ? "ON" : "OFF"}`);
159
+ console.log(` Chat Completions: ${state.chatCompletionsEnabled === true ? "ON" : "OFF"}`);
160
+ console.log(` 图片缓存: ${cache.dir} (${cache.files} files)`);
161
+
162
+ if (issues.length > 0) {
163
+ console.log("");
164
+ console.log("Warnings:");
165
+ for (const issue of issues) {
166
+ console.log(` - ${issue}`);
167
+ }
168
+ }
169
+ });
170
+
171
+ lingzhuCmd
172
+ .command("cache-cleanup")
173
+ .description("清理 24 小时前的图片缓存")
174
+ .action(async () => {
175
+ const summary = await cleanupImageCache();
176
+ console.log(`removed=${summary.removed} kept=${summary.kept}`);
177
+ });
178
+
179
+ return lingzhuCmd;
180
+ }
@@ -0,0 +1,196 @@
1
+ import crypto from "node:crypto";
2
+ import type { LingzhuConfig } from "./types.js";
3
+
4
+ const DEFAULT_CONFIG: Required<LingzhuConfig> = {
5
+ enabled: true,
6
+ authAk: "",
7
+ agentId: "main",
8
+ includeMetadata: true,
9
+ requestTimeoutMs: 60000,
10
+ systemPrompt: "",
11
+ defaultNavigationMode: "0",
12
+ enableFollowUp: true,
13
+ followUpMaxCount: 3,
14
+ maxImageBytes: 5 * 1024 * 1024,
15
+ sessionMode: "per_user",
16
+ sessionNamespace: "lingzhu",
17
+ autoReceiptAck: true,
18
+ visibleProgressHeartbeat: true,
19
+ visibleProgressHeartbeatSec: 10,
20
+ debugLogging: false,
21
+ debugLogPayloads: false,
22
+ debugLogDir: "",
23
+ enableExperimentalNativeActions: false,
24
+ };
25
+
26
+ export function resolveLingzhuConfig(raw: unknown): LingzhuConfig {
27
+ const cfg = (raw ?? {}) as Partial<LingzhuConfig>;
28
+
29
+ const timeout = typeof cfg.requestTimeoutMs === "number" && Number.isFinite(cfg.requestTimeoutMs)
30
+ ? Math.max(5000, Math.min(300000, Math.trunc(cfg.requestTimeoutMs)))
31
+ : DEFAULT_CONFIG.requestTimeoutMs;
32
+ const followUpMaxCount = typeof cfg.followUpMaxCount === "number" && Number.isFinite(cfg.followUpMaxCount)
33
+ ? Math.max(0, Math.min(8, Math.trunc(cfg.followUpMaxCount)))
34
+ : DEFAULT_CONFIG.followUpMaxCount;
35
+ const maxImageBytes = typeof cfg.maxImageBytes === "number" && Number.isFinite(cfg.maxImageBytes)
36
+ ? Math.max(256 * 1024, Math.min(20 * 1024 * 1024, Math.trunc(cfg.maxImageBytes)))
37
+ : DEFAULT_CONFIG.maxImageBytes;
38
+ const defaultNavigationMode = cfg.defaultNavigationMode === "1" || cfg.defaultNavigationMode === "2"
39
+ ? cfg.defaultNavigationMode
40
+ : DEFAULT_CONFIG.defaultNavigationMode;
41
+ const sessionMode = cfg.sessionMode === "shared_agent" || cfg.sessionMode === "per_message"
42
+ ? cfg.sessionMode
43
+ : DEFAULT_CONFIG.sessionMode;
44
+ const visibleProgressHeartbeatSec =
45
+ typeof cfg.visibleProgressHeartbeatSec === "number" && Number.isFinite(cfg.visibleProgressHeartbeatSec)
46
+ ? Math.max(5, Math.min(120, Math.trunc(cfg.visibleProgressHeartbeatSec)))
47
+ : DEFAULT_CONFIG.visibleProgressHeartbeatSec;
48
+
49
+ return {
50
+ enabled: cfg.enabled ?? DEFAULT_CONFIG.enabled,
51
+ authAk: cfg.authAk ?? DEFAULT_CONFIG.authAk,
52
+ agentId: cfg.agentId ?? DEFAULT_CONFIG.agentId,
53
+ includeMetadata: cfg.includeMetadata ?? DEFAULT_CONFIG.includeMetadata,
54
+ requestTimeoutMs: timeout,
55
+ systemPrompt: typeof cfg.systemPrompt === "string" ? cfg.systemPrompt.trim() : DEFAULT_CONFIG.systemPrompt,
56
+ defaultNavigationMode,
57
+ enableFollowUp: cfg.enableFollowUp ?? DEFAULT_CONFIG.enableFollowUp,
58
+ followUpMaxCount,
59
+ maxImageBytes,
60
+ sessionMode,
61
+ sessionNamespace: typeof cfg.sessionNamespace === "string" && cfg.sessionNamespace.trim()
62
+ ? cfg.sessionNamespace.trim()
63
+ : DEFAULT_CONFIG.sessionNamespace,
64
+ autoReceiptAck: cfg.autoReceiptAck ?? DEFAULT_CONFIG.autoReceiptAck,
65
+ visibleProgressHeartbeat: cfg.visibleProgressHeartbeat ?? DEFAULT_CONFIG.visibleProgressHeartbeat,
66
+ visibleProgressHeartbeatSec,
67
+ debugLogging: cfg.debugLogging ?? DEFAULT_CONFIG.debugLogging,
68
+ debugLogPayloads: cfg.debugLogPayloads ?? DEFAULT_CONFIG.debugLogPayloads,
69
+ debugLogDir: typeof cfg.debugLogDir === "string" ? cfg.debugLogDir.trim() : DEFAULT_CONFIG.debugLogDir,
70
+ enableExperimentalNativeActions:
71
+ cfg.enableExperimentalNativeActions ?? DEFAULT_CONFIG.enableExperimentalNativeActions,
72
+ };
73
+ }
74
+
75
+ export function generateAuthAk(): string {
76
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
77
+ const segments = [8, 4, 4, 4, 12];
78
+
79
+ return segments
80
+ .map((len) => {
81
+ const bytes = crypto.randomBytes(len);
82
+ let value = "";
83
+
84
+ for (let i = 0; i < len; i += 1) {
85
+ value += chars[bytes[i] % chars.length];
86
+ }
87
+
88
+ return value;
89
+ })
90
+ .join("-");
91
+ }
92
+
93
+ export const lingzhuConfigSchema = {
94
+ type: "object" as const,
95
+ additionalProperties: false,
96
+ properties: {
97
+ enabled: { type: "boolean" as const },
98
+ authAk: { type: "string" as const },
99
+ agentId: { type: "string" as const },
100
+ includeMetadata: { type: "boolean" as const },
101
+ requestTimeoutMs: { type: "number" as const, minimum: 5000, maximum: 300000 },
102
+ systemPrompt: { type: "string" as const },
103
+ defaultNavigationMode: { type: "string" as const, enum: ["0", "1", "2"] },
104
+ enableFollowUp: { type: "boolean" as const },
105
+ followUpMaxCount: { type: "number" as const, minimum: 0, maximum: 8 },
106
+ maxImageBytes: { type: "number" as const, minimum: 262144, maximum: 20971520 },
107
+ sessionMode: { type: "string" as const, enum: ["per_user", "shared_agent", "per_message"] },
108
+ sessionNamespace: { type: "string" as const },
109
+ autoReceiptAck: { type: "boolean" as const },
110
+ visibleProgressHeartbeat: { type: "boolean" as const },
111
+ visibleProgressHeartbeatSec: { type: "number" as const, minimum: 5, maximum: 120 },
112
+ debugLogging: { type: "boolean" as const },
113
+ debugLogPayloads: { type: "boolean" as const },
114
+ debugLogDir: { type: "string" as const },
115
+ enableExperimentalNativeActions: { type: "boolean" as const },
116
+ },
117
+ parse(value: unknown): LingzhuConfig {
118
+ return resolveLingzhuConfig(value);
119
+ },
120
+ uiHints: {
121
+ enabled: { label: "启用灵珠接入" },
122
+ authAk: {
123
+ label: "鉴权密钥 (AK)",
124
+ sensitive: true,
125
+ help: "灵珠平台调用时携带的 Bearer Token,留空则自动生成",
126
+ },
127
+ agentId: {
128
+ label: "智能体 ID",
129
+ help: "使用的 OpenClaw 智能体 ID,默认 main",
130
+ },
131
+ includeMetadata: {
132
+ label: "透传设备元信息",
133
+ help: "是否将 metadata 中的时间、位置、电量等信息传给 OpenClaw,默认开启",
134
+ },
135
+ requestTimeoutMs: {
136
+ label: "上游请求超时 (ms)",
137
+ help: "调用 OpenClaw /v1/chat/completions 的超时时间,范围 5000~300000",
138
+ },
139
+ systemPrompt: {
140
+ label: "自定义系统提示词",
141
+ help: "可补充业务约束,帮助模型更稳定地选择拍照、导航、日程或退出工具",
142
+ },
143
+ defaultNavigationMode: {
144
+ label: "默认导航方式",
145
+ help: "当模型未明确指定时使用的导航模式:0=驾车,1=步行,2=骑行",
146
+ },
147
+ enableFollowUp: {
148
+ label: "启用 follow_up 建议",
149
+ help: "是否在普通文本回答后生成 follow_up 建议",
150
+ },
151
+ followUpMaxCount: {
152
+ label: "follow_up 上限",
153
+ help: "最多返回多少条 follow_up 建议,范围 0~8",
154
+ },
155
+ maxImageBytes: {
156
+ label: "图片大小上限 (bytes)",
157
+ help: "下载远程图片或解码 data URL 时允许的最大体积,范围 256KB~20MB",
158
+ },
159
+ sessionMode: {
160
+ label: "会话策略",
161
+ help: "per_user 按用户保持上下文,shared_agent 全员共享,per_message 每次独立",
162
+ },
163
+ sessionNamespace: {
164
+ label: "会话命名空间",
165
+ help: "构造 OpenClaw session key 时使用的前缀,便于多套桥接并存",
166
+ },
167
+ autoReceiptAck: {
168
+ label: "自动接收回执",
169
+ help: "在请求进入后立即发送一条可见回执,避免用户在眼镜端误以为没有收到请求",
170
+ },
171
+ visibleProgressHeartbeat: {
172
+ label: "可见进度心跳",
173
+ help: "长时间没有上游正文输出时,桥接层补发轻量可见进度帧,而不只发送 SSE 注释心跳",
174
+ },
175
+ visibleProgressHeartbeatSec: {
176
+ label: "可见心跳间隔(秒)",
177
+ help: "桥接层补发可见进度帧的最小间隔,范围 5~120 秒",
178
+ },
179
+ debugLogging: {
180
+ label: "文件调试日志",
181
+ help: "启用后将桥接链路写入插件目录 logs/ 或自定义目录",
182
+ },
183
+ debugLogPayloads: {
184
+ label: "记录完整载荷",
185
+ help: "启用后日志中包含完整请求和响应 JSON,仅建议联调时开启",
186
+ },
187
+ debugLogDir: {
188
+ label: "调试日志目录",
189
+ help: "调试日志写入目录,留空则使用插件目录下的 logs/",
190
+ },
191
+ enableExperimentalNativeActions: {
192
+ label: "实验性原生动作",
193
+ help: "启用通知、Toast、TTS、录像和自定义页面等实验性桥接动作",
194
+ },
195
+ },
196
+ };
@@ -0,0 +1,111 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { LingzhuConfig } from "./types.js";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const DEFAULT_LOG_DIR = path.resolve(__dirname, "../logs");
8
+
9
+ function sanitizeSegment(value: string): string {
10
+ return value.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_");
11
+ }
12
+
13
+ function redactValue(value: unknown): unknown {
14
+ if (Array.isArray(value)) {
15
+ return value.map((item) => redactValue(item));
16
+ }
17
+
18
+ if (value && typeof value === "object") {
19
+ const result: Record<string, unknown> = {};
20
+ for (const [key, innerValue] of Object.entries(value as Record<string, unknown>)) {
21
+ const lowerKey = key.toLowerCase();
22
+ if (
23
+ lowerKey.includes("authorization") ||
24
+ lowerKey.includes("authak") ||
25
+ lowerKey.includes("token") ||
26
+ lowerKey.includes("secret") ||
27
+ lowerKey === "ak"
28
+ ) {
29
+ result[key] = "***redacted***";
30
+ } else {
31
+ result[key] = redactValue(innerValue);
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+
37
+ return value;
38
+ }
39
+
40
+ export function resolveDebugLogDir(config: LingzhuConfig): string {
41
+ if (config.debugLogDir && config.debugLogDir.trim()) {
42
+ return config.debugLogDir.trim();
43
+ }
44
+
45
+ return DEFAULT_LOG_DIR;
46
+ }
47
+
48
+ export function getDebugLogFilePath(config: LingzhuConfig): string {
49
+ const now = new Date();
50
+ const date = [
51
+ now.getFullYear(),
52
+ String(now.getMonth() + 1).padStart(2, "0"),
53
+ String(now.getDate()).padStart(2, "0"),
54
+ ].join("-");
55
+ return path.join(resolveDebugLogDir(config), `lingzhu-${date}.log`);
56
+ }
57
+
58
+ export function writeDebugLog(
59
+ config: LingzhuConfig,
60
+ event: string,
61
+ payload: unknown,
62
+ always = false
63
+ ): void {
64
+ if (!always && config.debugLogging !== true) {
65
+ return;
66
+ }
67
+
68
+ const filePath = getDebugLogFilePath(config);
69
+ const line = JSON.stringify({
70
+ ts: new Date().toISOString(),
71
+ event,
72
+ payload: redactValue(payload),
73
+ });
74
+
75
+ void (async () => {
76
+ try {
77
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
78
+ await fs.promises.appendFile(filePath, `${line}\n`, "utf8");
79
+ } catch {
80
+ // Ignore logging failures so debug logging cannot break request handling.
81
+ }
82
+ })();
83
+ }
84
+
85
+ export function summarizeForDebug(value: unknown, includePayload: boolean): unknown {
86
+ if (includePayload) {
87
+ return value;
88
+ }
89
+
90
+ if (value && typeof value === "object") {
91
+ const summary: Record<string, unknown> = {};
92
+ for (const [key, innerValue] of Object.entries(value as Record<string, unknown>)) {
93
+ if (Array.isArray(innerValue)) {
94
+ summary[key] = `[array:${innerValue.length}]`;
95
+ } else if (innerValue && typeof innerValue === "object") {
96
+ summary[key] = "{object}";
97
+ } else if (typeof innerValue === "string" && innerValue.length > 160) {
98
+ summary[key] = `${innerValue.slice(0, 160)}...`;
99
+ } else {
100
+ summary[key] = innerValue;
101
+ }
102
+ }
103
+ return summary;
104
+ }
105
+
106
+ return value;
107
+ }
108
+
109
+ export function buildRequestLogName(messageId: string, event: string): string {
110
+ return `${sanitizeSegment(messageId || "unknown")}.${event}`;
111
+ }
@@ -0,0 +1,4 @@
1
+ import { EventEmitter } from "node:events";
2
+
3
+ export const lingzhuEventBus = new EventEmitter();
4
+ lingzhuEventBus.setMaxListeners(100);