@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.
- package/AGENTS.md +26 -0
- package/README.md +47 -161
- package/assets/connectors/lingzhu/openclaw-bridge/README.md +124 -0
- package/assets/connectors/lingzhu/openclaw-bridge/index.ts +162 -0
- package/assets/connectors/lingzhu/openclaw-bridge/openclaw.plugin.json +145 -0
- package/assets/connectors/lingzhu/openclaw-bridge/package.json +35 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/cli.ts +180 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/config.ts +196 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/debug-log.ts +111 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/events.ts +4 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/http-handler.ts +1133 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/image-cache.ts +75 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/lingzhu-tools.ts +246 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/transform.ts +541 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/types.ts +131 -0
- package/assets/connectors/lingzhu/openclaw-bridge/tsconfig.json +14 -0
- package/assets/connectors/lingzhu/openclaw.lingzhu.config.template.json +39 -0
- package/bin/ds.js +2048 -166
- package/docs/en/00_QUICK_START.md +152 -0
- package/docs/en/01_SETTINGS_REFERENCE.md +1104 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +404 -0
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +325 -0
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +216 -0
- package/docs/en/05_TUI_GUIDE.md +141 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +679 -0
- package/docs/en/07_MEMORY_AND_MCP.md +253 -0
- package/docs/en/08_FIGURE_STYLE_GUIDE.md +97 -0
- package/docs/en/09_DOCTOR.md +152 -0
- package/docs/en/90_ARCHITECTURE.md +247 -0
- package/docs/en/91_DEVELOPMENT.md +195 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +29 -0
- package/docs/zh/00_QUICK_START.md +152 -0
- package/docs/zh/01_SETTINGS_REFERENCE.md +1137 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +414 -0
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +324 -0
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +230 -0
- package/docs/zh/05_TUI_GUIDE.md +128 -0
- package/docs/zh/06_RUNTIME_AND_CANVAS.md +271 -0
- package/docs/zh/07_MEMORY_AND_MCP.md +235 -0
- package/docs/zh/08_FIGURE_STYLE_GUIDE.md +97 -0
- package/docs/zh/09_DOCTOR.md +154 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +29 -0
- package/install.sh +41 -16
- package/package.json +5 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +6 -1
- package/src/deepscientist/artifact/guidance.py +9 -2
- package/src/deepscientist/artifact/service.py +1026 -39
- package/src/deepscientist/bash_exec/monitor.py +27 -5
- package/src/deepscientist/bash_exec/runtime.py +639 -0
- package/src/deepscientist/bash_exec/service.py +99 -16
- package/src/deepscientist/bridges/base.py +3 -0
- package/src/deepscientist/bridges/connectors.py +292 -13
- package/src/deepscientist/channels/qq.py +19 -2
- package/src/deepscientist/channels/relay.py +1 -0
- package/src/deepscientist/cli.py +32 -25
- package/src/deepscientist/config/models.py +28 -2
- package/src/deepscientist/config/service.py +202 -7
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +68 -6
- package/src/deepscientist/daemon/api/router.py +3 -0
- package/src/deepscientist/daemon/app.py +531 -15
- package/src/deepscientist/doctor.py +511 -0
- package/src/deepscientist/gitops/diff.py +3 -0
- package/src/deepscientist/home.py +26 -2
- package/src/deepscientist/latex_runtime.py +17 -4
- package/src/deepscientist/lingzhu_support.py +182 -0
- package/src/deepscientist/mcp/context.py +3 -1
- package/src/deepscientist/mcp/server.py +55 -2
- package/src/deepscientist/prompts/builder.py +222 -58
- package/src/deepscientist/quest/layout.py +2 -0
- package/src/deepscientist/quest/service.py +133 -14
- package/src/deepscientist/quest/stage_views.py +65 -1
- package/src/deepscientist/runners/codex.py +2 -0
- package/src/deepscientist/runtime_tools/__init__.py +16 -0
- package/src/deepscientist/runtime_tools/builtins.py +19 -0
- package/src/deepscientist/runtime_tools/models.py +29 -0
- package/src/deepscientist/runtime_tools/registry.py +40 -0
- package/src/deepscientist/runtime_tools/service.py +59 -0
- package/src/deepscientist/runtime_tools/tinytex.py +25 -0
- package/src/deepscientist/shared.py +44 -17
- package/src/deepscientist/tinytex.py +276 -0
- package/src/prompts/connectors/lingzhu.md +15 -0
- package/src/prompts/connectors/qq.md +121 -0
- package/src/prompts/system.md +214 -37
- package/src/skills/analysis-campaign/SKILL.md +46 -7
- package/src/skills/baseline/SKILL.md +12 -5
- package/src/skills/decision/SKILL.md +7 -5
- package/src/skills/experiment/SKILL.md +22 -5
- package/src/skills/finalize/SKILL.md +9 -5
- package/src/skills/idea/SKILL.md +6 -5
- package/src/skills/intake-audit/SKILL.md +277 -0
- package/src/skills/intake-audit/references/state-audit-template.md +41 -0
- package/src/skills/rebuttal/SKILL.md +409 -0
- package/src/skills/rebuttal/references/action-plan-template.md +63 -0
- package/src/skills/rebuttal/references/evidence-update-template.md +30 -0
- package/src/skills/rebuttal/references/response-letter-template.md +113 -0
- package/src/skills/rebuttal/references/review-matrix-template.md +55 -0
- package/src/skills/review/SKILL.md +295 -0
- package/src/skills/review/references/experiment-todo-template.md +29 -0
- package/src/skills/review/references/review-report-template.md +83 -0
- package/src/skills/review/references/revision-log-template.md +40 -0
- package/src/skills/scout/SKILL.md +6 -5
- package/src/skills/write/SKILL.md +8 -4
- package/src/tui/dist/components/WelcomePanel.js +17 -43
- package/src/tui/dist/components/messages/BashExecOperationMessage.js +3 -2
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-7v-dHngU.js → AiManusChatView-CZpg376x.js} +127 -597
- package/src/ui/dist/assets/{AnalysisPlugin-B_Xmz-KE.js → AnalysisPlugin-CtHA22g3.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-Cko-0tm1.js → AutoFigurePlugin-BSWmLMmF.js} +63 -8
- package/src/ui/dist/assets/{CliPlugin-BsU0ht7q.js → CliPlugin-CJ7jdm_s.js} +43 -609
- package/src/ui/dist/assets/{CodeEditorPlugin-DcMMP0Rt.js → CodeEditorPlugin-DhInVGFf.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BqoQ5QyY.js → CodeViewerPlugin-D1n8S9r5.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-D7eHNhU6.js → DocViewerPlugin-C4XM_kqk.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-DLJN42T5.js → GitDiffViewerPlugin-W6kS9r6v.js} +1 -1
- package/src/ui/dist/assets/{ImageViewerPlugin-gJMV7MOu.js → ImageViewerPlugin-DPeUx_Oz.js} +5 -6
- package/src/ui/dist/assets/{LabCopilotPanel-B857sfxP.js → LabCopilotPanel-eAelUaub.js} +12 -15
- package/src/ui/dist/assets/LabPlugin-BbOrBxKY.js +2676 -0
- package/src/ui/dist/assets/{LatexPlugin-DWKEo-Wj.js → LatexPlugin-C-HhkVXY.js} +16 -16
- package/src/ui/dist/assets/{MarkdownViewerPlugin-DBzoEmhv.js → MarkdownViewerPlugin-BDIzIBfh.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DoHc-8vo.js → MarketplacePlugin-DAOJphwr.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-CKjKH-yS.js → NotebookEditor-BsoMvDoU.js} +3 -3
- package/src/ui/dist/assets/{PdfLoader-zFoL0VPo.js → PdfLoader-fiC7RtHf.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-DXPaL9Nt.js → PdfMarkdownPlugin-C5OxZBFK.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-DhK8qCFp.js → PdfViewerPlugin-CAbxQebk.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-CdSi6krf.js → SearchPlugin-SE33Lb9B.js} +1 -1
- package/src/ui/dist/assets/{Stepper-V-WiDQJl.js → Stepper-0Av7GfV7.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-hIs1Efiu.js → TextViewerPlugin-Daf2gJDI.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-DG8b0q2X.js → VNCViewer-BKrMUIOX.js} +9 -10
- package/src/ui/dist/assets/{bibtex-HDac6fVW.js → bibtex-JBdOEe45.js} +1 -1
- package/src/ui/dist/assets/{code-BnBeNxBc.js → code-B0TDFCZz.js} +1 -1
- package/src/ui/dist/assets/{file-content-IRQ3jHb8.js → file-content-3YtrSacz.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-DZoQ9I6r.js → file-diff-panel-CJEg5OG1.js} +1 -1
- package/src/ui/dist/assets/{file-socket-BMCdLc-P.js → file-socket-CYQYdmB1.js} +1 -1
- package/src/ui/dist/assets/{file-utils-CltILB3w.js → file-utils-Cd1C9Ppl.js} +1 -1
- package/src/ui/dist/assets/{image-Boe6ffhu.js → image-B33ctrvC.js} +1 -1
- package/src/ui/dist/assets/{index-2Zf65FZt.js → index-9CLPVeZh.js} +1 -1
- package/src/ui/dist/assets/{index-DZqJ-qAM.js → index-BNQWqmJ2.js} +60 -2154
- package/src/ui/dist/assets/{index-DO43pFZP.js → index-BVXsmS7V.js} +84086 -84365
- package/src/ui/dist/assets/{index-BlplpvE1.js → index-Buw_N1VQ.js} +2 -2
- package/src/ui/dist/assets/{index-Bq2bvfkl.css → index-SwmFAld3.css} +2622 -2619
- package/src/ui/dist/assets/{message-square-mUHn_Ssb.js → message-square-D0cUJ9yU.js} +1 -1
- package/src/ui/dist/assets/{monaco-fe0arNEU.js → monaco-UZLYkp2n.js} +1 -1
- package/src/ui/dist/assets/{popover-D_7i19qU.js → popover-CTeiY-dK.js} +1 -1
- package/src/ui/dist/assets/{project-sync-DyVGrU7H.js → project-sync-Dbs01Xky.js} +2 -8
- package/src/ui/dist/assets/{sigma-BzazRyxQ.js → sigma-CM08S-xT.js} +1 -1
- package/src/ui/dist/assets/{tooltip-DN_yjHFH.js → tooltip-pDtzvU9p.js} +1 -1
- package/src/ui/dist/assets/trash-YvPCP-da.js +32 -0
- package/src/ui/dist/assets/{useCliAccess-DV2L2Qxy.js → useCliAccess-Bavi74Ac.js} +12 -42
- package/src/ui/dist/assets/{useFileDiffOverlay-DyTj-p_V.js → useFileDiffOverlay-CVXY6oeg.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-ozYHtUwq.js → wrap-text-Cf4flRW7.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BN9MUyCQ.js → zoom-out-Hb0Z1YpT.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/uv.lock +1155 -0
- package/assets/fonts/Inter-Variable.ttf +0 -0
- package/assets/fonts/NotoSerifSC-Regular-C94HN_ZN.ttf +0 -0
- package/assets/fonts/NunitoSans-Variable.ttf +0 -0
- package/assets/fonts/Satoshi-Medium-ByP-Zb-9.woff2 +0 -0
- package/assets/fonts/SourceSans3-Variable.ttf +0 -0
- package/assets/fonts/ds-fonts.css +0 -83
- package/src/ui/dist/assets/Inter-Variable-VF2RPR_K.ttf +0 -0
- package/src/ui/dist/assets/LabPlugin-bL7rpic8.js +0 -43
- package/src/ui/dist/assets/NotoSerifSC-Regular-C94HN_ZN-C94HN_ZN.ttf +0 -0
- package/src/ui/dist/assets/NunitoSans-Variable-B_ZymHAd.ttf +0 -0
- package/src/ui/dist/assets/Satoshi-Medium-ByP-Zb-9-GkA34YXu.woff2 +0 -0
- package/src/ui/dist/assets/SourceSans3-Variable-CD-WOsSK.ttf +0 -0
- package/src/ui/dist/assets/info-CcsK_htA.js +0 -18
- 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
|
+
}
|