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.
- package/CLIENT-CONTRACT.md +467 -41
- package/README.md +15 -14
- package/client-ui/pc/app.css +74 -0
- package/client-ui/pc/js/04-task-list.js +5 -1
- package/client-ui/pc/js/07-chat-stream-render.js +3 -3
- package/client-ui/pc/js/90-glue.js +40 -2
- package/client-ui/pc/js/92-composer.js +19 -4
- package/client-ui/pc/js/93-rail.js +16 -5
- package/client-ui/shared/conv/02-media-kinds.js +2 -2
- package/client-ui/shared/conv/03-chat-message.js +247 -23
- package/client-ui/shared/conv/06-ask-answer.js +75 -1
- package/client-ui/shared/conv/09-stream-apply.js +72 -7
- package/client-ui/shared/conv/19-attachments.js +157 -6
- package/client-ui/shared/conv/27-conv-ledger.js +5 -2
- package/client-ui/shared/conv/29-msg-variants.js +217 -0
- package/client-ui/shared/conv/conv.css +329 -174
- package/client-ui/shared/conv/dom-map.md +1 -0
- package/client-ui/shared/conversation.css +13 -2
- package/client-ui/shared/conversation.js +373 -50
- package/client-ui/shared/transport-http.js +14 -0
- package/client-ui-mobile/apps.css +16 -0
- package/client-ui-mobile/apps.js +406 -0
- package/client-ui-mobile/index.html +5554 -639
- package/client-ui-mobile/reqs.css +110 -1
- package/client-ui-mobile/reqs.js +533 -74
- package/client-ui-mobile/reqshot.js +505 -0
- package/client-ui-mobile/scripts.css +67 -0
- package/client-ui-mobile/scripts.js +599 -0
- package/client-ui-mobile/vendor/cose-base.js +3214 -0
- package/client-ui-mobile/vendor/cytoscape-fcose.js +1549 -0
- package/client-ui-mobile/vendor/cytoscape.min.js +32 -0
- package/client-ui-mobile/vendor/layout-base.js +5230 -0
- package/cmd/magic-client/browsectl.go +199 -0
- package/cmd/magic-client/clientctl.go +69 -10
- package/cmd/magic-client/main.go +210 -32
- package/internal/agent/ask.go +45 -0
- package/internal/agent/ask_test.go +39 -0
- package/internal/agent/attachments_test.go +6 -2
- package/internal/agent/capability.go +7 -1
- package/internal/agent/claude.go +20 -15
- package/internal/agent/codebuddy.go +125 -34
- package/internal/agent/codebuddy_account_test.go +54 -0
- package/internal/agent/codebuddy_stream_test.go +103 -0
- package/internal/agent/credits.go +713 -0
- package/internal/agent/credits_minimax_test.go +97 -0
- package/internal/agent/credits_test.go +211 -0
- package/internal/agent/custom_models.go +155 -0
- package/internal/agent/custom_models_test.go +125 -0
- package/internal/agent/dsh.go +123 -49
- package/internal/agent/dsh_caps_cache_test.go +184 -0
- package/internal/agent/dsh_resume.go +157 -7
- package/internal/agent/dsh_test.go +108 -6
- package/internal/agent/engine.go +37 -1
- package/internal/agent/engine_base.go +21 -17
- package/internal/agent/engine_base_test.go +2 -2
- package/internal/agent/engine_test.go +47 -86
- package/internal/agent/env.go +173 -1
- package/internal/agent/env_test.go +114 -0
- package/internal/agent/fallback.go +313 -0
- package/internal/agent/fallback_test.go +340 -0
- package/internal/agent/launch.go +43 -1
- package/internal/agent/limitnotice.go +207 -1
- package/internal/agent/limitnotice_test.go +140 -0
- package/internal/agent/llmhttp.go +46 -4
- package/internal/agent/llmhttp_test.go +157 -0
- package/internal/agent/models.go +53 -1
- package/internal/agent/models_test.go +20 -5
- package/internal/agent/pidalive_windows_test.go +40 -0
- package/internal/agent/runcmd.go +26 -0
- package/internal/agent/runcmd_windows.go +36 -5
- package/internal/agent/stream.go +166 -6
- package/internal/agent/stream_test.go +25 -38
- package/internal/agent/stream_timeline_test.go +2 -37
- package/internal/agent/streamjson.go +119 -7
- package/internal/agent/tools_suffix_test.go +6 -3
- package/internal/agent/trae.go +50 -243
- package/internal/agent/trae_exec.go +433 -0
- package/internal/agent/trae_exec_test.go +181 -0
- package/internal/agent/wraplaunch_test.go +113 -0
- package/internal/cli/ask.go +168 -37
- package/internal/cli/ask_test.go +1 -1
- package/internal/cli/checkin.go +77 -0
- package/internal/cli/clientcmd.go +3 -2
- package/internal/cli/control.go +46 -5
- package/internal/cli/control_test.go +65 -0
- package/internal/cli/engines_test.go +103 -0
- package/internal/cli/events.go +15 -0
- package/internal/cli/events_test.go +50 -0
- package/internal/cli/judge.go +104 -0
- package/internal/cli/quotas.go +125 -0
- package/internal/cli/requirements.go +51 -0
- package/internal/cli/root.go +157 -11
- package/internal/cli/session.go +75 -3
- package/internal/cli/session_test.go +82 -3
- package/internal/cli/sessionlog.go +6 -0
- package/internal/cli/sessionlog_test.go +22 -2
- package/internal/cli/suggest.go +7 -5
- package/internal/cli/team.go +37 -9
- package/internal/cli/team_test.go +39 -0
- package/internal/client/ab.go +372 -0
- package/internal/client/ab_test.go +287 -0
- package/internal/client/apk.go +8 -0
- package/internal/client/apk_test.go +59 -0
- package/internal/client/apps.go +1078 -0
- package/internal/client/apps_test.go +516 -0
- package/internal/client/browse.go +84 -36
- package/internal/client/browse_test.go +107 -9
- package/internal/client/browseinput.go +481 -0
- package/internal/client/browseinput_test.go +216 -0
- package/internal/client/corecmd.go +23 -0
- package/internal/client/corecmd_other.go +8 -0
- package/internal/client/corecmd_windows.go +24 -0
- package/internal/client/coreexe.go +196 -0
- package/internal/client/coreexe_test.go +161 -0
- package/internal/client/coreupgrade.go +866 -0
- package/internal/client/coreupgrade_test.go +561 -0
- package/internal/client/daemon.go +297 -33
- package/internal/client/daemon_test.go +33 -0
- package/internal/client/engines_checkin_test.go +57 -0
- package/internal/client/fallback_flags_test.go +64 -0
- package/internal/client/feishu.go +1259 -0
- package/internal/client/feishu_test.go +649 -0
- package/internal/client/health_identity_test.go +84 -0
- package/internal/client/hostenv.go +68 -0
- package/internal/client/hostenv_test.go +37 -0
- package/internal/client/jobs.go +518 -24
- package/internal/client/jobs_auto.go +571 -0
- package/internal/client/jobs_auto_test.go +431 -0
- package/internal/client/jobs_test.go +299 -2
- package/internal/client/knowledge.go +854 -0
- package/internal/client/knowledge_test.go +510 -0
- package/internal/client/lan.go +3 -3
- package/internal/client/plugins.go +5 -1
- package/internal/client/prefs.go +67 -0
- package/internal/client/procstart_linux.go +59 -0
- package/internal/client/publicurl.go +82 -0
- package/internal/client/quotas_merge_test.go +107 -0
- package/internal/client/requirements.go +207 -2
- package/internal/client/requirements_shots_test.go +200 -0
- package/internal/client/requirements_test.go +103 -0
- package/internal/client/restart.go +288 -0
- package/internal/client/restart_handoff_test.go +230 -0
- package/internal/client/restart_test.go +290 -0
- package/internal/client/scripts.go +1047 -0
- package/internal/client/scripts_decode_implausible.go +38 -0
- package/internal/client/scripts_decode_other.go +31 -0
- package/internal/client/scripts_decode_test.go +130 -0
- package/internal/client/scripts_decode_windows.go +182 -0
- package/internal/client/scripts_test.go +739 -0
- package/internal/client/service.go +717 -97
- package/internal/client/service_test.go +217 -1
- package/internal/client/sessions_cache.go +148 -0
- package/internal/client/sessions_cache_test.go +163 -0
- package/internal/client/sessions_fold.go +165 -0
- package/internal/client/sessions_fold_test.go +204 -0
- package/internal/client/singleinstance.go +470 -0
- package/internal/client/singleinstance_test.go +607 -0
- package/internal/client/suggest.go +105 -2
- package/internal/client/suggest_test.go +86 -0
- package/internal/client/team_flags.go +25 -11
- package/internal/client/team_flags_test.go +39 -0
- package/internal/client/testmain_test.go +88 -0
- package/internal/client/workspace_default_test.go +158 -0
- package/internal/client/workspacelockenv_test.go +54 -0
- package/internal/config/config.go +48 -0
- package/internal/judge/judge.go +315 -0
- package/internal/requirements/bootstrap.go +16 -0
- package/internal/requirements/consolidate.go +100 -9
- package/internal/requirements/doc.go +154 -3
- package/internal/requirements/graph.go +138 -0
- package/internal/requirements/memory.go +7 -0
- package/internal/requirements/modules.go +312 -0
- package/internal/requirements/modules_test.go +373 -0
- package/internal/requirements/relations.go +155 -0
- package/internal/requirements/relations_test.go +231 -0
- package/internal/requirements/requirements.go +10 -0
- package/internal/requirements/shots.go +418 -0
- package/internal/requirements/shots_test.go +387 -0
- package/internal/session/multiclient_test.go +368 -0
- package/internal/session/session.go +175 -11
- package/internal/session/session_test.go +7 -0
- package/internal/session/status.go +21 -0
- package/internal/session/status_test.go +28 -0
- package/internal/session/title.go +16 -0
- package/internal/session/title_test.go +26 -0
- package/internal/session/workspace_lock.go +265 -28
- package/internal/suggest/suggest.go +23 -1
- package/internal/team/event.go +3 -1
- package/internal/team/execute.go +21 -6
- package/internal/team/fallback_route_test.go +80 -0
- package/internal/team/planner.go +15 -3
- package/internal/team/route.go +89 -17
- package/internal/team/team.go +18 -4
- package/npm/apk/magic-agent-mobile-release.apk +0 -0
- package/npm/apk/version.json +6 -3
- package/npm/dist/darwin-arm64/magic-agent +0 -0
- package/npm/dist/darwin-arm64/magic-client +0 -0
- package/npm/dist/darwin-x64/magic-agent +0 -0
- package/npm/dist/darwin-x64/magic-client +0 -0
- package/npm/dist/linux-arm64/magic-agent +0 -0
- package/npm/dist/linux-arm64/magic-client +0 -0
- package/npm/dist/linux-x64/magic-agent +0 -0
- package/npm/dist/linux-x64/magic-client +0 -0
- package/npm/dist/win32-x64/magic-agent.exe +0 -0
- package/npm/dist/win32-x64/magic-client.exe +0 -0
- package/npm/install.js +11 -0
- package/package.json +14 -3
|
@@ -382,22 +382,102 @@ func TestDshHeadlessArgs(t *testing.T) {
|
|
|
382
382
|
cases := []struct {
|
|
383
383
|
name string
|
|
384
384
|
sessionID string
|
|
385
|
+
patch string
|
|
385
386
|
json bool
|
|
386
387
|
want []string
|
|
387
388
|
}{
|
|
388
|
-
{"纯 headless", "", false, []string{"--profile", "headless", "任务"}},
|
|
389
|
-
{"续接", "s1", false, []string{"--profile", "headless", "--session-id", "s1", "任务"}},
|
|
390
|
-
{"事件流", "", true, []string{"--profile", "headless", "--json", "任务"}},
|
|
391
|
-
{"续接 + 事件流", "s1", true, []string{"--profile", "headless", "--session-id", "s1", "--json", "任务"}},
|
|
389
|
+
{"纯 headless", "", "", false, []string{"--profile", "headless", "任务"}},
|
|
390
|
+
{"续接", "s1", "", false, []string{"--profile", "headless", "--session-id", "s1", "任务"}},
|
|
391
|
+
{"事件流", "", "", true, []string{"--profile", "headless", "--json", "任务"}},
|
|
392
|
+
{"续接 + 事件流", "s1", "", true, []string{"--profile", "headless", "--session-id", "s1", "--json", "任务"}},
|
|
393
|
+
// --patch 是**启动器** flag,必须在 --profile 之前(放到后面 headless 会报 unknown option)。
|
|
394
|
+
{"模型 patch", "s1", "/tmp/p.yml", false,
|
|
395
|
+
[]string{"--patch", "/tmp/p.yml", "--profile", "headless", "--session-id", "s1", "任务"}},
|
|
392
396
|
}
|
|
393
397
|
for _, c := range cases {
|
|
394
|
-
got := dshHeadlessArgs("headless", c.sessionID, "任务", c.json)
|
|
398
|
+
got := dshHeadlessArgs("headless", c.sessionID, c.patch, "任务", c.json)
|
|
395
399
|
if fmt.Sprint(got) != fmt.Sprint(c.want) {
|
|
396
400
|
t.Errorf("%s: %v want %v", c.name, got, c.want)
|
|
397
401
|
}
|
|
398
402
|
}
|
|
399
403
|
}
|
|
400
404
|
|
|
405
|
+
// headless 提示词传输:Windows 走 stdin("-"),其余平台保持位置参数。
|
|
406
|
+
func TestDshHeadlessPromptArg(t *testing.T) {
|
|
407
|
+
arg, stdin := dshHeadlessPromptArg("【用户】\n只回复 OK")
|
|
408
|
+
if platformIsWindows() {
|
|
409
|
+
if arg != "-" || stdin != "【用户】\n只回复 OK" {
|
|
410
|
+
t.Errorf("Windows 应走 stdin:arg=%q stdin=%q", arg, stdin)
|
|
411
|
+
}
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
if arg != "【用户】\n只回复 OK" || stdin != "" {
|
|
415
|
+
t.Errorf("非 Windows 应走位置参数:arg=%q stdin=%q", arg, stdin)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// dshHeadlessPatchFor:把 -m 落成 agent-default-model 覆盖;没 -m 不落文件。
|
|
420
|
+
func TestDshHeadlessPatchFor(t *testing.T) {
|
|
421
|
+
dir := t.TempDir()
|
|
422
|
+
w, _ := argsCaptureCLI(t, dir, "dsh", `echo 'ok'`)
|
|
423
|
+
|
|
424
|
+
if path, cleanup := dshHeadlessPatchFor(context.Background(), w.bin, ""); path != "" {
|
|
425
|
+
t.Fatalf("没有 -m 不该写 patch,got %q", path)
|
|
426
|
+
} else {
|
|
427
|
+
cleanup()
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
path, cleanup := dshHeadlessPatchFor(context.Background(), w.bin, "volcengine/deepseek-v4.1-flash-1017")
|
|
431
|
+
if path == "" {
|
|
432
|
+
t.Fatal("有 -m 应写出临时 patch")
|
|
433
|
+
}
|
|
434
|
+
data, err := os.ReadFile(path)
|
|
435
|
+
if err != nil {
|
|
436
|
+
t.Fatalf("读 patch: %v", err)
|
|
437
|
+
}
|
|
438
|
+
body := string(data)
|
|
439
|
+
for _, want := range []string{"id: agent-default-model", `provider: "volcengine"`, `model: "deepseek-v4.1-flash-1017"`} {
|
|
440
|
+
if !strings.Contains(body, want) {
|
|
441
|
+
t.Errorf("patch 内容缺 %q:%q", want, body)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
cleanup()
|
|
445
|
+
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
446
|
+
t.Errorf("cleanup 应删掉临时 patch,stat err = %v", err)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 续接轮把 -m 透成 --patch:模型选择在 headless 通道同样生效(2026-10-25 报障)。
|
|
451
|
+
func TestDshResumePassesModelPatch(t *testing.T) {
|
|
452
|
+
dir := t.TempDir()
|
|
453
|
+
w, log := argsCaptureCLI(t, dir, "dsh", `echo 'ok'`)
|
|
454
|
+
|
|
455
|
+
resp, err := (&DshEngine{BinPath: w.bin}).Complete(context.Background(), Request{
|
|
456
|
+
Messages: []Message{{Role: "user", Content: "hi"}},
|
|
457
|
+
SessionID: "sess-1",
|
|
458
|
+
Model: "volcengine/deepseek-v4.1-flash-1017",
|
|
459
|
+
})
|
|
460
|
+
if err != nil {
|
|
461
|
+
t.Fatalf("Complete: %v", err)
|
|
462
|
+
}
|
|
463
|
+
args := readFileString(t, log)
|
|
464
|
+
var headlessLine string
|
|
465
|
+
for _, ln := range strings.Split(args, "\n") {
|
|
466
|
+
if strings.Contains(ln, "--patch") {
|
|
467
|
+
headlessLine = ln
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if headlessLine == "" {
|
|
471
|
+
t.Fatalf("续接轮应带 --patch(headless 没有 --model):%q", args)
|
|
472
|
+
}
|
|
473
|
+
if strings.Index(headlessLine, "--patch") > strings.Index(headlessLine, "--profile") {
|
|
474
|
+
t.Errorf("--patch 必须排在 --profile 之前:%q", headlessLine)
|
|
475
|
+
}
|
|
476
|
+
if resp.Model != "volcengine/deepseek-v4.1-flash-1017" {
|
|
477
|
+
t.Errorf("Model = %q want 本轮 -m 指定的那条路由", resp.Model)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
401
481
|
// -c/--continue:按会话登记表找**同一 workspace** 下最近一条 dsh 会话(其它引擎不算)。
|
|
402
482
|
func TestDshLatestSessionIDPrefersWorkspace(t *testing.T) {
|
|
403
483
|
sess := t.TempDir()
|
|
@@ -870,6 +950,28 @@ func TestDshListModelsFromProfilePatch(t *testing.T) {
|
|
|
870
950
|
}
|
|
871
951
|
}
|
|
872
952
|
|
|
953
|
+
// home 级 $DSH_HOME/cordis.patch.yml(对所有 profile 生效)也要进探测链:
|
|
954
|
+
// 2026-10-23 实测 dsh 0.2.1-rc.2 重写配置后 profile 级 patch 清成空 [],
|
|
955
|
+
// provider 只剩 home 级这一份,不读它就整条链空手而归。
|
|
956
|
+
func TestDshListModelsFromHomePatch(t *testing.T) {
|
|
957
|
+
t.Setenv("MAGIC_AGENT_DSH_PROFILE", "")
|
|
958
|
+
home := writeDshPatch(t, "sdk", "[]\n")
|
|
959
|
+
if err := os.WriteFile(filepath.Join(home, "cordis.patch.yml"),
|
|
960
|
+
[]byte("- id: llm-pi-ai\n config:\n providers:\n minimax:\n models:\n - id: MiniMax-M3.1-Flash\n"), 0o644); err != nil {
|
|
961
|
+
t.Fatal(err)
|
|
962
|
+
}
|
|
963
|
+
t.Setenv("DSH_HOME", home)
|
|
964
|
+
|
|
965
|
+
got, err := (&DshEngine{}).ListModels(context.Background())
|
|
966
|
+
if err != nil {
|
|
967
|
+
t.Fatalf("ListModels: %v", err)
|
|
968
|
+
}
|
|
969
|
+
want := []string{"minimax/MiniMax-M3.1-Flash"}
|
|
970
|
+
if !equalStrings(got, want) {
|
|
971
|
+
t.Errorf("ListModels() = %v\n want %v", got, want)
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
873
975
|
// 两条通道(sdk + headless 回退)的 patch 取并集:magic-agent 两条都会用到。
|
|
874
976
|
func TestDshListModelsUnionAcrossProfiles(t *testing.T) {
|
|
875
977
|
t.Setenv("MAGIC_AGENT_DSH_PROFILE", "")
|
|
@@ -1175,7 +1277,7 @@ func TestDshIgnoredParamsProduceNoFlags(t *testing.T) {
|
|
|
1175
1277
|
_ = req.Temperature
|
|
1176
1278
|
_ = req.Tools
|
|
1177
1279
|
_ = req.Timeout
|
|
1178
|
-
args := dshHeadlessArgs(e.profile(), "", "【用户】\nhi", false)
|
|
1280
|
+
args := dshHeadlessArgs(e.profile(), "", "", "【用户】\nhi", false)
|
|
1179
1281
|
want := []string{"--profile", "headless", "【用户】\nhi"}
|
|
1180
1282
|
if fmt.Sprint(args) != fmt.Sprint(want) {
|
|
1181
1283
|
t.Errorf("headless args = %v want %v", args, want)
|
package/internal/agent/engine.go
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// prompt.go — 多轮消息扁平化为单条 prompt 的公共逻辑
|
|
13
13
|
// claude.go — Claude Code CLI 引擎(--print --output-format json)
|
|
14
14
|
// codebuddy.go — CodeBuddy Code CLI 引擎(一个独立 CLI、两个账号)
|
|
15
|
-
// trae.go —
|
|
15
|
+
// trae.go — TRAE CLI 2.0 引擎(exec --json,-m 指定模型)
|
|
16
16
|
// llmengine.go — simonw/LLM CLI 引擎(-m / -s / -o 透传)
|
|
17
17
|
// codex.go — Codex CLI 引擎(exec / exec resume + --output-schema)
|
|
18
18
|
// openclaw.go — OpenClaw CLI 引擎(agent --local --json)
|
|
@@ -92,6 +92,24 @@ type Request struct {
|
|
|
92
92
|
// 引擎实现负责剥掉可能的 "engine/model" 前缀。
|
|
93
93
|
Model string
|
|
94
94
|
|
|
95
|
+
// FallbackEngine / FallbackModel 主引擎失败时的**备用引擎**(2026-10-24)。
|
|
96
|
+
//
|
|
97
|
+
// 只在「主引擎只有过程、没有正文」时降级(见 fallback.go):已经流出正文再换
|
|
98
|
+
// 模型重来,用户会看到两段互相矛盾的答案。降级时 SessionID / Continue 一律清空 ——
|
|
99
|
+
// 备用引擎没有主引擎那条会话,拿旧 id 去 resume 只会报「会话不存在」。
|
|
100
|
+
// 空引擎 = 没配备用,行为与以前完全一致。引擎实现不读这两个字段。
|
|
101
|
+
FallbackEngine string
|
|
102
|
+
FallbackModel string
|
|
103
|
+
|
|
104
|
+
// FallbackDown 降级标记:为 true 时本轮请求**本身就是备用引擎在跑**(见 fallback.go)。
|
|
105
|
+
//
|
|
106
|
+
// 为什么要有它(2026-10-26 修「主备没真的发挥作用」):模型清单只属于它自己的引擎。
|
|
107
|
+
// 用户配的备用模型(如 codebuddy 的 `hy3`)拿到别的引擎上只会吃一句
|
|
108
|
+
// `--model hy3 未知`,于是备用「跑了但没用」。fallbackTarget 因此清空 Model,
|
|
109
|
+
// 这个字段让引擎侧在 Model 为空时**不要回落到自己的默认模型** —— 配置里的备用模型
|
|
110
|
+
// 作废时「用备用引擎自己的默认模型重跑」比「带着一个必然失败的模型再失败一次」有用。
|
|
111
|
+
FallbackDown bool
|
|
112
|
+
|
|
95
113
|
// SystemPrompt 可选系统提示词。各引擎自行决定注入方式
|
|
96
114
|
//(claude/codebuddy用 --append-system-prompt,trae 拼进 prompt 头)。
|
|
97
115
|
SystemPrompt string
|
|
@@ -211,6 +229,24 @@ type Request struct {
|
|
|
211
229
|
// 默认值是否开启按引擎区分,见 AppendDefaultOn(dsh 默认关)。
|
|
212
230
|
Append <-chan string
|
|
213
231
|
|
|
232
|
+
// Reply 原生控制应答通道(可写回引擎自己的控制协议行)。
|
|
233
|
+
//
|
|
234
|
+
// 为什么需要(2026-10-25 实测):codebuddy / claude 在 headless 下遇到需要宿主裁决的动作
|
|
235
|
+
// (计划审批 ExitPlanMode、manual 档下的工具授权)时会发一行
|
|
236
|
+
//
|
|
237
|
+
// {"type":"control_request","request_id":"perm_…","request":{"subtype":"can_use_tool",…}}
|
|
238
|
+
//
|
|
239
|
+
// 然后**原地等 stdin 上的 control_response**。不回就整轮挂住(用户侧看到的是
|
|
240
|
+
// 「转圈、没有审批卡」)。回的内容必须走子进程 stdin —— 不能当成下一条 user 消息,
|
|
241
|
+
// 那时 CLI 正阻塞在控制请求上,user 消息要等到这一轮结束才会被读。
|
|
242
|
+
//
|
|
243
|
+
// 语义:非 nil 时本次调用的 stdin 会**保持打开**(首条 user 消息之后不 EOF),
|
|
244
|
+
// 通道里每收到一行就原样写进子进程 stdin;这一轮收尾(turn_end / result / error)
|
|
245
|
+
// 或 Stream 返回时关掉写端,让 CLI 正常退出。
|
|
246
|
+
// 只有 stream-json 输入通道的引擎(claude / codebuddy / codebuddy-ai)支持;其余引擎
|
|
247
|
+
// 拿到非 nil 会在 CLI 层被挡下(见 AppendSupportOf)。
|
|
248
|
+
Reply <-chan string
|
|
249
|
+
|
|
214
250
|
// OnSessionID 引擎发现 session_id 时回调(用于「看历史」持久化)。
|
|
215
251
|
//
|
|
216
252
|
// 谁需要它:CLI 在 runStreamAsk 里包一个 sessionWriter,每条事件落盘到
|
|
@@ -22,16 +22,16 @@ import (
|
|
|
22
22
|
// ┌──────────────┬──────────────────────────┬───────────────────────────┬────────────────────────────┬──────────────────┬───────────────────────┬─────────────────────┬──────────────────────┐
|
|
23
23
|
// │ Request 字段 │ claude │ codebuddy │ trae │ llm │ codex │ openclaw │ dsh │
|
|
24
24
|
// ├──────────────┼──────────────────────────┼───────────────────────────┼────────────────────────────┼──────────────────┼───────────────────────┼─────────────────────┼──────────────────────┤
|
|
25
|
-
// │ Model │ --model <m> │ --model <m> │ -
|
|
26
|
-
// │ SessionID │ --resume <id> │ --resume <id> │
|
|
27
|
-
// │ Continue │ --continue │ --continue │
|
|
25
|
+
// │ Model │ --model <m> │ --model <m> │ -m <m> │ -m <m> │ -m <m> │ --model <m> │ 不支持(配置层)⑨ │
|
|
26
|
+
// │ SessionID │ --resume <id> │ --resume <id> │ exec resume <id> │ --cid <id> │ exec resume <id> │ --session-id <id> │ 不支持(显式报错)⑨ │
|
|
27
|
+
// │ Continue │ --continue │ --continue │ exec resume --last │ -c │ resume --last │ sessions 查最新 id │ 不支持(显式报错)⑨ │
|
|
28
28
|
// │ MaxTokens │ --settings(env 注入)① │ --settings(env 注入)① │ 不支持(静默忽略)② │ -o max_tokens │ 不支持(静默忽略)② │ 静默忽略② │ 静默忽略② │
|
|
29
29
|
// │ Temperature │ 不支持 │ 不支持 │ 不支持(静默忽略)② │ -o temperature │ 不支持(静默忽略)② │ 静默忽略② │ 静默忽略② │
|
|
30
30
|
// │ Reasoning │ --effort <l> │ --effort <l> │ 不支持(忽略) │ 不支持 │ -c reasoning_effort │ 不支持 │ 不支持 │
|
|
31
31
|
// │ Tools │ --tools / 档位③ │ --tools / 档位③ │ --disallowed-tool / 全放行③│ 不支持 │ -s 沙箱档位④ │ 内嵌 agent(忽略) │ 内嵌 agent(忽略)⑨ │
|
|
32
|
-
// │ Permission │ --permission-mode⑧ │ --permission-mode⑧ │
|
|
32
|
+
// │ Permission │ --permission-mode⑧ │ --permission-mode⑧ │ 未接线(仅 -y) │ 不支持 │ 未接线(沙箱×审批两轴)│ 未接线(exec.mode) │ 未接线(配置层预设)⑨│
|
|
33
33
|
// │ SystemPrompt │ --append-system-prompt │ --append-system-prompt │ 展平进 prompt 头部 │ -s │ -c developer_instr.⑤ │ 展平进 prompt 头部 │ 展平进 prompt 头部 │
|
|
34
|
-
// │ Timeout │ 进程级超时 │ 进程级超时 │
|
|
34
|
+
// │ Timeout │ 进程级超时 │ 进程级超时 │ 进程级超时 │ 进程级超时 │ 进程级超时 │ + --timeout <sec> │ 进程级超时 │
|
|
35
35
|
// │ JSONSchema │ 不支持 │ 不支持 │ 不支持 │ 输出后处理抽 JSON│ --output-schema 原生 │ 输出后处理抽 JSON │ 输出后处理抽 JSON │
|
|
36
36
|
// │ Workspace │ 子进程 cwd⑥ │ 子进程 cwd⑥ │ 子进程 cwd⑥ │ 不支持⑦ │ -C/--cd + cwd⑥ │ 不支持⑦ │ 子进程 cwd⑥ │
|
|
37
37
|
// └──────────────┴──────────────────────────┴───────────────────────────┴────────────────────────────┴──────────────────┴───────────────────────┴─────────────────────┴──────────────────────┘
|
|
@@ -41,11 +41,10 @@ import (
|
|
|
41
41
|
//
|
|
42
42
|
// 注入输出上限(已用真实 CLI --help 验证);两者无 temperature 原生参数,也无对应 settings 键。
|
|
43
43
|
//
|
|
44
|
-
// ② trae 的
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// 三者构造参数时统一静默忽略。
|
|
44
|
+
// ② trae 2.0 的 exec 没有 max-tokens / temperature 原生参数(1.x 的 `-c` 覆盖
|
|
45
|
+
// 会整体替换模型配置块,曾实测导致模型名漂移),因此这两项在 trae 上明确不支持;
|
|
46
|
+
// codex 实测 `-c model_max_output_tokens=` / `-c temperature=` 会被 CLI 静默忽略
|
|
47
|
+
// (无对应生效键);openclaw agent 无相关 flag —— 三者构造参数时统一静默忽略。
|
|
49
48
|
//
|
|
50
49
|
// ③ Tools 语义:关闭 = claude/codebuddy `--tools ""`;trae `--disallowed-tool`
|
|
51
50
|
//
|
|
@@ -53,7 +52,7 @@ import (
|
|
|
53
52
|
// 开启 = claude/codebuddy `--permission-mode <档位>`(四档模型,见 ⑧ 与 permission.go;
|
|
54
53
|
// 改造前恒传 `--dangerously-skip-permissions` / `-y` = 第 4 档);trae `-y`。
|
|
55
54
|
// 白名单 = claude/codebuddy `--tools a,b` + `--permission-mode <档位>`;trae **无收窄能力**
|
|
56
|
-
// (`--allowed-tool`
|
|
55
|
+
// (`--allowed-tool` 只做自动批准、不裁剪工具集,实测仍下发全部工具),
|
|
57
56
|
// 故白名单降级为「默认开启所有工具」= 全放行 `-y`,与 codex 的白名单降级同构。
|
|
58
57
|
// llm 无工具概念。
|
|
59
58
|
//
|
|
@@ -170,10 +169,15 @@ var (
|
|
|
170
169
|
binName: "trae-cli",
|
|
171
170
|
envVar: "MAGIC_AGENT_TRAE_BIN",
|
|
172
171
|
candidates: []string{
|
|
172
|
+
// TRAE CLI 2.0 的官方 v2 安装器落点:主程序 traex,
|
|
173
|
+
// 同目录还有 trae-cli / traecli 兼容入口(.cmd / 符号链接)。
|
|
174
|
+
"~/AppData/Local/Programs/TraeCLI/bin/traex.exe",
|
|
175
|
+
"~/.local/bin/traex",
|
|
176
|
+
"~/.trae/cli/current/traex",
|
|
177
|
+
// 兼容入口(PATH 不干净时的兜底)
|
|
173
178
|
"~/.local/bin/trae-cli",
|
|
174
|
-
"~/bin/trae-cli",
|
|
175
179
|
},
|
|
176
|
-
notFound: "
|
|
180
|
+
notFound: "TRAE CLI not found (install TRAE CLI or set MAGIC_AGENT_TRAE_BIN)",
|
|
177
181
|
}
|
|
178
182
|
llmBase = cliBase{
|
|
179
183
|
binName: "llm",
|
|
@@ -370,9 +374,9 @@ func WorkspaceSupportOf(engine string) string {
|
|
|
370
374
|
// codex npm install -g @openai/codex
|
|
371
375
|
// openclaw npm install -g openclaw@latest(官方文档;装完通常还要 openclaw onboard --install-daemon)
|
|
372
376
|
// dsh npm i -g @deepseek-ai/dsh
|
|
373
|
-
// trae sh -c "$(curl -
|
|
374
|
-
// (官方
|
|
375
|
-
//
|
|
377
|
+
// trae sh -c "$(curl -fsSL https://trae.cn/trae-cli/install_v2.sh)"
|
|
378
|
+
// (官方 TRAE CLI 2.0 安装脚本;Windows 用
|
|
379
|
+
// `irm https://trae.cn/trae-cli/install_v2.ps1 | iex`。1.x 已不再支持)
|
|
376
380
|
// llm python3 -m venv ~/.llm-venv && ~/.llm-venv/bin/pip install llm
|
|
377
381
|
// (与 npm postinstall 同一条隔离安装路径,避开系统 Python;另有 pip / pipx / brew)
|
|
378
382
|
//
|
|
@@ -400,7 +404,7 @@ func InstallCommandOf(engine string) string {
|
|
|
400
404
|
case "dsh":
|
|
401
405
|
return "npm i -g @deepseek-ai/dsh"
|
|
402
406
|
case "trae":
|
|
403
|
-
return `sh -c "$(curl -
|
|
407
|
+
return `sh -c "$(curl -fsSL https://trae.cn/trae-cli/install_v2.sh)"`
|
|
404
408
|
case "llm":
|
|
405
409
|
return "python3 -m venv ~/.llm-venv && ~/.llm-venv/bin/pip install llm"
|
|
406
410
|
}
|
|
@@ -199,7 +199,7 @@ func TestInstallCommandOf(t *testing.T) {
|
|
|
199
199
|
"codex": "npm install -g @openai/codex",
|
|
200
200
|
"openclaw": "npm install -g openclaw@latest",
|
|
201
201
|
"dsh": "npm i -g @deepseek-ai/dsh",
|
|
202
|
-
"trae": `sh -c "$(curl -
|
|
202
|
+
"trae": `sh -c "$(curl -fsSL https://trae.cn/trae-cli/install_v2.sh)"`,
|
|
203
203
|
"llm": "python3 -m venv ~/.llm-venv && ~/.llm-venv/bin/pip install llm",
|
|
204
204
|
}
|
|
205
205
|
for engine, want := range cases {
|
|
@@ -288,7 +288,7 @@ func TestFirstSemver(t *testing.T) {
|
|
|
288
288
|
{"codex-cli 0.154.0", "0.154.0"},
|
|
289
289
|
{"OpenClaw 2026.6.11 (e085fa1)", "2026.6.11"},
|
|
290
290
|
{"0.1.5-rc.2", "0.1.5-rc.2"},
|
|
291
|
-
{"
|
|
291
|
+
{"traecli 0.211.1\n", "0.211.1"},
|
|
292
292
|
{"1.2.3+build.7", "1.2.3+build.7"},
|
|
293
293
|
// 认不出来就空串(**不编**)
|
|
294
294
|
{"no version here", ""},
|
|
@@ -4,10 +4,9 @@ package agent
|
|
|
4
4
|
//
|
|
5
5
|
// 用 fake CLI 脚本(写入临时目录的 shell 脚本)模拟三家 CLI 的行为,
|
|
6
6
|
// 验证:
|
|
7
|
-
// - 参数构造(--model、--tools ""、--output-format、-
|
|
8
|
-
// --query-timeout 映射)
|
|
7
|
+
// - 参数构造(--model、--tools ""、--output-format、-m model、exec resume)
|
|
9
8
|
// - 输出解析(claude JSON envelope、codebuddy 三种 envelope、
|
|
10
|
-
// user_query 回显剥离、trae
|
|
9
|
+
// user_query 回显剥离、trae 2.0 的 exec --json 事件流)
|
|
11
10
|
// - FlattenPrompt 扁平化逻辑
|
|
12
11
|
// - 引擎注册表 / Lookup
|
|
13
12
|
|
|
@@ -209,7 +208,7 @@ func TestReasoningEffortArgs(t *testing.T) {
|
|
|
209
208
|
}
|
|
210
209
|
// dsh 的 launcher/headless 没有 reasoning 开关:NoThinking 对它保持现状,
|
|
211
210
|
// 不能塞 --effort(实测启动器报 "--profile <name> is required")。
|
|
212
|
-
dshNo := dshHeadlessArgs("headless", "", "hi", false)
|
|
211
|
+
dshNo := dshHeadlessArgs("headless", "", "", "hi", false)
|
|
213
212
|
if len(dshNo) < 3 || dshNo[0] != "--profile" || dshNo[1] != "headless" {
|
|
214
213
|
t.Errorf("dsh 参数不应被 NoThinking 改坏: %v", dshNo)
|
|
215
214
|
}
|
|
@@ -488,11 +487,11 @@ func TestCodeBuddyDefaultModel(t *testing.T) {
|
|
|
488
487
|
|
|
489
488
|
// ── Trae 引擎 ─────────────────────────────────────────────────
|
|
490
489
|
|
|
491
|
-
func
|
|
490
|
+
func TestTraeComplete(t *testing.T) {
|
|
492
491
|
dir := t.TempDir()
|
|
493
492
|
log := filepath.Join(dir, "args.log")
|
|
494
|
-
cli := filepath.Join(dir, "
|
|
495
|
-
script := "#!/bin/sh\nprintf '%s\\n' \"$*\" > " + log + "\
|
|
493
|
+
cli := filepath.Join(dir, "traex")
|
|
494
|
+
script := "#!/bin/sh\nprintf '%s\\n' \"$*\" > " + log + "\n" + traeV2Stub("trae 的回答", "th-1")
|
|
496
495
|
if err := os.WriteFile(cli, []byte(script), 0o755); err != nil {
|
|
497
496
|
t.Fatal(err)
|
|
498
497
|
}
|
|
@@ -510,16 +509,18 @@ func TestTraeCompletePlainText(t *testing.T) {
|
|
|
510
509
|
if resp.Text != "trae 的回答" {
|
|
511
510
|
t.Errorf("Text = %q", resp.Text)
|
|
512
511
|
}
|
|
512
|
+
if resp.SessionID != "th-1" {
|
|
513
|
+
t.Errorf("SessionID = %q want th-1", resp.SessionID)
|
|
514
|
+
}
|
|
513
515
|
|
|
514
516
|
data, _ := os.ReadFile(log)
|
|
515
517
|
args := string(data)
|
|
516
|
-
//
|
|
517
|
-
if strings.Contains(args, "
|
|
518
|
-
t.Errorf("
|
|
518
|
+
// 2.0:exec --json,不再有 -p / --query-timeout / model.name
|
|
519
|
+
if !strings.Contains(args, "exec") || !strings.Contains(args, "--json") {
|
|
520
|
+
t.Errorf("2.0 应走 exec --json: %q", args)
|
|
519
521
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
t.Errorf("--query-timeout not mapped: %q", args)
|
|
522
|
+
if strings.Contains(args, "--query-timeout") || strings.Contains(args, "model.name") {
|
|
523
|
+
t.Errorf("不应出现 1.x 参数: %q", args)
|
|
523
524
|
}
|
|
524
525
|
// 工具禁用
|
|
525
526
|
if !strings.Contains(args, "--disallowed-tool Bash") {
|
|
@@ -534,8 +535,8 @@ func TestTraeCompletePlainText(t *testing.T) {
|
|
|
534
535
|
func TestTraeExplicitModel(t *testing.T) {
|
|
535
536
|
dir := t.TempDir()
|
|
536
537
|
log := filepath.Join(dir, "args.log")
|
|
537
|
-
cli := filepath.Join(dir, "
|
|
538
|
-
script := "#!/bin/sh\nprintf '%s\\n' \"$*\" > " + log + "\
|
|
538
|
+
cli := filepath.Join(dir, "traex")
|
|
539
|
+
script := "#!/bin/sh\nprintf '%s\\n' \"$*\" > " + log + "\n" + traeV2Stub("ok", "th-1")
|
|
539
540
|
if err := os.WriteFile(cli, []byte(script), 0o755); err != nil {
|
|
540
541
|
t.Fatal(err)
|
|
541
542
|
}
|
|
@@ -552,59 +553,19 @@ func TestTraeExplicitModel(t *testing.T) {
|
|
|
552
553
|
}
|
|
553
554
|
data, _ := os.ReadFile(log)
|
|
554
555
|
args := string(data)
|
|
555
|
-
if !strings.Contains(args, "-
|
|
556
|
-
t.Errorf("-
|
|
556
|
+
if !strings.Contains(args, "-m My-MiniMax-M3") {
|
|
557
|
+
t.Errorf("-m model not passed: %q", args)
|
|
557
558
|
}
|
|
558
559
|
}
|
|
559
560
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if err := os.WriteFile(cli, []byte(script), 0o755); err != nil {
|
|
566
|
-
t.Fatal(err)
|
|
567
|
-
}
|
|
568
|
-
e := &TraeEngine{BinPath: cli}
|
|
569
|
-
_, err := e.Complete(context.Background(), Request{
|
|
570
|
-
Messages: []Message{{Role: "user", Content: "hi"}},
|
|
571
|
-
Timeout: 30 * time.Minute,
|
|
572
|
-
})
|
|
573
|
-
if err != nil {
|
|
574
|
-
t.Fatalf("Complete: %v", err)
|
|
575
|
-
}
|
|
576
|
-
data, _ := os.ReadFile(log)
|
|
577
|
-
if !strings.Contains(string(data), "--query-timeout 10m0s") {
|
|
578
|
-
t.Errorf("query-timeout should cap at 600s: %q", string(data))
|
|
579
|
-
}
|
|
561
|
+
// traeV2Stub 造 trae 2.0 的 JSONL 事件流(thread.started + 正文 + turn.completed)。
|
|
562
|
+
func traeV2Stub(text, sessionID string) string {
|
|
563
|
+
return "printf '%s\\n' '{\"type\":\"thread.started\",\"thread_id\":\"" + sessionID + "\"}'\n" +
|
|
564
|
+
"printf '%s\\n' '{\"type\":\"item.completed\",\"item\":{\"id\":\"i\",\"type\":\"agent_message\",\"text\":\"" + text + "\"}}'\n" +
|
|
565
|
+
"printf '%s\\n' '{\"type\":\"turn.completed\"}'\n"
|
|
580
566
|
}
|
|
581
567
|
|
|
582
|
-
// ──
|
|
583
|
-
|
|
584
|
-
func TestTraeDefaultModel(t *testing.T) {
|
|
585
|
-
home := t.TempDir()
|
|
586
|
-
t.Setenv("HOME", home)
|
|
587
|
-
|
|
588
|
-
// 无配置文件 → 空串
|
|
589
|
-
if got := TraeDefaultModel(); got != "" {
|
|
590
|
-
t.Errorf("no config: got %q want empty", got)
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// 有配置 → 读 model.name
|
|
594
|
-
cfgDir := filepath.Join(home, ".trae")
|
|
595
|
-
if err := os.MkdirAll(cfgDir, 0o755); err != nil {
|
|
596
|
-
t.Fatal(err)
|
|
597
|
-
}
|
|
598
|
-
yaml := "model:\n name: My-MiniMax-M3\nmodels:\n- name: \"My-MiniMax-M3\"\n"
|
|
599
|
-
if err := os.WriteFile(filepath.Join(cfgDir, "trae_cli.yaml"), []byte(yaml), 0o644); err != nil {
|
|
600
|
-
t.Fatal(err)
|
|
601
|
-
}
|
|
602
|
-
if got := TraeDefaultModel(); got != "My-MiniMax-M3" {
|
|
603
|
-
t.Errorf("got %q want My-MiniMax-M3", got)
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// ── 会话续接(SessionID → --resume 映射)──────────────────────
|
|
568
|
+
// ── 会话续接(SessionID → exec resume 映射)────────────────────
|
|
608
569
|
|
|
609
570
|
// argsCaptureCLI 写一个把 "$*" 逐行记入 args.log 的假 CLI,返回 (引擎, log 路径)。
|
|
610
571
|
func argsCaptureCLI(t *testing.T, dir, name, cannedOutput string) (*engineWithLog, string) {
|
|
@@ -737,9 +698,9 @@ echo '{"type":"result","result":"好"}'
|
|
|
737
698
|
}
|
|
738
699
|
}
|
|
739
700
|
|
|
740
|
-
func
|
|
701
|
+
func TestTraeResumeSession(t *testing.T) {
|
|
741
702
|
dir := t.TempDir()
|
|
742
|
-
w, log := argsCaptureCLI(t, dir, "trae-cli",
|
|
703
|
+
w, log := argsCaptureCLI(t, dir, "trae-cli", traeV2Stub("续上了", "tr-9"))
|
|
743
704
|
e := w.trae()
|
|
744
705
|
resp, err := e.Complete(context.Background(), Request{
|
|
745
706
|
Messages: []Message{{Role: "user", Content: "hi"}},
|
|
@@ -748,21 +709,23 @@ func TestTraeResumeSessionEqualsForm(t *testing.T) {
|
|
|
748
709
|
if err != nil {
|
|
749
710
|
t.Fatalf("Complete: %v", err)
|
|
750
711
|
}
|
|
751
|
-
//
|
|
712
|
+
// 2.0 的 thread.started 会给出真实会话 id。
|
|
752
713
|
if resp.SessionID != "tr-9" {
|
|
753
|
-
t.Errorf("Response.SessionID = %q want tr-9
|
|
714
|
+
t.Errorf("Response.SessionID = %q want tr-9", resp.SessionID)
|
|
754
715
|
}
|
|
755
716
|
data, _ := os.ReadFile(log)
|
|
756
717
|
args := string(data)
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
718
|
+
if !strings.Contains(args, "exec resume tr-9") {
|
|
719
|
+
t.Errorf("exec resume tr-9 not passed: %q", args)
|
|
720
|
+
}
|
|
721
|
+
if strings.Contains(args, "--resume") {
|
|
722
|
+
t.Errorf("2.0 不应再出现 --resume: %q", args)
|
|
760
723
|
}
|
|
761
724
|
}
|
|
762
725
|
|
|
763
726
|
func TestTraeNewSessionDefault(t *testing.T) {
|
|
764
727
|
dir := t.TempDir()
|
|
765
|
-
w, log := argsCaptureCLI(t, dir, "trae-cli",
|
|
728
|
+
w, log := argsCaptureCLI(t, dir, "trae-cli", traeV2Stub("ok", "th-new"))
|
|
766
729
|
e := w.trae()
|
|
767
730
|
resp, err := e.Complete(context.Background(), Request{
|
|
768
731
|
Messages: []Message{{Role: "user", Content: "hi"}},
|
|
@@ -770,13 +733,12 @@ func TestTraeNewSessionDefault(t *testing.T) {
|
|
|
770
733
|
if err != nil {
|
|
771
734
|
t.Fatalf("Complete: %v", err)
|
|
772
735
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
t.Errorf("Response.SessionID = %q want empty", resp.SessionID)
|
|
736
|
+
if resp.SessionID != "th-new" {
|
|
737
|
+
t.Errorf("Response.SessionID = %q want th-new", resp.SessionID)
|
|
776
738
|
}
|
|
777
739
|
data, _ := os.ReadFile(log)
|
|
778
|
-
if strings.Contains(string(data), "
|
|
779
|
-
t.Errorf("默认新会话不应带
|
|
740
|
+
if strings.Contains(string(data), "resume") {
|
|
741
|
+
t.Errorf("默认新会话不应带 resume: %q", string(data))
|
|
780
742
|
}
|
|
781
743
|
}
|
|
782
744
|
|
|
@@ -822,11 +784,10 @@ func TestCodeBuddyContinueSession(t *testing.T) {
|
|
|
822
784
|
}
|
|
823
785
|
}
|
|
824
786
|
|
|
825
|
-
// trae
|
|
826
|
-
|
|
827
|
-
func TestTraeContinueBareResume(t *testing.T) {
|
|
787
|
+
// trae 2.0:Continue → `exec resume --last`(不再有裸 --resume)。
|
|
788
|
+
func TestTraeContinueResumeLast(t *testing.T) {
|
|
828
789
|
dir := t.TempDir()
|
|
829
|
-
w, log := argsCaptureCLI(t, dir, "trae-cli",
|
|
790
|
+
w, log := argsCaptureCLI(t, dir, "trae-cli", traeV2Stub("ok", "th-last"))
|
|
830
791
|
e := w.trae()
|
|
831
792
|
if _, err := e.Complete(context.Background(), Request{
|
|
832
793
|
Messages: []Message{{Role: "user", Content: "hi"}},
|
|
@@ -836,11 +797,11 @@ func TestTraeContinueBareResume(t *testing.T) {
|
|
|
836
797
|
}
|
|
837
798
|
data, _ := os.ReadFile(log)
|
|
838
799
|
args := string(data)
|
|
839
|
-
if !strings.Contains(args, "--
|
|
840
|
-
t.Errorf("
|
|
800
|
+
if !strings.Contains(args, "exec resume --last") {
|
|
801
|
+
t.Errorf("exec resume --last not passed: %q", args)
|
|
841
802
|
}
|
|
842
|
-
if strings.Contains(args, "--resume
|
|
843
|
-
t.Errorf("
|
|
803
|
+
if strings.Contains(args, "--resume") {
|
|
804
|
+
t.Errorf("2.0 不应再出现 --resume: %q", args)
|
|
844
805
|
}
|
|
845
806
|
}
|
|
846
807
|
|
|
@@ -857,7 +818,7 @@ func TestSessionPriorityOverContinue(t *testing.T) {
|
|
|
857
818
|
}{
|
|
858
819
|
{"claude", "s-1", "--resume s-1", "--continue", "claude", `echo '{"type":"result","result":"ok"}'`},
|
|
859
820
|
{"codebuddy", "cb-1", "--resume cb-1", "--continue", "codebuddy", `echo '{"type":"result","result":"ok"}'`},
|
|
860
|
-
{"trae", "tr-1", "
|
|
821
|
+
{"trae", "tr-1", "exec resume tr-1", "--continue", "trae-cli", traeV2Stub("ok", "tr-1")},
|
|
861
822
|
}
|
|
862
823
|
for _, tc := range cases {
|
|
863
824
|
t.Run(tc.name, func(t *testing.T) {
|
|
@@ -999,7 +960,7 @@ func TestTemperatureIgnoredByCLIs(t *testing.T) {
|
|
|
999
960
|
_, err := w.codebuddy().Complete(context.Background(), req)
|
|
1000
961
|
return err
|
|
1001
962
|
}},
|
|
1002
|
-
{"trae", "trae-cli",
|
|
963
|
+
{"trae", "trae-cli", traeV2Stub("ok", "th-1"),
|
|
1003
964
|
func(w *engineWithLog, req Request) error {
|
|
1004
965
|
_, err := w.trae().Complete(context.Background(), req)
|
|
1005
966
|
return err
|