Add activity card, card events, HITL anchoring and UI vocabulary extension
Why: Contract foundation for the inbox/command-bus/workspaces phase (proposal D28/D30): ActivityCard as the first-class state object behind the right-rail inbox, card.* stream events, inline-confirm anchoring (anchorMessageId + blocked/blockReason) on HITL, seven new UI schema vocabularies, plugin provides.commands/workspaces, workflow card step and workspace declarations. Evidence: go vet clean; conformance goldens regenerated and verified — new activity_card / hitl_card_blocked / event_card_created / event_card_resolved / ui_vocabularies samples plus extended plugin_manifest; CardStatus.Terminal helper unit-covered via build; ts/types.ts mirrors all additions. Impact: Additive only (omitempty fields, new types, new event types) — existing consumers ignore unknown values per forward-compat rule; vocabulary spec types carry a Spec suffix to avoid const-name collisions. Refs: modules/documents docs/proposals/2026-09-30-ui-inbox-command-workspace.md
This commit is contained in:
+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"}},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user