Files
Agent-System-Backend/internal/tools/document_renderer.go
weichenandCursor 1afe164798 Pin published company default renderer and drop candidate wording
Why: agent-document-renderer 31fbe54 publishes company.default@1.0.0 with
review status approved and publicationAllowed true (owner decision
2026-10-16: A4, ~2.5cm margins, SimSun + Times New Roman 12pt body, SimHei
headings, no logo). Backend still hard-coded Candidate=true and every prompt,
skill and tool text called the default a pending-human-visual-review
candidate.

Evidence: go vet ./... exit 0; gofmt -l on changed packages empty;
go test -count=1 ./internal/docgen/... ./internal/prompts/...
./internal/skills/... ./internal/tools/... ok; go test -count=1 ./... exit 0
(42 packages ok); go build ./... and CGO_ENABLED=0 go build ./cmd/agent-system
ok; git diff --cached --check clean. New tests failed before the change.

Impact: bump deps/agent-document-renderer 0d1c512 -> 31fbe54 and regenerate
the renderer identity. The manifest profile receipt now reports the
renderer's own review status and derives Candidate from the snapshot state
(false for the published default). Prompts (base.deliverables v12,
tool.render_document v2, task.report v4, task.doc-minutes v4), docx-author
and house-style skills, the render_document tool description and comparison
exception details describe the default as published (approved by owner
2026-10-16) while still limiting it to ordinary uncontrolled workspace
working/preview Word. Routing is unchanged: project, controlled,
special-template, review and approved outputs still go to RDM, with no
Backend default fallback and no legacy Word builder fallback.

Refs: unified renderer plan Tasks 7/10
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 05:07:19 +00:00

102 lines
4.0 KiB
Go

package tools
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"agent-document-renderer/render"
"agent-system-backend/internal/docgen"
core "agent-system-core"
)
func registerDocumentRenderer(r *Registry) error {
return r.Register(core.ToolDesc{
Name: "render_document", Source: core.SourceBuiltin, Edits: true, Risk: "medium",
Description: "把普通工作区 Markdown 通过统一文档渲染器生成 Markdown、DOCX、manifest 和可下载归档。只允许非受控工作区交付;项目受控文档必须交给 RDM。公司默认规则已发布(负责人 2026-10-16 批准),但只用于普通工作区的 working/preview 交付,评审状态会写入 manifest。",
Parameters: json.RawMessage(`{"type":"object","additionalProperties":false,"properties":{` +
`"operationId":{"type":"string"},"name":{"type":"string"},"documentId":{"type":"string"},"documentType":{"type":"string"},` +
`"language":{"type":"string"},"markdown":{"type":"string"},` +
`"mode":{"type":"string","enum":["working","preview"]},` +
`"controlled":{"type":"boolean"},"projectDocument":{"type":"boolean"}` +
`},"required":["operationId","name","documentId","markdown","controlled","projectDocument"]}`),
}, executeDocumentRenderer)
}
type documentRendererArgs struct {
OperationID string `json:"operationId"`
Name string `json:"name"`
DocumentID string `json:"documentId"`
DocumentType string `json:"documentType"`
Language string `json:"language"`
Markdown string `json:"markdown"`
Mode string `json:"mode"`
Controlled *bool `json:"controlled"`
ProjectDocument *bool `json:"projectDocument"`
}
type documentDeliveryHost interface {
ExecuteDocumentDelivery(context.Context, docgen.OperationRequest) (docgen.OperationReceipt, error)
}
func executeDocumentRenderer(rc ToolExecCtx, raw json.RawMessage) (string, error) {
var args documentRendererArgs
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&args); err != nil {
return "", fmt.Errorf("render_document: bad args: %w", err)
}
var extra any
if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
return "", errors.New("render_document: arguments must contain one JSON object")
}
if strings.TrimSpace(args.OperationID) == "" || strings.TrimSpace(args.Name) == "" || strings.TrimSpace(args.DocumentID) == "" || strings.TrimSpace(args.Markdown) == "" {
return "", errors.New("render_document: operationId, name, documentId, and markdown are required")
}
if args.Controlled == nil || args.ProjectDocument == nil {
return "", errors.New("render_document: controlled and projectDocument must be explicit")
}
if *args.Controlled || *args.ProjectDocument {
return "", docgen.ErrRDMRequired
}
if strings.TrimSpace(rc.OwnerID()) == "" {
return "", errors.New("render_document: trusted requester identity is required")
}
mode := render.Mode(args.Mode)
if mode == "" {
mode = render.ModePreview
}
if mode != render.ModeWorking && mode != render.ModePreview {
return "", fmt.Errorf("render_document: mode %q is unavailable for an ordinary workspace document", mode)
}
documentType := strings.TrimSpace(args.DocumentType)
if documentType == "" {
documentType = "general"
}
language := strings.TrimSpace(args.Language)
if language == "" {
language = "zh-CN"
}
host, ok := rc.(documentDeliveryHost)
if !ok {
return "", errors.New("render_document: trusted delivery host is unavailable")
}
receipt, err := host.ExecuteDocumentDelivery(execParentCtx(rc), docgen.OperationRequest{
OperationID: args.OperationID, DocumentID: args.DocumentID, Title: args.Name,
DocumentType: documentType, Language: language, Markdown: []byte(args.Markdown), Mode: mode,
Controlled: *args.Controlled, ProjectDocument: *args.ProjectDocument,
})
encoded, marshalErr := json.Marshal(receipt)
if marshalErr != nil {
return "", marshalErr
}
if err != nil {
return "", fmt.Errorf("render_document: %s", encoded)
}
return string(encoded), nil
}