Compare commits

..
Author SHA1 Message Date
weichenandCursor 0b5c7747a3 Keep the context section type inside the contract file
Why: A separately exported ContextSection is unused inside the UI package and pushes the knip ratchet over its baseline.
Evidence: UI knip reported 56 unused exports against baseline 55. The section shape stays on ContextObservation.sections.
Impact: Generated frontend and UI contracts drop the extra export. JSON field names are unchanged.
Refs: .task/feature-sillytavern-review.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-04 18:36:08 +00:00
weichen bd632236c2 Extend messages and context observation for variants and estimates
Why: Labeled secrets, excluded turns, and candidate replies need fields on the shared message contract, and context snapshots need a section breakdown plus a stable prefix hash.
Evidence: go test ./... in agent-system-core. New fields use omitempty, so existing golden JSON stays the same.
Impact: Clients that ignore unknown fields keep working. Assemblers can now mark a message excluded or as one candidate in a group.
Refs: docs/records/2026-10-04-sillytavern-review.md
2026-10-04 18:18:57 +00:00
weichenandCursor 5dc190ee63 Record the model that served a run's usage
Why: a fallback chain can answer with a different model than the one that was requested, and usage has to name the model that actually served.
Evidence: go test .
Impact: the field is omitted when empty, so existing usage JSON stays the same.
Refs: feature/durable-rewind
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 17:28:40 +00:00
weichen b7e5baf15c Merge pull request 'Add session permission mode and confirmation detail' (#19) from feature/permission-hooks into main 2026-10-03 17:06:48 +00:00
weichenandCursor cf7df54874 Add session permission mode and confirmation detail
Why: The product needs a session mode and a place to show tool arguments on a confirmation card without inventing a second card type.
Evidence: GOWORK=off go test . passed. New fields use omitempty, so existing golden JSON stays the same.
Impact: Empty permission mode still means the profile policy. Clients that ignore unknown fields keep working.
Refs: feature/permission-hooks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 17:02:28 +00:00
weichen 88e481afcd Merge pull request 'Add a finalizing phase and a check part' (#18) from feature/final-reply-compose into main 2026-10-02 14:15:05 +00:00
weichenandCursor 7c66075ee9 Add a finalizing phase and a check part
Why: The final reply is composed after the work loop, and the automatic end check needs its own part and event so the timeline can show it without mixing it into the user-facing text.
Evidence: go test ./...
Impact: New RunPhase finalizing, PartType check, and run.check. Existing payloads are unchanged.
Refs: feature/final-reply-compose
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-02 13:32:32 +00:00
weichen 3f7067fb2d Merge pull request 'Add streaming events for in-flight tool calls' (#17) from feature/stream-display-overhaul into main 2026-10-02 05:04:38 +00:00
weichenandCursor f08a4a90c6 Add streaming events for in-flight tool calls
Why: The client only learned about a tool call once execution started, so long argument generation looked like a frozen cursor.
Evidence: go test ./...
Impact: New tool.call.delta and tool.output events; tool.result may include durationMs. Unknown event types stay ignored.
Refs: feature/stream-display-overhaul
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-02 02:11:42 +00:00
weichen 4d1123e45c Merge pull request 'Add script steps and critic fields to workflow definitions' (#16) from feature/agent-harness-p4 into main
Merge pull request #16
2026-10-02 01:03:55 +00:00
weichenandCursor f93f473a0e Add script steps and critic fields to workflow definitions
Why: The workflow executor needs a shared contract for subagent profiles, plugin scripts, and per-step plus whole-run retry caps.
Evidence: go test ./... passed, including the new workflow_def golden.
Impact: New fields are omitempty, so existing workflow JSON still decodes. Kind script is additive.
Refs: feature/agent-harness-p4
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 15:55:52 +00:00
weichen a8e81e078d Merge pull request 'Add optional tools, model, and maxTurns to prompt modules' (#15) from feature/agent-harness-p3b into main 2026-10-01 15:51:54 +00:00
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
16 changed files with 614 additions and 140 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` 主机条目,克隆/推送直接可用。
+38 -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,6 +133,12 @@ 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"}},
@@ -176,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"},
})
}
@@ -192,6 +207,26 @@ func TestGoldenIngest(t *testing.T) {
})
}
func TestGoldenWorkflowDef(t *testing.T) {
golden(t, "workflow_def", WorkflowDef{
ID: "demo.brief", Title: "简报", Description: "按主题写一份简报",
IntentID: "demo.brief", MaxRetries: 6,
Steps: []WorkflowStep{
{ID: "length", Title: "篇幅", Kind: "hitl", Hitl: &HITLCardTemplate{
Question: "篇幅?", Kind: HITLKindChoice, AllowFreeInput: false,
Choices: []HITLChoice{{ID: "short", Label: "短", Description: "一页", Impact: "更快"}},
}},
{ID: "draft", Title: "起草", Kind: "prompt", Agent: "research",
Prompt: "写一份关于 {{slots.topic}} 的简报。用户说:{{input}}。上一步:{{steps.length.output}}",
Critic: &StepCritic{Criteria: "必须点明主题", MaxRetries: 1}},
{ID: "lookup", Title: "查时间", Kind: "tool", Tool: "time_now", Args: json.RawMessage(`{"tz":"UTC"}`)},
{ID: "save", Title: "存稿", Kind: "artifact", Artifact: "brief.md", Prompt: "{{steps.draft.output}}"},
{ID: "note", Title: "通知", Kind: "card", Card: &ActivityCardTemplate{Kind: CardKindResult, Title: "简报已写好"}},
{ID: "run", Title: "脚本", Kind: "script", Script: "scripts/demo.py"},
},
})
}
func TestGoldenPlugin(t *testing.T) {
golden(t, "plugin_manifest", PluginManifest{
ID: "example", Name: "示例插件", Version: "0.1.0", APIVersion: PluginAPIVersion,
+67 -13
View File
@@ -1,14 +1,20 @@
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"`
TS string `json:"ts"` // RFC3339
ProjectID ID `json:"projectID,omitempty"` // 项目流事件的项目归属(D50;只增不删)
TS string `json:"ts"` // RFC3339
V int `json:"v"`
Type StreamEventType `json:"type"`
Payload json.RawMessage `json:"payload,omitempty"`
@@ -16,12 +22,28 @@ 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"
EvToolCallDelta StreamEventType = "tool.call.delta" // 参数增量:工具开始执行前即可上屏
EvToolCall StreamEventType = "tool.call"
EvToolOutput StreamEventType = "tool.output" // 执行中的 stdout/stderr 增量
EvToolResult StreamEventType = "tool.result"
EvRunPhase StreamEventType = "run.phase"
EvRunCheck StreamEventType = "run.check" // 终轮自动检查,过程区一行
EvHITLRequest StreamEventType = "hitl.request"
EvHITLResolved StreamEventType = "hitl.resolved"
EvPlanUpdate StreamEventType = "plan.update"
@@ -63,17 +85,40 @@ type ToolCallPayload struct {
Arguments string `json:"arguments,omitempty"`
}
// ToolCallDeltaPayload 是模型正在编写的工具参数增量。Index 标识同轮第几次调用
// (与供应商 tool_calls 下标一致);Name/CallID 往往只在首个分片出现。
type ToolCallDeltaPayload struct {
Index int `json:"index"`
CallID string `json:"callId,omitempty"`
Name string `json:"name,omitempty"`
Delta string `json:"delta"`
}
// ToolOutputPayload 是工具执行过程中的输出增量。Stream 为 stdout 或 stderr。
type ToolOutputPayload struct {
CallID string `json:"callId"`
Stream string `json:"stream"`
Text string `json:"text"`
}
type ToolResultPayload struct {
CallID string `json:"callId"`
Status string `json:"status"`
Summary string `json:"summary,omitempty"`
Truncated bool `json:"truncated,omitempty"`
CallID string `json:"callId"`
Status string `json:"status"`
Summary string `json:"summary,omitempty"`
Truncated bool `json:"truncated,omitempty"`
DurationMs float64 `json:"durationMs,omitempty"`
}
type RunPhasePayload struct {
Phase RunPhase `json:"phase"`
}
// RunCheckPayload 与 CheckPart 同形,让运行中和重新加载后的检查行一致。
type RunCheckPayload struct {
Passed bool `json:"passed"`
Reason string `json:"reason,omitempty"`
}
type HITLResolvedPayload struct {
CardID ID `json:"cardId"`
Answer string `json:"answer,omitempty"`
@@ -96,15 +141,15 @@ type AgentSpawnPayload struct {
}
type AgentProgressPayload struct {
AgentRun ID `json:"agentRun"`
Note string `json:"note,omitempty"`
AgentRun ID `json:"agentRun"`
Note string `json:"note,omitempty"`
Phase RunPhase `json:"phase,omitempty"`
}
type AgentResultPayload struct {
AgentRun ID `json:"agentRun"`
AgentRun ID `json:"agentRun"`
Status RunStatus `json:"status"`
Summary string `json:"summary,omitempty"`
Summary string `json:"summary,omitempty"`
// Text 为子代理完整文本产出(squad 步骤交付物用;截断摘要见 Summary)。
Text string `json:"text,omitempty"`
}
@@ -119,11 +164,20 @@ type RunCompletedPayload struct {
Context *ContextObservation `json:"context,omitempty"`
}
// ContextSection 是组装结果里一段的估算。EstTokens 用 UTF-8 字节数 / 3.35,
// 再按上一请求的真实用量校准。
type ContextSection struct {
Name string `json:"name"`
EstTokens int `json:"estTokens"`
}
// ContextObservation 记录一次模型输入的实际构成与预算裁剪(ctxasm 产出)。
type ContextObservation struct {
Items []string `json:"items"` // 如 system(画像) / history(12) / attachments(1) / knowledge(3)
Truncations []string `json:"truncations,omitempty"` // 裁剪记录(历史超窗/工具结果超限/附件截断)
EstChars int `json:"estChars"` // 估算输入字符量
Items []string `json:"items"` // 如 system(画像) / history(12) / attachments(1) / knowledge(3)
Truncations []string `json:"truncations,omitempty"` // 裁剪记录(历史超窗/工具结果超限/附件截断)
EstChars int `json:"estChars"` // 估算输入量(token;字段名保持兼容)
Sections []ContextSection `json:"sections,omitempty"`
PrefixHash string `json:"prefixHash,omitempty"` // 系统前缀哈希,前缀不变时保持稳定
}
type RunFailedPayload struct {
+2
View File
@@ -23,6 +23,8 @@ type HITLCard struct {
// Blocked:阻断态(门禁不通过等)——确认入口禁用并给出出路;阻断本身进审计。
Blocked bool `json:"blocked,omitempty"`
BlockReason string `json:"blockReason,omitempty"`
// Detail 是确认卡上的参数预览或其他补充说明。
Detail string `json:"detail,omitempty"`
}
type HITLKind string
+93 -64
View File
@@ -17,24 +17,30 @@ type ID = string
// Owner 是租户标识(多租户主线,设计 D5/D16):空串 = 单租户默认(嵌入式档/
// 未启用认证时的隐式 owner)。API 层强制 owner 过滤,跨租户访问一律 NotFound。
type Project struct {
ID ID `json:"id"`
Owner ID `json:"owner,omitempty"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID ID `json:"id"`
Owner ID `json:"owner,omitempty"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Settings map[string]any `json:"settings,omitempty"` // 项目层设置覆盖(4.9 覆盖链)
}
type Conversation struct {
ID ID `json:"id"`
ProjectID ID `json:"projectId"`
Owner ID `json:"owner,omitempty"` // 冗余自项目,查询过滤用
Title string `json:"title"`
ProfileID ID `json:"profileId,omitempty"` // 空为默认画像
Pinned bool `json:"pinned,omitempty"` // 侧栏置顶(方案 A P6/P8:本端组织偏好,服务端权威)
Archived bool `json:"archived,omitempty"` // 归档:列表默认隐藏,数据保留
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID ID `json:"id"`
ProjectID ID `json:"projectId"`
Owner ID `json:"owner,omitempty"` // 冗余自项目,查询过滤用
Title string `json:"title"`
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"` // 跨会话入站:私有会话,不被他读、不出现在他方列表
// PermissionMode 是会话权限:空或 default 沿用画像;plan 只读加计划工具;auto_edit 放行工作区写文件。
PermissionMode string `json:"permissionMode,omitempty"`
// SessionNote 是本会话要求,组装时附在上下文末尾,不进入系统前缀。
SessionNote string `json:"sessionNote,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// ---- 消息与部件 ----
@@ -59,16 +65,22 @@ const (
PartHITL PartType = "hitl"
PartPlan PartType = "plan"
PartError PartType = "error"
PartCard PartType = "card" // 插件自定义卡片(Data 为 UI schema)
PartCard PartType = "card" // 插件自定义卡片(Data 为 UI schema)
PartCheck PartType = "check" // 终轮自动检查,过程区一行,不是给用户的回复
)
type Message struct {
ID ID `json:"id"`
ConversationID ID `json:"conversationId"`
RunID ID `json:"runId,omitempty"`
Role Role `json:"role"`
Parts []Part `json:"parts"`
CreatedAt time.Time `json:"createdAt"`
ID ID `json:"id"`
ConversationID ID `json:"conversationId"`
RunID ID `json:"runId,omitempty"`
Role Role `json:"role"`
Parts []Part `json:"parts"`
CreatedAt time.Time `json:"createdAt"`
// Excluded 为真时界面仍显示,但组装上下文时整条(及同一 run 的成组消息)不送入模型。
Excluded bool `json:"excluded,omitempty"`
// VariantGroup 把同一轮的候选回复绑在一起。只把未排除的那条送入模型。
VariantGroup string `json:"variantGroup,omitempty"`
VariantIndex int `json:"variantIndex,omitempty"`
}
// Part 是判别联合:Type 决定哪些字段有意义;未用字段为零值。
@@ -82,25 +94,30 @@ type Part struct {
Artifact *ArtifactRef `json:"artifact,omitempty"`
Hitl *HITLSummary `json:"hitl,omitempty"`
Plan *PlanSnapshot `json:"plan,omitempty"`
Card *CardPart `json:"card,omitempty"`
Card *CardPart `json:"card,omitempty"`
Err *ErrorPart `json:"error,omitempty"`
Check *CheckPart `json:"check,omitempty"`
// 过程标记:true 的部件在 run 终态后由前端折叠为摘要行(4.2)。
Process bool `json:"process,omitempty"`
// DurationMs 是 thinking 部件的思考时长(毫秒,从首个推理增量到最后一个)。
// 工具耗时在 ToolCallPart.Duration,不复用本字段。
DurationMs float64 `json:"durationMs,omitempty"`
}
type ToolCallPart struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments string `json:"arguments,omitempty"` // 原始 JSON
Duration float64 `json:"durationMs,omitempty"`
ID string `json:"id"`
Name string `json:"name"`
Arguments string `json:"arguments,omitempty"` // 原始 JSON
Duration float64 `json:"durationMs,omitempty"`
}
type ToolResultPart struct {
CallID string `json:"callId"`
Status string `json:"status"` // ok | error
Summary string `json:"summary,omitempty"`
Truncated bool `json:"truncated,omitempty"` // 超长截断并转工件引用(4.10)
CallID string `json:"callId"`
Status string `json:"status"` // ok | error
Summary string `json:"summary,omitempty"`
Truncated bool `json:"truncated,omitempty"` // 超长截断并转工件引用(4.10)
}
type ArtifactRef struct {
@@ -121,10 +138,19 @@ type ErrorPart struct {
Message string `json:"message"`
}
// CheckPart 是终轮自动检查的结果。过程区显示一行,不进入给用户的回复。
type CheckPart struct {
Passed bool `json:"passed"`
Reason string `json:"reason,omitempty"`
}
// 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)----
@@ -140,39 +166,40 @@ const (
type RunStatus string
const (
RunPending RunStatus = "pending"
RunRunning RunStatus = "running"
RunWaitingHITL RunStatus = "waiting_hitl"
RunCompleted RunStatus = "completed"
RunFailed RunStatus = "failed"
RunCancelled RunStatus = "cancelled"
RunInterrupted RunStatus = "interrupted"
RunPending RunStatus = "pending"
RunRunning RunStatus = "running"
RunWaitingHITL RunStatus = "waiting_hitl"
RunCompleted RunStatus = "completed"
RunFailed RunStatus = "failed"
RunCancelled RunStatus = "cancelled"
RunInterrupted RunStatus = "interrupted"
)
type RunPhase string
const (
PhaseThinking RunPhase = "thinking"
PhaseActing RunPhase = "acting"
PhaseThinking RunPhase = "thinking"
PhaseActing RunPhase = "acting"
PhaseResponding RunPhase = "responding"
PhaseDone RunPhase = "done"
PhaseFinalizing RunPhase = "finalizing" // 工作循环已结束,正在整理给用户的最终回复
PhaseDone RunPhase = "done"
)
type Run struct {
ID ID `json:"id"`
ParentRunID ID `json:"parentRunId,omitempty"`
ConversationID ID `json:"conversationId"`
ProjectID ID `json:"projectId"`
Owner ID `json:"owner,omitempty"` // 冗余自会话
Kind RunKind `json:"kind"`
ProfileID ID `json:"profileId,omitempty"`
Status RunStatus `json:"status"`
Phase RunPhase `json:"phase,omitempty"`
AgentName string `json:"agentName,omitempty"` // subagent 显示名
CreatedAt time.Time `json:"createdAt"`
EndedAt *time.Time `json:"endedAt,omitempty"`
Usage *Usage `json:"usage,omitempty"`
Error *ErrorPart `json:"error,omitempty"`
ID ID `json:"id"`
ParentRunID ID `json:"parentRunId,omitempty"`
ConversationID ID `json:"conversationId"`
ProjectID ID `json:"projectId"`
Owner ID `json:"owner,omitempty"` // 冗余自会话
Kind RunKind `json:"kind"`
ProfileID ID `json:"profileId,omitempty"`
Status RunStatus `json:"status"`
Phase RunPhase `json:"phase,omitempty"`
AgentName string `json:"agentName,omitempty"` // subagent 显示名
CreatedAt time.Time `json:"createdAt"`
EndedAt *time.Time `json:"endedAt,omitempty"`
Usage *Usage `json:"usage,omitempty"`
Error *ErrorPart `json:"error,omitempty"`
}
type Usage struct {
@@ -181,6 +208,8 @@ type Usage struct {
TotalTokens int `json:"totalTokens"`
DurationMs float64 `json:"durationMs"`
ToolCalls int `json:"toolCalls"`
// Model 是实际给出这段用量的模型。空表示当时没有记录。
Model string `json:"model,omitempty"`
}
// ---- 工件 ----
@@ -201,20 +230,20 @@ type Artifact struct {
// ---- 计划与待办(右栏 Plan / Todos)----
type PlanSnapshot struct {
PlanID ID `json:"planId"`
Title string `json:"title"`
Steps []PlanStep `json:"steps"`
PlanID ID `json:"planId"`
Title string `json:"title"`
Steps []PlanStep `json:"steps"`
}
type PlanStep struct {
ID string `json:"id"`
Text string `json:"text"`
Status string `json:"status"` // pending | in_progress | done | skipped
AgentRun ID `json:"agentRun,omitempty"`
ID string `json:"id"`
Text string `json:"text"`
Status string `json:"status"` // pending | in_progress | done | skipped
AgentRun ID `json:"agentRun,omitempty"`
}
type TodoList struct {
ConversationID ID `json:"conversationId"`
ConversationID ID `json:"conversationId"`
Items []TodoItem `json:"items"`
}
+63 -43
View File
@@ -8,59 +8,79 @@ 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"`
}
// WorkflowDef:工作流即数据(4.6)。Step 可产 HITL 卡片、调工具、产出工件。
// 模板占位符:{{input}}、{{slots.<key>}}、{{steps.<id>.output}}。
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"` // 绑定意图(命中即启动)
MaxRetries int `json:"maxRetries,omitempty"` // 整条工作流的 critic 重试上限
Steps []WorkflowStep `json:"steps"`
}
// StepCritic:单步验收。无法解析的判定按不通过处理。
type StepCritic struct {
Criteria string `json:"criteria"`
MaxRetries int `json:"maxRetries,omitempty"` // 本步重试上限;0 表示缺省 1 次
}
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 | script
Prompt string `json:"prompt,omitempty"` // prompt: 发给模型的指令模板
Agent string `json:"agent,omitempty"` // prompt: 子代理 profile,缺省 general
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: 产收件箱任务卡
Script string `json:"script,omitempty"` // script: 插件目录内的相对路径
Critic *StepCritic `json:"critic,omitempty"`
}
// ActivityCardTemplate:工作流步骤产卡模板(kind=card)——步骤执行时生成
@@ -76,20 +96,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
}
+69
View File
@@ -0,0 +1,69 @@
{
"id": "demo.brief",
"title": "简报",
"description": "按主题写一份简报",
"intentId": "demo.brief",
"maxRetries": 6,
"steps": [
{
"id": "length",
"title": "篇幅",
"kind": "hitl",
"hitl": {
"question": "篇幅?",
"kind": "choice",
"choices": [
{
"id": "short",
"label": "短",
"description": "一页",
"impact": "更快"
}
],
"allowFreeInput": false
}
},
{
"id": "draft",
"title": "起草",
"kind": "prompt",
"prompt": "写一份关于 {{slots.topic}} 的简报。用户说:{{input}}。上一步:{{steps.length.output}}",
"agent": "research",
"critic": {
"criteria": "必须点明主题",
"maxRetries": 1
}
},
{
"id": "lookup",
"title": "查时间",
"kind": "tool",
"tool": "time_now",
"args": {
"tz": "UTC"
}
},
{
"id": "save",
"title": "存稿",
"kind": "artifact",
"prompt": "{{steps.draft.output}}",
"artifact": "brief.md"
},
{
"id": "note",
"title": "通知",
"kind": "card",
"card": {
"kind": "result",
"title": "简报已写好"
}
},
{
"id": "run",
"title": "脚本",
"kind": "script",
"script": "scripts/demo.py"
}
]
}
+18 -11
View File
@@ -20,16 +20,23 @@ 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"`
// ReadOnly、Edits、Risk 是工具注册时的权限声明。Risk 取 low、medium、high。
ReadOnly bool `json:"readOnly,omitempty"`
Edits bool `json:"edits,omitempty"`
Risk string `json:"risk,omitempty"`
}
// HTTPToolSpec:无代码自定义工具(4.8)。模板变量 {{param.x}} 从调用参数注入。
type HTTPToolSpec struct {
Method string `json:"method"`
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
BodyTemplate string `json:"bodyTemplate,omitempty"` // JSON 模板
AuthRef string `json:"authRef,omitempty"` // 设置中的密钥引用(settings key)
TimeoutMs int `json:"timeoutMs,omitempty"`
Method string `json:"method"`
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
BodyTemplate string `json:"bodyTemplate,omitempty"` // JSON 模板
AuthRef string `json:"authRef,omitempty"` // 设置中的密钥引用(settings key)
TimeoutMs int `json:"timeoutMs,omitempty"`
// ResultPath 从响应 JSON 中裁剪(jq 点路径);空取整个响应。
ResultPath string `json:"resultPath,omitempty"`
}
@@ -41,9 +48,9 @@ type ToolCallRequest struct {
}
type ToolCallResult struct {
CallID string `json:"callId"`
Status string `json:"status"` // ok | error
Output string `json:"output,omitempty"`
Truncated bool `json:"truncated,omitempty"`
Artifact *Artifact `json:"artifact,omitempty"` // 超长转工件
CallID string `json:"callId"`
Status string `json:"status"` // ok | error
Output string `json:"output,omitempty"`
Truncated bool `json:"truncated,omitempty"`
Artifact *Artifact `json:"artifact,omitempty"` // 超长转工件
}
+67 -5
View File
@@ -14,6 +14,9 @@ export interface Project {
export interface Conversation {
id: ID; projectId: ID; owner?: ID; title: string; profileId?: ID;
pinned?: boolean; archived?: boolean;
allowCrossRead?: boolean; crossPrivate?: boolean;
permissionMode?: 'default' | 'plan' | 'auto_edit' | '';
sessionNote?: string;
createdAt: string; updatedAt: string;
}
@@ -21,7 +24,7 @@ export type Role = 'user' | 'assistant' | 'system';
export type PartType =
| 'text' | 'thinking' | 'tool_call' | 'tool_result' | 'artifact_ref'
| 'hitl' | 'plan' | 'error' | 'card';
| 'hitl' | 'plan' | 'error' | 'card' | 'check';
export interface Part {
type: PartType;
@@ -31,25 +34,31 @@ 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 };
check?: { passed: boolean; reason?: string }; // 终轮自动检查,过程区一行
process?: boolean; // run 终态后折叠为摘要行
durationMs?: number; // thinking 部件的思考时长(毫秒);工具耗时在 toolCall.durationMs
}
export interface Message {
id: ID; conversationId: ID; runId?: ID; role: Role;
parts: Part[]; createdAt: string;
excluded?: boolean;
variantGroup?: string;
variantIndex?: number;
}
export type RunKind = 'conversation' | 'subagent' | 'tool';
export type RunStatus =
| 'pending' | 'running' | 'waiting_hitl' | 'completed'
| 'failed' | 'cancelled' | 'interrupted';
export type RunPhase = 'thinking' | 'acting' | 'responding' | 'done';
export type RunPhase = 'thinking' | 'acting' | 'responding' | 'finalizing' | 'done';
export interface Usage {
promptTokens: number; completionTokens: number; totalTokens: number;
durationMs: number; toolCalls: number;
model?: string;
}
export interface Run {
@@ -77,8 +86,9 @@ export interface TodoList {
// ---- 流式事件 ----
export type StreamEventType =
| 'message.delta' | 'thinking.delta' | 'tool.call' | 'tool.result'
| 'run.phase' | 'hitl.request' | 'hitl.resolved' | 'plan.update' | 'todo.update'
| 'message.delta' | 'thinking.delta'
| 'tool.call.delta' | 'tool.call' | 'tool.output' | 'tool.result'
| 'run.phase' | 'run.check' | 'hitl.request' | 'hitl.resolved' | 'plan.update' | 'todo.update'
| 'agent.spawn' | 'agent.progress' | 'agent.result'
| 'artifact.created' | 'artifact.updated'
| 'run.completed' | 'run.failed' | 'error'
@@ -91,6 +101,9 @@ export interface StreamEvent<P = unknown> {
// payload 形状(按 type 判别;未知字段忽略)
export interface MessageDeltaPayload { index: number; text: string }
export interface ToolCallDeltaPayload { index: number; callId?: string; name?: string; delta: string }
export interface ToolOutputPayload { callId: string; stream: 'stdout' | 'stderr'; text: string }
export interface ToolResultPayload { callId: string; status: string; summary?: string; truncated?: boolean; durationMs?: number }
export interface AgentSpawnPayload { agentRun: ID; name: string; task: string }
export interface AgentProgressPayload { agentRun: ID; note?: string; phase?: RunPhase }
export interface AgentResultPayload { agentRun: ID; status: RunStatus; summary?: string }
@@ -100,11 +113,19 @@ export interface RunCompletedPayload {
context?: ContextObservation
}
interface ContextSection {
name: string
estTokens: number
}
export interface ContextObservation {
items: string[]
truncations?: string[]
estChars: number
sections?: ContextSection[]
prefixHash?: string
}
export interface RunCheckPayload { passed: boolean; reason?: string }
export interface RunFailedPayload { code: string; message: string }
// ---- 收件箱卡片 ----
@@ -142,6 +163,7 @@ export interface HITLCard {
choices?: HITLChoice[]; allowFreeInput: boolean; freeInputHint?: string;
timeoutSec?: number | null; form?: UINode[]; createdAt: string; resolved: boolean;
anchorMessageId?: ID; blocked?: boolean; blockReason?: string;
detail?: string;
}
export interface HITLResponse {
@@ -207,6 +229,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; }
@@ -246,18 +270,56 @@ 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 {
id: string; title: string; icon?: string; body: UINode[]; refreshTool?: string;
}
/** 模板占位符:{{input}}、{{slots.<key>}}、{{steps.<id>.output}}。 */
export interface WorkflowDef {
id: string; title: string; description?: string; intentId?: string;
/** 整条工作流的 critic 重试上限。 */
maxRetries?: number;
steps: WorkflowStep[];
}
export interface StepCritic {
criteria: string;
/** 本步重试上限;缺省 1 次。 */
maxRetries?: number;
}
export interface WorkflowStep {
id: string; title: string;
kind: 'prompt' | 'tool' | 'hitl' | 'artifact' | 'card' | 'script';
prompt?: string;
/** prompt 步骤的子代理 profile,缺省 general。 */
agent?: string;
tool?: string;
args?: unknown;
hitl?: { question: string; kind: HITLKind; choices?: HITLChoice[]; allowFreeInput: boolean; freeInputHint?: string };
artifact?: string;
card?: { kind: CardKind; title: string; tone?: string; body?: UINode[]; actions?: { id: string; label: string; style?: 'primary' | 'default' | 'danger'; command?: string }[] };
/** script 步骤在插件目录内的相对路径。 */
script?: string;
critic?: StepCritic;
}
export interface PluginWorkspace {
id: string; title: string; icon?: string; shortcut?: string;
badgeTool?: string; body: UINode[]; refreshTool?: string;
}
export interface PluginPageDecl {
id: string; title: string; module: string;
}
// ---- 摄取 / 检索 ----
export interface ParsedDocument {