magic-agent 2.4.3 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CLIENT-CONTRACT.md +467 -41
  2. package/README.md +15 -14
  3. package/client-ui/pc/app.css +74 -0
  4. package/client-ui/pc/js/04-task-list.js +5 -1
  5. package/client-ui/pc/js/07-chat-stream-render.js +3 -3
  6. package/client-ui/pc/js/90-glue.js +40 -2
  7. package/client-ui/pc/js/92-composer.js +19 -4
  8. package/client-ui/pc/js/93-rail.js +16 -5
  9. package/client-ui/shared/conv/02-media-kinds.js +2 -2
  10. package/client-ui/shared/conv/03-chat-message.js +247 -23
  11. package/client-ui/shared/conv/06-ask-answer.js +75 -1
  12. package/client-ui/shared/conv/09-stream-apply.js +72 -7
  13. package/client-ui/shared/conv/19-attachments.js +157 -6
  14. package/client-ui/shared/conv/27-conv-ledger.js +5 -2
  15. package/client-ui/shared/conv/29-msg-variants.js +217 -0
  16. package/client-ui/shared/conv/conv.css +329 -174
  17. package/client-ui/shared/conv/dom-map.md +1 -0
  18. package/client-ui/shared/conversation.css +13 -2
  19. package/client-ui/shared/conversation.js +373 -50
  20. package/client-ui/shared/transport-http.js +14 -0
  21. package/client-ui-mobile/apps.css +16 -0
  22. package/client-ui-mobile/apps.js +406 -0
  23. package/client-ui-mobile/index.html +5554 -639
  24. package/client-ui-mobile/reqs.css +110 -1
  25. package/client-ui-mobile/reqs.js +533 -74
  26. package/client-ui-mobile/reqshot.js +505 -0
  27. package/client-ui-mobile/scripts.css +67 -0
  28. package/client-ui-mobile/scripts.js +599 -0
  29. package/client-ui-mobile/vendor/cose-base.js +3214 -0
  30. package/client-ui-mobile/vendor/cytoscape-fcose.js +1549 -0
  31. package/client-ui-mobile/vendor/cytoscape.min.js +32 -0
  32. package/client-ui-mobile/vendor/layout-base.js +5230 -0
  33. package/cmd/magic-client/browsectl.go +199 -0
  34. package/cmd/magic-client/clientctl.go +69 -10
  35. package/cmd/magic-client/main.go +210 -32
  36. package/internal/agent/ask.go +45 -0
  37. package/internal/agent/ask_test.go +39 -0
  38. package/internal/agent/attachments_test.go +6 -2
  39. package/internal/agent/capability.go +7 -1
  40. package/internal/agent/claude.go +20 -15
  41. package/internal/agent/codebuddy.go +125 -34
  42. package/internal/agent/codebuddy_account_test.go +54 -0
  43. package/internal/agent/codebuddy_stream_test.go +103 -0
  44. package/internal/agent/credits.go +713 -0
  45. package/internal/agent/credits_minimax_test.go +97 -0
  46. package/internal/agent/credits_test.go +211 -0
  47. package/internal/agent/custom_models.go +155 -0
  48. package/internal/agent/custom_models_test.go +125 -0
  49. package/internal/agent/dsh.go +123 -49
  50. package/internal/agent/dsh_caps_cache_test.go +184 -0
  51. package/internal/agent/dsh_resume.go +157 -7
  52. package/internal/agent/dsh_test.go +108 -6
  53. package/internal/agent/engine.go +37 -1
  54. package/internal/agent/engine_base.go +21 -17
  55. package/internal/agent/engine_base_test.go +2 -2
  56. package/internal/agent/engine_test.go +47 -86
  57. package/internal/agent/env.go +173 -1
  58. package/internal/agent/env_test.go +114 -0
  59. package/internal/agent/fallback.go +313 -0
  60. package/internal/agent/fallback_test.go +340 -0
  61. package/internal/agent/launch.go +43 -1
  62. package/internal/agent/limitnotice.go +207 -1
  63. package/internal/agent/limitnotice_test.go +140 -0
  64. package/internal/agent/llmhttp.go +46 -4
  65. package/internal/agent/llmhttp_test.go +157 -0
  66. package/internal/agent/models.go +53 -1
  67. package/internal/agent/models_test.go +20 -5
  68. package/internal/agent/pidalive_windows_test.go +40 -0
  69. package/internal/agent/runcmd.go +26 -0
  70. package/internal/agent/runcmd_windows.go +36 -5
  71. package/internal/agent/stream.go +166 -6
  72. package/internal/agent/stream_test.go +25 -38
  73. package/internal/agent/stream_timeline_test.go +2 -37
  74. package/internal/agent/streamjson.go +119 -7
  75. package/internal/agent/tools_suffix_test.go +6 -3
  76. package/internal/agent/trae.go +50 -243
  77. package/internal/agent/trae_exec.go +433 -0
  78. package/internal/agent/trae_exec_test.go +181 -0
  79. package/internal/agent/wraplaunch_test.go +113 -0
  80. package/internal/cli/ask.go +168 -37
  81. package/internal/cli/ask_test.go +1 -1
  82. package/internal/cli/checkin.go +77 -0
  83. package/internal/cli/clientcmd.go +3 -2
  84. package/internal/cli/control.go +46 -5
  85. package/internal/cli/control_test.go +65 -0
  86. package/internal/cli/engines_test.go +103 -0
  87. package/internal/cli/events.go +15 -0
  88. package/internal/cli/events_test.go +50 -0
  89. package/internal/cli/judge.go +104 -0
  90. package/internal/cli/quotas.go +125 -0
  91. package/internal/cli/requirements.go +51 -0
  92. package/internal/cli/root.go +157 -11
  93. package/internal/cli/session.go +75 -3
  94. package/internal/cli/session_test.go +82 -3
  95. package/internal/cli/sessionlog.go +6 -0
  96. package/internal/cli/sessionlog_test.go +22 -2
  97. package/internal/cli/suggest.go +7 -5
  98. package/internal/cli/team.go +37 -9
  99. package/internal/cli/team_test.go +39 -0
  100. package/internal/client/ab.go +372 -0
  101. package/internal/client/ab_test.go +287 -0
  102. package/internal/client/apk.go +8 -0
  103. package/internal/client/apk_test.go +59 -0
  104. package/internal/client/apps.go +1078 -0
  105. package/internal/client/apps_test.go +516 -0
  106. package/internal/client/browse.go +84 -36
  107. package/internal/client/browse_test.go +107 -9
  108. package/internal/client/browseinput.go +481 -0
  109. package/internal/client/browseinput_test.go +216 -0
  110. package/internal/client/corecmd.go +23 -0
  111. package/internal/client/corecmd_other.go +8 -0
  112. package/internal/client/corecmd_windows.go +24 -0
  113. package/internal/client/coreexe.go +196 -0
  114. package/internal/client/coreexe_test.go +161 -0
  115. package/internal/client/coreupgrade.go +866 -0
  116. package/internal/client/coreupgrade_test.go +561 -0
  117. package/internal/client/daemon.go +297 -33
  118. package/internal/client/daemon_test.go +33 -0
  119. package/internal/client/engines_checkin_test.go +57 -0
  120. package/internal/client/fallback_flags_test.go +64 -0
  121. package/internal/client/feishu.go +1259 -0
  122. package/internal/client/feishu_test.go +649 -0
  123. package/internal/client/health_identity_test.go +84 -0
  124. package/internal/client/hostenv.go +68 -0
  125. package/internal/client/hostenv_test.go +37 -0
  126. package/internal/client/jobs.go +518 -24
  127. package/internal/client/jobs_auto.go +571 -0
  128. package/internal/client/jobs_auto_test.go +431 -0
  129. package/internal/client/jobs_test.go +299 -2
  130. package/internal/client/knowledge.go +854 -0
  131. package/internal/client/knowledge_test.go +510 -0
  132. package/internal/client/lan.go +3 -3
  133. package/internal/client/plugins.go +5 -1
  134. package/internal/client/prefs.go +67 -0
  135. package/internal/client/procstart_linux.go +59 -0
  136. package/internal/client/publicurl.go +82 -0
  137. package/internal/client/quotas_merge_test.go +107 -0
  138. package/internal/client/requirements.go +207 -2
  139. package/internal/client/requirements_shots_test.go +200 -0
  140. package/internal/client/requirements_test.go +103 -0
  141. package/internal/client/restart.go +288 -0
  142. package/internal/client/restart_handoff_test.go +230 -0
  143. package/internal/client/restart_test.go +290 -0
  144. package/internal/client/scripts.go +1047 -0
  145. package/internal/client/scripts_decode_implausible.go +38 -0
  146. package/internal/client/scripts_decode_other.go +31 -0
  147. package/internal/client/scripts_decode_test.go +130 -0
  148. package/internal/client/scripts_decode_windows.go +182 -0
  149. package/internal/client/scripts_test.go +739 -0
  150. package/internal/client/service.go +717 -97
  151. package/internal/client/service_test.go +217 -1
  152. package/internal/client/sessions_cache.go +148 -0
  153. package/internal/client/sessions_cache_test.go +163 -0
  154. package/internal/client/sessions_fold.go +165 -0
  155. package/internal/client/sessions_fold_test.go +204 -0
  156. package/internal/client/singleinstance.go +470 -0
  157. package/internal/client/singleinstance_test.go +607 -0
  158. package/internal/client/suggest.go +105 -2
  159. package/internal/client/suggest_test.go +86 -0
  160. package/internal/client/team_flags.go +25 -11
  161. package/internal/client/team_flags_test.go +39 -0
  162. package/internal/client/testmain_test.go +88 -0
  163. package/internal/client/workspace_default_test.go +158 -0
  164. package/internal/client/workspacelockenv_test.go +54 -0
  165. package/internal/config/config.go +48 -0
  166. package/internal/judge/judge.go +315 -0
  167. package/internal/requirements/bootstrap.go +16 -0
  168. package/internal/requirements/consolidate.go +100 -9
  169. package/internal/requirements/doc.go +154 -3
  170. package/internal/requirements/graph.go +138 -0
  171. package/internal/requirements/memory.go +7 -0
  172. package/internal/requirements/modules.go +312 -0
  173. package/internal/requirements/modules_test.go +373 -0
  174. package/internal/requirements/relations.go +155 -0
  175. package/internal/requirements/relations_test.go +231 -0
  176. package/internal/requirements/requirements.go +10 -0
  177. package/internal/requirements/shots.go +418 -0
  178. package/internal/requirements/shots_test.go +387 -0
  179. package/internal/session/multiclient_test.go +368 -0
  180. package/internal/session/session.go +175 -11
  181. package/internal/session/session_test.go +7 -0
  182. package/internal/session/status.go +21 -0
  183. package/internal/session/status_test.go +28 -0
  184. package/internal/session/title.go +16 -0
  185. package/internal/session/title_test.go +26 -0
  186. package/internal/session/workspace_lock.go +265 -28
  187. package/internal/suggest/suggest.go +23 -1
  188. package/internal/team/event.go +3 -1
  189. package/internal/team/execute.go +21 -6
  190. package/internal/team/fallback_route_test.go +80 -0
  191. package/internal/team/planner.go +15 -3
  192. package/internal/team/route.go +89 -17
  193. package/internal/team/team.go +18 -4
  194. package/npm/apk/magic-agent-mobile-release.apk +0 -0
  195. package/npm/apk/version.json +6 -3
  196. package/npm/dist/darwin-arm64/magic-agent +0 -0
  197. package/npm/dist/darwin-arm64/magic-client +0 -0
  198. package/npm/dist/darwin-x64/magic-agent +0 -0
  199. package/npm/dist/darwin-x64/magic-client +0 -0
  200. package/npm/dist/linux-arm64/magic-agent +0 -0
  201. package/npm/dist/linux-arm64/magic-client +0 -0
  202. package/npm/dist/linux-x64/magic-agent +0 -0
  203. package/npm/dist/linux-x64/magic-client +0 -0
  204. package/npm/dist/win32-x64/magic-agent.exe +0 -0
  205. package/npm/dist/win32-x64/magic-client.exe +0 -0
  206. package/npm/install.js +11 -0
  207. package/package.json +14 -3
@@ -1,65 +1,58 @@
1
1
  package agent
2
2
 
3
- // trae.go - Trae CLI 引擎。
3
+ // trae.go - TRAE CLI 引擎(2.0,命令名 traex)。
4
+ //
5
+ // 后端:TRAE CLI 2.0(0.2xx,Rust 版),非交互入口为
6
+ // `traex exec --json`,输出 Codex 同源的 JSONL 事件流。
7
+ // 参数构造与事件解析见 trae_exec.go。
4
8
  //
5
- // 后端:trae-cli(~/.local/bin/trae-cli)。非交互模式:-p,纯文本输出。
6
9
  // 与 claude/codebuddy 的关键差异:
7
10
  //
8
- // - 没有 --model flag;模型默认取 trae 自身配置(~/.trae/trae_cli.yaml
9
- // 的 model.name)。显式指定模型用 -c "model.name=<name>" 覆盖。
10
- // - --output-format 有 json,但实测纯文本已足够稳定,这里用 text +
11
- // noToolSuffix(拼进 system 部分)压制 agent loop。
12
- // - 自带 --query-timeout,把外层超时同时传给 CLI,保证语义一致。
13
- // - 会话续接:--resume=<id>(pflag 可选值风格,必须等号传值);
14
- // 没有 --continue,续接最近一次会话用裸 --resume(AUTO 模式,
15
- // 实测 -p 非交互模式下可用,能接住上一轮记忆)。
16
- // - 工具:禁用靠 --disallowed-tool 逐个减法;启用靠全放行开关 -y。
17
- // trae 没有「只允许白名单这几个工具」的表达能力,白名单模式因此
18
- // 降级为「默认开启所有工具」(详见 buildArgs 的工具模式映射)。
19
- // - MaxTokens / Temperature 不支持:`-c <k>=<v>` 会整体覆盖模型配置块
20
- // (实测注入 model.max_tokens 后模型名漂移并触发配额错误),因此这两个
21
- // 参数构造时静默忽略(见 engine_base.go 矩阵②)。
11
+ // - 没有独立 system prompt 通道;system / 多轮消息由 FlattenPrompt
12
+ // 拼进提示词(off 模式再追加 noToolSuffix)。
13
+ // - 模型用 -m/--model(不是 1.x 的 -c model.name=);默认取
14
+ // ~/.trae/traecli.toml 的 model。
15
+ // - 续接:`exec resume <id>` / `exec resume --last`(没有 --continue)。
16
+ // - 工具:off 靠 --disallowed-tool 逐个减法;on / 白名单用 -y 全放行
17
+ // (trae 没有「只允许白名单」的表达能力)。
18
+ // - 没有 --query-timeout:超时完全由外层 runner 杀进程组。
19
+ // - MaxTokens / Temperature 不支持,静默忽略(见 engine_base.go 矩阵②)。
20
+ // - 附件没有原生图片通道(exec 的 -i 只收图片且与 1.x 行为不一致),
21
+ // 统一把绝对路径写进提示词,靠 CLI 的读文件工具。
22
22
  //
23
- // CLI 路径解析顺序:显式 BinPath → MAGIC_AGENT_TRAE_BIN → ~/.local/bin →
24
- // ~/bin → PATH(探测链统一收敛在 engine_base.go 的 cliBase)。
23
+ // CLI 路径解析顺序:显式 BinPath → MAGIC_AGENT_TRAE_BIN → 安装器默认位 →
24
+ // PATH(探测链统一收敛在 engine_base.go 的 cliBase)。
25
25
 
26
26
  import (
27
27
  "context"
28
- "encoding/json"
29
28
  "fmt"
30
29
  "os"
31
30
  "path/filepath"
32
- "strings"
33
31
  "time"
34
32
  )
35
33
 
36
- // TraeEngine 通过 trae-cli 实现 Engine。
34
+ // TraeEngine 通过 traex 实现 Engine。
37
35
  type TraeEngine struct {
38
36
  // BinPath 显式指定 CLI 路径(空 = 自动探测)。测试注入用。
39
37
  BinPath string
40
38
 
41
- // Model 显式模型(非空时 -c model.name=<model> 覆盖 CLI 默认)。
39
+ // Model 显式模型(非空时 -m <model>)。
42
40
  Model string
43
41
  }
44
42
 
45
43
  // Name 实现 Engine。
