magic-agent 2.3.0 → 2.3.9
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 +78 -13
- package/README.md +10 -4
- package/client-ui/pc/app.css +14 -0
- package/client-ui/pc/js/93-rail.js +3 -1
- package/client-ui/shared/conv/03-chat-message.js +20 -3
- package/client-ui/shared/conv/07-stream-delegation.js +15 -0
- package/client-ui/shared/conv/27-conv-ledger.js +1 -1
- package/client-ui/shared/conv/conv.css +17 -0
- package/client-ui/shared/conversation.js +209 -39
- package/client-ui/shared/transport-http.js +193 -38
- package/client-ui-mobile/index.html +733 -107
- package/client-ui-mobile/reqs.js +42 -29
- package/internal/agent/capability.go +8 -1
- package/internal/agent/claude.go +5 -3
- package/internal/agent/codebuddy.go +6 -2
- package/internal/agent/codex.go +7 -2
- package/internal/agent/dsh_sdk.go +8 -0
- package/internal/agent/dsh_sdk_test.go +17 -0
- package/internal/agent/engine.go +20 -0
- package/internal/agent/engine_test.go +52 -0
- package/internal/agent/llmengine.go +6 -0
- package/internal/agent/llmengine_test.go +7 -0
- package/internal/agent/llmhttp.go +6 -0
- package/internal/agent/llmhttp_test.go +27 -0
- package/internal/agent/llmmodels.go +13 -0
- package/internal/agent/permission.go +5 -0
- package/internal/cli/ask.go +32 -17
- package/internal/cli/requirements.go +2 -0
- package/internal/cli/sessionlog.go +5 -0
- package/internal/cli/sessionlog_user_test.go +49 -0
- package/internal/client/cors_test.go +32 -0
- package/internal/client/jobs.go +1160 -0
- package/internal/client/jobs_test.go +369 -0
- package/internal/client/requirements.go +27 -7
- package/internal/client/requirements_test.go +51 -0
- package/internal/client/service.go +259 -142
- package/internal/client/service_test.go +78 -0
- package/internal/requirements/consolidate.go +86 -24
- package/internal/requirements/requirements.go +9 -0
- package/internal/requirements/requirements_test.go +129 -0
- package/internal/requirements/store.go +11 -3
- package/internal/session/session.go +15 -1
- package/internal/session/workspace_lock.go +165 -0
- package/internal/session/workspace_lock_test.go +90 -0
- package/internal/suggest/suggest.go +101 -7
- package/internal/suggest/suggest_test.go +44 -0
- package/npm/apk/magic-agent-mobile-release.apk +0 -0
- package/npm/apk/version.json +3 -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/package.json +3 -2
|
@@ -85,6 +85,9 @@ func TestStreamAskPersistsUserMessage(t *testing.T) {
|
|
|
85
85
|
if evs[0].SessionID != sid {
|
|
86
86
|
t.Fatalf("首条应带上 session_id(pending 刷盘时回填),实得 %q", evs[0].SessionID)
|
|
87
87
|
}
|
|
88
|
+
if evs[0].Hosted {
|
|
89
|
+
t.Fatal("普通提问不该带 hosted —— 只有客户端显式传 --hosted 的托管自动发送才标")
|
|
90
|
+
}
|
|
88
91
|
|
|
89
92
|
// ② 引擎事件也都在,且排在 user 之后。
|
|
90
93
|
sawText := false
|
|
@@ -122,6 +125,52 @@ func TestStreamAskPersistsUserMessage(t *testing.T) {
|
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
128
|
+
// TestStreamAskPersistsHostedUserMessage `--hosted` 只给 user 事件加一个标识
|
|
129
|
+
// (2026-10-23 用户:「是托管发送的要标识 显示时区别于“我”发送」)。
|
|
130
|
+
//
|
|
131
|
+
// 标识必须落在**会话历史**里:客户端只画实时那条不够 —— 重开会话时气泡是从
|
|
132
|
+
// kind:"user" 回放出来的,历史没这个字段就又会变回「我」。
|
|
133
|
+
func TestStreamAskPersistsHostedUserMessage(t *testing.T) {
|
|
134
|
+
dir := t.TempDir()
|
|
135
|
+
prev := SessionLogDirOverride
|
|
136
|
+
SessionLogDirOverride = dir
|
|
137
|
+
defer func() { SessionLogDirOverride = prev }()
|
|
138
|
+
|
|
139
|
+
const sid = "sess-hosted-log"
|
|
140
|
+
const prompt = "继续下一步"
|
|
141
|
+
registerFake(&userEchoStreamEngine{name: "fake-hosted-log", sessionID: sid})
|
|
142
|
+
|
|
143
|
+
if _, _, err := runAskCmd(t, "", "-e", "fake-hosted-log", "--stream", "--save-history", "--hosted", prompt); err != nil {
|
|
144
|
+
t.Fatalf("--stream --hosted: %v", err)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
evs, err := ReadSessionLog(dir, sid)
|
|
148
|
+
if err != nil {
|
|
149
|
+
t.Fatalf("读回会话日志失败:%v", err)
|
|
150
|
+
}
|
|
151
|
+
if len(evs) == 0 || evs[0].Kind != "user" {
|
|
152
|
+
t.Fatalf("首条应是 user,实得:%s", dumpKinds(evs))
|
|
153
|
+
}
|
|
154
|
+
if !evs[0].Hosted {
|
|
155
|
+
t.Fatal("--hosted 没有写进 user 事件(历史回放会退回「我」发送)")
|
|
156
|
+
}
|
|
157
|
+
// 正文不能被标识污染:发给引擎 / 回放的都是原句。
|
|
158
|
+
if evs[0].Text != prompt {
|
|
159
|
+
t.Fatalf("user 文本被改动:期望 %q,实得 %q", prompt, evs[0].Text)
|
|
160
|
+
}
|
|
161
|
+
raw := readLines(t, filepath.Join(dir, sid+".jsonl"))
|
|
162
|
+
if len(raw) == 0 {
|
|
163
|
+
t.Fatal("落盘文件是空的")
|
|
164
|
+
}
|
|
165
|
+
var first map[string]any
|
|
166
|
+
if err := json.Unmarshal([]byte(raw[0]), &first); err != nil {
|
|
167
|
+
t.Fatal(err)
|
|
168
|
+
}
|
|
169
|
+
if first["hosted"] != true {
|
|
170
|
+
t.Fatalf("落盘 JSON 应带 hosted:true(只增字段),实得 %v", first)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
125
174
|
// TestStreamAskNoArchiveByDefault **默认什么都不存**(用户 2026-10-12:
|
|
126
175
|
// 「加一个参数 默认不存档」+「会话登记也不需要」)。
|
|
127
176
|
//
|
|
@@ -97,6 +97,38 @@ func TestCORSForLocalOrigins(t *testing.T) {
|
|
|
97
97
|
})
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/* 写方法(DELETE / PUT)必须出现在预检的 Allow-Methods 里(2026-10-23)。
|
|
101
|
+
*
|
|
102
|
+
* 为什么单独钉:容器里的页面源是 https://localhost,接口在局域网 IP,天生跨源;
|
|
103
|
+
* 删除会话(DELETE)、重命名 / 置顶同步(PUT)都带自定义头 X-Magic-Token ⇒ 必发预检。
|
|
104
|
+
* 预检里没点名的方法,浏览器会判定「未授权」而**不发出真请求** —— 用户侧就是
|
|
105
|
+
* 「确认删除了,但什么都没发生」。上面那条测试只协商 GET,所以一直没照到这里。
|
|
106
|
+
*/
|
|
107
|
+
func TestCORSPreflightAllowsWriteMethods(t *testing.T) {
|
|
108
|
+
svc := newTestService(t, fakeCore(t, goodContract))
|
|
109
|
+
srv := httptest.NewServer(svc.Handler())
|
|
110
|
+
defer srv.Close()
|
|
111
|
+
|
|
112
|
+
for _, method := range []string{"DELETE", "PUT", "POST"} {
|
|
113
|
+
req, err := http.NewRequest(http.MethodOptions, srv.URL+"/desk/session/x", nil)
|
|
114
|
+
if err != nil {
|
|
115
|
+
t.Fatal(err)
|
|
116
|
+
}
|
|
117
|
+
req.Header.Set("Origin", "https://localhost")
|
|
118
|
+
req.Header.Set("Access-Control-Request-Method", method)
|
|
119
|
+
req.Header.Set("Access-Control-Request-Headers", "x-magic-token")
|
|
120
|
+
res, err := http.DefaultClient.Do(req)
|
|
121
|
+
if err != nil {
|
|
122
|
+
t.Fatal(err)
|
|
123
|
+
}
|
|
124
|
+
res.Body.Close()
|
|
125
|
+
allowed := res.Header.Get("Access-Control-Allow-Methods")
|
|
126
|
+
if !containsFold(allowed, method) {
|
|
127
|
+
t.Fatalf("预检必须允许 %s,得到 Allow-Methods=%q(浏览器会拦掉真请求)", method, allowed)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
100
132
|
func containsFold(s, sub string) bool {
|
|
101
133
|
return len(s) >= len(sub) && (indexFold(s, sub) >= 0)
|
|
102
134
|
}
|