magic-agent 1.0.109 → 1.0.110

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.
@@ -388,10 +388,11 @@ textarea{font:inherit}
388
388
  /* 环境切换菜单(挂在任务页标题下):已保存的连接 + 「添加连接」。
389
389
  与会话行长按菜单同一套浮层范式(scrim 挡住下层,点空白即关)。 */
390
390
  .envmenu{position:fixed;z-index:1201;width:min(320px,calc(100vw - 24px));
391
+ max-height:min(70vh,560px);overflow-y:auto;
391
392
  background:var(--raised);border:1px solid var(--border);border-radius:18px;
392
393
  box-shadow:0 18px 44px -14px var(--app-shadow);padding:6px}
393
394
  .envmenu__scrim{position:fixed;inset:0;z-index:1200}
394
- .envmenu__head{padding:10px 12px 6px;font-size:12px;font-weight:700;letter-spacing:.04em;color:var(--muted)}
395
+ .envmenu__head{display:block;padding:10px 12px 6px;font-size:12px;font-weight:700;letter-spacing:.04em;color:var(--muted)}
395
396
  .envmenu__item{display:flex;align-items:center;gap:12px;width:100%;padding:11px 12px;
396
397
  border-radius:12px;text-align:left}
397
398
  .envmenu__item:active{background:var(--subtle)}
@@ -405,7 +406,7 @@ textarea{font:inherit}
405
406
  .envmenu__s{font-size:12px;line-height:16px;color:var(--muted);
406
407
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
407
408
  .envmenu__check{flex:0 0 auto;color:var(--primary-action)}
408
- .envmenu__sep{height:1px;margin:6px 4px;background:var(--border)}
409
+ .envmenu__sep{display:block;height:1px;margin:6px 4px;background:var(--border)}
409
410
  .envmenu__add{display:flex;align-items:center;gap:10px;width:100%;padding:12px;border-radius:12px;
410
411
  font-size:15px;font-weight:500;color:var(--primary-action);text-align:left}
411
412
  .envmenu__add:active{background:var(--subtle)}
@@ -1250,7 +1251,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1250
1251
  老 core 没有这个字段 → 空 → 回落到下面这份内置表(与 internal/team 一致)。 */
1251
1252
  var TEAM_ROLE_LABELS = {
1252
1253
  writer: '写作', researcher: '调研', reviewer: '评审', coder: '编码',
1253
- designer: '视觉', director: '影像', builder: '工程'
1254
+ designer: '视觉', director: '影像', builder: '游戏'
1254
1255
  };
1255
1256
  var TEAM_ROLE_BUILTIN = [
1256
1257
  { name: 'writer', kind: 'text', duty: '把材料写成结构清晰、可直接交付的文档' },
@@ -1259,7 +1260,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1259
1260
  { name: 'coder', kind: 'code', duty: '把复杂代码改动落到可构建、可测试的文件上' },
1260
1261
  { name: 'designer', kind: 'image', duty: '产出可直接使用的画面资产' },
1261
1262
  { name: 'director', kind: 'video', duty: '把脚本 / 分镜变成可播放的影像' },
1262
- { name: 'builder', kind: 'game', duty: '交付能跑起来的工程与可复现的启动方式' }
1263
+ { name: 'builder', kind: 'game', duty: '游戏内玩法 / 系统开发,交付能跑起来、可复现启动的工程' }
1263
1264
  ];
1264
1265
  var TEAM_ROLES = []; // core 上报的角色清单;空 = 老 core(或还没连上)→ 用内置表
1265
1266
  function teamRoleList() { return TEAM_ROLES.length ? TEAM_ROLES : TEAM_ROLE_BUILTIN; }
@@ -1359,6 +1360,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1359
1360
  var CONN_KEY = 'magic.remote.conns.v1', CONN_MAX = 8;
1360
1361
  var LS_KEY = 'magic.remote.recent.v1'; // 旧版「最近连接」:只在首次升级时搬一次
1361
1362
  var ACTIVE_ID = '';
1363
+ var CONN_SEQ = 0; // 每连一次 +1:上一台桌面迟到的响应不许画到这一台上面
1362
1364
 
1363
1365
  function connHost(url) {
1364
1366
  try { return new URL(url).host; }
@@ -1380,6 +1382,8 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1380
1382
  host: connHost(c.url), app: '', at: c.at || 0 };
1381
1383
  });
1382
1384
  saveConns(list);
1385
+ /* 搬完就删旧键:否则用户「清空」后新列表为空,下次又会被旧记录搬回来。 */
1386
+ try { localStorage.removeItem(LS_KEY); } catch (e) {}
1383
1387
  }
1384
1388
  } catch (e) {}
1385
1389
  }
@@ -1408,7 +1412,10 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1408
1412
  return next;
1409
1413
  }
1410
1414
  function removeConn(id) { saveConns(loadConns().filter(function (c) { return c.id !== id; })); }
1411
- function clearConns() { saveConns([]); }
1415
+ function clearConns() {
1416
+ saveConns([]);
1417
+ try { localStorage.removeItem(LS_KEY); } catch (e) {}
1418
+ }
1412
1419
 
