magic-agent 1.0.0 → 1.0.2
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/npm/bin/magic-agent.js +43 -0
- 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,123 @@
|
|
|
1
|
+
/* 传输层的一个**现成实现**:HTTP(打 magic-client 插件的 /desk/* 接口)。
|
|
2
|
+
*
|
|
3
|
+
* ⚠️ 这是给「手上已经有一个 HTTP 端点」的对接方用的**可选件**,不是组件的一部分。
|
|
4
|
+
* 组件本身与传输无关(见 conversation.js 头部的 transport 契约)。
|
|
5
|
+
* 按定稿口径 magic-agent 自己不跑服务 —— 掌天瓶那种宿主应当自己实现一个走
|
|
6
|
+
* IPC → CLI 的 transport(引擎清单/MAGIC 的 ask 与 control/会话历史都由 CLI 承担),
|
|
7
|
+
* 而不是让组件去连某个服务。
|
|
8
|
+
*
|
|
9
|
+
* ── 这一份覆盖的接口(与 CLIENT-CONTRACT.md 第二章一致)──
|
|
10
|
+
* GET /desk/engines?models=1 引擎 + 模型 + 每引擎能力
|
|
11
|
+
* POST /desk/ask SSE:open / 事件 / end
|
|
12
|
+
* POST /desk/ask/{id}/control 打断 / 收工 / 回答案
|
|
13
|
+
* GET /desk/session/{id}/messages 全量历史
|
|
14
|
+
* GET /desk/session/{id}/events?after=<seq> 增量续读
|
|
15
|
+
*
|
|
16
|
+
* ── 用法 ──
|
|
17
|
+
* const transport = MagicTransportHttp({ base: '', token: '…' }); // 同源时 base 传空串
|
|
18
|
+
* MagicConversation.create({ el, transport, … });
|
|
19
|
+
*/
|
|
20
|
+
(function () {
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
function MagicTransportHttp(opts) {
|
|
24
|
+
opts = opts || {};
|
|
25
|
+
var BASE = String(opts.base || '').replace(/\/+$/, '');
|
|
26
|
+
var TOKEN = opts.token || '';
|
|
27
|
+
|
|
28
|
+
function hdr(extra) {
|
|
29
|
+
var h = extra || {};
|
|
30
|
+
if (TOKEN) h['X-Magic-Token'] = TOKEN;
|
|
31
|
+
return h;
|
|
32
|
+
}
|
|
33
|
+
function api(path, o) {
|
|
34
|
+
o = o || {};
|
|
35
|
+
o.headers = hdr(o.headers);
|
|
36
|
+
if (o.json !== undefined) {
|
|
37
|
+
o.method = o.method || 'POST';
|
|
38
|
+
o.headers['Content-Type'] = 'application/json';
|
|
39
|
+
o.body = JSON.stringify(o.json);
|
|
40
|
+
}
|
|
41
|
+
return fetch(BASE + path, o).then(function (r) {
|
|
42
|
+
if (!r.ok) throw new Error(r.status + ' ' + r.statusText);
|
|
43
|
+
return r;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
/* 引擎清单:带模型(选择器要用),所以固定 ?models=1。 */
|
|
49
|
+
listEngines: function () {
|
|
50
|
+
return api('/desk/engines?models=1').then(function (r) { return r.json(); });
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
/* 一轮:把 SSE 拆成三个回调交给组件,组件不关心里面是 SSE 还是别的。 */
|
|
54
|
+
ask: function (payload, h) {
|
|
55
|
+
var aborted = false;
|
|
56
|
+
var ctl = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
57
|
+
var ended = false;
|
|
58
|
+
var finish = function (code) {
|
|
59
|
+
if (ended) return;
|
|
60
|
+
ended = true;
|
|
61
|
+
if (h && h.onEnd) h.onEnd(code);
|
|
62
|
+
};
|
|
63
|
+
api('/desk/ask', { json: payload, signal: ctl ? ctl.signal : undefined })
|
|
64
|
+
.then(function (resp) {
|
|
65
|
+
var reader = resp.body.getReader(), dec = new TextDecoder(), buf = '';
|
|
66
|
+
return (function pump() {
|
|
67
|
+
return reader.read().then(function (chunk) {
|
|
68
|
+
if (chunk.done) return finish(0);
|
|
69
|
+
buf += dec.decode(chunk.value, { stream: true });
|
|
70
|
+
var blocks = buf.split('\n\n');
|
|
71
|
+
buf = blocks.pop();
|
|
72
|
+
blocks.forEach(function (b) {
|
|
73
|
+
var name = '', data = '';
|
|
74
|
+
b.split('\n').forEach(function (ln) {
|
|
75
|
+
if (ln.indexOf('event: ') === 0) name = ln.slice(7).trim();
|
|
76
|
+
else if (ln.indexOf('data: ') === 0) data += ln.slice(6);
|
|
77
|
+
});
|
|
78
|
+
if (!data) return;
|
|
79
|
+
var ev; try { ev = JSON.parse(data); } catch (e) { return; }
|
|
80
|
+
if (name === 'open') {
|
|
81
|
+
if (h && h.onOpen) h.onOpen(ev && (ev.id || ev.ask || ev.run));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (name === 'end') { finish(ev && ev.code); return; }
|
|
85
|
+
if (h && h.onEvent) h.onEvent(ev);
|
|
86
|
+
});
|
|
87
|
+
if (ended) return;
|
|
88
|
+
return pump();
|
|
89
|
+
});
|
|
90
|
+
})();
|
|
91
|
+
})
|
|
92
|
+
.catch(function (e) {
|
|
93
|
+
/* 被中止不算错(用户点了打断),别把它报成发送失败。 */
|
|
94
|
+
if (aborted) return finish(null);
|
|
95
|
+
if (h && h.onError) h.onError(e);
|
|
96
|
+
finish(null);
|
|
97
|
+
});
|
|
98
|
+
return { cancel: function () { aborted = true; if (ctl) ctl.abort(); } };
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
control: function (p) {
|
|
102
|
+
var body = { op: p.op };
|
|
103
|
+
if (p.text != null) body.text = p.text;
|
|
104
|
+
return api('/desk/ask/' + encodeURIComponent(p.id) + '/control', { json: body })
|
|
105
|
+
.then(function () { return true; });
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
readSession: function (id, o) {
|
|
109
|
+
o = o || {};
|
|
110
|
+
/* 有游标就走增量接口:长会话下差一个数量级。
|
|
111
|
+
形状与全量接口不同(多 lastSeq / snapshotRequired),一并原样交给组件。 */
|
|
112
|
+
if (o.after != null) {
|
|
113
|
+
return api('/desk/session/' + encodeURIComponent(id) + '/events?after=' + o.after)
|
|
114
|
+
.then(function (r) { return r.json(); });
|
|
115
|
+
}
|
|
116
|
+
return api('/desk/session/' + encodeURIComponent(id) + '/messages')
|
|
117
|
+
.then(function (r) { return r.json(); });
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
window.MagicTransportHttp = MagicTransportHttp;
|
|
123
|
+
})();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2014 The Caveat Project Authors (https://github.com/googlefonts/caveat)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
6
|
+
<title>掌天瓶 · 对话</title>
|
|
7
|
+
<!--
|
|
8
|
+
移动端宿主:把**同一个**公共对话组件(shared/conversation.js)挂到页面上。
|
|
9
|
+
与 PC 那份是同一份实现 —— 差别只有两个:这个文件里的颜色令牌用的是
|
|
10
|
+
agents-anywhere 安卓端(Jetpack Compose)那套十六进制值,以及多了一条
|
|
11
|
+
移动端形态的顶栏。
|
|
12
|
+
|
|
13
|
+
── 顶栏为什么还在宿主里 ──
|
|
14
|
+
组件只负责对话区(消息流 + 输入框 + 三项选择器)。标题栏、返回、主题这些
|
|
15
|
+
属于宿主形态,PC 与移动端的形态本来就不一样,塞进组件反而会让两边互相牵制。
|
|
16
|
+
|
|
17
|
+
── 视觉基准(逐值抄自 AA 安卓端的 Compose 源码,不是目测)──
|
|
18
|
+
designsystem/Theme.kt → 全部色令牌(暗色 canvas #09090B / 亮色 #FDFCFB 等)
|
|
19
|
+
SessionDetailScreen.kt → 顶栏 BackIconButton / TitlePill 的尺寸
|
|
20
|
+
对话区本身(气泡 / markdown / 工具卡 / 思考块 / 审批卡)在共享组件里,
|
|
21
|
+
那里同样标了对应的 Compose 行号。
|
|
22
|
+
|
|
23
|
+
── 公共组件在哪 ──
|
|
24
|
+
对话区在 ../client-ui/shared/ 下(PC 与移动端同一份实现)。这一页**按同级
|
|
25
|
+
路径 shared/ 引用**:打包时由 magic-test 的 tools/build-mobile-www.cjs 把
|
|
26
|
+
client-ui/shared 一起复制进 www/shared —— 所以产物里是这个相对关系。
|
|
27
|
+
直接拿浏览器开这个文件时,请让静态服务器同时暴露 client-ui-mobile/ 与
|
|
28
|
+
client-ui/shared(或在临时目录里拼出同样的同级关系),否则共享模块会 404。
|
|
29
|
+
-->
|
|
30
|
+
<link rel="stylesheet" href="shared/conversation.css">
|
|
31
|
+
<style>
|
|
32
|
+
/* ===== 令牌:AA 安卓端那一套,映射到共享组件的颜色契约 =====
|
|
33
|
+
⚠️ 默认 = 暗色:跟 anywhere 的 DarkAgentsAnywhereColors 逐字段对齐。
|
|
34
|
+
用户在系统里选了亮色才切亮色(没设主题时不会莫名跑出白底)。 */
|
|
35
|
+
:root{
|
|
36
|
+
color-scheme:dark;
|
|
37
|
+
--canvas:#09090B;
|
|
38
|
+
--ink:#FAFAFA; --ink-soft:#E5E5E5; --muted:#A3A3A3; --faint:#737373;
|
|
39
|
+
--border:rgba(255,255,255,.09); --subtle:#292929; --raised:#1F1F1F;
|
|
40
|
+
--primary-action:#FAFAFA; --on-primary-action:#09090B;
|
|
41
|
+
--app-shadow:rgba(0,0,0,.32);
|
|
42
|
+
|
|
43
|
+
--bubble:#2A2A2A; --bubble-text:#F4F4F5;
|
|
44
|
+
--code:#111113; --status-neutral:#2D2D2D; --status-neutral-text:#D4D4D8;
|
|
45
|
+
--activity:rgba(24,24,27,.063);
|
|
46
|
+
|
|
47
|
+
--md-body:#EDEDEF; --md-muted:#A1A1AA; --md-border:#27272A; --md-code-bg:#292929;
|
|
48
|
+
--link:#EBB353; --prompt:#E8798F; --shimmer:#FFFFFF;
|
|
49
|
+
--panel:#1F1F1F; --panel-border:#27272A;
|
|
50
|
+
|
|
51
|
+
--accent:#39A887; --warning:#F59E0B; --success:#22C55E;
|
|
52
|
+
--error-surface:#2A1214; --error-border:#5F2429; --error-text:#FCA5A5; --error-icon:#F97066;
|
|
53
|
+
--running:#10B981; --transitioning:#3B82F6; --active:#F59E0B;
|
|
54
|
+
|
|
55
|
+
/* 移动端沿用 AA 的三档圆角(18 / 12 / full),与 Compose 规格一致 */
|
|
56
|
+
--r-card:18px; --r:12px; --r-full:999px;
|
|
57
|
+
--font:-apple-system,BlinkMacSystemFont,"PingFang SC","Noto Sans CJK SC",system-ui,sans-serif;
|
|
58
|
+
--mono:ui-monospace,SFMono-Regular,Menlo,"SF Mono",monospace;
|
|
59
|
+
}
|
|
60
|
+
@media (prefers-color-scheme:light){
|
|
61
|
+
:root{
|
|
62
|
+
color-scheme:light;
|
|
63
|
+
--canvas:#FDFCFB;
|
|
64
|
+
--ink:#0A0A0B; --ink-soft:#2B2B2B; --muted:#777777; --faint:#AAA8A2;
|
|
65
|
+
--border:#E7E5E0; --subtle:#F1F0ED; --raised:#FFFFFF;
|
|
66
|
+
--primary-action:#0A0A0B; --on-primary-action:#FFFFFF;
|
|
67
|
+
--app-shadow:rgba(0,0,0,.13);
|
|
68
|
+
--bubble:#F1F0ED; --bubble-text:#242522;
|
|
69
|
+
--code:rgba(255,255,255,.72); --status-neutral:#E4E2DD; --status-neutral-text:#6F6E69;
|
|
70
|
+
--activity:rgba(241,240,237,.08);
|
|
71
|
+
--md-body:#242522; --md-muted:#7C7B76; --md-border:#E4E1DB; --md-code-bg:#F1F0ED;
|
|
72
|
+
--link:#EBB353; --prompt:#C45D74; --shimmer:#000000;
|
|
73
|
+
--panel:#ECECEA; --panel-border:transparent;
|
|
74
|
+
--accent:#18866A; --warning:#D97706; --success:#16A34A;
|
|
75
|
+
--error-surface:#FFF4F4; --error-border:#F4C7C7; --error-text:#B42318; --error-icon:#B42318;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
|
80
|
+
/* ⚠️ 锁死视口高度而不是 min-height:用 min-height 时 body 会被内容撑高,
|
|
81
|
+
滚动条落到 document 上,.conv__thread 的 overflow-y:auto 就永远不触发 ——
|
|
82
|
+
后果是打开会话看不到顶部,也做不了「回到底部」。 */
|
|
83
|
+
html,body{margin:0;height:100%;overflow:hidden}
|
|
84
|
+
body{background:var(--canvas);color:var(--ink);font:15px/1.45 var(--font);
|
|
85
|
+
overscroll-behavior:none;display:flex;flex-direction:column}
|
|
86
|
+
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
|
|
87
|
+
svg{display:block}
|
|
88
|
+
|
|
89
|
+
/* ── 顶栏 SessionDetailScreen:BackIconButton 40dp 圆 · TitlePill 34dp 胶囊 ── */
|
|
90
|
+
.detailbar{display:grid;grid-template-columns:40px 1fr 40px;align-items:center;gap:8px;
|
|
91
|
+
padding:calc(env(safe-area-inset-top) + 10px) 16px 8px;flex:0 0 auto}
|
|
92
|
+
.iconbtn{width:40px;height:40px;border-radius:var(--r-full);display:flex;align-items:center;
|
|
93
|
+
justify-content:center;background:var(--raised);color:var(--ink);border:1px solid var(--border)}
|
|
94
|
+
.titlepill{justify-self:center;max-width:100%;height:34px;display:flex;align-items:center;
|
|
95
|
+
padding:0 14px;border:1px solid var(--border);border-radius:var(--r-full);background:var(--raised);
|
|
96
|
+
font-size:14px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
97
|
+
|
|
98
|
+
/* 组件挂载点:占满剩余高度 */
|
|
99
|
+
#conv{flex:1;min-height:0;display:flex}
|
|
100
|
+
|
|
101
|
+
/* 挂载失败时说明原因,而不是留一片空白 */
|
|
102
|
+
.boot-err{margin:16px;padding:14px;border-radius:14px;background:var(--error-surface);
|
|
103
|
+
border:1.2px solid var(--error-border);color:var(--error-text);font-size:13.5px;line-height:1.55}
|
|
104
|
+
|
|
105
|
+
/* 底部安全区:iPhone 的 home indicator 会压住输入框 */
|
|
106
|
+
.composer{padding-bottom:calc(16px + env(safe-area-inset-bottom))}
|
|
107
|
+
|
|
108
|
+
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
|
109
|
+
</style>
|
|
110
|
+
</head>
|
|
111
|
+
<body>
|
|
112
|
+
|
|
113
|
+
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
|
114
|
+
<symbol id="i-back" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5 8 12l7 7"/></symbol>
|
|
115
|
+
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5.5v13M5.5 12h13"/></symbol>
|
|
116
|
+
</svg>
|
|
117
|
+
|
|
118
|
+
<div class="detailbar">
|
|
119
|
+
<button class="iconbtn" id="btnNew" aria-label="新会话"><svg width="20" height="20"><use href="#i-plus"/></svg></button>
|
|
120
|
+
<span class="titlepill" id="title">新会话</span>
|
|
121
|
+
<span></span>
|
|
122
|
+
</div>
|
|
123
|
+
<div id="conv"></div>
|
|
124
|
+
|
|
125
|
+
<script src="shared/conversation.js"></script>
|
|
126
|
+
<script src="shared/transport-http.js"></script>
|
|
127
|
+
<script>
|
|
128
|
+
(function () {
|
|
129
|
+
'use strict';
|
|
130
|
+
|
|
131
|
+
/* 基址与令牌:两种宿主。
|
|
132
|
+
· 打包进 App(Capacitor):页面是本地文件,地址与令牌由 boot.js 从配对信息注入 ——
|
|
133
|
+
那时 window.MAGIC_BASE 非空,所有 /desk/* 请求要前缀到桌面地址上;
|
|
134
|
+
· 浏览器同源打开(调试):不带 MAGIC_BASE,token 在 ?token= 上。 */
|
|
135
|
+
var TOKEN = window.MAGIC_TOKEN || new URLSearchParams(location.search).get('token') || '';
|
|
136
|
+
var BASE = (window.MAGIC_BASE || '').replace(/\/+$/, '');
|
|
137
|
+
|
|
138
|
+
var titleEl = document.getElementById('title');
|
|
139
|
+
|
|
140
|
+
function showBootError(msg) {
|
|
141
|
+
var d = document.createElement('div');
|
|
142
|
+
d.className = 'boot-err';
|
|
143
|
+
d.textContent = msg;
|
|
144
|
+
document.getElementById('conv').appendChild(d);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* 打包进 App 时页面在本地(capacitor://localhost 或 file://),
|
|
148
|
+
不能直接 fetch 本地相对路径 —— 下面这层把 <img src> 之类的相对资源
|
|
149
|
+
前缀成桌面地址。与 boot.js 的垫片同一思路,只是这里组件不产生 img,
|
|
150
|
+
所以只需要保证 fetch 走 BASE(组件已按 opts.base 处理)。 */
|
|
151
|
+
|
|
152
|
+
var conv = null;
|
|
153
|
+
(async function boot() {
|
|
154
|
+
try {
|
|
155
|
+
/* 传输层由宿主注入 —— 组件本身不连后端。
|
|
156
|
+
打包进 App 时这里会换成「走自己后端」的实现;当前先用 HTTP 参考件。 */
|
|
157
|
+
var transport = window.MagicTransportHttp({ base: BASE, token: TOKEN });
|
|
158
|
+
conv = window.MagicConversation.create({
|
|
159
|
+
el: document.getElementById('conv'),
|
|
160
|
+
transport: transport,
|
|
161
|
+
titleEl: titleEl,
|
|
162
|
+
});
|
|
163
|
+
document.getElementById('btnNew').onclick = function () { conv.newSession(); };
|
|
164
|
+
/* 供对接方/调试取用:window.magicConv.setSession(sid) 换会话 */
|
|
165
|
+
window.magicConv = conv;
|
|
166
|
+
} catch (e) {
|
|
167
|
+
showBootError('对话组件挂载失败:' + (e && e.message ? e.message : e));
|
|
168
|
+
}
|
|
169
|
+
})();
|
|
170
|
+
})();
|
|
171
|
+
</script>
|
|
172
|
+
</body>
|
|
173
|
+
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// magic-agent 的 npm bin 转发层:
|
|
5
|
+
// 按当前平台挑出预编译的 Go 二进制,原样转发 argv / stdio / 退出码。
|
|
6
|
+
//
|
|
7
|
+
// 之所以用 spawnSync + stdio:inherit 而不是直接 exec:
|
|
8
|
+
// - 保持真实 fd 0/1/2,Go 侧 process.stdin.isTTY 判定不受影响(管道读 prompt 依赖它)
|
|
9
|
+
// - 退出码、Ctrl-C 信号行为与直接运行二进制一致
|
|
10
|
+
|
|
11
|
+
const { spawnSync } = require("child_process");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
const { binaryPath, distDir, currentTarget, exeName } = require("../lib/platform");
|
|
16
|
+
|
|
17
|
+
const target = currentTarget();
|
|
18
|
+
const bin = binaryPath(target);
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(bin)) {
|
|
21
|
+
const found = fs.existsSync(distDir)
|
|
22
|
+
? fs.readdirSync(distDir).filter((d) => fs.existsSync(path.join(distDir, d, exeName(d.startsWith("win32") ? "windows" : "linux"))))
|
|
23
|
+
: [];
|
|
24
|
+
process.stderr.write(
|
|
25
|
+
`magic-agent: 当前平台 ${target.dir} 没有可用二进制(期望 ${bin})\n` +
|
|
26
|
+
`已打包的平台:${found.length ? found.join(", ") : "(无)"}\n` +
|
|
27
|
+
`解决办法:在有 Go 的环境执行 \`npm run build\`,或用 \`go build -o bin/magic-agent ./cmd/magic-agent\` 自行编译后直接调用。\n`
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const res = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
33
|
+
|
|
34
|
+
if (res.error) {
|
|
35
|
+
process.stderr.write(`magic-agent: 启动失败: ${res.error.message}\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
if (res.signal) {
|
|
39
|
+
// 让父进程以相同信号退出,保持 shell 语义(如超时/中断)
|
|
40
|
+
process.kill(process.pid, res.signal);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
process.exit(res.status === null ? 1 : res.status);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magic-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "专业的 agent CLI 代理工具 - 统一 claude / codebuddy / trae / llm / codex / openclaw 引擎",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"internal/",
|
|
23
23
|
"go.mod",
|
|
24
24
|
"go.sum",
|
|
25
|
+
"client-ui/",
|
|
26
|
+
"client-ui-mobile/",
|
|
27
|
+
"CLIENT-CONTRACT.md",
|
|
25
28
|
"README.md",
|
|
26
29
|
"LICENSE"
|
|
27
30
|
],
|