feat: add workspace upgrade migration for AGENT.md media section
Existing deployments have an AGENT.md without media sending instructions, causing the LLM to tell users it cannot send files. This adds an idempotent upgrade that appends the media section on startup if missing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f30893fbb7
commit
61c917d96f
3 changed files with 127 additions and 0 deletions
|
|
@ -405,6 +405,9 @@ func agentCmd() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply workspace upgrades before starting the agent
|
||||||
|
migrate.UpgradeWorkspace(cfg.WorkspacePath())
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
|
||||||
|
|
@ -540,6 +543,9 @@ func gatewayCmd() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply workspace upgrades before starting the agent
|
||||||
|
migrate.UpgradeWorkspace(cfg.WorkspacePath())
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
msgBus := bus.NewMessageBus()
|
||||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
|
||||||
|
|
|
||||||
51
pkg/migrate/upgrade.go
Normal file
51
pkg/migrate/upgrade.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package migrate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpgradeWorkspace applies incremental upgrades to an existing workspace.
|
||||||
|
// Each upgrade is idempotent — it checks whether it has already been applied
|
||||||
|
// before making changes.
|
||||||
|
func UpgradeWorkspace(workspace string) {
|
||||||
|
upgradeAgentMediaSection(workspace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// upgradeAgentMediaSection ensures AGENT.md contains the media sending instructions.
|
||||||
|
// Added in v0.x to teach the LLM it can send files via the message tool.
|
||||||
|
func upgradeAgentMediaSection(workspace string) {
|
||||||
|
agentPath := filepath.Join(workspace, "AGENT.md")
|
||||||
|
|
||||||
|
data, err := os.ReadFile(agentPath)
|
||||||
|
if err != nil {
|
||||||
|
return // File doesn't exist or unreadable — skip
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(data)
|
||||||
|
|
||||||
|
// Already applied
|
||||||
|
if strings.Contains(content, "## Media & File Sending") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
section := `
|
||||||
|
|
||||||
|
## Media & File Sending
|
||||||
|
|
||||||
|
You CAN send files directly to users. When you need to share a file (image, document, audio, video), use the ` + "`message`" + ` tool with the ` + "`media`" + ` parameter containing the local file path(s). The file will be delivered natively through the user's channel (Telegram, Discord, Slack, etc.). Do NOT tell users you cannot send files — just send them.`
|
||||||
|
|
||||||
|
content += section
|
||||||
|
|
||||||
|
if err := os.WriteFile(agentPath, []byte(content), 0644); err != nil {
|
||||||
|
logger.ErrorCF("migrate", "Failed to upgrade AGENT.md", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.InfoC("migrate", "Upgraded AGENT.md with media sending instructions")
|
||||||
|
}
|
||||||
70
pkg/migrate/upgrade_test.go
Normal file
70
pkg/migrate/upgrade_test.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
package migrate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpgradeAgentMediaSection(t *testing.T) {
|
||||||
|
t.Run("appends media section to existing AGENT.md", func(t *testing.T) {
|
||||||
|
workspace := t.TempDir()
|
||||||
|
agentPath := filepath.Join(workspace, "AGENT.md")
|
||||||
|
os.WriteFile(agentPath, []byte("# Agent Instructions\n\nBe helpful."), 0644)
|
||||||
|
|
||||||
|
UpgradeWorkspace(workspace)
|
||||||
|
|
||||||
|
data, err := os.ReadFile(agentPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reading AGENT.md: %v", err)
|
||||||
|
}
|
||||||
|
content := string(data)
|
||||||
|
|
||||||
|
if !strings.Contains(content, "## Media & File Sending") {
|
||||||
|
t.Error("expected media section to be appended")
|
||||||
|
}
|
||||||
|
if !strings.Contains(content, "You CAN send files directly") {
|
||||||
|
t.Error("expected media instructions in content")
|
||||||
|
}
|
||||||
|
// Original content preserved
|
||||||
|
if !strings.Contains(content, "# Agent Instructions") {
|
||||||
|
t.Error("original content should be preserved")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("idempotent — does not duplicate section", func(t *testing.T) {
|
||||||
|
workspace := t.TempDir()
|
||||||
|
agentPath := filepath.Join(workspace, "AGENT.md")
|
||||||
|
os.WriteFile(agentPath, []byte("# Agent Instructions\n\nBe helpful."), 0644)
|
||||||
|
|
||||||
|
UpgradeWorkspace(workspace)
|
||||||
|
UpgradeWorkspace(workspace)
|
||||||
|
|
||||||
|
data, _ := os.ReadFile(agentPath)
|
||||||
|
count := strings.Count(string(data), "## Media & File Sending")
|
||||||
|
if count != 1 {
|
||||||
|
t.Errorf("expected exactly 1 media section, got %d", count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("skips when AGENT.md does not exist", func(t *testing.T) {
|
||||||
|
workspace := t.TempDir()
|
||||||
|
// No AGENT.md created — should not panic or error
|
||||||
|
UpgradeWorkspace(workspace)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("skips when section already present", func(t *testing.T) {
|
||||||
|
workspace := t.TempDir()
|
||||||
|
agentPath := filepath.Join(workspace, "AGENT.md")
|
||||||
|
original := "# Agent\n\n## Media & File Sending\n\nAlready here."
|
||||||
|
os.WriteFile(agentPath, []byte(original), 0644)
|
||||||
|
|
||||||
|
UpgradeWorkspace(workspace)
|
||||||
|
|
||||||
|
data, _ := os.ReadFile(agentPath)
|
||||||
|
if string(data) != original {
|
||||||
|
t.Error("file should not be modified when section already exists")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue