feat(agent): add Grok ACP agent (#1688)

This commit is contained in:
zensi-dev
2026-07-15 00:35:38 +05:30
committed by GitHub
parent 8be7212589
commit 7a90408086
11 changed files with 355 additions and 6 deletions
+6 -5
View File
@@ -29,13 +29,13 @@ Open source, multi-provider, no telemetry, not tied to any cloud.
## Features ## Features
- **Multi-agent support** - Claude Code, Codex, GitHub Copilot, Gemini CLI, Amp, Auggie, OpenCode, Cursor, Devin, Qwen, Factory Droid, iFlow, Kilocode, Pi, Kimi, AWS Kiro, Qoder, Trae, Oh My Pi - **Multi-agent support** - Claude Code, Codex, GitHub Copilot, Gemini CLI, Amp, Auggie, OpenCode, Cursor, Devin, Qwen, Factory Droid, iFlow, Kilocode, Pi, Kimi, AWS Kiro, Qoder, Trae, Oh My Pi, Grok
- **Parallel task execution** – start and manage multiple tasks from different sources simultaneously, boosting productivity with AI agents - **Parallel task execution** – start and manage multiple tasks from different sources simultaneously, boosting productivity with AI agents
- **Integrated workspace** - Built-in terminal, code editor with LSP, git changes panel, embedded vscode and chat in one IDE-like view - **Integrated workspace** - Built-in terminal, code editor with LSP, git changes panel, embedded vscode and chat in one IDE-like view
- **Kanban task management** - Drag-and-drop boards, columns, and workflow automation - **Kanban task management** - Drag-and-drop boards, columns, and workflow automation
- **Agentic workflows** - Multi-step pipelines that mix-and-match agents per step - for example, Claude Code Opus to design a plan, GitHub Copilot Sonnet to implement it, and Codex GPT 5.4 to review the changes. See [docs/workflow-tips.md](docs/workflow-tips.md) - **Agentic workflows** - Multi-step pipelines that mix-and-match agents per step - for example, Claude Code Opus to design a plan, GitHub Copilot Sonnet to implement it, and Codex GPT 5.4 to review the changes. See [docs/workflow-tips.md](docs/workflow-tips.md)
- **Sub-tasks** - Agents can spawn sub-tasks that resume from the parent task's session. Useful for splitting a task that has grown too big, or producing several PRs from the same starting point. - **Sub-tasks** - Agents can spawn sub-tasks that resume from the parent task's session. Useful for splitting a task that has grown too big, or producing several PRs from the same starting point.
- **CLI passthrough** - Drop into raw agent CLI mode for direct terminal interaction with any supported agent, leverage the full power of their TUIs - **CLI passthrough** - Drop into raw agent CLI mode for direct terminal interaction with agents that support it, leveraging their full native TUI
- **Workspace isolation** - Git worktrees prevent concurrent agents from conflicting - **Workspace isolation** - Git worktrees prevent concurrent agents from conflicting
- **Multi-repository tasks** - Span a single task across multiple repositories, with one worktree per repo, per-repo branches, per-repo PRs, and per-repo grouping in the Changes panel and review dialog - **Multi-repository tasks** - Span a single task across multiple repositories, with one worktree per repo, per-repo branches, per-repo PRs, and per-repo grouping in the Changes panel and review dialog
- **Flexible runtimes** - Run agents as local processes, in isolated Docker containers, on remote servers via SSH, or in cloud executors like sprites.dev - **Flexible runtimes** - Run agents as local processes, in isolated Docker containers, on remote servers via SSH, or in cloud executors like sprites.dev
@@ -89,8 +89,9 @@ Connect Kandev to the tools your team already uses — pull issues into the kanb
| **Qoder** | `qodercli --acp` *(install Qoder CLI)* | | **Qoder** | `qodercli --acp` *(install Qoder CLI)* |
| **Trae** | `traecli acp serve` *(install Trae IDE CLI)* | | **Trae** | `traecli acp serve` *(install Trae IDE CLI)* |
| **Oh My Pi** | `omp acp` *(install `bun install -g @oh-my-pi/pi-coding-agent`)* | | **Oh My Pi** | `omp acp` *(install `bun install -g @oh-my-pi/pi-coding-agent`)* |
| **Grok** | `grok --no-auto-update agent stdio` *(install `npm install -g @xai-official/grok`)* |
> All agents communicate via [ACP](https://agentclientprotocol.com) (Agent Client Protocol). Some agents support ACP natively, while others use ACP adapter packages that bridge their native protocols. **CLI Passthrough mode** is also available for direct terminal interaction with any agent CLI. If your agent isn't supported yet, open an issue or submit a PR with the integration. See [Adding a New Agent CLI](docs/add-agent-cli.md) for a step-by-step guide. > All agents communicate via [ACP](https://agentclientprotocol.com) (Agent Client Protocol). Some agents support ACP natively, while others use ACP adapter packages that bridge their native protocols. **CLI Passthrough mode** is available when an integration provides a passthrough command. If your agent isn't supported yet, open an issue or submit a PR with the integration. See [Adding a New Agent CLI](docs/add-agent-cli.md) for a step-by-step guide.
### Bring your own TUI agents ### Bring your own TUI agents
@@ -249,8 +250,8 @@ There are a few similar tools in this space, and new ones appearing everyday. He
- **Server-first architecture** - The core app runs as a server you can access from any device, including your phone. - **Server-first architecture** - The core app runs as a server you can access from any device, including your phone.
- **Remote runtimes** - Run agents on remote servers via SSH, Docker hosts, and cloud environments, not just your local machine. - **Remote runtimes** - Run agents on remote servers via SSH, Docker hosts, and cloud environments, not just your local machine.
- **Multi-provider** - Use Claude Code, Codex, Copilot, Gemini, Amp, Auggie, OpenCode, Cursor, Devin, Qwen, Droid, iFlow, Kilocode, Pi, Kimi, Kiro, Qoder, Trae, and Oh My Pi side by side. Not locked to one vendor. - **Multi-provider** - Use Claude Code, Codex, Copilot, Gemini, Amp, Auggie, OpenCode, Cursor, Devin, Qwen, Droid, iFlow, Kilocode, Pi, Kimi, Kiro, Qoder, Trae, Oh My Pi, and Grok side by side. Not locked to one vendor.
- **CLI passthrough and chat** - Interact with agents through structured chat messages or drop into raw CLI mode for full agent TUI capabilities. - **CLI passthrough and chat** - Interact with agents through structured chat messages or, where supported, drop into raw CLI mode for full agent TUI capabilities.
- **Open source and self-hostable** - No vendor lock-in, no telemetry, runs on your infrastructure. - **Open source and self-hostable** - No vendor lock-in, no telemetry, runs on your infrastructure.
## Contributing ## Contributing
@@ -0,0 +1,137 @@
//nolint:dupl // Native-binary ACP agents follow the same minimal scaffold; differences are the binary name, argv, and auth surface.
package agents
import (
"context"
_ "embed"
"time"
"github.com/kandev/kandev/internal/agent/usage"
"github.com/kandev/kandev/pkg/agent"
)
//go:embed logos/grok_acp_light.svg
var grokACPLogoLight []byte
//go:embed logos/grok_acp_dark.svg
var grokACPLogoDark []byte
const grokACPBin = "grok"
// grokACPArgv is the ACP / inference launch argv. --no-auto-update must
// precede the agent subcommand so background self-update checks do not run
// for Kandev-managed sessions.
var grokACPArgv = []string{grokACPBin, "--no-auto-update", "agent", "stdio"}
var (
_ Agent = (*GrokACP)(nil)
_ InferenceAgent = (*GrokACP)(nil)
_ LoginAgent = (*GrokACP)(nil)
)
// GrokACP implements Agent for xAI's Grok Build CLI using native ACP over
// stdin/stdout. Raw TUI passthrough is intentionally deferred (requires a
// project-scoped PassthroughMCPStrategy — see ADR 0014).
type GrokACP struct{}
func NewGrokACP() *GrokACP {
return &GrokACP{}
}
func (a *GrokACP) ID() string { return "grok-acp" }
func (a *GrokACP) Name() string { return "Grok ACP Agent" }
func (a *GrokACP) DisplayName() string { return "Grok" }
func (a *GrokACP) Description() string {
return "xAI Grok coding agent using the ACP protocol over stdin/stdout."
}
func (a *GrokACP) Enabled() bool { return true }
func (a *GrokACP) DisplayOrder() int { return 20 }
func (a *GrokACP) Logo(v LogoVariant) []byte {
if v == LogoDark {
return grokACPLogoDark
}
return grokACPLogoLight
}
func (a *GrokACP) IsInstalled(ctx context.Context) (*DiscoveryResult, error) {
result, err := Detect(ctx, WithCommand(grokACPBin))
if err != nil {
return result, err
}
result.SupportsMCP = true
result.Capabilities = DiscoveryCapabilities{
SupportsSessionResume: true,
}
return result, nil
}
func (a *GrokACP) BuildCommand(opts CommandOptions) Command {
return Cmd(grokACPArgv...).Build()
}
func (a *GrokACP) Runtime() *RuntimeConfig {
canRecover := true
return &RuntimeConfig{
Cmd: Cmd(grokACPArgv...).Build(),
WorkingDir: "{workspace}",
RequiredEnv: []string{}, // Cached OAuth or optional XAI_API_KEY — not a launch prerequisite.
Env: map[string]string{},
ResourceLimits: ResourceLimits{MemoryMB: 4096, CPUCores: 2.0, Timeout: time.Hour},
Protocol: agent.ProtocolACP,
ProjectSkillDir: ".grok/skills",
UserSkillDir: ".grok/skills",
SessionConfig: SessionConfig{
NativeSessionResume: true,
CanRecover: &canRecover,
SessionDirTemplate: "{home}/.grok",
},
}
}
func (a *GrokACP) RemoteAuth() *RemoteAuth {
return &RemoteAuth{
Methods: []RemoteAuthMethod{
{
Type: "files",
Label: "Copy auth files",
SourceFiles: map[string][]string{
"darwin": {".grok/auth.json"},
"linux": {".grok/auth.json"},
},
// Only auth.json — do not copy config.toml, sessions, logs, or caches.
TargetRelDir: ".grok",
},
{
Type: "env",
EnvVar: "XAI_API_KEY",
},
},
}
}
// LoginCommand uses device-code auth so headless/container/SSH environments
// can complete sign-in without a local browser.
func (a *GrokACP) LoginCommand() *LoginCommand {
return &LoginCommand{
Cmd: []string{grokACPBin, "login", "--device-auth"},
Description: "Sign in with your xAI / Grok account.",
}
}
func (a *GrokACP) InstallScript() string {
return "npm install -g @xai-official/grok"
}
func (a *GrokACP) PermissionSettings() map[string]PermissionSetting {
return emptyPermSettings
}
func (a *GrokACP) InferenceConfig() *InferenceConfig {
return &InferenceConfig{
Supported: true,
Command: NewCommand(grokACPArgv...),
}
}
func (a *GrokACP) BillingType() usage.BillingType { return defaultBillingType() }
@@ -0,0 +1,201 @@
package agents
import (
"context"
"os/exec"
"slices"
"strings"
"testing"
"github.com/kandev/kandev/internal/agent/usage"
"github.com/kandev/kandev/pkg/agent"
)
func TestGrokACP_IDAndDisplay(t *testing.T) {
a := NewGrokACP()
if got := a.ID(); got != "grok-acp" {
t.Errorf("ID() = %q, want grok-acp", got)
}
if got := a.DisplayName(); got != "Grok" {
t.Errorf("DisplayName() = %q, want Grok", got)
}
if !a.Enabled() {
t.Error("Enabled() = false, want true")
}
if got := a.DisplayOrder(); got != 20 {
t.Errorf("DisplayOrder() = %d, want 20", got)
}
}
func TestGrokACP_AllCommandSurfaces(t *testing.T) {
a := NewGrokACP()
want := []string{"grok", "--no-auto-update", "agent", "stdio"}
assertArgvEqual(t, "BuildCommand", a.BuildCommand(CommandOptions{}).Args(), want)
rt := a.Runtime()
if rt == nil {
t.Fatal("Runtime() returned nil")
}
if rt.Protocol != agent.ProtocolACP {
t.Errorf("Runtime.Protocol = %q, want ACP", rt.Protocol)
}
assertArgvEqual(t, "Runtime.Cmd", rt.Cmd.Args(), want)
ic := a.InferenceConfig()
if ic == nil || !ic.Supported {
t.Fatalf("InferenceConfig() = %+v, want Supported=true", ic)
}
assertArgvEqual(t, "InferenceConfig.Command", ic.Command.Args(), want)
// ACP-only slice: no PassthroughAgent.
if _, ok := any(a).(PassthroughAgent); ok {
t.Error("GrokACP must not implement PassthroughAgent in this slice")
}
}
func TestGrokACP_InstallScript(t *testing.T) {
got := NewGrokACP().InstallScript()
want := "npm install -g @xai-official/grok"
if got != want {
t.Errorf("InstallScript() = %q, want %q", got, want)
}
}
func TestGrokACP_DetectionRequiresGlobalBinary(t *testing.T) {
if _, err := exec.LookPath("grok"); err == nil {
t.Skip("detection binary \"grok\" is on PATH; can't verify availability requirement")
}
result, err := NewGrokACP().IsInstalled(context.Background())
if err != nil {
t.Fatalf("IsInstalled error: %v", err)
}
if result.Available {
t.Error("Available=true without grok on PATH; discovery must not imply install")
}
}
func TestGrokACP_LogosNonEmpty(t *testing.T) {
a := NewGrokACP()
if len(a.Logo(LogoLight)) == 0 {
t.Error("Logo(LogoLight) is empty")
}
if len(a.Logo(LogoDark)) == 0 {
t.Error("Logo(LogoDark) is empty")
}
if !strings.Contains(string(a.Logo(LogoLight)), "<svg") {
t.Error("Logo(LogoLight) is not SVG")
}
}
func TestGrokACP_DisplayOrderUnique(t *testing.T) {
all := []Agent{
NewClaudeACP(), NewCodexACP(), NewAuggie(), NewOpenCodeACP(),
NewGemini(), NewCopilotACP(), NewAmpACP(), NewQwenACP(),
NewIFlowACP(), NewDroidACP(), NewKilocodeACP(), NewPiACP(),
NewCursorACP(), NewKimiACP(), NewKiroACP(), NewQoderACP(),
NewTraeACP(), NewOmpACP(), NewDevinACP(), NewGrokACP(),
NewMockAgent(),
}
seen := map[int]string{}
for _, ag := range all {
order := ag.DisplayOrder()
if other, exists := seen[order]; exists {
t.Errorf("DisplayOrder %d collision: %s and %s", order, other, ag.ID())
}
seen[order] = ag.ID()
}
}
func TestGrokACP_RemoteAuth(t *testing.T) {
auth := NewGrokACP().RemoteAuth()
if auth == nil {
t.Fatal("RemoteAuth() returned nil")
}
if len(auth.Methods) != 2 {
t.Fatalf("Methods len = %d, want 2", len(auth.Methods))
}
files := auth.Methods[0]
if files.Type != "files" {
t.Errorf("Methods[0].Type = %q, want files", files.Type)
}
if files.TargetRelDir != ".grok" {
t.Errorf("TargetRelDir = %q, want .grok", files.TargetRelDir)
}
for _, osName := range []string{"darwin", "linux"} {
paths := files.SourceFiles[osName]
if !slices.Equal(paths, []string{".grok/auth.json"}) {
t.Errorf("SourceFiles[%s] = %#v, want [.grok/auth.json] only", osName, paths)
}
for _, p := range paths {
if strings.Contains(p, "config.toml") || strings.Contains(p, "session") {
t.Errorf("must not copy non-auth state: %q", p)
}
}
}
env := auth.Methods[1]
if env.Type != "env" || env.EnvVar != "XAI_API_KEY" {
t.Errorf("Methods[1] = %+v, want env XAI_API_KEY", env)
}
}
func TestGrokACP_LoginCommand(t *testing.T) {
cmd := NewGrokACP().LoginCommand()
if cmd == nil {
t.Fatal("LoginCommand() returned nil")
}
want := []string{"grok", "login", "--device-auth"}
if !slices.Equal(cmd.Cmd, want) {
t.Errorf("LoginCommand.Cmd = %#v, want %#v", cmd.Cmd, want)
}
if cmd.Description == "" {
t.Error("LoginCommand.Description is empty")
}
}
func TestGrokACP_SessionAndSkills(t *testing.T) {
rt := NewGrokACP().Runtime()
if rt.WorkingDir != "{workspace}" {
t.Errorf("WorkingDir = %q, want {workspace}", rt.WorkingDir)
}
if len(rt.RequiredEnv) != 0 {
t.Errorf("RequiredEnv = %#v, want empty (cached OAuth is valid without XAI_API_KEY)", rt.RequiredEnv)
}
if rt.ProjectSkillDir != ".grok/skills" {
t.Errorf("ProjectSkillDir = %q, want .grok/skills", rt.ProjectSkillDir)
}
if rt.UserSkillDir != ".grok/skills" {
t.Errorf("UserSkillDir = %q, want .grok/skills", rt.UserSkillDir)
}
sc := rt.SessionConfig
if !sc.NativeSessionResume {
t.Error("NativeSessionResume = false, want true")
}
if sc.CanRecover == nil || !*sc.CanRecover {
t.Error("CanRecover must be true")
}
if sc.SessionDirTemplate != "{home}/.grok" {
t.Errorf("SessionDirTemplate = %q, want {home}/.grok", sc.SessionDirTemplate)
}
}
func TestGrokACP_PermissionAndBillingDefaults(t *testing.T) {
a := NewGrokACP()
if len(a.PermissionSettings()) != 0 {
t.Errorf("PermissionSettings() = %#v, want empty (agentctl auto-approve is authoritative)", a.PermissionSettings())
}
if got := a.BillingType(); got != usage.BillingTypeAPIKey {
t.Errorf("BillingType() = %q, want %q", got, usage.BillingTypeAPIKey)
}
// Catalog still injects agentctl auto-approve.
catalog := CatalogPermissionSettings(a)
auto, ok := catalog[PermissionKeyAutoApprove]
if !ok {
t.Fatal("catalog missing auto_approve")
}
if auto.ApplyMethod != PermissionApplyMethodAgentctlAutoApprove {
t.Errorf("auto_approve ApplyMethod = %q, want agentctl auto-approve", auto.ApplyMethod)
}
}
@@ -0,0 +1 @@
<svg fill="#FFFFFF" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>

After

Width:  |  Height:  |  Size: 752 B

@@ -0,0 +1 @@
<svg fill="#000000" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>

After

Width:  |  Height:  |  Size: 752 B

@@ -89,6 +89,7 @@ func TestUserSkillDir_KnownProviders(t *testing.T) {
{"codex", NewCodexACP(), ".codex/skills"}, {"codex", NewCodexACP(), ".codex/skills"},
{"opencode", NewOpenCodeACP(), ".config/opencode/skills"}, {"opencode", NewOpenCodeACP(), ".config/opencode/skills"},
{"copilot", NewCopilotACP(), ".copilot/skills"}, {"copilot", NewCopilotACP(), ".copilot/skills"},
{"grok", NewGrokACP(), ".grok/skills"},
{"mock-agent", NewMockAgent(), ".mock-agent/skills"}, {"mock-agent", NewMockAgent(), ".mock-agent/skills"},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -64,6 +64,7 @@ func (r *Registry) LoadDefaults() {
agents.NewTraeACP(), agents.NewTraeACP(),
agents.NewOmpACP(), agents.NewOmpACP(),
agents.NewDevinACP(), agents.NewDevinACP(),
agents.NewGrokACP(),
agents.NewMockAgent(), agents.NewMockAgent(),
} }
@@ -253,6 +253,9 @@ func TestRegistry_LoadDefaults(t *testing.T) {
if !reg.Exists("auggie") { if !reg.Exists("auggie") {
t.Error("expected default agent 'auggie' to be loaded") t.Error("expected default agent 'auggie' to be loaded")
} }
if !reg.Exists("grok-acp") {
t.Error("expected default agent 'grok-acp' to be loaded")
}
} }
func TestRegistry_List_OrderedByDisplayOrder(t *testing.T) { func TestRegistry_List_OrderedByDisplayOrder(t *testing.T) {
@@ -717,6 +717,7 @@ var allowedProbeCommands = map[string]string{
"auggie": "auggie", "auggie": "auggie",
"cursor-agent": "cursor-agent", "cursor-agent": "cursor-agent",
"devin": "devin", "devin": "devin",
"grok": "grok",
"kimi": "kimi", "kimi": "kimi",
"kiro-cli-chat": "kiro-cli-chat", "kiro-cli-chat": "kiro-cli-chat",
"mock-agent": "mock-agent", "mock-agent": "mock-agent",
+1 -1
View File
@@ -16,7 +16,7 @@ This page expands the short feature list in the README without turning the READM
## Agent Interfaces ## Agent Interfaces
- **ACP agents:** Kandev supports ACP-native and ACP-adapter agents such as Claude Code, Codex, GitHub Copilot, Gemini CLI, Amp, Auggie, OpenCode, Cursor, Qwen, Factory Droid, iFlow, Kilocode, Pi, Kimi, Kiro, Qoder, Trae, and Oh My Pi. - **ACP agents:** Kandev supports ACP-native and ACP-adapter agents such as Claude Code, Codex, GitHub Copilot, Gemini CLI, Amp, Auggie, OpenCode, Cursor, Qwen, Factory Droid, iFlow, Kilocode, Pi, Kimi, Kiro, Qoder, Trae, Oh My Pi, and Grok.
- **Bring-your-own TUI agents:** any agent CLI can run in a PTY terminal through CLI passthrough, even without ACP support. - **Bring-your-own TUI agents:** any agent CLI can run in a PTY terminal through CLI passthrough, even without ACP support.
- **Voice mode:** dictate chat prompts from the composer. Supported engines include browser Web Speech, local in-browser Whisper Web, and server-side Whisper. Settings include language, click-to-toggle or hold-to-talk activation, auto-send, Whisper model size, and keyboard shortcut. - **Voice mode:** dictate chat prompts from the composer. Supported engines include browser Web Speech, local in-browser Whisper Web, and server-side Whisper. Settings include language, click-to-toggle or hold-to-talk activation, auto-send, Whisper model size, and keyboard shortcut.
- **Utility agents:** one-shot helpers can generate or improve prompt text, branch names, commit messages, commit descriptions, PR titles, PR descriptions, and session summaries. Users can choose a default model, override models per action, and add custom utility agents. - **Utility agents:** one-shot helpers can generate or improve prompt text, branch names, commit messages, commit descriptions, PR titles, PR descriptions, and session summaries. Users can choose a default model, override models per action, and add custom utility agents.
+2
View File
@@ -264,6 +264,7 @@ Each agent type defines `ProjectSkillDir` in its `RuntimeConfig`:
| Agent CLI | `ProjectSkillDir` | | Agent CLI | `ProjectSkillDir` |
|---|---| |---|---|
| `claude-acp` (Claude Code) | `.claude/skills` | | `claude-acp` (Claude Code) | `.claude/skills` |
| `grok-acp` (Grok) | `.grok/skills` |
| `codex-acp`, `opencode-acp`, `gemini`, `copilot-acp`, `auggie`, `amp-acp` | `.agents/skills` | | `codex-acp`, `opencode-acp`, `gemini`, `copilot-acp`, `auggie`, `amp-acp` | `.agents/skills` |
Default (if unset): `.agents/skills`. Skills are written to `<worktree>/<ProjectSkillDir>/kandev-<slug>/SKILL.md`. The `kandev-` prefix distinguishes injected skills from team-committed skills already in the repo. Default (if unset): `.agents/skills`. Skills are written to `<worktree>/<ProjectSkillDir>/kandev-<slug>/SKILL.md`. The `kandev-` prefix distinguishes injected skills from team-committed skills already in the repo.
@@ -274,6 +275,7 @@ Before writing skills, all existing `kandev-*` directories in the target path ar
``` ```
.claude/skills/kandev-* .claude/skills/kandev-*
.grok/skills/kandev-*
.agents/skills/kandev-* .agents/skills/kandev-*
``` ```