46
44
  func (e *TraeEngine) Name() string { return "trae" }
47
45
 
48
- // DefaultTraeTimeout 单次尝试默认超时(2026-10-10 由 10 分钟统一上调到 1 小时)。
46
+ // DefaultTraeTimeout 单次尝试默认超时(1 小时,与其余 CLI 引擎对齐)。
49
47
  const DefaultTraeTimeout = time.Hour
50
48
 
51
49
  // traeOffDisableTools off 模式下逐个 --disallowed-tool 禁用的实体工具。
52
50
  //
53
- // 取自 trae-cli 实测的 18 个内置工具里「会读文件 / 会改机器」的那批:
54
- // Agent、ExitPlanMode、EnterPlanMode 是常驻元工具(实测 --disallowed-tool
55
- // 摘不掉,补集禁用后仍保留),Skill、TaskCreate/Get/List/Update、
56
- // BashOutput、KillShell 属会话内辅助、无外部副作用,故不在禁用之列。
57
- //
58
- // 注意工具名必须是真实存在的:编辑与写入在 trae 里叫 Edit / Write ——
59
- // 此前这里写的是并不存在的 `Replace`,等于 off 模式漏禁 Write。
51
+ // 取「会读文件 / 会改机器」的那批;Skill / 任务管理等会话内辅助工具
52
+ // 无外部副作用,不在禁用之列。
60
53
  var traeOffDisableTools = []string{"Bash", "Edit", "Write", "Glob", "Grep", "Read"}
