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.
Files changed (41) hide show
  1. package/CLIENT-CONTRACT.md +298 -0
  2. package/README.md +35 -0
  3. package/client-ui/assets/OFL-Caveat.txt +93 -0
  4. package/client-ui/assets/caveat.woff2 +0 -0
  5. package/client-ui/index.html +246 -0
  6. package/client-ui/pc/app.css +5110 -0
  7. package/client-ui/pc/assets/damiao-mascot.png +0 -0
  8. package/client-ui/pc/assets/icon.png +0 -0
  9. package/client-ui/pc/index.html +154 -0
  10. package/client-ui/pc/js/00-base.js +52 -0
  11. package/client-ui/pc/js/03-chat-message.js +880 -0
  12. package/client-ui/pc/js/04-task-list.js +430 -0
  13. package/client-ui/pc/js/05-task-list-render.js +812 -0
  14. package/client-ui/pc/js/06-ask-answer.js +204 -0
  15. package/client-ui/pc/js/07-chat-stream-render.js +493 -0
  16. package/client-ui/pc/js/08-ask-event-map.js +171 -0
  17. package/client-ui/pc/js/09-stream-apply.js +367 -0
  18. package/client-ui/pc/js/10-markdown.js +430 -0
  19. package/client-ui/pc/js/18-askbox.js +262 -0
  20. package/client-ui/pc/js/19-attachments.js +679 -0
  21. package/client-ui/pc/js/29-msg-variants.js +462 -0
  22. package/client-ui/pc/js/90-glue.js +212 -0
  23. package/client-ui/pc/js/91-workbench-stubs.js +134 -0
  24. package/client-ui/pc/js/92-composer.js +406 -0
  25. package/client-ui/pc/js/93-rail.js +362 -0
  26. package/client-ui/pc/vendor/highlight.min.js +1262 -0
  27. package/client-ui/pc/vendor/marked.min.js +6 -0
  28. package/client-ui/pc/vendor/purify.min.js +3 -0
  29. package/client-ui/shared/conversation.css +256 -0
  30. package/client-ui/shared/conversation.js +1225 -0
  31. package/client-ui/shared/transport-http.js +123 -0
  32. package/client-ui-mobile/assets/OFL-Caveat.txt +93 -0
  33. package/client-ui-mobile/assets/caveat.woff2 +0 -0
  34. package/client-ui-mobile/index.html +173 -0
  35. package/npm/bin/magic-agent.js +43 -0
  36. package/npm/dist/darwin-arm64/magic-agent +0 -0
  37. package/npm/dist/darwin-x64/magic-agent +0 -0
  38. package/npm/dist/linux-arm64/magic-agent +0 -0
  39. package/npm/dist/linux-x64/magic-agent +0 -0
  40. package/npm/dist/win32-x64/magic-agent.exe +0 -0
  41. 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.
@@ -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.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
  ],