Add the public extension surface and platform services
Why: Downstream products must extend the base through a public package and host API instead of editing internal code. Evidence: go test ./... passed, including plugin v2 assets, sidecar startup, migration v13, pending confirms, HITL timeout, hash embedder, command pairs, and the extension surface snapshot. Impact: apiVersion 2.0-experimental rejects empty network allowlists for declarative HTTP. Hash embeddings stay opt-in via models.embedding.provider=hash. Plugin secrets resolve per owner at execution time. Refs: docs/proposals/2026-10-01-plugin-contract-v2.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -57,14 +57,21 @@ type pending struct {
|
||||
}
|
||||
|
||||
type Bus struct {
|
||||
reg *agentcommand.Registry
|
||||
audit AuditStore
|
||||
cards CardPort
|
||||
now func() time.Time
|
||||
handlers map[string]Handler
|
||||
slash map[string]string // "/x" → command id
|
||||
pendMu sync.Mutex
|
||||
pending map[string]pending
|
||||
reg *agentcommand.Registry
|
||||
audit AuditStore
|
||||
cards CardPort
|
||||
now func() time.Time
|
||||
handlers map[string]Handler
|
||||
slash map[string]string // "/x" → command id
|
||||
pendMu sync.Mutex
|
||||
pending map[string]pending
|
||||
pendingStore PendingStore
|
||||
}
|
||||
|
||||
// PendingStore 把危险命令的确认挂起态落盘。进程内 map 仍是热路径;重启后从存储取回。
|
||||
type PendingStore interface {
|
||||
SavePendingConfirm(cardID, owner, payload string) error
|
||||
TakePendingConfirm(cardID string) (owner, payload string, ok bool, err error)
|
||||
}
|
||||
|
||||
func New(audit AuditStore, cards CardPort, now func() time.Time) *Bus {
|
||||
@@ -80,6 +87,9 @@ func New(audit AuditStore, cards CardPort, now func() time.Time) *Bus {
|
||||
// Registry 暴露注册表(列清单/插件 overlay 注册用)。
|
||||
func (b *Bus) Registry() *agentcommand.Registry { return b.reg }
|
||||
|
||||
// SetPendingStore 注入确认挂起态的持久化实现(nil = 仅进程内)。
|
||||
func (b *Bus) SetPendingStore(st PendingStore) { b.pendingStore = st }
|
||||
|
||||
// RegisterBuiltin 登记内置命令并绑定宿主处理器与 `/` 别名。
|
||||
func (b *Bus) RegisterBuiltin(spec agentcommand.CommandSpec, slash string, h Handler) error {
|
||||
spec.Binding = agentcommand.Binding{Kind: agentcommand.BindBuiltin}
|
||||
@@ -159,6 +169,10 @@ func (b *Bus) Dispatch(ctx context.Context, req Request) Response {
|
||||
b.pendMu.Lock()
|
||||
b.pending[card.ID] = pending{req: req, cardID: card.ID}
|
||||
b.pendMu.Unlock()
|
||||
if b.pendingStore != nil {
|
||||
raw, _ := json.Marshal(req)
|
||||
_ = b.pendingStore.SavePendingConfirm(card.ID, req.Owner, string(raw))
|
||||
}
|
||||
r := b.auditReceipt(req, agentcommand.StateAccepted, card.ID, "awaiting explicit confirm", start)
|
||||
return Response{Receipt: r, Card: &card}
|
||||
}
|
||||
@@ -182,6 +196,19 @@ func (b *Bus) Confirm(owner, cardID, actionID string) Response {
|
||||
delete(b.pending, cardID)
|
||||
}
|
||||
b.pendMu.Unlock()
|
||||
if ok && actionID == "confirm" && b.pendingStore != nil {
|
||||
_, _, _, _ = b.pendingStore.TakePendingConfirm(cardID)
|
||||
}
|
||||
if !ok && b.pendingStore != nil {
|
||||
_, payload, found, err := b.pendingStore.TakePendingConfirm(cardID)
|
||||
if err == nil && found {
|
||||
var req Request
|
||||
if json.Unmarshal([]byte(payload), &req) == nil {
|
||||
p = pending{req: req, cardID: cardID}
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return Response{Receipt: agentcommand.ExecReceipt{CommandID: "unknown", State: agentcommand.StateRejected, Reason: "no pending confirm (expired or already resolved)", Time: b.now().UTC()}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user