cardbuddy 0.14.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +0 -0
- package/dist/preview-editor/assets/index-BkbBxDTT.css +1 -0
- package/dist/preview-editor/assets/index-Bro07DTa.js +313 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/standalone.js +3 -0
- package/dist/vendored/card-editor/runtime/adapter.js +2 -2
- package/dist/vendored/mcp/client.mjs +9 -2
- package/dist/vendored/server/core/ai-chat.js +1702 -134
- package/dist/vendored/server/core/browser.js +12 -3
- package/dist/vendored/server/core/cardFiles.js +3 -0
- package/dist/vendored/server/core/cardHistory.js +1 -0
- package/dist/vendored/server/core/cardImportMeta.js +7 -6
- package/dist/vendored/server/core/cardOpenFolder.js +1 -0
- package/dist/vendored/server/core/cardSave.js +1 -0
- package/dist/vendored/server/core/cardSync.js +3 -0
- package/dist/vendored/server/core/cardValidate.js +16 -5
- package/dist/vendored/server/core/codeBridge.js +5 -1
- package/dist/vendored/server/core/logStream.js +1 -0
- package/dist/vendored/server/core/sliceStatic.js +24 -4
- package/dist/vendored/server/core/sliceUpload.js +1 -0
- package/dist/vendored/server/core/turnJournal.js +6 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +257 -201
- package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +14 -82
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/diagnosis.mjs +207 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/layout-correction-sim.mjs +62 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/paradigm-router.mjs +76 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker-runner.mjs +31 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/pixel-worker.mjs +138 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +24 -20
- package/dist/vendored/skills/card-skill-trainer/scripts/lib/telemetry.mjs +46 -0
- package/dist/vendored/skills/card-skill-trainer/scripts/plan_next_round.mjs +4 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +279 -90
- package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +74 -2
- package/dist/vendored/skills/card-skill-trainer/scripts/sink_rules.mjs +127 -0
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +208 -380
- package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +1 -15
- package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +11 -20
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +233 -61
- package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +202 -4
- package/dist/vendored/skills/dsl-card-generator/references/iteration_protocol.md +87 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +42 -33
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +59 -11
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +44 -8
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +26 -6
- package/dist/vendored/skills/dsl-card-generator/references/scripts_reference.md +54 -0
- package/dist/vendored/skills/dsl-card-generator/references/semantic_adjudication.md +23 -0
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +6 -6
- package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +33 -4
- package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +13 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +5 -14
- package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +133 -2
- package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +226 -19
- package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +6 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +0 -35
- package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +92 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +277 -27
- package/dist/vendored/skills/dsl-card-generator/scripts/gate.mjs +147 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +12 -8
- package/dist/vendored/skills/dsl-card-generator/scripts/ir_merge.mjs +288 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/ir_restore.mjs +175 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +1015 -54
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/component_contract.mjs +315 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/geometry_audit.mjs +131 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +1 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout_rules.mjs +160 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +23 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +0 -37
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/python.mjs +99 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/adaptive-width.mjs +105 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/bindings.mjs +260 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/expr.mjs +52 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/image.mjs +96 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/registry.mjs +166 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/structure.mjs +127 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/rules/walk.mjs +61 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/scope_alias.mjs +125 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +17 -5
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +102 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +5727 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +4214 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +1110 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +322 -71
- package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +87 -43
- package/dist/vendored/skills/dsl-card-generator/scripts/readiness.mjs +634 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +5 -8
- package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +91 -43
- package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +7 -11
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +272 -46
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +43 -10499
- package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +188 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +23 -15
- package/dist/vendored/skills/shared/exit-codes.md +79 -0
- package/dist/vendored/skills/shared/spec-contract.md +118 -0
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +43 -20
- package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +4 -4
- package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +47 -9
- package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +14 -25
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +546 -91
- package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +51 -5
- package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +217 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +64 -34
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-Cm0USikc.js +0 -307
- package/dist/preview-editor/assets/index-cbTCHp8J.css +0 -1
- package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +0 -122
- package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +0 -153
|
@@ -14,7 +14,16 @@ import { devApiBase } from '../../tools/finmall-env.mjs'
|
|
|
14
14
|
* 的 ElementHandle,不存在时为 null)。
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* 截图要打开的「编辑器页面」基址。
|
|
19
|
+
*
|
|
20
|
+
* 惰性读取(不是模块级常量):preview 模式下编辑器由 cardbuddy 自己托管在同一端口,
|
|
21
|
+
* 启动期才知道端口号;模块级常量会在 import 时就把基址钉死在默认的 vite 端口上。
|
|
22
|
+
* 实测(2026-09-21 demo 建卡回合):`card_screenshot` 两次返回
|
|
23
|
+
* `page.goto: net::ERR_CONNECTION_REFUSED at http://127.0.0.1:5180` —— 服务跑在 8787、
|
|
24
|
+
* 截图却去敲 5180,模型拿到一条纯噪音(还以为是卡片的问题)。
|
|
25
|
+
*/
|
|
26
|
+
const baseUrl = () => process.env.CARD_SCREENSHOT_BASE || devApiBase()
|
|
18
27
|
const IDLE_CLOSE_MS = 5 * 60 * 1000
|
|
19
28
|
const MAX_QUEUED = 10
|
|
20
29
|
const NAV_TIMEOUT_MS = 30000
|
|
@@ -79,7 +88,7 @@ function serialize(task) {
|
|
|
79
88
|
*/
|
|
80
89
|
async function openCardScreen(context, id) {
|
|
81
90
|
const page = await context.newPage()
|
|
82
|
-
const target = `${
|
|
91
|
+
const target = `${baseUrl()}/?card=${encodeURIComponent(id)}&chrome=off`
|
|
83
92
|
await page.goto(target, { waitUntil: 'domcontentloaded', timeout: NAV_TIMEOUT_MS })
|
|
84
93
|
// 等卡片首帧挂载(App.vue 置 data-card-ready=1)。用 waitForFunction 精确判断属性出现,
|
|
85
94
|
// 而非等待「元素可见」(避免卡片有但属性未置位时误判)。
|
|
@@ -97,7 +106,7 @@ async function openCardScreen(context, id) {
|
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
export {
|
|
100
|
-
|
|
109
|
+
baseUrl,
|
|
101
110
|
IDLE_CLOSE_MS,
|
|
102
111
|
MAX_QUEUED,
|
|
103
112
|
NAV_TIMEOUT_MS,
|
|
@@ -303,6 +303,7 @@ export function createCardFilesMiddleware({ root, notify = null }) {
|
|
|
303
303
|
// 不生成封面 —— 空白卡在 /__card-image 直接 404,网格回退统一占位图。
|
|
304
304
|
if (pathname === '/__cards' && method === 'POST') {
|
|
305
305
|
let body = ''
|
|
306
|
+
req.setEncoding('utf8')
|
|
306
307
|
req.on('data', (c) => {
|
|
307
308
|
body += c
|
|
308
309
|
if (body.length > 64 * 1024) req.destroy()
|
|
@@ -414,6 +415,7 @@ export function createCardFilesMiddleware({ root, notify = null }) {
|
|
|
414
415
|
}
|
|
415
416
|
if (method === 'POST') {
|
|
416
417
|
let body = ''
|
|
418
|
+
req.setEncoding('utf8')
|
|
417
419
|
req.on('data', (c) => {
|
|
418
420
|
body += c
|
|
419
421
|
if (body.length > 16 * 1024 * 1024) req.destroy()
|
|
@@ -471,6 +473,7 @@ export function createCardFilesMiddleware({ root, notify = null }) {
|
|
|
471
473
|
// 重命名卡片(目录改名 + 描述写回):PATCH /__card/<oldId>,body { id?, desc? }
|
|
472
474
|
if (method === 'PATCH') {
|
|
473
475
|
let body = ''
|
|
476
|
+
req.setEncoding('utf8')
|
|
474
477
|
req.on('data', (c) => {
|
|
475
478
|
body += c
|
|
476
479
|
if (body.length > 64 * 1024) req.destroy()
|
|
@@ -117,6 +117,7 @@ export function createHistoryMiddleware({ root }) {
|
|
|
117
117
|
// POST:把「上一版本 schema」落盘为一张新快照
|
|
118
118
|
if (method === 'POST') {
|
|
119
119
|
let body = ''
|
|
120
|
+
req.setEncoding('utf8')
|
|
120
121
|
req.on('data', (c) => {
|
|
121
122
|
body += c
|
|
122
123
|
if (body.length > 32 * 1024 * 1024) req.destroy()
|
|
@@ -129,7 +129,11 @@ export function recordExport(dir, verified) {
|
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
131
|
* dev server 中间件:GET /__import-meta/:id 暴露导入来源元数据(供 UI「来源」tab / AI skill 读取)。
|
|
132
|
-
* 纯只读、no-store
|
|
132
|
+
* 纯只读、no-store。
|
|
133
|
+
*
|
|
134
|
+
* 无侧车(本地新建卡)用 **200 + imported:false** 表达,不用 404:
|
|
135
|
+
* 旧版回 404,于是每打开一张本地卡 DevTools 就多一条红色 404 —— 而「这张卡没从远端同步过」
|
|
136
|
+
* 是常态答案、不是故障。红色报错会误导用户,也会被图片/网络审计工具当成异常报出来。
|
|
133
137
|
*
|
|
134
138
|
* @param {{ root: string }} opts
|
|
135
139
|
* @returns {(req, res, next) => void}
|
|
@@ -145,11 +149,8 @@ export function createImportMetaMiddleware({ root }) {
|
|
|
145
149
|
const meta = readImportMeta(dir)
|
|
146
150
|
res.setHeader('Cache-Control', 'no-store')
|
|
147
151
|
res.setHeader('Content-Type', 'application/json')
|
|
148
|
-
if (!meta) {
|
|
149
|
-
res.statusCode = 404
|
|
150
|
-
return res.end(JSON.stringify({ error: 'import-meta not found', id, hint: '该卡尚未经同步远程导入,无导入来源元数据' }))
|
|
151
|
-
}
|
|
152
152
|
res.statusCode = 200
|
|
153
|
-
|
|
153
|
+
// imported 布尔让调用方一眼判断,不必靠状态码区分「本地新建卡」与「已同步卡」
|
|
154
|
+
return res.end(JSON.stringify(meta ? { id, imported: true, meta } : { id, imported: false, meta: null }))
|
|
154
155
|
}
|
|
155
156
|
}
|
|
@@ -25,6 +25,7 @@ export function createSaveMiddleware({ root }) {
|
|
|
25
25
|
const method = (req.method || 'GET').toUpperCase()
|
|
26
26
|
if (pathname !== '/__save' || method !== 'POST') return next()
|
|
27
27
|
let body = ''
|
|
28
|
+
req.setEncoding('utf8')
|
|
28
29
|
req.on('data', (c) => {
|
|
29
30
|
body += c
|
|
30
31
|
if (body.length > 32 * 1024 * 1024) req.destroy()
|
|
@@ -24,6 +24,7 @@ export function createSyncMiddleware({ root }) {
|
|
|
24
24
|
if ((req.method || 'GET').toUpperCase() !== 'POST') return next()
|
|
25
25
|
|
|
26
26
|
let body = ''
|
|
27
|
+
req.setEncoding('utf8')
|
|
27
28
|
req.on('data', (c) => {
|
|
28
29
|
body += c
|
|
29
30
|
if (body.length > 1 * 1024 * 1024) req.destroy()
|
|
@@ -119,6 +120,7 @@ export function createPreviewMiddleware({ root }) {
|
|
|
119
120
|
if ((req.method || 'GET').toUpperCase() !== 'POST') return next()
|
|
120
121
|
|
|
121
122
|
let body = ''
|
|
123
|
+
req.setEncoding('utf8')
|
|
122
124
|
req.on('data', (c) => {
|
|
123
125
|
body += c
|
|
124
126
|
if (body.length > 4 * 1024 * 1024) req.destroy()
|
|
@@ -195,6 +197,7 @@ export function createLoginCredentialsMiddleware({ root }) {
|
|
|
195
197
|
if ((req.method || 'GET').toUpperCase() !== 'POST') return next()
|
|
196
198
|
|
|
197
199
|
let body = ''
|
|
200
|
+
req.setEncoding('utf8')
|
|
198
201
|
req.on('data', (c) => {
|
|
199
202
|
body += c
|
|
200
203
|
if (body.length > 64 * 1024) req.destroy()
|
|
@@ -17,7 +17,8 @@ const VALIDATE_SCRIPT_REL = 'dsl-card-generator/scripts/validate_schema.py'
|
|
|
17
17
|
* - GET /__validate :全量校验,逐卡跑 Python 校验器,汇总返回
|
|
18
18
|
*
|
|
19
19
|
* 实现:子进程调用 `skills/dsl-card-generator/scripts/validate_schema.py --json`。
|
|
20
|
-
* Python 解释器解析顺序:`
|
|
20
|
+
* Python 解释器解析顺序:`DSL_CARD_PYTHON`(权威)→ `WORKBUDDY_PYTHON` → `DSL_PYTHON`
|
|
21
|
+
* → `PYTHON` → 探测 `python3` / `python` / `py`;都不存在时返回
|
|
21
22
|
* **503 {error:'python-not-found'}** 而非 500,让 Agent / CI 能区分「环境没装」和「卡片有问题」。
|
|
22
23
|
*
|
|
23
24
|
* @param {{ root: string }} opts
|
|
@@ -32,10 +33,20 @@ export function scriptPath(root) {
|
|
|
32
33
|
return resolveSkillPathOrProject(root, VALIDATE_SCRIPT_REL)
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* 解析可用的 python 解释器;找不到返回 null(导出供 MCP 复用)。
|
|
38
|
+
*
|
|
39
|
+
* 能力对齐 `skills/dsl-card-generator/scripts/lib/python.mjs`(权威实现):环境变量链
|
|
40
|
+
* 四个键 + 自动探测含 `py`。此前只认 `DSL_PYTHON` 且不含 `py` —— Windows 上 Python 从
|
|
41
|
+
* Store 安装时往往只有 `py` 启动器,会在这里被判成「环境没装 Python」并回 503,
|
|
42
|
+
* 而 skill 脚本侧其实是能跑的(同一台机器两种结论)。
|
|
43
|
+
*/
|
|
36
44
|
export function resolvePython() {
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
for (const key of ['DSL_CARD_PYTHON', 'WORKBUDDY_PYTHON', 'DSL_PYTHON', 'PYTHON']) {
|
|
46
|
+
const val = process.env[key]
|
|
47
|
+
if (val) return val
|
|
48
|
+
}
|
|
49
|
+
for (const cmd of ['python3', 'python', 'py']) {
|
|
39
50
|
try {
|
|
40
51
|
execFileSync(cmd, ['--version'], { stdio: 'ignore', timeout: 5000 })
|
|
41
52
|
return cmd
|
|
@@ -92,7 +103,7 @@ export function createValidateMiddleware({ root }) {
|
|
|
92
103
|
res.setHeader('Content-Type', 'application/json')
|
|
93
104
|
return res.end(JSON.stringify({
|
|
94
105
|
error: 'python-not-found',
|
|
95
|
-
message: '未找到 python3/python,请在 PATH 中安装 Python 或设置
|
|
106
|
+
message: '未找到 python3/python/py,请在 PATH 中安装 Python 或设置 DSL_CARD_PYTHON 环境变量',
|
|
96
107
|
}))
|
|
97
108
|
}
|
|
98
109
|
|
|
@@ -73,7 +73,10 @@ export function resolveEditorSpec(file, env = process.env, platform = process.pl
|
|
|
73
73
|
|
|
74
74
|
/** VSCode 未安装 / CLI 缺失时的协议兜底(安装器默认注册 vscode:// 处理器) */
|
|
75
75
|
export function resolveProtocolSpec(file, env = process.env, platform = process.platform) {
|
|
76
|
-
|
|
76
|
+
// 反斜杠一律转正斜杠:协议 URL 只认 `/`(`C:\t\a.js` 直接拼进 URL 会被 VSCode 当非法路径)。
|
|
77
|
+
// 按**目标平台**的形态转,而不是宿主 `path.sep` —— 后者在 macOS / Linux 上是 `/`,
|
|
78
|
+
// win32 分支里那条 `C:\…` 路径永远不会被转,测试与「跨平台拉起」都会静默失效。
|
|
79
|
+
const url = 'vscode://file/' + String(file).replace(/\\/g, '/')
|
|
77
80
|
if (platform === 'darwin') return { cmd: 'open', args: [url], shell: false }
|
|
78
81
|
if (platform === 'win32') {
|
|
79
82
|
return { command: `start "" "${url}"`, shell: true }
|
|
@@ -291,6 +294,7 @@ export function createCodeBridgeMiddleware({ notify = null, watchFile = null } =
|
|
|
291
294
|
const readBody = (req, limit = MAX_BODY) =>
|
|
292
295
|
new Promise((resolve, reject) => {
|
|
293
296
|
let body = ''
|
|
297
|
+
req.setEncoding('utf8')
|
|
294
298
|
req.on('data', (c) => {
|
|
295
299
|
body += c
|
|
296
300
|
if (body.length > limit) {
|
|
@@ -10,6 +10,12 @@ import path from 'node:path'
|
|
|
10
10
|
* - Vite(5180):编辑器预览同源访问 /slices/*;
|
|
11
11
|
* - cardbuddy(8787):本地开发服务若直接托管预览页,也能取到切图。
|
|
12
12
|
* 仅服务本地生成产物,不做任何远端回源(远端切图走 assetProxy 的 /minio 等前缀)。
|
|
13
|
+
*
|
|
14
|
+
* **未命中一律 404,禁止 next() 落 SPA 兜底**(2026-09-21 事故):
|
|
15
|
+
* 此前未命中走 `next()`,SPA 兜底对任意路径都返回 `200 + text/html`,于是「引用路径写错 /
|
|
16
|
+
* 切图名含 `#` 被浏览器截断」表现为 —— 图片空白、网络面板满屏 200、控制台零报错,
|
|
17
|
+
* 30 分钟无人定位。返回 404 后同一故障会立刻在 devtools 里显示为红色 404,
|
|
18
|
+
* 这是「坏图必须吵」的最小代价。
|
|
13
19
|
*/
|
|
14
20
|
|
|
15
21
|
const MIME = {
|
|
@@ -27,15 +33,29 @@ const MIME = {
|
|
|
27
33
|
*/
|
|
28
34
|
export function createSliceStaticMiddleware({ root }) {
|
|
29
35
|
const slicesRoot = path.join(root, 'src', 'slices')
|
|
36
|
+
const notFound = (res, reason) => {
|
|
37
|
+
res.statusCode = 404
|
|
38
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
39
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
40
|
+
res.end(`slice not found: ${reason}`)
|
|
41
|
+
}
|
|
30
42
|
return (req, res, next) => {
|
|
31
43
|
const url = (req.url || '').split('?')[0]
|
|
32
44
|
if (!url.startsWith('/slices/')) return next()
|
|
33
|
-
const
|
|
45
|
+
const raw = url.slice('/slices/'.length)
|
|
46
|
+
let rel
|
|
47
|
+
try {
|
|
48
|
+
rel = decodeURIComponent(raw)
|
|
49
|
+
} catch {
|
|
50
|
+
return notFound(res, `${raw}(percent 编码非法,多半是文件名里带了 % 之类的保留字符)`)
|
|
51
|
+
}
|
|
34
52
|
// 防目录穿越:禁止 .. 且解析后必须仍在 src/slices 内
|
|
35
|
-
if (rel.includes('..')) return
|
|
53
|
+
if (!rel || rel.includes('..')) return notFound(res, `${raw}(非法路径)`)
|
|
36
54
|
const file = path.join(slicesRoot, rel)
|
|
37
|
-
if (!file.startsWith(slicesRoot + path.sep) && file !== slicesRoot) return
|
|
38
|
-
if (!fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
55
|
+
if (!file.startsWith(slicesRoot + path.sep) && file !== slicesRoot) return notFound(res, `${raw}(越出切图目录)`)
|
|
56
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
57
|
+
return notFound(res, `${raw}(文件不存在于 src/slices;若 schema 里引用的是这个路径,检查文件名是否含 # ? % & + 或空格)`)
|
|
58
|
+
}
|
|
39
59
|
const ext = path.extname(file).toLowerCase()
|
|
40
60
|
res.setHeader('Content-Type', MIME[ext] || 'application/octet-stream')
|
|
41
61
|
res.setHeader('Cache-Control', 'public, max-age=0')
|
|
@@ -218,6 +218,8 @@ export class TurnJournal {
|
|
|
218
218
|
id: `srv-${this.turnId}`,
|
|
219
219
|
role: 'assistant',
|
|
220
220
|
content: '',
|
|
221
|
+
// 过程性正文的切点:最后一次工具调用之前的正文字数(前端据此折叠,见 runtime/ai-narration.js)
|
|
222
|
+
narrationChars: 0,
|
|
221
223
|
reasoning: '',
|
|
222
224
|
reasoningMs: 0,
|
|
223
225
|
toolCalls: [],
|
|
@@ -252,6 +254,9 @@ export class TurnJournal {
|
|
|
252
254
|
msg.content += l.text || ''
|
|
253
255
|
break
|
|
254
256
|
case 'tool-call-start': {
|
|
257
|
+
// 工具调用之前累积的正文 = 过程性正文(模型把像素比对 / 逐行对齐这类推理写进正文时,
|
|
258
|
+
// 这段能占几万字)。取最后一次工具调用的切点:它之后的才是给用户的收尾结论。
|
|
259
|
+
if (msg.content) msg.narrationChars = msg.content.length
|
|
255
260
|
const row = rowOf(l.toolCallId, l.toolName)
|
|
256
261
|
row.toolName = l.toolName || row.toolName
|
|
257
262
|
row.startedAt = row.startedAt || l.ts
|
|
@@ -259,6 +264,7 @@ export class TurnJournal {
|
|
|
259
264
|
break
|
|
260
265
|
}
|
|
261
266
|
case 'tool-call': {
|
|
267
|
+
if (msg.content) msg.narrationChars = msg.content.length
|
|
262
268
|
const row = rowOf(l.toolCallId, l.toolName)
|
|
263
269
|
row.toolName = l.toolName || row.toolName
|
|
264
270
|
row.args = l.args || {}
|