diff --git a/third_party/homeagent-sdk/example/a2a/plugin.go b/third_party/homeagent-sdk/example/a2a/plugin.go index d48214b..a68eb9c 100644 --- a/third_party/homeagent-sdk/example/a2a/plugin.go +++ b/third_party/homeagent-sdk/example/a2a/plugin.go @@ -24,8 +24,8 @@ type Plugin struct { // 会话表:session_id → 上下文前缀。A2A 无状态协议下由插件侧维护 // 多轮上下文:同 session 的后续请求会把之前的对话拼进注入文本。 - sessMu sync.Mutex - sessions map[string]*a2aSession + sessMu sync.Mutex + sessions map[string]*a2aSession } // a2aSession 记录一个会话的轮次历史,用于延续上下文。 @@ -47,6 +47,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.SetAutoRestart(true) p.sdk = s p.sessions = make(map[string]*a2aSession) + // 入站通道:本插件用 p.name 通道注入输入(见 InjectInputSync 调用), + // 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。 + _ = s.RegisterInputChannel(p.name, sdk.ChannelDef{}) tp := p.name + "_" // 注册自身为输出通道:agent 回复 emit 到本通道时有落点, @@ -66,7 +69,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { Key: "listen", Default: "127.0.0.1:12000", Type: "string", DisplayName: "监听地址", Description: "A2A 服务端监听地址,设为空可禁用 HTTP 服务", - Category: p.name, + Category: p.name, }) // Outbound: query + discover @@ -75,10 +78,10 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "agent_url": map[string]interface{}{"type": "string", "description": "目标 Agent 的 A2A 端点 URL"}, - "query": map[string]interface{}{"type": "string", "description": "发送给目标 Agent 的文本查询"}, + "agent_url": map[string]interface{}{"type": "string", "description": "目标 Agent 的 A2A 端点 URL"}, + "query": map[string]interface{}{"type": "string", "description": "发送给目标 Agent 的文本查询"}, "session_id": map[string]interface{}{"type": "string", "description": "可选。上次调用返回的 session_id,传入可延续与该 agent 的多轮对话上下文"}, - "timeout": map[string]interface{}{"type": "integer", "description": "超时时间(秒),默认 60"}, + "timeout": map[string]interface{}{"type": "integer", "description": "超时时间(秒),默认 60"}, }, "required": []string{"agent_url", "query"}, }, @@ -287,7 +290,7 @@ func (p *Plugin) handleIncomingA2A(w http.ResponseWriter, r *http.Request) { Query string `json:"query,omitempty"` SessionID string `json:"session_id,omitempty"` Limit int `json:"limit,omitempty"` - Message *struct { + Message *struct { Role string `json:"role"` Parts []struct { Text string `json:"text,omitempty"` @@ -347,7 +350,7 @@ func (p *Plugin) handleIncomingA2A(w http.ResponseWriter, r *http.Request) { if sess := p.sessions[sessionID]; sess != nil { sess.History = append(sess.History, "用户: "+queryText, "助手: "+reply) if len(sess.History) > maxSessionTurns*2 { - sess.History = sess.History[len(sess.History)-maxSessionTurns*2 :] + sess.History = sess.History[len(sess.History)-maxSessionTurns*2:] } sess.LastUsed = time.Now() } @@ -357,11 +360,11 @@ func (p *Plugin) handleIncomingA2A(w http.ResponseWriter, r *http.Request) { "jsonrpc": "2.0", "id": req.ID, "result": map[string]interface{}{ - "id": fmt.Sprintf("task_%d", time.Now().UnixNano()), - "status": "completed", + "id": fmt.Sprintf("task_%d", time.Now().UnixNano()), + "status": "completed", "session_id": sessionID, "message": map[string]interface{}{ - "role": "agent", + "role": "agent", "parts": []map[string]string{{"type": "text", "text": reply}}, }, }, @@ -457,10 +460,10 @@ type A2AResponse struct { } type A2AResult struct { - TaskID string `json:"id,omitempty"` - Status string `json:"status,omitempty"` - SessionID string `json:"session_id,omitempty"` - Message *A2AMessage `json:"message,omitempty"` + TaskID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + SessionID string `json:"session_id,omitempty"` + Message *A2AMessage `json:"message,omitempty"` AgentCard *A2AAgentCard `json:"agent_card,omitempty"` } diff --git a/third_party/homeagent-sdk/example/acp/plugin.go b/third_party/homeagent-sdk/example/acp/plugin.go index 72dc373..ba6e8d3 100644 --- a/third_party/homeagent-sdk/example/acp/plugin.go +++ b/third_party/homeagent-sdk/example/acp/plugin.go @@ -47,6 +47,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.SetAutoRestart(true) p.sdk = s p.sessions = make(map[string]*sessionState) + // 入站通道:本插件用 p.name 通道注入输入(见 InjectInputSync 调用), + // 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。 + _ = s.RegisterInputChannel(p.name, sdk.ChannelDef{}) tp := p.name + "_" // 注册自身为输出通道:agent 回复 emit 到本通道时有落点。 diff --git a/third_party/homeagent-sdk/example/browser/plugin.go b/third_party/homeagent-sdk/example/browser/plugin.go index 81b7662..253a7a9 100644 --- a/third_party/homeagent-sdk/example/browser/plugin.go +++ b/third_party/homeagent-sdk/example/browser/plugin.go @@ -204,6 +204,9 @@ func newHTTPClient(timeout int, proxyURL string) *http.Client { func (p *Plugin) Start(s *sdk.PluginSDK) error { p.sdk = s s.SetAutoRestart(true) + // 入站通道:本插件用 p.name 通道注入输入(见 InjectInputSync 调用), + // 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。 + _ = s.RegisterInputChannel(p.name, sdk.ChannelDef{}) s.Settings().RegisterDef(sdk.ConfigDef{ Key: "timeout", Default: "30", Type: "int", diff --git a/third_party/homeagent-sdk/example/calendar/plugin.go b/third_party/homeagent-sdk/example/calendar/plugin.go index 6b475c9..4f09eb4 100644 --- a/third_party/homeagent-sdk/example/calendar/plugin.go +++ b/third_party/homeagent-sdk/example/calendar/plugin.go @@ -15,31 +15,31 @@ import ( ) const ( - RepeatNone = "none" - RepeatDaily = "daily" - RepeatWeekday = "weekday" - RepeatWeekly = "weekly" - RepeatBiweekly = "biweekly" - RepeatMonthly = "monthly" - RepeatYearly = "yearly" + RepeatNone = "none" + RepeatDaily = "daily" + RepeatWeekday = "weekday" + RepeatWeekly = "weekly" + RepeatBiweekly = "biweekly" + RepeatMonthly = "monthly" + RepeatYearly = "yearly" RepeatLunarYearly = "lunar_yearly" ) type CalendarEvent struct { - ID string `json:"id"` - Title string `json:"title"` - StartTime string `json:"start_time"` - EndTime string `json:"end_time,omitempty"` - AllDay bool `json:"all_day,omitempty"` - Location string `json:"location,omitempty"` - Note string `json:"note,omitempty"` - Reminds []int `json:"reminds,omitempty"` - RemindAt []int64 `json:"remind_at,omitempty"` - Repeat string `json:"repeat,omitempty"` - ParentID string `json:"parent_id,omitempty"` - Lunar bool `json:"lunar,omitempty"` - LunarMonth int `json:"lunar_month,omitempty"` - LunarDay int `json:"lunar_day,omitempty"` + ID string `json:"id"` + Title string `json:"title"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time,omitempty"` + AllDay bool `json:"all_day,omitempty"` + Location string `json:"location,omitempty"` + Note string `json:"note,omitempty"` + Reminds []int `json:"reminds,omitempty"` + RemindAt []int64 `json:"remind_at,omitempty"` + Repeat string `json:"repeat,omitempty"` + ParentID string `json:"parent_id,omitempty"` + Lunar bool `json:"lunar,omitempty"` + LunarMonth int `json:"lunar_month,omitempty"` + LunarDay int `json:"lunar_day,omitempty"` } type Plugin struct { @@ -276,6 +276,9 @@ func nextLunarYearly(targetMonth, targetDay int, after time.Time) (time.Time, bo func (p *Plugin) Start(s *sdk.PluginSDK) error { p.sdk = s + // 入站通道:本插件用 "calendar" 通道注入输入(见 Inject* 调用), + // 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。 + _ = s.RegisterInputChannel("calendar", sdk.ChannelDef{NoMemory: true}) dataDirVal, err := s.Settings().GetCore("core.daemon.data_dir") if err != nil || dataDirVal == "" { dataDirVal = "." @@ -359,7 +362,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.RegisterTool(tp+"today", sdk.ToolDef{ Name: tp + "today", Description: "Show today's events with countdown.", Parameters: map[string]interface{}{ - "type": "object", + "type": "object", "properties": map[string]interface{}{}, }, }, p.handleToday) @@ -367,7 +370,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.RegisterTool(tp+"week", sdk.ToolDef{ Name: tp + "week", Description: "Show this week's events grouped by day.", Parameters: map[string]interface{}{ - "type": "object", + "type": "object", "properties": map[string]interface{}{}, }, }, p.handleWeek) diff --git a/third_party/homeagent-sdk/example/memo/plugin.go b/third_party/homeagent-sdk/example/memo/plugin.go index acb06a6..53ab9c1 100644 --- a/third_party/homeagent-sdk/example/memo/plugin.go +++ b/third_party/homeagent-sdk/example/memo/plugin.go @@ -48,6 +48,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.SetAutoRestart(true) p.sdk = s p.tp = p.name + "_" + // 入站通道:本插件用 p.name 通道注入输入(见 Inject* 调用), + // 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。 + _ = s.RegisterInputChannel(p.name, sdk.ChannelDef{NoMemory: true}) dataDirVal, err := s.Settings().GetCore("core.daemon.data_dir") if err != nil || dataDirVal == "" { diff --git a/third_party/homeagent-sdk/example/rss/plugin.go b/third_party/homeagent-sdk/example/rss/plugin.go index 5336eb2..46c4773 100644 --- a/third_party/homeagent-sdk/example/rss/plugin.go +++ b/third_party/homeagent-sdk/example/rss/plugin.go @@ -104,6 +104,9 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.SetAutoRestart(true) p.sdk = s p.client = &http.Client{Timeout: 30 * time.Second} + // 入站通道:本插件用 "rss" 通道注入输入(见 Inject* 调用), + // 输入侧必须显式登记 —— 否则"把该 inputch 划给驻留子"会报 `inputch 未注册`。 + _ = s.RegisterInputChannel("rss", sdk.ChannelDef{NoMemory: true}) p.fp = gofeed.NewParser() p.stopCh = make(chan struct{}) p.seenGUIDs = make(map[string]bool) @@ -158,7 +161,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { s.RegisterTool(tp+"list", sdk.ToolDef{ Name: tp + "list", Description: "List all subscribed feeds", Parameters: map[string]interface{}{ - "type": "object", + "type": "object", "properties": map[string]interface{}{}, }, }, p.handleList) @@ -167,7 +170,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { Name: tp + "check_now", Description: "Manually check all feeds for new articles now", NoMemory: true, Parameters: map[string]interface{}{ - "type": "object", + "type": "object", "properties": map[string]interface{}{}, }, }, p.handleCheckNow) @@ -445,7 +448,7 @@ func (p *Plugin) loadData() { return } var data struct { - Feeds []FeedSub `json:"feeds"` + Feeds []FeedSub `json:"feeds"` SeenGUIDs map[string]bool `json:"seen"` } if json.Unmarshal(b, &data) != nil { @@ -463,7 +466,7 @@ func (p *Plugin) saveData() { p.mu.RLock() defer p.mu.RUnlock() data := struct { - Feeds []FeedSub `json:"feeds"` + Feeds []FeedSub `json:"feeds"` SeenGUIDs map[string]bool `json:"seen"` }{ Feeds: p.feeds, @@ -488,8 +491,6 @@ func (p *Plugin) cleanupData() { } } - - // atomicWriteJSON 原子写 JSON:先写临时文件再 rename,避免进程崩溃截断数据文件。 func atomicWriteJSON(path string, data []byte) error { tmp := path + ".tmp" diff --git a/third_party/homeagent-sdk/sdk/plugin.go b/third_party/homeagent-sdk/sdk/plugin.go index f66a789..9f10d90 100644 --- a/third_party/homeagent-sdk/sdk/plugin.go +++ b/third_party/homeagent-sdk/sdk/plugin.go @@ -467,6 +467,11 @@ func (s *PluginSDK) RegisterPluginAPI(name string) error { } // RegisterOutputChannel registers an output channel that the output_send tool can route to. +// +// 与 RegisterInputChannel 的分工:本函数声明**出站**(output_send__ 的回复发给谁); +// 入站(谁会往 注入输入)是另一件事,用 RegisterInputChannel 声明。 +// 若该通道同时也是你的注入入口,两个都要登记。 +// // name: channel name (e.g. "qq", "webui") // caps: bitmask of supported output capabilities (CapText, CapFile, etc.) // desc: description of the channel, expected meta format, and type enum @@ -483,6 +488,15 @@ func (s *PluginSDK) RegisterOutputChannel(name string, caps int, desc string, de } // RegisterInputChannel registers an input channel with its memory behavior. +// +// 契约:**凡是用 InjectText*/InjectInput*/InjectInterrupt*(source, "", ...) +// 注入的通道名,都应当在这里登记**。inputch 是内核里最基本的**输入路由单位**: +// 只有登记过的通道才能在 inputch 登记表里出现,父 agent 才能"把某个 inputch 划给驻留子"; +// 没登记就划分会直接失败(`inputch 未注册`)。 +// +// 只登记输出通道(RegisterOutputChannel)而没登记输入通道时,内核会兜底登记同名 +// inputch 并打告警日志 —— 兜底只为兼容老插件,新插件请显式登记。 +// // def.NoMemory: 此通道输入不参与记忆计算 // def.Cleaner: 计算层对输入文本清洗后(不改原文)再向量化/提关键词 func (s *PluginSDK) RegisterInputChannel(name string, def ChannelDef) error {