draftgo-cli 3.0.1 → 3.0.33

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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -124
  3. package/package.json +21 -8
  4. package/resources/skill/SKILL.md +62 -89
  5. package/resources/skill/init/SKILL.md +18 -67
  6. package/resources/skill/manifest.json +27 -0
  7. package/resources/skill/pull/SKILL.md +18 -44
  8. package/resources/skill/push/SKILL.md +30 -247
  9. package/resources/skill/references/aihub.md +86 -0
  10. package/resources/skill/references/api-endpoints.md +178 -0
  11. package/resources/skill/references/api.json +20248 -0
  12. package/resources/skill/{quickref → references}/app-api.md +44 -14
  13. package/resources/skill/{core → references}/architecture.md +6 -26
  14. package/resources/skill/references/chat-sdk.md +201 -0
  15. package/resources/skill/references/custom-services.md +308 -0
  16. package/resources/skill/references/data.md +298 -0
  17. package/resources/skill/references/db-relations.md +227 -0
  18. package/resources/skill/references/frontend.md +788 -0
  19. package/resources/skill/references/modules.md +66 -0
  20. package/resources/skill/references/parallel.md +48 -0
  21. package/resources/skill/{specs → references}/runtime.md +31 -1
  22. package/resources/skill/{specs → references}/security.md +3 -3
  23. package/resources/skill/references/ui-protocol.md +99 -0
  24. package/resources/skill/scripts/draftgo_delete.py +0 -2
  25. package/resources/skill/scripts/draftgo_init.py +15 -3
  26. package/resources/skill/scripts/draftgo_pull.py +154 -87
  27. package/resources/skill/scripts/draftgo_push.py +440 -183
  28. package/resources/skill/story/SKILL.md +13 -23
  29. package/src/cli.js +22 -7
  30. package/src/commandRegistry.js +34 -0
  31. package/src/commands/api.js +204 -0
  32. package/src/commands/autoPush.js +41 -0
  33. package/src/commands/check.js +27 -17
  34. package/src/commands/delete.js +6 -4
  35. package/src/commands/deploy.js +31 -0
  36. package/src/commands/help.js +41 -28
  37. package/src/commands/init.js +34 -20
  38. package/src/commands/local.js +9 -3
  39. package/src/commands/map.js +18 -7
  40. package/src/commands/sync.js +11 -4
  41. package/src/commands/update.js +39 -52
  42. package/src/commands/verifyUi.js +199 -0
  43. package/src/index.js +13 -46
  44. package/src/localdev/compose.js +48 -197
  45. package/src/localdev/index.js +116 -216
  46. package/src/localdev/mysqlClient.js +12 -9
  47. package/src/localdev/services.js +163 -0
  48. package/src/platforms.js +3 -3
  49. package/src/projectConfig.js +12 -2
  50. package/src/projectMap.js +240 -68
  51. package/src/skill.js +113 -29
  52. package/src/updateCheck.js +37 -15
  53. package/resources/skill/core/modules.md +0 -54
  54. package/resources/skill/practices/anti-patterns.md +0 -70
  55. package/resources/skill/practices/best-practices.md +0 -41
  56. package/resources/skill/practices/dev-declaration.md +0 -94
  57. package/resources/skill/quickref/api-endpoints.md +0 -130
  58. package/resources/skill/quickref/api.json +0 -17675
  59. package/resources/skill/quickref/dg-components.md +0 -198
  60. package/resources/skill/rules/dev-workflow.md +0 -652
  61. package/resources/skill/rules/frontend.md +0 -210
  62. package/resources/skill/rules/parallel.md +0 -263
  63. package/resources/skill/specs/data.md +0 -108
  64. package/resources/skill/specs/ui-protocol.md +0 -68
  65. package/src/commands/doctor.js +0 -54
  66. package/src/commands/new.js +0 -183
  67. package/src/commands/projectScript.js +0 -37
  68. /package/resources/skill/{rules → references}/debugging-syntax.md +0 -0
@@ -22,9 +22,11 @@ const App = window.parent?.App;
22
22
  **响应格式**:`{ code: 200, data: <载荷>, message: "success" }`
23
23
  **消费范式**:
24
24
  ```javascript
25
- const res = await App.get('pages', { page: 1, page_size: 20 });
25
+ const res = await App.get('pages'); // 不传分页参数时全量返回
26
26
  if (res.code !== 200) { App.showError(res.message); return; }
27
- const items = res.data.items; // 分页列表
27
+ const items = res.data.items;
28
+
29
+ const paged = await App.get('pages', { page: 1, page_size: 20 }); // 显式分页
28
30
  ```
29
31
 
30
32
  ## 反馈
@@ -80,18 +82,6 @@ App.setColorScheme('custom', customVarsObject); // 自定义配色
80
82
  App.getColorScheme(); // 获取方案详情
81
83
  ```
82
84
 
83
- ## 外部 API
84
-
85
- ```javascript
86
- App.listApis(); // 列出可调用 API(含 code/name/schema)
87
- App.callApi('weather-now', {
88
- path_params: { city: 'beijing' },
89
- query_params: { unit: 'metric' },
90
- // body: {...} // POST/PUT/PATCH 才生效
91
- });
92
- // 返回:{ status_code, headers, body, duration_ms, error }
93
- ```
94
-
95
85
  ## 其他
96
86
 
97
87
  ```javascript
@@ -108,3 +98,43 @@ App.t(key, fallback?); // 国际化文本
108
98
  |---|---|
109
99
  | `localStorage.dg_access_token` | 访问 token |
110
100
  | `localStorage.dg_refresh_token` | 刷新 token |
101
+
102
+ ## AI 对话与兼容门面
103
+
104
+ 新页面的可见对话使用 `<dg-chat>` 或 `DraftGoChat.create()`。两者都由 `/assets/draftgo-chat.js` 注册,壳层不会默认注入:
105
+
106
+ ```html
107
+ <script src="/assets/draftgo-chat.js"></script>
108
+ <dg-chat protocol="draftgo-agent" agent-id="AGENT_ID"></dg-chat>
109
+ ```
110
+
111
+ 完整配置、协议、事件和安全约束见 `references/chat-sdk.md`。
112
+
113
+ ### `DraftGoAI` 旧兼容门面
114
+
115
+ `window.DraftGoAI` 由同一完整版脚本注入,用 Agent id 而非直连模型。它适合旧代码、无 UI 的轻量文本调用和图片生成;新页面不要用它重建对话界面。
116
+
117
+ | 方法 | 签名 | 说明 |
118
+ |---|---|---|
119
+ | `DraftGoAI.chat` | `(agentId, message, onDelta?, options?)` | 对话;**默认流式**,`onDelta(delta, full)` 每个增量触发一次,Promise resolve 完整文本 |
120
+ | `DraftGoAI.images` | `(agentId, prompt, options?)` | 图片模式 Agent,`POST /api/agents/{id}/images`,返回标准信封 |
121
+ | `DraftGoAI.getSelectableModels` | `(agentId)` | 返回 `{ user_selectable, models }` |
122
+
123
+ ```javascript
124
+ // 流式对话
125
+ const text = await DraftGoAI.chat(agentId, '你好', (delta, full) => render(full));
126
+
127
+ // 多轮:传 sessionId(→ body.session_id)复用同一会话;Agent 开启“持续对话”后服务端续写历史
128
+ await DraftGoAI.chat(agentId, '接着上一条', onDelta, { sessionId: threadKey });
129
+
130
+ // 用户选模型 + 覆盖模型
131
+ const { user_selectable, models } = await DraftGoAI.getSelectableModels(agentId);
132
+ await DraftGoAI.chat(agentId, '你好', onDelta, { model: models[0]?.id, sessionId: threadKey });
133
+
134
+ // 图片生成(不要用 chat)
135
+ const res = await DraftGoAI.images(agentId, '生成主图', { size: '1024x1024', n: 1 });
136
+ ```
137
+
138
+ **`options` 常用键**:`sessionId`(多轮会话键)、`model`(覆盖模型)、`stream`(默认 true)、`context`。
139
+ 不传 `sessionId` = 无状态单轮。富交互 UI(附件、模型选择器、推理展示、历史、重生成)用
140
+ `<dg-chat>`,见 `references/chat-sdk.md`;Agent 能力全景见 `references/aihub.md`。
@@ -28,27 +28,6 @@ DraftGo **不是传统 SPA**,是「数据库驱动的页面资产运行时」
28
28
 
29
29
  ---
30
30
 
31
- ## dg-* = shadcn(核心认知,最高优先级)
32
-
33
- ```
34
- dg-button → shadcn <Button>
35
- dg-card → shadcn <Card>
36
- dg-form → shadcn <Form>
37
- dg-table → shadcn <Table>
38
- dg-dialog → shadcn <Dialog>
39
- dg-sheet → shadcn <Sheet>
40
- dg-tabs → shadcn <Tabs>
41
- dg-dropdown-menu → shadcn <DropdownMenu>
42
- dg-tooltip → shadcn <Tooltip>
43
- dg-skeleton → shadcn <Skeleton>
44
- ```
45
-
46
- - `dg-*` = shadcn/ui 的 DraftGo HTML 协议表达,**不是** daisyUI / Bootstrap / 自研库
47
- - 数据库页面不进入 Vite/React 编译链,所以不能写 TSX;改用对应 `dg-*` 标签
48
- - 完整映射表见 `{{SKILL_DIR}}/specs/ui-protocol.md`
49
-
50
- ---
51
-
52
31
  ## App 对象是什么(1 分钟)
53
32
 
54
33
  壳层将能力对象赋值给 `window.App`,页面内通过 `window.parent.App` 访问:
@@ -57,7 +36,8 @@ dg-skeleton → shadcn <Skeleton>
57
36
  const App = window.parent?.App;
58
37
 
59
38
  // 请求(自动携带 token)
60
- await App.get('pages', { page: 1, page_size: 20 });
39
+ await App.get('pages'); // 全量列表
40
+ await App.get('pages', { page: 1, page_size: 20 }); // 分页列表
61
41
  await App.post('db/order', { data: { name: '张三' } });
62
42
 
63
43
  // 反馈
@@ -75,7 +55,7 @@ App.isAdmin // 是否管理员
75
55
  App.theme // 'light' | 'dark'
