Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

### Added
- **codex**: support `max` reasoning effort in project configuration and through `/reasoning max` (#1683).
- **`agent_session_idle_timeout_mins`**: new per-project config option that closes an idle live agent process after a clean turn while preserving the cc-connect session and saved agent session ID. The next message starts a new agent process and resumes the same conversation. Set to `0` or leave unset to disable (#1338).
- **Reasonix agent**: new agent adapter for Reasonix multi-model coding agent, bridging via HTTP serve API (POST /submit, SSE /events, POST /approve). Supports default/yolo/plan permission modes, SSE auto-reconnect with backoff, and thinking accumulator. (#1281)
- **cloud_web platform**: 新增 self-hosted IM Gateway 作为 first-class platform 接入 (CWIP v1 协议,支持 websocket / long_poll / gateway 3 种 transport,完整 inbound/outbound + capability negotiation + graceful degradation)。 详见 docs/cloud-web.md + #1282。
Expand Down
44 changes: 31 additions & 13 deletions agent/codex/codex.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ type Agent struct {
mu sync.RWMutex
}

type reasoningEffortDefinition struct {
name string
aliases []string
}

var reasoningEffortDefinitions = []reasoningEffortDefinition{
{name: "low"},
{name: "medium", aliases: []string{"med"}},
{name: "high"},
{name: "xhigh", aliases: []string{"x-high", "very-high"}},
{name: "max", aliases: []string{"maximum"}},
}

func New(opts map[string]any) (core.Agent, error) {
workDir, _ := opts["work_dir"].(string)
if workDir == "" {
Expand Down Expand Up @@ -142,20 +155,21 @@ func normalizeMode(raw string) string {
}

func normalizeReasoningEffort(raw string) string {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "":
return ""
case "low":
return "low"
case "medium", "med":
return "medium"
case "high":
return "high"
case "xhigh", "x-high", "very-high":
return "xhigh"
default:
normalized := strings.ToLower(strings.TrimSpace(raw))
if normalized == "" {
return ""
}
for _, definition := range reasoningEffortDefinitions {
if normalized == definition.name {
return definition.name
}
for _, alias := range definition.aliases {
if normalized == alias {
return definition.name
}
}
}
return ""
}

func (a *Agent) Name() string { return "codex" }
Expand Down Expand Up @@ -200,7 +214,11 @@ func (a *Agent) GetReasoningEffort() string {
}

func (a *Agent) AvailableReasoningEfforts() []string {
return []string{"low", "medium", "high", "xhigh"}
efforts := make([]string, len(reasoningEffortDefinitions))
for i, definition := range reasoningEffortDefinitions {
efforts[i] = definition.name
}
return efforts
}

func (a *Agent) configuredModels() []core.ModelOption {
Expand Down
11 changes: 10 additions & 1 deletion agent/codex/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,19 @@ func TestNormalizeReasoningEffort_RejectsMinimal(t *testing.T) {
}
}

func TestNormalizeReasoningEffort_AcceptsMax(t *testing.T) {
if got := normalizeReasoningEffort("max"); got != "max" {
t.Fatalf("normalizeReasoningEffort(max) = %q, want max", got)
}
if got := normalizeReasoningEffort("maximum"); got != "max" {
t.Fatalf("normalizeReasoningEffort(maximum) = %q, want max", got)
}
}

func TestAvailableReasoningEfforts_ExcludesMinimal(t *testing.T) {
agent := &Agent{}
got := agent.AvailableReasoningEfforts()
want := []string{"low", "medium", "high", "xhigh"}
want := []string{"low", "medium", "high", "xhigh", "max"}
if len(got) != len(want) {
t.Fatalf("AvailableReasoningEfforts len = %d, want %d, got=%v", len(got), len(want), got)
}
Expand Down
10 changes: 7 additions & 3 deletions core/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9768,7 +9768,7 @@ func (e *Engine) cmdReasoning(p Platform, msg *Message, args []string) {
buttons = append(buttons, row)
}
sb.WriteString("\n")
sb.WriteString(e.i18n.T(MsgReasoningUsage))
sb.WriteString(e.reasoningUsage(efforts))
e.replyWithButtons(p, msg.ReplyCtx, sb.String(), buttons)
return
}
Expand All @@ -9790,7 +9790,7 @@ func (e *Engine) cmdReasoning(p Platform, msg *Message, args []string) {
}
}
if !valid {
e.reply(p, msg.ReplyCtx, e.i18n.T(MsgReasoningUsage))
e.reply(p, msg.ReplyCtx, e.reasoningUsage(efforts))
return
}

Expand All @@ -9805,6 +9805,10 @@ func (e *Engine) cmdReasoning(p Platform, msg *Message, args []string) {
e.reply(p, msg.ReplyCtx, e.i18n.Tf(MsgReasoningChanged, target))
}

func (e *Engine) reasoningUsage(efforts []string) string {
return e.i18n.Tf(MsgReasoningUsage, strings.Join(efforts, "|"))
}

func (e *Engine) cmdMode(p Platform, msg *Message, args []string) {
agent, _, _, err := e.commandContext(p, msg)
if err != nil {
Expand Down Expand Up @@ -12917,7 +12921,7 @@ func (e *Engine) renderReasoningCard() *Card {
Markdown(sb.String()).
Select(e.i18n.T(MsgReasoningSelectPlaceholder), opts, initVal).
Buttons(e.cardBackButton())
cb.Note(e.i18n.T(MsgReasoningUsage))
cb.Note(e.reasoningUsage(efforts))
return cb.Build()
}

Expand Down
20 changes: 20 additions & 0 deletions core/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ type stubModelModeAgent struct {
model string
mode string
reasoningEffort string
reasoningLevels []string
providers []ProviderConfig
active string
}
Expand Down Expand Up @@ -545,6 +546,9 @@ func (a *stubModelModeAgent) GetReasoningEffort() string {
}

func (a *stubModelModeAgent) AvailableReasoningEfforts() []string {
if a.reasoningLevels != nil {
return a.reasoningLevels
}
return []string{"low", "medium", "high", "xhigh"}
}

Expand Down Expand Up @@ -5506,6 +5510,22 @@ func TestCmdReasoning_RejectsMinimal(t *testing.T) {
}
}

func TestCmdReasoning_UsageListsOnlyAgentReasoningEfforts(t *testing.T) {
p := &stubPlatformEngine{n: "plain"}
agent := &stubModelModeAgent{reasoningLevels: []string{"off", "minimal", "low", "medium", "high", "xhigh"}}
e := NewEngine("test", agent, []Platform{p}, "", LangEnglish)
msg := &Message{SessionKey: "test:user1", ReplyCtx: "ctx"}

e.cmdReasoning(p, msg, []string{"unsupported"})

if len(p.sent) != 1 || !strings.Contains(p.sent[0], "/reasoning <off|minimal|low|medium|high|xhigh>") {
t.Fatalf("sent = %v, want usage with agent-provided reasoning efforts", p.sent)
}
if strings.Contains(p.sent[0], "max") {
t.Fatalf("sent = %v, usage advertised an unavailable reasoning effort", p.sent)
}
}

// TestCmdReasoning_MultiWorkspaceSavesToWorkspaceSessions is a regression test
// for the bug where cmdReasoning called e.sessions.Save() (global) instead of
// sessions.Save() (workspace-resolved), leaving workspace session state unsaved.
Expand Down
10 changes: 5 additions & 5 deletions core/i18n.go
Original file line number Diff line number Diff line change
Expand Up @@ -2483,11 +2483,11 @@ var messages = map[MsgKey]map[Language]string{
LangSpanish: "Niveles de razonamiento disponibles:\n",
},
MsgReasoningUsage: {
LangEnglish: "Usage: `/reasoning <number>` or `/reasoning <low|medium|high|xhigh>`",
LangChinese: "用法: `/reasoning <序号>` 或 `/reasoning <low|medium|high|xhigh>`",
LangTraditionalChinese: "用法: `/reasoning <序號>` 或 `/reasoning <low|medium|high|xhigh>`",
LangJapanese: "使い方: `/reasoning <番号>` または `/reasoning <low|medium|high|xhigh>`",
LangSpanish: "Uso: `/reasoning <número>` o `/reasoning <low|medium|high|xhigh>`",
LangEnglish: "Usage: `/reasoning <number>` or `/reasoning <%s>`",
LangChinese: "用法: `/reasoning <序号>` 或 `/reasoning <%s>`",
LangTraditionalChinese: "用法: `/reasoning <序號>` 或 `/reasoning <%s>`",
LangJapanese: "使い方: `/reasoning <番号>` または `/reasoning <%s>`",
LangSpanish: "Uso: `/reasoning <número>` o `/reasoning <%s>`",
},
MsgModeUsage: {
LangEnglish: "\nUse `/mode <name>` to switch.\nAvailable: %s",
Expand Down
Loading