@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,284 +1,284 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* artifact-target.js — 产物寻址(2026-07-28 由 canvas-target.js 泛化;
|
|
3
|
-
* 2026-07-29 形态判定与解析下沉到 kinds/ 注册表;2026-08-07 任务层退役)
|
|
4
|
-
*
|
|
5
|
-
* 定死三件事:
|
|
6
|
-
*
|
|
7
|
-
* **① 产物有形态(kind),文件就是真相。** 判定规则在 kinds/ 注册表:
|
|
8
|
-
* canvas.html → deck;index.html(工作区根 / 声明的产物根 / 约定构建目录)
|
|
9
|
-
* → site。
|
|
10
|
-
*
|
|
11
|
-
* **② 寻址永远带着 kind 一起返回。** 下游(截图 / 分页 / 导出 / fit 注入)必须
|
|
12
|
-
* 能看见"这是 deck 还是 site",才可能给出对的行为。kind 每次 resolve 按工作区
|
|
13
|
-
* 现状重算,不缓存。
|
|
14
|
-
*
|
|
15
|
-
* **③ 源和产物可以分开。** 构建型站点的源在工作区根、产物在 dist/ 之类的产物根。
|
|
16
|
-
* resolve 返回 workspaceRoot(源,agent 的地盘)和 artifactDir(被预览 / 导出 /
|
|
17
|
-
* 发布的根),deck 和手写站点两者相同。消费方要"看"产物就用 artifactDir,
|
|
18
|
-
* 别再自己 dirname(entryPath)。
|
|
19
|
-
*
|
|
20
|
-
* 寻址顺序(越靠前越明确):
|
|
21
|
-
* ① 调用方显式给的 path
|
|
22
|
-
* ② 本会话的"当前产物"——最近写过 / 截过 / 预览过哪份就是哪份
|
|
23
|
-
* ③ 工作区只有一份产物就是它;多份就报错列出来让调用方指定
|
|
24
|
-
*
|
|
25
|
-
* ## 2026-08-07:第 ③ 条曾经是第 ④ 条
|
|
26
|
-
*
|
|
27
|
-
* 中间夹着一条「认领绑在本会话名下那个任务」——`tasks/<任务>/.nd-task.json`
|
|
28
|
-
* 里记着 sessionId,寻址时按当前会话去反查它的任务。那条规则存在的前提是
|
|
29
|
-
* **一个项目里有多个任务、而每个任务属于一个会话**;线上数据说这个前提从来
|
|
30
|
-
* 没成立过(13 个有产物的项目,每个都恰好一个任务)。
|
|
31
|
-
*
|
|
32
|
-
* 任务层拆掉之后,"这个会话的产物"这个问题本身就没有意义了 —— 产物属于项目,
|
|
33
|
-
* 谁开的对话都看得见同一批。剩下的歧义(工作区里有多份产物、调用方没说哪份)
|
|
34
|
-
* 由 ② 的最近使用和 ③ 的显式报错解决,这两条本来就在,而且不依赖任何绑定。
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
import path from 'node:path';
|
|
38
|
-
import fs from 'node:fs/promises';
|
|
39
|
-
import {
|
|
40
|
-
KINDS, kindDef, readTaskMarker, taskManifest, artifactOfPath, can,
|
|
41
|
-
} from './kinds/index.js';
|
|
42
|
-
|
|
43
|
-
export const KIND_DECK = 'deck';
|
|
44
|
-
export const KIND_SITE = 'site';
|
|
45
|
-
|
|
46
|
-
/** 每种形态的入口文件名(从注册表派生;改形态契约去 kinds/) */
|
|
47
|
-
export const ENTRY_FILE = Object.freeze(
|
|
48
|
-
Object.fromEntries(Object.values(KINDS).map(k => [k.id, k.entryFile])),
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 产物入口**长什么样**(扩展名集合),同样从注册表派生。
|
|
53
|
-
* `.html` / `.htm` 是 deck+site 的形状,`.docx` 是 word 的 —— 加形态时这里
|
|
54
|
-
* 自动跟上,不用再回来补一条正则。
|
|
55
|
-
*/
|
|
56
|
-
const ENTRY_EXTS = new Set(
|
|
57
|
-
Object.values(KINDS)
|
|
58
|
-
.map(k => (k.entryFile.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase())
|
|
59
|
-
.filter(Boolean)
|
|
60
|
-
.concat('.htm'), // html 的老写法,注册表里只写了 .html
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// 形态判定与解析的权威在 kinds/,这里转发老名字(消费方 import 不用改两次)
|
|
64
|
-
export { readTaskMarker, taskManifest, kindDef, artifactOfPath };
|
|
65
|
-
export { formatAllowed, docxClaimedFiles, isDirArtifact } from './kinds/index.js';
|
|
66
|
-
export { can };
|
|
67
|
-
|
|
68
|
-
/** sessionId → { path, kind }。会话结束不清也无妨(几个短字符串) */
|
|
69
|
-
const activeArtifact = new Map();
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 记住这个会话正在做哪份产物(写文件 / 截图 / 预览时调)。
|
|
73
|
-
*
|
|
74
|
-
* 只认**产物入口的形状**(ENTRY_EXTS,从注册表派生)。以前还认"任务内的非
|
|
75
|
-
* html"(改 style.css 也算在这个任务上干活,寻址时走任务入口),任务层没了之后
|
|
76
|
-
* 那一支失去了落点:改 `style.css` 说明不了在做哪一份 deck,工作区根上可能并排
|
|
77
|
-
* 放着好几份。认不出来就不认,让 resolve 走"只有一份就是它 / 多份报错"那条 ——
|
|
78
|
-
* 比猜一个错的默认目标强。
|
|
79
|
-
*/
|
|
80
|
-
export function setActiveArtifact(sessionId, relPath, kind) {
|
|
81
|
-
if (!sessionId || typeof relPath !== 'string') return;
|
|
82
|
-
const p = normalizeRel(relPath);
|
|
83
|
-
if (/\.template\.(html?|css)$/i.test(p)) return; // 模板不是产物
|
|
84
|
-
const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
|
|
85
|
-
if (!ENTRY_EXTS.has(ext)) return;
|
|
86
|
-
activeArtifact.set(sessionId, { path: p, kind: kind || null });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getActiveArtifact(sessionId) {
|
|
90
|
-
return (sessionId && activeArtifact.get(sessionId)) || null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function clearActiveArtifact(sessionId) {
|
|
94
|
-
activeArtifact.delete(sessionId);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function normalizeRel(p) {
|
|
98
|
-
return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function exists(p) {
|
|
102
|
-
try { await fs.access(p); return true; } catch { return false; }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** workspace 内才算数(resolve 不出根) */
|
|
106
|
-
function insideWorkspace(workspaceRoot, absPath) {
|
|
107
|
-
const root = path.resolve(workspaceRoot);
|
|
108
|
-
return absPath === root || absPath.startsWith(root + path.sep);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* 站点的页面清单(相对产物根)。
|
|
113
|
-
*/
|
|
114
|
-
export async function listSitePages(workspaceRoot) {
|
|
115
|
-
const m = await KINDS[KIND_SITE].manifest(workspaceRoot, null);
|
|
116
|
-
return m.pages;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 推断某个 html 路径的产物形态。
|
|
121
|
-
*
|
|
122
|
-
* 多产物平权:canvas.html 跟一个根站并存时仍然是 deck、about.html 是站点子页 ——
|
|
123
|
-
* 同名文件形态可以不同,问 artifacts 清单而不是猜文件名。清单里找不到(文件
|
|
124
|
-
* 还没写出来)才退回文件名约定。
|
|
125
|
-
*/
|
|
126
|
-
export async function kindOfPath(workspaceRoot, relPath) {
|
|
127
|
-
const p = normalizeRel(relPath);
|
|
128
|
-
const base = path.posix.basename(p);
|
|
129
|
-
const m = await taskManifest(workspaceRoot);
|
|
130
|
-
if (m) {
|
|
131
|
-
const art = artifactOfPath(m, p);
|
|
132
|
-
if (art) return art.kind;
|
|
133
|
-
if (m.kind) return m.kind;
|
|
134
|
-
}
|
|
135
|
-
if (base === ENTRY_FILE[KIND_DECK]) return KIND_DECK;
|
|
136
|
-
if (base === ENTRY_FILE[KIND_SITE]) return KIND_SITE;
|
|
137
|
-
// 清单里没有(文件还没写出来)时按扩展名回退。.docx 只可能是 word 形态,
|
|
138
|
-
// 让它落进 deck 会让感知层拿 playwright 去开一个二进制包。
|
|
139
|
-
const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
|
|
140
|
-
const byExt = Object.values(KINDS).find(k => k.entryFile.toLowerCase().endsWith(ext) && ext !== '.html');
|
|
141
|
-
if (byExt) return byExt.id;
|
|
142
|
-
return KIND_DECK; // 认不出的散装 .html 一律按 deck
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** 这个工作区现有的产物入口(平权,没有主次) */
|
|
146
|
-
export async function listWorkspaceArtifacts(workspaceRoot) {
|
|
147
|
-
const m = await taskManifest(workspaceRoot);
|
|
148
|
-
return (m?.artifacts || []).map(a => ({ rel: a.entryRel, kind: a.kind }));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** 兼容旧名:只要 deck 那些 */
|
|
152
|
-
export async function listWorkspaceDecks(workspaceRoot) {
|
|
153
|
-
return (await listWorkspaceArtifacts(workspaceRoot))
|
|
154
|
-
.filter(a => a.kind === KIND_DECK)
|
|
155
|
-
.map(a => a.rel);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* 解析产物目标。
|
|
160
|
-
*
|
|
161
|
-
* @param {string} workspaceRoot
|
|
162
|
-
* @param {string|null} relPath 调用方显式给的路径(优先级最高)
|
|
163
|
-
* @param {string|null} sessionId
|
|
164
|
-
* @returns {Promise<{ ok: true, absPath: string, relPath: string, kind: 'deck'|'site',
|
|
165
|
-
* task: string|null, taskDir: string|null,
|
|
166
|
-
* artifactDir: string|null, artifactRel: string|null }
|
|
167
|
-
* | { ok: false, message: string }>}
|
|
168
|
-
* taskDir 源(任务根,agent 的地盘)
|
|
169
|
-
* artifactDir 产物根(被预览 / 导出 / 发布的目录);deck 和手写站点 = taskDir
|
|
170
|
-
*/
|
|
171
|
-
export async function resolveArtifactTarget(workspaceRoot, relPath, sessionId) {
|
|
172
|
-
const decorate = async (rel) => {
|
|
173
|
-
const p = normalizeRel(rel);
|
|
174
|
-
// 按所属产物定 kind 和产物根(多产物平权:canvas.html 是 deck、
|
|
175
|
-
// v2/index.html 是另一个站,root 各归各)
|
|
176
|
-
let kind = null;
|
|
177
|
-
let root = '';
|
|
178
|
-
let artifact = null;
|
|
179
|
-
const m = await taskManifest(workspaceRoot);
|
|
180
|
-
if (m) {
|
|
181
|
-
artifact = artifactOfPath(m, p);
|
|
182
|
-
if (artifact) { kind = artifact.kind; root = artifact.root || ''; }
|
|
183
|
-
else if (m.kind) { kind = m.kind; root = m.root || ''; }
|
|
184
|
-
}
|
|
185
|
-
if (!kind) kind = await kindOfPath(workspaceRoot, p);
|
|
186
|
-
return {
|
|
187
|
-
ok: true,
|
|
188
|
-
absPath: path.resolve(workspaceRoot, p),
|
|
189
|
-
relPath: p,
|
|
190
|
-
kind,
|
|
191
|
-
// task / taskDir 保留成兼容字段:任务层没了,"源目录"永远是工作区根。
|
|
192
|
-
// 下游有十几处读 taskDir 当"agent 的地盘",给它对的值比改十几处安全。
|
|
193
|
-
task: null,
|
|
194
|
-
taskDir: workspaceRoot,
|
|
195
|
-
artifact, // 所属产物条目(manifest.artifacts 里那条;散文件为 null)
|
|
196
|
-
artifactDir: root ? path.join(workspaceRoot, root) : workspaceRoot,
|
|
197
|
-
artifactRel: root || '.',
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const tryPath = async (rel) => {
|
|
202
|
-
if (!rel) return null;
|
|
203
|
-
const abs = path.resolve(workspaceRoot, rel);
|
|
204
|
-
if (!insideWorkspace(workspaceRoot, abs)) return null;
|
|
205
|
-
return (await exists(abs)) ? decorate(rel) : null;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
if (relPath) {
|
|
209
|
-
const hit = await tryPath(relPath);
|
|
210
|
-
if (hit) { setActiveArtifact(sessionId, hit.relPath, hit.kind); return hit; }
|
|
211
|
-
const abs = path.resolve(workspaceRoot, relPath);
|
|
212
|
-
return {
|
|
213
|
-
ok: false,
|
|
214
|
-
message: insideWorkspace(workspaceRoot, abs)
|
|
215
|
-
? `${relPath} not found. Write it first, or check the path.`
|
|
216
|
-
: 'path escapes workspace',
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const active = getActiveArtifact(sessionId);
|
|
221
|
-
if (active?.path) {
|
|
222
|
-
const activeHit = await tryPath(active.path);
|
|
223
|
-
if (activeHit) return activeHit;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const all = await listWorkspaceArtifacts(workspaceRoot);
|
|
227
|
-
if (all.length === 1) {
|
|
228
|
-
setActiveArtifact(sessionId, all[0].rel, all[0].kind);
|
|
229
|
-
return decorate(all[0].rel);
|
|
230
|
-
}
|
|
231
|
-
if (all.length > 1) {
|
|
232
|
-
return {
|
|
233
|
-
ok: false,
|
|
234
|
-
message: 'Multiple artifacts in this workspace — pass path explicitly:\n'
|
|
235
|
-
+ all.map(a => `- ${a.rel} (${a.kind})`).join('\n'),
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
return {
|
|
239
|
-
ok: false,
|
|
240
|
-
message: 'No artifact found. Deck = canvas.html, site = index.html — '
|
|
241
|
-
+ 'write one first, or pass path explicitly.',
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* 浏览器类工具的统一闸门:目标形态不能用浏览器打开就别往下走。
|
|
247
|
-
*
|
|
248
|
-
* 用法是 resolveCanvasTarget 之后立刻过一道:
|
|
249
|
-
* const target = await resolveCanvasTarget(...);
|
|
250
|
-
* if (!target.ok) return err(target.message);
|
|
251
|
-
* const gate = requireBrowsable(target);
|
|
252
|
-
* if (gate) return err(gate);
|
|
253
|
-
*
|
|
254
|
-
* 不把它塞进 resolveArtifactTarget 内部,是因为导出、寻址这些**正当**需要拿到
|
|
255
|
-
* 不可浏览目标的调用方也走那个函数,在源头拦会误伤。
|
|
256
|
-
*
|
|
257
|
-
* @returns {string|null} 不可浏览时返回给 agent 的说明,可浏览返回 null
|
|
258
|
-
*/
|
|
259
|
-
export function requireBrowsable(target) {
|
|
260
|
-
if (!target?.kind || can(target.kind, 'browsable')) return null;
|
|
261
|
-
const who = target.relPath || target.kind;
|
|
262
|
-
// 可渲染形态(docx)要给对路的替代方案。说"直接 Read 它的文件"是**错的建议**
|
|
263
|
-
// —— 那是个二进制 zip,Read 出来是乱码,而它其实是**看得见**的,只是要先渲染。
|
|
264
|
-
if (can(target.kind, 'renderable')) {
|
|
265
|
-
return `${who} 是 ${target.kind} 形态,没有 DOM,读页面 / 查元素 / 取计算样式这几个工具对它无效。`
|
|
266
|
-
+ '看长相用 screenshot(它会渲染成页图,可以带 pages 参数指定范围);'
|
|
267
|
-
+ '看结构读它的 token 源(同名 .json),别去 Read 那个 .docx 本身,它是二进制包。';
|
|
268
|
-
}
|
|
269
|
-
return `${who} 是 ${target.kind} 形态,没有可以用浏览器打开的入口,`
|
|
270
|
-
+ '截图 / 读页面 / 查元素这类工具对它没有意义。直接 Read 它的文件。';
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** 给各工具复用的 path 参数描述(保持措辞一致) */
|
|
274
|
-
export const ARTIFACT_PATH_DESC =
|
|
275
|
-
'Relative path of the artifact entry (deck: "canvas.html", site: "index.html" '
|
|
276
|
-
+ 'or its built output like "dist/index.html", word: "文档.docx"). '
|
|
277
|
-
+ 'Omit to use the artifact you are currently working on.';
|
|
278
|
-
|
|
279
|
-
// ── 兼容层:老名字继续可用,内部全部走上面的实现 ────────────────────────────
|
|
280
|
-
export const CANVAS_PATH_DESC = ARTIFACT_PATH_DESC;
|
|
281
|
-
export const resolveCanvasTarget = resolveArtifactTarget;
|
|
282
|
-
export const setActiveDeck = (sessionId, relPath) => setActiveArtifact(sessionId, relPath);
|
|
283
|
-
export const getActiveDeck = (sessionId) => getActiveArtifact(sessionId)?.path || null;
|
|
284
|
-
export const clearActiveDeck = clearActiveArtifact;
|
|
1
|
+
/**
|
|
2
|
+
* artifact-target.js — 产物寻址(2026-07-28 由 canvas-target.js 泛化;
|
|
3
|
+
* 2026-07-29 形态判定与解析下沉到 kinds/ 注册表;2026-08-07 任务层退役)
|
|
4
|
+
*
|
|
5
|
+
* 定死三件事:
|
|
6
|
+
*
|
|
7
|
+
* **① 产物有形态(kind),文件就是真相。** 判定规则在 kinds/ 注册表:
|
|
8
|
+
* canvas.html → deck;index.html(工作区根 / 声明的产物根 / 约定构建目录)
|
|
9
|
+
* → site。
|
|
10
|
+
*
|
|
11
|
+
* **② 寻址永远带着 kind 一起返回。** 下游(截图 / 分页 / 导出 / fit 注入)必须
|
|
12
|
+
* 能看见"这是 deck 还是 site",才可能给出对的行为。kind 每次 resolve 按工作区
|
|
13
|
+
* 现状重算,不缓存。
|
|
14
|
+
*
|
|
15
|
+
* **③ 源和产物可以分开。** 构建型站点的源在工作区根、产物在 dist/ 之类的产物根。
|
|
16
|
+
* resolve 返回 workspaceRoot(源,agent 的地盘)和 artifactDir(被预览 / 导出 /
|
|
17
|
+
* 发布的根),deck 和手写站点两者相同。消费方要"看"产物就用 artifactDir,
|
|
18
|
+
* 别再自己 dirname(entryPath)。
|
|
19
|
+
*
|
|
20
|
+
* 寻址顺序(越靠前越明确):
|
|
21
|
+
* ① 调用方显式给的 path
|
|
22
|
+
* ② 本会话的"当前产物"——最近写过 / 截过 / 预览过哪份就是哪份
|
|
23
|
+
* ③ 工作区只有一份产物就是它;多份就报错列出来让调用方指定
|
|
24
|
+
*
|
|
25
|
+
* ## 2026-08-07:第 ③ 条曾经是第 ④ 条
|
|
26
|
+
*
|
|
27
|
+
* 中间夹着一条「认领绑在本会话名下那个任务」——`tasks/<任务>/.nd-task.json`
|
|
28
|
+
* 里记着 sessionId,寻址时按当前会话去反查它的任务。那条规则存在的前提是
|
|
29
|
+
* **一个项目里有多个任务、而每个任务属于一个会话**;线上数据说这个前提从来
|
|
30
|
+
* 没成立过(13 个有产物的项目,每个都恰好一个任务)。
|
|
31
|
+
*
|
|
32
|
+
* 任务层拆掉之后,"这个会话的产物"这个问题本身就没有意义了 —— 产物属于项目,
|
|
33
|
+
* 谁开的对话都看得见同一批。剩下的歧义(工作区里有多份产物、调用方没说哪份)
|
|
34
|
+
* 由 ② 的最近使用和 ③ 的显式报错解决,这两条本来就在,而且不依赖任何绑定。
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
import fs from 'node:fs/promises';
|
|
39
|
+
import {
|
|
40
|
+
KINDS, kindDef, readTaskMarker, taskManifest, artifactOfPath, can,
|
|
41
|
+
} from './kinds/index.js';
|
|
42
|
+
|
|
43
|
+
export const KIND_DECK = 'deck';
|
|
44
|
+
export const KIND_SITE = 'site';
|
|
45
|
+
|
|
46
|
+
/** 每种形态的入口文件名(从注册表派生;改形态契约去 kinds/) */
|
|
47
|
+
export const ENTRY_FILE = Object.freeze(
|
|
48
|
+
Object.fromEntries(Object.values(KINDS).map(k => [k.id, k.entryFile])),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 产物入口**长什么样**(扩展名集合),同样从注册表派生。
|
|
53
|
+
* `.html` / `.htm` 是 deck+site 的形状,`.docx` 是 word 的 —— 加形态时这里
|
|
54
|
+
* 自动跟上,不用再回来补一条正则。
|
|
55
|
+
*/
|
|
56
|
+
const ENTRY_EXTS = new Set(
|
|
57
|
+
Object.values(KINDS)
|
|
58
|
+
.map(k => (k.entryFile.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase())
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.concat('.htm'), // html 的老写法,注册表里只写了 .html
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// 形态判定与解析的权威在 kinds/,这里转发老名字(消费方 import 不用改两次)
|
|
64
|
+
export { readTaskMarker, taskManifest, kindDef, artifactOfPath };
|
|
65
|
+
export { formatAllowed, docxClaimedFiles, isDirArtifact } from './kinds/index.js';
|
|
66
|
+
export { can };
|
|
67
|
+
|
|
68
|
+
/** sessionId → { path, kind }。会话结束不清也无妨(几个短字符串) */
|
|
69
|
+
const activeArtifact = new Map();
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 记住这个会话正在做哪份产物(写文件 / 截图 / 预览时调)。
|
|
73
|
+
*
|
|
74
|
+
* 只认**产物入口的形状**(ENTRY_EXTS,从注册表派生)。以前还认"任务内的非
|
|
75
|
+
* html"(改 style.css 也算在这个任务上干活,寻址时走任务入口),任务层没了之后
|
|
76
|
+
* 那一支失去了落点:改 `style.css` 说明不了在做哪一份 deck,工作区根上可能并排
|
|
77
|
+
* 放着好几份。认不出来就不认,让 resolve 走"只有一份就是它 / 多份报错"那条 ——
|
|
78
|
+
* 比猜一个错的默认目标强。
|
|
79
|
+
*/
|
|
80
|
+
export function setActiveArtifact(sessionId, relPath, kind) {
|
|
81
|
+
if (!sessionId || typeof relPath !== 'string') return;
|
|
82
|
+
const p = normalizeRel(relPath);
|
|
83
|
+
if (/\.template\.(html?|css)$/i.test(p)) return; // 模板不是产物
|
|
84
|
+
const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
|
|
85
|
+
if (!ENTRY_EXTS.has(ext)) return;
|
|
86
|
+
activeArtifact.set(sessionId, { path: p, kind: kind || null });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getActiveArtifact(sessionId) {
|
|
90
|
+
return (sessionId && activeArtifact.get(sessionId)) || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function clearActiveArtifact(sessionId) {
|
|
94
|
+
activeArtifact.delete(sessionId);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeRel(p) {
|
|
98
|
+
return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function exists(p) {
|
|
102
|
+
try { await fs.access(p); return true; } catch { return false; }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** workspace 内才算数(resolve 不出根) */
|
|
106
|
+
function insideWorkspace(workspaceRoot, absPath) {
|
|
107
|
+
const root = path.resolve(workspaceRoot);
|
|
108
|
+
return absPath === root || absPath.startsWith(root + path.sep);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 站点的页面清单(相对产物根)。
|
|
113
|
+
*/
|
|
114
|
+
export async function listSitePages(workspaceRoot) {
|
|
115
|
+
const m = await KINDS[KIND_SITE].manifest(workspaceRoot, null);
|
|
116
|
+
return m.pages;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 推断某个 html 路径的产物形态。
|
|
121
|
+
*
|
|
122
|
+
* 多产物平权:canvas.html 跟一个根站并存时仍然是 deck、about.html 是站点子页 ——
|
|
123
|
+
* 同名文件形态可以不同,问 artifacts 清单而不是猜文件名。清单里找不到(文件
|
|
124
|
+
* 还没写出来)才退回文件名约定。
|
|
125
|
+
*/
|
|
126
|
+
export async function kindOfPath(workspaceRoot, relPath) {
|
|
127
|
+
const p = normalizeRel(relPath);
|
|
128
|
+
const base = path.posix.basename(p);
|
|
129
|
+
const m = await taskManifest(workspaceRoot);
|
|
130
|
+
if (m) {
|
|
131
|
+
const art = artifactOfPath(m, p);
|
|
132
|
+
if (art) return art.kind;
|
|
133
|
+
if (m.kind) return m.kind;
|
|
134
|
+
}
|
|
135
|
+
if (base === ENTRY_FILE[KIND_DECK]) return KIND_DECK;
|
|
136
|
+
if (base === ENTRY_FILE[KIND_SITE]) return KIND_SITE;
|
|
137
|
+
// 清单里没有(文件还没写出来)时按扩展名回退。.docx 只可能是 word 形态,
|
|
138
|
+
// 让它落进 deck 会让感知层拿 playwright 去开一个二进制包。
|
|
139
|
+
const ext = (p.match(/\.[a-z0-9]+$/i) || [''])[0].toLowerCase();
|
|
140
|
+
const byExt = Object.values(KINDS).find(k => k.entryFile.toLowerCase().endsWith(ext) && ext !== '.html');
|
|
141
|
+
if (byExt) return byExt.id;
|
|
142
|
+
return KIND_DECK; // 认不出的散装 .html 一律按 deck
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** 这个工作区现有的产物入口(平权,没有主次) */
|
|
146
|
+
export async function listWorkspaceArtifacts(workspaceRoot) {
|
|
147
|
+
const m = await taskManifest(workspaceRoot);
|
|
148
|
+
return (m?.artifacts || []).map(a => ({ rel: a.entryRel, kind: a.kind }));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 兼容旧名:只要 deck 那些 */
|
|
152
|
+
export async function listWorkspaceDecks(workspaceRoot) {
|
|
153
|
+
return (await listWorkspaceArtifacts(workspaceRoot))
|
|
154
|
+
.filter(a => a.kind === KIND_DECK)
|
|
155
|
+
.map(a => a.rel);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 解析产物目标。
|
|
160
|
+
*
|
|
161
|
+
* @param {string} workspaceRoot
|
|
162
|
+
* @param {string|null} relPath 调用方显式给的路径(优先级最高)
|
|
163
|
+
* @param {string|null} sessionId
|
|
164
|
+
* @returns {Promise<{ ok: true, absPath: string, relPath: string, kind: 'deck'|'site',
|
|
165
|
+
* task: string|null, taskDir: string|null,
|
|
166
|
+
* artifactDir: string|null, artifactRel: string|null }
|
|
167
|
+
* | { ok: false, message: string }>}
|
|
168
|
+
* taskDir 源(任务根,agent 的地盘)
|
|
169
|
+
* artifactDir 产物根(被预览 / 导出 / 发布的目录);deck 和手写站点 = taskDir
|
|
170
|
+
*/
|
|
171
|
+
export async function resolveArtifactTarget(workspaceRoot, relPath, sessionId) {
|
|
172
|
+
const decorate = async (rel) => {
|
|
173
|
+
const p = normalizeRel(rel);
|
|
174
|
+
// 按所属产物定 kind 和产物根(多产物平权:canvas.html 是 deck、
|
|
175
|
+
// v2/index.html 是另一个站,root 各归各)
|
|
176
|
+
let kind = null;
|
|
177
|
+
let root = '';
|
|
178
|
+
let artifact = null;
|
|
179
|
+
const m = await taskManifest(workspaceRoot);
|
|
180
|
+
if (m) {
|
|
181
|
+
artifact = artifactOfPath(m, p);
|
|
182
|
+
if (artifact) { kind = artifact.kind; root = artifact.root || ''; }
|
|
183
|
+
else if (m.kind) { kind = m.kind; root = m.root || ''; }
|
|
184
|
+
}
|
|
185
|
+
if (!kind) kind = await kindOfPath(workspaceRoot, p);
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
absPath: path.resolve(workspaceRoot, p),
|
|
189
|
+
relPath: p,
|
|
190
|
+
kind,
|
|
191
|
+
// task / taskDir 保留成兼容字段:任务层没了,"源目录"永远是工作区根。
|
|
192
|
+
// 下游有十几处读 taskDir 当"agent 的地盘",给它对的值比改十几处安全。
|
|
193
|
+
task: null,
|
|
194
|
+
taskDir: workspaceRoot,
|
|
195
|
+
artifact, // 所属产物条目(manifest.artifacts 里那条;散文件为 null)
|
|
196
|
+
artifactDir: root ? path.join(workspaceRoot, root) : workspaceRoot,
|
|
197
|
+
artifactRel: root || '.',
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const tryPath = async (rel) => {
|
|
202
|
+
if (!rel) return null;
|
|
203
|
+
const abs = path.resolve(workspaceRoot, rel);
|
|
204
|
+
if (!insideWorkspace(workspaceRoot, abs)) return null;
|
|
205
|
+
return (await exists(abs)) ? decorate(rel) : null;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (relPath) {
|
|
209
|
+
const hit = await tryPath(relPath);
|
|
210
|
+
if (hit) { setActiveArtifact(sessionId, hit.relPath, hit.kind); return hit; }
|
|
211
|
+
const abs = path.resolve(workspaceRoot, relPath);
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
message: insideWorkspace(workspaceRoot, abs)
|
|
215
|
+
? `${relPath} not found. Write it first, or check the path.`
|
|
216
|
+
: 'path escapes workspace',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const active = getActiveArtifact(sessionId);
|
|
221
|
+
if (active?.path) {
|
|
222
|
+
const activeHit = await tryPath(active.path);
|
|
223
|
+
if (activeHit) return activeHit;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const all = await listWorkspaceArtifacts(workspaceRoot);
|
|
227
|
+
if (all.length === 1) {
|
|
228
|
+
setActiveArtifact(sessionId, all[0].rel, all[0].kind);
|
|
229
|
+
return decorate(all[0].rel);
|
|
230
|
+
}
|
|
231
|
+
if (all.length > 1) {
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
message: 'Multiple artifacts in this workspace — pass path explicitly:\n'
|
|
235
|
+
+ all.map(a => `- ${a.rel} (${a.kind})`).join('\n'),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
ok: false,
|
|
240
|
+
message: 'No artifact found. Deck = canvas.html, site = index.html — '
|
|
241
|
+
+ 'write one first, or pass path explicitly.',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 浏览器类工具的统一闸门:目标形态不能用浏览器打开就别往下走。
|
|
247
|
+
*
|
|
248
|
+
* 用法是 resolveCanvasTarget 之后立刻过一道:
|
|
249
|
+
* const target = await resolveCanvasTarget(...);
|
|
250
|
+
* if (!target.ok) return err(target.message);
|
|
251
|
+
* const gate = requireBrowsable(target);
|
|
252
|
+
* if (gate) return err(gate);
|
|
253
|
+
*
|
|
254
|
+
* 不把它塞进 resolveArtifactTarget 内部,是因为导出、寻址这些**正当**需要拿到
|
|
255
|
+
* 不可浏览目标的调用方也走那个函数,在源头拦会误伤。
|
|
256
|
+
*
|
|
257
|
+
* @returns {string|null} 不可浏览时返回给 agent 的说明,可浏览返回 null
|
|
258
|
+
*/
|
|
259
|
+
export function requireBrowsable(target) {
|
|
260
|
+
if (!target?.kind || can(target.kind, 'browsable')) return null;
|
|
261
|
+
const who = target.relPath || target.kind;
|
|
262
|
+
// 可渲染形态(docx)要给对路的替代方案。说"直接 Read 它的文件"是**错的建议**
|
|
263
|
+
// —— 那是个二进制 zip,Read 出来是乱码,而它其实是**看得见**的,只是要先渲染。
|
|
264
|
+
if (can(target.kind, 'renderable')) {
|
|
265
|
+
return `${who} 是 ${target.kind} 形态,没有 DOM,读页面 / 查元素 / 取计算样式这几个工具对它无效。`
|
|
266
|
+
+ '看长相用 screenshot(它会渲染成页图,可以带 pages 参数指定范围);'
|
|
267
|
+
+ '看结构读它的 token 源(同名 .json),别去 Read 那个 .docx 本身,它是二进制包。';
|
|
268
|
+
}
|
|
269
|
+
return `${who} 是 ${target.kind} 形态,没有可以用浏览器打开的入口,`
|
|
270
|
+
+ '截图 / 读页面 / 查元素这类工具对它没有意义。直接 Read 它的文件。';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** 给各工具复用的 path 参数描述(保持措辞一致) */
|
|
274
|
+
export const ARTIFACT_PATH_DESC =
|
|
275
|
+
'Relative path of the artifact entry (deck: "canvas.html", site: "index.html" '
|
|
276
|
+
+ 'or its built output like "dist/index.html", word: "文档.docx"). '
|
|
277
|
+
+ 'Omit to use the artifact you are currently working on.';
|
|
278
|
+
|
|
279
|
+
// ── 兼容层:老名字继续可用,内部全部走上面的实现 ────────────────────────────
|
|
280
|
+
export const CANVAS_PATH_DESC = ARTIFACT_PATH_DESC;
|
|
281
|
+
export const resolveCanvasTarget = resolveArtifactTarget;
|
|
282
|
+
export const setActiveDeck = (sessionId, relPath) => setActiveArtifact(sessionId, relPath);
|
|
283
|
+
export const getActiveDeck = (sessionId) => getActiveArtifact(sessionId)?.path || null;
|
|
284
|
+
export const clearActiveDeck = clearActiveArtifact;
|