fix(tool): a refusal's remedy names the setting tool by its capability id, tool:setting, now that the tool sits behind use_capability and a bare "setting" is not a name the model can see

This commit is contained in:
yhh committed 2026-11-06 22:51:42 -08:00
1 parent 20c2705623
commit 6264845f53
5 files changed
+7 -7

No files matched your search

@@ -172,7 +172,7 @@ func TestEffectTheModelCannotProposeAGuardedSetting(t *testing.T) {
default:
}
result := rec.toolResult()
for _, want := range []string{"setting.guarded", "(fix: setting op=open id=permissions.mode)"} {
for _, want := range []string{"setting.guarded", "(fix: tool:setting op=open id=permissions.mode)"} {
if !strings.Contains(result, want) {
t.Fatalf("tool result = %q, want %q", result, want)
}
@@ -32,7 +32,7 @@ func TestEffectSettingToolCannotChangeOrDiscloseUserPersona(t *testing.T) {
default:
}
result := rec.toolResult()
for _, want := range []string{"setting.guarded", "(fix: setting op=open id=" + tc.id + ")"} {
for _, want := range []string{"setting.guarded", "(fix: tool:setting op=open id=" + tc.id + ")"} {
if !strings.Contains(result, want) {
t.Fatalf("tool result = %q, want %q", result, want)
}
+2 -2
View File
@@ -7,7 +7,7 @@ import (
func TestARefusalRendersTheSettingThatWouldLiftIt(t *testing.T) {
r := Refusal{Code: "peer.disabled", Message: "Messages between sessions are off.", Remedy: &Remedy{Op: RemedyPropose, Setting: "peers.enabled", Value: json.RawMessage(`true`)}}
want := "Messages between sessions are off. (refusal: peer.disabled) (fix: setting op=propose id=peers.enabled value=true)"
want := "Messages between sessions are off. (refusal: peer.disabled) (fix: tool:setting op=propose id=peers.enabled value=true)"
if got := r.String(); got != want {
t.Fatalf("got %q, want %q", got, want)
}
@@ -15,7 +15,7 @@ func TestARefusalRendersTheSettingThatWouldLiftIt(t *testing.T) {
func TestARemedyThatOnlyOpensCarriesNoValue(t *testing.T) {
r := Refusal{Code: "x.y", Remedy: &Remedy{Op: RemedyOpen, Setting: "permissions.mode", Value: json.RawMessage(`"yolo"`)}}
if got, want := r.String(), "(refusal: x.y) (fix: setting op=open id=permissions.mode)"; got != want {
if got, want := r.String(), "(refusal: x.y) (fix: tool:setting op=open id=permissions.mode)"; got != want {
t.Fatalf("got %q, want %q", got, want)
}
}
+2 -2
View File
@@ -25,9 +25,9 @@ const (
func (r Remedy) String() string {
if r.Op == RemedyPropose && len(r.Value) > 0 {
return fmt.Sprintf("setting op=%s id=%s value=%s", r.Op, r.Setting, r.Value)
return fmt.Sprintf("tool:setting op=%s id=%s value=%s", r.Op, r.Setting, r.Value)
}
return fmt.Sprintf("setting op=%s id=%s", r.Op, r.Setting)
return fmt.Sprintf("tool:setting op=%s id=%s", r.Op, r.Setting)
}
// SettingInfo is one row of what the model may learn about a setting.
+1 -1
View File
@@ -19,7 +19,7 @@ type settingTool struct{ link *tool.SettingsLink }
func (*settingTool) Name() string { return "setting" }
func (*settingTool) Description() string {
return "Find and change the user's own settings. op=list finds settings by words of their id (for example \"peers dispatch\"). op=propose asks the user to confirm one change: give the id, the new value and a short reason; nothing changes until the user answers, and the result says what changed or that they declined. op=open points the user at a setting that only they may change. Use it when a refusal ends with (fix: setting ...), or when a tool you need is not offered because a switch is off. Do not change a setting the task did not call for."
return "Find and change the user's own settings. op=list finds settings by words of their id (for example \"peers dispatch\"). op=propose asks the user to confirm one change: give the id, the new value and a short reason; nothing changes until the user answers, and the result says what changed or that they declined. op=open points the user at a setting that only they may change. Use it when a refusal ends with (fix: tool:setting ...), or when a tool you need is not offered because a switch is off. Do not change a setting the task did not call for."
}
func (*settingTool) Schema() json.RawMessage {
return json.RawMessage(`{"type":"object","properties":{"op":{"type":"string","enum":["list","propose","open"]},"query":{"type":"string","description":"Words of the setting's id, for op=list"},"id":{"type":"string","description":"The setting's id, for propose and open"},"value":{"description":"The new value as JSON, for op=propose"},"reason":{"type":"string","description":"Why the change helps the user, shown on the card, for op=propose"}},"required":["op"],"additionalProperties":false}`)