@xiaobuyu/nodesign 0.0.1 → 0.0.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/README.md +156 -106
- package/bin/nodesign.js +0 -0
- package/package.json +1 -1
- package/server/api/README.md +45 -45
- package/server/api/_guard.js +75 -75
- package/server/api/admin.js +187 -187
- package/server/api/assets/docx-page.js +103 -103
- package/server/api/assets/helpers.js +22 -22
- package/server/api/assets/notes.js +144 -144
- package/server/api/assets.js +896 -896
- package/server/api/board.js +57 -57
- package/server/api/browse.js +138 -138
- package/server/api/canvas.js +444 -444
- package/server/api/chatai.js +324 -324
- package/server/api/exports/build-standalone.js +980 -980
- package/server/api/exports/cards.js +118 -118
- package/server/api/exports/docx-pdf.js +46 -46
- package/server/api/exports/export-page.js +142 -142
- package/server/api/exports/handoff.js +147 -147
- package/server/api/exports.js +690 -690
- package/server/api/instruction.js +75 -75
- package/server/api/me.js +116 -116
- package/server/api/memory.js +185 -185
- package/server/api/pending-changes.js +366 -366
- package/server/api/plugins.js +164 -164
- package/server/api/projects.js +220 -220
- package/server/api/publish.js +64 -64
- package/server/api/recent.js +73 -73
- package/server/api/sessions.js +589 -589
- package/server/api/skills.js +77 -77
- package/server/api/standalone-fit.js +168 -168
- package/server/api/turn-compose.js +180 -180
- package/server/api/turn-inflight.js +44 -44
- package/server/auth/README.md +17 -17
- package/server/auth/middleware.js +138 -138
- package/server/auth/origin-guard.js +71 -71
- package/server/auth/session.js +108 -108
- package/server/auth/tier.js +113 -113
- package/server/auth/users-store.js +348 -348
- package/server/edit/README.md +29 -29
- package/server/engine/README.md +113 -113
- package/server/engine/agent/agent-shared.js +550 -550
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -4
- package/server/engine/agent/auto-mode-rules.js +78 -78
- package/server/engine/agent/context.js +418 -418
- package/server/engine/agent/events.js +464 -464
- package/server/engine/agent/hooks/canvas-validate.js +199 -199
- package/server/engine/agent/hooks/failure.js +120 -120
- package/server/engine/agent/hooks/file-events.js +75 -75
- package/server/engine/agent/hooks/lifecycle.js +233 -233
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -80
- package/server/engine/agent/hooks/post-guidance.js +206 -206
- package/server/engine/agent/hooks/post-subagent-report.js +104 -104
- package/server/engine/agent/hooks/post-trim.js +58 -58
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -34
- package/server/engine/agent/hooks/pre-defaults.js +84 -84
- package/server/engine/agent/hooks/pre-injectors.js +277 -277
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -78
- package/server/engine/agent/hooks/pre-starter-files.js +61 -61
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -78
- package/server/engine/agent/hooks/site-validate.js +94 -94
- package/server/engine/agent/hooks/tool-prompts.js +31 -31
- package/server/engine/agent/hooks/turn-state-memory.js +56 -56
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -215
- package/server/engine/agent/hooks.js +354 -354
- package/server/engine/agent/init-contract.js +69 -69
- package/server/engine/agent/isolation.js +159 -159
- package/server/engine/agent/model-table.js +292 -292
- package/server/engine/agent/plugin-loader.js +266 -266
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -336
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -71
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -98
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -583
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -483
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -100
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -270
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -70
- package/server/engine/agent/prompts/tools/site-reference.md +106 -106
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -125
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -48
- package/server/engine/agent/session-loop.js +1024 -1024
- package/server/engine/agent/session-model.js +164 -164
- package/server/engine/agent/skill.js +204 -204
- package/server/engine/agent/task-events.js +78 -78
- package/server/engine/agents/ds-extractor.md +132 -132
- package/server/engine/agents/explorer.md +190 -190
- package/server/engine/agents/index.js +230 -230
- package/server/engine/agents/schemas/design-system.json +156 -156
- package/server/engine/agents/schemas/tweak-schema.json +113 -113
- package/server/engine/agents/tweak-proposer.md +127 -127
- package/server/engine/agents/vision-checker.md +254 -254
- package/server/engine/browse/capture.js +422 -422
- package/server/engine/browse/card.js +137 -137
- package/server/engine/browse/handover.js +59 -59
- package/server/engine/browse/page-digest.js +119 -119
- package/server/engine/browse/refs.js +196 -196
- package/server/engine/browse/registry.js +307 -307
- package/server/engine/browse/screencast.js +170 -170
- package/server/engine/browse/state.js +121 -121
- package/server/engine/chatai/chat-log.js +79 -79
- package/server/engine/chatai/index.js +193 -193
- package/server/engine/chatai/openai-compat.js +152 -152
- package/server/engine/chatai/orchestrate.js +309 -309
- package/server/engine/chatai/perform.js +40 -40
- package/server/engine/chatai/summarize.js +79 -79
- package/server/engine/mcp/capability-gate.js +52 -52
- package/server/engine/mcp/index.js +339 -339
- package/server/engine/mcp/param-sanitizer.js +142 -142
- package/server/engine/mcp/tools/arrange-on-board.js +124 -124
- package/server/engine/mcp/tools/artifact-session.js +278 -278
- package/server/engine/mcp/tools/browse-computer.js +421 -421
- package/server/engine/mcp/tools/browse-find-batch.js +183 -183
- package/server/engine/mcp/tools/browse-screenshot.js +157 -157
- package/server/engine/mcp/tools/browse.js +480 -480
- package/server/engine/mcp/tools/build-docx.js +101 -101
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -99
- package/server/engine/mcp/tools/create-on-board.js +155 -155
- package/server/engine/mcp/tools/crystallize-skill.js +168 -168
- package/server/engine/mcp/tools/explain-style.js +237 -237
- package/server/engine/mcp/tools/export-handoff.js +133 -133
- package/server/engine/mcp/tools/expose-tweaks.js +149 -149
- package/server/engine/mcp/tools/generate-image.js +842 -842
- package/server/engine/mcp/tools/get-computed-styles.js +164 -164
- package/server/engine/mcp/tools/get-pending-changes.js +205 -205
- package/server/engine/mcp/tools/h3box-ssh.js +84 -84
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -82
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -554
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -112
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -215
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -103
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -80
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -280
- package/server/engine/mcp/tools/highlight.js +68 -68
- package/server/engine/mcp/tools/list-pages.js +222 -222
- package/server/engine/mcp/tools/lookup-tags.js +59 -59
- package/server/engine/mcp/tools/navigate-to-page.js +60 -60
- package/server/engine/mcp/tools/organize-board.js +64 -64
- package/server/engine/mcp/tools/paint-still.js +434 -434
- package/server/engine/mcp/tools/pin-to-board.js +130 -130
- package/server/engine/mcp/tools/preview-deck.js +130 -130
- package/server/engine/mcp/tools/profile-scroll.js +305 -305
- package/server/engine/mcp/tools/publish-site.js +102 -102
- package/server/engine/mcp/tools/query-elements.js +195 -195
- package/server/engine/mcp/tools/read-board.js +103 -103
- package/server/engine/mcp/tools/read-document.js +93 -93
- package/server/engine/mcp/tools/read-page.js +264 -264
- package/server/engine/mcp/tools/read-tavern-json.js +143 -143
- package/server/engine/mcp/tools/record-decision.js +140 -140
- package/server/engine/mcp/tools/relate-on-board.js +110 -110
- package/server/engine/mcp/tools/report-issue.js +115 -115
- package/server/engine/mcp/tools/roll-film.js +266 -266
- package/server/engine/mcp/tools/screenshot-docx.js +138 -138
- package/server/engine/mcp/tools/screenshot-url.js +195 -195
- package/server/engine/mcp/tools/screenshot.js +583 -583
- package/server/engine/mcp/tools/tier-gate.js +96 -96
- package/server/engine/mcp/tools/trace-motion.js +215 -215
- package/server/engine/mcp/tools/web-search.js +548 -548
- package/server/engine/motion/inventory.js +349 -349
- package/server/engine/perception/session.js +235 -235
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -5
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -192
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -667
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -22
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -25
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -21
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -23
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -21
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -162
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -407
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -80
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -228
- package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -78
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -268
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -323
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -134
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -61
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -163
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -86
- package/server/engine/runs/active-runs.js +698 -698
- package/server/engine/runs/live-turn.js +252 -252
- package/server/engine/runs/store.js +364 -364
- package/server/engine/runs/turn-relay.js +217 -217
- package/server/engine/runtime/workspace.js +164 -164
- package/server/lib/artifact-file-path.js +80 -80
- package/server/lib/artifact-target.js +284 -284
- package/server/lib/asset-refs.js +239 -239
- package/server/lib/async-queue.js +104 -104
- package/server/lib/auto-relations.js +130 -130
- package/server/lib/binding-types.js +60 -60
- package/server/lib/board-kind-sizes.js +62 -62
- package/server/lib/board-relations.js +133 -133
- package/server/lib/browse-proxy.js +180 -180
- package/server/lib/canvas-id.js +36 -36
- package/server/lib/cover.js +227 -227
- package/server/lib/danbooru-tags.js +291 -291
- package/server/lib/doc-extract.js +156 -156
- package/server/lib/docx/build-from-source.js +260 -260
- package/server/lib/docx/build.js +490 -490
- package/server/lib/docx/dump-styles.js +287 -287
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -164
- package/server/lib/docx/merge-runs.js +129 -129
- package/server/lib/docx/numbering.js +218 -218
- package/server/lib/docx/order.js +152 -152
- package/server/lib/docx/rawzip.js +172 -172
- package/server/lib/docx/render.js +92 -92
- package/server/lib/docx/text-lint.js +225 -225
- package/server/lib/docx/tokens.js +335 -335
- package/server/lib/docx/units.js +29 -29
- package/server/lib/docx/xml.js +291 -291
- package/server/lib/docx-pages.js +187 -187
- package/server/lib/export-collect.js +289 -289
- package/server/lib/export-package.js +210 -210
- package/server/lib/html-srcset.js +145 -145
- package/server/lib/image-variant.js +460 -460
- package/server/lib/ingress/forward-openai-chat.js +305 -305
- package/server/lib/ingress/openai-chat.js +491 -491
- package/server/lib/ingress/session-notice.js +66 -66
- package/server/lib/ingress/session-routes.js +68 -68
- package/server/lib/ingress/slot-probe.js +130 -130
- package/server/lib/ingress/upstream-billing.js +52 -52
- package/server/lib/ingress/upstream-fail-streak.js +72 -72
- package/server/lib/ingress/upstream-truncation.js +48 -48
- package/server/lib/issues-store.js +182 -182
- package/server/lib/kinds/deck.js +72 -72
- package/server/lib/kinds/docx.js +283 -283
- package/server/lib/kinds/file-kinds.js +113 -113
- package/server/lib/kinds/index.js +202 -202
- package/server/lib/kinds/site.js +161 -161
- package/server/lib/model-ingress.js +543 -543
- package/server/lib/moderation.js +255 -255
- package/server/lib/notice-store.js +103 -103
- package/server/lib/plugin-install.js +104 -104
- package/server/lib/plugin-validator.js +721 -721
- package/server/lib/publish-store.js +115 -115
- package/server/lib/quick-summary.js +52 -52
- package/server/lib/quota.js +270 -270
- package/server/lib/rate-window.js +29 -29
- package/server/lib/reference-assets.js +92 -92
- package/server/lib/region-shot.js +135 -135
- package/server/lib/safe-path.js +73 -73
- package/server/lib/sdk-session.js +33 -33
- package/server/lib/showcase-store.js +92 -92
- package/server/lib/site-publish.js +465 -465
- package/server/lib/ssrf-guard.js +432 -432
- package/server/lib/task-scan.js +158 -158
- package/server/lib/tavern-json.js +154 -154
- package/server/lib/video-variant.js +237 -237
- package/server/lib/workspace-path.js +33 -33
- package/server/ops/fix-sdk-musl.mjs +40 -40
- package/server/ops/install-macos-fonts.sh +114 -114
- package/server/ops/macos-fonts.conf +336 -336
- package/server/ops/sandbox-shim/bwrap +57 -57
- package/server/projects/assets-summary.js +119 -119
- package/server/projects/auto-name.js +53 -53
- package/server/projects/board-store.js +640 -640
- package/server/projects/move-entry.js +103 -103
- package/server/projects/store.js +270 -270
- package/server/projects/ui-config.js +54 -54
- package/server/projects/workspace-templates.js +53 -53
- package/server/projects/workspace.js +1025 -1025
- package/server/runtime/local-config.js +177 -177
- package/server/runtime/profile.js +72 -72
- package/server/services/rembg-service.py +334 -334
- package/server/shared/README.md +25 -25
- package/server/shared/deck.js +54 -54
- package/server/shared/time.js +49 -49
- package/server/style-pipeline/README.md +53 -53
- package/server/ws/broker.js +36 -36
- package/server/ws/browse-channel.js +177 -177
- package/server/ws/index.js +386 -386
- package/web/README.md +89 -89
- package/web/dist/assets/BrowserWindow-BYCgizHw.js +11 -0
- package/web/dist/assets/{DeckWindow-4G0xX7P3.js → DeckWindow-pNydEM4L.js} +1 -1
- package/web/dist/assets/{DocxWindow-BXQsoPkh.js → DocxWindow-PPgzh3Gq.js} +1 -1
- package/web/dist/assets/{SiteWindow-D6eS-2gM.js → SiteWindow-DM0LQPCO.js} +1 -1
- package/web/dist/assets/{index-Cbl3ATvp.js → index-BHTBJlpY.js} +32 -32
- package/web/dist/assets/{pending-edit-apply-DTjo2geW.js → pending-edit-apply-C0I13CbD.js} +1 -1
- package/web/dist/assets/{rotate-cw-LMT3x1Zj.js → rotate-cw-B1UQ8Hld.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CBoAkfhi.js → square-dashed-mouse-pointer-8lrgqmfv.js} +1 -1
- package/web/dist/index.html +17 -17
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +0 -11
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# bwrap 垫片 —— 绕开 apply-seccomp 的 unshare 竞态(2026-08-15)
|
|
3
|
-
#
|
|
4
|
-
# 📮 上游 issue:https://github.com/anthropics/claude-code/issues/86928
|
|
5
|
-
# (含 strace 时序、内核源码引用、49 行 C 最小复现、三条修法建议)
|
|
6
|
-
# ⏳ **这是临时设施**:上游修掉之后把整个目录连同 isolation.js 里的
|
|
7
|
-
# sandboxShimEnv() 一起摘掉,别让它变成永久的一层。
|
|
8
|
-
#
|
|
9
|
-
# 病:SDK 沙盒每条命令都以 `ARGV0=apply-seccomp /proc/self/fd/3 <命令>` 起手 ——
|
|
10
|
-
# 那是那个 306MB bun 二进制**自我重执行**成 seccomp helper。bun 启动时 mimalloc
|
|
11
|
-
# 会先起一根 `mi-scavenger` 线程,helper 随即把它关掉并 join;但内核在
|
|
12
|
-
# `mm_release()` 里唤醒 joiner 的时机**早于** `release_task` 把死线程从
|
|
13
|
-
# thread group 摘除,于是紧接着那句 `unshare(CLONE_NEWUSER)` 撞上
|
|
14
|
-
# `!thread_group_empty` → EINVAL。(kernel/fork.c:CLONE_NEWUSER 会被隐式加上
|
|
15
|
-
# CLONE_THREAD,而 check_unshare_flags 对 CLONE_THREAD 要求 thread group 为空。)
|
|
16
|
-
#
|
|
17
|
-
# 所以它是**内核级竞态**,不是配置问题:40 行 C 能脱离 claude 复现,1 vCPU 空载
|
|
18
|
-
# 200/200 必炸,join 后加 50µs 睡眠就 0/200。机器越闲越容易炸(joiner 抢占了
|
|
19
|
-
# 垂死线程),多核几乎见不到 —— 这是这台单核机特有的。
|
|
20
|
-
#
|
|
21
|
-
# 治:把那句 racy 的 syscall 从执行路径上拿掉。`unshare -U --map-current-user
|
|
22
|
-
# --keep-caps` 由 util-linux **单线程**先把 user namespace 建好并把能力放进
|
|
23
|
-
# ambient set,helper 进去后第一步 `unshare(NEWNS|NEWPID)` 直接成功,**根本不会
|
|
24
|
-
# 走到那句 CLONE_NEWUSER**。helper 在 exec 载荷之前自带 PR_CAP_AMBIENT_CLEAR_ALL,
|
|
25
|
-
# 能力不外泄。
|
|
26
|
-
#
|
|
27
|
-
# 安全不变量(每条都在真会话里验过,改这个文件前重验):
|
|
28
|
-
# Seccomp: 2(filter 仍在)· AF_UNIX socket() 仍被拒(pm2 rpc.sock 逃逸路照旧关着)
|
|
29
|
-
# · uid 仍是 1000 · userns 嵌套层数不变 · ambient caps 在载荷 exec 前清空
|
|
30
|
-
#
|
|
31
|
-
# ⚠️ 这是垫在 SDK 内部实现上的补丁,**pattern 变了就自动失效**(原样透传 = 退回
|
|
32
|
-
# 现状的偶发,不会退成"没有沙盒")。升 SDK 之后查 $NODESIGN_SHIM_LOG 里
|
|
33
|
-
# rewrote=1 的计数还在不在涨。
|
|
34
|
-
#
|
|
35
|
-
# 上游修法(issue 里提的三条):对 EINVAL 做有界重试 + sched_yield(最简单,
|
|
36
|
-
# 实测 50µs 退避就闭合窗口)/改成轮询 /proc/self/status 的 Threads: 到 1
|
|
37
|
-
# 再 unshare/让 helper 在运行时起线程之前就把 namespace 建好。
|
|
38
|
-
|
|
39
|
-
PAT='ARGV0=apply-seccomp /proc/self/fd/3 '
|
|
40
|
-
REP='/usr/bin/unshare -U --map-current-user --keep-caps /usr/bin/env ARGV0=apply-seccomp /proc/self/fd/3 '
|
|
41
|
-
REAL=${NODESIGN_REAL_BWRAP:-/usr/bin/bwrap}
|
|
42
|
-
|
|
43
|
-
# 兜底:unshare 不在(非 util-linux 系统)→ 原样透传,退回现状
|
|
44
|
-
[ -x /usr/bin/unshare ] || exec "$REAL" "$@"
|
|
45
|
-
|
|
46
|
-
args=()
|
|
47
|
-
hit=0
|
|
48
|
-
for a in "$@"; do
|
|
49
|
-
case "$a" in
|
|
50
|
-
*"$PAT"*) hit=1; a=${a//"$PAT"/"$REP"} ;;
|
|
51
|
-
esac
|
|
52
|
-
args+=("$a")
|
|
53
|
-
done
|
|
54
|
-
|
|
55
|
-
[ -n "$NODESIGN_SHIM_LOG" ] && printf '%s rewrote=%d nargs=%d\n' "$(date +%s)" "$hit" "$#" >>"$NODESIGN_SHIM_LOG" 2>/dev/null
|
|
56
|
-
|
|
57
|
-
exec "$REAL" "${args[@]}"
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# bwrap 垫片 —— 绕开 apply-seccomp 的 unshare 竞态(2026-08-15)
|
|
3
|
+
#
|
|
4
|
+
# 📮 上游 issue:https://github.com/anthropics/claude-code/issues/86928
|
|
5
|
+
# (含 strace 时序、内核源码引用、49 行 C 最小复现、三条修法建议)
|
|
6
|
+
# ⏳ **这是临时设施**:上游修掉之后把整个目录连同 isolation.js 里的
|
|
7
|
+
# sandboxShimEnv() 一起摘掉,别让它变成永久的一层。
|
|
8
|
+
#
|
|
9
|
+
# 病:SDK 沙盒每条命令都以 `ARGV0=apply-seccomp /proc/self/fd/3 <命令>` 起手 ——
|
|
10
|
+
# 那是那个 306MB bun 二进制**自我重执行**成 seccomp helper。bun 启动时 mimalloc
|
|
11
|
+
# 会先起一根 `mi-scavenger` 线程,helper 随即把它关掉并 join;但内核在
|
|
12
|
+
# `mm_release()` 里唤醒 joiner 的时机**早于** `release_task` 把死线程从
|
|
13
|
+
# thread group 摘除,于是紧接着那句 `unshare(CLONE_NEWUSER)` 撞上
|
|
14
|
+
# `!thread_group_empty` → EINVAL。(kernel/fork.c:CLONE_NEWUSER 会被隐式加上
|
|
15
|
+
# CLONE_THREAD,而 check_unshare_flags 对 CLONE_THREAD 要求 thread group 为空。)
|
|
16
|
+
#
|
|
17
|
+
# 所以它是**内核级竞态**,不是配置问题:40 行 C 能脱离 claude 复现,1 vCPU 空载
|
|
18
|
+
# 200/200 必炸,join 后加 50µs 睡眠就 0/200。机器越闲越容易炸(joiner 抢占了
|
|
19
|
+
# 垂死线程),多核几乎见不到 —— 这是这台单核机特有的。
|
|
20
|
+
#
|
|
21
|
+
# 治:把那句 racy 的 syscall 从执行路径上拿掉。`unshare -U --map-current-user
|
|
22
|
+
# --keep-caps` 由 util-linux **单线程**先把 user namespace 建好并把能力放进
|
|
23
|
+
# ambient set,helper 进去后第一步 `unshare(NEWNS|NEWPID)` 直接成功,**根本不会
|
|
24
|
+
# 走到那句 CLONE_NEWUSER**。helper 在 exec 载荷之前自带 PR_CAP_AMBIENT_CLEAR_ALL,
|
|
25
|
+
# 能力不外泄。
|
|
26
|
+
#
|
|
27
|
+
# 安全不变量(每条都在真会话里验过,改这个文件前重验):
|
|
28
|
+
# Seccomp: 2(filter 仍在)· AF_UNIX socket() 仍被拒(pm2 rpc.sock 逃逸路照旧关着)
|
|
29
|
+
# · uid 仍是 1000 · userns 嵌套层数不变 · ambient caps 在载荷 exec 前清空
|
|
30
|
+
#
|
|
31
|
+
# ⚠️ 这是垫在 SDK 内部实现上的补丁,**pattern 变了就自动失效**(原样透传 = 退回
|
|
32
|
+
# 现状的偶发,不会退成"没有沙盒")。升 SDK 之后查 $NODESIGN_SHIM_LOG 里
|
|
33
|
+
# rewrote=1 的计数还在不在涨。
|
|
34
|
+
#
|
|
35
|
+
# 上游修法(issue 里提的三条):对 EINVAL 做有界重试 + sched_yield(最简单,
|
|
36
|
+
# 实测 50µs 退避就闭合窗口)/改成轮询 /proc/self/status 的 Threads: 到 1
|
|
37
|
+
# 再 unshare/让 helper 在运行时起线程之前就把 namespace 建好。
|
|
38
|
+
|
|
39
|
+
PAT='ARGV0=apply-seccomp /proc/self/fd/3 '
|
|
40
|
+
REP='/usr/bin/unshare -U --map-current-user --keep-caps /usr/bin/env ARGV0=apply-seccomp /proc/self/fd/3 '
|
|
41
|
+
REAL=${NODESIGN_REAL_BWRAP:-/usr/bin/bwrap}
|
|
42
|
+
|
|
43
|
+
# 兜底:unshare 不在(非 util-linux 系统)→ 原样透传,退回现状
|
|
44
|
+
[ -x /usr/bin/unshare ] || exec "$REAL" "$@"
|
|
45
|
+
|
|
46
|
+
args=()
|
|
47
|
+
hit=0
|
|
48
|
+
for a in "$@"; do
|
|
49
|
+
case "$a" in
|
|
50
|
+
*"$PAT"*) hit=1; a=${a//"$PAT"/"$REP"} ;;
|
|
51
|
+
esac
|
|
52
|
+
args+=("$a")
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
[ -n "$NODESIGN_SHIM_LOG" ] && printf '%s rewrote=%d nargs=%d\n' "$(date +%s)" "$hit" "$#" >>"$NODESIGN_SHIM_LOG" 2>/dev/null
|
|
56
|
+
|
|
57
|
+
exec "$REAL" "${args[@]}"
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* server/projects/assets-summary.js — 工作区素材摘要(2026-08-18 从 workspace.js 拆出)
|
|
3
|
-
*
|
|
4
|
-
* 这条摘要进每个会话的 system 提示:告诉 agent 工作区里已经有什么素材。
|
|
5
|
-
* 拆出来的理由是它跟"工作区生命周期"没关系 —— 它是个格式化器。
|
|
6
|
-
*
|
|
7
|
-
* ⚠️ 存在的意义在于:**跨会话可见的东西如果没人提,等于不存在**。
|
|
8
|
-
* `assets/` 不随 sessionId 分桶,下个会话 Read 得到;但 agent 不会去猜有什么,
|
|
9
|
-
* 它会重新搜一遍、重新逛一遍那个站。
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import path from 'node:path';
|
|
13
|
-
import fs from 'node:fs/promises';
|
|
14
|
-
|
|
15
|
-
const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg']);
|
|
16
|
-
const TEXT_DOC_EXT = new Set(['.md', '.txt', '.json']);
|
|
17
|
-
const BINARY_DOC_EXT = new Set(['.pdf', '.pptx', '.ppt', '.docx', '.doc', '.xlsx', '.xls']);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @param {string} sessionRoot - sessions/<sid>/ 绝对路径
|
|
21
|
-
* @returns {Promise<{ count: number, summary: string, hasBinaryDocs: boolean }>}
|
|
22
|
-
* count=0 时 summary 为空字符串,调用方据此判断是否注入提示。
|
|
23
|
-
* hasBinaryDocs=true 时调用方追加 python 处理提醒(Read 拿不到这些的内容)。
|
|
24
|
-
*/
|
|
25
|
-
export async function readAssetsSummary(sessionRoot) {
|
|
26
|
-
try {
|
|
27
|
-
const assetsLink = path.join(sessionRoot, 'assets');
|
|
28
|
-
const stat = await fs.stat(assetsLink).catch(() => null);
|
|
29
|
-
if (!stat) return { count: 0, summary: '', hasBinaryDocs: false };
|
|
30
|
-
|
|
31
|
-
const entries = await fs.readdir(assetsLink, { withFileTypes: true }).catch(() => []);
|
|
32
|
-
const files = entries.filter((e) => !e.name.startsWith('.') && (e.isFile() || e.isSymbolicLink()));
|
|
33
|
-
// ⚠️ 别在这儿提前 return:顶层一个文件都没有、但 assets/references/ 下有一堆
|
|
34
|
-
// 采集素材是**常见情形**(agent 逛过站但用户没上传过东西)。提前 return 会让
|
|
35
|
-
// 那些素材对 agent 彻底隐形。
|
|
36
|
-
|
|
37
|
-
const images = [];
|
|
38
|
-
const textDocs = [];
|
|
39
|
-
const binaryDocs = [];
|
|
40
|
-
const others = [];
|
|
41
|
-
for (const f of files) {
|
|
42
|
-
const ext = path.extname(f.name).toLowerCase();
|
|
43
|
-
if (IMAGE_EXT.has(ext)) images.push(f.name);
|
|
44
|
-
else if (TEXT_DOC_EXT.has(ext)) textDocs.push(f.name);
|
|
45
|
-
else if (BINARY_DOC_EXT.has(ext)) binaryDocs.push(f.name);
|
|
46
|
-
else others.push(f.name);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 摘要:种类 + 头几个文件名(避免太长)
|
|
50
|
-
const parts = [];
|
|
51
|
-
if (images.length > 0) {
|
|
52
|
-
const sample = images.slice(0, 3).join('、');
|
|
53
|
-
parts.push(`${images.length} 张图(${sample}${images.length > 3 ? ` 等` : ''})`);
|
|
54
|
-
}
|
|
55
|
-
if (textDocs.length > 0) {
|
|
56
|
-
const sample = textDocs.slice(0, 3).join('、');
|
|
57
|
-
parts.push(`${textDocs.length} 个文本文档(${sample}${textDocs.length > 3 ? ` 等` : ''})`);
|
|
58
|
-
}
|
|
59
|
-
if (binaryDocs.length > 0) {
|
|
60
|
-
const sample = binaryDocs.slice(0, 3).join('、');
|
|
61
|
-
parts.push(`${binaryDocs.length} 个 PDF/Office 文档(${sample}${binaryDocs.length > 3 ? ` 等` : ''})`);
|
|
62
|
-
}
|
|
63
|
-
if (others.length > 0) {
|
|
64
|
-
parts.push(`${others.length} 个其他文件`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 完整文件清单(按路径列)—— 让 agent 不用再 Glob/LS 探。
|
|
68
|
-
// (08-07 扁平化后 assets/ 是真目录,Glob 也能用;08-21 前这里还写着"symlink 别用 Glob",删了。)
|
|
69
|
-
const allNames = [...images, ...textDocs, ...binaryDocs, ...others];
|
|
70
|
-
|
|
71
|
-
// 参考素材(2026-08-18):`assets/references/**` 也要报,否则**没有任何机制
|
|
72
|
-
// 告诉 agent 它们存在** —— 上面这遍只列顶层文件,而参考图和 browser_capture
|
|
73
|
-
// 的采集都在子目录里。跨会话可见的东西如果没人提,等于不存在:agent 会重新
|
|
74
|
-
// 去搜一遍、重新逛一遍那个站。
|
|
75
|
-
// 只报数量和头几个名字(几十个文件全列会把这条 system 撑爆)。
|
|
76
|
-
const refs = [];
|
|
77
|
-
try {
|
|
78
|
-
const refRoot = path.join(assetsLink, 'references');
|
|
79
|
-
const walk = async (dir, rel, depth) => {
|
|
80
|
-
for (const e of await fs.readdir(dir, { withFileTypes: true })) {
|
|
81
|
-
if (e.name === '.meta' || e.name.startsWith('.')) continue;
|
|
82
|
-
if (e.isDirectory()) { if (depth > 0) await walk(path.join(dir, e.name), `${rel}/${e.name}`, depth - 1); continue; }
|
|
83
|
-
refs.push(`${rel}/${e.name}`);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
await walk(refRoot, 'assets/references', 2);
|
|
87
|
-
} catch { /* 没有这个目录就没有 */ }
|
|
88
|
-
|
|
89
|
-
const refLine = refs.length
|
|
90
|
-
? `另有 ${refs.length} 件参考素材在 assets/references/ 下(${refs.slice(0, 3).map(r => r.split('/').pop()).join('、')}`
|
|
91
|
-
+ `${refs.length > 3 ? ' 等' : ''})—— 逛站采回来的调色板/字体/结构/动效清单 json 就在这儿,`
|
|
92
|
-
+ '出处记在同目录的 .meta/<同名>.json 里。**先看有没有现成的,别重复去搜。**'
|
|
93
|
-
: '';
|
|
94
|
-
|
|
95
|
-
// ⚠️ `paths` 会被 UserPromptSubmit 首轮全量打进状态块(之后只报变化,见
|
|
96
|
-
// hooks/user-prompt-submit.js)。refs 随每次 browser_capture 单调增长,首轮也要封顶;
|
|
97
|
-
// `allPaths` 不封顶,给按项算变化用(不直接打进文案)。
|
|
98
|
-
const REF_CAP = 12;
|
|
99
|
-
const TOP_CAP = 60;
|
|
100
|
-
const refPaths = refs.slice(0, REF_CAP);
|
|
101
|
-
const overflow = refs.length - refPaths.length;
|
|
102
|
-
if (overflow > 0) {
|
|
103
|
-
// 别只是截断了不说 —— 「静默截断」读起来就是"全都在这儿了"
|
|
104
|
-
refPaths.push(`(另有 ${overflow} 件在 assets/references/ 下,名字和出处见同目录 .meta/)`);
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
count: files.length + refs.length,
|
|
108
|
-
summary: [
|
|
109
|
-
files.length ? `workspace 里已有 ${files.length} 个参考素材:${parts.join('、')}` : '',
|
|
110
|
-
refLine,
|
|
111
|
-
].filter(Boolean).join('\n'),
|
|
112
|
-
hasBinaryDocs: binaryDocs.length > 0,
|
|
113
|
-
paths: [...allNames.slice(0, TOP_CAP).map((n) => `assets/${n}`), ...refPaths],
|
|
114
|
-
allPaths: [...allNames.map((n) => `assets/${n}`), ...refs],
|
|
115
|
-
};
|
|
116
|
-
} catch {
|
|
117
|
-
return { count: 0, summary: '', hasBinaryDocs: false, paths: [], allPaths: [] };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* server/projects/assets-summary.js — 工作区素材摘要(2026-08-18 从 workspace.js 拆出)
|
|
3
|
+
*
|
|
4
|
+
* 这条摘要进每个会话的 system 提示:告诉 agent 工作区里已经有什么素材。
|
|
5
|
+
* 拆出来的理由是它跟"工作区生命周期"没关系 —— 它是个格式化器。
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ 存在的意义在于:**跨会话可见的东西如果没人提,等于不存在**。
|
|
8
|
+
* `assets/` 不随 sessionId 分桶,下个会话 Read 得到;但 agent 不会去猜有什么,
|
|
9
|
+
* 它会重新搜一遍、重新逛一遍那个站。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import fs from 'node:fs/promises';
|
|
14
|
+
|
|
15
|
+
const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg']);
|
|
16
|
+
const TEXT_DOC_EXT = new Set(['.md', '.txt', '.json']);
|
|
17
|
+
const BINARY_DOC_EXT = new Set(['.pdf', '.pptx', '.ppt', '.docx', '.doc', '.xlsx', '.xls']);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} sessionRoot - sessions/<sid>/ 绝对路径
|
|
21
|
+
* @returns {Promise<{ count: number, summary: string, hasBinaryDocs: boolean }>}
|
|
22
|
+
* count=0 时 summary 为空字符串,调用方据此判断是否注入提示。
|
|
23
|
+
* hasBinaryDocs=true 时调用方追加 python 处理提醒(Read 拿不到这些的内容)。
|
|
24
|
+
*/
|
|
25
|
+
export async function readAssetsSummary(sessionRoot) {
|
|
26
|
+
try {
|
|
27
|
+
const assetsLink = path.join(sessionRoot, 'assets');
|
|
28
|
+
const stat = await fs.stat(assetsLink).catch(() => null);
|
|
29
|
+
if (!stat) return { count: 0, summary: '', hasBinaryDocs: false };
|
|
30
|
+
|
|
31
|
+
const entries = await fs.readdir(assetsLink, { withFileTypes: true }).catch(() => []);
|
|
32
|
+
const files = entries.filter((e) => !e.name.startsWith('.') && (e.isFile() || e.isSymbolicLink()));
|
|
33
|
+
// ⚠️ 别在这儿提前 return:顶层一个文件都没有、但 assets/references/ 下有一堆
|
|
34
|
+
// 采集素材是**常见情形**(agent 逛过站但用户没上传过东西)。提前 return 会让
|
|
35
|
+
// 那些素材对 agent 彻底隐形。
|
|
36
|
+
|
|
37
|
+
const images = [];
|
|
38
|
+
const textDocs = [];
|
|
39
|
+
const binaryDocs = [];
|
|
40
|
+
const others = [];
|
|
41
|
+
for (const f of files) {
|
|
42
|
+
const ext = path.extname(f.name).toLowerCase();
|
|
43
|
+
if (IMAGE_EXT.has(ext)) images.push(f.name);
|
|
44
|
+
else if (TEXT_DOC_EXT.has(ext)) textDocs.push(f.name);
|
|
45
|
+
else if (BINARY_DOC_EXT.has(ext)) binaryDocs.push(f.name);
|
|
46
|
+
else others.push(f.name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 摘要:种类 + 头几个文件名(避免太长)
|
|
50
|
+
const parts = [];
|
|
51
|
+
if (images.length > 0) {
|
|
52
|
+
const sample = images.slice(0, 3).join('、');
|
|
53
|
+
parts.push(`${images.length} 张图(${sample}${images.length > 3 ? ` 等` : ''})`);
|
|
54
|
+
}
|
|
55
|
+
if (textDocs.length > 0) {
|
|
56
|
+
const sample = textDocs.slice(0, 3).join('、');
|
|
57
|
+
parts.push(`${textDocs.length} 个文本文档(${sample}${textDocs.length > 3 ? ` 等` : ''})`);
|
|
58
|
+
}
|
|
59
|
+
if (binaryDocs.length > 0) {
|
|
60
|
+
const sample = binaryDocs.slice(0, 3).join('、');
|
|
61
|
+
parts.push(`${binaryDocs.length} 个 PDF/Office 文档(${sample}${binaryDocs.length > 3 ? ` 等` : ''})`);
|
|
62
|
+
}
|
|
63
|
+
if (others.length > 0) {
|
|
64
|
+
parts.push(`${others.length} 个其他文件`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 完整文件清单(按路径列)—— 让 agent 不用再 Glob/LS 探。
|
|
68
|
+
// (08-07 扁平化后 assets/ 是真目录,Glob 也能用;08-21 前这里还写着"symlink 别用 Glob",删了。)
|
|
69
|
+
const allNames = [...images, ...textDocs, ...binaryDocs, ...others];
|
|
70
|
+
|
|
71
|
+
// 参考素材(2026-08-18):`assets/references/**` 也要报,否则**没有任何机制
|
|
72
|
+
// 告诉 agent 它们存在** —— 上面这遍只列顶层文件,而参考图和 browser_capture
|
|
73
|
+
// 的采集都在子目录里。跨会话可见的东西如果没人提,等于不存在:agent 会重新
|
|
74
|
+
// 去搜一遍、重新逛一遍那个站。
|
|
75
|
+
// 只报数量和头几个名字(几十个文件全列会把这条 system 撑爆)。
|
|
76
|
+
const refs = [];
|
|
77
|
+
try {
|
|
78
|
+
const refRoot = path.join(assetsLink, 'references');
|
|
79
|
+
const walk = async (dir, rel, depth) => {
|
|
80
|
+
for (const e of await fs.readdir(dir, { withFileTypes: true })) {
|
|
81
|
+
if (e.name === '.meta' || e.name.startsWith('.')) continue;
|
|
82
|
+
if (e.isDirectory()) { if (depth > 0) await walk(path.join(dir, e.name), `${rel}/${e.name}`, depth - 1); continue; }
|
|
83
|
+
refs.push(`${rel}/${e.name}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
await walk(refRoot, 'assets/references', 2);
|
|
87
|
+
} catch { /* 没有这个目录就没有 */ }
|
|
88
|
+
|
|
89
|
+
const refLine = refs.length
|
|
90
|
+
? `另有 ${refs.length} 件参考素材在 assets/references/ 下(${refs.slice(0, 3).map(r => r.split('/').pop()).join('、')}`
|
|
91
|
+
+ `${refs.length > 3 ? ' 等' : ''})—— 逛站采回来的调色板/字体/结构/动效清单 json 就在这儿,`
|
|
92
|
+
+ '出处记在同目录的 .meta/<同名>.json 里。**先看有没有现成的,别重复去搜。**'
|
|
93
|
+
: '';
|
|
94
|
+
|
|
95
|
+
// ⚠️ `paths` 会被 UserPromptSubmit 首轮全量打进状态块(之后只报变化,见
|
|
96
|
+
// hooks/user-prompt-submit.js)。refs 随每次 browser_capture 单调增长,首轮也要封顶;
|
|
97
|
+
// `allPaths` 不封顶,给按项算变化用(不直接打进文案)。
|
|
98
|
+
const REF_CAP = 12;
|
|
99
|
+
const TOP_CAP = 60;
|
|
100
|
+
const refPaths = refs.slice(0, REF_CAP);
|
|
101
|
+
const overflow = refs.length - refPaths.length;
|
|
102
|
+
if (overflow > 0) {
|
|
103
|
+
// 别只是截断了不说 —— 「静默截断」读起来就是"全都在这儿了"
|
|
104
|
+
refPaths.push(`(另有 ${overflow} 件在 assets/references/ 下,名字和出处见同目录 .meta/)`);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
count: files.length + refs.length,
|
|
108
|
+
summary: [
|
|
109
|
+
files.length ? `workspace 里已有 ${files.length} 个参考素材:${parts.join('、')}` : '',
|
|
110
|
+
refLine,
|
|
111
|
+
].filter(Boolean).join('\n'),
|
|
112
|
+
hasBinaryDocs: binaryDocs.length > 0,
|
|
113
|
+
paths: [...allNames.slice(0, TOP_CAP).map((n) => `assets/${n}`), ...refPaths],
|
|
114
|
+
allPaths: [...allNames.map((n) => `assets/${n}`), ...refs],
|
|
115
|
+
};
|
|
116
|
+
} catch {
|
|
117
|
+
return { count: 0, summary: '', hasBinaryDocs: false, paths: [], allPaths: [] };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* server/projects/auto-name.js — 用会话摘要给项目起名(2026-07-28)
|
|
3
|
-
*
|
|
4
|
-
* 首页那个大输入框以前建的是"闪聊"(kind=quick),名字硬切用户第一句话前 30 字,
|
|
5
|
-
* 而且不算正经项目。现在它直接建真项目,名字先用那句话垫着(auto_named=1),
|
|
6
|
-
* 第一轮跑完就用 **SDK helper 写的会话摘要** 改一次名 —— 那是它本来就在写的东西
|
|
7
|
-
* (incrementally 落进 jsonl,我们列会话时读的就是它),不额外花一次模型调用。
|
|
8
|
-
*
|
|
9
|
-
* 只改一次:改完清 auto_named。用户自己改过名的项目(updateProject 带 name 会
|
|
10
|
-
* 自动清零)永远不动。
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import path from 'node:path';
|
|
14
|
-
import { getSessionInfo } from '@anthropic-ai/claude-agent-sdk';
|
|
15
|
-
import { getProject, updateProject } from './store.js';
|
|
16
|
-
import { getProjectWorkspace } from './workspace.js';
|
|
17
|
-
import { withConfigDir } from '../lib/sdk-session.js';
|
|
18
|
-
import { platform } from '../runtime/platform.js';
|
|
19
|
-
|
|
20
|
-
const MAX_NAME = 40;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {string} projectId
|
|
24
|
-
* @param {string} sessionId
|
|
25
|
-
* @returns {Promise<string|null>} 改成了什么名字;没改返 null
|
|
26
|
-
*/
|
|
27
|
-
export async function autoNameProjectFromSession(projectId, sessionId) {
|
|
28
|
-
if (!projectId || !sessionId) return null;
|
|
29
|
-
let project;
|
|
30
|
-
try { project = getProject(projectId); } catch { return null; }
|
|
31
|
-
if (!project || !project.autoNamed) return null;
|
|
32
|
-
|
|
33
|
-
const sessionRoot = path.join(getProjectWorkspace(projectId), 'sessions', sessionId);
|
|
34
|
-
let info;
|
|
35
|
-
try {
|
|
36
|
-
info = await withConfigDir(platform.claudeConfigDir, () =>
|
|
37
|
-
getSessionInfo(sessionId, { dir: sessionRoot }),
|
|
38
|
-
);
|
|
39
|
-
} catch {
|
|
40
|
-
return null; // 读不到就下轮再说,auto_named 还留着
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const summary = String(info?.customTitle || info?.summary || '').trim();
|
|
44
|
-
if (!summary) return null;
|
|
45
|
-
const name = summary.length > MAX_NAME ? summary.slice(0, MAX_NAME) + '…' : summary;
|
|
46
|
-
if (name === project.name) {
|
|
47
|
-
updateProject(projectId, { autoNamed: false }); // 同名也算定了,别每轮再问
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
updateProject(projectId, { name, autoNamed: false });
|
|
52
|
-
return name;
|
|
53
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* server/projects/auto-name.js — 用会话摘要给项目起名(2026-07-28)
|
|
3
|
+
*
|
|
4
|
+
* 首页那个大输入框以前建的是"闪聊"(kind=quick),名字硬切用户第一句话前 30 字,
|
|
5
|
+
* 而且不算正经项目。现在它直接建真项目,名字先用那句话垫着(auto_named=1),
|
|
6
|
+
* 第一轮跑完就用 **SDK helper 写的会话摘要** 改一次名 —— 那是它本来就在写的东西
|
|
7
|
+
* (incrementally 落进 jsonl,我们列会话时读的就是它),不额外花一次模型调用。
|
|
8
|
+
*
|
|
9
|
+
* 只改一次:改完清 auto_named。用户自己改过名的项目(updateProject 带 name 会
|
|
10
|
+
* 自动清零)永远不动。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { getSessionInfo } from '@anthropic-ai/claude-agent-sdk';
|
|
15
|
+
import { getProject, updateProject } from './store.js';
|
|
16
|
+
import { getProjectWorkspace } from './workspace.js';
|
|
17
|
+
import { withConfigDir } from '../lib/sdk-session.js';
|
|
18
|
+
import { platform } from '../runtime/platform.js';
|
|
19
|
+
|
|
20
|
+
const MAX_NAME = 40;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} projectId
|
|
24
|
+
* @param {string} sessionId
|
|
25
|
+
* @returns {Promise<string|null>} 改成了什么名字;没改返 null
|
|
26
|
+
*/
|
|
27
|
+
export async function autoNameProjectFromSession(projectId, sessionId) {
|
|
28
|
+
if (!projectId || !sessionId) return null;
|
|
29
|
+
let project;
|
|
30
|
+
try { project = getProject(projectId); } catch { return null; }
|
|
31
|
+
if (!project || !project.autoNamed) return null;
|
|
32
|
+
|
|
33
|
+
const sessionRoot = path.join(getProjectWorkspace(projectId), 'sessions', sessionId);
|
|
34
|
+
let info;
|
|
35
|
+
try {
|
|
36
|
+
info = await withConfigDir(platform.claudeConfigDir, () =>
|
|
37
|
+
getSessionInfo(sessionId, { dir: sessionRoot }),
|
|
38
|
+
);
|
|
39
|
+
} catch {
|
|
40
|
+
return null; // 读不到就下轮再说,auto_named 还留着
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const summary = String(info?.customTitle || info?.summary || '').trim();
|
|
44
|
+
if (!summary) return null;
|
|
45
|
+
const name = summary.length > MAX_NAME ? summary.slice(0, MAX_NAME) + '…' : summary;
|
|
46
|
+
if (name === project.name) {
|
|
47
|
+
updateProject(projectId, { autoNamed: false }); // 同名也算定了,别每轮再问
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
updateProject(projectId, { name, autoNamed: false });
|
|
52
|
+
return name;
|
|
53
|
+
}
|