Compare commits

..
Author SHA1 Message Date
weichenandCursor 9b8cf79c96 Add optional tools, model, and maxTurns to prompt modules
Why: User overrides of a subagent profile need a place on the shared contract, and empty fields must stay omitted so older modules keep their meaning.
Evidence: go test ./... passed, including the new prompt_module_subagent golden.
Impact: Existing modules serialize the same. Frontend and UI contracts still need a copy of the type.
Refs: feature/agent-harness-p3b
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 15:21:47 +00:00
weichen 493a846f07 Accept plugin manifest apiVersion 2.0-experimental
Why: Downstream repositories need a versioned public manifest for skills, MCP, knowledge, parsers, agents, channels, and pages without forking the base.

Evidence: go test ./... passed after omitzero on empty scope and backend so the golden plugin_manifest stays stable.

Impact: apiVersion 1.0 remains accepted. New provides fields are optional. Merge without squash so Backend can pin this commit.

Refs: Agent-System-Dev feature/base-extension-roadmap
2026-10-01 10:47:10 +00:00
weichen 447ea5c5ae Merge remote-tracking branch 'origin/main' into feature/base-extension-roadmap 2026-10-01 10:46:55 +00:00
weichenandCursor fdc0c1b933 Accept plugin manifest apiVersion 2.0-experimental
Why: Downstream repositories need a versioned public manifest for skills, MCP, knowledge, parsers, agents, channels, and pages without forking the base.
Evidence: go test ./... passed after omitzero on empty scope and backend so the golden plugin_manifest stays stable.
Impact: apiVersion 1.0 remains accepted. New provides fields are optional. Scope and backend are omitted when zero.
Refs: docs/proposals/2026-10-01-plugin-contract-v2.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 10:34:29 +00:00
weichen b80407a9a6 Merge pull request 'Add thinking duration to message parts' (#13) from feature/chat-timeline-preview into main
Merged by the timeline task after the owner asked to land the open pull requests.
2026-10-01 07:38:46 +00:00
weichenandCursor d4375300cd Add thinking duration to message parts
Why: The chat timeline needs a persisted thinking duration, and tool duration already lives on the tool call, so the thinking part had nowhere to put it.
Evidence: go test ./... passes; the message golden now includes durationMs on the thinking part.
Impact: Additive JSON field with omitempty. Existing clients ignore unknown fields.
Refs: feature/chat-timeline-preview
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 06:03:19 +00:00
weichen 1b04a34bbc Merge pull request 'Extract project channel key helpers (CF3 review fix)' (#12) from feature/project-inbox-r1 into main 2026-09-30 19:10:08 +00:00
weichen c8266fef42 Merge main into feature/project-inbox-r1 (sync before merge per branch protection) 2026-09-30 19:10:04 +00:00
weichen ed579bf29c Extract project channel key helpers (CF3 review fix)
Why: project channel convention (pseudo-key project:<projectId>) was
duplicated across runmgr/server/store; centralize so replay envelopes
match live stamping.
Evidence: go test green.
Impact: no behavior change; enables store replay stamping.
2026-09-30 19:07:06 +00:00
weichen f8b77c0d02 Merge pull request 'Add ProjectID to stream event envelope (CF3 project channel)' (#11) from feature/project-inbox-r1 into main 2026-09-30 18:56:52 +00:00
weichen 0b3392acea Add ProjectID to stream event envelope for project channel (CF3)
Why: D50 approved — the project inbox needs its own SSE stream reusing the
conversation event mechanism via the pseudo-key project:<projectId>; the
envelope stamps project ownership (additive field).
Evidence: conformance golden event_project_stream; go test green.
Impact: additive only (omitempty); consumers ignore unknown fields.
Refs: documents proposal 2026-09-30-card-stream-artifact-unify-project-inbox.md (D50).
2026-09-30 18:56:47 +00:00
weichen 90b57477bb Merge pull request 'Add Hidden to ToolDesc for reserved tool capabilities' (#10) from feature/file-preview-caps into main 2026-09-30 17:20:20 +00:00
weichen 6bdc4f2ada Add Hidden to ToolDesc for reserved tool capabilities
Why: the fs tool family (proposal 2026-09-30-file-preview-and-reserved-
capabilities) is implemented but must stay invisible to models and tool
listings until a runtime gate opens it.

Evidence: new bool field with json omitempty; no golden samples carry it;
go test ./... green.

Impact: registries can mark gated tools; servers and runners filter on
it (backend PR in the same task).

Refs: task feature/file-preview-caps.
2026-09-30 17:13:15 +00:00
weichen cafd9a54e5 Merge pull request 'Add CardID to CardPart for inbox-reference card parts' (#8) from feature/cardflow-r1 into main 2026-09-30 17:10:52 +00:00
weichen a64fb5a168 Add CardID to CardPart for inbox-reference card parts
Why: 卡流机制(CF1)需要消息部件正式引用收件箱 ActivityCard——submit_card 产卡时卡片为产物权威载体,消息只存引用+锚点渲染(原仅能从 data 字符串里正则抠 card:<id>)。
Evidence: go test ./... 通过;golden message.json 增 card 引用部件锁定 cardId 线格式(-update 重生成后逐字节一致)。
Impact: CardPart 增可选 cardId(omitempty,旧数据零影响);ts/types.ts 同步,镜像经 make contracts 再生成。
Refs: Dev 任务卡 feature/cardflow-r1;提案 cardflow-and-process-split(编制中)。
2026-09-30 16:10:20 +00:00
weichen c9aff06ed5 Merge pull request 'Record Gitea access facts in AGENTS.md' (#7) from feature/gitea-access-facts into main 2026-09-30 16:08:25 +00:00
weichen e6706450e4 Record Gitea access facts in AGENTS.md
Why: agents rediscovered Gitea hosts and credential locations from
scratch each session, and the retired git-panel host kept failing with
TLS broken-pipe errors before the right host was recalled.

Evidence: curl git-home...:30319/api/v1/version with token from
~/.secrets/gitea -> {"version":"1.27.3"}; ssh -p 31411 git@git-ssh...
(BatchMode) -> "Hi there, weichen!" with key ~/.ssh/home_gitea (ED25519,
SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20). Same section already
merged in Agent-System-Dev (PR #73).

Impact: AGENTS.md only; records hosts and credential locations, no
secret values (baseline hard boundary 2).

Refs: Agent-System-Dev PR #73
2026-09-30 16:06:15 +00:00
weichen fd441794b1 Merge pull request 'Add cross-session flags to the Conversation contract' (#6) from feature/cross-session-reading into main 2026-09-30 15:00:44 +00:00
weichen 284e8f365f Add cross-session flags to the Conversation contract
Why: cross-session transcript reading needs per-conversation outbound
(allowCrossRead, default true) and inbound (crossPrivate, default false)
flags alongside the project-level gate; see the approved proposal.

Evidence: go test ./... passes with golden conversation.json regenerated
to exercise both wire fields.

Impact: additive contract fields (omitempty), wire-compatible for
existing clients that ignore unknown fields.

Refs: Agent-System-Documents proposals/2026-09-30-cross-session-reading.md
(Dev task feature/cross-session-reading).
2026-09-30 15:00:22 +00:00
weichen 10a84345b7 Merge pull request 'Add the avatar vocabulary to UINode (UI capability platform)' (#5) from feature/ui-capability-platform into main 2026-09-30 14:22:27 +00:00
weichen e5c9c540cd Add the avatar vocabulary to UINode (UI capability platform)
Why: plugins and the host need one shared avatar primitive for member/
owner displays in cards, messages, and admin surfaces (user decision
2026-09-30: avatars, cards, settings become base capabilities plugins
reuse directly).

Evidence: go test green with regenerated golden (single avatar with
sub/tone plus a stacked group); omitempty keeps existing wire samples
byte-stable.

Impact: additive optional UINode field + type; unknown-type consumers
ignore it (forward compat). Renderers gain 'avatar' via the ui repo.

Refs: Agent-System-Documents proposal 2026-09-30-ui-redesign-and-auth.md
2026-09-30 14:22:10 +00:00
weichen 9c4d163333 Merge pull request 'Add pinned and archived flags to Conversation (R1)' (#4) from feature/task-sidebar-r1 into main 2026-09-30 10:45:12 +00:00
16 changed files with 380 additions and 73 deletions
+17
View File
@@ -17,3 +17,20 @@
- 模块边界、公开契约与隔离档位见 `policy/modules/`(如有)与 PROJECT.md。
- 跨仓 gitlink 变更须经组合验证后在 Dev 仓推进;受保护分支经 PR 合入。
- 验证命令见 PROJECT.md「验证」节;完成声明须附实际命令输出证据。
## Gitea 与 Git 访问事实(2026-09-30 实测)
历史文档与旧链接里的 `git-panel.u1680832.nyat.app:30319` 已失效(TLS 握手失败),
见到一律按过期处理,不要再试。当前权威通道:
- **Web / API(HTTPS)**:`https://git-home.u1680832.nyat.app:30319`(Gitea 1.27.3,
API 前缀 `/api/v1`;偶发 broken pipe / 超时,重试即可)。
- **Git over SSH**:`ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/<Repo>.git`
(各仓 origin 与 gitlink 均为此格式)。
- **API token**:`~/.secrets/gitea`,两行键值 `URL_PREFIX=…` 与
`ACCESS_TOKEN=<40 位 hex>`;取值 `grep ACCESS_TOKEN ~/.secrets/gitea | cut -d= -f2-`,
用作 `Authorization: token <值>` 请求头。**token 值不入仓、不回显、不写进产出物**
(baseline 硬边界 2:只记录来源、字段与可用状态)。
- **SSH key**:`~/.ssh/home_gitea`(ED25519,公钥指纹
`SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20`,Gitea 侧密钥名 GMKtec);
`~/.ssh/config` 已有 `git-ssh.u1680832.nyat.app` 主机条目,克隆/推送直接可用。
+24 -3
View File
@@ -42,11 +42,12 @@ func sampleMessage() Message {
ID: "msg_abc123def456", ConversationID: "cnv_abc123def456", RunID: "run_abc123def456",
Role: RoleAssistant,
Parts: []Part{
{Type: PartThinking, Text: "考虑先检索…", Process: true},
{Type: PartThinking, Text: "考虑先检索…", Process: true, DurationMs: 1500},
{Type: PartToolCall, Process: true, ToolCall: &ToolCallPart{ID: "call_1", Name: "history_search", Arguments: `{"q":"部署"}`, Duration: 12.5}},
{Type: PartToolResult, Process: true, ToolResult: &ToolResultPart{CallID: "call_1", Status: "ok", Summary: "3 条命中"}},
{Type: PartText, Text: "根据历史记录……"},
{Type: PartArtifactRef, Artifact: &ArtifactRef{ArtifactID: "art_abc123def456", Name: "报告.md", Kind: "document"}},
{Type: PartCard, Card: &CardPart{Kind: "result", Data: []UINode{{Type: "markdown", Markdown: "全文见卡片"}}, CardID: "card_abc123def456"}},
},
}
}
@@ -59,11 +60,19 @@ func sampleRun() Run {
}
}
func TestGoldenPromptModule(t *testing.T) {
golden(t, "prompt_module_subagent", PromptModule{
ID: "agent.sub.explore", Layer: LayerAgent, Version: 1,
Title: "探索子代理", Body: "只读检索。",
Tools: []string{"knowledge_search", "web_search"}, Model: "lightweight", MaxTurns: 8,
})
}
func TestGoldenDomain(t *testing.T) {
golden(t, "message", sampleMessage())
golden(t, "run", sampleRun())
golden(t, "project", Project{ID: "prj_abc123def456", Name: "示例项目"})
golden(t, "conversation", Conversation{ID: "cnv_abc123def456", ProjectID: "prj_abc123def456", Title: "新会话"})
golden(t, "conversation", Conversation{ID: "cnv_abc123def456", ProjectID: "prj_abc123def456", Title: "新会话", AllowCrossRead: true, CrossPrivate: true})
golden(t, "artifact", Artifact{ID: "art_abc123def456", ProjectID: "prj_abc123def456", Name: "报告.md", Kind: "document", MIME: "text/markdown", Size: 128})
}
@@ -124,7 +133,19 @@ func TestGoldenCardEvents(t *testing.T) {
Payload: json.RawMessage(`{"cardId":"card_abc123def456","actionId":"escalate"}`)})
}
func TestGoldenProjectStreamEvent(t *testing.T) {
// 项目流(D50):伪会话键 "project:<projectId>" 复用会话流机制;信封带 ProjectID。
golden(t, "event_project_stream", StreamEvent{Seq: 47, ConversationID: "project:prj_abc123def456", ProjectID: "prj_abc123def456", TS: "2026-09-30T11:00:00Z", V: 1, Type: EvCardCreated,
Payload: json.RawMessage(`{"card":{"id":"card_def456abc789","projectId":"prj_abc123def456","kind":"result","title":"项目级卡","status":"done","createdAt":"2026-09-30T11:00:00Z","updatedAt":"2026-09-30T11:00:00Z"}}`)})
}
func TestGoldenUIVocabularies(t *testing.T) {
golden(t, "ui_avatar", []UINode{
{Type: UIAvatar, Avatar: &UIAvatarSpec{Name: "weichen", Sub: "OWNER"}},
{Type: UIAvatar, Avatar: &UIAvatarSpec{
Stack: []UIAvatarSpec{{Name: "甲", Tone: "ok"}, {Name: "乙", Tone: "warn"}, {Name: "丙"}},
}},
})
golden(t, "ui_vocabularies", []UINode{
{Type: UIProgress, Progress: &UIProgressSpec{Label: "市场可行性 78%", Value: 0.78, Tone: "ok"}},
{Type: UICompare, Compare: &UICompareSpec{
@@ -170,7 +191,7 @@ func TestGoldenProfileAndTools(t *testing.T) {
golden(t, "tool_http", ToolDesc{
Name: "http_translate", Description: "调用翻译 API", Source: SourceUser,
Parameters: json.RawMessage(`{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}`),
HTTP: &HTTPToolSpec{Method: "POST", URL: "https://api.example.com/translate", BodyTemplate: `{"q":"{{param.text}}"}`, TimeoutMs: 10000, ResultPath: "data.0"},
HTTP: &HTTPToolSpec{Method: "POST", URL: "https://api.example.com/translate", BodyTemplate: `{"q":"{{param.text}}"}`, TimeoutMs: 10000, ResultPath: "data.0"},
})
}
+20 -1
View File
@@ -1,13 +1,19 @@
package core
import "encoding/json"
import (
"encoding/json"
"strings"
)
// 事件信封:后端 → 前端的唯一增量通道(SSE),持久化后转发,Seq 在会话内单调。
// 消费方对未知 Type 静默忽略(原则 6 前向兼容)。
// 项目流(D50):项目频道事件以伪会话键 "project:<projectId>" 复用同一机制
//(独立 seq、持久化、断线重放),信封以 ProjectID 标识归属(会话流事件为空)。
type StreamEvent struct {
Seq int64 `json:"seq"`
RunID ID `json:"runId,omitempty"`
ConversationID ID `json:"conversationId"`
ProjectID ID `json:"projectID,omitempty"` // 项目流事件的项目归属(D50;只增不删)
TS string `json:"ts"` // RFC3339
V int `json:"v"`
Type StreamEventType `json:"type"`
@@ -16,6 +22,19 @@ type StreamEvent struct {
type StreamEventType string
// 项目流频道约定(D50):项目频道以伪会话键 "project:<projectId>" 复用会话
// 事件的持久化/seq/重放机制;信封 ProjectID 由该键派生(发布与重放同源)。
const projectChannelPrefix = "project:"
// ProjectChannelKey 返回项目流的事件频道键。
func ProjectChannelKey(projectID ID) ID { return ID(projectChannelPrefix + string(projectID)) }
// ProjectFromChannel 反解项目流频道键;非项目流频道返回 false。
func ProjectFromChannel(key ID) (ID, bool) {
pid, ok := strings.CutPrefix(string(key), projectChannelPrefix)
return ID(pid), ok && pid != ""
}
const (
EvMessageDelta StreamEventType = "message.delta"
EvThinkingDelta StreamEventType = "thinking.delta"
+11 -2
View File
@@ -33,6 +33,8 @@ type Conversation struct {
ProfileID ID `json:"profileId,omitempty"` // 空为默认画像
Pinned bool `json:"pinned,omitempty"` // 侧栏置顶(方案 A P6/P8:本端组织偏好,服务端权威)
Archived bool `json:"archived,omitempty"` // 归档:列表默认隐藏,数据保留
AllowCrossRead bool `json:"allowCrossRead,omitempty"` // 跨会话出站:本会话读取同项目其他会话记录(需项目开关 crossSession.read)
CrossPrivate bool `json:"crossPrivate,omitempty"` // 跨会话入站:私有会话,不被他读、不出现在他方列表
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
@@ -87,6 +89,10 @@ type Part struct {
// 过程标记:true 的部件在 run 终态后由前端折叠为摘要行(4.2)。
Process bool `json:"process,omitempty"`
// DurationMs 是 thinking 部件的思考时长(毫秒,从首个推理增量到最后一个)。
// 工具耗时在 ToolCallPart.Duration,不复用本字段。
DurationMs float64 `json:"durationMs,omitempty"`
}
type ToolCallPart struct {
@@ -122,9 +128,12 @@ type ErrorPart struct {
}
// CardPart.Data 是 UI schema 节点(4.6 有限词汇)。
// CardID 非空 = 收件箱 ActivityCard 的引用部件(如 submit_card 产卡:卡片为产物
// 权威载体,消息只存引用+锚点渲染);空 = 插件内嵌卡片部件(自包含 Data)。
type CardPart struct {
Kind string `json:"kind"`
Data []UINode `json:"data"`
Kind string `json:"kind"`
Data []UINode `json:"data"`
CardID ID `json:"cardId,omitempty"`
}
// ---- Run(一切运行皆 Run)----
+52 -43
View File
@@ -8,35 +8,44 @@ import "encoding/json"
const PluginAPIVersion = "1.0"
type PluginManifest struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
APIVersion string `json:"apiVersion"`
Requires PluginRequires `json:"requires,omitempty"`
Provides PluginProvides `json:"provides"`
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
APIVersion string `json:"apiVersion"`
Requires PluginRequires `json:"requires,omitempty"`
Provides PluginProvides `json:"provides"`
Scope PluginScope `json:"scope,omitzero"`
Backend PluginBackend `json:"backend,omitzero"`
}
type PluginRequires struct {
Network []string `json:"network,omitempty"` // 允许访问的域名(展示用,v1 自装自审)
Network []string `json:"network,omitempty"` // 出站域名白名单;v2 空名单拒绝出站,v1 空名单保持放行
}
type PluginProvides struct {
Tools []string `json:"tools,omitempty"` // tools/ 目录下的资产名
Workflows []string `json:"workflows,omitempty"`
Intents []string `json:"intents,omitempty"`
Prompts []string `json:"prompts,omitempty"`
Panels []string `json:"panels,omitempty"`
Cards []string `json:"cards,omitempty"`
Settings []string `json:"settings,omitempty"`
Commands []string `json:"commands,omitempty"` // commands/ 资产名(spec 权威在 Agent-Command 库)
Tools []string `json:"tools,omitempty"` // tools/ 目录下的资产名
Workflows []string `json:"workflows,omitempty"`
Intents []string `json:"intents,omitempty"`
Prompts []string `json:"prompts,omitempty"`
Panels []string `json:"panels,omitempty"`
Cards []string `json:"cards,omitempty"`
Settings []string `json:"settings,omitempty"`
Commands []string `json:"commands,omitempty"` // commands/ 资产名(spec 权威在 Agent-Command 库)
Workspaces []string `json:"workspaces,omitempty"` // workspaces/ 资产名(工作区弹层)
Skills []string `json:"skills,omitempty"`
MCPServers []string `json:"mcpServers,omitempty"`
Knowledge []string `json:"knowledge,omitempty"`
Parsers []string `json:"parsers,omitempty"`
Agents []string `json:"agents,omitempty"`
Channels []string `json:"channels,omitempty"`
Pages []string `json:"pages,omitempty"`
}
// PluginPanel:右栏面板声明(UI schema 静态骨架 + 可选数据提供者)。
type PluginPanel struct {
ID string `json:"id"`
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
ID string `json:"id"`
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Body []UINode `json:"body"`
// Tool 绑定:面板挂载时调用该工具刷新 Body 中的动态区(v1 简化为整体刷新)。
RefreshTool string `json:"refreshTool,omitempty"`
@@ -44,23 +53,23 @@ type PluginPanel struct {
// WorkflowDef:工作流即数据(4.6)。Step 可产 HITL 卡片、调工具、产出工件。
type WorkflowDef struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
IntentID string `json:"intentId,omitempty"` // 绑定意图(命中即启动)
Steps []WorkflowStep `json:"steps"`
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
IntentID string `json:"intentId,omitempty"` // 绑定意图(命中即启动)
Steps []WorkflowStep `json:"steps"`
}
type WorkflowStep struct {
ID string `json:"id"`
Title string `json:"title"`
Kind string `json:"kind"` // prompt | tool | hitl | artifact | card
Prompt string `json:"prompt,omitempty"` // prompt: 发给模型的指令模板
Tool string `json:"tool,omitempty"` // tool: 工具名
Args json.RawMessage `json:"args,omitempty"` // tool: 参数模板
Hitl *HITLCardTemplate `json:"hitl,omitempty"` // hitl: 卡片模板
Artifact string `json:"artifact,omitempty"` // artifact: 产出工件名模板
Card *ActivityCardTemplate `json:"card,omitempty"` // card: 产收件箱任务卡
ID string `json:"id"`
Title string `json:"title"`
Kind string `json:"kind"` // prompt | tool | hitl | artifact | card
Prompt string `json:"prompt,omitempty"` // prompt: 发给模型的指令模板
Tool string `json:"tool,omitempty"` // tool: 工具名
Args json.RawMessage `json:"args,omitempty"` // tool: 参数模板
Hitl *HITLCardTemplate `json:"hitl,omitempty"` // hitl: 卡片模板
Artifact string `json:"artifact,omitempty"` // artifact: 产出工件名模板
Card *ActivityCardTemplate `json:"card,omitempty"` // card: 产收件箱任务卡
}
// ActivityCardTemplate:工作流步骤产卡模板(kind=card)——步骤执行时生成
@@ -76,20 +85,20 @@ type ActivityCardTemplate struct {
// PluginWorkspace:工作区弹层声明(深读与治理视图;Esc/遮罩关闭回对话现场,
// 不承载任何唯一步骤——每个操作必须另有命令路径,提案 D31)。
type PluginWorkspace struct {
ID string `json:"id"`
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Shortcut string `json:"shortcut,omitempty"` // "1"–"9"(⌘N / Ctrl+N 召出)
BadgeTool string `json:"badgeTool,omitempty"` // 角标提供者工具:返回 {count,tone}
Body []UINode `json:"body"`
ID string `json:"id"`
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Shortcut string `json:"shortcut,omitempty"` // "1"–"9"(⌘N / Ctrl+N 召出)
BadgeTool string `json:"badgeTool,omitempty"` // 角标提供者工具:返回 {count,tone}
Body []UINode `json:"body"`
// RefreshTool:挂载时调用刷新 Body 动态区(与 Panel 同机制,v1 整体刷新)。
RefreshTool string `json:"refreshTool,omitempty"`
}
type HITLCardTemplate struct {
Question string `json:"question"`
Kind HITLKind `json:"kind"`
Choices []HITLChoice `json:"choices,omitempty"`
AllowFreeInput bool `json:"allowFreeInput"`
FreeInputHint string `json:"freeInputHint,omitempty"`
Question string `json:"question"`
Kind HITLKind `json:"kind"`
Choices []HITLChoice `json:"choices,omitempty"`
AllowFreeInput bool `json:"allowFreeInput"`
FreeInputHint string `json:"freeInputHint,omitempty"`
}
+100
View File
@@ -0,0 +1,100 @@
package core
// PluginAPIVersionExperimental 是插件契约 v2 的实验窗口。
// 1.0 清单继续有效;v2 只增加可选字段,不收紧 1.0 已有资产。
const PluginAPIVersionExperimental = "2.0-experimental"
// PluginAPIAccepted 报告宿主是否接受该 apiVersion。空值由宿主填成 PluginAPIVersion。
func PluginAPIAccepted(v string) bool {
switch v {
case PluginAPIVersion, PluginAPIVersionExperimental:
return true
default:
return false
}
}
// PluginScope 限制插件对哪些租户/项目可见。两者都空 = 全部可见。
type PluginScope struct {
Tenants []string `json:"tenants,omitempty"`
Projects []string `json:"projects,omitempty"`
}
// Allows 判断 owner/project 是否落在作用域内。
func (s PluginScope) Allows(owner, project string) bool {
if len(s.Tenants) > 0 && !scopeContains(s.Tenants, owner) {
return false
}
if len(s.Projects) > 0 && !scopeContains(s.Projects, project) {
return false
}
return true
}
func scopeContains(xs []string, v string) bool {
for _, x := range xs {
if x == v {
return true
}
}
return false
}
// PluginBackend 声明进程内或 sidecar 代码档。SchemaVersion 是插件自有数据的迁移版本。
type PluginBackend struct {
Mode string `json:"mode,omitempty"` // inproc | sidecar
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
SchemaVersion int `json:"schemaVersion,omitempty"`
}
// PluginSkill 是 skills/<name>.md 的装载结果(SKILL.md 正文)。
type PluginSkill struct {
Name string `json:"name"`
Body string `json:"body"`
}
// PluginMCPServer 是 mcpServers/<name>.json,字段与 agent-native MCP ServerConfig 对齐。
type PluginMCPServer struct {
Name string `json:"name"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
URL string `json:"url,omitempty"`
}
// PluginKnowledgeSet 是知识数据集声明(数据在插件侧,检索引擎在基座)。
type PluginKnowledgeSet struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
}
// PluginParserDecl 是解析器声明。实现可以是内置名或 sidecar 工具名。
type PluginParserDecl struct {
ID string `json:"id"`
Title string `json:"title"`
MIME string `json:"mime,omitempty"`
Tool string `json:"tool,omitempty"`
}
// PluginAgentDecl 是画像/子代理声明。
type PluginAgentDecl struct {
ID string `json:"id"`
Title string `json:"title"`
Instructions string `json:"instructions,omitempty"`
Tools []string `json:"tools,omitempty"`
}
// PluginChannelDecl 是渠道适配器声明(飞书等由下游实现,基座只登记入口)。
type PluginChannelDecl struct {
ID string `json:"id"`
Kind string `json:"kind"`
Title string `json:"title,omitempty"`
}
// PluginPageDecl 是前端 ESM 页面。Module 相对插件目录或绝对 URL。
type PluginPageDecl struct {
ID string `json:"id"`
Title string `json:"title"`
Module string `json:"module"`
}
+26
View File
@@ -0,0 +1,26 @@
package core
import "testing"
func TestPluginAPIAccepted(t *testing.T) {
if !PluginAPIAccepted(PluginAPIVersion) || !PluginAPIAccepted(PluginAPIVersionExperimental) {
t.Fatal("current and experimental windows must be accepted")
}
if PluginAPIAccepted("0.9") || PluginAPIAccepted("") {
t.Fatal("unknown or empty apiVersion must be rejected")
}
}
func TestPluginScopeAllows(t *testing.T) {
open := PluginScope{}
if !open.Allows("a", "p") {
t.Fatal("empty scope is visible to everyone")
}
scoped := PluginScope{Tenants: []string{"acme"}, Projects: []string{"p1"}}
if !scoped.Allows("acme", "p1") {
t.Fatal("matching tenant and project should pass")
}
if scoped.Allows("other", "p1") || scoped.Allows("acme", "p2") {
t.Fatal("scope must reject mismatches")
}
}
+4
View File
@@ -26,6 +26,10 @@ type PromptModule struct {
BasedOnVersion int `json:"basedOnVersion,omitempty"` // 用户副本基于的内置版本
Drifted bool `json:"drifted,omitempty"` // 用户副本落后于当前内置版本(提示用,不阻断)
Disabled bool `json:"disabled,omitempty"` // 用户停用
// 子代理 profile。只对 agent.sub.* 有意义;空字段表示沿用内置。
Tools []string `json:"tools,omitempty"`
Model string `json:"model,omitempty"` // lightweight | chat
MaxTurns int `json:"maxTurns,omitempty"`
}
// PromptTrigger 触发条件;模块命中 = 任一 trigger 命中(再过 RequiresAttachments)。
+2
View File
@@ -2,6 +2,8 @@
"id": "cnv_abc123def456",
"projectId": "prj_abc123def456",
"title": "新会话",
"allowCrossRead": true,
"crossPrivate": true,
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z"
}
+19
View File
@@ -0,0 +1,19 @@
{
"seq": 47,
"conversationId": "project:prj_abc123def456",
"projectID": "prj_abc123def456",
"ts": "2026-09-30T11:00:00Z",
"v": 1,
"type": "card.created",
"payload": {
"card": {
"id": "card_def456abc789",
"projectId": "prj_abc123def456",
"kind": "result",
"title": "项目级卡",
"status": "done",
"createdAt": "2026-09-30T11:00:00Z",
"updatedAt": "2026-09-30T11:00:00Z"
}
}
}
+15 -1
View File
@@ -7,7 +7,8 @@
{
"type": "thinking",
"text": "考虑先检索…",
"process": true
"process": true,
"durationMs": 1500
},
{
"type": "tool_call",
@@ -39,6 +40,19 @@
"name": "报告.md",
"kind": "document"
}
},
{
"type": "card",
"card": {
"kind": "result",
"data": [
{
"type": "markdown",
"markdown": "全文见卡片"
}
],
"cardId": "card_abc123def456"
}
}
],
"createdAt": "0001-01-01T00:00:00Z"
+14
View File
@@ -0,0 +1,14 @@
{
"id": "agent.sub.explore",
"layer": "agent",
"version": 1,
"title": "探索子代理",
"description": "",
"body": "只读检索。",
"tools": [
"knowledge_search",
"web_search"
],
"model": "lightweight",
"maxTurns": 8
}
+27
View File
@@ -0,0 +1,27 @@
[
{
"type": "avatar",
"avatar": {
"name": "weichen",
"sub": "OWNER"
}
},
{
"type": "avatar",
"avatar": {
"stack": [
{
"name": "甲",
"tone": "ok"
},
{
"name": "乙",
"tone": "warn"
},
{
"name": "丙"
}
]
}
}
]
+3
View File
@@ -20,6 +20,9 @@ type ToolDesc struct {
Source SourceKind `json:"source"`
PluginID string `json:"pluginId,omitempty"`
HTTP *HTTPToolSpec `json:"http,omitempty"` // 声明式 HTTP 工具(无代码)
// Hidden 预留能力形态:默认不对模型与 /api/tools 列表可见,须运行时开关
// (如 runtime.fsTools)或管理动作开启后可用(提案 2026-09-30-file-preview-and-reserved-capabilities)。
Hidden bool `json:"hidden,omitempty"`
}
// HTTPToolSpec:无代码自定义工具(4.8)。模板变量 {{param.x}} 从调用参数注入。
+15 -2
View File
@@ -14,6 +14,7 @@ export interface Project {
export interface Conversation {
id: ID; projectId: ID; owner?: ID; title: string; profileId?: ID;
pinned?: boolean; archived?: boolean;
allowCrossRead?: boolean; crossPrivate?: boolean;
createdAt: string; updatedAt: string;
}
@@ -31,9 +32,10 @@ export interface Part {
artifact?: { artifactId: ID; name: string; kind?: string };
hitl?: { cardId: ID; question: string; resolved: boolean; answer?: string };
plan?: PlanSnapshot;
card?: { kind: string; data: UINode[] };
card?: { kind: string; data: UINode[]; cardId?: ID }; // cardId 非空 = 收件箱卡片引用部件(submit_card)
error?: { code: string; message: string };
process?: boolean; // run 终态后折叠为摘要行
durationMs?: number; // thinking 部件的思考时长(毫秒);工具耗时在 toolCall.durationMs
}
export interface Message {
@@ -152,7 +154,7 @@ export interface HITLResponse {
export type UINodeType =
| 'section' | 'list' | 'kv' | 'form' | 'markdown' | 'doc-ref' | 'actions' | 'chart-lite'
| 'progress' | 'compare' | 'tree' | 'table' | 'steps' | 'tabs' | 'timeline';
| 'avatar' | 'progress' | 'compare' | 'tree' | 'table' | 'steps' | 'tabs' | 'timeline';
export interface UINode {
type: UINodeType;
@@ -166,6 +168,7 @@ export interface UINode {
chart?: { kind: 'bar' | 'line'; labels: string[]; series: number[] };
children?: UINode[];
avatar?: { name?: string; sub?: string; tone?: string; stack?: { name?: string; sub?: string; tone?: string }[] };
progress?: { label?: string; value: number; tone?: string };
compare?: {
left: { title?: string; tone?: string; nodes?: UINode[] };
@@ -206,6 +209,8 @@ export interface PromptModule {
body: string; outputContract?: string;
source?: 'builtin' | 'plugin' | 'user'; owner?: string;
basedOnVersion?: number; drifted?: boolean; disabled?: boolean;
/** 子代理 profile。只对 agent.sub.* 有意义;空字段表示沿用内置。 */
tools?: string[]; model?: string; maxTurns?: number;
}
export interface PromptBlock { moduleId: string; version: number; layer: string; body: string; }
@@ -245,7 +250,11 @@ export interface PluginManifest {
tools?: string[]; workflows?: string[]; intents?: string[];
prompts?: string[]; panels?: string[]; cards?: string[]; settings?: string[];
commands?: string[]; workspaces?: string[];
skills?: string[]; mcpServers?: string[]; knowledge?: string[];
parsers?: string[]; agents?: string[]; channels?: string[]; pages?: string[];
};
scope?: { tenants?: string[]; projects?: string[] };
backend?: { mode?: 'inproc' | 'sidecar'; command?: string; args?: string[]; schemaVersion?: number };
}
export interface PluginPanel {
@@ -257,6 +266,10 @@ export interface PluginWorkspace {
badgeTool?: string; body: UINode[]; refreshTool?: string;
}
export interface PluginPageDecl {
id: string; title: string; module: string;
}
// ---- 摄取 / 检索 ----
export interface ParsedDocument {
+31 -21
View File
@@ -17,6 +17,7 @@ const (
UIChartLite UINodeType = "chart-lite"
// —— 收件箱/工作区扩展词汇(提案 D28;graph 远期,追溯视图按"对象为中心 +
// 分组泳道 + 逐层展开"约束设计,禁止力导向裸图)——
UIAvatar UINodeType = "avatar" // 头像(成员/责任人展示;UI 能力平台,插件可用)
UIProgress UINodeType = "progress" // 进度条(摄取/生成/填充度)
UICompare UINodeType = "compare" // 左右对比(基线 vs 新内容、diff 确认)
UITree UINodeType = "tree" // 树(文档树/笔记树骨架;节点带状态灯/徽标/计数)
@@ -27,24 +28,33 @@ const (
)
type UINode struct {
Type UINodeType `json:"type"`
Title string `json:"title,omitempty"`
Items []string `json:"items,omitempty"` // list
KV [][2]string `json:"kv,omitempty"` // kv
Fields []UIField `json:"fields,omitempty"` // form
Markdown string `json:"markdown,omitempty"`
DocID ID `json:"docId,omitempty"` // doc-ref
Actions []UIAction `json:"actions,omitempty"`
Chart *UIChart `json:"chart,omitempty"`
Children []UINode `json:"children,omitempty"` // section
Type UINodeType `json:"type"`
Title string `json:"title,omitempty"`
Items []string `json:"items,omitempty"` // list
KV [][2]string `json:"kv,omitempty"` // kv
Fields []UIField `json:"fields,omitempty"` // form
Markdown string `json:"markdown,omitempty"`
DocID ID `json:"docId,omitempty"` // doc-ref
Actions []UIAction `json:"actions,omitempty"`
Chart *UIChart `json:"chart,omitempty"`
Children []UINode `json:"children,omitempty"` // section
Progress *UIProgressSpec `json:"progress,omitempty"` // progress
Compare *UICompareSpec `json:"compare,omitempty"` // compare
Tree *UITreeSpec `json:"tree,omitempty"` // tree
Table *UITableSpec `json:"table,omitempty"` // table
Steps *UIStepsSpec `json:"steps,omitempty"` // steps
Tabs *UITabsSpec `json:"tabs,omitempty"` // tabs
Timeline *UITimelineSpec `json:"timeline,omitempty"` // timeline
Avatar *UIAvatarSpec `json:"avatar,omitempty"` // avatar
Progress *UIProgressSpec `json:"progress,omitempty"` // progress
Compare *UICompareSpec `json:"compare,omitempty"` // compare
Tree *UITreeSpec `json:"tree,omitempty"` // tree
Table *UITableSpec `json:"table,omitempty"` // table
Steps *UIStepsSpec `json:"steps,omitempty"` // steps
Tabs *UITabsSpec `json:"tabs,omitempty"` // tabs
Timeline *UITimelineSpec `json:"timeline,omitempty"` // timeline
}
// UIAvatar:头像(成员/责任人;Name 取首字,Tone 走语义色;Stack 为叠放组)。
type UIAvatarSpec struct {
Name string `json:"name,omitempty"`
Sub string `json:"sub,omitempty"` // 名字旁的小注(角色/状态)
Tone string `json:"tone,omitempty"` // ok/warn/danger/info/留空=品牌色
Stack []UIAvatarSpec `json:"stack,omitempty"` // 叠放头像组(多成员)
}
// UIProgress:0–1 归一进度或 n/m 计数(Value=Done/Total 任一形式,前端按 <1 判定)。
@@ -88,9 +98,9 @@ type UITableColumn struct {
}
type UITableRow struct {
ID string `json:"id,omitempty"`
Cells [][2]string `json:"cells"` // 与 Columns.Key 对应的有序单元格
Actions []UIAction `json:"actions,omitempty"` // 行级操作(走命令总线)
ID string `json:"id,omitempty"`
Cells [][2]string `json:"cells"` // 与 Columns.Key 对应的有序单元格
Actions []UIAction `json:"actions,omitempty"` // 行级操作(走命令总线)
}
type UITableSpec struct {
@@ -146,7 +156,7 @@ type UIField struct {
type UIAction struct {
ID string `json:"id"`
Label string `json:"label"`
Style string `json:"style,omitempty"` // primary | default | danger
Style string `json:"style,omitempty"` // primary | default | danger
Command string `json:"command,omitempty"` // 绑定命令总线命令 id;空则降级为发消息
}