61
54
 
62
- // bin 探测 trae-cli 路径(委托 cliBase 统一探测链)。
55
+ // bin 探测 traex 路径(委托 cliBase 统一探测链)。
63
56
  func (e *TraeEngine) bin() string {
64
57
  return traeBase.resolve(e.BinPath)
65
58
  }
@@ -73,42 +66,18 @@ func (e *TraeEngine) Detect() (bool, string) {
73
66
  return true, p
74
67
  }
75
68
 
76
- // TraeDefaultModel 读取 ~/.trae/trae_cli.yaml 顶层 model.name,
77
- // 返回 trae CLI 当前配置的默认模型(如 "My-MiniMax-M3")。
78
- // 轻量扫描解析(不引入 yaml 依赖);文件缺失/格式变化返回空串。
69
+ // TraeDefaultModel 返回 trae CLI 当前配置的默认模型(如 "MiniMax-M3.1-Flash")。
70
+ // 2.0 读 ~/.trae/traecli.toml 的顶层 `model = "..."`;文件缺失/格式变化返回空串。
79
71
  func TraeDefaultModel() string {
80
72
  home, err := os.UserHomeDir()
81
73
  if err != nil {
82
74
  return ""
83
75
  }
84
- data, err := os.ReadFile(filepath.Join(home, ".trae", "trae_cli.yaml"))
85
- if err != nil {
86
- return ""
87
- }
88
- inModelBlock := false
89
- for _, ln := range strings.Split(string(data), "\n") {
90
- trimmed := strings.TrimSpace(ln)
91
- if !inModelBlock {
92
- inModelBlock = ln == trimmed && strings.HasPrefix(trimmed, "model:")
93
- continue
94
- }
95
- if strings.HasPrefix(trimmed, "name:") {
96
- v := strings.TrimSpace(strings.TrimPrefix(trimmed, "name:"))
97
- v = strings.Trim(v, `"'`)
98
- if v != "" {
99
- return v
100
- }
101
- }
102
- if ln == trimmed && trimmed != "" {
103
- inModelBlock = false
104
- }
105
- }
106
- return ""
76
+ return traeDefaultModelFromTOML(filepath.Join(home, ".trae", "traecli.toml"))
107
77
  }
108
78
 