1413
1420
  /* 当前连接:先按地址 + 令牌找;App 壳注入的连接还没落盘时给一条临时记录,
1414
1421
  这样标题与菜单里仍然看得见「现在连的是谁」。 */
@@ -1467,7 +1474,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1467
1474
  del.type = 'button';
1468
1475
  del.className = 'recent__del';
1469
1476
  del.setAttribute('aria-label', '删除连接');
1470
- del.appendChild(svgUse('i-trash', 18));
1477
+ del.appendChild(svgUse('#i-trash', 18));
1471
1478
  del.onclick = function () { removeConn(item.id); renderRecent(); };
1472
1479
  row.appendChild(b); row.appendChild(del);
1473
1480
  recentListEl.appendChild(row);
@@ -1520,7 +1527,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1520
1527
  b.className = 'envmenu__item';
1521
1528
  var ico = document.createElement('span');
1522
1529
  ico.className = 'envmenu__ico';
1523
- ico.appendChild(svgUse('i-laptop', 20));
1530
+ ico.appendChild(svgUse('#i-laptop', 20));
1524
1531
  b.appendChild(ico);
1525
1532
  var col = document.createElement('span');
1526
1533
  col.className = 'envmenu__col';
@@ -1529,12 +1536,16 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1529
1536
  tl.appendChild(elSpan('envmenu__name', connLabel(c) || c.host || '未命名'));
1530
1537
  if (c.app) tl.appendChild(elSpan('envmenu__badge', c.app));
1531
1538
  col.appendChild(tl);
1532
- col.appendChild(elSpan('envmenu__s', (c.host || connHost(c.url)) + (active ? ' · 当前' : '')));
1539
+ /* 名字就是 host 时不重复写一遍:副标题只留「地址(有名字时)+ 状态」。 */
1540
+ var cname = connLabel(c) || c.host || '';
1541
+ var chost = c.host || connHost(c.url);
1542
+ var csub = (cname && cname !== chost) ? chost : '';
1543
+ col.appendChild(elSpan('envmenu__s', (csub ? csub + ' · ' : '') + (active ? '当前连接' : '点击切换')));
1533
1544
  b.appendChild(col);
1534
1545
  if (active) {
1535
1546
  var ck = document.createElement('span');
1536
1547
  ck.className = 'envmenu__check';
1537
- ck.appendChild(svgUse('i-check', 18));
1548
+ ck.appendChild(svgUse('#i-check', 18));
1538
1549
  b.appendChild(ck);
1539
1550
  }
1540
1551
  b.onclick = function () { closeEnvMenu(); switchEnv(c); };
@@ -1544,7 +1555,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1544
1555
  var add = document.createElement('button');
1545
1556
  add.type = 'button';
1546
1557
  add.className = 'envmenu__add';
1547
- add.appendChild(svgUse('i-plus', 18));
1558
+ add.appendChild(svgUse('#i-plus', 18));
1548
1559
  add.appendChild(elSpan('', '添加连接'));
1549
1560
  add.onclick = function () { closeEnvMenu(); showLauncher(); };
1550
1561
  envMenuEl.appendChild(add);
