Files
kandev/apps/backend/internal/agent/agents/qoder_acp.go
T
João Salavisa 7b796b1eaa fix(agents): set confirmed SessionDirTemplate for Pi, Qoder, Kiro and Trae (#2019)
All four templates were confirmed against the shipped CLI, not inferred
from the agent name:

- pi-acp     -> {home}/.pi              (pi-acp@0.0.32 package source)
- qoder-acp  -> {home}/.qoder           (qodercli 1.1.7, --config-dir proof)
- kiro-acp   -> {home}/.kiro            (Kiro CLI 2.15.0, ACP session/new)
- trae-acp   -> {home}/.cache/trae-cli  (Trae CLI 0.120.48, ACP session/new)

Kiro and Trae deliberately break the "{home}/.<agent>" shape: Kiro keeps
its login token in ~/.local/share/kiro-cli/data.sqlite3 while sessions
live in ~/.kiro, and Trae keeps config in ~/.trae but session state in
the cache dir with auth in the OS keyring. Each Runtime() records the
evidence and the residual gaps.

Extends the shared ACP agent spec table with sessionDirTemplate so every
agent's session root is pinned, including the ones already set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 23:17:47 +01:00

119 lines
3.3 KiB
Go

//nolint:dupl // Native-binary ACP agents (Cursor, Kimi, Kiro, Qoder, Trae) follow the same minimal scaffold; differences are the binary name and subcommand.
package agents
import (
"context"
_ "embed"
"time"
"github.com/kandev/kandev/internal/agent/usage"
"github.com/kandev/kandev/pkg/agent"
)
//go:embed logos/qoder_acp_light.svg
var qoderACPLogoLight []byte
//go:embed logos/qoder_acp_dark.svg
var qoderACPLogoDark []byte
const qoderACPBin = "qodercli"
var (
_ Agent = (*QoderACP)(nil)
_ PassthroughAgent = (*QoderACP)(nil)
_ InferenceAgent = (*QoderACP)(nil)
)
// QoderACP implements Agent for the Qoder CLI using ACP.
type QoderACP struct {
StandardPassthrough
}
func NewQoderACP() *QoderACP {
return &QoderACP{
StandardPassthrough: StandardPassthrough{
PermSettings: emptyPermSettings,
Cfg: PassthroughConfig{
Supported: true,
Label: "CLI Passthrough",
Description: "Show terminal directly instead of chat interface",
PassthroughCmd: NewCommand(qoderACPBin),
ModelFlag: NewParam("--model", "{model}"),
IdleTimeout: 3 * time.Second,
BufferMaxBytes: DefaultBufferMaxBytes,
},
},
}
}
func (a *QoderACP) ID() string { return "qoder-acp" }
func (a *QoderACP) Name() string { return "Qoder ACP Agent" }
func (a *QoderACP) DisplayName() string { return "Qoder" }
func (a *QoderACP) Description() string {
return "Qoder coding agent using the ACP protocol via qodercli --acp."
}
func (a *QoderACP) Enabled() bool { return true }
func (a *QoderACP) DisplayOrder() int { return 16 }
func (a *QoderACP) Logo(v LogoVariant) []byte {
if v == LogoDark {
return qoderACPLogoDark
}
return qoderACPLogoLight
}
func (a *QoderACP) IsInstalled(ctx context.Context) (*DiscoveryResult, error) {
result, err := Detect(ctx, WithCommand(qoderACPBin))
if err != nil {
return result, err
}
result.SupportsMCP = true
result.Capabilities = DiscoveryCapabilities{
SupportsSessionResume: true,
}
return result, nil
}
func (a *QoderACP) BuildCommand(opts CommandOptions) Command {
return Cmd(qoderACPBin, "--acp").Build()
}
func (a *QoderACP) Runtime() *RuntimeConfig {
canRecover := true
return &RuntimeConfig{
Cmd: Cmd(qoderACPBin, "--acp").Build(),
WorkingDir: "{workspace}",
Env: map[string]string{},
ResourceLimits: ResourceLimits{MemoryMB: 4096, CPUCores: 2.0, Timeout: time.Hour},
Protocol: agent.ProtocolACP,
SessionConfig: SessionConfig{
// Verified against qodercli 1.1.7: every user-level artifact
// (settings.json, .auth/, logs/sessions/<project>) is written
// under ~/.qoder, and the --config-dir flag relocates that single
// root wholesale — running with it leaves nothing else in $HOME.
SessionDirTemplate: "{home}/.qoder",
NativeSessionResume: true,
CanRecover: &canRecover,
},
}
}
func (a *QoderACP) RemoteAuth() *RemoteAuth { return nil }
func (a *QoderACP) InstallScript() string {
return "Install Qoder CLI from https://qoder.com"
}
func (a *QoderACP) PermissionSettings() map[string]PermissionSetting {
return emptyPermSettings
}
func (a *QoderACP) InferenceConfig() *InferenceConfig {
return &InferenceConfig{
Supported: true,
Command: NewCommand(qoderACPBin, "--acp"),
}
}
func (a *QoderACP) BillingType() usage.BillingType { return defaultBillingType() }