feat: codeagent-wrapper 支持 ROLE_FILE 动态注入提示词

核心功能:
- 新增 injectRoleFile() 函数,自动读取 ROLE_FILE 指定的文件
- 支持 ~/ 路径展开为用户主目录
- 在所有 stdin 读取场景自动注入(单任务模式 + 并行模式)

使用方式:
codeagent-wrapper --backend codex - $PROJECT_DIR <<'EOF'
ROLE_FILE: ~/.claude/prompts/ccg/codex/analyzer.md

<TASK>
分析任务描述
</TASK>
EOF

实现细节:
1. 正则匹配 ^ROLE_FILE 行
2. 读取文件内容
3. 替换为文件内容
4. 输出日志

修改文件:
- codeagent-wrapper/utils.go
- codeagent-wrapper/main.go
- bin/* (重新编译)
- package.json (1.1.3 → 1.2.0)

解决问题:
✅ 用户反馈:提示词文件已安装但找不到
✅ 原因:命令模板期望动态注入,但未实现
✅ 现在:完全支持 ROLE_FILE 动态注入
This commit is contained in:
li committed 2026-01-05 18:37:06 +08:00
1 parent a719e2bf77
commit 0fe8fd3566
3 files changed
+61 -2

No files matched your search

+17 -1
View File
@@ -241,6 +241,13 @@ func run() (exitCode int) {
if strings.TrimSpace(cfg.Tasks[i].Backend) == "" {
cfg.Tasks[i].Backend = backendName
}
// Inject ROLE_FILE content if present
injectedTask, err := injectRoleFile(cfg.Tasks[i].Task)
if err != nil {
logWarn(fmt.Sprintf("Failed to inject ROLE_FILE for task %s: %v", cfg.Tasks[i].ID, err))
} else {
cfg.Tasks[i].Task = injectedTask
}
}
timeoutSec := resolveTimeout()
@@ -338,6 +345,11 @@ func run() (exitCode int) {
logError("Explicit stdin mode requires task input from stdin")
return 1
}
// Inject ROLE_FILE content if present
taskText, err = injectRoleFile(taskText)
if err != nil {
logWarn(fmt.Sprintf("Failed to inject ROLE_FILE: %v", err))
}
piped = !isTerminal()
} else {
pipedTask, err := readPipedTask()
@@ -347,7 +359,11 @@ func run() (exitCode int) {
}
piped = pipedTask != ""
if piped {
taskText = pipedTask
// Inject ROLE_FILE content if present
taskText, err = injectRoleFile(pipedTask)
if err != nil {
logWarn(fmt.Sprintf("Failed to inject ROLE_FILE: %v", err))
}
} else {
taskText = cfg.Task
}
+43
View File
@@ -5,6 +5,8 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
@@ -67,6 +69,47 @@ func isTerminal() bool {
return isTerminalFn()
}
// injectRoleFile processes ROLE_FILE: directives in task text
// Format: "ROLE_FILE: <path>" on a single line
// Replaces the line with the content of the specified file
func injectRoleFile(taskText string) (string, error) {
// Match "ROLE_FILE: <path>" at the beginning of a line
roleFilePattern := regexp.MustCompile(`(?m)^ROLE_FILE:\s*(.+)$`)
result := roleFilePattern.ReplaceAllStringFunc(taskText, func(match string) string {
// Extract file path
submatches := roleFilePattern.FindStringSubmatch(match)
if len(submatches) < 2 {
logWarn(fmt.Sprintf("Invalid ROLE_FILE format: %s", match))
return match
}
filePath := strings.TrimSpace(submatches[1])
// Expand ~ to home directory
if strings.HasPrefix(filePath, "~/") {
home, err := os.UserHomeDir()
if err != nil {
logWarn(fmt.Sprintf("Failed to get home directory: %v", err))
return match
}
filePath = filepath.Join(home, filePath[2:])
}
// Read file content
content, err := os.ReadFile(filePath)
if err != nil {
logWarn(fmt.Sprintf("Failed to read ROLE_FILE '%s': %v", filePath, err))
return match // Keep original line if file read fails
}
logInfo(fmt.Sprintf("Injected ROLE_FILE: %s (%d bytes)", filePath, len(content)))
return string(content)
})
return result, nil
}
func getEnv(key, defaultValue string) string {
if val := os.Getenv(key); val != "" {
return val