draftgo-cli 4.0.25 → 4.0.26
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 +21 -37
- package/package.json +3 -5
- package/resources/skill/SKILL.md +9 -5
- package/resources/skill/manifest.json +2 -5
- package/resources/skill/references/ai.md +41 -0
- package/resources/skill/references/app-api.md +2 -50
- package/resources/skill/references/architecture.md +1 -1
- package/resources/skill/references/chat-sdk.md +29 -37
- package/resources/skill/references/checkout.md +4 -4
- package/resources/skill/references/data.md +0 -46
- package/resources/skill/references/delivery.md +3 -3
- package/resources/skill/references/diagnostics.md +10 -11
- package/resources/skill/references/frontend.md +23 -20
- package/resources/skill/references/mcp.md +4 -14
- package/resources/skill/references/methods.md +15 -68
- package/resources/skill/references/modules.md +23 -44
- package/resources/skill/references/runtime.md +3 -20
- package/resources/skill/story/SKILL.md +2 -2
- package/src/apiContractCache.js +14 -6
- package/src/cli.js +0 -7
- package/src/commandRegistry.js +0 -6
- package/src/commands/api.js +87 -17
- package/src/commands/apiKey.js +2 -6
- package/src/commands/autoPush.js +15 -51
- package/src/commands/capabilities.js +22 -15
- package/src/commands/check.js +19 -53
- package/src/commands/checkout.js +1 -4
- package/src/commands/clean.js +1 -1
- package/src/commands/commit.js +1 -4
- package/src/commands/components.js +12 -8
- package/src/commands/conflict.js +4 -6
- package/src/commands/conflicts.js +1 -2
- package/src/commands/connect.js +0 -8
- package/src/commands/delete.js +15 -11
- package/src/commands/deploy.js +64 -26
- package/src/commands/diff.js +1 -4
- package/src/commands/group.js +2 -3
- package/src/commands/help.js +19 -41
- package/src/commands/init.js +13 -6
- package/src/commands/local.js +4 -1
- package/src/commands/map.js +138 -23
- package/src/commands/reconcile.js +1 -15
- package/src/commands/role.js +1 -2
- package/src/commands/status.js +12 -40
- package/src/commands/verify.js +8 -7
- package/src/commands/worklog.js +11 -5
- package/src/contractCompatibility.js +10 -2
- package/src/localRuntime/compose.js +41 -27
- package/src/localRuntime/detect.js +6 -6
- package/src/localRuntime/index.js +47 -47
- package/src/localRuntime/services.js +2 -39
- package/src/mcp/client.js +99 -134
- package/src/mcp/parallel.js +25 -2
- package/src/mcp/protocol.js +38 -9
- package/src/mcp/tools.js +10 -19
- package/src/projectConfig.js +1 -4
- package/src/{workspaceHealth.js → projectHealth.js} +5 -5
- package/src/projectMap.js +1 -1
- package/src/runtimeFiles.js +2 -1
- package/src/worklog.js +3 -2
- package/src/worktree/backend.js +127 -15
- package/src/worktree/index.js +64 -22
- package/src/worktree/locks.js +52 -0
- package/src/worktree/manifest.js +18 -4
- package/src/worktree/status.js +4 -2
- package/resources/custom-service-sdk/ai.go +0 -520
- package/resources/custom-service-sdk/ai_test.go +0 -156
- package/resources/custom-service-sdk/auth_test.go +0 -56
- package/resources/custom-service-sdk/billing.go +0 -596
- package/resources/custom-service-sdk/billing_test.go +0 -150
- package/resources/custom-service-sdk/go.mod +0 -3
- package/resources/custom-service-sdk/manifest.json +0 -77
- package/resources/custom-service-sdk/platform.go +0 -352
- package/resources/custom-service-sdk/platform_logger_test.go +0 -24
- package/resources/custom-service-sdk/registration_test.go +0 -39
- package/resources/custom-service-sdk/resources.go +0 -247
- package/resources/custom-service-sdk/resources_billing_test.go +0 -115
- package/resources/custom-service-sdk/resources_files_test.go +0 -57
- package/resources/custom-service-sdk/resources_scope_test.go +0 -92
- package/resources/custom-service-sdk/sdk.go +0 -209
- package/resources/skill/references/aihub.md +0 -116
- package/resources/skill/references/custom-services.md +0 -201
- package/src/commands/customService.js +0 -95
- package/src/commands/dataRange.js +0 -33
- package/src/commands/grant.js +0 -29
- package/src/commands/space.js +0 -41
- package/src/customServices.js +0 -484
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
// Package sdk is the public contract for DraftGo Go custom services.
|
|
2
|
-
//
|
|
3
|
-
// A service implements Register and calls App.Route, App.On and App.Schedule.
|
|
4
|
-
// The isolated script runner invokes Register once at publish time to discover
|
|
5
|
-
// handlers, then invokes the selected Handler for each execution.
|
|
6
|
-
package sdk
|
|
7
|
-
|
|
8
|
-
import (
|
|
9
|
-
"context"
|
|
10
|
-
"fmt"
|
|
11
|
-
"net/http"
|
|
12
|
-
"strings"
|
|
13
|
-
"sync"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
// Handler is a user supplied custom-service function.
|
|
17
|
-
type Handler func(*Context) (any, error)
|
|
18
|
-
|
|
19
|
-
// Registration is an immutable description of one platform trigger.
|
|
20
|
-
type Registration struct {
|
|
21
|
-
Kind string `json:"kind"`
|
|
22
|
-
Method string `json:"method,omitempty"`
|
|
23
|
-
Path string `json:"path,omitempty"`
|
|
24
|
-
Event string `json:"event,omitempty"`
|
|
25
|
-
Cron string `json:"cron,omitempty"`
|
|
26
|
-
Name string `json:"name,omitempty"`
|
|
27
|
-
Handler Handler `json:"-"`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// App is supplied to Register. It is safe to inspect registrations after
|
|
31
|
-
// Register returns; registration itself is intentionally limited to startup.
|
|
32
|
-
type App struct {
|
|
33
|
-
mu sync.RWMutex
|
|
34
|
-
registrations []Registration
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// NewApp creates the registration collector used by a script runner.
|
|
38
|
-
func NewApp() *App { return &App{} }
|
|
39
|
-
|
|
40
|
-
// Route registers an HTTP endpoint. Path is relative to the custom-service
|
|
41
|
-
// mount point and must start with '/'.
|
|
42
|
-
func (app *App) Route(method, path string, handler Handler) {
|
|
43
|
-
method, path = strings.ToUpper(strings.TrimSpace(method)), strings.TrimSpace(path)
|
|
44
|
-
if method == "" || path == "" || !strings.HasPrefix(path, "/") || handler == nil {
|
|
45
|
-
panic("sdk.Route requires method, absolute path and handler")
|
|
46
|
-
}
|
|
47
|
-
trimmed := strings.Trim(path, "/")
|
|
48
|
-
if trimmed == "" {
|
|
49
|
-
path = "/"
|
|
50
|
-
} else {
|
|
51
|
-
path = "/" + trimmed
|
|
52
|
-
}
|
|
53
|
-
app.add(Registration{Kind: "route", Method: method, Path: path, Handler: handler})
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// On registers a named DraftGo event handler.
|
|
57
|
-
func (app *App) On(event string, handler Handler) {
|
|
58
|
-
event = strings.TrimSpace(event)
|
|
59
|
-
if event == "" || handler == nil {
|
|
60
|
-
panic("sdk.On requires event and handler")
|
|
61
|
-
}
|
|
62
|
-
app.add(Registration{Kind: "event", Event: event, Handler: handler})
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Schedule registers a five-field cron expression or a DraftGo interval
|
|
66
|
-
// expression (for example "interval:5m").
|
|
67
|
-
func (app *App) Schedule(cron string, handler Handler) {
|
|
68
|
-
cron = strings.TrimSpace(cron)
|
|
69
|
-
if cron == "" || handler == nil {
|
|
70
|
-
panic("sdk.Schedule requires cron and handler")
|
|
71
|
-
}
|
|
72
|
-
app.add(Registration{Kind: "scheduled", Cron: cron, Handler: handler})
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
func (app *App) add(registration Registration) {
|
|
76
|
-
app.mu.Lock()
|
|
77
|
-
defer app.mu.Unlock()
|
|
78
|
-
for _, existing := range app.registrations {
|
|
79
|
-
if registration.Kind == "route" && existing.Kind == "route" && existing.Method == registration.Method && existing.Path == registration.Path {
|
|
80
|
-
panic(fmt.Sprintf("duplicate route %s %s", registration.Method, registration.Path))
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Function names are not a stable Go runtime API. A deterministic ID based
|
|
84
|
-
// on registration order is therefore persisted in the published manifest.
|
|
85
|
-
registration.Name = fmt.Sprintf("%s-%d", registration.Kind, len(app.registrations)+1)
|
|
86
|
-
app.registrations = append(app.registrations, registration)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Registrations returns a copy, so callers cannot mutate the published
|
|
90
|
-
// registration set.
|
|
91
|
-
func (app *App) Registrations() []Registration {
|
|
92
|
-
app.mu.RLock()
|
|
93
|
-
defer app.mu.RUnlock()
|
|
94
|
-
result := make([]Registration, len(app.registrations))
|
|
95
|
-
copy(result, app.registrations)
|
|
96
|
-
return result
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Context is passed to a handler. Input and User are JSON-compatible values
|
|
100
|
-
// supplied by DraftGo; platform resource methods will be added by the runner
|
|
101
|
-
// RPC client rather than by exposing database handles to scripts.
|
|
102
|
-
type Context struct {
|
|
103
|
-
Input map[string]any `json:"input"`
|
|
104
|
-
User map[string]any `json:"user,omitempty"`
|
|
105
|
-
Headers http.Header `json:"headers,omitempty"`
|
|
106
|
-
Platform bool `json:"platform"`
|
|
107
|
-
WorkspaceID int64 `json:"workspace_id,omitempty"`
|
|
108
|
-
SpaceID int64 `json:"space_id,omitempty"`
|
|
109
|
-
OwnerUserID int64 `json:"owner_user_id,omitempty"`
|
|
110
|
-
Principal ScopePrincipal `json:"principal"`
|
|
111
|
-
|
|
112
|
-
DB Database `json:"-"`
|
|
113
|
-
Users Users `json:"-"`
|
|
114
|
-
Auth Auth `json:"-"`
|
|
115
|
-
Notify Notifier `json:"-"`
|
|
116
|
-
HTTP HTTPClient `json:"-"`
|
|
117
|
-
Cache Cache `json:"-"`
|
|
118
|
-
Config Config `json:"-"`
|
|
119
|
-
AIHub AIHub `json:"-"`
|
|
120
|
-
Knowledge Knowledge `json:"-"`
|
|
121
|
-
Memory Memory `json:"-"`
|
|
122
|
-
Files FileStore `json:"-"`
|
|
123
|
-
Scope ScopeAPI `json:"-"`
|
|
124
|
-
Billing BillingAPI `json:"-"`
|
|
125
|
-
// Admin groups explicitly elevated operations for trusted custom services.
|
|
126
|
-
// Elevation applies to one RPC call, is audited, and never exposes credentials.
|
|
127
|
-
Admin Admin `json:"-"`
|
|
128
|
-
Log *Logger `json:"-"`
|
|
129
|
-
|
|
130
|
-
context context.Context
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// NewContext constructs the platform facade used by the isolated runner. The
|
|
134
|
-
// client is intentionally a narrow RPC interface rather than a database or
|
|
135
|
-
// HTTP-server handle.
|
|
136
|
-
func NewContext(draftgo context.Context, input, user map[string]any, headers http.Header, client Client) *Context {
|
|
137
|
-
return NewContextWithResource(draftgo, input, user, headers, client, ResourceContext{}, ScopePrincipal{})
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// NewContextWithResource constructs a handler context with persisted resource
|
|
141
|
-
// ownership and the verified caller/system principal supplied by DraftGo. Custom services
|
|
142
|
-
// cannot override these values through Input or request headers.
|
|
143
|
-
func NewContextWithResource(draftgo context.Context, input, user map[string]any, headers http.Header, client Client, resource ResourceContext, principal ScopePrincipal) *Context {
|
|
144
|
-
if draftgo == nil {
|
|
145
|
-
draftgo = context.Background()
|
|
146
|
-
}
|
|
147
|
-
if input == nil {
|
|
148
|
-
input = map[string]any{}
|
|
149
|
-
}
|
|
150
|
-
platform := platformClient{client: client, context: draftgo}
|
|
151
|
-
adminPlatform := platformClient{client: client, context: draftgo, admin: true}
|
|
152
|
-
return &Context{
|
|
153
|
-
Input: input, User: user, Headers: headers, context: draftgo,
|
|
154
|
-
Platform: resource.ScopeType == ScopePlatform, WorkspaceID: resource.WorkspaceID, SpaceID: resource.SpaceID, OwnerUserID: resource.OwnerUserID, Principal: principal,
|
|
155
|
-
DB: dbClient{platform}, Users: usersClient{platform}, Auth: authClient{platformClient: platform, user: user}, Notify: notifierClient{platform},
|
|
156
|
-
HTTP: httpClient{platform}, Cache: cacheClient{platform}, Config: configClient{platform}, AIHub: aiHubClient{platform},
|
|
157
|
-
Knowledge: knowledgeClient{platform}, Memory: memoryClient{platform}, Files: fileStoreClient{platform}, Scope: scopeClient{platform}, Billing: billingClient{platform},
|
|
158
|
-
Admin: Admin{
|
|
159
|
-
DB: dbClient{adminPlatform}, Users: usersClient{adminPlatform}, Auth: authClient{platformClient: adminPlatform, user: user}, Notify: notifierClient{adminPlatform},
|
|
160
|
-
HTTP: httpClient{adminPlatform}, Cache: cacheClient{adminPlatform}, Config: configClient{adminPlatform}, AIHub: aiHubClient{adminPlatform},
|
|
161
|
-
Knowledge: knowledgeClient{adminPlatform}, Memory: memoryClient{adminPlatform},
|
|
162
|
-
Files: fileStoreClient{adminPlatform}, Scope: scopeClient{adminPlatform}, Billing: billingClient{adminPlatform},
|
|
163
|
-
},
|
|
164
|
-
Log: &Logger{},
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Admin exposes explicitly elevated platform capabilities to trusted custom
|
|
169
|
-
// services. The runtime uses a system principal (actor_id=0), scopes elevation to one
|
|
170
|
-
// audited RPC call, and never exposes platform credentials.
|
|
171
|
-
type Admin struct {
|
|
172
|
-
DB Database
|
|
173
|
-
Users Users
|
|
174
|
-
Auth Auth
|
|
175
|
-
Notify Notifier
|
|
176
|
-
HTTP HTTPClient
|
|
177
|
-
Cache Cache
|
|
178
|
-
Config Config
|
|
179
|
-
AIHub AIHub
|
|
180
|
-
Knowledge Knowledge
|
|
181
|
-
Memory Memory
|
|
182
|
-
Files FileStore
|
|
183
|
-
Scope ScopeAPI
|
|
184
|
-
Billing BillingAPI
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Context returns the invocation deadline/cancellation context supplied by
|
|
188
|
-
// DraftGo. Pass it to SDK calls that accept a context.Context.
|
|
189
|
-
func (draftgo *Context) Context() context.Context {
|
|
190
|
-
if draftgo == nil || draftgo.context == nil {
|
|
191
|
-
return context.Background()
|
|
192
|
-
}
|
|
193
|
-
return draftgo.context
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Response allows handlers to explicitly control an HTTP response.
|
|
197
|
-
type Response struct {
|
|
198
|
-
Body any `json:"body"`
|
|
199
|
-
StatusCode int `json:"status_code"`
|
|
200
|
-
Headers http.Header `json:"headers,omitempty"`
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Respond creates a response result. A status of zero defaults to 200.
|
|
204
|
-
func (draftgo *Context) Respond(body any, status int, headers http.Header) Response {
|
|
205
|
-
if status == 0 {
|
|
206
|
-
status = http.StatusOK
|
|
207
|
-
}
|
|
208
|
-
return Response{Body: body, StatusCode: status, Headers: headers}
|
|
209
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
read_when: 创建或调优 AI Agent 时 · 需要工具/子智能体/记忆/多轮/结构化输出/多模态时
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# AIHub / Agent 资源
|
|
6
|
-
|
|
7
|
-
## 最短管理流程
|
|
8
|
-
|
|
9
|
-
AIHub、知识库与记忆都是结构化远端资源。先按领域搜索实时 operation,再描述契约并调用;`request.json` 只包含 describe 允许的字段:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
draftgo api search "AIHub Agent"
|
|
13
|
-
draftgo api search "knowledge base"
|
|
14
|
-
draftgo api search "agent memory"
|
|
15
|
-
draftgo api describe <operation_id>
|
|
16
|
-
draftgo api call <operation_id> --input request.json --output json
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
已有精确 `operation_id` 时跳过 search。写入后用对应 get/list operation 回读目标 ID;需要验证运行行为时,再查调用 operation 和 AI run 日志,不把配置成功当成推理成功。知识库存放可检索资料,长期记忆存放 Agent 运行时按作用域提炼的用户/Agent 记忆,两者不要互相代替。
|
|
20
|
-
|
|
21
|
-
AIHub 与知识库是独立权限域:Agent、模型和 AI 运行使用 `aihub:*`,知识库、文档、上传、检索和重建使用 `knowledge:*`。配置了 `aihub:*` 不会自动获得知识库权限,反之亦然;具体动作仍以实时 operation 契约为准。
|
|
22
|
-
|
|
23
|
-
完成条件:目标资产可回读,状态与调用权限符合预期;任务涉及调用时,运行成功且日志能按入口 Agent/模型定位。任何文件和输出都不得包含供应商密钥。
|
|
24
|
-
|
|
25
|
-
AIHub 是结构化远端资源,不 checkout,也不生成本地镜像。先使用 MCP `draftgo_resource_search`/`draftgo_resource_list`
|
|
26
|
-
定位资产;未知 operation 才用 `draftgo_api_search`,首次使用或 registry revision 变化时 `draftgo_api_describe`,然后通过 `draftgo_api_call` 读写。
|
|
27
|
-
更新 AIHub 资产时只发送实时契约允许的字段;常见字段包括
|
|
28
|
-
`type, name, data, priority, version, tags, describe, permission, status`。供应商和模型使用各自的实时接口,
|
|
29
|
-
不要经旧 AIHub 资产接口写入。任何输出、工作区文件或调用参数都不得保存 API Key 或 header 值;只可处理
|
|
30
|
-
“是否已配置”以及 header 名称等非秘密元数据。
|
|
31
|
-
**Agent 的全部行为都在 `data`(尤其 `data.spec`)里**——本页就是 `data.spec` 的字段地图。
|
|
32
|
-
|
|
33
|
-
## 条目骨架
|
|
34
|
-
|
|
35
|
-
```jsonc
|
|
36
|
-
{
|
|
37
|
-
"type": "agent", // AIHub 资产类型
|
|
38
|
-
"name": "产品顾问",
|
|
39
|
-
"describe": "面向用户的产品答疑助手",
|
|
40
|
-
"status": "active",
|
|
41
|
-
"data": {
|
|
42
|
-
"mode": "chat", // chat | image_generation
|
|
43
|
-
"spec": { /* 见下表 */ }
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
页面对话 UI 使用 `<dg-chat protocol="draftgo-agent" agent-id="AGENT_ID">` 或 `DraftGoChat.create()`;
|
|
49
|
-
旧代码/无 UI 文本调用可用 `DraftGoAI.chat(...)`,图片模式使用 `DraftGoAI.images(...)`。调用前都必须加载
|
|
50
|
-
`/assets/draftgo-chat.js`,完整用法见 `references/chat-sdk.md`。后端 operation 与可调用 Agent 列表通过 MCP 实时确认;已知契约可复用项目缓存。
|
|
51
|
-
|
|
52
|
-
## `data.spec` 字段地图
|
|
53
|
-
|
|
54
|
-
留空即维持默认/旧行为;除标注外都是可选。运行时统一在 `parseOrchestrationConfig` + 就地读取时带默认值与 clamp。
|
|
55
|
-
|
|
56
|
-
| 字段 | 类型 / 取值 | 说明 |
|
|
57
|
-
|---|---|---|
|
|
58
|
-
| `mode` | `chat` / `image_generation` | 决定主要交互形态;具体 Responses、Embedding、Rerank、TTS、ASR、Video operation 以 MCP 和模型 capability 为准 |
|
|
59
|
-
| `model` | string | 主模型(逻辑模型名,映射到供应商路由) |
|
|
60
|
-
| `fallback_models` | string[] | 主模型失败后按序回退(跨模型 failover) |
|
|
61
|
-
| `model_selection.user_selectable` | bool | 是否允许调用方在请求里覆盖 `model`(配合 `selectable-models`) |
|
|
62
|
-
| `ttft_timeout` | number(秒,1–600,空=不启用) | 首字超时 failover:首个 SSE data 事件超时即跨模型+跨供应商切换,推理模型不误杀 |
|
|
63
|
-
| `sync_request_timeout` | number(秒,1–600,默认 100) | 非流式请求上限 |
|
|
64
|
-
| `stream_ttl` | number(秒,1–3600,默认 600) | 流式请求上限 |
|
|
65
|
-
| `max_tokens` | number / 空 | 最大输出 token;留空时不写入请求,即不由 Agent 额外限制 |
|
|
66
|
-
| `reasoning_effort` | `off`/`minimal`/`low`/`medium`/`high` | `off`/留空均不透传;其它值仅 OpenAI 系模型生效 |
|
|
67
|
-
| `system_prompt_template` | string | 系统提示模板 |
|
|
68
|
-
| `context.max_history` | number(默认 20) | 工作窗口:保留最近 N 条;关闭持续对话时即滑动窗口硬上限 |
|
|
69
|
-
| `output_format.mode` | `text` / `json` | JSON 时按 `output_format.json.{schema,schema_name,strategy}` 约束/校验/降级 |
|
|
70
|
-
| `capabilities.vision.{enabled,input,max_mb}` | 见值 | 图片/视觉输入(`image_url` part),`input`⊂{base64,url},默认 5MB |
|
|
71
|
-
| `capabilities.files.{enabled,allowed_ext,max_mb}` | 见值 | 文件附件抽取成文本注入;白名单 `.txt .md .docx .pdf .xlsx .json .csv`(pptx 不支持),默认 8MB |
|
|
72
|
-
| `tools.max_iterations` | 1–50(默认 10) | ReAct 工具循环步数上限 |
|
|
73
|
-
| `tools.sources[].{type,id}` | `mcp` / `custom_script` | 绑定 MCP 与「自定义服务作为工具」 |
|
|
74
|
-
| `knowledge_base_ids` | int[] | 绑定知识库,生成检索工具 |
|
|
75
|
-
| `skills` | 见运行时 | 绑定 Skill |
|
|
76
|
-
| `sub_agent_ids` | int[] | 子智能体:为每个 id 生成 `agent_{id}` 委派工具(用法同 `knowledge_base_ids`) |
|
|
77
|
-
| `call_permissions` | 角色配置 | 哪些角色可以调用此 Agent;调用接口与筛选条件以 MCP 实时契约为准 |
|
|
78
|
-
| `billing_mode` | `disabled` / `inherit_model` / `per_call` / `usage` | Agent 零售计费;公开 Agent 强制 `disabled`,订阅/会员额度由业务服务管理 |
|
|
79
|
-
|
|
80
|
-
### `orchestration.*`(编排开关)
|
|
81
|
-
|
|
82
|
-
| 字段 | 默认 | 说明 |
|
|
83
|
-
|---|---|---|
|
|
84
|
-
| `tool_concurrency` | 8(1–32) | 单步内并发执行工具数 |
|
|
85
|
-
| `on_max_steps` | `error` | 达步数上限:`error` 报错 / `stop` 返回最后一条 |
|
|
86
|
-
| `tool_disclosure.{mode,threshold_tools}` | `off` | 工具渐进披露:`off`/`auto`/`always`,首轮只给目录+`load_tools` |
|
|
87
|
-
| `planning.{enabled,prompt}` | false | 规划层:执行前先让模型列步骤,提示折叠进 system |
|
|
88
|
-
| `memory.{enabled,scope}` | false / `user_agent` | 长期记忆:对话后自动提炼、下轮召回注入;作用域 `agent`/`user`/`user_agent` |
|
|
89
|
-
| `compaction.{enabled,keep_recent,trigger_messages,trigger_tokens,preset}` | 关闭 | **持续对话/上下文闭环**:见下 |
|
|
90
|
-
| `checkpoint_input_mode` / `checkpoint_max_messages` / `checkpoint_ttl_seconds` | 全量 / 100 / 86400 | 会话历史持久化(配合请求 `session_id`) |
|
|
91
|
-
| `delegation.{max_depth,max_total_calls}` | 2 / 8 | 最大嵌套层数与整棵调用树共享的子 Agent 调用次数;并行分支也从同一预算扣减 |
|
|
92
|
-
|
|
93
|
-
同一模型步骤返回多个子 Agent 工具调用时,运行时立即按 `tool_concurrency` 并发执行;不同步骤自然串行,由模型自行决定编排方式。委派成功后,子 Agent 的每个模型回合 token 会汇总到入口 run,总量也写入对应委派 span。一次入口请求只创建一条主 run,Agent 列归属入口 Agent,委派链路从 span 查看。
|
|
94
|
-
|
|
95
|
-
### 持续对话(上下文闭环)
|
|
96
|
-
|
|
97
|
-
- `compaction.enabled=true` = 闭环:填满工作窗口后把溢出旧消息**摘要成一条滚动 summary** 续接,
|
|
98
|
-
而非直接丢弃。此时后端**跳过 `context.max_history` 硬砍**,让完整 checkpoint 历史进入压缩器蒸馏。
|
|
99
|
-
- 触发为双通道任一命中:`trigger_messages`(条数)或 `trigger_tokens`(估算 token,256–2000000)。
|
|
100
|
-
- `keep_recent`:保留最近 N 条不压缩。`preset`(`aggressive`/`balanced`/`conservative`)是管理台档位回显,
|
|
101
|
-
后端只认 `keep_recent`/`trigger_messages`/`trigger_tokens` 三个底层字段。
|
|
102
|
-
- 配合请求体 `session_id` 才会加载/续写会话历史;`<dg-chat>` 为每个 UI thread 自动维护该值,兼容门面可通过 `DraftGoAI.chat(..., {sessionId})` 显式传入。不传即无状态单轮。
|
|
103
|
-
- 关闭时逐字回退为 `max_history` 滑动窗口(旧行为,零影响)。
|
|
104
|
-
|
|
105
|
-
## 观测
|
|
106
|
-
|
|
107
|
-
每次调用都开一条 AI run,管理台 `/admin/ai-runs` 展示状态、tokens、延迟、`ttft_ms` 与 span 链路。
|
|
108
|
-
运行记录接口通过 MCP 实时发现,不维护静态路径表;已知 operation 复用 registry revision 未变化的契约缓存。
|
|
109
|
-
|
|
110
|
-
模型价格使用 `disabled`、`per_call`、`usage`。模型能否调用某项能力必须同时满足模型声明、Provider
|
|
111
|
-
profile、transport readiness 和实际 route;不要只根据供应商名称推断支持音频、视频或其他能力。
|
|
112
|
-
能力名大小写敏感,使用实时 describe 返回的 canonical 值,不使用历史别名。
|
|
113
|
-
|
|
114
|
-
Agent 对外响应(`/api/agents/{id}/chat`、图片接口、Go/Script SDK Agent 调用)不返回内部 `model`、`fallback_models` 或 `upstream_model`;错误也不暴露内部模型名、供应商名称、服务地址或运行时实现名。只有显式启用 `model_selection.user_selectable` 后,`/selectable-models` 才作为授权的模型选择目录返回可选逻辑模型名。运行日志仍在服务端保留真实模型、供应商与链路信息用于定位。
|
|
115
|
-
|
|
116
|
-
> 权威细节以 DraftGo `docs/modules/ai-platform/agent-runtime.md` 和实时 MCP schema 为准;本页是基座开发者视角的字段速查。
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
read_when: 编写、修改、试运行、发布或评审自定义服务时;使用服务 SDK、Route、Event、Scheduled handler 或服务依赖时
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Go 自定义服务契约
|
|
6
|
-
|
|
7
|
-
自定义服务是完整的 Go `package main`。平台从源码生成并锁定依赖,执行 `Register` 保存触发器清单,并在独立子进程中运行 handler。SDK 固定导入 `draftgo/sdk`,由构建器注入。
|
|
8
|
-
|
|
9
|
-
## 最短流程
|
|
10
|
-
|
|
11
|
-
先通过 MCP resource/API operation 定位或创建服务并取得 ID;operation schema 以实时 describe 为准。完整正文只走两文件 worktree:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
draftgo checkout custom-services <id...>
|
|
15
|
-
draftgo diff custom-services <id>
|
|
16
|
-
draftgo commit custom-services <id...>
|
|
17
|
-
draftgo validate custom-services <id>
|
|
18
|
-
draftgo test custom-services <id> --source draft --handler route:POST:/path --input request.json
|
|
19
|
-
draftgo publish custom-services <id...>
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
`commit custom-services` 是一体化交付入口,会依次完成 commit、validate 和 publish,并在成功后启用线上版本。`publish custom-services` 仍保留为兼容入口;`validate` 和 `test` 仍会先 commit 当前 worktree。不同服务可并发,同一服务保持 edit -> diff -> commit 顺序。
|
|
23
|
-
|
|
24
|
-
`test` 默认执行 cloud draft;`--source published` 直接测试线上版本且不会提交本地改动,`--source auto` 按生命周期选择。草稿 revision 是内容版本,`validated_revision` / `validated_hash` 绑定源码、依赖、SDK 与 Runner 协议;保存会使旧凭证失效。Runner/SDK 变化导致 `validation_stale` 时,publish 只自动重新 validate 一次;revision 冲突直接停止。
|
|
25
|
-
|
|
26
|
-
## Worktree
|
|
27
|
-
|
|
28
|
-
| 文件 | 用法 |
|
|
29
|
-
|---|---|
|
|
30
|
-
| `service.go` | 完整 `package main`;在 `Register` 中声明 handler |
|
|
31
|
-
| `service.json` | 服务元数据;以实时 checkout/API schema 标出的可编辑字段为准 |
|
|
32
|
-
|
|
33
|
-
两个文件都必须保留。`service.json` 中的 `schema_version`、`id`、`revision`、`validation_status`、`validated_revision`、`validated_hash` 是服务端状态,不手工伪造;`Register` 是 Route/Event/Scheduled handler 的唯一事实来源,不在元数据中维护触发器或运行模式。
|
|
34
|
-
|
|
35
|
-
CLI 只支持当前 schema v2 的 `service.go`/`service.json` worktree。旧四文件或 schema v1 会明确拒绝;删除旧 worktree 后重新执行 checkout 即可。
|
|
36
|
-
|
|
37
|
-
SDK 固定导入 `draftgo/sdk`。第三方依赖在 `service.go` 顶部声明 `//draftgo:require github.com/google/uuid@v1.6.0`;必须固定显式版本,禁止 floating version、本地路径与自定义 replace。CLI 会从随包发布的 SDK 快照生成 `go.mod/go.sum`、`draftgo_local_main.go` 与 `.draftgo-sdk/`,仅供 gopls 和本地 `go build`;这些带托管标记的文件会自动刷新,不能手工维护,也不会上传。服务端仍独立生成并锁定实际运行依赖。
|
|
38
|
-
|
|
39
|
-
## 最小服务
|
|
40
|
-
|
|
41
|
-
```go
|
|
42
|
-
package main
|
|
43
|
-
|
|
44
|
-
import "draftgo/sdk"
|
|
45
|
-
|
|
46
|
-
func Register(app *sdk.App) {
|
|
47
|
-
app.Route("GET", "/health", health)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
func health(ctx *sdk.Context) (any, error) {
|
|
51
|
-
return ctx.Respond(map[string]any{"ok": true}, 200, nil), nil
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
`Register` 只注册 handler,不访问网络、数据库或通知系统。平台在 validate 时执行它并自动记录 handler。
|
|
56
|
-
|
|
57
|
-
## 触发器
|
|
58
|
-
|
|
59
|
-
### Route
|
|
60
|
-
|
|
61
|
-
`app.Route("POST", "/orders", createOrder)` 在 `slug=commerce` 时对应 `POST /api/x/commerce/orders`。Route 精确匹配,不支持 `/orders/{id}` 模板;ID 使用 query 或 body。
|
|
62
|
-
|
|
63
|
-
输入字段为 `method`、`headers`、`body`、`query_params`、`path_params`。身份由 `ctx.Auth.CurrentUser()` 获取。普通 `ctx.DB`、`ctx.Users`、`ctx.Billing` 等调用继承请求调用者权限;管理员创建服务、持有服务凭据或 `RequireAdmin` 都不会自动提升 SDK 调用。
|
|
64
|
-
|
|
65
|
-
`ctx.Admin.*` 是自定义服务显式选择的单次系统内部授权:该次 RPC 使用 system actor_id=0,按 `*:*:all` 全权限执行,可跨用户/空间,不检查 AccessGrant;每次调用进入审计,内部授权不会泄漏到后续普通调用。Event/Scheduled 没有调用者,普通 `ctx.xxx` 返回 403。服务代码仍拿不到服务凭据、数据库连接或管理员凭据。公开 Route 不应无条件调用 Admin;先完成业务鉴权、参数校验和幂等设计,返回值仍需自行脱敏。
|
|
66
|
-
|
|
67
|
-
### Event
|
|
68
|
-
|
|
69
|
-
`app.On("user.registered", welcome)` 异步执行,不阻塞原请求。输入含 `event`、`timestamp`、`payload`。payload 中可解析的 `user_id` / `actor_user_id` 会作为继承身份;无法解析时才使用系统身份。
|
|
70
|
-
|
|
71
|
-
### Scheduled
|
|
72
|
-
|
|
73
|
-
`app.Schedule("0 2 * * *", cleanup)` 使用五字段 cron,也支持 `interval:5m`。定时任务没有用户调用者;执行空间来自服务持久化的 ResourceOwnership,普通 SDK 调用必须改用 `ctx.Admin.*`,运行时不会猜测用户或回退到 platform。
|
|
74
|
-
|
|
75
|
-
## 试运行
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
# Route;request.json 是 handler input object
|
|
79
|
-
draftgo test custom-services 12 --source draft --handler route:POST:/orders --input request.json
|
|
80
|
-
|
|
81
|
-
# Event / Scheduled / 直接 handler 名
|
|
82
|
-
draftgo test custom-services 12 --handler event:order.paid --input event.json
|
|
83
|
-
draftgo test custom-services 12 --handler scheduled:weekday-report
|
|
84
|
-
draftgo test custom-services 12 --handler health
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
`--source draft` 是默认值,会提交并验证当前工作树草稿;`--source published` 直接试运行已发布版本,不提交本地改动;`auto` 由服务生命周期决定。`--headers headers.json` 与 `--user user.json` 注入测试上下文,文件必须是 UTF-8 JSON object。默认 `--side-effect-policy deny`:外部 HTTP、通知和 AI 调用会失败;用 `mock` 返回模拟结果。只有用户明确要求真实副作用时才使用 `--side-effect-policy live --test-write`。数据库等 SDK 写操作同样必须显式 `--test-write`。试运行结果包含 execution ID、来源 revision、状态、输出、HTTP 状态/响应头、日志和 SDK operation 摘要。
|
|
88
|
-
|
|
89
|
-
## SDK
|
|
90
|
-
|
|
91
|
-
所有平台访问通过受控 RPC 返回主服务,不自行读取宿主环境变量、数据库连接或 token。
|
|
92
|
-
|
|
93
|
-
| 入口 | 核心能力 |
|
|
94
|
-
|---|---|
|
|
95
|
-
| `ctx.DB` | Create/CreateMany/Get/Update/UpdateMany/Delete/Query |
|
|
96
|
-
| `ctx.Users` | Get/List/Update |
|
|
97
|
-
| `ctx.Auth` | RequireLogin/RequireAdmin/RequireRole/CurrentUser |
|
|
98
|
-
| `ctx.Notify` | 站内通知 |
|
|
99
|
-
| `ctx.HTTP` | Get/Post/Put/Patch/Delete |
|
|
100
|
-
| `ctx.Cache` / `Config` | 缓存与系统配置 |
|
|
101
|
-
| `ctx.AIHub` | Agent/模型推理、图片、Embedding、AI 资产与运行记录 |
|
|
102
|
-
| `ctx.Knowledge` | 知识库、文档、Chunk、上传、检索和重建 |
|
|
103
|
-
| `ctx.Memory` | 长期记忆与全局检索配置 |
|
|
104
|
-
| `ctx.Files` | 文件夹、资产上传/下载、绑定、回收站和恢复 |
|
|
105
|
-
| `ctx.Scope` | 当前 platform/space、principal 与 ResourceOwnership 授权检查 |
|
|
106
|
-
| `ctx.Billing` | 账本、权益、支付、套餐、订阅和 AI 计费;所有写操作按实时权限与风险契约执行 |
|
|
107
|
-
|
|
108
|
-
常用示例:
|
|
109
|
-
|
|
110
|
-
```go
|
|
111
|
-
records, err := ctx.DB.Query("order", sdk.QueryOptions{
|
|
112
|
-
Filters: map[string]any{"status": "paid"}, Page: 1, PageSize: 20,
|
|
113
|
-
})
|
|
114
|
-
reply, err := ctx.AIHub.Chat(ctx.Context(), sdk.AIChatRequest{
|
|
115
|
-
AgentID: 12, Message: "总结订单",
|
|
116
|
-
})
|
|
117
|
-
response, err := ctx.HTTP.Get(ctx.Context(), "https://api.example.com/health", nil, 10*time.Second)
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
精确签名以 checkout 后自动生成的 `.draftgo-sdk/billing.go` 为准;该文件由 CLI 同步当前 SDK,只读不改。不要猜方法名、字段或直接调用账务 HTTP API。
|
|
121
|
-
|
|
122
|
-
### Billing 决策与用法
|
|
123
|
-
|
|
124
|
-
需要余额、退款、权益、套餐或订阅时使用 `ctx.Billing` / `ctx.Admin.Billing`,不得用动态 DB、缓存或自行维护余额重建金钱状态。金额统一使用最小货币单位:人民币 `AmountMinor: 990` 表示 9.90 元。
|
|
125
|
-
|
|
126
|
-
| 场景 | 方法 |
|
|
127
|
-
|---|---|
|
|
128
|
-
| 当前调用者操作服务归属内账务 | `ctx.Billing.*` |
|
|
129
|
-
| 自定义服务执行系统级账务管理 | `ctx.Admin.Billing.*`;单次调用按系统全权限执行并审计 |
|
|
130
|
-
| 金额已确定且应立即扣除 | `DebitAccount` |
|
|
131
|
-
| 最终金额不确定或业务可能失败 | `HoldFunds` -> `SettleHold`;失败时 `ReleaseHold` |
|
|
132
|
-
| 更正已入账流水 | `ReverseJournal`,不要用反向充值伪造冲正 |
|
|
133
|
-
| 次数、额度或功能许可 | `ConsumeEntitlement`,不要混用钱包余额 |
|
|
134
|
-
| 三方支付退款 | `RefundPaymentOrder` |
|
|
135
|
-
|
|
136
|
-
直接扣款示例:
|
|
137
|
-
|
|
138
|
-
```go
|
|
139
|
-
result, err := ctx.Billing.DebitAccount(ctx.Context(), sdk.MoneyOperationRequest{
|
|
140
|
-
AmountMinor: 990,
|
|
141
|
-
Reference: "order:20260829-001",
|
|
142
|
-
Reason: "购买专业版功能",
|
|
143
|
-
IdempotencyKey: "order:20260829-001:debit",
|
|
144
|
-
})
|
|
145
|
-
if err != nil {
|
|
146
|
-
return ctx.Respond(map[string]any{"error": "扣款失败"}, 409, nil), nil
|
|
147
|
-
}
|
|
148
|
-
return ctx.Respond(map[string]any{
|
|
149
|
-
"journal_id": result.JournalID,
|
|
150
|
-
"balance_minor": result.BalanceMinor,
|
|
151
|
-
}, 200, nil), nil
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
每个金额写操作都必须使用可持久复用、业务唯一的 `IdempotencyKey`,推荐 `{业务类型}:{业务ID}:{动作}`;不得使用时间戳或每次重试生成的新 UUID。相同 key 的相同请求安全重试;同一 key 改变目标或金额会发生幂等冲突。余额不足时操作失败且不会产生负余额。
|
|
155
|
-
|
|
156
|
-
预授权流程必须为 `HoldFunds` -> `SettleHold` / `ReleaseHold`,三个动作分别使用稳定幂等键,并持久化返回的 `HoldID`。不确定远端调用是否已成功时,先按业务 ID 或流水回读,不自动换 key 重扣。日志只能记录业务引用、`JournalID` / `HoldID` 和脱敏错误,不记录完整用户资料或支付凭据。
|
|
157
|
-
|
|
158
|
-
试运行真实账务写入必须显式执行:
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
draftgo test custom-services 12 --source draft --handler route:POST:/charge --input request.json --side-effect-policy live --test-write
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
只验证编译与路由时不要开启真实写入。涉及金额的测试使用专门测试账户、最小金额和唯一业务 ID,完成后回读余额与流水;禁止在生产用户账户上试扣。
|
|
165
|
-
|
|
166
|
-
HTTP host 可由 `config.http_allowed_hosts` 限制;timeout 最终为 1-30 秒,响应体最大 5 MB。日志每次执行最多 500 条、单条 4096 字符,不记录 token、Cookie、密码或完整个人信息。
|
|
167
|
-
|
|
168
|
-
### AI 权限
|
|
169
|
-
|
|
170
|
-
普通 AIHub、Knowledge、Memory 使用调用者与服务 Grant 的权限交集。AIHub 使用 `aihub:*`,Knowledge 独立使用 `knowledge:*`;两组权限互不包含。显式 `ctx.Admin.AIHub`、`ctx.Admin.Knowledge`、`ctx.Admin.Memory` 对单次可信服务调用启用管理提升并写入审计。
|
|
171
|
-
|
|
172
|
-
服务不再配置 AI 启用开关、图片开关、直接模型调用开关、Agent 白名单或模型白名单;`config.aihub` 会被拒绝。Agent 与模型是否可调用直接跟随 AI 板块自身配置。普通调用按调用者与服务 Grant 的交集鉴权;只有源码中显式选择的 `ctx.Admin.*` 才启用单次可信管理提升。
|
|
173
|
-
|
|
174
|
-
typed helper 未覆盖的新端点可使用 `ctx.AIHub.Request`,但 path 只能位于平台允许的 AI/Knowledge/Memory 前缀,不能传外部 URL、路径穿越或内嵌 query。
|
|
175
|
-
|
|
176
|
-
## 权限与运行
|
|
177
|
-
|
|
178
|
-
- `scripts:read/create/update/delete/execute` 控制服务管理。
|
|
179
|
-
- Role 不带作用域;服务必须通过覆盖持久化 ResourceOwnership 的有效 AccessGrant 授权。工作区成员关系不能单独授权。
|
|
180
|
-
- platform Grant 可跨空间但只能使用显式权限;space Grant 不能跨根。请求中的范围不能覆盖服务已保存的归属。
|
|
181
|
-
- 普通服务调用访问动态 DB 时仍受目标 DB 的 DataRange(`none` / `own` / `all`)限制;`ctx.Admin.*` 只应用于源码明确选择的单次可信管理操作,不能从请求参数隐式开启。
|
|
182
|
-
- Route 不接受服务级 `permission`;入口由宿主认证、服务 ownership 和调用者 `scripts:execute` AccessGrant 控制,业务级公开/登录规则在 handler 内显式实现。
|
|
183
|
-
- 定时任务和事件没有调用者,普通 `ctx.xxx` 直接返回 403;必须在对应单次调用显式使用 `ctx.Admin.*`。服务归属仍由持久化 ResourceOwnership 提供,不创建服务主体授权。
|
|
184
|
-
- 平台运行时注入真实 user 或 system principal;不要在 `service.json`、源码、测试参数或日志中保存/模拟服务身份凭据。Route 普通调用按用户 AccessGrant,Event/Scheduled 仅允许 Admin RPC。
|
|
185
|
-
- `config.timeout`、`max_concurrency`、`queue_timeout_ms` 控制执行;Route 饱和返回 429。
|
|
186
|
-
- 超时会终止独立子进程。运行器不是不可信多租户安全沙箱,只授予可信编辑者服务权限。
|
|
187
|
-
- 构建键覆盖源码、依赖、SDK 和 Runner 协议;有效验证凭证绑定该键,任一部分变化都必须重新验证。
|
|
188
|
-
|
|
189
|
-
## 实时 API
|
|
190
|
-
|
|
191
|
-
管理接口使用标准 `{code,data,message}` 信封。未知 operation 才 search,首次使用或 registry revision 变化时 describe;完整服务正文不进入 MCP 参数。
|
|
192
|
-
|
|
193
|
-
有效 `app.Route` 会按实际 method 和 `/api/x/{slug}/{path}` 动态加入 MCP registry。运行时响应由 handler 决定,不强制管理 API 信封;精确匹配、身份继承和执行限制仍以本文件为准。
|
|
194
|
-
|
|
195
|
-
## 完成条件
|
|
196
|
-
|
|
197
|
-
- `package main` 且实现无业务副作用的 `Register(app *sdk.App)`。
|
|
198
|
-
- `service.go`、`service.json` 齐全,handler 仅在 `Register` 注册,服务端状态字段未被手工伪造。
|
|
199
|
-
- trigger 与 handler 签名正确,第三方依赖已锁定。
|
|
200
|
-
- Route 权限、管理员调用和返回脱敏符合真实调用者范围。
|
|
201
|
-
- `draftgo commit custom-services` 成功并显示 published;需要运行行为证据时执行相关 `draftgo test`。
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const log = require('../logger');
|
|
6
|
-
const services = require('../customServices');
|
|
7
|
-
const { report, formatSummary, formatStat } = require('../diffReport');
|
|
8
|
-
|
|
9
|
-
function ids(positional) { return positional.map(String).filter(Boolean); }
|
|
10
|
-
function isServiceType(value) {
|
|
11
|
-
return String(value || '').toLowerCase() === 'custom-services';
|
|
12
|
-
}
|
|
13
|
-
function serviceIds(positional) {
|
|
14
|
-
const values = ids(positional);
|
|
15
|
-
return isServiceType(values[0]) ? values.slice(1) : [];
|
|
16
|
-
}
|
|
17
|
-
function output(flags, value) {
|
|
18
|
-
if (flags.output === 'json') console.log(JSON.stringify(value, null, 2));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function checkout(projectDir, positional, flags = {}) {
|
|
22
|
-
const values = ids(positional);
|
|
23
|
-
if (!values.length) { log.err('Usage: draftgo checkout custom-services <id...>'); return 1; }
|
|
24
|
-
const result = await services.checkout(projectDir, values, { force: Boolean(flags.force) });
|
|
25
|
-
if (flags.output === 'json') output(flags, result);
|
|
26
|
-
else result.forEach((item) => log.ok(`custom service ${item.resource_id} -> ${item.local_path}`));
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function commit(projectDir, positional, flags = {}) {
|
|
31
|
-
const values = ids(positional);
|
|
32
|
-
if (!values.length) { log.err('Usage: draftgo commit custom-services <id...>'); return 1; }
|
|
33
|
-
// The user-facing custom-service commit is a complete delivery operation.
|
|
34
|
-
const result = await services.publish(projectDir, values);
|
|
35
|
-
if (flags.output === 'json') output(flags, result);
|
|
36
|
-
else result.forEach((item) => log.ok(`custom service ${item.id || item.resource_id}: published (commit + validate + publish)`));
|
|
37
|
-
return 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function diff(projectDir, positional, flags = {}) {
|
|
41
|
-
const id = positional[0];
|
|
42
|
-
if (!id) { log.err('Usage: draftgo diff custom-services <id>'); return 1; }
|
|
43
|
-
const result = services.diff(projectDir, id);
|
|
44
|
-
const concise = Boolean(flags.stat || flags.summary);
|
|
45
|
-
const value = concise ? report(result.entry, result.file_outputs.map((file) => ({
|
|
46
|
-
path: file.filename,
|
|
47
|
-
changed: file.changed,
|
|
48
|
-
output: file.output,
|
|
49
|
-
base_path: path.join(result.entry.base_dir, file.filename),
|
|
50
|
-
local_path: path.join(result.entry.local_dir, file.filename),
|
|
51
|
-
}))) : null;
|
|
52
|
-
if (flags.output === 'json' && concise) output(flags, value);
|
|
53
|
-
else if (flags.output === 'json') output(flags, result);
|
|
54
|
-
else if (flags.stat) process.stdout.write(formatStat(value));
|
|
55
|
-
else if (flags.summary) process.stdout.write(formatSummary(value));
|
|
56
|
-
else if (result.changed) process.stdout.write(result.output);
|
|
57
|
-
else log.dim(`custom service ${id}: no local changes`);
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function validate(projectDir, positional, flags = {}) {
|
|
62
|
-
const id = serviceIds(positional)[0];
|
|
63
|
-
if (!id) { log.err('Usage: draftgo validate custom-services <id>'); return 1; }
|
|
64
|
-
const result = await services.validate(projectDir, id);
|
|
65
|
-
if (flags.output === 'json') output(flags, result); else log.ok(`custom service ${id}: ${result.validation_status}`);
|
|
66
|
-
return result.validation_status === 'passed' ? 0 : 1;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function test(projectDir, positional, flags = {}) {
|
|
70
|
-
const id = serviceIds(positional)[0];
|
|
71
|
-
if (!id) { log.err('Usage: draftgo test custom-services <id> [--input <json-file>]'); return 1; }
|
|
72
|
-
let input = {};
|
|
73
|
-
if (flags.input) input = JSON.parse(fs.readFileSync(String(flags.input), 'utf8'));
|
|
74
|
-
const readJSON = (value) => value ? JSON.parse(fs.readFileSync(String(value), 'utf8')) : undefined;
|
|
75
|
-
const policy = String(flags['side-effect-policy'] || 'deny').toLowerCase();
|
|
76
|
-
if (!['deny', 'mock', 'live'].includes(policy)) throw new Error('--side-effect-policy must be deny, mock, or live.');
|
|
77
|
-
const source = String(flags.source || 'draft').toLowerCase();
|
|
78
|
-
if (!['auto', 'draft', 'published'].includes(source)) throw new Error('--source must be auto, draft, or published.');
|
|
79
|
-
const result = await services.test(projectDir, id, input, { handler: flags.handler, headers: readJSON(flags.headers), user: readJSON(flags.user),
|
|
80
|
-
testWrite: Boolean(flags['test-write']), sideEffectPolicy: policy, source });
|
|
81
|
-
if (flags.output === 'json') output(flags, result);
|
|
82
|
-
else log[result.status === 'success' ? 'ok' : 'err'](`custom service ${id}: ${result.status}`);
|
|
83
|
-
return result.status === 'success' ? 0 : 1;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function publish(projectDir, positional, flags = {}) {
|
|
87
|
-
const values = serviceIds(positional);
|
|
88
|
-
if (!values.length) { log.err('Usage: draftgo publish custom-services <id...>'); return 1; }
|
|
89
|
-
const result = await services.publish(projectDir, values);
|
|
90
|
-
if (flags.output === 'json') output(flags, result);
|
|
91
|
-
else values.forEach((id) => log.ok(`custom service ${id}: published`));
|
|
92
|
-
return 0;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
module.exports = { checkout, commit, diff, validate, test, publish, serviceIds, isServiceType };
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const log = require('../logger');
|
|
4
|
-
const { callOperation } = require('./api');
|
|
5
|
-
|
|
6
|
-
// DataRange is represented by db_meta record policies; it is not a second
|
|
7
|
-
// authorization scope or a free-form client-side filter.
|
|
8
|
-
const OPERATIONS = Object.freeze({
|
|
9
|
-
list: 'list_meta_api_db_meta_get',
|
|
10
|
-
create: 'create_meta_api_db_meta_post',
|
|
11
|
-
get: 'get_meta_api_db_meta__type__get',
|
|
12
|
-
update: 'update_meta_api_db_meta__id__put',
|
|
13
|
-
batch: 'update_meta_batch_api_db_meta_batch_patch',
|
|
14
|
-
delete: 'delete_meta_api_db_meta__id__delete',
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
function operationKey(value) {
|
|
18
|
-
const action = String(value || 'list').trim().toLowerCase();
|
|
19
|
-
return OPERATIONS[action] ? action : '';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function dataRangeCommand(projectDir, positional, flags = {}) {
|
|
23
|
-
const action = operationKey(positional[0]);
|
|
24
|
-
if (!action) {
|
|
25
|
-
log.err('Usage: draftgo data-range list|create|get|update|batch|delete --input <json-file>');
|
|
26
|
-
return 1;
|
|
27
|
-
}
|
|
28
|
-
return callOperation(projectDir, OPERATIONS[action], flags);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
module.exports = dataRangeCommand;
|
|
32
|
-
module.exports.OPERATIONS = OPERATIONS;
|
|
33
|
-
module.exports.operationKey = operationKey;
|