109
- // ListModels 实现 ModelLister:`trae-cli models --json` 是 trae 自己的
110
- // 权威清单(含用户自定义模型,如 ~/.trae/trae_cli.yaml 里的 My-MiniMax-M3)。
111
- // 取 name(= -c model.name=<name> 接受的那个标识),缺失时退回 real_name。
79
+ // ListModels 实现 ModelLister:`traex models --json` 是 CLI 自己的权威清单。
80
+ // 取 name(= -m 接受的标识),缺失时退回 real_name。
112
81
  func (e *TraeEngine) ListModels(ctx context.Context) ([]string, error) {
113
82
  stdout, err := probeCLI(ctx, "trae", e.bin(), "models", "--json")
114
83
  if err != nil {
@@ -120,48 +89,26 @@ func (e *TraeEngine) ListModels(ctx context.Context) ([]string, error) {
120
89
  }
121
90
  models := modelNamesFromObjects(objs, "name", "real_name")
122
91
  if len(models) == 0 {
123
- return nil, fmt.Errorf("%w: trae-cli models --json returned no entries", ErrNoModelSource)
92
+ return nil, fmt.Errorf("%w: traecli models --json returned no entries", ErrNoModelSource)
124
93
  }
125
94
  return models, nil
126
95
  }
127
96
 
128
- // Complete 实现 Engine:单次调用 trae-cli。
97
+ // Complete 实现 Engine:单次 `exec --json`,把 JSONL 聚合成最终正文。
129
98
  func (e *TraeEngine) Complete(ctx context.Context, req Request) (Response, error) {
130
99
  start := time.Now()
131
100
  bin := e.bin()
132
101
  if bin == "" {
133
102
  return Response{}, fmt.Errorf("trae CLI not found; set MAGIC_AGENT_TRAE_BIN")
134
103
  }
104
+ return e.completeExec(ctx, bin, req, start)
105
+ }
135
106
 
136
- prompt := FlattenPrompt(req.SystemPrompt, req.Messages, toolsIsOff(req))
137
- if prompt == "" {
138
- return Response{}, fmt.Errorf("trae: empty prompt")
139
- }
140
- // trae 没有独立 system 注入 flag;无 system prompt 时约束也要生效。
141
- // 仅 off 模式追加:on/白名单下 noToolSuffix 会与「允许调用工具」冲突。
142
- if req.SystemPrompt == "" && toolsIsOff(req) {
143
- prompt += noToolSuffix
144
- }
145
- // 附件:trae-cli 没有图片/附件输入通道(--help 无 image/attach/input-format),
146
- // 只能把绝对路径写进提示词,靠 trae 的读文件工具看 —— 因此需要工具可用。
147
- prompt = appendAttachmentSection(prompt, req.Attachments)
148
-
149
- args := e.buildArgs(req, prompt)
150
-
151
- // workspace:trae 无工作目录 flag(有 --add-dir 但那是追加额外目录),子进程 cwd 即原生方式
152
- stdout, stderr, err := runCLIIn(ctx, req.Workspace, bin, args...)
153
- if err != nil {
154
- return Response{}, wrapCliError("trae", stdout, stderr, err)
155
- }
156
-
157
- text := strings.TrimSpace(stdout)
158
- if text == "" {
159
- return Response{}, fmt.Errorf("trae CLI returned empty output")
160
- }
161
-
107
+ // modelLabel 本轮展示用模型名:请求显式 > 引擎注入 > CLI 配置默认。
108
+ func (e *TraeEngine) modelLabel(req Request) string {
162
109
  modelLabel := stripModelPrefix(req.Model)
163
110
  if modelLabel == "" {
164
- modelLabel = e.Model
111
+ modelLabel = stripModelPrefix(e.Model)
165
112
  }
166
113
  if modelLabel == "" {
167
114
  if m := TraeDefaultModel(); m != "" {
@@ -170,175 +117,35 @@ func (e *TraeEngine) Complete(ctx context.Context, req Request) (Response, error
170
117
  modelLabel = "cli-default"
171
118
  }
172
119
  }
173
- return Response{
174
- Text: text,
175
- Model: modelLabel,
176
- // 纯文本模式下 CLI 不回 session_id;续接请求时把传入的 id 原样
177
- // 回传,让调用方可以逐轮透传(新会话仍为空)。
178
- SessionID: req.SessionID,
179
- Latency: time.Since(start),
180
- }, nil
181
- }
182
-
183
- // buildArgs 构造 trae-cli 参数(Complete 与 Stream 共用)。
184
- func (e *TraeEngine) buildArgs(req Request, prompt string) []string {
185
- args := []string{"-p"}
186
- switch {
187
- case req.SessionID != "":
188
- // 续接指定会话:pflag 可选值风格(--resume string[="AUTO"]),
189
- // 必须用等号形式 --resume=<id>;空格形式下裸 id 会被吞成 AUTO 默认值。
190
- args = append(args, "--resume="+req.SessionID)
191
- case req.Continue:
192
- // 续接最近一次会话:裸 --resume 触发 AUTO 模式(实测 -p 可用)。
193
- args = append(args, "--resume")
194
- }
195
- // 模型:显式值 > 引擎注入值;都为空则用 CLI 自身默认。
196
- model := e.Model
197
- if model == "" {
198
- model = stripModelPrefix(req.Model)
199
- }
200
- if model != "" {
201
- args = append(args, "-c", "model.name="+model)
202
- }
203
- // 统一参数矩阵:req.MaxTokens / req.Temperature 在 trae 上明确不支持、
204
- // 静默忽略 —— `-c <k>=<v>` 会整体覆盖模型配置块,实测注入
205
- // model.max_tokens 后模型名漂移并触发配额错误(见 engine_base.go 矩阵②)。
206
- _ = req.MaxTokens
207
- _ = req.Temperature
208
- // 工具模式映射。trae-cli 的权限开关实测语义(2026-09-16 真机验证):
209
- //
210
- // --allowed-tool 只做「自动批准该工具」,**不裁剪**工具集
211
- // (实测 --allowed-tool WebFetch 仍下发全部 18 个工具)
212
- // --disallowed-tool 真正做减法,从工具集里移除该工具(实测 18 → 4)
213
- // -y / --yolo 跳过全部权限检查 ⇒ 全工具可用
214
- //
215
- // 由此得出映射:
216
- //
217
- // off 逐个 --disallowed-tool 关掉内置工具(Bash/Edit/Write/Glob/Grep/Read)
218
- // on -y(全放行;非 -p 交互场景下的权限旁路)
219
- // allowlist 默认开启所有工具 ⇒ 与 on 同一条全放行路径(-y)
220
- //
221
- // ⚠️ 白名单在 trae 上「降级为全放行」是刻意的默认语义,不是实现漏做:
222
- // trae 无法表达「只允许这几个工具」——想收窄只能对补集逐个 --disallowed-tool,
223
- // 而补集随 CLI 版本漂移(今天 18 个工具、明天可能加),漏一条就等于白名单失效,
224
- // 维护成本高于收益。故认定「默认开启所有工具」为白名单模式在 trae 上的契约,
225
- // 与 codex 的白名单降级(engine_base.go 矩阵④:白名单 → 全放行)保持一致。
226
- //
227
- // 返回值里不再出现 --allowed-tool:既然 -y 已全量放行,逐条声明只是
228
- // 「看起来像收窄」的噪音(这正是此前 allowlist 被 -y 架空的观感来源)。
229
- // 需要收窄时请用 off 模式(真减法),而不要指望白名单。
230
- tools := toolsOrDefault(req.Tools)
231
- switch {
232
- case tools.IsOff():
233
- // 真实工具名,逐个减法;`Replace` 不是 trae 的工具(编辑/写入是
234
- // Edit / Write),此前写成 Replace 等于漏禁 Write。
235
- for _, t := range traeOffDisableTools {
236
- args = append(args, "--disallowed-tool", t)
237
- }
238
- case tools.IsOn():
239
- args = append(args, "-y")
240
- default:
241
- // 默认开启所有工具:全放行开关是唯一有效机制。
242
- args = append(args, "-y")
243
- }
244
- // 把外层超时透传给 CLI 的 query-timeout(取上限 600s,CLI 单查询上限)。
245
- if req.Timeout > 0 {
246
- qt := req.Timeout
247
- if qt > 600*time.Second {
248
- qt = 600 * time.Second
249
- }
250
- args = append(args, "--query-timeout", qt.String())
251
- } else {
252
- args = append(args, "--query-timeout", "600s")
253
- }
254
- args = append(args, prompt)
255
- return args
120
+ return modelLabel
256
121
  }
257
122
 
258
- // Stream 实现 Streamer:流式调用 trae-cli。
259
- // trae 的 stream-json 用 delta.content 直出正文增量;
260
- // 模型侧不开 reasoning,无 thinking 通道(Thinking 恒为空)。
123
+ // Stream 实现 Streamer:逐行消费 `exec --json`,正文 / 思考 / 工具实时转发。
261
124
  func (e *TraeEngine) Stream(ctx context.Context, req Request, onEvent func(StreamEvent)) (StreamResult, error) {
262
125
  start := time.Now()
263
126
  bin := e.bin()
264
127
  if bin == "" {
265
128
  return StreamResult{}, fmt.Errorf("trae CLI not found; set MAGIC_AGENT_TRAE_BIN")
266
129
  }
130
+ return e.streamExec(ctx, bin, req, start, onEvent)
131
+ }
267
132
 
133
+ // traePrompt 组装 2.0 的单条提示词(system + 多轮 + off 约束 + 附件路径)。
134
+ func traePrompt(req Request) (string, error) {
268
135
  prompt := FlattenPrompt(req.SystemPrompt, req.Messages, toolsIsOff(req))
269
136
  if prompt == "" {
270
- return StreamResult{}, fmt.Errorf("trae: empty prompt")
137
+ return "", fmt.Errorf("trae: empty prompt")
271
138
  }
272
139
  if req.SystemPrompt == "" && toolsIsOff(req) {
273
140
  prompt += noToolSuffix
274
141
  }
275
- prompt = appendAttachmentSection(prompt, req.Attachments)
276
-
277
- args := e.buildArgs(req, prompt)
278
- args = append(args, "--output-format", "stream-json", "--include-partial-messages")
279
-
280
- acc := &streamAccumulator{Engine: e.Name(), OnEvent: onEvent, OnSessionID: req.OnSessionID}
281
- var fin struct {
282
- Type string `json:"type"`
283
- Subtype string `json:"subtype"`
284
- IsError bool `json:"is_error"`
285
- Result string `json:"result"`
286
- SessionID string `json:"session_id"`
287
- }
288
- seenResult := false
289
-
290
- err := runStreamCLIIn(ctx, req.Workspace, bin, args, func(line string) error {
291
- isResult, perr := acc.handleNDJSONLine(line)
292
- if perr != nil {
293
- return perr
294
- }
295
- if isResult {
296
- _ = json.Unmarshal([]byte(line), &fin)
297
- seenResult = true
298
- }
299
- return nil
300
- })
301
- if err != nil {
302
- return StreamResult{}, err
303
- }
304
- if !seenResult {
305
- err := fmt.Errorf("trae CLI stream ended without result line")
306
- acc.emitTurnFailed(acc.SessionID, err)
307
- return StreamResult{}, err
308
- }
309
- if fin.IsError {
310
- return StreamResult{}, fmt.Errorf("trae CLI error (subtype=%s): %s", fin.Subtype, truncateStr(fin.Result, 500))
311
- }
312
-
313
- text := fin.Result
314
- if text == "" {
315
- text = strings.TrimSpace(acc.Text.String())
316
- }
317
- if text == "" {
318
- return StreamResult{}, fmt.Errorf("trae CLI returned empty result")
319
- }
142
+ return appendAttachmentSection(prompt, req.Attachments), nil
143
+ }
320
144
 
321
- modelLabel := stripModelPrefix(req.Model)
322
- if modelLabel == "" {
323
- modelLabel = e.Model
324
- }
325
- if modelLabel == "" {
326
- if m := TraeDefaultModel(); m != "" {
327
- modelLabel = m
328
- } else {
329
- modelLabel = "cli-default"
330
- }
145
+ // traeModelFor 模型取值:请求显式 > 引擎注入(都去掉 `trae/` 前缀)。
146
+ func traeModelFor(req Request, e *TraeEngine) string {
147
+ if m := stripModelPrefix(req.Model); m != "" {
148
+ return m
331
149
  }
332
- return StreamResult{
333
- Response: Response{
334
- Engine: e.Name(),
335
- Text: text,
336
- Model: modelLabel,
337
- SessionID: fin.SessionID,
338
- Latency: time.Since(start),
339
- },
340
- Thinking: acc.Thinking.String(),
341
- // trae 协议不暴露独立工具事件通道;Tools 恒为 nil。
342
- // 见 stream.go 顶部协议说明。
343
- }, nil
150
+ return stripModelPrefix(e.Model)
344
151
  }