76
56
  ```
77
57
 
78
- 完整 API 速查表见 `{{SKILL_DIR}}/quickref/app-api.md`
58
+ 完整 API 速查表见 `{{SKILL_DIR}}/references/app-api.md`
79
59
 
80
60
  ---
81
61
 
@@ -83,9 +63,9 @@ App.theme // 'light' | 'dark'
83
63
 
84
64
  | 层 | 技术 |
85
65
  |---|---|
86
- | 后端 | FastAPI 0.115.0 · SQLAlchemy 2.0.36 · Pydantic 2.10.0 · MySQL · Redis |
87
- | 壳层前端 | React · Vite · shadcn/ui · Tailwind CSS |
88
- | 数据库页面 | 原生 HTML · `/assets/tailwindcss.js`(运行时)· FontAwesome · GSAP · 内置 SVG 图标库 |
66
+ | 后端 | Go 1.26 · 标准库 `net/http` · `database/sql`(go-sql-driver/mysql)· MySQL · Redis |
67
+ | 壳层前端 | React · Vite |
68
+ | 数据库页面 | 原生 HTML · `/assets/tailwindcss.js`(运行时)· FontAwesome · GSAP · html2canvas · 内置 SVG 图标库 |
89
69
  | CLI | Node.js(draftgo-cli) |
90
70
 
91
71
  数据库页面**不需要** npm install,也**不进入** React 编译,直接写 HTML + 本地资源路径。
@@ -0,0 +1,201 @@
1
+ ---
2
+ read_when: 页面需要 AI 对话 UI 时 · 使用 dg-chat 或 DraftGoChat 时 · 接入 Agent/OpenAI/Anthropic/custom transport 时
3
+ ---
4
+
5
+ # DraftGo Chat SDK
6
+
7
+ DraftGo 页面只使用完整版 `/assets/draftgo-chat.js`。它是原生 Web Component,不依赖 React、JSX 或 npm。不要为单个页面复制消息状态机、流解析、停止、重试或历史逻辑。
8
+
9
+ ## 目录
10
+
11
+ - [最小接入](#最小接入)
12
+ - [协议](#协议)
13
+ - [配置与布局](#配置与布局)
14
+ - [JavaScript API 与事件](#javascript-api-与事件)
15
+ - [历史与会话](#历史与会话)
16
+ - [扩展](#扩展)
17
+ - [鉴权与安全](#鉴权与安全)
18
+
19
+ ## 最小接入
20
+
21
+ 脚本不是壳层默认全局注入。使用 `<dg-chat>` 或兼容门面 `DraftGoAI` 前必须先加载:
22
+
23
+ ```html
24
+ <script src="/assets/draftgo-chat.js"></script>
25
+ <dg-chat protocol="draftgo-agent" agent-id="AGENT_ID"></dg-chat>
26
+ ```
27
+
28
+ `draftgo-agent` 默认请求 `/api/agents/{id}/chat`,并复用同源 DraftGo App 的 Bearer token 与刷新机制。
29
+
30
+ 需要 JavaScript 配置时:
31
+
32
+ ```html
33
+ <div id="chat-host"></div>
34
+ <script src="/assets/draftgo-chat.js"></script>
35
+ <script>
36
+ const chat = DraftGoChat.create('#chat-host', {
37
+ protocol: 'draftgo-agent',
38
+ agentId: 'AGENT_ID',
39
+ surface: 'inline',
40
+ view: 'conversation',
41
+ stream: true,
42
+ features: {
43
+ attachments: true,
44
+ reasoning: true,
45
+ history: true,
46
+ artifacts: true
47
+ }
48
+ });
49
+ </script>
50
+ ```
51
+
52
+ 新页面优先使用 `<dg-chat>` 或 `DraftGoChat.create()`。`DraftGoAI.chat()` 是同一脚本提供的旧代码兼容门面,内部创建隐藏 `<dg-chat>`,仅适合不需要对话 UI 的轻量文本调用。图片模式继续使用 `DraftGoAI.images()`。
53
+
54
+ ## 协议
55
+
56
+ | `protocol` | 必需配置 | 说明 |
57
+ |---|---|---|
58
+ | `draftgo-agent` | `agentId` | DraftGo Agent;默认端点 `/api/agents/{id}/chat` |
59
+ | `openai-chat`(alias `openai`) | `endpoint` | OpenAI Chat Completions 兼容协议 |
60
+ | `openai-responses` | `endpoint` | OpenAI Responses 协议 |
61
+ | `anthropic-messages`(alias `anthropic`) | `endpoint` | Anthropic Messages 协议 |
62
+ | `custom` 或注册名 | 已注册 transport | 页面提供 async generator transport |
63
+
64
+ 外部协议必须指向服务端代理:
65
+
66
+ ```html
67
+ <dg-chat protocol="openai-chat" endpoint="/api/ai-proxy/openai" model="MODEL"></dg-chat>
68
+ <dg-chat protocol="openai-responses" endpoint="/api/ai-proxy/responses" model="MODEL"></dg-chat>
69
+ <dg-chat protocol="anthropic-messages" endpoint="/api/ai-proxy/anthropic" model="MODEL"></dg-chat>
70
+ ```
71
+
72
+ 这些 `/api/ai-proxy/*` 是接入方实现的占位路由,不是 DraftGo 自动提供的默认代理。不要将供应商密钥或长期 token 写入页面。
73
+
74
+ 内置 transport 支持 SSE、NDJSON 和普通 JSON,并把上游响应归一化为 `text.delta`、`reasoning.delta`、`tool.start`、`tool.delta`、`tool.finish`、`artifact.complete`、`source`、`usage`、`message.finish`、`error` 等事件。
75
+
76
+ ## 配置与布局
77
+
78
+ 简单配置使用属性:
79
+
80
+ ```html
81
+ <dg-chat
82
+ id="page-assistant"
83
+ protocol="draftgo-agent"
84
+ agent-id="AGENT_ID"
85
+ surface="drawer"
86
+ view="threads"
87
+ position="right"
88
+ title="AI Assistant"
89
+ placeholder="输入内容"
90
+ persist="true"
91
+ stream="true"
92
+ enable-attachments
93
+ show-reasoning
94
+ ></dg-chat>
95
+ ```
96
+
97
+ | 配置 | 可选值 |
98
+ |---|---|
99
+ | `surface` | `inline`、`floating`、`drawer`、`fullscreen` |
100
+ | `view` | `conversation`、`threads`、`compact`、`workspace` |
101
+ | `position` | `left`、`right` |
102
+ | `theme` | `light`、`dark`;省略时跟随系统 |
103
+
104
+ 复杂的纯数据配置使用 `config-ref`:
105
+
106
+ ```html
107
+ <script type="application/json" id="page-chat-config">
108
+ {
109
+ "protocol": "draftgo-agent",
110
+ "agentId": "AGENT_ID",
111
+ "surface": "inline",
112
+ "view": "workspace",
113
+ "storageKey": "dg-chat:PAGE_KEY:INSTANCE_KEY",
114
+ "features": {
115
+ "reasoning": true,
116
+ "attachments": true,
117
+ "history": true,
118
+ "feedback": false,
119
+ "artifacts": true
120
+ },
121
+ "suggestions": ["总结当前页面", "生成结构化结果"],
122
+ "context": { "page": "PAGE_KEY" }
123
+ }
124
+ </script>
125
+ <dg-chat config-ref="page-chat-config"></dg-chat>
126
+ ```
127
+
128
+ 函数、DOM Node、renderer、plugin、upload 和 custom transport 只能通过 JavaScript 配置,不能写进 JSON。主题优先使用 `--dg-chat-*` CSS variables、公开 `::part()` 和 slots,不选择 Shadow DOM 内部 class。
129
+
130
+ ## JavaScript API 与事件
131
+
132
+ ```javascript
133
+ const chat = document.querySelector('#page-assistant');
134
+
135
+ chat.configure({ model: 'MODEL', context: { page: 'PAGE_KEY' } });
136
+ await chat.send('处理当前输入');
137
+ chat.stop();
138
+ chat.regenerate();
139
+ chat.newThread();
140
+ chat.selectThread('THREAD_ID');
141
+ chat.deleteThread('THREAD_ID');
142
+ chat.renameThread('THREAD_ID', '新的会话名称');
143
+ chat.clearThreads();
144
+ chat.setMessages([]);
145
+ chat.clear();
146
+ chat.open();
147
+ chat.close();
148
+ chat.toggle();
149
+ ```
150
+
151
+ 全局 API:`DraftGoChat.create()`、`registerTransport()`、`registerRenderer()`、`registerComponent()`、`definePreset()`、`use()`。
152
+
153
+ 常用事件:`dg-chat:ready`、`dg-chat:before-send`、`dg-chat:message`、`dg-chat:run-start`、`dg-chat:stream-event`、`dg-chat:run-finish`、`dg-chat:run-abort`、`dg-chat:error`、`dg-chat:message-action`、`dg-chat:session-change`、`dg-chat:model-change`、`dg-chat:open`、`dg-chat:close`。
154
+
155
+ ```javascript
156
+ chat.addEventListener('dg-chat:run-finish', event => {
157
+ console.log(event.detail);
158
+ });
159
+ ```
160
+
161
+ 页面销毁或离开路由时调用 `stop()`,取消上传、plugin 和在途 transport。
162
+
163
+ ## 历史与会话
164
+
165
+ - `persist` 默认开启,将 UI thread 历史写入 `localStorage`;敏感或临时页面设为 `false`。
166
+ - 同一路由存在多个实例时,每个实例设置唯一 `storageKey`。
167
+ - 每个 UI thread 自动维护独立 `sessionId`;`draftgo-agent` 将其作为 `session_id` 发送。
168
+ - `newThread()` 创建新的后端会话边界;不要让不同用户共享固定 `storageKey` 或 `sessionId`。
169
+ - `clear()` 和 `setMessages([])` 会重置当前 session,避免视觉清空后恢复旧 checkpoint。
170
+
171
+ ## 扩展
172
+
173
+ 自定义协议通过 async generator 产生统一事件:
174
+
175
+ ```javascript
176
+ DraftGoChat.registerTransport('page-protocol', async function* (request, context) {
177
+ const response = await fetch('/api/x/page/chat', {
178
+ method: 'POST',
179
+ signal: context.signal,
180
+ headers: { 'Content-Type': 'application/json' },
181
+ body: JSON.stringify({ messages: request.messages })
182
+ });
183
+ const data = await response.json();
184
+ yield { type: 'text.delta', delta: String(data.text || '') };
185
+ yield { type: 'message.finish' };
186
+ });
187
+
188
+ chat.configure({ protocol: 'page-protocol' });
189
+ ```
190
+
191
+ 局部定制优先级:属性/JSON → CSS variables、parts、slots → 实例 `components`/`renderers` → plugin/custom transport。全局注册会影响同一 window 的所有实例;页面特有行为优先放在实例配置中。
192
+
193
+ ## 鉴权与安全
194
+
195
+ - 同源请求优先使用 iframe 可见的 `window.App.fetchAbsolute`,复用 DraftGo token 和刷新机制。
196
+ - 供应商凭证不得自动或手工发送到外部 origin;OpenAI/Anthropic 使用服务端代理。
197
+ - 页面 HTML、JSON config、`headers`、`context` 和 `localStorage` 中不得保存长期供应商密钥。
198
+ - 不把模型输出直接赋给 `innerHTML`;使用 SDK 内置 Markdown/URL 安全渲染或显式净化。
199
+ - 后端始终负责 Agent 调用权限、模型白名单、附件能力和大小限制,前端开关不能越权。
200
+
201
+ 交付前确认:只加载一个完整版脚本;新页面以 `<dg-chat>` 为对话 UI;同页多实例隔离 `storageKey`;外部协议走服务端代理;页面离开时停止在途请求。
@@ -0,0 +1,308 @@
1
+ ---
2
+ read_when: 编写、修改、调试或评审自定义服务时;使用服务 SDK、路由、事件、定时任务或服务依赖时
3
+ ---
4
+
5
+ # Go 自定义服务契约
6
+
7
+ DraftGo 的新自定义服务使用 Go。服务代码是完整的 `package main`,可以使用标准库和 `go.mod` 中声明的第三方库。平台在保存或发布时编译服务、运行 `Register` 并保存触发器清单;执行时在独立子进程中调用选定 handler。
8
+
9
+ 平台 SDK 的固定导入路径是 `draftgo/sdk`。它是 DraftGo 构建器注入的本地 module,不从 GitHub 或其他网络仓库下载,也不要在服务的 `go.mod` 中自行添加或 `replace` 此依赖。
10
+
11
+ ## 索引
12
+
13
+ - [最小服务](#最小服务)、[本地资源](#本地资源)、[触发器](#触发器)
14
+ - [平台 SDK](#平台-sdk)、[AI 平台 SDK](#ai-平台-sdk)
15
+ - [权限与运行限制](#权限与运行限制)、[管理 API](#管理-api)、[验收清单](#验收清单)
16
+
17
+ ## 最小服务
18
+
19
+ ```go
20
+ package main
21
+
22
+ import "draftgo/sdk"
23
+
24
+ func Register(app *sdk.App) {
25
+ app.Route("GET", "/health", health)
26
+ app.On("order.paid", afterPaid)
27
+ app.Schedule("0 9 * * 1-5", weekdayReport)
28
+ }
29
+
30
+ func health(draftgo *sdk.Context) (any, error) {
31
+ return draftgo.Respond(map[string]any{"ok": true}, 200, nil), nil
32
+ }
33
+
34
+ func afterPaid(draftgo *sdk.Context) (any, error) {
35
+ draftgo.Log.Info("order paid event received")
36
+ return nil, nil
37
+ }
38
+
39
+ func weekdayReport(draftgo *sdk.Context) (any, error) { return nil, nil }
40
+ ```
41
+
42
+ `Register` 必须没有业务副作用。它只注册 handler;网络请求、写数据库、发通知等操作放在 handler 内。
43
+
44
+ ## 本地资源
45
+
46
+ ```text
47
+ .draftgo/custom_scripts/
48
+ ├── index.json
49
+ └── script_<id>_<slug>.go
50
+ ```
51
+
52
+ `index.json` 的 Go 服务字段:
53
+
54
+ ```json
55
+ {
56
+ "name": "order-service",
57
+ "slug": "order-service",
58
+ "mode": "mixed",
59
+ "code_file": ".draftgo/custom_scripts/script_new_order-service.go",
60
+ "go_mod": "module example.com/order-service\n\ngo 1.26.0\n\nrequire github.com/google/uuid v1.6.0\n",
61
+ "go_sum": ""
62
+ }
63
+ ```
64
+
65
+ - 新服务使用 `mode=mixed`,允许同一个 `Register` 同时注册 route、event 和 scheduled。旧服务可继续使用单一 `route`、`event` 或 `scheduled` mode。
66
+ - `go_mod` 和可选的 `go_sum` 随服务版本保存并由 `draftgo push custom_scripts` 推送。
67
+ - 新依赖应锁定明确版本。构建错误会在保存/发布时返回,不会替换当前有效清单。
68
+
69
+ ## 触发器
70
+
71
+ ### Route
72
+
73
+ ```go
74
+ func Register(app *sdk.App) {
75
+ app.Route("POST", "/orders", createOrder)
76
+ }
77
+
78
+ func createOrder(draftgo *sdk.Context) (any, error) {
79
+ body, _ := draftgo.Input["body"].(map[string]any)
80
+ return draftgo.Respond(body, 201, nil), nil
81
+ }
82
+ ```
83
+
84
+ `slug=commerce` 时地址为 `POST /api/x/commerce/orders`。Route 精确匹配,不支持 `/orders/{id}` 模板;ID 使用 query 或 body。
85
+
86
+ `draftgo.Input` 的 Route 字段:`method`、`headers`、`body`、`query_params`、`path_params`。当前身份通过 `draftgo.Auth.CurrentUser()` 获取,入站请求头通过 `draftgo.Headers.Get("Authorization")` 等读取。
87
+
88
+ Route 默认以请求调用者身份访问 `draftgo.DB`、`draftgo.Users` 和其他平台能力。服务由管理员创建、拥有 `scripts:*` 管理权限,或在请求中收到 SAT,都不会让普通 SDK 调用自动提升;`draftgo.Auth.RequireAdmin()` 也只检查当前调用者。
89
+
90
+ 可信服务需要管理员权限时,逐次显式使用 `draftgo.Admin.*`。这不是服务配置项,也不需要 `admin_access` 开关:调用 `Admin` 就是管理员调用声明。运行时为**这一次**平台 SDK 调用注入管理员身份,并把服务、版本、真实调用者、操作、资源和结果写进该次执行的审计日志;SAT、数据库连接和管理员凭据不会暴露给服务代码。
91
+
92
+ ```go
93
+ func catalog(draftgo *sdk.Context) (any, error) {
94
+ // 继承调用者权限
95
+ owned, err := draftgo.DB.Query("order", sdk.QueryOptions{})
96
+ if err != nil { return nil, err }
97
+
98
+ // 显式管理员权限;仅此调用提升
99
+ internal, err := draftgo.Admin.DB.Query("internal_catalog", sdk.QueryOptions{})
100
+ if err != nil { return nil, err }
101
+
102
+ return draftgo.Respond(map[string]any{
103
+ "orders": owned.Items,
104
+ "catalog": internal.Items, // 生产代码应再按客户端可见字段组装
105
+ }, 200, nil)
106
+ }
107
+ ```
108
+
109
+ `draftgo.Admin` 提供与普通 SDK 对齐的 `DB`、`Users`、`Auth`、`Notify`、`HTTP`、`Cache`、`Config`、`AIHub`、`Knowledge`、`Memory` 能力。它等价于管理员在平台拥有的权限,不做资源级白名单;因此只能授予可信服务编辑者,并且 Route 返回值仍必须由代码负责脱敏。
110
+
111
+ ### Event
112
+
113
+ ```go
114
+ app.On("user.registered", welcome)
115
+
116
+ func welcome(draftgo *sdk.Context) (any, error) {
117
+ payload, _ := draftgo.Input["payload"].(map[string]any)
118
+ draftgo.Log.Info("registered user: " + fmt.Sprint(payload["user_id"]))
119
+ return nil, nil
120
+ }
121
+ ```
122
+
123
+ 事件异步且不阻塞原请求。事件输入含 `event`、`timestamp`、`payload`。
124
+
125
+ 事件 `payload` 含 `user_id` 或 `actor_user_id` 且该用户仍存在时,handler 继承该用户身份;无法解析用户时才以系统身份执行。事件服务应把事件数据视为业务输入,而不是把它当成绕过资源权限的通道。
126
+
127
+ ### Scheduled
128
+
129
+ ```go
130
+ app.Schedule("0 2 * * *", cleanup)
131
+ ```
132
+
133
+ cron 使用五字段表达式,也支持 `interval:5m`。定时 handler 的 `draftgo.Input` 为空对象。
134
+
135
+ 定时任务没有调用者,会以系统身份执行。因此它只能由可信编辑者维护,写入范围应限制在明确的数据类型,并在 handler 中记录可审计的业务日志。
136
+
137
+ ## 平台 SDK
138
+
139
+ 所有资源访问经受控 RPC 返回 Go 主服务。不要自行读取数据库连接、服务 token 或宿主机环境变量。
140
+
141
+ ```go
142
+ record, err := draftgo.DB.Create("order", map[string]any{"title": "DraftGo"})
143
+ records, err := draftgo.DB.Query("order", sdk.QueryOptions{
144
+ Filters: map[string]any{"status": "paid"},
145
+ Page: 1, PageSize: 20, OrderBy: "id", Order: "desc",
146
+ })
147
+
148
+ user, err := draftgo.Users.Get(12)
149
+ err = draftgo.Auth.RequireLogin()
150
+ err = draftgo.Notify.Send(12, "完成", "订单已创建", "info")
151
+
152
+ cached, err := draftgo.Cache.Get("daily-report")
153
+ err = draftgo.Cache.Set("daily-report", map[string]any{"ok": true}, time.Hour)
154
+
155
+ value, err := draftgo.Config.Get("feature_flag", false)
156
+ response, err := draftgo.HTTP.Get(draftgo.Context(), "https://api.example.com/health", nil, 10*time.Second)
157
+
158
+ reply, err := draftgo.AIHub.Chat(draftgo.Context(), sdk.AIChatRequest{AgentID: 12, Message: "总结订单"})
159
+ draftgo.Log.Info("service completed")
160
+ ```
161
+
162
+ `draftgo.DB` 支持 `Create`、`CreateMany`、`Get`、`Update`、`UpdateMany`、`Delete`、`Query`。`Query` 返回 `sdk.QueryResult{Items, Total, Page, PageSize}`。
163
+
164
+ `draftgo.Users` 支持 `Get`、`List`、`Update`。`draftgo.Auth` 支持 `RequireLogin`、`RequireAdmin`、`RequireRole`、`CurrentUser`。
165
+
166
+ `draftgo.HTTP` 支持 `Get`、`Post`、`Put`、`Patch`、`Delete`;响应为 `sdk.HTTPResponse{StatusCode, Headers, Data}`。服务可使用 `config.http_allowed_hosts` 限制出站目标;HTTP timeout 最终限制为 1-30 秒,响应体最大 5 MB。
167
+
168
+ ## AI 平台 SDK
169
+
170
+ | 入口 | 能力 |
171
+ |---|---|
172
+ | `draftgo.AIHub` | Agent/模型推理、图片、Embedding、Agent/Prompt/Skill/MCP 资产 CRUD、供应商、模型路由、Skill 安装与版本、运行记录 |
173
+ | `draftgo.Knowledge` | 知识库、文档、Chunk CRUD,文档上传、检索、重建索引 |
174
+ | `draftgo.Memory` | 长期记忆 CRUD 与全局检索配置 |
175
+
176
+ 普通入口继承调用用户身份,继续经过 `aihub:read/create/update/delete/execute/invoke` 等平台权限检查。`draftgo.Admin.AIHub`、`draftgo.Admin.Knowledge`、`draftgo.Admin.Memory` 提供同构接口,每次调用以管理员身份执行并进入自定义服务执行审计。
177
+
178
+ ### 推理与配置
179
+
180
+ ```go
181
+ reply, err := draftgo.AIHub.Chat(draftgo.Context(), sdk.AIChatRequest{
182
+ AgentID: 12,
183
+ Message: "总结订单",
184
+ })
185
+
186
+ direct, err := draftgo.AIHub.Chat(draftgo.Context(), sdk.AIChatRequest{
187
+ Model: "gpt-4.1-mini",
188
+ Messages: []map[string]any{{"role": "user", "content": "hello"}},
189
+ })
190
+
191
+ vectors, err := draftgo.AIHub.Embeddings(draftgo.Context(), sdk.AIEmbeddingRequest{
192
+ Model: "text-embedding-3-small",
193
+ Input: []string{"first document", "second document"},
194
+ })
195
+ ```
196
+
197
+ `GenerateImage` 支持 Agent 和直接模型两种调用。普通入口的推理必须在服务 `config.aihub` 中显式开启:
198
+
199
+ ```json
200
+ {
201
+ "aihub": {
202
+ "enabled": true,
203
+ "allowed_agents": [12, 18],
204
+ "allow_direct_model_call": true,
205
+ "allowed_models": ["gpt-4.1-mini", "text-embedding-3-small"],
206
+ "allow_images": true
207
+ }
208
+ }
209
+ ```
210
+
211
+ 空白名单表示不额外限制,但用户权限、Agent 调用权限和模型外部调用策略仍然生效。Agent 预览与后续新增的 `/api/v1/*` 推理端点也使用同一组白名单策略。
212
+
213
+ ### AI 资产、Skill 与 MCP
214
+
215
+ Agent、Prompt、Skill、MCP 使用统一资产 CRUD,通过 `type` 区分:
216
+
217
+ ```go
218
+ skills, err := draftgo.AIHub.ListAssets(draftgo.Context(), map[string]any{
219
+ "type": "skill", "page": 1, "page_size": 50,
220
+ })
221
+
222
+ mcp, err := draftgo.AIHub.CreateAsset(draftgo.Context(), map[string]any{
223
+ "type": "mcp", "name": "internal-tools",
224
+ "data": map[string]any{"transport": "http", "url": "https://mcp.example.com"},
225
+ "status": 1,
226
+ })
227
+
228
+ tools, err := draftgo.AIHub.DiscoverMCPTools(draftgo.Context(), map[string]any{
229
+ "transport": "http", "url": "https://mcp.example.com",
230
+ })
231
+ ```
232
+
233
+ 资产方法包括 `ListAssets/GetAsset/CreateAsset/UpdateAsset/UpdateAssets/DeleteAsset`、`AgentReadiness/PublishAgent`、`DiscoverMCPTools`、`InstallSkill/InstallSkillArchive/ListSkillVersions/RollbackSkill`。供应商使用 `ListProviders/GetProvider/CreateProvider/UpdateProvider/DeleteProvider/DiscoverProvider/SyncProvider`;模型和路由使用 `ListModels/GetModel/CreateModel/UpdateModel/DeleteModel/ProbeModel/UpdateModelRoute/DeleteModelRoute`;运行记录使用 `ListRuns/GetRun/DeleteRuns`。
234
+
235
+ ### 知识库与记忆
236
+
237
+ ```go
238
+ base, err := draftgo.Knowledge.Create(draftgo.Context(), map[string]any{
239
+ "name": "产品手册", "embedding_model_id": 7,
240
+ })
241
+
242
+ document, err := draftgo.Knowledge.UploadDocument(draftgo.Context(), baseID,
243
+ sdk.KnowledgeDocumentUpload{
244
+ Filename: "manual.pdf", MIMEType: "application/pdf", Content: pdfBytes,
245
+ Metadata: map[string]any{"product": "DraftGo"},
246
+ })
247
+
248
+ matches, err := draftgo.Knowledge.Retrieve(draftgo.Context(), baseID, map[string]any{
249
+ "query": "如何配置模型供应商?", "mode": "hybrid", "top_k": 6,
250
+ })
251
+
252
+ memory, err := draftgo.Memory.Create(draftgo.Context(), map[string]any{
253
+ "scope": "user_agent", "user_id": 42, "agent_id": 12,
254
+ "content": "用户偏好简洁的中文回答", "importance": 0.8,
255
+ })
256
+ ```
257
+
258
+ `Knowledge` 还提供知识库 `List/Get/Update/Delete`、文档 `ListDocuments/GetDocument/UpdateDocument/DeleteDocument/ReindexDocument`、Chunk `ListChunks/UpdateChunk` 以及 `RebuildIndex`。文档上传受 8 MiB 限制。`Memory` 提供 `List/Get/Create/Update/Delete`,全局配置使用 `draftgo.Memory.GetConfig` 和 `draftgo.Memory.UpdateConfig`。
259
+
260
+ ### 扩展入口
261
+
262
+ typed helper 尚未覆盖新端点时,使用 `AIHub.Request`:
263
+
264
+ ```go
265
+ value, err := draftgo.AIHub.Request(draftgo.Context(), sdk.AIRequest{
266
+ Method: "GET",
267
+ Path: "/api/aihub/types",
268
+ })
269
+ ```
270
+
271
+ `Request` 只接受 `/api/aihub`、`/api/agents`、`/api/v1`、`/api/images`、`/api/knowledge-bases`、`/api/memories`、`/api/skills` 路径;禁止外部 URL、路径穿越和在 `Path` 中拼 query。身份、权限、推理开关与审计规则和 typed helper 相同。
272
+
273
+ 日志每次执行最多 500 条、单条最多 4096 字符。不要记录 token、Cookie、密码或完整个人信息。
274
+
275
+ ## 权限与运行限制
276
+
277
+ - `scripts:read/create/update/delete/execute` 控制可信人员管理服务。
278
+ - Route 调用者仍由服务 `permission` 与 `config.route_security` 控制;管理权限不绕过 Route 调用权限。
279
+ - Route 的普通 SDK 数据访问继承调用者权限;不要把“管理员创建服务”误写成自动提升。只有显式 `draftgo.Admin.*` 调用才以管理员执行并记录审计;定时任务和无可解析用户的事件是系统身份例外。
280
+ - `config.timeout`、`max_concurrency`、`queue_timeout_ms` 适用于服务执行。Route 饱和时返回 HTTP 429。
281
+ - Go 服务以独立进程运行,超时会终止该进程;它不是为不可信多租户代码准备的安全沙箱。只向可信编辑者授予服务编辑权限。
282
+ - 每个保存版本按源码、依赖、SDK 和 Runner 协议生成不可变构建键。代码或依赖变更会生成新构建产物;旧版本可通过现有版本恢复接口重新激活。
283
+
284
+ ## 管理 API
285
+
286
+ 管理 API 使用 `{code, data, message}` 信封并受 `scripts:*` 权限控制:
287
+
288
+ | 方法 | 路径 |
289
+ |---|---|
290
+ | `POST` / `GET` | `/api/scripts` |
291
+ | `GET` | `/api/scripts/options/roles` |
292
+ | `GET` / `PUT` / `DELETE` | `/api/scripts/{id}` |
293
+ | `GET` | `/api/scripts/{id}/routes` |
294
+ | `POST` | `/api/scripts/{id}/enable`、`/api/scripts/{id}/disable`、`/api/scripts/{id}/execute` |
295
+ | `GET` | `/api/scripts/{id}/versions`、`/api/scripts/{id}/executions` |
296
+ | `POST` | `/api/scripts/{id}/versions/{version_id}/restore` |
297
+ | `GET` | `/api/scripts/{id}/executions/{execution_id}` |
298
+
299
+ 运行时 route 为 `ANY /api/x/{slug}/{path}`,不使用管理 API 信封。
300
+
301
+ ## 验收清单
302
+
303
+ - [ ] `package main` 且实现 `Register(app *sdk.App)`。
304
+ - [ ] 服务使用 `mode=mixed`,第三方库写入 `go_mod`。
305
+ - [ ] 路由使用 `app.Route`,事件使用 `app.On`,定时任务使用 `app.Schedule`。
306
+ - [ ] handler 返回 `(any, error)`,需要状态码时使用 `draftgo.Respond`。
307
+ - [ ] Route 显式设置 `permission` 与 `route_security`。
308
+ - [ ] 推送后请求无副作用 GET Route,必要时运行 `draftgo push custom_scripts --probe-routes`。