helmd v2: Go-server som styr agent i tmux — REST+SSE, frågedetektor, fildelning, ntfy, CI-release
- tmux som sanningskälla (agentoberoende, överlever omstart via adoption) - API: sessions, prompt, question/answer, keys, mode, config, shares, notify, events - frågedetektor testad mot riktiga agy 1.1.9-dumpar + mock - integrationstest: 26 tester i isolerad debian-container (scripts/run-integration.sh) - e2e-verifierad mot riktig agy (trust-fråga -> svar -> prompt) - .gitea/workflows/helmd-release.yaml -> rullande helmd-latest (x64+arm64) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
126
helmd/question_test.go
Normal file
126
helmd/question_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
// Real captures from a live agy 1.1.9 session (2026-08-05).
|
||||
|
||||
const agyApproval = ` python3 -c '
|
||||
import json
|
||||
with open("analys.ipynb", "r", encoding="utf-8") as f:
|
||||
⋯ (10 lines hidden)
|
||||
Do you want to proceed?
|
||||
> 1. Yes
|
||||
2. Yes, and always allow in this conversation for commands that start with 'python3 -c '
|
||||
import json
|
||||
with open("analys.ipynb", "r", encoding="utf-8") as f:...'
|
||||
3. Yes, and always allow for commands that start with 'python3 -c '
|
||||
import json
|
||||
with open("analys.ipynb", "r", encoding="utf-8") as f:...' (Persist to settings.json)
|
||||
4. No
|
||||
↑/↓ Navigate · tab Amend · ctrl+g edit/expand command
|
||||
esc to cancel Gemini 3.6 Flash · high`
|
||||
|
||||
const agyTrust = `Accessing workspace:
|
||||
/tmp/scratch/gemini-arena
|
||||
Do you trust the contents of this project?
|
||||
Antigravity CLI requires permission to read, edit, and execute files here.
|
||||
> Yes, I trust this folder
|
||||
No, exit
|
||||
↑/↓ Navigate · enter Confirm
|
||||
Gemini 3.6 Flash · high`
|
||||
|
||||
const agyIdle = ` ▄▀▀▄ Antigravity CLI 1.1.9
|
||||
▀▀▀▀▀▀ brasse.bb@gmail.com (Google AI Pro)
|
||||
────────────────────────────────────────────────────────────
|
||||
>
|
||||
────────────────────────────────────────────────────────────
|
||||
? for shortcuts Gemini 3.6 Flash · high`
|
||||
|
||||
// Agent output containing a markdown numbered list must NOT trigger.
|
||||
const agyMarkdownList = ` Here are 5 targeted CLI tools:
|
||||
1. ntfy: sends push notifications to your phone.
|
||||
2. tea: manages Gitea repositories from the command line.
|
||||
3. skopeo: inspects registry images.
|
||||
────────────────────────────────────────────────────────────
|
||||
>
|
||||
────────────────────────────────────────────────────────────
|
||||
? for shortcuts Gemini 3.6 Flash · high`
|
||||
|
||||
const geminiAuthPicker = ` ╭──────────────────────────────────────────────╮
|
||||
│ ? Get started │
|
||||
│ │
|
||||
│ How would you like to authenticate? │
|
||||
│ │
|
||||
│ ● 1. Sign in with Google │
|
||||
│ 2. Use Gemini API Key │
|
||||
│ 3. Vertex AI │
|
||||
│ │
|
||||
│ (Use Enter to select) │
|
||||
╰──────────────────────────────────────────────╯`
|
||||
|
||||
func TestApprovalPrompt(t *testing.T) {
|
||||
q := DetectQuestion(agyApproval)
|
||||
if q == nil {
|
||||
t.Fatal("expected question, got nil")
|
||||
}
|
||||
if !q.Numbered || len(q.Options) != 4 {
|
||||
t.Fatalf("want 4 numbered options, got %+v", q.Options)
|
||||
}
|
||||
if q.Options[0].Label != "Yes" || q.Options[3].Label != "No" {
|
||||
t.Errorf("bad labels: %+v", q.Options)
|
||||
}
|
||||
if q.Selected != 0 {
|
||||
t.Errorf("want selected=0, got %d", q.Selected)
|
||||
}
|
||||
if q.Text != "Do you want to proceed?" {
|
||||
t.Errorf("bad text: %q", q.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustPrompt(t *testing.T) {
|
||||
q := DetectQuestion(agyTrust)
|
||||
if q == nil {
|
||||
t.Fatal("expected question, got nil")
|
||||
}
|
||||
if q.Numbered || len(q.Options) != 2 {
|
||||
t.Fatalf("want 2 unnumbered options, got %+v", q.Options)
|
||||
}
|
||||
if q.Selected != 0 {
|
||||
t.Errorf("want selected=0, got %d", q.Selected)
|
||||
}
|
||||
if q.Options[1].Label != "No, exit" {
|
||||
t.Errorf("bad option: %+v", q.Options[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdleScreenNoQuestion(t *testing.T) {
|
||||
if q := DetectQuestion(agyIdle); q != nil {
|
||||
t.Fatalf("idle screen misdetected as question: %+v", q)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkdownListNoQuestion(t *testing.T) {
|
||||
if q := DetectQuestion(agyMarkdownList); q != nil {
|
||||
t.Fatalf("markdown list misdetected as question: %+v", q)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoxedNumberedPicker(t *testing.T) {
|
||||
q := DetectQuestion(geminiAuthPicker)
|
||||
if q == nil {
|
||||
t.Fatal("expected question, got nil")
|
||||
}
|
||||
if len(q.Options) != 3 || !q.Numbered {
|
||||
t.Fatalf("want 3 numbered options, got %+v", q.Options)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameQuestionSameHash(t *testing.T) {
|
||||
a, b := DetectQuestion(agyApproval), DetectQuestion(agyApproval)
|
||||
if a.Hash != b.Hash {
|
||||
t.Error("hash not stable")
|
||||
}
|
||||
if DetectQuestion(agyTrust).Hash == a.Hash {
|
||||
t.Error("different questions share hash")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user