This commit is contained in:
Daniel Lim 2026-04-13 12:36:26 +09:00 committed by GitHub
commit 03bea7a287
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 11 deletions

View file

@ -3468,12 +3468,18 @@ func (al *AgentLoop) applyExplicitSkillCommand(
if opts == nil || strings.TrimSpace(opts.SessionKey) == "" {
return true, true, commandsUnavailableSkillMessage()
}
al.setPendingSkills(opts.SessionKey, []string{skillName})
armedSkills := al.setPendingSkills(opts.SessionKey, []string{skillName})
if len(armedSkills) == 1 {
return true, true, fmt.Sprintf(
"Skill %q is armed for your next message. Send your next prompt normally, or use /use clear to cancel.",
skillName,
)
}
return true, true, fmt.Sprintf(
"Skills %s are armed for your next message. Send your next prompt normally, or use /use clear to cancel.",
quoteSkillNames(armedSkills),
)
}
message := strings.TrimSpace(strings.Join(parts[2:], " "))
if message == "" {
@ -3603,24 +3609,27 @@ func buildUseCommandHelp(agent *AgentInstance) string {
)
}
func (al *AgentLoop) setPendingSkills(sessionKey string, skillNames []string) {
func (al *AgentLoop) setPendingSkills(sessionKey string, skillNames []string) []string {
sessionKey = strings.TrimSpace(sessionKey)
if sessionKey == "" || len(skillNames) == 0 {
return
return nil
}
filtered := make([]string, 0, len(skillNames))
for _, name := range skillNames {
name = strings.TrimSpace(name)
if name != "" {
filtered = append(filtered, name)
merged := make([]string, 0, len(skillNames))
if existingValue, ok := al.pendingSkills.Load(sessionKey); ok {
if existingSkills, ok := existingValue.([]string); ok {
merged = append(merged, existingSkills...)
}
}
merged = append(merged, skillNames...)
filtered := uniqueSkillNames(merged)
if len(filtered) == 0 {
return
return nil
}
al.pendingSkills.Store(sessionKey, filtered)
return append([]string(nil), filtered...)
}
func (al *AgentLoop) takePendingSkills(sessionKey string) []string {
@ -3650,6 +3659,31 @@ func (al *AgentLoop) clearPendingSkills(sessionKey string) {
al.pendingSkills.Delete(sessionKey)
}
func uniqueSkillNames(skillNames []string) []string {
filtered := make([]string, 0, len(skillNames))
seen := make(map[string]struct{}, len(skillNames))
for _, name := range skillNames {
name = strings.TrimSpace(name)
if name == "" {
continue
}
if _, exists := seen[name]; exists {
continue
}
seen[name] = struct{}{}
filtered = append(filtered, name)
}
return filtered
}
func quoteSkillNames(skillNames []string) string {
quoted := make([]string, 0, len(skillNames))
for _, name := range skillNames {
quoted = append(quoted, fmt.Sprintf("%q", name))
}
return strings.Join(quoted, ", ")
}
func mapCommandError(result commands.ExecuteResult) string {
if result.Command == "" {
return fmt.Sprintf("Failed to execute command: %v", result.Err)

View file

@ -365,6 +365,60 @@ func TestApplyExplicitSkillCommand_ArmsSkillForNextMessage(t *testing.T) {
}
}
func TestApplyExplicitSkillCommand_AppendsPendingSkillsForNextMessage(t *testing.T) {
al, cfg, _, _, cleanup := newTestAgentLoop(t)
defer cleanup()
for _, skillName := range []string{"shell", "finance-news"} {
skillDir := filepath.Join(cfg.Agents.Defaults.Workspace, "skills", skillName)
if err := os.MkdirAll(skillDir, 0o755); err != nil {
t.Fatalf("MkdirAll(%s) error = %v", skillName, err)
}
if err := os.WriteFile(
filepath.Join(skillDir, "SKILL.md"),
[]byte("# "+skillName+"\n\nSkill test fixture.\n"),
0o644,
); err != nil {
t.Fatalf("WriteFile(%s/SKILL.md) error = %v", skillName, err)
}
}
agent := al.GetRegistry().GetDefaultAgent()
if agent == nil {
t.Fatal("expected default agent")
}
opts := &processOptions{SessionKey: "agent:main:test"}
_, handled, reply := al.applyExplicitSkillCommand("/use shell", agent, opts)
if !handled {
t.Fatal("expected /use shell to be handled")
}
if !strings.Contains(reply, `Skill "shell" is armed for your next message`) {
t.Fatalf("unexpected first reply: %q", reply)
}
_, handled, reply = al.applyExplicitSkillCommand("/use finance-news", agent, opts)
if !handled {
t.Fatal("expected /use finance-news to be handled")
}
if !strings.Contains(reply, `Skills "shell", "finance-news" are armed for your next message`) {
t.Fatalf("unexpected second reply: %q", reply)
}
_, handled, reply = al.applyExplicitSkillCommand("/use shell", agent, opts)
if !handled {
t.Fatal("expected duplicate /use shell to be handled")
}
if !strings.Contains(reply, `Skills "shell", "finance-news" are armed for your next message`) {
t.Fatalf("unexpected duplicate reply: %q", reply)
}
pending := al.takePendingSkills(opts.SessionKey)
if !slices.Equal(pending, []string{"shell", "finance-news"}) {
t.Fatalf("pending skills = %#v, want [shell finance-news]", pending)
}
}
func TestApplyExplicitSkillCommand_InlineMessageMutatesOptions(t *testing.T) {
al, cfg, _, _, cleanup := newTestAgentLoop(t)
defer cleanup()