Codex application override added.

This commit is contained in:
Storm Dragon committed 2026-10-28 20:46:12 -04:00
1 parent b887f6ea95
commit 7c53cb39d4
23 files changed
+1771 -155

No files matched your search

+5 -2
View File
@@ -9,6 +9,7 @@ import (
"slices"
"git.stormux.org/storm/fenrir/internal/applicationoverrides"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/codex"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/networkmanager"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/ninjam"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/rogue"
@@ -189,6 +190,8 @@ func (a *application) select_override(id string) {
}
var adapter applicationoverrides.Adapter
switch id {
case "codex":
adapter = &codex.Adapter{}
case "ninjam":
adapter = &ninjam.Adapter{}
case "networkmanager":
@@ -318,8 +321,8 @@ func (a *application) open_applications() {
a.voice.Say(i18n.T("Settings are busy"))
return
}
items := []string{i18n.T("Default"), i18n.T("NetworkManager"), i18n.T("Ninjam"), i18n.T("Rogue")}
ids := []string{"default", "networkmanager", "ninjam", "rogue"}
items := []string{i18n.T("Default"), i18n.T("NetworkManager"), i18n.T("Ninjam"), i18n.T("Rogue"), i18n.T("Codex")}
ids := []string{"default", "networkmanager", "ninjam", "rogue", "codex"}
position := 0
if selected := a.overrides[a.lastTerminal]; selected != nil {
position = slices.Index(ids, selected.id)
+3 -1
View File
@@ -181,8 +181,9 @@ func TestApplicationsMenuActivatesOnlyOnEnterAndReleasesCapture(t *testing.T) {
{"down", "NetworkManager"},
{"down", "Ninjam"},
{"down", "Rogue"},
{"down", "Codex"},
{"down", "Default"},
{"up", "Rogue"},
{"up", "Codex"},
} {
a.ui.handle(interaction.Key{Name: step.key})
if got := voice.messages[len(voice.messages)-1]; got != step.want {
@@ -218,6 +219,7 @@ func TestApplicationsMenuActivatesOnlyOnEnterAndReleasesCapture(t *testing.T) {
}
a.open_applications()
a.ui.handle(interaction.Key{Name: "down"}) // Rogue
a.ui.handle(interaction.Key{Name: "down"}) // Codex
a.ui.handle(interaction.Key{Name: "down"}) // list wraps to Default
a.ui.handle(interaction.Key{Name: "enter"})
if a.saves_unavailable() || a.engine.Tracking_mode() != "cursor" {
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"encoding/json"
"os"
"strings"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/codex"
)
func (a *application) codex_event(text string) string {
kind, payload, _ := strings.Cut(text, " ")
if kind != "CODEX" {
return "Unsupported application event"
}
var event codex.Event
if err := json.Unmarshal([]byte(payload), &event); err != nil {
return "Invalid Codex event JSON"
}
if a.options.TerminalMode {
if event.Window == "" || event.Window != os.Getenv("WINDOWID") || event.Terminal != 1 {
return "Codex event belongs to another terminal"
}
} else if event.Window != "" || event.Terminal != a.lastTerminal {
return "Codex event belongs to another terminal"
}
selected := a.overrides[event.Terminal]
if selected == nil || selected.id != "codex" {
return "Codex override is not selected"
}
if !selected.initialized || a.overrideTransition {
a.overrideDirty = true
return "Codex override is loading"
}
adapter, ok := selected.adapter.(*codex.Adapter)
if !ok {
return "Codex override is unavailable"
}
if err := adapter.Accept(event); err != nil {
return err.Error()
}
a.overrideDirty = true
return ""
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"encoding/json"
"os"
"slices"
"strings"
"testing"
"time"
"git.stormux.org/storm/fenrir/internal/applicationoverrides/codex"
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/control"
)
func TestCodexEventRoutingWholeMessagesMuteReviewAndExit(t *testing.T) {
t.Setenv("WINDOWID", "42")
a, voice := persistence_application(t, true)
a.options.TerminalMode = true
snapshot := override_screen(1, "OpenAI Codex")
override_read(t, a, snapshot)
a.select_override("codex")
override_read(t, a, snapshot)
base := codex.Event{Version: 1, Epoch: 1, Window: "42", Terminal: 1, Session: "fixture-session", Kind: "status", Status: "working"}
send := func(event codex.Event, uid int) string {
t.Helper()
encoded, err := json.Marshal(event)
if err != nil {
t.Fatal(err)
}
reply := make(chan string, 1)
a.remote_request(control.Request{UID: uid, Text: "APPLICATION CODEX " + string(encoded), Reply: reply})
return <-reply
}
other := base
other.Window = "43"
if reply := send(other, os.Geteuid()); !strings.Contains(reply, "another terminal") {
t.Fatal(reply)
}
if reply := send(base, os.Geteuid()+1); !strings.Contains(reply, "not permitted") {
t.Fatal(reply)
}
a.settings.live.Remote.EnableCommandRemote = false
if reply := send(base, os.Geteuid()); !strings.Contains(reply, "disabled") {
t.Fatal(reply)
}
a.settings.live.Remote.EnableCommandRemote = true
if reply := send(base, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
a.handle_command(command.ReadLiveRegion10)
if got := voice.messages[len(voice.messages)-1]; got != "Codex working" {
t.Fatal(got)
}
message := base
message.Kind, message.ID, message.Parts, message.Text = "message", "first", 2, "Complete "
start := len(voice.messages)
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, snapshot)
if len(voice.messages) != start {
t.Fatal("partial text spoken", voice.messages[start:])
}
message.Part, message.Text = 1, "message"
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, snapshot)
if got := voice.messages[len(voice.messages)-1]; got != "Complete message" {
t.Fatal(got)
}
message.Part, message.Parts, message.Text = 0, 1, "Complete message"
start = len(voice.messages)
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, override_screen(1, "file output and repeated streaming text"))
if len(voice.messages) != start {
t.Fatal("duplicate or tool output spoken", voice.messages[start:])
}
a.handle_command(command.Mute)
message.ID, message.Text = "muted", "Message while muted"
if reply := send(message, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, snapshot)
if len(voice.messages) != start {
t.Fatal("mute bypassed")
}
a.handle_command(command.Mute)
a.handle_command(command.ReadLiveRegion1)
if got := voice.messages[len(voice.messages)-1]; got != "Message while muted" {
t.Fatal(got)
}
// Stored replies must not replace raw screen review.
a.handle_command(command.CurrentLine)
if slices.Contains(voice.messages[len(voice.messages)-1:], "Message while muted") {
t.Fatal("history replaced raw review")
}
base.Status = "closed"
if reply := send(base, os.Geteuid()); reply != "" {
t.Fatal(reply)
}
override_read(t, a, override_screen(1, "[Username@host directory] $"))
if a.overrideCurrent != nil || a.overrides[1] != nil {
t.Fatal("shell did not restore defaults")
}
}
func TestCodexWorkingToneUsesExistingSoundGates(t *testing.T) {
for _, mode := range []string{"enabled", "sound off", "progress off", "muted", "automatic off", "menu"} {
t.Run(mode, func(t *testing.T) {
a, _ := persistence_application(t, true)
snapshot := override_screen(1, "OpenAI Codex")
override_read(t, a, snapshot)
a.select_override("codex")
override_read(t, a, snapshot)
sounds := &fakeSounds{}
a.cues = sounds
a.settings.live.Presentation.Sound.Enabled = mode != "sound off"
a.settings.live.Presentation.Progress.Enabled = mode != "progress off"
a.settings.live.Reading.Automatic = mode != "automatic off"
a.configure_reader()
if mode == "muted" {
a.handle_command(command.Mute)
}
if mode == "menu" {
a.open_applications()
}
adapter := a.overrideCurrent.adapter.(*codex.Adapter)
if err := adapter.Accept(codex.Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "status", Status: "working"}); err != nil {
t.Fatal(err)
}
override_read(t, a, snapshot)
want := mode == "enabled" || mode == "muted" || mode == "automatic off"
if (len(sounds.progress) > 0) != want {
t.Fatal(mode, sounds.progress)
}
if err := adapter.Accept(codex.Event{Version: 1, Epoch: 1, Session: "fixture-session", Kind: "status", Status: "permission"}); err != nil {
t.Fatal(err)
}
override_read(t, a, snapshot)
if _, ok := a.engine.Next_progress(time.Now().Add(2 * time.Second)); ok {
t.Fatal("approval wait retained working beep")
}
})
}
}
+14 -1
View File
@@ -188,6 +188,12 @@ func (a *application) remote_request(request control.Request) {
} else {
section, text, _ := strings.Cut(request.Text, " ")
switch strings.ToUpper(section) {
case "APPLICATION":
if !a.settings.live.Remote.EnableCommandRemote {
response = "Command remote control is disabled"
} else {
response = a.codex_event(text)
}
case "COMMAND":
if !a.settings.live.Remote.EnableCommandRemote {
response = "Command remote control is disabled"
@@ -245,7 +251,14 @@ func (a *application) remote_request(request control.Request) {
func remote_reply(reply chan string, response string) {
if response != "" {
slog.Warn("remote request rejected", "error", response, "at", time.Now())
// An opt-in bridge may be attached while its override is unselected,
// loading, or on another VT. Its normal retry must not fill the log.
switch response {
case "Codex override is not selected", "Codex override is loading", "Codex event belongs to another terminal":
slog.Debug("application event deferred", "reason", response, "at", time.Now())
default:
slog.Warn("remote request rejected", "error", response, "at", time.Now())
}
}
select {
case reply <- response: