Merge pull request 'Add activity card, card events, HITL anchoring and UI vocabulary extension' (#2) from feature/core-cards-commands-r1 into main
This commit was merged in pull request #2.
This commit is contained in:
+56
@@ -0,0 +1,56 @@
|
||||
package core
|
||||
|
||||
import "time"
|
||||
|
||||
// ActivityCard:异步任务/待确认事项的统一状态对象——右栏收件箱的条目。
|
||||
// 与一次性消息相对:卡片是有生命周期的状态(进行中→完成/待办→已决/失败/弃置),
|
||||
// 由服务端权威维护,经 card.* 事件驱动前端投影;确认动作回写对话时间线并留审计。
|
||||
//
|
||||
// 生命周期:active → done | action_required → resolved | failed | dismissed。
|
||||
// Kind 决定卡身的默认内容组合(progress 流式区 / compare 对比 / review-list 裁决清单…),
|
||||
// Body 用 UI 有限词汇渲染;插件可扩展 Kind(前端按未知 kind 走通用卡渲染)。
|
||||
type ActivityCard struct {
|
||||
ID ID `json:"id"`
|
||||
// ProjectID/ConversationID 定位归属;ConversationID 为空表示项目级卡片(远期跨会话收件箱)。
|
||||
ProjectID ID `json:"projectId"`
|
||||
ConversationID ID `json:"conversationId,omitempty"`
|
||||
SourceRunID ID `json:"sourceRunId,omitempty"` // 溯源:产生卡片的 run
|
||||
AnchorMessageID ID `json:"anchorMessageId,omitempty"` // 互定位:出生消息(↙ 定位到对话)
|
||||
Kind CardKind `json:"kind"`
|
||||
Title string `json:"title"`
|
||||
Status CardStatus `json:"status"`
|
||||
Tone string `json:"tone,omitempty"` // ok | warn | danger | info
|
||||
Body []UINode `json:"body,omitempty"`
|
||||
Actions []UIAction `json:"actions,omitempty"` // 走命令总线;未绑定 command 降级为发消息
|
||||
Pinned bool `json:"pinned,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type CardKind string
|
||||
|
||||
const (
|
||||
CardKindProgress CardKind = "progress" // 后台任务进度(流式区 + 进度行)
|
||||
CardKindConfirm CardKind = "confirm" // 待确认(相关性/冲突/门禁/投影)
|
||||
CardKindCompare CardKind = "compare" // 左右对比(基线 vs 新内容)
|
||||
CardKindReviewList CardKind = "review-list" // 逐条裁决清单(候选边/缺项)
|
||||
CardKindResult CardKind = "result" // 结果与产物(结论 + 来源 + 打开入口)
|
||||
CardKindError CardKind = "error" // 失败显式呈现(可重试/可取回)
|
||||
CardKindStatus CardKind = "status" // 常态状态卡(文档进度等)
|
||||
)
|
||||
|
||||
type CardStatus string
|
||||
|
||||
const (
|
||||
CardActive CardStatus = "active" // 进行中
|
||||
CardActionRequired CardStatus = "action_required" // 待用户处理
|
||||
CardDone CardStatus = "done" // 完成(未读结果)
|
||||
CardResolved CardStatus = "resolved" // 已确认/已处理(盖章留痕)
|
||||
CardFailed CardStatus = "failed"
|
||||
CardDismissed CardStatus = "dismissed" // 用户关闭(非确认语义)
|
||||
)
|
||||
|
||||
// Terminal 报告卡片是否处于终态(终态卡进入"已完成"可收纳区)。
|
||||
func (s CardStatus) Terminal() bool {
|
||||
return s == CardResolved || s == CardFailed || s == CardDismissed
|
||||
}
|
||||
+72
-1
@@ -83,6 +83,77 @@ func TestGoldenHITL(t *testing.T) {
|
||||
},
|
||||
AllowFreeInput: true, FreeInputHint: "也可以直接输入其他格式要求",
|
||||
})
|
||||
// 阻断态 + 锚定(内联确认卡:anchorMessageId 内联于对话流;blocked 禁用确认并给出出路)。
|
||||
golden(t, "hitl_card_blocked", HITLCard{
|
||||
ID: "hitl_def456abc789", RunID: "run_1", ConversationID: "cnv_1",
|
||||
AnchorMessageID: "msg_abc123def456",
|
||||
Question: "批准变更 CO-2026-007?", Kind: HITLKindConfirm,
|
||||
Choices: []HITLChoice{
|
||||
{ID: "approve", Label: "确认批准", Description: "变更单进入执行态", Impact: "立即生效并留痕,不可裸文本撤回"},
|
||||
{ID: "reject", Label: "驳回", Description: "关闭变更单并记录理由", Impact: "留痕可审计"},
|
||||
},
|
||||
Blocked: true, BlockReason: "存在未闭环高风险变更单,可先处理后重新检查",
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoldenActivityCard(t *testing.T) {
|
||||
golden(t, "activity_card", ActivityCard{
|
||||
ID: "card_abc123def456", ProjectID: "prj_abc123def456", ConversationID: "cnv_abc123def456",
|
||||
SourceRunID: "run_abc123def456", AnchorMessageID: "msg_abc123def456",
|
||||
Kind: CardKindCompare, Title: "项目执行周期不一致 · 目标已基线",
|
||||
Status: CardActionRequired, Tone: "danger",
|
||||
Body: []UINode{
|
||||
{Type: UICompare, Compare: &UICompareSpec{
|
||||
Left: UICompareSide{Title: "基线 V1.2 · 已评审", Tone: "danger", Nodes: []UINode{{Type: UIMarkdown, Markdown: "12 个月(立项到注册)"}}},
|
||||
Right: UICompareSide{Title: "新资料 · 待确认", Tone: "ok", Nodes: []UINode{{Type: UIMarkdown, Markdown: "18 个月(含临床试验)"}}},
|
||||
}},
|
||||
{Type: UIProgress, Title: "切片进度", Progress: &UIProgressSpec{Label: "切片 24/24", Value: 1, Tone: "ok"}},
|
||||
},
|
||||
Actions: []UIAction{
|
||||
{ID: "escalate", Label: "发起变更流程", Style: "primary", Command: "change.escalate"},
|
||||
{ID: "keep", Label: "维持原样", Style: "danger"},
|
||||
},
|
||||
Pinned: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoldenCardEvents(t *testing.T) {
|
||||
golden(t, "event_card_created", StreamEvent{Seq: 45, RunID: "run_1", ConversationID: "cnv_1", TS: "2026-09-30T10:00:00Z", V: 1, Type: EvCardCreated,
|
||||
Payload: json.RawMessage(`{"card":{"id":"card_abc123def456","projectId":"prj_1","kind":"progress","title":"竞品分析 PPTX 入库","status":"active","createdAt":"2026-09-30T10:00:00Z","updatedAt":"2026-09-30T10:00:00Z"}}`)})
|
||||
golden(t, "event_card_resolved", StreamEvent{Seq: 46, RunID: "run_1", ConversationID: "cnv_1", TS: "2026-09-30T10:05:00Z", V: 1, Type: EvCardResolved,
|
||||
Payload: json.RawMessage(`{"cardId":"card_abc123def456","actionId":"escalate"}`)})
|
||||
}
|
||||
|
||||
func TestGoldenUIVocabularies(t *testing.T) {
|
||||
golden(t, "ui_vocabularies", []UINode{
|
||||
{Type: UIProgress, Progress: &UIProgressSpec{Label: "市场可行性 78%", Value: 0.78, Tone: "ok"}},
|
||||
{Type: UICompare, Compare: &UICompareSpec{
|
||||
Left: UICompareSide{Title: "基线", Tone: "danger"},
|
||||
Right: UICompareSide{Title: "新内容", Tone: "ok", Nodes: []UINode{{Type: UIMarkdown, Markdown: "18 个月"}}},
|
||||
}},
|
||||
{Type: UITree, Tree: &UITreeSpec{Nodes: []UITreeNode{
|
||||
{ID: "stage-1", Label: "商务与策划", Count: 24, Children: []UITreeNode{
|
||||
{ID: "topic-1", Label: "市场调研", Tone: "ok", Badge: "多挂载", Count: 12, Selected: true},
|
||||
}},
|
||||
}}},
|
||||
{Type: UITable, Table: &UITableSpec{
|
||||
Columns: []UITableColumn{{Key: "id", Label: "编号"}, {Key: "title", Label: "标题"}},
|
||||
Rows: []UITableRow{
|
||||
{ID: "ur-005", Cells: [][2]string{{"id", "UR-005"}, {"title", "项目执行周期"}}, Actions: []UIAction{{ID: "trace", Label: "追溯"}}},
|
||||
},
|
||||
}},
|
||||
{Type: UISteps, Steps: &UIStepsSpec{Current: 2, Steps: []UIStep{
|
||||
{Label: "申请", Status: "done"}, {Label: "影响分析", Status: "done"}, {Label: "评审", Status: "active"}, {Label: "批准", Status: "pending"},
|
||||
}}},
|
||||
{Type: UITabs, Tabs: &UITabsSpec{Active: "view", Tabs: []UITab{
|
||||
{ID: "view", Label: "查看", Nodes: []UINode{{Type: UIMarkdown, Markdown: "正文"}}},
|
||||
{ID: "history", Label: "历史版本"},
|
||||
}}},
|
||||
{Type: UITimeline, Timeline: &UITimelineSpec{Items: []UITimelineItem{
|
||||
{Time: "10:42", Title: "v0.6 填入注册分类界定", Tone: "ok", Markdown: "来源 S-121", Ref: "S-121"},
|
||||
{Time: "09-28", Title: "v0.5 更新竞品定价段落", Ref: "陈磊确认"},
|
||||
}}},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoldenProfileAndTools(t *testing.T) {
|
||||
@@ -119,7 +190,7 @@ func TestGoldenPlugin(t *testing.T) {
|
||||
golden(t, "plugin_manifest", PluginManifest{
|
||||
ID: "example", Name: "示例插件", Version: "0.1.0", APIVersion: PluginAPIVersion,
|
||||
Requires: PluginRequires{Network: []string{"api.example.com"}},
|
||||
Provides: PluginProvides{Tools: []string{"http_echo"}, Intents: []string{"demo"}, Panels: []string{"demo"}, Cards: []string{"note"}, Settings: []string{"greeting"}},
|
||||
Provides: PluginProvides{Tools: []string{"http_echo"}, Intents: []string{"demo"}, Panels: []string{"demo"}, Cards: []string{"note"}, Settings: []string{"greeting"}, Commands: []string{"demo.refresh"}, Workspaces: []string{"demo-main"}},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ const (
|
||||
EvRunCompleted StreamEventType = "run.completed"
|
||||
EvRunFailed StreamEventType = "run.failed"
|
||||
EvError StreamEventType = "error"
|
||||
// 收件箱卡片(activity.go):前端右栏收件箱的唯一数据源(投影语义,服务端权威)。
|
||||
EvCardCreated StreamEventType = "card.created"
|
||||
EvCardUpdated StreamEventType = "card.updated"
|
||||
EvCardStatus StreamEventType = "card.status"
|
||||
EvCardResolved StreamEventType = "card.resolved"
|
||||
)
|
||||
|
||||
// ProtocolVersion 是事件信封与 payload 词汇的当前版本。
|
||||
@@ -131,3 +136,24 @@ type ErrorPayload struct {
|
||||
Message string `json:"message"`
|
||||
RunID ID `json:"runId,omitempty"`
|
||||
}
|
||||
|
||||
// CardPayload:card.created / card.updated 携带整卡投影(服务端权威,前端整体替换)。
|
||||
type CardPayload struct {
|
||||
Card ActivityCard `json:"card"`
|
||||
}
|
||||
|
||||
// CardStatusPayload:card.status 携带状态迁移(含 Tone,便于轻量更新状态灯)。
|
||||
type CardStatusPayload struct {
|
||||
CardID ID `json:"cardId"`
|
||||
Status CardStatus `json:"status"`
|
||||
Tone string `json:"tone,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
// CardResolvedPayload:card.resolved 携带所点动作语义——确认结论回写对话时间线
|
||||
// (AI 回执)并写入审计;Answer 为自由输入文本(如有)。
|
||||
type CardResolvedPayload struct {
|
||||
CardID ID `json:"cardId"`
|
||||
ActionID string `json:"actionId,omitempty"`
|
||||
Answer string `json:"answer,omitempty"`
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ type HITLCard struct {
|
||||
Form []UINode `json:"form,omitempty"` // form kind 的字段(UI 词汇)
|
||||
CreatedAt string `json:"createdAt"`
|
||||
Resolved bool `json:"resolved"`
|
||||
// AnchorMessageID:锚定的发起消息——未决卡内联渲染于该消息之后(对话流组成部分),
|
||||
// 并在右栏收件箱生成投影卡(双现;任一侧响应即双侧销项)。
|
||||
AnchorMessageID ID `json:"anchorMessageId,omitempty"`
|
||||
// Blocked:阻断态(门禁不通过等)——确认入口禁用并给出出路;阻断本身进审计。
|
||||
Blocked bool `json:"blocked,omitempty"`
|
||||
BlockReason string `json:"blockReason,omitempty"`
|
||||
}
|
||||
|
||||
type HITLKind string
|
||||
|
||||
@@ -28,6 +28,8 @@ type PluginProvides struct {
|
||||
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/ 资产名(工作区弹层)
|
||||
}
|
||||
|
||||
// PluginPanel:右栏面板声明(UI schema 静态骨架 + 可选数据提供者)。
|
||||
@@ -52,12 +54,36 @@ type WorkflowDef struct {
|
||||
type WorkflowStep struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Kind string `json:"kind"` // prompt | tool | hitl | artifact
|
||||
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)——步骤执行时生成
|
||||
// ActivityCard(右栏收件箱),Body/Actions 走 UI 有限词汇;Actions 建议绑定命令。
|
||||
type ActivityCardTemplate struct {
|
||||
Kind CardKind `json:"kind"`
|
||||
Title string `json:"title"`
|
||||
Tone string `json:"tone,omitempty"`
|
||||
Body []UINode `json:"body,omitempty"`
|
||||
Actions []UIAction `json:"actions,omitempty"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
// RefreshTool:挂载时调用刷新 Body 动态区(与 Panel 同机制,v1 整体刷新)。
|
||||
RefreshTool string `json:"refreshTool,omitempty"`
|
||||
}
|
||||
|
||||
type HITLCardTemplate struct {
|
||||
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"id": "card_abc123def456",
|
||||
"projectId": "prj_abc123def456",
|
||||
"conversationId": "cnv_abc123def456",
|
||||
"sourceRunId": "run_abc123def456",
|
||||
"anchorMessageId": "msg_abc123def456",
|
||||
"kind": "compare",
|
||||
"title": "项目执行周期不一致 · 目标已基线",
|
||||
"status": "action_required",
|
||||
"tone": "danger",
|
||||
"body": [
|
||||
{
|
||||
"type": "compare",
|
||||
"compare": {
|
||||
"left": {
|
||||
"title": "基线 V1.2 · 已评审",
|
||||
"tone": "danger",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "markdown",
|
||||
"markdown": "12 个月(立项到注册)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"right": {
|
||||
"title": "新资料 · 待确认",
|
||||
"tone": "ok",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "markdown",
|
||||
"markdown": "18 个月(含临床试验)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "progress",
|
||||
"title": "切片进度",
|
||||
"progress": {
|
||||
"label": "切片 24/24",
|
||||
"value": 1,
|
||||
"tone": "ok"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "escalate",
|
||||
"label": "发起变更流程",
|
||||
"style": "primary",
|
||||
"command": "change.escalate"
|
||||
},
|
||||
{
|
||||
"id": "keep",
|
||||
"label": "维持原样",
|
||||
"style": "danger"
|
||||
}
|
||||
],
|
||||
"pinned": true,
|
||||
"createdAt": "0001-01-01T00:00:00Z",
|
||||
"updatedAt": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"seq": 45,
|
||||
"runId": "run_1",
|
||||
"conversationId": "cnv_1",
|
||||
"ts": "2026-09-30T10:00:00Z",
|
||||
"v": 1,
|
||||
"type": "card.created",
|
||||
"payload": {
|
||||
"card": {
|
||||
"id": "card_abc123def456",
|
||||
"projectId": "prj_1",
|
||||
"kind": "progress",
|
||||
"title": "竞品分析 PPTX 入库",
|
||||
"status": "active",
|
||||
"createdAt": "2026-09-30T10:00:00Z",
|
||||
"updatedAt": "2026-09-30T10:00:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"seq": 46,
|
||||
"runId": "run_1",
|
||||
"conversationId": "cnv_1",
|
||||
"ts": "2026-09-30T10:05:00Z",
|
||||
"v": 1,
|
||||
"type": "card.resolved",
|
||||
"payload": {
|
||||
"cardId": "card_abc123def456",
|
||||
"actionId": "escalate"
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"id": "hitl_def456abc789",
|
||||
"runId": "run_1",
|
||||
"conversationId": "cnv_1",
|
||||
"question": "批准变更 CO-2026-007?",
|
||||
"kind": "confirm",
|
||||
"choices": [
|
||||
{
|
||||
"id": "approve",
|
||||
"label": "确认批准",
|
||||
"description": "变更单进入执行态",
|
||||
"impact": "立即生效并留痕,不可裸文本撤回"
|
||||
},
|
||||
{
|
||||
"id": "reject",
|
||||
"label": "驳回",
|
||||
"description": "关闭变更单并记录理由",
|
||||
"impact": "留痕可审计"
|
||||
}
|
||||
],
|
||||
"allowFreeInput": false,
|
||||
"createdAt": "",
|
||||
"resolved": false,
|
||||
"anchorMessageId": "msg_abc123def456",
|
||||
"blocked": true,
|
||||
"blockReason": "存在未闭环高风险变更单,可先处理后重新检查"
|
||||
}
|
||||
Vendored
+6
@@ -23,6 +23,12 @@
|
||||
],
|
||||
"settings": [
|
||||
"greeting"
|
||||
],
|
||||
"commands": [
|
||||
"demo.refresh"
|
||||
],
|
||||
"workspaces": [
|
||||
"demo-main"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+152
@@ -0,0 +1,152 @@
|
||||
[
|
||||
{
|
||||
"type": "progress",
|
||||
"progress": {
|
||||
"label": "市场可行性 78%",
|
||||
"value": 0.78,
|
||||
"tone": "ok"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "compare",
|
||||
"compare": {
|
||||
"left": {
|
||||
"title": "基线",
|
||||
"tone": "danger"
|
||||
},
|
||||
"right": {
|
||||
"title": "新内容",
|
||||
"tone": "ok",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "markdown",
|
||||
"markdown": "18 个月"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tree",
|
||||
"tree": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "stage-1",
|
||||
"label": "商务与策划",
|
||||
"count": 24,
|
||||
"children": [
|
||||
{
|
||||
"id": "topic-1",
|
||||
"label": "市场调研",
|
||||
"tone": "ok",
|
||||
"badge": "多挂载",
|
||||
"count": 12,
|
||||
"selected": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "table",
|
||||
"table": {
|
||||
"columns": [
|
||||
{
|
||||
"key": "id",
|
||||
"label": "编号"
|
||||
},
|
||||
{
|
||||
"key": "title",
|
||||
"label": "标题"
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"id": "ur-005",
|
||||
"cells": [
|
||||
[
|
||||
"id",
|
||||
"UR-005"
|
||||
],
|
||||
[
|
||||
"title",
|
||||
"项目执行周期"
|
||||
]
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "trace",
|
||||
"label": "追溯"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "steps",
|
||||
"steps": {
|
||||
"current": 2,
|
||||
"steps": [
|
||||
{
|
||||
"label": "申请",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "影响分析",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "评审",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"label": "批准",
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tabs",
|
||||
"tabs": {
|
||||
"active": "view",
|
||||
"tabs": [
|
||||
{
|
||||
"id": "view",
|
||||
"label": "查看",
|
||||
"nodes": [
|
||||
{
|
||||
"type": "markdown",
|
||||
"markdown": "正文"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "history",
|
||||
"label": "历史版本"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeline",
|
||||
"timeline": {
|
||||
"items": [
|
||||
{
|
||||
"time": "10:42",
|
||||
"title": "v0.6 填入注册分类界定",
|
||||
"tone": "ok",
|
||||
"markdown": "来源 S-121",
|
||||
"ref": "S-121"
|
||||
},
|
||||
{
|
||||
"time": "09-28",
|
||||
"title": "v0.5 更新竞品定价段落",
|
||||
"ref": "陈磊确认"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
+52
-2
@@ -80,7 +80,8 @@ export type StreamEventType =
|
||||
| 'run.phase' | 'hitl.request' | 'hitl.resolved' | 'plan.update' | 'todo.update'
|
||||
| 'agent.spawn' | 'agent.progress' | 'agent.result'
|
||||
| 'artifact.created' | 'artifact.updated'
|
||||
| 'run.completed' | 'run.failed' | 'error';
|
||||
| 'run.completed' | 'run.failed' | 'error'
|
||||
| 'card.created' | 'card.updated' | 'card.status' | 'card.resolved';
|
||||
|
||||
export interface StreamEvent<P = unknown> {
|
||||
seq: number; runId?: ID; conversationId: ID; ts: string; v: number;
|
||||
@@ -105,6 +106,28 @@ export interface ContextObservation {
|
||||
}
|
||||
export interface RunFailedPayload { code: string; message: string }
|
||||
|
||||
// ---- 收件箱卡片 ----
|
||||
|
||||
export type CardKind =
|
||||
| 'progress' | 'confirm' | 'compare' | 'review-list'
|
||||
| 'result' | 'error' | 'status';
|
||||
|
||||
export type CardStatus =
|
||||
| 'active' | 'action_required' | 'done'
|
||||
| 'resolved' | 'failed' | 'dismissed';
|
||||
|
||||
export interface ActivityCard {
|
||||
id: ID; projectId: ID; conversationId?: ID;
|
||||
sourceRunId?: ID; anchorMessageId?: ID;
|
||||
kind: CardKind; title: string; status: CardStatus; tone?: string;
|
||||
body?: UINode[]; actions?: { id: string; label: string; style?: 'primary' | 'default' | 'danger'; command?: string }[];
|
||||
pinned?: boolean; createdAt: string; updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CardPayload { card: ActivityCard }
|
||||
export interface CardStatusPayload { cardId: ID; status: CardStatus; tone?: string; note?: string }
|
||||
export interface CardResolvedPayload { cardId: ID; actionId?: string; answer?: string }
|
||||
|
||||
// ---- HITL ----
|
||||
|
||||
export type HITLKind = 'choice' | 'input' | 'confirm' | 'form';
|
||||
@@ -117,6 +140,7 @@ export interface HITLCard {
|
||||
id: ID; runId: ID; conversationId: ID; question: string; kind: HITLKind;
|
||||
choices?: HITLChoice[]; allowFreeInput: boolean; freeInputHint?: string;
|
||||
timeoutSec?: number | null; form?: UINode[]; createdAt: string; resolved: boolean;
|
||||
anchorMessageId?: ID; blocked?: boolean; blockReason?: string;
|
||||
}
|
||||
|
||||
export interface HITLResponse {
|
||||
@@ -126,7 +150,8 @@ export interface HITLResponse {
|
||||
// ---- UI 词汇 ----
|
||||
|
||||
export type UINodeType =
|
||||
| 'section' | 'list' | 'kv' | 'form' | 'markdown' | 'doc-ref' | 'actions' | 'chart-lite';
|
||||
| 'section' | 'list' | 'kv' | 'form' | 'markdown' | 'doc-ref' | 'actions' | 'chart-lite'
|
||||
| 'progress' | 'compare' | 'tree' | 'table' | 'steps' | 'tabs' | 'timeline';
|
||||
|
||||
export interface UINode {
|
||||
type: UINodeType;
|
||||
@@ -139,6 +164,25 @@ export interface UINode {
|
||||
actions?: { id: string; label: string; style?: 'primary' | 'default' | 'danger' }[];
|
||||
chart?: { kind: 'bar' | 'line'; labels: string[]; series: number[] };
|
||||
children?: UINode[];
|
||||
|
||||
progress?: { label?: string; value: number; tone?: string };
|
||||
compare?: {
|
||||
left: { title?: string; tone?: string; nodes?: UINode[] };
|
||||
right: { title?: string; tone?: string; nodes?: UINode[] };
|
||||
};
|
||||
tree?: {
|
||||
nodes: {
|
||||
id: string; label: string; tone?: string; badge?: string; count?: number;
|
||||
selected?: boolean; children?: unknown[];
|
||||
}[];
|
||||
};
|
||||
table?: {
|
||||
columns: { key: string; label: string }[];
|
||||
rows: { id?: string; cells: [string, string][]; actions?: UINode['actions'] }[];
|
||||
};
|
||||
steps?: { current: number; steps: { label: string; status?: 'pending' | 'active' | 'done' | 'failed'; tone?: string }[] };
|
||||
tabs?: { active?: string; tabs: { id: string; label: string; nodes?: UINode[] }[] };
|
||||
timeline?: { items: { time?: string; title: string; tone?: string; markdown?: string; ref?: string }[] };
|
||||
}
|
||||
|
||||
export interface UIField {
|
||||
@@ -178,6 +222,7 @@ export interface PluginManifest {
|
||||
provides: {
|
||||
tools?: string[]; workflows?: string[]; intents?: string[];
|
||||
prompts?: string[]; panels?: string[]; cards?: string[]; settings?: string[];
|
||||
commands?: string[]; workspaces?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -185,6 +230,11 @@ export interface PluginPanel {
|
||||
id: string; title: string; icon?: string; body: UINode[]; refreshTool?: string;
|
||||
}
|
||||
|
||||
export interface PluginWorkspace {
|
||||
id: string; title: string; icon?: string; shortcut?: string;
|
||||
badgeTool?: string; body: UINode[]; refreshTool?: string;
|
||||
}
|
||||
|
||||
// ---- 摄取 / 检索 ----
|
||||
|
||||
export interface ParsedDocument {
|
||||
|
||||
@@ -15,6 +15,15 @@ const (
|
||||
UIDocRef UINodeType = "doc-ref" // 工件引用(右栏查看器打开)
|
||||
UIActions UINodeType = "actions" // 可点动作(发命令/发消息)
|
||||
UIChartLite UINodeType = "chart-lite"
|
||||
// —— 收件箱/工作区扩展词汇(提案 D28;graph 远期,追溯视图按"对象为中心 +
|
||||
// 分组泳道 + 逐层展开"约束设计,禁止力导向裸图)——
|
||||
UIProgress UINodeType = "progress" // 进度条(摄取/生成/填充度)
|
||||
UICompare UINodeType = "compare" // 左右对比(基线 vs 新内容、diff 确认)
|
||||
UITree UINodeType = "tree" // 树(文档树/笔记树骨架;节点带状态灯/徽标/计数)
|
||||
UITable UINodeType = "table" // 表格(大表/切片表;行级操作)
|
||||
UISteps UINodeType = "steps" // 流程步骤条(变更七步等)
|
||||
UITabs UINodeType = "tabs" // 页签容器(工作区多页签)
|
||||
UITimeline UINodeType = "timeline" // 时间线(版本链/批注流/事件史)
|
||||
)
|
||||
|
||||
type UINode struct {
|
||||
@@ -28,6 +37,99 @@ type UINode struct {
|
||||
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
|
||||
}
|
||||
|
||||
// UIProgress:0–1 归一进度或 n/m 计数(Value=Done/Total 任一形式,前端按 <1 判定)。
|
||||
type UIProgressSpec struct {
|
||||
Label string `json:"label,omitempty"`
|
||||
Value float64 `json:"value"`
|
||||
Tone string `json:"tone,omitempty"`
|
||||
}
|
||||
|
||||
// UICompare:左右对比布局;两侧各带语义色调(如左=基线 danger、右=新内容 ok)。
|
||||
type UICompareSide struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Tone string `json:"tone,omitempty"`
|
||||
Nodes []UINode `json:"nodes,omitempty"`
|
||||
}
|
||||
|
||||
type UICompareSpec struct {
|
||||
Left UICompareSide `json:"left"`
|
||||
Right UICompareSide `json:"right"`
|
||||
}
|
||||
|
||||
// UITreeNode:树节点;Badge 为角标文本(如"多挂载"),Count 为子孙计数,
|
||||
// onSelect 语义由宿主按 Tree 用途路由(命令或工作区定位)。
|
||||
type UITreeNode struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Tone string `json:"tone,omitempty"` // 状态灯
|
||||
Badge string `json:"badge,omitempty"`
|
||||
Count int `json:"count,omitempty"`
|
||||
Selected bool `json:"selected,omitempty"`
|
||||
Children []UITreeNode `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type UITreeSpec struct {
|
||||
Nodes []UITreeNode `json:"nodes"`
|
||||
}
|
||||
|
||||
type UITableColumn struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
type UITableRow struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Cells [][2]string `json:"cells"` // 与 Columns.Key 对应的有序单元格
|
||||
Actions []UIAction `json:"actions,omitempty"` // 行级操作(走命令总线)
|
||||
}
|
||||
|
||||
type UITableSpec struct {
|
||||
Columns []UITableColumn `json:"columns"`
|
||||
Rows []UITableRow `json:"rows"`
|
||||
}
|
||||
|
||||
type UIStep struct {
|
||||
Label string `json:"label"`
|
||||
Status string `json:"status,omitempty"` // pending | active | done | failed
|
||||
Tone string `json:"tone,omitempty"`
|
||||
}
|
||||
|
||||
type UIStepsSpec struct {
|
||||
Current int `json:"current"` // 当前步下标(0 起)
|
||||
Steps []UIStep `json:"steps"`
|
||||
}
|
||||
|
||||
type UITab struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Nodes []UINode `json:"nodes,omitempty"`
|
||||
}
|
||||
|
||||
type UITabsSpec struct {
|
||||
Active string `json:"active,omitempty"` // 默认选中页签 ID
|
||||
Tabs []UITab `json:"tabs"`
|
||||
}
|
||||
|
||||
type UITimelineItem struct {
|
||||
Time string `json:"time,omitempty"` // 展示用时间文本
|
||||
Title string `json:"title"`
|
||||
Tone string `json:"tone,omitempty"`
|
||||
Markdown string `json:"markdown,omitempty"` // 条目正文
|
||||
Ref string `json:"ref,omitempty"` // 引用标识(切片/版本/操作者)
|
||||
}
|
||||
|
||||
type UITimelineSpec struct {
|
||||
Items []UITimelineItem `json:"items"`
|
||||
}
|
||||
|
||||
type UIField struct {
|
||||
@@ -42,9 +144,10 @@ type UIField struct {
|
||||
}
|
||||
|
||||
type UIAction struct {
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Style string `json:"style,omitempty"` // primary | default | danger
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Style string `json:"style,omitempty"` // primary | default | danger
|
||||
Command string `json:"command,omitempty"` // 绑定命令总线命令 id;空则降级为发消息
|
||||
}
|
||||
|
||||
type UIChart struct {
|
||||
|
||||
Reference in New Issue
Block a user