draftgo-cli 3.0.52 → 3.0.54
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 +17 -23
- package/package.json +2 -3
- package/resources/skill/SKILL.md +6 -12
- package/resources/skill/init/SKILL.md +2 -2
- package/resources/skill/manifest.json +3 -3
- package/resources/skill/references/checkout.md +1 -1
- package/resources/skill/references/mcp.md +4 -4
- package/src/cli.js +0 -1
- package/src/commandRegistry.js +0 -1
- package/src/commands/help.js +2 -9
- package/src/mcp/client.js +131 -2
- package/src/commands/context.js +0 -27
- package/src/context/index.js +0 -662
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Workflow 2.0
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The root DraftGo Skill loads automatically in supported AI hosts. Agents read the smallest task-specific Reference set first, then query current project state through precise MCP tools instead of generating an aggregate context bundle.
|
|
6
6
|
|
|
7
7
|
Pages, navigations, documents, and custom services are long-content resources. Custom services use `draftgo checkout custom-services <id>` and a four-file worktree (`service.go`, `go.mod`, `go.sum`, `service.json`). `commit` saves the shared cloud draft, `test` validates or executes it in the DraftGo Runner, and `publish` explicitly promotes a validated draft without interrupting the current live version. Start later work with `refresh`; it stops when local edits exist. Cloud versions are the only history, while local storage keeps only current content and one base.
|
|
8
8
|
|
|
@@ -24,9 +24,7 @@ CLI 不再依赖 Python 同步脚本,也不会创建 DraftGo 业务资源的
|
|
|
24
24
|
## 工作模型
|
|
25
25
|
|
|
26
26
|
```text
|
|
27
|
-
|
|
28
|
-
-> 复用一个 MCP 会话并行查询实时上下文
|
|
29
|
-
|
|
27
|
+
根 SKILL.md -> Agent 按任务读取最少必要的 Reference
|
|
30
28
|
AI 宿主 -> draftgo mcp serve -> DraftGo /mcp
|
|
31
29
|
|
|
|
32
30
|
+-> 结构化资源、元数据、搜索和正文片段
|
|
@@ -35,7 +33,7 @@ draftgo checkout/commit -> DraftGo 专用 HTTP 正文端点
|
|
|
35
33
|
(完整 HTML/Markdown 不经过 MCP 上下文)
|
|
36
34
|
```
|
|
37
35
|
|
|
38
|
-
-
|
|
36
|
+
- Agent 负责理解任务语义、选择 Reference,并通过精确 MCP 工具逐步定位相关资源;CLI 不生成聚合上下文包。
|
|
39
37
|
- MCP 用于项目概览、资源搜索/列表、元数据、正文片段和实时 API 调用。
|
|
40
38
|
- `pages`、`navigations` 和 `docs/articles` 的完整正文使用 `checkout` / `commit`。
|
|
41
39
|
- `db_meta`、AIHub、`system_config`、角色、用户、文档分类、自定义服务等结构化资源直接通过 MCP/API 操作,不生成本地索引。
|
|
@@ -45,20 +43,19 @@ draftgo checkout/commit -> DraftGo 专用 HTTP 正文端点
|
|
|
45
43
|
|
|
46
44
|
## Agent 阅读顺序
|
|
47
45
|
|
|
48
|
-
DraftGo Skill 不能被 MCP 替代。根 `SKILL.md` 会在 Skill
|
|
46
|
+
DraftGo Skill 不能被 MCP 替代。根 `SKILL.md` 会在 Skill 触发时自动加载;Agent 按任务路由渐进读取相关 Reference:
|
|
49
47
|
|
|
50
48
|
```text
|
|
51
49
|
已加载的 SKILL.md
|
|
52
|
-
->
|
|
53
|
-
->
|
|
54
|
-
->
|
|
50
|
+
-> 读取任务所需的最少 Reference
|
|
51
|
+
-> 用 resource_search / api_search 精确定位
|
|
52
|
+
-> 按需 describe、读取 metadata 或片段
|
|
55
53
|
-> 必要时 checkout 正文
|
|
56
54
|
```
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
当前 context JSON 使用 `schema_version: "1.1"`;`live.resources` 和 `live.api` 以带来源的 `pages` 序列保留批量原始响应,`live.data_structures` 保留 db_meta 的 registry、describe 契约及完整分页读取结果。
|
|
56
|
+
页面任务读取 `frontend.md`,按需补读 `runtime.md` / `app-api.md`;数据、自定义服务和 AIHub 任务分别读取对应 Reference。目标明确时不要全量枚举无关资源。`mcp test` 是连接诊断,不是每次资源查询的前置步骤。
|
|
60
57
|
|
|
61
|
-
|
|
58
|
+
只回答无需实时状态的本地规则问题时,不必调用 MCP。静态资源须先区分:平台内置目录由 `frontend.md` 说明,指定页面依赖经 MCP 定位、checkout 后本地搜索,服务器全量文件不能凭现有 Skill、MCP 摘要或 checkout 声称已枚举。
|
|
62
59
|
|
|
63
60
|
Skill 说明产品约束、内置能力和操作规则;MCP 说明当前远端状态;checkout 正文才能证明某个页面完整引用了哪些静态资源。汇报时应明确标注这三类证据,不能互相替代。
|
|
64
61
|
|
|
@@ -104,6 +101,8 @@ draftgo mcp test # 验证 initialize、tools/list、项目/正文
|
|
|
104
101
|
draftgo mcp serve # 启动 stdio -> 远端 Streamable HTTP bridge
|
|
105
102
|
```
|
|
106
103
|
|
|
104
|
+
`mcp serve` 会保留远端 `Mcp-Session-Id`。服务重启或 session 过期后,它会自动重新 initialize,并只重试一次被明确拒绝为 session 无效的当前请求;普通工具失败、超时和 5xx 不会自动重试。`mcp test` 使用独立的新连接,只证明当前配置能够建立新 session,不能直接证明宿主此前持有的 session 仍有效。
|
|
105
|
+
|
|
107
106
|
`setup` 不传 target 时自动检测当前项目的宿主。可用 `--target codex,cursor` 传入多个目标。
|
|
108
107
|
|
|
109
108
|
当前 MCP 宿主支持:
|
|
@@ -156,7 +155,8 @@ DraftGo MCP 预期提供以下工具:
|
|
|
156
155
|
## 标准工作流
|
|
157
156
|
|
|
158
157
|
```text
|
|
159
|
-
|
|
158
|
+
Agent 按任务读取相关 Reference
|
|
159
|
+
→ MCP 精确定位
|
|
160
160
|
→ Agent 自主设计并批量 checkout
|
|
161
161
|
→ 独立资源按需并行开发
|
|
162
162
|
→ 主 Agent 一次统一验证
|
|
@@ -164,13 +164,9 @@ context 一次准备信息
|
|
|
164
164
|
→ draftgo changelog add "<完成结果>"
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
无依赖且修改不同资源的工作可以并行;同一文件或 DraftGo 资源全程只分配给一个 Agent。开始并行实现前先确定共享 schema、接口、权限和路由。单页面、强依赖或仍共享主要资源的任务由一个 Agent 完成。主 Agent 负责
|
|
168
|
-
|
|
169
|
-
前端任务先准备上下文并按需补充 MCP 定位:
|
|
167
|
+
无依赖且修改不同资源的工作可以并行;同一文件或 DraftGo 资源全程只分配给一个 Agent。开始并行实现前先确定共享 schema、接口、权限和路由。单页面、强依赖或仍共享主要资源的任务由一个 Agent 完成。主 Agent 负责 Reference/MCP 路由、资源 owner、汇总回读、统一验证、commit 和最终 changelog。
|
|
170
168
|
|
|
171
|
-
|
|
172
|
-
draftgo context --task frontend --output json
|
|
173
|
-
```
|
|
169
|
+
前端任务先读取 `references/frontend.md`,涉及 iframe、路由、认证或全局层时补读 `runtime.md` / `app-api.md`,再通过 MCP 定位目标资源。
|
|
174
170
|
|
|
175
171
|
确定全部正文资源后批量 checkout,自主设计并按唯一 owner 编辑,最后统一验证和交付:
|
|
176
172
|
|
|
@@ -208,7 +204,6 @@ draftgo changelog add "完成文档管理系统及角色权限"
|
|
|
208
204
|
|
|
209
205
|
| 命令 | 说明 |
|
|
210
206
|
|---|---|
|
|
211
|
-
| `draftgo context --task <task> --output json` | 精确读取任务 Reference 章节,并复用一个 MCP 会话并行查询项目、资源、API 和数据结构;保留原始结果及来源。 |
|
|
212
207
|
| `draftgo map` | 并行读取 pages、navigations、docs/articles 元数据,再叠加本地 checkout 状态;`--type` 可限制一种类型。 |
|
|
213
208
|
| `draftgo checkout <pages|nav|docs> <id...>` | 下载完整正文和 base,校验 hash 后原子落盘;`--force` 可显式覆盖已修改的本地正文。 |
|
|
214
209
|
| `draftgo check` | 默认只检查本地 checkout;`--remote` 额外对比 local/base/manifest/remote 的版本和 hash。 |
|
|
@@ -354,8 +349,7 @@ checkout 会通过 MCP 获取元数据和专用下载地址,流式写入临时
|
|
|
354
349
|
## 常用选项
|
|
355
350
|
|
|
356
351
|
- `--project <dir>`:操作指定项目目录,默认当前目录。
|
|
357
|
-
- `--output json`:为
|
|
358
|
-
- `--task <task>`:选择 context 路由;支持 frontend、data、custom-service、aihub、content、project。
|
|
352
|
+
- `--output json`:为 map、check、checkout、commit、diff、conflicts、conflict、api 或 delete 输出 JSON。
|
|
359
353
|
- `--type <type>`:限制 `map` 查询一种正文资源类型;支持 pages、nav/navigations、docs/articles 及其别名。
|
|
360
354
|
- `--strict`:check/deploy/auto-push 将 warnings 视为失败。
|
|
361
355
|
- `--remote`:让 check 按需对比 checkout 的远端版本和 hash;默认仍为快速离线检查。
|
|
@@ -376,7 +370,7 @@ checkout 会通过 MCP 获取元数据和专用下载地址,流式写入临时
|
|
|
376
370
|
- `--token auto|never`:verify 默认读取 `.draftgo/config.json`,对与 `server` 同源的地址自动附加 `token=<SAT>`;配置无 SAT、跨源地址或 `never` 模式均不附加。
|
|
377
371
|
- `--screenshot on-failure|always|never`:控制 UI 截图。
|
|
378
372
|
- `--delivery local|preview|deploy`:控制 deploy 行为。
|
|
379
|
-
- `--timeout <ms>`:控制 connect
|
|
373
|
+
- `--timeout <ms>`:控制 connect 或 MCP test 的远端请求超时。
|
|
380
374
|
|
|
381
375
|
设置 `DRAFTGO_NO_UPDATE_CHECK=1` 可关闭 npm 版本检查,适用于离线或 CI 环境。
|
|
382
376
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "draftgo-cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.54",
|
|
4
4
|
"description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"draftgo": "bin/draftgo.js"
|
|
@@ -45,9 +45,8 @@
|
|
|
45
45
|
"lint": "node scripts/check-syntax.js",
|
|
46
46
|
"validate:skill": "node scripts/validate-skill.js",
|
|
47
47
|
"verify:package": "node scripts/verify-package.js",
|
|
48
|
-
"test": "npm run lint && npm run validate:skill && npm run verify:package && npm run test:unit && npm run test:
|
|
48
|
+
"test": "npm run lint && npm run validate:skill && npm run verify:package && npm run test:unit && npm run test:workflow && npm run test:changelog && npm run test:mcp && npm run test:worktree && npm run test:migration && npm run test:local && npm run test:e2e",
|
|
49
49
|
"test:unit": "node tests/unit.js",
|
|
50
|
-
"test:context": "node tests/context.test.js",
|
|
51
50
|
"test:workflow": "node tests/workflow-upgrade.test.js",
|
|
52
51
|
"test:changelog": "node tests/changelog.test.js",
|
|
53
52
|
"test:mcp": "node tests/mcp.test.js",
|
package/resources/skill/SKILL.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: draftgo
|
|
3
|
-
description: Use this skill to inspect, develop, debug, or deliver a DraftGo application.
|
|
3
|
+
description: Use this skill to inspect, develop, debug, or deliver a DraftGo application. Read task-specific references before using live MCP discovery, use checkout/commit for complete page, navigation, document, and custom-service bodies, and write the changelog only after all verification and delivery succeed.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Workflow 2.0 authority
|
|
7
7
|
|
|
8
8
|
- Treat pages, navigations, docs/articles, and custom services as checkout resources. Never save complete MCP responses, duplicate source snapshots, temporary Go modules, SDK stubs, executables, or build directories by hand.
|
|
9
|
-
-
|
|
10
|
-
- Custom-service flow: `
|
|
9
|
+
- The root Skill loads automatically. Before acting, read the smallest relevant Reference set from the task-routing table, then use MCP only for live project evidence.
|
|
10
|
+
- Custom-service flow: read `references/custom-services.md` -> locate the service through MCP -> `checkout custom-services` -> edit four files -> `commit` cloud draft -> `test` in the server Runner -> `check --remote` -> `publish` -> `refresh`. CLI and admin UI edit the same cloud draft.
|
|
11
11
|
- Do not create Task overhead for a small, single-point change. For cross-module, cross-session, or multi-Agent work, create or resume `.draftgo/Task/[YYYY-MM-DD]<slug>/Task.md`. It is the only task state: preserve both the user's original request and the clarified executable requirement, assign one owner per item/resource, and check an item only with verification evidence. Changelog remains the final delivery record, not a task tracker.
|
|
12
12
|
- Normal delivery uses `draftgo verify`; add `--remote` only when remote comparison is required and `--url` only for browser evidence. UI verification is iframe-aware and defaults to one viewport with no screenshot. Only explicit visual acceptance or regression diagnosis may pass `--viewport both` or `--screenshot on-failure|always`.
|
|
13
13
|
- `.draftgo/tmp/` is entirely disposable. CLI data uses `.draftgo/tmp/<command>/<run-id>/`; AI-created scratch files use `.draftgo/tmp/ai/<run-id>/`. User-facing evidence belongs under registered `.draftgo/artifacts/`. Use `draftgo clean --dry-run` before `draftgo clean --yes`.
|
|
@@ -19,19 +19,13 @@ description: Use this skill to inspect, develop, debug, or deliver a DraftGo app
|
|
|
19
19
|
- 本地 Skill / Reference 提供产品约束、运行机制和实践;MCP 提供当前项目、资源、API、权限与 schema。
|
|
20
20
|
- pages、navigations、docs/articles 的完整正文只通过 `draftgo checkout`、本地 worktree 和 `draftgo commit` 处理,不放进 MCP 参数、结果、日志或聊天上下文。
|
|
21
21
|
- db_meta、AIHub、system_config、roles、users、doc_categories 和自定义服务元数据直接通过 MCP 读写,不建立本地索引。
|
|
22
|
-
-
|
|
22
|
+
- 根 Skill 会在触发时自动加载。Agent 必须先按任务读取最少必要的 Reference,再通过 MCP 获取实时项目证据;MCP 不可用时报告连接、协议或 SAT 问题,不回退到旧索引或 `pull --all`。
|
|
23
23
|
- CLI 不提供页面模板,不预设页面骨架、视觉风格、原生技术或组件库选择。Agent 根据任务、受众、现有产品语言和可用资源自主设计。
|
|
24
24
|
- 本地 Docker、MySQL、Redis、Milvus 与 `draftgo local ...` 独立于 MCP 工作流,必须保留。
|
|
25
25
|
|
|
26
26
|
## 任务路由
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
draftgo context --task <frontend|data|custom-service|aihub|content|project> --output json
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
context 会校验本地 Reference,返回逐字章节、来源、实时项目/资源/API/数据结构和 bundle 同步状态;不同步时先运行 `draftgo update`。它不会限制后续 MCP 查询。只回答本地规则或需要 context 未加载的专题时,直接补读对应 Reference:
|
|
28
|
+
根 Skill 自动加载后,Agent 根据用户任务先读取下表中最少必要的 Reference,再用 MCP 查询当前项目。不要预先读取所有 Reference,也不要在目标明确时全量枚举无关资源:
|
|
35
29
|
|
|
36
30
|
| 任务 | 专题资料与后续动作 |
|
|
37
31
|
|---|---|
|
|
@@ -50,7 +44,7 @@ context 会校验本地 Reference,返回逐字章节、来源、实时项目/
|
|
|
50
44
|
- 同一文件或 DraftGo 资源全程只能由一个 Agent 修改;其他 Agent 只读分析并回传建议。
|
|
51
45
|
- 先确定共享 schema、接口、权限和 route,再并行处理无依赖且 owner 不冲突的工作单元。
|
|
52
46
|
- 单页面、强依赖或仍共享主要资源的任务不强行并行。
|
|
53
|
-
- 主 Agent
|
|
47
|
+
- 主 Agent 负责 Reference/MCP 路由、owner 分配、汇总回读、统一验证、交付和完成日志;子 Agent 不单独写 changelog。
|
|
54
48
|
- 冲突 owner、接口依赖或验证失败会阻塞依赖项,不影响无关分支继续汇总。
|
|
55
49
|
|
|
56
50
|
## 开发与交付
|
|
@@ -17,7 +17,7 @@ description: Install or refresh the DraftGo skill, connect a project to a DraftG
|
|
|
17
17
|
- 已有 DraftGo 服务器:运行 draftgo connect。
|
|
18
18
|
- 需要本地 Docker 基座:运行 draftgo local setup。
|
|
19
19
|
6. 连接后运行 draftgo mcp setup(或指定目标),再运行 draftgo mcp test 验证 initialize、tools/list 和关键工具。
|
|
20
|
-
7.
|
|
20
|
+
7. 根 Skill 自动加载后,按实际任务读取最少必要的 Reference,并通过 MCP project_overview、resource_search 或 api_search 获取实时信息;需要叠加本地 checkout 状态时运行 `draftgo map`。MCP 不可用时报告原因,不回退到 pull --all。
|
|
21
21
|
|
|
22
22
|
draftgo init 只负责 Skill 安装;draftgo connect 负责保存并验证 server/SAT;draftgo mcp setup 负责宿主配置。不要混用职责。
|
|
23
23
|
|
|
@@ -33,7 +33,7 @@ draftgo init 只负责 Skill 安装;draftgo connect 负责保存并验证 serv
|
|
|
33
33
|
## 后续动作
|
|
34
34
|
|
|
35
35
|
- 宿主配置和诊断:draftgo mcp setup/status/test,详见 ../references/mcp.md。
|
|
36
|
-
-
|
|
36
|
+
- 开发准备:根 Skill 自动加载;按任务路由读取最少必要的 Reference,再按需直接调用 MCP。
|
|
37
37
|
- 远端资源补充定位:MCP project_overview、resource_search、resource_list。
|
|
38
38
|
- 正文编辑:自主设计并批量 checkout,不同资源按唯一 owner 并行修改,由主 Agent 统一运行 check、交付前 verify、diff 和 commit。
|
|
39
39
|
- 结构化资源:直接使用 MCP 的 api_search、api_describe、api_call;不 checkout、不生成本地镜像。
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"schema_version": "1.0",
|
|
3
3
|
"id": "draftgo",
|
|
4
4
|
"name": "DraftGo 开发助手",
|
|
5
|
-
"version": "3.0.
|
|
5
|
+
"version": "3.0.54",
|
|
6
6
|
"entry": "SKILL.md",
|
|
7
|
-
"description": "以
|
|
7
|
+
"description": "以 Skill/reference 任务路由、MCP 实时发现、长正文 checkout/commit、统一验证和完成日志为边界的 DraftGo 工作流。",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"capabilities": [
|
|
10
10
|
"draftgo-development",
|
|
11
|
-
"
|
|
11
|
+
"reference-routing",
|
|
12
12
|
"completion-changelog",
|
|
13
13
|
"custom-service-ai-sdk",
|
|
14
14
|
"mcp-bridge",
|
|
@@ -98,7 +98,7 @@ base 已变化时必须拒绝覆盖。
|
|
|
98
98
|
4. 完整正文不得作为 MCP tool 参数发送。
|
|
99
99
|
5. 底座确认 hash 和新版本后,CLI 原子更新 `.base` 与 manifest。返回 hash 不一致时不得推进基线。
|
|
100
100
|
|
|
101
|
-
推荐编辑顺序:主 Agent
|
|
101
|
+
推荐编辑顺序:主 Agent 按任务读取相关 Reference -> MCP 精确定位 -> Agent 自主设计并批量 checkout -> 独立资源按唯一 owner 并行编辑 -> 主 Agent 汇总回读 -> 统一运行一次 `draftgo check` -> `draftgo diff` -> commit -> 页面变化时用 `draftgo verify <type> <id> --remote --url <url>` 验证远端已提交版本。遇到本地正文已经等于远端、但 base/manifest 落后时,先用 `draftgo check --remote` 确认 `committed_unrecorded`,再运行 `draftgo reconcile`;不要手改 manifest。
|
|
102
102
|
|
|
103
103
|
单个 commit 成功不自动写 changelog。只有整个任务统一验证且全部 commit/MCP 交付成功后,主 Agent 才执行一次 `draftgo changelog add "<完成结果>"`。任何检查失败、409/412 或交付失败都不得写入。
|
|
104
104
|
|
|
@@ -12,7 +12,7 @@ DraftGo MCP 用于实时发现、结构化查询和普通 API 操作。完整 pa
|
|
|
12
12
|
MCP tool result 或参数传输;需要全文时使用 `draftgo checkout`,提交时使用 `draftgo commit`。MCP 返回
|
|
13
13
|
`artifact` / `omitted` 时保留该语义,不要尝试重新展开长内容。
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
不要在 CLI 或 MCP 服务端增加聚合上下文工具。自然语言任务的语义筛选由已加载根 Skill 的 Agent 完成:先读取任务路由指定的最少必要 Reference,再调用下面的精确 MCP 工具,并根据结果逐步缩小范围。
|
|
16
16
|
|
|
17
17
|
标准发现顺序:
|
|
18
18
|
|
|
@@ -75,18 +75,18 @@ describe 缺少与本次调用有关的参数、请求体、responses、permissi
|
|
|
75
75
|
## CLI
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
draftgo context --task frontend --output json
|
|
79
78
|
draftgo mcp setup [target...]
|
|
80
79
|
draftgo mcp status [target...]
|
|
81
80
|
draftgo mcp test
|
|
82
81
|
draftgo mcp serve
|
|
83
82
|
```
|
|
84
83
|
|
|
85
|
-
- `context` 是开发任务的一次性准备入口;不保存业务镜像,不传输完整正文。
|
|
86
84
|
- `setup` 写入项目级宿主配置,只替换 `draftgo` MCP 条目并保留其他配置。
|
|
87
85
|
- `status` 检查配置是否存在、格式是否有效,以及是否误写了凭据或远端 URL。
|
|
88
86
|
- `test` 从 `.draftgo/config.json` 读取连接,验证 initialize、tools/list、三类正文 resource_list,以及 db_meta 的 api_search、api_describe 和只读 api_call。
|
|
89
|
-
- `serve` 启动 stdio bridge,把宿主请求代理到当前项目配置的远端 Streamable HTTP `/mcp
|
|
87
|
+
- `serve` 启动 stdio bridge,把宿主请求代理到当前项目配置的远端 Streamable HTTP `/mcp`。远端重启或 session 过期后,bridge 会清除旧 `Mcp-Session-Id`、重新 initialize,并对被明确拒绝为 session 无效的当前请求重试一次;普通超时、服务端错误和工具错误不会自动重试。
|
|
88
|
+
|
|
89
|
+
`draftgo mcp test` 每次使用独立的新连接,因此它证明当前 server、SAT 和协议可以建立新 session,不代表宿主已持有的旧 session 仍然有效。宿主工具报告 session 失效但 `mcp test` 成功时,bridge 应自动恢复;若恢复仍失败,再检查远端日志、代理头传递和服务可用性。
|
|
90
90
|
|
|
91
91
|
`draftgo connect` 保存并验证 server/SAT、探测 `/mcp` 和关键工具,并可提示宿主 setup;`--server` 表示基础地址,完整 MCP endpoint 使用 `--mcp-url` 显式传入。它不下载业务资源,
|
|
92
92
|
也不创建 pages、navigation、docs、db_meta 等本地镜像。底座仍在开发或暂不可达时,报告诊断结果即可,
|
package/src/cli.js
CHANGED
package/src/commandRegistry.js
CHANGED
|
@@ -7,7 +7,6 @@ const commands = [
|
|
|
7
7
|
{ name: 'update', run: (dir, args, flags) => require('./commands/update')(dir, args, flags) },
|
|
8
8
|
{ name: 'uninstall', aliases: ['remove'], run: (dir, args, flags) => require('./commands/uninstall')(dir, args, flags) },
|
|
9
9
|
{ name: 'status', run: (dir) => require('./commands/status')(dir) },
|
|
10
|
-
{ name: 'context', run: (dir, args, flags) => require('./commands/context')(dir, args, flags) },
|
|
11
10
|
{ name: 'changelog', run: (dir, args, flags) => require('./commands/changelog')(dir, args, flags) },
|
|
12
11
|
{ name: 'task', run: (dir, args, flags) => require('./commands/task')(dir, args, flags) },
|
|
13
12
|
{ name: 'clean', run: (dir, args, flags) => require('./commands/clean')(dir, args, flags) },
|
package/src/commands/help.js
CHANGED
|
@@ -17,8 +17,7 @@ DraftGo Next frontend baseline: React + Vite.
|
|
|
17
17
|
Database pages may use native web technologies or bundled component libraries.
|
|
18
18
|
MCP handles live discovery and structured resources; complete page, navigation,
|
|
19
19
|
document, and custom-service bodies use checkout/commit outside MCP context.
|
|
20
|
-
The
|
|
21
|
-
it is a CLI orchestration command, not an MCP tool.
|
|
20
|
+
The installed Skill routes agents to task-specific References and MCP tools.
|
|
22
21
|
|
|
23
22
|
Usage:
|
|
24
23
|
draftgo init [<target>...] Install the DraftGo Skill. No target = detect.
|
|
@@ -37,9 +36,6 @@ Usage:
|
|
|
37
36
|
draftgo mcp test Test project, resource, and API MCP calls.
|
|
38
37
|
draftgo mcp serve Bridge local stdio to the remote /mcp endpoint.
|
|
39
38
|
|
|
40
|
-
draftgo context --task <task> Read exact task Reference sections, then reuse
|
|
41
|
-
one MCP session to query project, resources,
|
|
42
|
-
APIs, and data structures in parallel.
|
|
43
39
|
draftgo map Read remote overview/resources through MCP and
|
|
44
40
|
overlay local checkout state.
|
|
45
41
|
draftgo checkout <type> <id...> Download pages/nav/docs body + verified base.
|
|
@@ -110,7 +106,7 @@ Important flags:
|
|
|
110
106
|
--mcp-url <url> (connect) Explicit endpoint; standard /mcp derives the
|
|
111
107
|
base URL; custom paths also need --server.
|
|
112
108
|
--token <sat> (connect) SAT for non-interactive use.
|
|
113
|
-
--timeout <ms> (connect/mcp test
|
|
109
|
+
--timeout <ms> (connect/mcp test) Network timeout.
|
|
114
110
|
--allow-offline (connect) Save only after explicitly accepting a
|
|
115
111
|
failed MCP validation.
|
|
116
112
|
--no-mcp-setup (connect) Do not write host MCP configuration.
|
|
@@ -121,8 +117,6 @@ Important flags:
|
|
|
121
117
|
--local-dev (init) Continue into local Docker setup.
|
|
122
118
|
--no-setup (init) Install the Skill without either setup flow.
|
|
123
119
|
--output json Print machine-readable output where supported.
|
|
124
|
-
--task <task> (context) frontend | data | custom-service | aihub |
|
|
125
|
-
content | project.
|
|
126
120
|
--type <type> (map) pages | nav/navigations | docs/articles.
|
|
127
121
|
--strict Treat check warnings as failures.
|
|
128
122
|
--remote (check) Compare checkout hashes/versions with remote.
|
|
@@ -167,7 +161,6 @@ Examples:
|
|
|
167
161
|
draftgo init codex
|
|
168
162
|
draftgo connect codex --server https://draftgo.example --token <sat>
|
|
169
163
|
draftgo mcp test
|
|
170
|
-
draftgo context --task frontend --output json
|
|
171
164
|
draftgo map --output json
|
|
172
165
|
draftgo checkout pages 42
|
|
173
166
|
draftgo check --strict
|
package/src/mcp/client.js
CHANGED
|
@@ -65,6 +65,44 @@ class McpRpcError extends Error {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
const SESSION_INVALID_CODES = new Set([
|
|
69
|
+
-32002,
|
|
70
|
+
'SESSION_EXPIRED',
|
|
71
|
+
'SESSION_NOT_FOUND',
|
|
72
|
+
'MCP_SESSION_EXPIRED',
|
|
73
|
+
'MCP_SESSION_NOT_FOUND',
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
function isSessionInvalidError(error) {
|
|
77
|
+
if (!error) return false;
|
|
78
|
+
if (Number(error.status) === 404) return true;
|
|
79
|
+
const code = error.code != null
|
|
80
|
+
? error.code
|
|
81
|
+
: error.rpc && error.rpc.error && error.rpc.error.code;
|
|
82
|
+
if (SESSION_INVALID_CODES.has(code)) return true;
|
|
83
|
+
const message = String(error.message
|
|
84
|
+
|| error.rpc && error.rpc.error && error.rpc.error.message
|
|
85
|
+
|| '');
|
|
86
|
+
return /\b(?:mcp\s+)?session(?:\s+id)?\s+(?:was\s+)?(?:not\s+found|expired|invalid|unknown|lost)\b/i.test(message)
|
|
87
|
+
|| /\b(?:server|mcp)\s+(?:is\s+)?(?:not initialized|uninitialized)\b/i.test(message)
|
|
88
|
+
|| /\binitialize first\b/i.test(message);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function requestIdKeys(message) {
|
|
92
|
+
const messages = Array.isArray(message) ? message : [message];
|
|
93
|
+
return new Set(messages
|
|
94
|
+
.filter((item) => isObject(item) && typeof item.method === 'string' && item.id != null)
|
|
95
|
+
.map((item) => `${typeof item.id}:${JSON.stringify(item.id)}`));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sessionInvalidResponse(message, expectedIds) {
|
|
99
|
+
const messages = Array.isArray(message) ? message : [message];
|
|
100
|
+
return messages.find((item) => isObject(item)
|
|
101
|
+
&& item.error
|
|
102
|
+
&& expectedIds.has(`${typeof item.id}:${JSON.stringify(item.id)}`)
|
|
103
|
+
&& isSessionInvalidError(item.error)) || null;
|
|
104
|
+
}
|
|
105
|
+
|
|
68
106
|
function diagnosticNextCursor(result) {
|
|
69
107
|
const value = diagnosticData(result);
|
|
70
108
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
@@ -154,6 +192,9 @@ class DraftGoMcpClient {
|
|
|
154
192
|
this.sessionId = null;
|
|
155
193
|
this.protocolVersion = options.protocolVersion || DEFAULT_PROTOCOL_VERSION;
|
|
156
194
|
this.nextId = 1;
|
|
195
|
+
this.sessionGeneration = 0;
|
|
196
|
+
this.sessionRecovery = null;
|
|
197
|
+
this.sessionRecoveryCount = 0;
|
|
157
198
|
this.onMessage = typeof options.onMessage === 'function' ? options.onMessage : null;
|
|
158
199
|
}
|
|
159
200
|
|
|
@@ -163,28 +204,65 @@ class DraftGoMcpClient {
|
|
|
163
204
|
}
|
|
164
205
|
|
|
165
206
|
const initialize = findInitializeRequest(message);
|
|
207
|
+
const internalRecovery = options._sessionRecoveryInternal === true;
|
|
208
|
+
if (!initialize && !internalRecovery && this.sessionRecovery) {
|
|
209
|
+
await this.sessionRecovery;
|
|
210
|
+
}
|
|
211
|
+
if (initialize) this.sessionId = null;
|
|
166
212
|
const requestedVersion = initialize
|
|
167
213
|
&& initialize.params
|
|
168
214
|
&& initialize.params.protocolVersion;
|
|
169
215
|
const delivered = [];
|
|
216
|
+
const expectedIds = requestIdKeys(message);
|
|
217
|
+
const attemptedSessionId = initialize ? null : this.sessionId;
|
|
218
|
+
const attemptedGeneration = this.sessionGeneration;
|
|
219
|
+
const canRecover = !initialize
|
|
220
|
+
&& !internalRecovery
|
|
221
|
+
&& options._sessionRecoveryAttempted !== true
|
|
222
|
+
&& attemptedSessionId != null;
|
|
223
|
+
let suppressedSessionError = null;
|
|
170
224
|
|
|
171
225
|
try {
|
|
172
226
|
await postJsonRpc(this.config, message, {
|
|
173
227
|
signal: options.signal,
|
|
174
228
|
timeoutMs: options.timeoutMs,
|
|
175
|
-
sessionId:
|
|
229
|
+
sessionId: attemptedSessionId,
|
|
176
230
|
protocolVersion: requestedVersion || this.protocolVersion,
|
|
177
|
-
onSession: (sessionId) => {
|
|
231
|
+
onSession: (sessionId) => {
|
|
232
|
+
if (initialize || (this.sessionGeneration === attemptedGeneration
|
|
233
|
+
&& this.sessionId === attemptedSessionId)) {
|
|
234
|
+
this.sessionId = sessionId;
|
|
235
|
+
}
|
|
236
|
+
},
|
|
178
237
|
onMessage: (remoteMessage) => {
|
|
179
238
|
const safe = redactValue(remoteMessage, this.secrets);
|
|
180
239
|
delivered.push(safe);
|
|
240
|
+
const invalid = canRecover && sessionInvalidResponse(safe, expectedIds);
|
|
241
|
+
if (invalid) {
|
|
242
|
+
suppressedSessionError = invalid;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (options._suppressCallbacks === true) return;
|
|
181
246
|
if (this.onMessage) this.onMessage(safe);
|
|
182
247
|
if (typeof options.onMessage === 'function') options.onMessage(safe);
|
|
183
248
|
},
|
|
184
249
|
});
|
|
250
|
+
if (suppressedSessionError) {
|
|
251
|
+
const remoteError = suppressedSessionError.error;
|
|
252
|
+
const error = new McpRpcError(
|
|
253
|
+
remoteError.message || `MCP error ${remoteError.code}`,
|
|
254
|
+
{ code: remoteError.code, data: remoteError.data, id: suppressedSessionError.id },
|
|
255
|
+
);
|
|
256
|
+
error.rpc = suppressedSessionError;
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
185
259
|
} catch (error) {
|
|
186
260
|
if (error && error.rpc) error.rpc = redactValue(error.rpc, this.secrets);
|
|
187
261
|
if (error && error.message) error.message = redactText(error.message, this.secrets);
|
|
262
|
+
if (canRecover && isSessionInvalidError(error)) {
|
|
263
|
+
await this.recoverSession(attemptedSessionId, attemptedGeneration, options);
|
|
264
|
+
return this.forward(message, { ...options, _sessionRecoveryAttempted: true });
|
|
265
|
+
}
|
|
188
266
|
throw error;
|
|
189
267
|
}
|
|
190
268
|
|
|
@@ -196,6 +274,54 @@ class DraftGoMcpClient {
|
|
|
196
274
|
return delivered;
|
|
197
275
|
}
|
|
198
276
|
|
|
277
|
+
async recoverSession(failedSessionId, failedGeneration, options = {}) {
|
|
278
|
+
if (this.sessionGeneration !== failedGeneration) return;
|
|
279
|
+
if (this.sessionId != null && this.sessionId !== failedSessionId) return;
|
|
280
|
+
if (this.sessionRecovery) return this.sessionRecovery;
|
|
281
|
+
|
|
282
|
+
const recoveryNumber = this.sessionRecoveryCount + 1;
|
|
283
|
+
this.sessionId = null;
|
|
284
|
+
const recovery = (async () => {
|
|
285
|
+
if (typeof options.onSessionRecovery === 'function') {
|
|
286
|
+
options.onSessionRecovery('started', { attempt: recoveryNumber });
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
await this.initialize({
|
|
290
|
+
id: `draftgo-session-recovery-${recoveryNumber}`,
|
|
291
|
+
protocolVersion: this.protocolVersion,
|
|
292
|
+
timeoutMs: options.timeoutMs,
|
|
293
|
+
signal: options.signal,
|
|
294
|
+
_sessionRecoveryInternal: true,
|
|
295
|
+
_suppressCallbacks: true,
|
|
296
|
+
});
|
|
297
|
+
this.sessionRecoveryCount = recoveryNumber;
|
|
298
|
+
if (typeof options.onSessionRecovery === 'function') {
|
|
299
|
+
options.onSessionRecovery('succeeded', { attempt: recoveryNumber });
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
this.sessionId = null;
|
|
303
|
+
if (typeof options.onSessionRecovery === 'function') {
|
|
304
|
+
options.onSessionRecovery('failed', { attempt: recoveryNumber });
|
|
305
|
+
}
|
|
306
|
+
const message = redactText(error && error.message ? error.message : error, this.secrets);
|
|
307
|
+
const wrapped = new McpRpcError(`DraftGo MCP session recovery failed: ${message}`, {
|
|
308
|
+
code: error && error.code != null ? error.code : -32000,
|
|
309
|
+
data: error && error.data,
|
|
310
|
+
});
|
|
311
|
+
wrapped.status = Number(error && error.status) || 0;
|
|
312
|
+
wrapped.sessionRecovery = true;
|
|
313
|
+
wrapped.cause = error;
|
|
314
|
+
throw wrapped;
|
|
315
|
+
}
|
|
316
|
+
})();
|
|
317
|
+
this.sessionRecovery = recovery;
|
|
318
|
+
try {
|
|
319
|
+
await recovery;
|
|
320
|
+
} finally {
|
|
321
|
+
if (this.sessionRecovery === recovery) this.sessionRecovery = null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
199
325
|
async request(method, params, options = {}) {
|
|
200
326
|
const id = options.id == null ? this.nextId++ : options.id;
|
|
201
327
|
const message = { jsonrpc: '2.0', id, method };
|
|
@@ -249,6 +375,7 @@ class DraftGoMcpClient {
|
|
|
249
375
|
throw error;
|
|
250
376
|
}
|
|
251
377
|
if (typeof options.onStage === 'function') options.onStage('initialized', 'succeeded');
|
|
378
|
+
this.sessionGeneration += 1;
|
|
252
379
|
return result;
|
|
253
380
|
}
|
|
254
381
|
|
|
@@ -425,6 +552,7 @@ class DraftGoMcpClient {
|
|
|
425
552
|
initialized,
|
|
426
553
|
protocolVersion: this.protocolVersion,
|
|
427
554
|
sessionId: this.sessionId,
|
|
555
|
+
sessionRecoveries: this.sessionRecoveryCount,
|
|
428
556
|
tools,
|
|
429
557
|
testedTool: tested[0].name,
|
|
430
558
|
toolResult: tested[0].result,
|
|
@@ -457,6 +585,7 @@ module.exports = {
|
|
|
457
585
|
diagnosticData,
|
|
458
586
|
diagnosticNextCursor,
|
|
459
587
|
findDBMetaListOperation,
|
|
588
|
+
isSessionInvalidError,
|
|
460
589
|
redactValue,
|
|
461
590
|
testConnection,
|
|
462
591
|
callTool,
|
package/src/commands/context.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const log = require('../logger');
|
|
4
|
-
const { collectContext, compactContext, TASK_PROFILES } = require('../context');
|
|
5
|
-
const { parseTimeout } = require('../timeout');
|
|
6
|
-
|
|
7
|
-
function printUsage() {
|
|
8
|
-
log.err('Usage: draftgo context --task <task> --output json');
|
|
9
|
-
log.dim(` tasks: ${Object.keys(TASK_PROFILES).join(', ')}`);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function contextCommand(projectDir, positional = [], flags = {}) {
|
|
13
|
-
if (positional.length || !flags.task || (flags.output && flags.output !== 'json')) {
|
|
14
|
-
printUsage();
|
|
15
|
-
return 1;
|
|
16
|
-
}
|
|
17
|
-
const result = await collectContext(projectDir, flags.task, {
|
|
18
|
-
timeoutMs: parseTimeout(flags.timeout),
|
|
19
|
-
});
|
|
20
|
-
console.log(JSON.stringify(flags.compact ? compactContext(result) : result, null, 2));
|
|
21
|
-
return 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
contextCommand.printUsage = printUsage;
|
|
25
|
-
contextCommand.parseTimeout = parseTimeout;
|
|
26
|
-
|
|
27
|
-
module.exports = contextCommand;
|
package/src/context/index.js
DELETED
|
@@ -1,662 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const pkg = require('../../package.json');
|
|
7
|
-
const { RESOURCES_DIR } = require('../paths');
|
|
8
|
-
const { loadProjectConfig } = require('../projectConfig');
|
|
9
|
-
const { TOOL_NAMES, openToolSession } = require('../mcp/tools');
|
|
10
|
-
const { readInstalledVersion } = require('../skill');
|
|
11
|
-
|
|
12
|
-
const SKILL_ROOT = path.join(RESOURCES_DIR, 'skill');
|
|
13
|
-
const MAX_STRUCTURED_TEXT_BYTES = 1024 * 1024;
|
|
14
|
-
const LONG_CONTENT_RESOURCE_TYPES = Object.freeze([
|
|
15
|
-
'pages',
|
|
16
|
-
'navigations',
|
|
17
|
-
'docs/articles',
|
|
18
|
-
'custom_services',
|
|
19
|
-
]);
|
|
20
|
-
const DB_META_LIST_PATH = '/api/db-meta';
|
|
21
|
-
const DB_META_PAGE_SIZE = 100;
|
|
22
|
-
|
|
23
|
-
const TASK_PROFILES = Object.freeze({
|
|
24
|
-
frontend: {
|
|
25
|
-
apiQueries: [
|
|
26
|
-
{ resource_type: 'pages' },
|
|
27
|
-
{ resource_type: 'navigations' },
|
|
28
|
-
{ resource_type: 'db' },
|
|
29
|
-
],
|
|
30
|
-
references: [
|
|
31
|
-
{
|
|
32
|
-
file: 'references/architecture.md',
|
|
33
|
-
headings: ['核心概念(30 秒)', 'App 对象是什么(1 分钟)', '技术栈(30 秒)'],
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
file: 'references/modules.md',
|
|
37
|
-
headings: ['可开发模块(开发者负责实现)', '平台内置模块(开箱即用,不需实现)'],
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
file: 'references/frontend.md',
|
|
41
|
-
headings: [
|
|
42
|
-
'页面开发强制规则',
|
|
43
|
-
'页面实践经验',
|
|
44
|
-
'必须 / 禁止',
|
|
45
|
-
'前端底座与组件库清单',
|
|
46
|
-
'本地静态资源清单',
|
|
47
|
-
'GSAP 动效规范',
|
|
48
|
-
'颜色 Token(强制)',
|
|
49
|
-
'导航栏开发',
|
|
50
|
-
'退出登录',
|
|
51
|
-
'加载体验',
|
|
52
|
-
'选择器与表单体验',
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
file: 'references/runtime.md',
|
|
57
|
-
headings: [
|
|
58
|
-
'iframe 注入机制',
|
|
59
|
-
'App 对象来源',
|
|
60
|
-
'URL 参数读取(标准三阶回落)',
|
|
61
|
-
'全局事件',
|
|
62
|
-
'前端全局层',
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
file: 'references/app-api.md',
|
|
67
|
-
headings: ['请求', '反馈', '路由', '状态', '主题', '其他'],
|
|
68
|
-
},
|
|
69
|
-
{ file: 'references/mcp.md', headings: ['API 发现与调用'] },
|
|
70
|
-
{
|
|
71
|
-
file: 'references/checkout.md',
|
|
72
|
-
headings: ['适用范围', '命令', 'Commit 流程', '409 / 412 冲突'],
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
data: {
|
|
77
|
-
apiQueries: [{ resource_type: 'db' }],
|
|
78
|
-
references: [
|
|
79
|
-
{
|
|
80
|
-
file: 'references/modules.md',
|
|
81
|
-
headings: ['可开发模块(开发者负责实现)', '模块选型决策'],
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
file: 'references/data.md',
|
|
85
|
-
headings: [
|
|
86
|
-
'DB Meta 结构',
|
|
87
|
-
'关联关系(ref)',
|
|
88
|
-
'CRUD 操作范式',
|
|
89
|
-
'filters 操作符',
|
|
90
|
-
'db_meta 实时契约',
|
|
91
|
-
'通用筛选参数(非动态 DB)',
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
{ file: 'references/mcp.md', headings: ['API 发现与调用', '协议与安全'] },
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
|
-
'custom-service': {
|
|
98
|
-
apiQueries: [{ resource_type: 'custom_scripts' }],
|
|
99
|
-
references: [
|
|
100
|
-
{
|
|
101
|
-
file: 'references/modules.md',
|
|
102
|
-
headings: ['可开发模块(开发者负责实现)', '模块选型决策', '自定义服务边界'],
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
file: 'references/custom-services.md',
|
|
106
|
-
headings: [
|
|
107
|
-
'最小服务',
|
|
108
|
-
'资源读写',
|
|
109
|
-
'触发器',
|
|
110
|
-
'平台 SDK',
|
|
111
|
-
'权限与运行限制',
|
|
112
|
-
'实时 API',
|
|
113
|
-
'验收清单',
|
|
114
|
-
],
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
file: 'references/data.md',
|
|
118
|
-
headings: ['自定义服务内的 draftgo.DB.Query', 'filters 操作符'],
|
|
119
|
-
},
|
|
120
|
-
{ file: 'references/mcp.md', headings: ['API 发现与调用', '协议与安全'] },
|
|
121
|
-
],
|
|
122
|
-
},
|
|
123
|
-
aihub: {
|
|
124
|
-
apiQueries: [
|
|
125
|
-
{ resource_type: 'aihub' },
|
|
126
|
-
{ resource_type: 'agents' },
|
|
127
|
-
],
|
|
128
|
-
references: [
|
|
129
|
-
{
|
|
130
|
-
file: 'references/aihub.md',
|
|
131
|
-
headings: ['条目骨架', '`data.spec` 字段地图', '观测'],
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
file: 'references/chat-sdk.md',
|
|
135
|
-
headings: [
|
|
136
|
-
'最小接入',
|
|
137
|
-
'协议',
|
|
138
|
-
'配置与布局',
|
|
139
|
-
'JavaScript API 与事件',
|
|
140
|
-
'历史与会话',
|
|
141
|
-
'鉴权与安全',
|
|
142
|
-
],
|
|
143
|
-
},
|
|
144
|
-
{ file: 'references/mcp.md', headings: ['API 发现与调用', '协议与安全'] },
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
content: {
|
|
148
|
-
apiQueries: [
|
|
149
|
-
{ resource_type: 'pages' },
|
|
150
|
-
{ resource_type: 'navigations' },
|
|
151
|
-
{ resource_type: 'docs/articles' },
|
|
152
|
-
],
|
|
153
|
-
references: [
|
|
154
|
-
{ file: 'references/architecture.md', headings: ['核心概念(30 秒)'] },
|
|
155
|
-
{ file: 'references/modules.md', headings: ['可开发模块(开发者负责实现)'] },
|
|
156
|
-
{
|
|
157
|
-
file: 'references/checkout.md',
|
|
158
|
-
headings: ['适用范围', '命令', 'Checkout 流程', 'Commit 流程', '409 / 412 冲突'],
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
},
|
|
162
|
-
project: {
|
|
163
|
-
apiQueries: [
|
|
164
|
-
{ resource_type: 'pages' },
|
|
165
|
-
{ resource_type: 'navigations' },
|
|
166
|
-
{ resource_type: 'docs/articles' },
|
|
167
|
-
{ resource_type: 'db' },
|
|
168
|
-
],
|
|
169
|
-
references: [
|
|
170
|
-
{
|
|
171
|
-
file: 'references/architecture.md',
|
|
172
|
-
headings: ['核心概念(30 秒)', '为什么这样设计(1 分钟)', '技术栈(30 秒)'],
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
file: 'references/modules.md',
|
|
176
|
-
headings: [
|
|
177
|
-
'可开发模块(开发者负责实现)',
|
|
178
|
-
'平台内置模块(开箱即用,不需实现)',
|
|
179
|
-
'模块选型决策',
|
|
180
|
-
'自定义服务边界',
|
|
181
|
-
],
|
|
182
|
-
},
|
|
183
|
-
{ file: 'references/mcp.md', headings: ['边界', 'API 发现与调用', '协议与安全'] },
|
|
184
|
-
{ file: 'references/checkout.md', headings: ['适用范围'] },
|
|
185
|
-
],
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const TASK_ALIASES = Object.freeze({
|
|
190
|
-
backend: 'custom-service',
|
|
191
|
-
service: 'custom-service',
|
|
192
|
-
ui: 'frontend',
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
function normalizeTask(value) {
|
|
196
|
-
const requested = String(value || '').trim().toLowerCase();
|
|
197
|
-
const task = Object.prototype.hasOwnProperty.call(TASK_ALIASES, requested)
|
|
198
|
-
? TASK_ALIASES[requested]
|
|
199
|
-
: requested;
|
|
200
|
-
if (!Object.prototype.hasOwnProperty.call(TASK_PROFILES, task)) {
|
|
201
|
-
const supported = Object.keys(TASK_PROFILES).join(', ');
|
|
202
|
-
throw new Error(`Unsupported context task: ${requested || '(empty)'}. Supported tasks: ${supported}.`);
|
|
203
|
-
}
|
|
204
|
-
return task;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function lineNumberAt(source, offset) {
|
|
208
|
-
let line = 1;
|
|
209
|
-
for (let index = 0; index < offset; index += 1) {
|
|
210
|
-
if (source.charCodeAt(index) === 10) line += 1;
|
|
211
|
-
}
|
|
212
|
-
return line;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function sectionEndLine(content, startLine) {
|
|
216
|
-
let newlines = 0;
|
|
217
|
-
for (let index = 0; index < content.length; index += 1) {
|
|
218
|
-
if (content.charCodeAt(index) === 10) newlines += 1;
|
|
219
|
-
}
|
|
220
|
-
return startLine + newlines - (content.endsWith('\n') ? 1 : 0);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function extractSections(source, requestedHeadings, sourcePath) {
|
|
224
|
-
const matches = [...source.matchAll(/^## ([^\r\n]+)\r?$/gm)];
|
|
225
|
-
const wanted = requestedHeadings ? new Set(requestedHeadings) : null;
|
|
226
|
-
const sections = [];
|
|
227
|
-
|
|
228
|
-
if (matches.length && matches[0].index > 0) {
|
|
229
|
-
const content = source.slice(0, matches[0].index);
|
|
230
|
-
const title = content.match(/^# ([^\r\n]+)\r?$/m);
|
|
231
|
-
sections.push({
|
|
232
|
-
source: {
|
|
233
|
-
kind: 'bundled_reference',
|
|
234
|
-
path: sourcePath,
|
|
235
|
-
heading: title ? title[1] : '(preamble)',
|
|
236
|
-
line_start: 1,
|
|
237
|
-
line_end: sectionEndLine(content, 1),
|
|
238
|
-
sha256: crypto.createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
239
|
-
},
|
|
240
|
-
content,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
for (let index = 0; index < matches.length; index += 1) {
|
|
245
|
-
const match = matches[index];
|
|
246
|
-
const heading = match[1];
|
|
247
|
-
if (wanted && !wanted.has(heading)) continue;
|
|
248
|
-
const start = match.index;
|
|
249
|
-
const end = index + 1 < matches.length ? matches[index + 1].index : source.length;
|
|
250
|
-
const content = source.slice(start, end);
|
|
251
|
-
const startLine = lineNumberAt(source, start);
|
|
252
|
-
sections.push({
|
|
253
|
-
source: {
|
|
254
|
-
kind: 'bundled_reference',
|
|
255
|
-
path: sourcePath,
|
|
256
|
-
heading,
|
|
257
|
-
line_start: startLine,
|
|
258
|
-
line_end: sectionEndLine(content, startLine),
|
|
259
|
-
sha256: crypto.createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
260
|
-
},
|
|
261
|
-
content,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (wanted) {
|
|
266
|
-
const found = new Set(matches.map((match) => match[1]));
|
|
267
|
-
const missing = requestedHeadings.filter((heading) => !found.has(heading));
|
|
268
|
-
if (missing.length) {
|
|
269
|
-
throw new Error(`${sourcePath} is missing required sections: ${missing.join(', ')}.`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
if (!sections.length) throw new Error(`${sourcePath} contains no selectable reference sections.`);
|
|
273
|
-
return sections;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function readTaskReferences(taskValue, options = {}) {
|
|
277
|
-
const task = normalizeTask(taskValue);
|
|
278
|
-
const skillRoot = options.skillRoot || SKILL_ROOT;
|
|
279
|
-
return TASK_PROFILES[task].references.flatMap((entry) => {
|
|
280
|
-
const absolute = path.join(skillRoot, entry.file);
|
|
281
|
-
const source = fs.readFileSync(absolute, 'utf8');
|
|
282
|
-
const sourcePath = path.posix.join('resources/skill', entry.file.replace(/\\/g, '/'));
|
|
283
|
-
return extractSections(source, entry.headings, sourcePath);
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function rawQuery(session, expectedName, args, server, options = {}) {
|
|
288
|
-
const tool = session.names[expectedName] || expectedName;
|
|
289
|
-
return session.client.toolsCall(tool, args, options).then((result) => ({
|
|
290
|
-
source: {
|
|
291
|
-
kind: 'mcp_tool',
|
|
292
|
-
server,
|
|
293
|
-
tool,
|
|
294
|
-
canonical_tool: expectedName,
|
|
295
|
-
arguments: args,
|
|
296
|
-
fetched_at: new Date().toISOString(),
|
|
297
|
-
},
|
|
298
|
-
result,
|
|
299
|
-
}));
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function structuredValueFromRaw(result) {
|
|
303
|
-
let value = result;
|
|
304
|
-
if (value && value.result) value = value.result;
|
|
305
|
-
if (value && value.structuredContent) value = value.structuredContent;
|
|
306
|
-
else if (value && value.structured_content) value = value.structured_content;
|
|
307
|
-
else if (value && Array.isArray(value.content)) {
|
|
308
|
-
const text = value.content.find((part) =>
|
|
309
|
-
part && part.type === 'text' && typeof part.text === 'string');
|
|
310
|
-
if (text) {
|
|
311
|
-
if (Buffer.byteLength(text.text, 'utf8') > MAX_STRUCTURED_TEXT_BYTES) {
|
|
312
|
-
throw new Error('DraftGo MCP pagination result exceeded the structured JSON limit.');
|
|
313
|
-
}
|
|
314
|
-
try {
|
|
315
|
-
value = JSON.parse(text.text);
|
|
316
|
-
} catch {
|
|
317
|
-
throw new Error('DraftGo MCP pagination result did not contain structured JSON.');
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (value && value.data) value = value.data;
|
|
322
|
-
if (value && Object.prototype.hasOwnProperty.call(value, 'value')) value = value.value;
|
|
323
|
-
return value;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function nextCursorFromRaw(result) {
|
|
327
|
-
const value = structuredValueFromRaw(result);
|
|
328
|
-
if (!value || typeof value !== 'object') return null;
|
|
329
|
-
const hasMore = value.has_more === true || value.hasMore === true;
|
|
330
|
-
let cursor = null;
|
|
331
|
-
let hasCursor = false;
|
|
332
|
-
if (Object.prototype.hasOwnProperty.call(value, 'next_cursor')) {
|
|
333
|
-
cursor = value.next_cursor;
|
|
334
|
-
hasCursor = true;
|
|
335
|
-
} else if (Object.prototype.hasOwnProperty.call(value, 'nextCursor')) {
|
|
336
|
-
cursor = value.nextCursor;
|
|
337
|
-
hasCursor = true;
|
|
338
|
-
} else if (hasMore && Object.prototype.hasOwnProperty.call(value, 'cursor')) {
|
|
339
|
-
cursor = value.cursor;
|
|
340
|
-
hasCursor = true;
|
|
341
|
-
}
|
|
342
|
-
if (hasMore && (!hasCursor || cursor == null || cursor === '')) {
|
|
343
|
-
throw new Error('DraftGo MCP pagination reported more results without a cursor.');
|
|
344
|
-
}
|
|
345
|
-
return hasCursor ? cursor : null;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async function collectLongContentResources(session, server, options = {}) {
|
|
349
|
-
const sequences = await Promise.all(LONG_CONTENT_RESOURCE_TYPES.map((resourceType) =>
|
|
350
|
-
rawPagedQuery(
|
|
351
|
-
session,
|
|
352
|
-
TOOL_NAMES.resourceList,
|
|
353
|
-
{ resource_type: resourceType, limit: 100 },
|
|
354
|
-
server,
|
|
355
|
-
options,
|
|
356
|
-
)));
|
|
357
|
-
return {
|
|
358
|
-
source: {
|
|
359
|
-
kind: 'mcp_tool_batch',
|
|
360
|
-
server,
|
|
361
|
-
canonical_tool: TOOL_NAMES.resourceList,
|
|
362
|
-
arguments: LONG_CONTENT_RESOURCE_TYPES.map((resourceType) => ({
|
|
363
|
-
resource_type: resourceType,
|
|
364
|
-
limit: 100,
|
|
365
|
-
})),
|
|
366
|
-
},
|
|
367
|
-
pages: sequences.flatMap((sequence) => sequence.pages),
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function collectTaskAPIs(session, queries, server, options = {}) {
|
|
372
|
-
const argumentsList = queries.map((query) => ({ ...query, limit: 100 }));
|
|
373
|
-
const sequences = await Promise.all(argumentsList.map((args) =>
|
|
374
|
-
rawPagedQuery(session, TOOL_NAMES.apiSearch, args, server, options)));
|
|
375
|
-
return {
|
|
376
|
-
source: {
|
|
377
|
-
kind: 'mcp_tool_batch',
|
|
378
|
-
server,
|
|
379
|
-
canonical_tool: TOOL_NAMES.apiSearch,
|
|
380
|
-
arguments: argumentsList,
|
|
381
|
-
},
|
|
382
|
-
pages: sequences.flatMap((sequence) => sequence.pages),
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function itemsFromRawSequence(sequence) {
|
|
387
|
-
if (!sequence || !Array.isArray(sequence.pages)) return [];
|
|
388
|
-
return sequence.pages.flatMap((page) => {
|
|
389
|
-
const value = structuredValueFromRaw(page);
|
|
390
|
-
if (Array.isArray(value)) return value;
|
|
391
|
-
if (value && Array.isArray(value.items)) return value.items;
|
|
392
|
-
if (value && Array.isArray(value.operations)) return value.operations;
|
|
393
|
-
return [];
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function selectDBMetaListOperation(sequence) {
|
|
398
|
-
return itemsFromRawSequence(sequence).find((operation) => {
|
|
399
|
-
if (!operation || typeof operation !== 'object') return false;
|
|
400
|
-
const method = String(operation.method || '').trim().toUpperCase();
|
|
401
|
-
const resourceType = String(operation.resource_type || '').trim().toLowerCase();
|
|
402
|
-
const operationPath = String(operation.path || operation.path_template || '').trim();
|
|
403
|
-
return method === 'GET'
|
|
404
|
-
&& resourceType === 'db_meta'
|
|
405
|
-
&& operationPath === DB_META_LIST_PATH
|
|
406
|
-
&& operation.destructive !== true;
|
|
407
|
-
}) || null;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function optionalPageInteger(value, field, minimum) {
|
|
411
|
-
if (value == null) return null;
|
|
412
|
-
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
413
|
-
throw new Error(`DraftGo db_meta API returned invalid ${field}.`);
|
|
414
|
-
}
|
|
415
|
-
return value;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function dbMetaPageInfo(response) {
|
|
419
|
-
let value = structuredValueFromRaw(response);
|
|
420
|
-
if (value && value.response) value = value.response;
|
|
421
|
-
if (value && value.data) value = value.data;
|
|
422
|
-
if (!value || typeof value !== 'object' || !Array.isArray(value.items)) return null;
|
|
423
|
-
return {
|
|
424
|
-
items: value.items,
|
|
425
|
-
total: optionalPageInteger(value.total, 'total', 0),
|
|
426
|
-
page: optionalPageInteger(value.page, 'page', 1),
|
|
427
|
-
pageSize: optionalPageInteger(value.page_size, 'page_size', 1),
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
async function rawPagedQuery(session, expectedName, baseArgs, server, options = {}) {
|
|
432
|
-
const pages = [];
|
|
433
|
-
const seen = new Set();
|
|
434
|
-
const maxPages = options.maxPages == null ? 100 : Number(options.maxPages);
|
|
435
|
-
if (!Number.isSafeInteger(maxPages) || maxPages < 1) {
|
|
436
|
-
throw new TypeError('maxPages must be a positive integer.');
|
|
437
|
-
}
|
|
438
|
-
let cursor = null;
|
|
439
|
-
|
|
440
|
-
for (let page = 0; page < maxPages; page += 1) {
|
|
441
|
-
const args = cursor == null ? { ...baseArgs } : { ...baseArgs, cursor };
|
|
442
|
-
const response = await rawQuery(session, expectedName, args, server, options);
|
|
443
|
-
pages.push(response);
|
|
444
|
-
const next = nextCursorFromRaw(response.result);
|
|
445
|
-
if (next == null || next === '') {
|
|
446
|
-
return {
|
|
447
|
-
source: {
|
|
448
|
-
kind: 'mcp_tool_sequence',
|
|
449
|
-
server,
|
|
450
|
-
canonical_tool: expectedName,
|
|
451
|
-
arguments: baseArgs,
|
|
452
|
-
},
|
|
453
|
-
pages,
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
const key = String(next);
|
|
457
|
-
if (seen.has(key)) throw new Error(expectedName + ' repeated a cursor.');
|
|
458
|
-
seen.add(key);
|
|
459
|
-
cursor = next;
|
|
460
|
-
}
|
|
461
|
-
throw new Error(expectedName + ' exceeded ' + maxPages + ' pages.');
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
async function collectDBMetaResources(session, operationID, server, options = {}) {
|
|
465
|
-
const pages = [];
|
|
466
|
-
let fetchedItems = 0;
|
|
467
|
-
let reportedTotal = null;
|
|
468
|
-
const maxPages = options.maxPages == null ? 100 : Number(options.maxPages);
|
|
469
|
-
if (!Number.isSafeInteger(maxPages) || maxPages < 1) {
|
|
470
|
-
throw new TypeError('maxPages must be a positive integer.');
|
|
471
|
-
}
|
|
472
|
-
for (let page = 1; page <= maxPages; page += 1) {
|
|
473
|
-
const args = {
|
|
474
|
-
operation_id: operationID,
|
|
475
|
-
query: { page, page_size: DB_META_PAGE_SIZE },
|
|
476
|
-
};
|
|
477
|
-
const response = await rawQuery(session, TOOL_NAMES.apiCall, args, server, options);
|
|
478
|
-
pages.push(response);
|
|
479
|
-
const info = dbMetaPageInfo(response);
|
|
480
|
-
if (!info) {
|
|
481
|
-
return {
|
|
482
|
-
source: {
|
|
483
|
-
kind: 'mcp_tool_sequence',
|
|
484
|
-
server,
|
|
485
|
-
canonical_tool: TOOL_NAMES.apiCall,
|
|
486
|
-
operation_id: operationID,
|
|
487
|
-
},
|
|
488
|
-
pages,
|
|
489
|
-
pagination: { complete: false, reason: 'response_shape_unavailable' },
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
const responsePage = info.page || page;
|
|
493
|
-
const responsePageSize = info.pageSize || DB_META_PAGE_SIZE;
|
|
494
|
-
if (responsePage !== page) {
|
|
495
|
-
throw new Error(`DraftGo db_meta API returned page ${responsePage} while page ${page} was requested.`);
|
|
496
|
-
}
|
|
497
|
-
fetchedItems += info.items.length;
|
|
498
|
-
if (info.total != null) {
|
|
499
|
-
if (reportedTotal != null && info.total !== reportedTotal) {
|
|
500
|
-
throw new Error(`DraftGo db_meta API changed total from ${reportedTotal} to ${info.total} during pagination.`);
|
|
501
|
-
}
|
|
502
|
-
reportedTotal = info.total;
|
|
503
|
-
}
|
|
504
|
-
if (reportedTotal != null) {
|
|
505
|
-
if (fetchedItems >= reportedTotal) {
|
|
506
|
-
return {
|
|
507
|
-
source: {
|
|
508
|
-
kind: 'mcp_tool_sequence',
|
|
509
|
-
server,
|
|
510
|
-
canonical_tool: TOOL_NAMES.apiCall,
|
|
511
|
-
operation_id: operationID,
|
|
512
|
-
},
|
|
513
|
-
pages,
|
|
514
|
-
pagination: { complete: true, total: reportedTotal },
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
if (!info.items.length) {
|
|
518
|
-
throw new Error('DraftGo db_meta API ended before the reported total was reached.');
|
|
519
|
-
}
|
|
520
|
-
} else if (info.items.length < responsePageSize) {
|
|
521
|
-
return {
|
|
522
|
-
source: {
|
|
523
|
-
kind: 'mcp_tool_sequence',
|
|
524
|
-
server,
|
|
525
|
-
canonical_tool: TOOL_NAMES.apiCall,
|
|
526
|
-
operation_id: operationID,
|
|
527
|
-
},
|
|
528
|
-
pages,
|
|
529
|
-
pagination: { complete: true, total: null },
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
throw new Error(`DraftGo db_meta API exceeded ${maxPages} pages.`);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
async function collectDataStructures(session, server, options = {}) {
|
|
537
|
-
const api = await rawPagedQuery(
|
|
538
|
-
session,
|
|
539
|
-
TOOL_NAMES.apiSearch,
|
|
540
|
-
{ resource_type: 'db_meta', limit: 100 },
|
|
541
|
-
server,
|
|
542
|
-
options,
|
|
543
|
-
);
|
|
544
|
-
const operation = selectDBMetaListOperation(api);
|
|
545
|
-
if (!operation || !operation.operation_id) {
|
|
546
|
-
throw new Error('DraftGo MCP does not expose a read-only GET /api/db-meta operation.');
|
|
547
|
-
}
|
|
548
|
-
const operationID = String(operation.operation_id);
|
|
549
|
-
const [contract, resources] = await Promise.all([
|
|
550
|
-
rawQuery(
|
|
551
|
-
session,
|
|
552
|
-
TOOL_NAMES.apiDescribe,
|
|
553
|
-
{ operation_id: operationID },
|
|
554
|
-
server,
|
|
555
|
-
options,
|
|
556
|
-
),
|
|
557
|
-
collectDBMetaResources(session, operationID, server, options),
|
|
558
|
-
]);
|
|
559
|
-
return { api, contract, resources };
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
async function collectContext(projectDir, taskValue, options = {}) {
|
|
563
|
-
const task = normalizeTask(taskValue);
|
|
564
|
-
const profile = TASK_PROFILES[task];
|
|
565
|
-
const references = readTaskReferences(task, options);
|
|
566
|
-
const installedSkillVersion = readInstalledSkillVersion(projectDir);
|
|
567
|
-
const config = options.config || loadProjectConfig(projectDir);
|
|
568
|
-
const expected = [
|
|
569
|
-
TOOL_NAMES.projectOverview,
|
|
570
|
-
TOOL_NAMES.resourceList,
|
|
571
|
-
TOOL_NAMES.apiSearch,
|
|
572
|
-
TOOL_NAMES.apiDescribe,
|
|
573
|
-
TOOL_NAMES.apiCall,
|
|
574
|
-
];
|
|
575
|
-
const openSession = options.openToolSession || openToolSession;
|
|
576
|
-
const session = options.session || await openSession(config, expected, options);
|
|
577
|
-
const controller = new AbortController();
|
|
578
|
-
const externalSignal = options.signal;
|
|
579
|
-
const forwardAbort = () => controller.abort(externalSignal.reason);
|
|
580
|
-
if (externalSignal) {
|
|
581
|
-
if (externalSignal.aborted) forwardAbort();
|
|
582
|
-
else externalSignal.addEventListener('abort', forwardAbort, { once: true });
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
let project;
|
|
586
|
-
let resources;
|
|
587
|
-
let api;
|
|
588
|
-
let dataStructures;
|
|
589
|
-
try {
|
|
590
|
-
const queryOptions = { ...options, signal: controller.signal };
|
|
591
|
-
[project, resources, api, dataStructures] = await Promise.all([
|
|
592
|
-
rawQuery(session, TOOL_NAMES.projectOverview, {}, config.server, queryOptions),
|
|
593
|
-
collectLongContentResources(session, config.server, queryOptions),
|
|
594
|
-
collectTaskAPIs(session, profile.apiQueries, config.server, queryOptions),
|
|
595
|
-
collectDataStructures(session, config.server, queryOptions),
|
|
596
|
-
]);
|
|
597
|
-
} catch (error) {
|
|
598
|
-
if (!controller.signal.aborted) controller.abort(error);
|
|
599
|
-
throw error;
|
|
600
|
-
} finally {
|
|
601
|
-
if (externalSignal) externalSignal.removeEventListener('abort', forwardAbort);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
return {
|
|
605
|
-
schema_version: '2.0',
|
|
606
|
-
task,
|
|
607
|
-
bundled_references: {
|
|
608
|
-
source: 'draftgo-cli',
|
|
609
|
-
version: pkg.version,
|
|
610
|
-
installed_skill_version: installedSkillVersion,
|
|
611
|
-
synchronized: installedSkillVersion == null ? null : installedSkillVersion === pkg.version,
|
|
612
|
-
sections: references.map((entry) => ({
|
|
613
|
-
...entry,
|
|
614
|
-
source: { ...entry.source, source: 'cli_bundle', version: pkg.version },
|
|
615
|
-
})),
|
|
616
|
-
},
|
|
617
|
-
live_project: project,
|
|
618
|
-
live_resources: resources,
|
|
619
|
-
live_apis: api,
|
|
620
|
-
live_db_meta: dataStructures,
|
|
621
|
-
live_session: {
|
|
622
|
-
server: config.server,
|
|
623
|
-
protocol_version: session.client.protocolVersion,
|
|
624
|
-
fetched_at: new Date().toISOString(),
|
|
625
|
-
},
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function compactContext(value) {
|
|
630
|
-
if (Array.isArray(value)) return value.map(compactContext);
|
|
631
|
-
if (!value || typeof value !== 'object') return value;
|
|
632
|
-
if (value.source && Object.prototype.hasOwnProperty.call(value, 'result')) {
|
|
633
|
-
return { source: compactContext(value.source), value: structuredValueFromRaw(value.result) };
|
|
634
|
-
}
|
|
635
|
-
const result = {};
|
|
636
|
-
for (const [key, child] of Object.entries(value)) result[key] = compactContext(child);
|
|
637
|
-
return result;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function readInstalledSkillVersion(projectDir) {
|
|
641
|
-
return readInstalledVersion(projectDir);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
module.exports = {
|
|
645
|
-
TASK_PROFILES,
|
|
646
|
-
normalizeTask,
|
|
647
|
-
extractSections,
|
|
648
|
-
readTaskReferences,
|
|
649
|
-
structuredValueFromRaw,
|
|
650
|
-
nextCursorFromRaw,
|
|
651
|
-
rawPagedQuery,
|
|
652
|
-
collectLongContentResources,
|
|
653
|
-
collectTaskAPIs,
|
|
654
|
-
itemsFromRawSequence,
|
|
655
|
-
selectDBMetaListOperation,
|
|
656
|
-
dbMetaPageInfo,
|
|
657
|
-
collectDBMetaResources,
|
|
658
|
-
collectDataStructures,
|
|
659
|
-
readInstalledSkillVersion,
|
|
660
|
-
collectContext,
|
|
661
|
-
compactContext,
|
|
662
|
-
};
|