magic-agent 1.0.1 → 1.0.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 +298 -0
- package/README.md +35 -0
- package/client-ui/assets/OFL-Caveat.txt +93 -0
- package/client-ui/assets/caveat.woff2 +0 -0
- package/client-ui/index.html +246 -0
- package/client-ui/pc/app.css +5110 -0
- package/client-ui/pc/assets/damiao-mascot.png +0 -0
- package/client-ui/pc/assets/icon.png +0 -0
- package/client-ui/pc/index.html +154 -0
- package/client-ui/pc/js/00-base.js +52 -0
- package/client-ui/pc/js/03-chat-message.js +880 -0
- package/client-ui/pc/js/04-task-list.js +430 -0
- package/client-ui/pc/js/05-task-list-render.js +812 -0
- package/client-ui/pc/js/06-ask-answer.js +204 -0
- package/client-ui/pc/js/07-chat-stream-render.js +493 -0
- package/client-ui/pc/js/08-ask-event-map.js +171 -0
- package/client-ui/pc/js/09-stream-apply.js +367 -0
- package/client-ui/pc/js/10-markdown.js +430 -0
- package/client-ui/pc/js/18-askbox.js +262 -0
- package/client-ui/pc/js/19-attachments.js +679 -0
- package/client-ui/pc/js/29-msg-variants.js +462 -0
- package/client-ui/pc/js/90-glue.js +212 -0
- package/client-ui/pc/js/91-workbench-stubs.js +134 -0
- package/client-ui/pc/js/92-composer.js +406 -0
- package/client-ui/pc/js/93-rail.js +362 -0
- package/client-ui/pc/vendor/highlight.min.js +1262 -0
- package/client-ui/pc/vendor/marked.min.js +6 -0
- package/client-ui/pc/vendor/purify.min.js +3 -0
- package/client-ui/shared/conversation.css +256 -0
- package/client-ui/shared/conversation.js +1225 -0
- package/client-ui/shared/transport-http.js +123 -0
- package/client-ui-mobile/assets/OFL-Caveat.txt +93 -0
- package/client-ui-mobile/assets/caveat.woff2 +0 -0
- package/client-ui-mobile/index.html +173 -0
- package/internal/agent/custom_models.go +151 -5
- package/internal/agent/custom_models_test.go +130 -7
- package/internal/agent/env.go +10 -2
- package/internal/agent/env_test.go +37 -0
- package/internal/agent/repair_models_test.go +194 -0
- package/internal/cli/ask.go +22 -18
- package/internal/cli/login.go +61 -4
- package/internal/cli/login_test.go +99 -0
- package/internal/cli/repair.go +60 -0
- package/internal/cli/repair_test.go +51 -0
- package/internal/cli/root.go +21 -0
- package/internal/cli/session.go +8 -1
- package/npm/dist/darwin-arm64/magic-agent +0 -0
- package/npm/dist/darwin-x64/magic-agent +0 -0
- package/npm/dist/linux-arm64/magic-agent +0 -0
- package/npm/dist/linux-x64/magic-agent +0 -0
- package/npm/dist/win32-x64/magic-agent.exe +0 -0
- package/package.json +4 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" data-theme="light">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>magic-client · 对话</title>
|
|
7
|
+
<!--
|
|
8
|
+
PC 宿主:把公共的对话 UI 组件(shared/conversation.js)挂到页面上。
|
|
9
|
+
它自己只做三件事 —— 提供颜色令牌、给一个容器、调 create()。对话怎么渲染、
|
|
10
|
+
怎么发、怎么答、怎么选引擎/模型/授权,全在共享组件里,与移动端是同一份实现。
|
|
11
|
+
|
|
12
|
+
── 为什么这里只有对话 ──
|
|
13
|
+
按定稿口径:PC 与移动端都**只提供基础对话 UI**,其余能力(会话列表、工作区、
|
|
14
|
+
业务模块、设置…)只暴露接口,由第三方对接自己的定制界面。
|
|
15
|
+
所以这一页刻意不铺侧边栏、不做工作台 —— 组件边界之外的都该是对接方的事。
|
|
16
|
+
|
|
17
|
+
── 视觉 ──
|
|
18
|
+
颜色取 agents-anywhere 桌面工作台的 oklch 令牌(globals.css 的 :root / .dark),
|
|
19
|
+
映射到共享组件约定的那组变量名上。于是同一份对话样式在桌面是这套调性、
|
|
20
|
+
在移动端是 AA 安卓端那套 hex 调性,而**结构尺寸共用**(不存在两份样式漂移)。
|
|
21
|
+
|
|
22
|
+
── 接线 ──
|
|
23
|
+
GET /desk/contract /desk/profile 取应用名与通道能力
|
|
24
|
+
GET /desk/engines?models=1 组件自己拉(也可由宿主用 engines: 传入)
|
|
25
|
+
POST /desk/ask /desk/ask/{id}/control 一轮与打断/收工/回答案
|
|
26
|
+
-->
|
|
27
|
+
<link rel="stylesheet" href="shared/conversation.css">
|
|
28
|
+
<style>
|
|
29
|
+
/* ===== 令牌:AA 桌面工作台的 oklch 值,映射到共享组件的颜色契约 =====
|
|
30
|
+
契约里的每个名字都必须在宿主这一层给出值 —— 共享样式只用不定义。 */
|
|
31
|
+
:root, [data-theme="light"] {
|
|
32
|
+
--canvas: oklch(1 0 0);
|
|
33
|
+
--ink: oklch(0.145 0 0);
|
|
34
|
+
--ink-soft: oklch(0.27 0 0);
|
|
35
|
+
--muted: oklch(0.556 0 0);
|
|
36
|
+
--faint: oklch(0.70 0 0);
|
|
37
|
+
--border: oklch(0.922 0 0);
|
|
38
|
+
--subtle: oklch(0.97 0 0);
|
|
39
|
+
--raised: oklch(1 0 0);
|
|
40
|
+
--primary-action: oklch(0.205 0 0);
|
|
41
|
+
--on-primary-action: oklch(0.985 0 0);
|
|
42
|
+
--app-shadow: oklch(0.145 0 0 / 12%);
|
|
43
|
+
|
|
44
|
+
--bubble: oklch(0.97 0 0);
|
|
45
|
+
--bubble-text: oklch(0.205 0 0);
|
|
46
|
+
--code: oklch(1 0 0 / 72%);
|
|
47
|
+
--status-neutral: oklch(0.94 0 0);
|
|
48
|
+
--status-neutral-text: oklch(0.48 0 0);
|
|
49
|
+
--activity: oklch(0.97 0 0 / 55%);
|
|
50
|
+
|
|
51
|
+
--md-body: oklch(0.205 0 0);
|
|
52
|
+
--md-muted: oklch(0.556 0 0);
|
|
53
|
+
--md-border: oklch(0.912 0 0);
|
|
54
|
+
--md-code-bg: oklch(0.97 0 0);
|
|
55
|
+
--link: oklch(0.60 0.11 250);
|
|
56
|
+
--prompt: oklch(0.58 0.16 12);
|
|
57
|
+
--shimmer: oklch(1 0 0 / 60%);
|
|
58
|
+
--panel: oklch(0.97 0 0);
|
|
59
|
+
--panel-border: oklch(0.912 0 0);
|
|
60
|
+
|
|
61
|
+
--accent: oklch(0.56 0.10 168);
|
|
62
|
+
--warning: oklch(0.70 0.15 65);
|
|
63
|
+
--success: oklch(0.62 0.15 150);
|
|
64
|
+
--error-surface: oklch(0.97 0.02 20);
|
|
65
|
+
--error-border: oklch(0.88 0.06 20);
|
|
66
|
+
--error-text: oklch(0.48 0.17 25);
|
|
67
|
+
--error-icon: oklch(0.52 0.17 25);
|
|
68
|
+
--running: oklch(0.70 0.15 160);
|
|
69
|
+
--transitioning: oklch(0.62 0.17 255);
|
|
70
|
+
--active: oklch(0.75 0.15 75);
|
|
71
|
+
|
|
72
|
+
--r-card: 14px; --r: 9px; --r-full: 999px;
|
|
73
|
+
--font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
74
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
75
|
+
}
|
|
76
|
+
[data-theme="dark"] {
|
|
77
|
+
color-scheme: dark;
|
|
78
|
+
--canvas: oklch(0.145 0 0);
|
|
79
|
+
--ink: oklch(0.985 0 0);
|
|
80
|
+
--ink-soft: oklch(0.90 0 0);
|
|
81
|
+
--muted: oklch(0.708 0 0);
|
|
82
|
+
--faint: oklch(0.556 0 0);
|
|
83
|
+
--border: oklch(1 0 0 / 10%);
|
|
84
|
+
--subtle: oklch(0.269 0 0);
|
|
85
|
+
--raised: oklch(0.205 0 0);
|
|
86
|
+
--primary-action: oklch(0.922 0 0);
|
|
87
|
+
--on-primary-action: oklch(0.205 0 0);
|
|
88
|
+
--app-shadow: oklch(0 0 0 / 52%);
|
|
89
|
+
|
|
90
|
+
--bubble: oklch(0.269 0 0);
|
|
91
|
+
--bubble-text: oklch(0.97 0 0);
|
|
92
|
+
--code: oklch(0.18 0 0);
|
|
93
|
+
--status-neutral: oklch(0.32 0 0);
|
|
94
|
+
--status-neutral-text: oklch(0.86 0 0);
|
|
95
|
+
--activity: oklch(0.22 0 0 / 60%);
|
|
96
|
+
|
|
97
|
+
--md-body: oklch(0.93 0 0);
|
|
98
|
+
--md-muted: oklch(0.68 0 0);
|
|
99
|
+
--md-border: oklch(0.32 0 0);
|
|
100
|
+
--md-code-bg: oklch(0.269 0 0);
|
|
101
|
+
--link: oklch(0.78 0.10 250);
|
|
102
|
+
--prompt: oklch(0.72 0.15 12);
|
|
103
|
+
--shimmer: oklch(1 0 0 / 18%);
|
|
104
|
+
--panel: oklch(0.24 0 0);
|
|
105
|
+
--panel-border: oklch(0.32 0 0);
|
|
106
|
+
|
|
107
|
+
--accent: oklch(0.72 0.11 168);
|
|
108
|
+
--warning: oklch(0.80 0.14 75);
|
|
109
|
+
--success: oklch(0.75 0.15 150);
|
|
110
|
+
--error-surface: oklch(0.24 0.05 20);
|
|
111
|
+
--error-border: oklch(0.38 0.10 20);
|
|
112
|
+
--error-text: oklch(0.80 0.12 20);
|
|
113
|
+
--error-icon: oklch(0.72 0.14 22);
|
|
114
|
+
--running: oklch(0.72 0.15 160);
|
|
115
|
+
--transitioning: oklch(0.68 0.16 255);
|
|
116
|
+
--active: oklch(0.80 0.14 75);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@font-face {
|
|
120
|
+
font-family: 'AA Caveat';
|
|
121
|
+
src: url('./assets/caveat.woff2') format('woff2');
|
|
122
|
+
font-display: swap; font-weight: 400 700;
|
|
123
|
+
}
|
|
124
|
+
* { box-sizing: border-box; }
|
|
125
|
+
html, body { height: 100%; margin: 0; }
|
|
126
|
+
body {
|
|
127
|
+
background: var(--canvas); color: var(--ink);
|
|
128
|
+
font: 14px/1.5 var(--font);
|
|
129
|
+
overflow: hidden; overscroll-behavior: none;
|
|
130
|
+
display: flex; flex-direction: column;
|
|
131
|
+
}
|
|
132
|
+
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
|
|
133
|
+
input, textarea { font: inherit; color: inherit; }
|
|
134
|
+
svg { display: block; }
|
|
135
|
+
|
|
136
|
+
/* 宿主自己的顶栏:字标 + 主题 + 新会话。组件不管这些。 */
|
|
137
|
+
.bar { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
|
|
138
|
+
padding: 10px 16px; border-bottom: 1px solid var(--border); }
|
|
139
|
+
.bar__wm { font-family: 'AA Caveat', cursive; font-weight: 500; font-size: 20px; line-height: 1;
|
|
140
|
+
margin-right: auto; white-space: nowrap; }
|
|
141
|
+
.bar__t { font-size: 13px; color: var(--muted); max-width: 40vw; overflow: hidden;
|
|
142
|
+
text-overflow: ellipsis; white-space: nowrap; }
|
|
143
|
+
.bar__b { height: 30px; padding: 0 12px; border-radius: var(--r-full);
|
|
144
|
+
border: 1px solid var(--border); background: var(--raised); font-size: 13px; }
|
|
145
|
+
.bar__b:hover { background: var(--subtle); }
|
|
146
|
+
|
|
147
|
+
/* 组件挂载点:占满剩下的高度。组件内部自己是 flex 纵向布局。 */
|
|
148
|
+
#conv { flex: 1; min-height: 0; display: flex; }
|
|
149
|
+
|
|
150
|
+
/* 宿主自己的一条横幅:组件挂载失败时说明原因,而不是留一片空白。 */
|
|
151
|
+
.boot-err { margin: 16px; padding: 14px 16px; border-radius: var(--r-card);
|
|
152
|
+
border: 1px solid var(--error-border); background: var(--error-surface);
|
|
153
|
+
color: var(--error-text); font-size: 13.5px; line-height: 1.6; }
|
|
154
|
+
|
|
155
|
+
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
|
|
156
|
+
</style>
|
|
157
|
+
</head>
|
|
158
|
+
<body>
|
|
159
|
+
|
|
160
|
+
<div class="bar">
|
|
161
|
+
<span class="bar__wm" id="wordmark">magic-client</span>
|
|
162
|
+
<span class="bar__t" id="title"></span>
|
|
163
|
+
<button class="bar__b" id="btnNew" type="button">新会话</button>
|
|
164
|
+
<button class="bar__b" id="btnTheme" type="button">暗色</button>
|
|
165
|
+
</div>
|
|
166
|
+
<div id="conv"></div>
|
|
167
|
+
|
|
168
|
+
<script src="shared/conversation.js"></script>
|
|
169
|
+
<script src="shared/transport-http.js"></script>
|
|
170
|
+
<script>
|
|
171
|
+
(function () {
|
|
172
|
+
'use strict';
|
|
173
|
+
|
|
174
|
+
/* 两种宿主的基址与令牌:
|
|
175
|
+
· 桌面/浏览器:插件同源提供页面,令牌在 URL 上(?token=…),基址即同源;
|
|
176
|
+
· 移动容器:由 boot.js 注入 window.MAGIC_BASE / MAGIC_TOKEN。
|
|
177
|
+
桌面端不设 MAGIC_BASE,下面这段完全不生效,同源行为逐字节不变。 */
|
|
178
|
+
var TOKEN = window.MAGIC_TOKEN || new URLSearchParams(location.search).get('token') || '';
|
|
179
|
+
var BASE = (window.MAGIC_BASE || '').replace(/\/+$/, '');
|
|
180
|
+
|
|
181
|
+
var root = document.documentElement;
|
|
182
|
+
function setTheme(t) {
|
|
183
|
+
root.setAttribute('data-theme', t);
|
|
184
|
+
document.getElementById('btnTheme').textContent = t === 'dark' ? '暗色' : '亮色';
|
|
185
|
+
}
|
|
186
|
+
setTheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
187
|
+
document.getElementById('btnTheme').onclick = function () {
|
|
188
|
+
setTheme(root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark');
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
function api(path) {
|
|
192
|
+
return fetch(BASE + path, { headers: TOKEN ? { 'X-Magic-Token': TOKEN } : {} });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function showBootError(msg) {
|
|
196
|
+
var d = document.createElement('div');
|
|
197
|
+
d.className = 'boot-err';
|
|
198
|
+
d.textContent = msg;
|
|
199
|
+
document.getElementById('conv').appendChild(d);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
var conv = null;
|
|
203
|
+
|
|
204
|
+
/* 启动:先把应用名读回来(字标用 profile.app.name),再挂组件。
|
|
205
|
+
组件自己会去拉引擎清单 —— 宿主不替它做这件事,避免两处各拉一遍。 */
|
|
206
|
+
(async function boot() {
|
|
207
|
+
try {
|
|
208
|
+
var prof = await (await api('/desk/profile')).json();
|
|
209
|
+
var app = (prof && prof.app) || {};
|
|
210
|
+
if (app.name) {
|
|
211
|
+
document.getElementById('wordmark').textContent = app.name;
|
|
212
|
+
document.title = app.name + ' · 对话';
|
|
213
|
+
}
|
|
214
|
+
/* profile 的 ui.themeVars 覆盖主题令牌(模块/项目可借此换调性) */
|
|
215
|
+
var vars = (prof && prof.ui && prof.ui.themeVars) || {};
|
|
216
|
+
Object.keys(vars).forEach(function (k) { root.style.setProperty(k, vars[k]); });
|
|
217
|
+
} catch (e) {
|
|
218
|
+
showBootError('读 profile 失败:' + (e && e.message ? e.message : e) +
|
|
219
|
+
'(地址要带 ?token=)—— 对话仍会尝试启动。');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
/* 传输层由宿主注入 —— 组件本身不连后端。
|
|
224
|
+
这一份是 HTTP 参考实现;掌天瓶那边会换成走 IPC → CLI 的实现。 */
|
|
225
|
+
var transport = window.MagicTransportHttp({ base: BASE, token: TOKEN });
|
|
226
|
+
conv = window.MagicConversation.create({
|
|
227
|
+
el: document.getElementById('conv'),
|
|
228
|
+
transport: transport,
|
|
229
|
+
titleEl: document.getElementById('title'),
|
|
230
|
+
});
|
|
231
|
+
document.getElementById('btnNew').onclick = function () { conv.newSession(); };
|
|
232
|
+
} catch (e) {
|
|
233
|
+
showBootError('对话组件挂载失败:' + (e && e.message ? e.message : e));
|
|
234
|
+
}
|
|
235
|
+
})();
|
|
236
|
+
|
|
237
|
+
/* 宿主把组件实例挂到 window 上,方便对接方/调试直接拿:
|
|
238
|
+
window.magicConv.send('…') / .setSession(sid) / .newSession() */
|
|
239
|
+
window.magicConv = null;
|
|
240
|
+
var iv = setInterval(function () {
|
|
241
|
+
if (conv) { window.magicConv = conv; clearInterval(iv); }
|
|
242
|
+
}, 50);
|
|
243
|
+
})();
|
|
244
|
+
</script>
|
|
245
|
+
</body>
|
|
246
|
+
</html>
|