Merge dd925c764b into 2b2bc26f8e
This commit is contained in:
commit
03bea7a287
2 changed files with 99 additions and 11 deletions
|
|
@ -3468,10 +3468,16 @@ func (al *AgentLoop) applyExplicitSkillCommand(
|
||||||
if opts == nil || strings.TrimSpace(opts.SessionKey) == "" {
|
if opts == nil || strings.TrimSpace(opts.SessionKey) == "" {
|
||||||
return true, true, commandsUnavailableSkillMessage()
|
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(
|
return true, true, fmt.Sprintf(
|
||||||
"Skill %q is armed for your next message. Send your next prompt normally, or use /use clear to cancel.",
|
"Skills %s are armed for your next message. Send your next prompt normally, or use /use clear to cancel.",
|
||||||
skillName,
|
quoteSkillNames(armedSkills),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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)
|
sessionKey = strings.TrimSpace(sessionKey)
|
||||||
if sessionKey == "" || len(skillNames) == 0 {
|
if sessionKey == "" || len(skillNames) == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered := make([]string, 0, len(skillNames))
|
merged := make([]string, 0, len(skillNames))
|
||||||
for _, name := range skillNames {
|
if existingValue, ok := al.pendingSkills.Load(sessionKey); ok {
|
||||||
name = strings.TrimSpace(name)
|
if existingSkills, ok := existingValue.([]string); ok {
|
||||||
if name != "" {
|
merged = append(merged, existingSkills...)
|
||||||
filtered = append(filtered, name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
merged = append(merged, skillNames...)
|
||||||
|
|
||||||
|
filtered := uniqueSkillNames(merged)
|
||||||
if len(filtered) == 0 {
|
if len(filtered) == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
al.pendingSkills.Store(sessionKey, filtered)
|
al.pendingSkills.Store(sessionKey, filtered)
|
||||||
|
return append([]string(nil), filtered...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (al *AgentLoop) takePendingSkills(sessionKey string) []string {
|
func (al *AgentLoop) takePendingSkills(sessionKey string) []string {
|
||||||
|
|
@ -3650,6 +3659,31 @@ func (al *AgentLoop) clearPendingSkills(sessionKey string) {
|
||||||
al.pendingSkills.Delete(sessionKey)
|
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 {
|
func mapCommandError(result commands.ExecuteResult) string {
|
||||||
if result.Command == "" {
|
if result.Command == "" {
|
||||||
return fmt.Sprintf("Failed to execute command: %v", result.Err)
|
return fmt.Sprintf("Failed to execute command: %v", result.Err)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
func TestApplyExplicitSkillCommand_InlineMessageMutatesOptions(t *testing.T) {
|
||||||
al, cfg, _, _, cleanup := newTestAgentLoop(t)
|
al, cfg, _, _, cleanup := newTestAgentLoop(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue