WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package workbench
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type QualityIssue struct {
|
||||
SegmentIDs []string `json:"segment_ids"`
|
||||
Severity string `json:"severity"`
|
||||
Problem string `json:"problem"`
|
||||
Suggestion string `json:"suggestion"`
|
||||
}
|
||||
|
||||
type QualityReport struct {
|
||||
DurationMinutes int `json:"duration_minutes,omitempty"`
|
||||
Passed bool `json:"passed"`
|
||||
Summary string `json:"summary"`
|
||||
Issues []QualityIssue `json:"issues"`
|
||||
ManuscriptHash string `json:"manuscript_hash"`
|
||||
SettingsHash string `json:"settings_hash,omitempty"`
|
||||
}
|
||||
|
||||
type QualitySettings struct {
|
||||
Rules string `json:"rules"`
|
||||
Style string `json:"style"`
|
||||
}
|
||||
|
||||
func (settings QualitySettings) validate() error {
|
||||
if len([]rune(settings.Rules)) > 6000 || len([]rune(settings.Style)) > 2000 {
|
||||
return fmt.Errorf("%w: 质检规则最多 6000 字,风格要求最多 2000 字", ErrInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (settings QualitySettings) hash() string {
|
||||
if settings.Rules == "" && settings.Style == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(compact(settings))))
|
||||
}
|
||||
|
||||
func (settings QualitySettings) instructions(advice string) string {
|
||||
return "任务质检规则:\n" + settings.Rules + "\n任务表达风格:\n" + settings.Style + "\n本次补充意见:\n" + advice
|
||||
}
|
||||
|
||||
func manuscriptHash(segments []Segment) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(compact(segments))))
|
||||
}
|
||||
|
||||
func validateQuality(report *QualityReport, segments []Segment) error {
|
||||
if report == nil || strings.TrimSpace(report.Summary) == "" {
|
||||
return fmt.Errorf("%w: AI 质检结果缺少总结,请重试", ErrInvalid)
|
||||
}
|
||||
ids := map[string]bool{}
|
||||
for _, seg := range segments {
|
||||
ids[seg.ID] = true
|
||||
}
|
||||
blocking := false
|
||||
for _, issue := range report.Issues {
|
||||
if issue.Severity != "blocking" && issue.Severity != "suggestion" {
|
||||
return fmt.Errorf("%w: AI 质检问题缺少有效等级", ErrInvalid)
|
||||
}
|
||||
if len(issue.SegmentIDs) == 0 || strings.TrimSpace(issue.Problem) == "" || strings.TrimSpace(issue.Suggestion) == "" {
|
||||
return fmt.Errorf("%w: AI 质检问题缺少位置或修改建议", ErrInvalid)
|
||||
}
|
||||
for _, id := range issue.SegmentIDs {
|
||||
if !ids[id] {
|
||||
return fmt.Errorf("%w: AI 质检引用了不存在的正文", ErrInvalid)
|
||||
}
|
||||
}
|
||||
blocking = blocking || issue.Severity == "blocking"
|
||||
}
|
||||
if report.Passed == blocking {
|
||||
return fmt.Errorf("%w: AI 质检结论与问题等级冲突,请重新质检", ErrInvalid)
|
||||
}
|
||||
report.ManuscriptHash = manuscriptHash(segments)
|
||||
return nil
|
||||
}
|
||||
|
||||
func qualityMarkdown(report *QualityReport) string {
|
||||
verdict := "通过"
|
||||
if !report.Passed {
|
||||
verdict = "待修改"
|
||||
}
|
||||
text := "## 跨稿质检:" + verdict + "\n\n" + report.Summary
|
||||
for i, issue := range report.Issues {
|
||||
severity := "建议"
|
||||
if issue.Severity == "blocking" {
|
||||
severity = "必须修改"
|
||||
}
|
||||
text += fmt.Sprintf("\n\n### %d. %s · %s\n\n%s\n\n修改建议:%s", i+1, severity, strings.Join(issue.SegmentIDs, "、"), issue.Problem, issue.Suggestion)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func requireQuality(d TaskData, accepted []Segment) error {
|
||||
if d.Quality == nil || !d.Quality.Passed || d.Quality.ManuscriptHash != manuscriptHash(accepted) || d.Quality.SettingsHash != d.QualitySettings.hash() || d.Quality.DurationMinutes != d.DurationMinutes {
|
||||
return fmt.Errorf("%w: 请先完成当前正文的跨稿质检并处理必须修改的问题,再组合成稿", ErrInvalid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Nodes may repeat an Agent, so assignment includes the canvas node identity.
|
||||
// Reject reversed dependencies instead of relying on a prompt-only ordering rule.
|
||||
func validateSegmentOrder(segments []Segment, flow FlowData) error {
|
||||
nodes := map[string]FlowNode{}
|
||||
byAgent := map[string][]string{}
|
||||
for _, node := range flow.Nodes {
|
||||
nodes[node.ID] = node
|
||||
byAgent[node.AgentID] = append(byAgent[node.AgentID], node.ID)
|
||||
}
|
||||
first, last := map[string]int{}, map[string]int{}
|
||||
for i := range segments {
|
||||
segment := &segments[i]
|
||||
if segment.NodeID == "" && len(byAgent[segment.AgentID]) == 1 {
|
||||
segment.NodeID = byAgent[segment.AgentID][0]
|
||||
}
|
||||
node, ok := nodes[segment.NodeID]
|
||||
if !ok || node.AgentID != segment.AgentID {
|
||||
return fmt.Errorf("%w: 内容段缺少对应画布节点或职责不匹配,请重新拆解", ErrInvalid)
|
||||
}
|
||||
if _, exists := first[node.ID]; !exists {
|
||||
first[node.ID] = i
|
||||
}
|
||||
last[node.ID] = i
|
||||
}
|
||||
for _, edge := range flow.Edges {
|
||||
end, sourcePresent := last[edge.Source]
|
||||
start, targetPresent := first[edge.Target]
|
||||
if targetPresent && (!sourcePresent || end >= start) {
|
||||
return fmt.Errorf("%w: 内容段没有遵循画布前后依赖,请重新拆解", ErrInvalid)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user