nandi/oripublic Fork 0
00224b167266e7d496672adfe1e60fcffeab7e63
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

mockagent_test.go · 167 lines · 5.2 KBGo Blame HistoryRaw
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday1package mockagent_test
2
3import (
4 "context"
5 "io"
6 "strings"
7 "sync"
8 "testing"
9 "time"
10
11 acp "github.com/coder/acp-go-sdk"
12
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday13 "rickub.com/bots-garden/ori/internal/agent"
14 "rickub.com/bots-garden/ori/internal/mockagent"
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday15)
16
17// panelHandler records everything the mock streams, and answers the
18// permission request with a fixed choice.
19type panelHandler struct {
20 mu sync.Mutex
21 optionId string
22 texts []string
23 updateKinds []string
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday24 commands []string
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday25 permissions int
26}
27
28func (h *panelHandler) HandleSessionUpdate(_ context.Context, n acp.SessionNotification) {
29 h.mu.Lock()
30 defer h.mu.Unlock()
31 switch {
32 case n.Update.AgentMessageChunk != nil:
33 h.updateKinds = append(h.updateKinds, "message")
34 if t := n.Update.AgentMessageChunk.Content.Text; t != nil {
35 h.texts = append(h.texts, t.Text)
36 }
37 case n.Update.AgentThoughtChunk != nil:
38 h.updateKinds = append(h.updateKinds, "thought")
39 case n.Update.ToolCall != nil:
40 h.updateKinds = append(h.updateKinds, "tool_call")
41 case n.Update.ToolCallUpdate != nil:
42 h.updateKinds = append(h.updateKinds, "tool_call_update")
43 case n.Update.Plan != nil:
44 h.updateKinds = append(h.updateKinds, "plan")
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday45 case n.Update.AvailableCommandsUpdate != nil:
46 h.updateKinds = append(h.updateKinds, "available_commands")
47 for _, c := range n.Update.AvailableCommandsUpdate.AvailableCommands {
48 h.commands = append(h.commands, c.Name)
49 }
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday50 }
51}
52
53func (h *panelHandler) HandlePermissionRequest(_ context.Context, req acp.RequestPermissionRequest) (acp.RequestPermissionResponse, error) {
54 h.mu.Lock()
55 defer h.mu.Unlock()
56 h.permissions++
57 _ = req
58 return acp.RequestPermissionResponse{Outcome: acp.NewRequestPermissionOutcomeSelected(acp.PermissionOptionId(h.optionId))}, nil
59}
60
61func (h *panelHandler) kinds() map[string]int {
62 h.mu.Lock()
63 defer h.mu.Unlock()
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday64 return h.kindsLocked()
65}
66
67// kindsLocked is kinds for callers already holding h.mu.
68func (h *panelHandler) kindsLocked() map[string]int {
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday69 counts := map[string]int{}
70 for _, k := range h.updateKinds {
71 counts[k]++
72 }
73 return counts
74}
75
76// connectPanel wires the mock agent to ori's real client code over pipes.
77func connectPanel(t *testing.T, handler agent.Handler) *agent.Session {
78 t.Helper()
79 clientToAgentR, clientToAgentW := io.Pipe()
80 agentToClientR, agentToClientW := io.Pipe()
81
82 mock := mockagent.New()
83 go func() { _ = mock.Run(clientToAgentR, agentToClientW) }()
84
85 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
86 t.Cleanup(cancel)
87 session, err := agent.Connect(ctx, clientToAgentW, agentToClientR, agent.Options{
88 Cwd: t.TempDir(),
89 Handler: handler,
90 })
91 if err != nil {
92 t.Fatalf("Connect to mock agent failed: %v", err)
93 }
94 return session
95}
96
97func TestScenarioStreamsEveryPanelFeature(t *testing.T) {
98 handler := &panelHandler{optionId: "allow-once"}
99 session := connectPanel(t, handler)
100
101 stop, err := session.PromptText(context.Background(), "show me everything")
102 if err != nil {
103 t.Fatalf("PromptText returned an error: %v", err)
104 }
105 if stop != acp.StopReasonEndTurn {
106 t.Errorf("stop reason = %q, want end_turn", stop)
107 }
108
109 counts := handler.kinds()
110 for kind, want := range map[string]int{"thought": 1, "plan": 2, "tool_call": 2, "tool_call_update": 2} {
111 if counts[kind] < want {
112 t.Errorf("streamed %d %q updates, want at least %d (all: %v)", counts[kind], kind, want, counts)
113 }
114 }
115 if handler.permissions != 1 {
116 t.Errorf("permission requests = %d, want 1", handler.permissions)
117 }
118 if all := strings.Join(handler.texts, ""); !strings.Contains(all, "show me everything") {
119 t.Errorf("final message %q does not echo the prompt", all)
120 }
121}
122
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday123func TestSessionStartAnnouncesAvailableCommands(t *testing.T) {
124 handler := &panelHandler{optionId: "allow-once"}
125 connectPanel(t, handler)
126
127 handler.mu.Lock()
128 defer handler.mu.Unlock()
129 if handler.kindsLocked()["available_commands"] != 1 {
130 t.Fatalf("available_commands updates = %d, want 1 (all: %v)", handler.kindsLocked()["available_commands"], handler.kindsLocked())
131 }
132 if len(handler.commands) != 2 || handler.commands[0] != "review" || handler.commands[1] != "compact" {
133 t.Errorf("commands = %v, want [review compact]", handler.commands)
134 }
135}
136
137func TestPromptEchoesAttachedResourceLinks(t *testing.T) {
138 handler := &panelHandler{optionId: "allow-once"}
139 session := connectPanel(t, handler)
140
141 _, err := session.Prompt(context.Background(), []acp.ContentBlock{
142 acp.TextBlock("look at @src/main.go"),
143 acp.ResourceLinkBlock("src/main.go", "file:///work/src/main.go"),
144 })
145 if err != nil {
146 t.Fatalf("Prompt returned an error: %v", err)
147 }
148 if all := strings.Join(handler.texts, ""); !strings.Contains(all, "[attached: src/main.go]") {
149 t.Errorf("final message %q does not name the attachment", all)
150 }
151}
152
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g yesterday153func TestScenarioStopsOnRejectedPermission(t *testing.T) {
154 handler := &panelHandler{optionId: "reject-once"}
155 session := connectPanel(t, handler)
156
157 stop, err := session.PromptText(context.Background(), "try to write")
158 if err != nil {
159 t.Fatalf("PromptText returned an error: %v", err)
160 }
161 if stop != acp.StopReasonRefusal {
162 t.Errorf("stop reason = %q, want refusal after a rejected permission", stop)
163 }
164 if counts := handler.kinds(); counts["tool_call"] != 1 {
165 t.Errorf("tool calls = %d, want only the read call (no edit after rejection)", counts["tool_call"])
166 }
167}