@@ -1643,9 +1654,12 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1643
1654
  function refreshMineVersion() {
1644
1655
  if (!TOKEN) { paintMineVersion(); return; }
1645
1656
  var hdr = { 'X-Magic-Token': TOKEN };
1657
+ var seq = CONN_SEQ; // 切环境后旧桌面的 health 不许写到新连接上
1646
1658
  fetch(BASE + '/desk/health', { headers: hdr })
1647
1659
  .then(function (r) { return r.ok ? r.json() : null; })
1648
1660
  .then(function (h) {
1661
+ if (seq !== CONN_SEQ) return;
1662
+ if (h && h.app) noteActiveMeta({ app: h.app });
1649
1663
  if (h && typeof h.version === 'string' && h.version) { SERVER_VERSION = h.version; return; }
1650
1664
  return fetch(BASE + '/desk/contract', { headers: hdr })
1651
1665
  .then(function (r) { return r.ok ? r.json() : null; })
@@ -2214,6 +2228,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
2214
2228
  function connect(target) {
2215
2229
  BASE = target.url || '';
2216
2230
  TOKEN = target.token || '';
2231
+ CONN_SEQ++; // 上一台桌面还在飞的请求:回来时直接丢弃(见 loadSessions)
2217
2232
  var c = upsertConn(target);
2218
2233
  ACTIVE_ID = c.id;
2219
2234
  showError('');
@@ -2224,6 +2239,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
2224
2239
  function disconnect() {
2225
2240
  stopPoll();
2226
2241
  closeEnvMenu();
2242
+ CONN_SEQ++;
2227
2243
  if (conv) { try { conv.destroy(); } catch (e) {} conv = null; }
2228
2244
  convEl.innerHTML = '';
2229
2245
  BASE = ''; TOKEN = '';
@@ -2273,9 +2289,11 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
2273
2289
  if (historyEl.hidden) return; // 只在这张页可见时拉
2274
2290
  if (!TOKEN) return; // 还没配对,拉也拉不到
2275
2291
  var hdr = TOKEN ? { 'X-Magic-Token': TOKEN } : {};
2292
+ var seq = CONN_SEQ;
2276
2293
  fetch(BASE + '/desk/sessions', { headers: hdr })
2277
2294
  .then(function (r) { return r.ok ? r.json() : null; })
2278
2295
  .then(function (list) {
2296
+ if (seq !== CONN_SEQ) return; // 响应回来时已经切到另一台桌面了
2279
2297
  if (!Array.isArray(list)) return;
2280
2298
  if (sessionSig(list) === sessionSig(LAST_SESSIONS)) return; // 没变 → 不动 DOM
2281
2299
  loadSessions(true); // 变了才走完整刷新(含标题归纳 / 健康),且不许闪
@@ -2284,6 +2302,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
2284
2302
  }
2285
2303
  function loadSessions(quiet) {
2286
2304
  var hdr = TOKEN ? { 'X-Magic-Token': TOKEN } : {};
2305
+ var seq = CONN_SEQ; // 切环境后旧请求可能才回来:序号对不上就不许改这一台的画面
2287
2306
  /* quiet(由 startListPoll 的轮询触发):不塞「读取中…」占位、失败也不覆盖现有列表 ——
2288
2307
  轮询是**后台行为**,不能因为一次网络抖动就把用户正看着的列表换成一行错误,
2289
2308
  也不该让他每次看到整页闪一下「读取中…」(这正是 deleteSessionRow 绕开它的原因)。 */
@@ -2299,6 +2318,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
2299
2318
  var pDirs = fetch(BASE + '/desk/dirs', { headers: hdr })
2300
2319
  .then(function (r) { return r.ok ? r.json() : {}; }).catch(function () { return {}; });
2301
2320
  Promise.all([pList, pTitles, pHealth, pDirs]).then(function (a) {
2321
+ if (seq !== CONN_SEQ) return;
2302
2322
  TITLES = a[1] || {};
2303
2323
  DW = (a[2] && a[2].defaultWorkspace) || '';
2304
2324
  DW_READY = true;
@@ -2332,6 +2352,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
2332
2352
  var pending = LAST_SESSIONS.filter(function (s) { return !s.title_summarized; });
2333
2353
  pending.slice(0, TITLE_SUMMARIZE_CAP).forEach(function (s) { setTimeout(function () { ensureTitle(s); }, 0); });
2334
2354
  }).catch(function (e) {
2355
+ if (seq !== CONN_SEQ) return;
2335
2356
  if (quiet) return; // 后台轮询失败:保留现有列表(见上面的说明)
2336
2357
  historyList.innerHTML = '<div class="history__hint">读取会话失败:' + (e && e.message ? e.message : e) + '</div>';
2337
2358
  });
@@ -103,7 +103,8 @@ const plannerPromptTemplate = `你是任务规划器。把用户的诉求拆成
103
103
  - code:代码产出(脚本、程序、构建 / 测试相关的改动)→ coder。
104
104
  - image:画面类产出(位图 / 矢量:封面、插画、海报、图标)→ designer。
105
105
  - video:影像类产出(分镜、剪辑、成片)→ director。
106
- - game:可运行的游戏工程(代码 + 素材 + 启动方式)→ builder。
106
+ - game:游戏相关产出 —— 游戏内玩法 / 系统开发(在现有工程里改代码、加功能),以及从零交付
107
+ 可运行的游戏工程(代码 + 素材 + 启动方式)→ builder。
107
108
  - {{CODE_GATE}}
108
109
  - **需求里出现了对应类别的产物,就必须单独拆出该类任务**,哪怕用户没明说「配图 / 做成视频」。
109
110
  例:「写 100 字游戏介绍并配 1 个封面」→ 1 个 writer + 1 个 designer;
@@ -87,8 +87,8 @@ var builtinRoles = []Role{
87
87
  },
88
88
  {
89
89
  Name: "builder",
90
- Duty: "交付能跑起来的工程与可复现的启动方式",
91
- SystemPrompt: "你的角色是游戏 / 工程构建者。优先让产物可运行:先搭最小可运行骨架,再补功能;所有命令都要在工作目录内可复现,依赖与启动方式写进交付物。",
90
+ Duty: "游戏内玩法 / 系统开发,交付能跑起来、可复现启动的工程",
91
+ SystemPrompt: "你的角色是游戏开发者。在现有工程里做玩法 / 系统开发时,先读懂现有结构与约定,只改相关范围;从零交付工程时,先搭最小可运行骨架再补功能。所有命令都要在工作目录内可复现,依赖与启动方式写进交付物。",
92
92
  Kind: KindGame,
93
93
  },
94
94
  }
Binary file
Binary file
Binary file
Binary file
Binary file
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.109",
3
+ "version": "1.0.110",
4
4
  "description": "专业的 agent CLI 代理工具 - 统一 claude / codebuddy / trae / llm / codex / openclaw 引擎",
5
5
  "license": "MIT",
6
6
  "bin": {