magic-agent 1.0.110 → 1.0.111

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.
@@ -385,7 +385,7 @@ textarea{font:inherit}
385
385
  .rowmenu__item:active{background:var(--subtle)}
386
386
  .rowmenu__item--danger{color:var(--error-text)}
387
387
  .rowmenu__l{flex:1;min-width:0}
388
- /* 环境切换菜单(挂在任务页标题下):已保存的连接 + 「添加连接」。
388
+ /* 环境切换菜单(挂在任务页标题下):已保存的连接 + 「环境设置」/「扫码或粘贴地址」。
389
389
  与会话行长按菜单同一套浮层范式(scrim 挡住下层,点空白即关)。 */
390
390
  .envmenu{position:fixed;z-index:1201;width:min(320px,calc(100vw - 24px));
391
391
  max-height:min(70vh,560px);overflow-y:auto;
@@ -417,8 +417,10 @@ textarea{font:inherit}
417
417
  box-shadow:0 12px 30px -12px var(--app-shadow)}
418
418
  button.setrow{font-family:inherit}
419
419
  /* 默认引擎/模型选择器:左列「自动」与正在浏览的引擎不能都画成选中态(两个浅蓝块会让人
420
- 分不清哪个是已保存的默认)。is-on = 已保存;is-cur = 右列此刻在浏览谁。 */
421
- .pick__citem.is-cur{background:var(--subtle)}
420
+ 分不清哪个是已保存的默认)。is-on = 已保存(实底);is-cur = 右列此刻在浏览谁。
421
+ 浏览态**不铺底**,只留一道左侧细条 + 主色文字 —— 有底色就会被读成「第二个选中项」。 */
422
+ .pick__citem.is-cur{box-shadow:inset 2px 0 0 var(--primary-action)}
423
+ .pick__citem.is-cur .pick__cl{color:var(--primary-action)}
422
424
 
423
425
  /* ══════════ 发现:插件目录 ══════════
424
426
  两项 magic-* 工程的 CLI(magic-shorts / magic-video)。数据来自桌面端 `/desk/plugins`:
@@ -462,6 +464,20 @@ button.setrow{font-family:inherit}
462
464
  .setrow__v{flex:0 0 auto;font-size:14px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:50%}
463
465
  .setrow--danger{color:var(--error-text)}
464
466
  .setrow--danger .setrow__l,.setrow--danger .setrow__v{color:var(--error-text)}
467
+ /* 环境编辑(我的 → 环境):名称 / 地址 / 令牌三栏。
468
+ 环境不能只靠扫码「连过才记得」—— 用户要显式配置 IP 和名字(2026-10-22)。 */
469
+ .envfield{display:block;width:100%;height:44px;margin-top:6px;padding:0 12px;border-radius:10px;
470
+ background:var(--subtle);border:1px solid transparent;color:var(--ink);font-size:15px;
471
+ -webkit-appearance:none;outline:none}
472
+ .envfield:focus{border-color:var(--primary-action)}
473
+ .envfield::placeholder{color:var(--faint)}
474
+ .envlabel{display:block;margin-top:14px;font-size:12px;font-weight:700;color:var(--muted)}
475
+ .envhint{margin-top:10px;font-size:12px;line-height:1.6;color:var(--faint)}
476
+ .envacts{display:flex;gap:8px;margin-top:16px}
477
+ .envacts .btn{flex:1;margin-top:0}
478
+ .envdel{margin-top:14px;width:100%;height:40px;border-radius:var(--r-full);
479
+ color:var(--error-text);font-size:14px;font-weight:600}
480
+ .envdel:active{background:var(--subtle)}
465
481
  /* 两行式配置行(团队模式):左列是「名字 + 这条适合什么」,右列是引擎。
466
482
  规则描述必须常显 —— 用户要的是「每类适合什么」一眼可见,而不是藏进详情页。 */
467
483
  .setrow__main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
@@ -473,10 +489,8 @@ button.setrow{font-family:inherit}
473
489
  font:13px/1.55 -apple-system,BlinkMacSystemFont,"PingFang SC",sans-serif;-webkit-appearance:none}
474
490
  .teamedit::placeholder{color:var(--faint)}
475
491
  .teamedit__acts{display:flex;gap:8px;margin-bottom:10px}
476
- /* 团队抽屉里的分区标题(规则 / 交给谁)与行尾箭头:点开才有的配置必须看得出「这里能点」。 */
477
- .pick__cap--team{padding:10px 4px 4px;font-size:11px;font-weight:700;letter-spacing:.04em;color:var(--muted)}
492
+ /* 团队行尾箭头:点开才有的配置必须看得出「这里能点」。 */
478
493
  .setrow__chev{flex:0 0 auto;color:var(--faint);font-size:16px;line-height:1}
479
- .pick__cols--team{height:min(36vh,36dvh,300px)}
480
494
  .seg{display:inline-flex;background:var(--subtle);border-radius:var(--r-full);padding:2px;gap:2px}
481
495
  .seg__b{height:28px;padding:0 12px;border-radius:var(--r-full);font-size:13px;color:var(--muted)}
482
496
  .seg__b.is-on{background:var(--raised);color:var(--ink);font-weight:600}
@@ -496,27 +510,27 @@ button.setrow{font-family:inherit}
496
510
  · 启动页不显示底栏(showLauncher 里 hidden),无需留白
497
511
 
498
512
  min-height 而不是 height:底栏还要吞 safe-area(34px),
499
- border-box 下固定 height 会让内部 tab 被压到 26px 高、图标文字挤成一团。
500
- **74**(2026-10-22 定稿):68 时图标 23 + 选中浅底 28 + 标签 12 ≈ 50,上下只剩 18px;
501
- 74 给到上下各约 21px,选中浅底才有完整的一圈呼吸,不会被栏边切掉。 */
513
+ border-box 下固定 height 会让内部 tab 被压到 26px 高。
514
+ **56**(2026-10-22 定稿「底栏只保留图标不需要文字了」):
515
+ 去掉标签后只剩 25px 图标 + 44×34 选中浅底,56 给到浅底上下各 11px,
516
+ 栏体也矮下来 —— 对话区因此多出 18px。 */
502
517
  .tabbar{position:absolute;left:0;right:0;bottom:0;z-index:60;
503
518
  display:flex;align-items:stretch;
504
- min-height:74px;
519
+ min-height:56px;
505
520
  background:color-mix(in srgb,var(--raised) 86%,transparent);
506
521
  -webkit-backdrop-filter:blur(18px) saturate(1.4);backdrop-filter:blur(18px) saturate(1.4);
507
522
  box-shadow:0 -1px 0 var(--border),0 -14px 30px -26px var(--app-shadow);
508
523
  padding-bottom:env(safe-area-inset-bottom)}
509
- .tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
510
- padding:6px 0;
511
- color:var(--muted);font-size:10.5px;letter-spacing:.02em;transition:color .16s}
524
+ /* 只有图标:文案从底栏拿掉(2026-10-22 用户),语义交给 aria-label,不靠视觉标签。
525
+ 命中区仍是 flex:1 × 56,比"图标 + 文字"那版更高更宽,点起来更稳。 */
526
+ .tab{flex:1;display:flex;align-items:center;justify-content:center;
527
+ color:var(--muted);transition:color .16s}
512
528
  .tab.is-on{color:var(--primary-action)}
513
- /* 选中态不只换颜色,还给一块 38×28 的浅底(`--activity` 是现成的品牌色低透明令牌):
514
- 颜色是给"看得清"的,形状是给"摸得到"的 —— 暗色下主色是浅蓝,光靠颜色区分太弱。 */
515
- .tab .icw{display:grid;place-items:center;width:38px;height:28px;border-radius:9px;
529
+ /* 选中态不只换颜色,还给一块 44×34 的浅底(`--activity` 是现成的品牌色低透明令牌):
530
+ 没有文字之后,颜色是唯一的语义线索,必须再补一个形状 —— 暗色下主色是浅蓝,光靠颜色区分太弱。 */
531
+ .tab .icw{display:grid;place-items:center;width:44px;height:34px;border-radius:11px;
516
532
  transition:background .18s ease}
517
533
  .tab.is-on .icw{background:var(--activity)}
518
- .tab .lb{line-height:1.15;transition:font-weight .16s}
519
- .tab.is-on .lb{font-weight:600}
520
534
  .tab svg{display:block;transition:transform .16s}
521
535
  .tab:active svg{transform:scale(.92)}
522
536
  /* ── 「快速新建」:**半嵌入**底栏的圆形主按钮(2026-10-22 定稿)
@@ -534,42 +548,43 @@ button.setrow{font-family:inherit}
534
548
  于是看起来像按钮自带发光轮廓。
535
549
 
536
550
  实现:
537
- ① **洞**:`::before` 是一块悬在栏顶边界内的半圆(宽 38*2 / 高 38,下两角半径 38),
551
+ ① **洞**:`::before` 是一块悬在栏顶边界内的半圆(宽 74 / 高 37,下两角半径 37),
538
552
  填槽底 `--fab-well`,在底栏上边缘切出一个半圆缺口。
539
- 高度只到 38,**不侵入上方内容**(试过 78px 整圆,会盖住列表底部)。
540
- ② **嵌**:按钮直径 60,圆心落在栏顶边线上(上露 30 / 下沉 30),
553
+ 高度只到 37,**不侵入上方内容**(试过整圆深度,会盖住列表底部)。
554
+ ② **嵌**:按钮直径 68,圆心落在栏顶边线上(上露 34 / 下沉 34),
541
555
  于是下半实打实坐进洞里。圆心压边线而不是抬高,是「嵌」与「放」的分界。
542
- ③ **接触影**:`radial-gradient` 把暗环**锚定在 r=30(按钮半径)**上,
543
- 从 29.5px 起最暗、到 33px 淡完。这是唯一能精确控制「暗在哪一圈」的写法 ——
556
+ ③ **接触影**:`radial-gradient` 把暗环**锚定在 r=34(按钮半径)**上,
557
+ 从 33.5px 起最暗、到 37px 淡完。这是唯一能精确控制「暗在哪一圈」的写法 ——
544
558
  `box-shadow: inset` 的暗边永远从元素边界向内,管不到元素外的那道槽。
545
- 实测梯度(正下方):浅色 189→237→255,暗色 21→27→31,两端都单调、无回亮。
546
- 几何全用相对量(`bottom:calc(100% - 30px)`、`top:0`),安全区变化时洞和按钮一起走。 */
547
- .tabfab{flex:0 0 76px}
559
+ 几何全用相对量(`bottom:calc(100% - 34px)`、`top:0`),安全区变化时洞和按钮一起走。
560
+
561
+ **两页一致**:会话页不再把「+」收进栏内(2026-10-22 用户:「圆没有突出了 …两个效果要一致」)。
562
+ 凸出的圆在列表页和对话页长得完全一样,只用 composer 的下内边距给它让出空间。 */
563
+ .tabfab{flex:0 0 84px}
548
564
  /* 洞 = 半圆 + 接触影。
549
565
  槽底是 `--fab-well`(栏面稍暗一档的**同色系**),不是 `--canvas`:
550
566
  用页面底色在暗色下会成黑箍,在浅色下又几乎看不见 —— 两头都不对。
551
567
  接触影必须画在槽底**之上**,且暗环两端用硬锚点,
552
568
  避免模糊半径把亮度峰值推到按钮外面(那正是前几版读成光晕的原因)。
553
569
 
554
- **露出的槽宽只有 3px**(洞半径 33 − 按钮半径 30)。曾经用 38(露 8px):
555
- 那圈灰色槽底读作「按钮下面垫了个盘子」,正是用户否掉的形态。
556
- 3px 只够出一条接触缝 —— 看得见槽、看不清槽底,就等于"切出来的"。
557
- 洞的高也取 33 而不是更深:槽只要在栏顶边界内切出缺口,深了只是浪费。 */
570
+ **露出的槽宽只有 3px**(洞半径 37 − 按钮半径 34)。曾经用更大:那圈灰色槽底读作「按钮下面垫了个盘子」,
571
+ 正是用户否掉的形态。3px 只够出一条接触缝 —— 看得见槽、看不清槽底,就等于"切出来的"。
572
+ 洞的高也取 37 而不是更深:槽只要在栏顶边界内切出缺口,深了只是浪费。 */
558
573
  .tabbar::before{content:"";position:absolute;z-index:1;pointer-events:none;
559
574
  left:50%;top:0;transform:translateX(-50%);
560
- width:66px;height:33px;
561
- border-radius:0 0 33px 33px;
575
+ width:74px;height:37px;
576
+ border-radius:0 0 37px 37px;
562
577
  background:
563
- radial-gradient(circle 33px at 50% 0,
564
- var(--fab-notch) 29.5px,
565
- var(--fab-notch) 30.5px,
566
- color-mix(in srgb,var(--fab-notch) 35%,transparent) 32px,
567
- transparent 33px),
578
+ radial-gradient(circle 37px at 50% 0,
579
+ var(--fab-notch) 33.5px,
580
+ var(--fab-notch) 34.5px,
581
+ color-mix(in srgb,var(--fab-notch) 35%,transparent) 36px,
582
+ transparent 37px),
568
583
  var(--fab-well)}
569
584
  .tabfab__b{position:absolute;z-index:2;
570
- left:50%;bottom:calc(100% - 30px); /* 圆心钉在栏顶边线上:上露 30 / 下沉 30 */
585
+ left:50%;bottom:calc(100% - 34px); /* 圆心钉在栏顶边线上:上露 34 / 下沉 34 */
571
586
  transform:translateX(-50%);
572
- width:60px;height:60px;
587
+ width:68px;height:68px;
573
588
  display:flex;align-items:center;justify-content:center;
574
589
  color:var(--on-primary-action);
575
590
  background:linear-gradient(178deg,rgba(255,255,255,.14),rgba(255,255,255,0) 60%),var(--primary-action);
@@ -582,25 +597,14 @@ button.setrow{font-family:inherit}
582
597
  .tabfab__b:active{transform:translateX(-50%) translateY(2px);
583
598
  box-shadow:inset 0 -6px 12px -7px rgba(15,23,42,.56), inset 0 2px 3px -2px rgba(255,255,255,.26)}
584
599
  .tabfab__b svg{display:block;stroke-width:2.4;transform:translateY(-1px)}
585
- /* 会话页(2026-10-22 用户:「不要挤压对话框」「tab 跟会话列表一样是浮在上面」):
586
- 底栏浮起后不再占位,composer 只需让出「栏高 + 安全区 + 12px 呼吸」——
587
- 输入框贴着栏上沿 12px。原来 composer 下留 60(= +上探 30 + 呼吸 30),再叠上占位的 68 栏,
588
- 屏幕底到输入框一共 162;现在 74 + 34 + 12 = 120,对话区多出 42px,也再没有那段死区。
589
- 74 必须与 .tabbar 的 min-height 对齐(改栏高要一起改)。
600
+ /* 会话页(2026-10-22 用户:「不要挤压对话框」「两个效果要一致」「圆可以更大」):
601
+ 底栏浮起后不占位,「+」在**两页都凸出 34**(68 直径的一半),所以 composer 只需让出
602
+ 「上探 34 + 呼吸 12 + 栏高 56」—— 输入框落在凸出圆的头顶 12px,不遮不压。
603
+ 原来 composer 下留 60(= 上探 30 + 呼吸 30)再叠占位的 68 栏,屏幕底到输入框 162;
604
+ 现在 56 + 34 + 12 = 102,对话区多出 60px。
605
+ 56 必须与 .tabbar 的 min-height 对齐(改栏高要一起改)。
590
606
  **只叠一次**:safe-area 由这里一次性算进来,别再给 .composer 的基础 padding 加一遍。 */
591
- #conv .composer{padding-bottom:calc(12px + 74px + env(safe-area-inset-bottom))}
592
-
593
- /* 会话页的「+」收进槽里(不上浮):
594
- 输入框离栏只剩 12px,骑在边线上的 + 会顶到输入框下沿;而且这一屏已经有 composer
595
- 左侧的附件「+」,两个浮起的圆会让"哪个是新建"含混。
596
- 只改**高度关系**,不改颜色 / 水平位置:圆仍钉在正中,从"骑在边线上"变成"坐在栏里"。
597
- 直径 60 → 56 是跟着 74 的栏高走的(上下各留 9px),不是"缩小来躲"。 */
598
- body.is-conv .tabbar::before{display:none}
599
- body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-bottom) + 9px);
600
- box-shadow:
601
- inset 0 -6px 11px -6px rgba(15,23,42,.42),
602
- inset 0 2px 2px -2px rgba(255,255,255,.28),
603
- 0 6px 14px -8px rgba(15,23,42,.35)}
607
+ #conv .composer{padding-bottom:calc(12px + 34px + 56px + env(safe-area-inset-bottom))}
604
608
 
605
609
  /* 组件挂载点:占满剩余高度 */
606
610
  #conv{flex:1;min-height:0;display:flex}
@@ -907,6 +911,8 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
907
911
  <div class="history__list">
908
912
  <div class="setcard">
909
913
  <div class="setrow"><span class="setrow__l">设备</span><span class="setrow__v" id="mineDevice">—</span></div>
914
+ <!-- 环境(多连接):显式配置每台桌面的名字 / IP / 令牌,并在这里切换 -->
915
+ <button class="setrow" id="btnEnvPage"><span class="setrow__l">环境</span><span class="setrow__v" id="mineEnv">—</span></button>
910
916
  <div class="setrow"><span class="setrow__l">主题</span>
911
917
  <span class="seg" id="segTheme">
912
918
  <button class="seg__b" data-v="system">系统</button>
@@ -959,19 +965,41 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
959
965
  <!-- ══════════ 我的 → 团队模式(计划 + 7 类子任务)══════════
960
966
  从「我的」那一行点进来。这里配的全都是**设备级偏好**(开关本身按会话,在会话页运行设置里)。
961
967
  **只有一套划分**:计划 + 7 个角色(writer / researcher / reviewer / coder / designer /
962
- director / builder)共 8 行,行内直接写清「这类适合什么」—— 不再并列「6 个类别」那一层
963
- (用户 2026-10-22:「只需要计划+7类子任务即可 描述好每一类适合的规则 规则、引擎都可配置」)。
964
- 每行点开 = 引擎/模型 + 规则:计划配拆解粒度与两个开关,角色配「这类任务适合什么」
965
- (写进 core 的拆解提示词,--team-rule role:<name>=…)。 -->
968
+ director / builder),不再并列「6 个类别」那一层。
969
+ 2026-10-22 用户:「配引擎和配规则要分开配置 不要在 1 个页面」——
970
+ 于是**两张卡、两个入口**:上面「交给谁」点开只有引擎/模型;下面「规则」点开只有规则
971
+ (计划 = 拆解口径,角色 = 这类任务适合什么)。两者互不掺杂。 -->
966
972
  <div class="history" id="teamPage" hidden>
967
973
  <div class="history__top">
968
974
  <button class="topbar__icon" id="btnTeamBack" aria-label="返回"><svg width="20" height="20"><use href="#i-back"/></svg></button>
969
975
  <span class="history__title">团队模式</span>
970
976
  </div>
971
977
  <div class="history__list">
972
- <div class="history__group">计划与 7 类子任务</div>
978
+ <div class="history__group">交给谁(引擎 / 模型)</div>
973
979
  <div class="setcard" id="teamRouteCard"></div>
974
- <div class="history__hint" style="text-align:left">点一行配置引擎与规则。留空 = 跟随主引擎 / 使用内置描述。</div>
980
+ <div class="history__group">规则(只影响计划怎么拆)</div>
981
+ <div class="setcard" id="teamRuleCard"></div>
982
+ <div class="history__hint" style="text-align:left">两张卡分开配:上面只选引擎,下面只改规则。留空 = 跟随主引擎 / 使用内置规则。</div>
983
+ </div>
984
+ </div>
985
+
986
+ <!-- ══════════ 我的 → 环境(多连接:名字 / IP / 令牌)══════════
987
+ 2026-10-22 用户:「环境没有地方配置 ip 和名字啊」。
988
+ 环境不能只靠「连过才记得」:这里显式新建 / 改名 / 改地址 / 删,点一行就编辑。
989
+ 地址填 IP:端口即可(不带 http:// 自动补),令牌可以写在地址的 ?token= 里,
990
+ 也可以单独填下面那栏(显式令牌优先)。 -->
991
+ <div class="history" id="envPage" hidden>
992
+ <div class="history__top">
993
+ <button class="topbar__icon" id="btnEnvBack" aria-label="返回"><svg width="20" height="20"><use href="#i-back"/></svg></button>
994
+ <span class="history__title">环境</span>
995
+ </div>
996
+ <div class="history__list">
997
+ <div class="history__group">已配置的桌面</div>
998
+ <div class="setcard" id="envList"></div>
999
+ <div class="history__actions" style="padding:10px 0 0">
1000
+ <button class="newtask" id="btnEnvAdd" type="button"><svg width="18" height="18"><use href="#i-plus"/></svg><span>新建环境</span></button>
1001
+ </div>
1002
+ <div class="history__hint" style="text-align:left">环境 = 一台跑着 magic-client 的桌面。名字随你起(例如「公司 Mac」),地址填 IP:端口,例如 192.168.1.5:8791。保存后可在这里或任务页标题处切换。</div>
975
1003
  </div>
976
1004
  </div>
977
1005
 
@@ -1124,18 +1152,20 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1124
1152
  「定时任务」那个 tab 已删(2026-10-22 用户:「定时任务tab先删除」)——
1125
1153
  功能本身没删:会话菜单里的「定时任务」仍然能唤起,只是不再占一个底部入口
1126
1154
  (那一页本来也只有一句「还没有这个接口」的说明,占着导航反而误导)。
1127
- 中间那枚「+」不是 tab:它没有 data-tab、不参与 setActive,点了就是新建会话。 -->
1155
+ 中间那枚「+」不是 tab:它没有 data-tab、不参与 setActive,点了就是新建会话。
1156
+ **只有图标、没有文字**(2026-10-22 用户:「底栏只保留图标不需要文字了」):
1157
+ 语义靠 aria-label 承担;图标本身已用"清单 / 文档"的轮廓分家,缩到 25px 仍可辨。 -->
1128
1158
  <nav class="tabbar" id="tabbar" hidden>
1129
- <button class="tab" type="button" data-tab="tasks"><span class="icw"><svg width="23" height="23"><use href="#i-task"/></svg></span><span class="lb">任务</span></button>
1159
+ <button class="tab" type="button" data-tab="tasks" aria-label="任务"><span class="icw"><svg width="25" height="25"><use href="#i-task"/></svg></span></button>
1130
1160
  <!-- 需求 tab 常驻:不做能力位控制(用户 2026-10-22:「移动端永远显示」)。
1131
1161
  老 core 上点进来会由 loadRequirements 的 501 分支给出明确文案,
1132
1162
  不会是一片空白 —— 入口固定比入口忽隐忽现更好理解。 -->
1133
- <button class="tab" type="button" data-tab="reqs"><span class="icw"><svg width="23" height="23"><use href="#i-req"/></svg></span><span class="lb">需求</span></button>
1163
+ <button class="tab" type="button" data-tab="reqs" aria-label="需求"><span class="icw"><svg width="25" height="25"><use href="#i-req"/></svg></span></button>
1134
1164
  <span class="tabfab">
1135
- <button class="tabfab__b" type="button" id="btnFabNew" aria-label="新建会话"><svg width="26" height="26"><use href="#i-plus"/></svg></button>
1165
+ <button class="tabfab__b" type="button" id="btnFabNew" aria-label="新建会话"><svg width="30" height="30"><use href="#i-plus"/></svg></button>
1136
1166
  </span>
1137
- <button class="tab" type="button" data-tab="discover"><span class="icw"><svg width="23" height="23"><use href="#i-compass"/></svg></span><span class="lb">发现</span></button>
1138
- <button class="tab" type="button" data-tab="mine"><span class="icw"><svg width="23" height="23"><use href="#i-user"/></svg></span><span class="lb">我的</span></button>
1167
+ <button class="tab" type="button" data-tab="discover" aria-label="发现"><span class="icw"><svg width="25" height="25"><use href="#i-compass"/></svg></span></button>
1168
+ <button class="tab" type="button" data-tab="mine" aria-label="我的"><span class="icw"><svg width="25" height="25"><use href="#i-user"/></svg></span></button>
1139
1169
  </nav>
1140
1170
  <!-- 第三方库归口 shared/vendor(与内核共用一份):markdown 渲染栈要用 marked + DOMPurify;
1141
1171
  highlight.js 由内核按需加载,地址在下面给(内核自己不知道页面的相对根;
@@ -1186,6 +1216,9 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1186
1216
  var taskSearchInput = document.getElementById('taskSearchInput');
1187
1217
  var mineEl = document.getElementById('mine');
1188
1218
  var mineDevice = document.getElementById('mineDevice');
1219
+ var mineEnv = document.getElementById('mineEnv');
1220
+ var envPageEl = document.getElementById('envPage');
1221
+ var envListEl = document.getElementById('envList');
1189
1222
  var mineVersion = document.getElementById('mineVersion');
1190
1223
  var segTheme = document.getElementById('segTheme');
1191
1224
  var segFont = document.getElementById('segFont');
@@ -1552,13 +1585,19 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1552
1585
  envMenuEl.appendChild(b);
1553
1586
  });
1554
1587
  envMenuEl.appendChild(elSpan('envmenu__sep', ''));
1555
- var add = document.createElement('button');
1556
- add.type = 'button';
1557
- add.className = 'envmenu__add';
1558
- add.appendChild(svgUse('#i-plus', 18));
1559
- add.appendChild(elSpan('', '添加连接'));
1560
- add.onclick = function () { closeEnvMenu(); showLauncher(); };
1561
- envMenuEl.appendChild(add);
1588
+ /* 两个入口必须分开:新建环境 = 手填名字 + IP;扫码/粘贴 = 用配对页给的链接直接连。
1589
+ 以前只有后者,「环境没有地方配置 ip 和名字」(2026-10-22 用户)。 */
1590
+ function menuAct(label, fn) {
1591
+ var b = document.createElement('button');
1592
+ b.type = 'button';
1593
+ b.className = 'envmenu__add';
1594
+ b.appendChild(svgUse('#i-plus', 18));
1595
+ b.appendChild(elSpan('', label));
1596
+ b.onclick = function () { closeEnvMenu(); fn(); };
1597
+ envMenuEl.appendChild(b);
1598
+ }
1599
+ menuAct('环境设置(名字 / IP)', function () { showEnvPage('tasks'); });
1600
+ menuAct('扫码或粘贴连接地址', function () { showLauncher(); });
1562
1601
  document.body.appendChild(envScrimEl);
1563
1602
  document.body.appendChild(envMenuEl);
1564
1603
  var btn = document.getElementById('btnEnvSwitch');
@@ -1569,6 +1608,144 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1569
1608
  btn.classList.add('is-open');
1570
1609
  }
1571
1610
 
1611
+ /* ── 环境管理(我的 → 环境):显式配置名字 / IP / 令牌 ──
1612
+ 2026-10-22 用户:「环境没有地方配置 ip 和名字啊」——
1613
+ 扫码只是「连上并记住」,不是配置入口。这里补上增删改。 */
1614
+ function showEnvPage(from) {
1615
+ hideAll(); envPageEl.hidden = false; tabbarEl.hidden = true;
1616
+ ENV_BACK = from === 'tasks' ? 'tasks' : 'mine';
1617
+ renderEnvPage();
1618
+ }
1619
+ var ENV_BACK = 'mine'; // 环境页的返回目标:从任务页标题进来就回任务页,从「我的」进来就回「我的」
1620
+ function renderEnvPage() {
1621
+ if (!envListEl) return;
1622
+ envListEl.innerHTML = '';
1623
+ var list = loadConns();
1624
+ if (!list.length) {
1625
+ envListEl.appendChild(hEl('div', 'history__hint', '还没有配置环境。点下面的「新建环境」,填名字和 IP 即可。'));
1626
+ return;
1627
+ }
1628
+ var cur = activeConn();
1629
+ list.forEach(function (c) {
1630
+ var b = hEl('button', 'setrow');
1631
+ b.type = 'button';
1632
+ var main = hEl('span', 'setrow__main');
1633
+ main.appendChild(hEl('span', 'setrow__l', connLabel(c) || '未命名'));
1634
+ main.appendChild(hEl('span', 'setrow__sub',
1635
+ (c.host || connHost(c.url)) + (cur && c.id === cur.id ? ' · 当前连接' : '')));
1636
+ b.appendChild(main);
1637
+ b.appendChild(hEl('span', 'setrow__v', '编辑'));
1638
+ b.onclick = function () { openEnvEditor(c); };
1639
+ envListEl.appendChild(b);
1640
+ });
1641
+ }
1642
+ /* conn 为空 = 新建;否则编辑(名字、地址、令牌都能改,也能删)。 */
1643
+ function openEnvEditor(conn) {
1644
+ var isNew = !conn;
1645
+ openHostSheet(isNew ? '新建环境' : '编辑环境', function (body) {
1646
+ var nameEl = hEl('input', 'envfield');
1647
+ nameEl.type = 'text';
1648
+ nameEl.placeholder = '例如 公司 Mac';
1649
+ nameEl.value = (conn && conn.name) || '';
1650
+ var addrEl = hEl('input', 'envfield');
1651
+ addrEl.type = 'text';
1652
+ addrEl.placeholder = '192.168.1.5:8791';
1653
+ addrEl.setAttribute('autocapitalize', 'off');
1654
+ addrEl.setAttribute('autocomplete', 'off');
1655
+ addrEl.setAttribute('spellcheck', 'false');
1656
+ /* 地址栏显示不带 http:// 的短形态(用户脑子里就是 IP:端口);https 原样保留。 */
1657
+ addrEl.value = conn && conn.url ? String(conn.url).replace(/^http:\/\//i, '') : '';
1658
+ var tokEl = hEl('input', 'envfield');
1659
+ tokEl.type = 'text';
1660
+ tokEl.placeholder = '可选,配对令牌';
1661
+ tokEl.setAttribute('autocapitalize', 'off');
1662
+ tokEl.setAttribute('autocomplete', 'off');
1663
+ tokEl.setAttribute('spellcheck', 'false');
1664
+ tokEl.value = (conn && conn.token) || '';
1665
+ body.appendChild(hEl('label', 'envlabel', '名称'));
1666
+ body.appendChild(nameEl);
1667
+ body.appendChild(hEl('label', 'envlabel', '地址(IP:端口)'));
1668
+ body.appendChild(addrEl);
1669
+ body.appendChild(hEl('label', 'envlabel', '令牌'));
1670
+ body.appendChild(tokEl);
1671
+ var err = hEl('div', 'field__err');
1672
+ err.hidden = true;
1673
+ body.appendChild(err);
1674
+ body.appendChild(hEl('div', 'envhint',
1675
+ '地址也可以带 ?token=…;下面这栏单独填时优先。填好保存后可在这里或任务页标题处切换。'));
1676
+ var acts = hEl('div', 'envacts');
1677
+ var saveB = hEl('button', 'btn btn--filled', '保存');
1678
+ var goB = hEl('button', 'btn btn--outlined', '保存并连接');
1679
+ saveB.type = 'button'; goB.type = 'button';
1680
+ acts.appendChild(saveB); acts.appendChild(goB);
1681
+ body.appendChild(acts);
1682
+ var delB = null;
1683
+ if (!isNew) {
1684
+ delB = hEl('button', 'envdel', '删除这个环境');
1685
+ delB.type = 'button';
1686
+ body.appendChild(delB);
1687
+ }
1688
+
1689
+ function collect() {
1690
+ err.hidden = true;
1691
+ var raw = String(addrEl.value || '').trim();
1692
+ var t = parseTarget(raw);
1693
+ if (!t) { err.textContent = '请填地址,例如 192.168.1.5:8791'; err.hidden = false; return null; }
1694
+ var tok = String(tokEl.value || '').trim();
1695
+ var hadParam = /[?&]token=/.test(raw);
1696
+ if (tok) t.token = tok;
1697
+ else if (!hadParam) {
1698
+ /* 令牌栏没动过就沿用旧的;用户主动清空 = 就是要去掉令牌,不能又塞回去。 */
1699
+ t.token = (conn && tokEl.value === (conn.token || '')) ? (conn.token || '') : '';
1700
+ }
1701
+ t.name = String(nameEl.value || '').trim();
1702
+ return t;
1703
+ }
1704
+ function persist(t) {
1705
+ var wasActive = !!(conn && conn.url === BASE && String(conn.token || '') === String(TOKEN || ''));
1706
+ var next = upsertConn(t);
1707
+ /* 改了地址/令牌 = 换了一条记录:旧的那条要删掉,别留一条连不上的僵尸。 */
1708
+ if (conn && conn.id !== next.id) removeConn(conn.id);
1709
+ /* 名称允许清空,也允许改:upsertConn 会沿用旧名,这里按输入框的最终值覆盖。 */
1710
+ var list = loadConns();
1711
+ for (var i = 0; i < list.length; i++) {
1712
+ if (list[i].id !== next.id) continue;
1713
+ list[i].name = t.name;
1714
+ if (!list[i].app && conn && conn.app) list[i].app = conn.app;
1715
+ }
1716
+ saveConns(list);
1717
+ closeHostSheet();
1718
+ if (wasActive && (next.url !== BASE || String(next.token || '') !== String(TOKEN || ''))) {
1719
+ switchEnv(next); // 改的正是当前这台:按新地址/令牌重连
1720
+ return;
1721
+ }
1722
+ renderEnvPage();
1723
+ if (!historyEl.hidden) historyTitle.textContent = envLabel() || '任务';
1724
+ if (!mineEl.hidden && mineEnv) mineEnv.textContent = envLabel() || '—';
1725
+ toast(isNew ? ('已添加「' + (next.name || next.host) + '」') : '已保存');
1726
+ }
1727
+ saveB.onclick = function () { var t = collect(); if (t) persist(t); };
1728
+ goB.onclick = function () {
1729
+ var t = collect();
1730
+ if (!t) return;
1731
+ persist(t);
1732
+ /* persist 内部遇到「改的是当前连接」已经自己重连了;这里只处理新增/改别的环境。 */
1733
+ var c = findConn(connIdOf(t)) || findConn(connIdOf({ url: t.url, token: t.token }));
1734
+ if (c && !(c.url === BASE && String(c.token || '') === String(TOKEN || ''))) switchEnv(c);
1735
+ };
1736
+ if (delB) {
1737
+ delB.onclick = function () {
1738
+ if (!window.confirm('删除环境「' + (connLabel(conn) || conn.host || '') + '」?')) return;
1739
+ removeConn(conn.id);
1740
+ closeHostSheet();
1741
+ renderEnvPage();
1742
+ toast('已删除');
1743
+ };
1744
+ }
1745
+ setTimeout(function () { try { nameEl.focus(); } catch (e) {} }, 60);
1746
+ });
1747
+ }
1748
+
1572
1749
  /* ══════════ 解析 + 校验连接地址 ══════════
1573
1750
  与 ZCode RemoteControlUrlPolicy 同职责:只放行 http(s),解析出 base 与令牌。
1574
1751
  ZCode 生产包写死 zcode.z.ai;magic-agent 是用户自托管(LAN IP 各不相同),
@@ -1596,8 +1773,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1596
1773
  }
1597
1774
 
1598
1775
  /* ══════════ 视图:启动页 → 会话列表 → 会话 ══════════ */
1599
- /* 离开会话页要摘掉 is-conv,否则底栏的「+」会一直保持"收进槽里"的形态 */
1600
- function hideAll() { document.body.classList.remove('is-conv'); launcherEl.hidden = true; historyEl.hidden = true; mineEl.hidden = true; reqsEl.hidden = true; discoverEl.hidden = true; teamPageEl.hidden = true; runSettingsPageEl.hidden = true; pluginsEl.hidden = true; pluginDetailEl.hidden = true; sessionEl.hidden = true; }
1776
+ function hideAll() { launcherEl.hidden = true; historyEl.hidden = true; mineEl.hidden = true; reqsEl.hidden = true; discoverEl.hidden = true; teamPageEl.hidden = true; runSettingsPageEl.hidden = true; pluginsEl.hidden = true; pluginDetailEl.hidden = true; sessionEl.hidden = true; envPageEl.hidden = true; }
1601
1777
  function setActive(name) {
1602
1778
  Array.prototype.forEach.call(tabbarEl.querySelectorAll('.tab'), function (b) {
1603
1779
  b.classList.toggle('is-on', b.getAttribute('data-tab') === name);
@@ -1615,7 +1791,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1615
1791
  historyTitle.textContent = envLabel() || '任务';
1616
1792
  loadSessions();
1617
1793
  }
1618
- function showSession(sub) { hideAll(); document.body.classList.add('is-conv'); sessionEl.hidden = false; tabbarEl.hidden = false; setActive('tasks'); subtitleEl.textContent = (sub != null ? sub : histSubtitle) || ''; }
1794
+ function showSession(sub) { hideAll(); sessionEl.hidden = false; tabbarEl.hidden = false; setActive('tasks'); subtitleEl.textContent = (sub != null ? sub : histSubtitle) || ''; }
1619
1795
  /* 需求页:进页面即拉一次清单(GET /desk/requirements,只读、不调模型)。
1620
1796
  「整理」按钮才触发归纳(POST /desk/requirements/consolidate)——
1621
1797
  两者代价差三个数量级,绝不能混成一个动作。 */
@@ -1627,6 +1803,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
1627
1803
  function showMine() {
1628
1804
  hideAll(); mineEl.hidden = false; tabbarEl.hidden = false; setActive('mine');
1629
1805
  mineDevice.textContent = histSubtitle || '本机';
1806
+ if (mineEnv) mineEnv.textContent = envLabel() || '—';
1630
1807
  var ns = document.getElementById('notifyState');
1631
1808
  if (ns) { var p = notifyPermState(); ns.textContent = p === 'plugin' ? '已接入' : (p === 'granted' ? '已开启' : '未开启'); }
1632
1809
  markSeg(segTheme, PREFS.theme); markSeg(segFont, PREFS.font); markSeg(segMode, TASK_MODE);
@@ -4080,13 +4257,14 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4080
4257
  var sum = document.getElementById('teamSummary');
4081
4258
  if (sum) sum.textContent = custom ? (custom + ' 项已单独指定') : '跟随主引擎';
4082
4259
  }
4083
- /* 团队模式子页面 = 计划行 + 7 个角色行,全部进同一个 #teamRouteCard(只有一套路由配置)。
4084
- 每行常显「这类适合什么」(角色 = 规则 / 计划 = 拆解口径),右侧是引擎 ——
4085
- 用户要的是「描述好每一类适合的规则」,所以描述不藏进详情页。
4086
- 签名含行文案与引擎:变了才重建,避免 5s 轮询时闪。 */
4260
+ /* 团队模式子页面 = **两张卡**(用户 2026-10-22:「配引擎和配规则要分开配置 不要在 1 个页面」):
4261
+ #teamRouteCard「交给谁」= 计划 + 7 个角色的引擎/模型,点开只有引擎;
4262
+ #teamRuleCard「规则」= 计划的拆解口径 + 7 个角色的「适合什么」,点开只有规则。
4263
+ 签名含两侧文案与引擎:变了才重建,避免 5s 轮询时闪。 */
4087
4264
  function paintTeamRoles(roles) {
4088
- var card = document.getElementById('teamRouteCard');
4089
- if (!card) return;
4265
+ var routeCard = document.getElementById('teamRouteCard');
4266
+ var ruleCard = document.getElementById('teamRuleCard');
4267
+ if (!routeCard || !ruleCard) return;
4090
4268
  var show = teamSupported();
4091
4269
  var plan = (TEAM.routes || {}).plan || {};
4092
4270
  var planSub = '拆解:' + ({ coarse: '少', standard: '标准', fine: '细' }[TEAM.split] || '标准')
@@ -4099,32 +4277,51 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4099
4277
  }).join(',');
4100
4278
  if (sig === TEAM_ROLE_SIG) return;
4101
4279
  TEAM_ROLE_SIG = sig;
4102
- card.innerHTML = '';
4280
+ routeCard.innerHTML = '';
4281
+ ruleCard.innerHTML = '';
4103
4282
  if (!show) return;
4104
- addTeamRow(card, 'plan', '计划任务', planSub);
4283
+ /* 上卡:只讲「交给谁」,右侧是引擎;不展示规则描述。 */
4284
+ addTeamRouteRow(routeCard, 'plan', '计划任务');
4285
+ roles.forEach(function (r) {
4286
+ addTeamRouteRow(routeCard, 'role:' + r.name, TEAM_ROLE_LABELS[r.name] || r.name);
4287
+ });
4288
+ /* 下卡:只讲「规则」——计划是拆解口径,角色是「这类适合什么」。 */
4289
+ addTeamRuleRow(ruleCard, 'plan', '计划任务规则', planSub);
4105
4290
  roles.forEach(function (r) {
4106
- addTeamRow(card, 'role:' + r.name, TEAM_ROLE_LABELS[r.name] || r.name, teamRoleDuty(r));
4291
+ var label = (TEAM_ROLE_LABELS[r.name] || r.name) + '规则';
4292
+ addTeamRuleRow(ruleCard, 'role:' + r.name, label, teamRoleDuty(r));
4107
4293
  });
4108
4294
  }
4109
- /* 一行:名字 + 规则描述 + 右侧引擎。点开 = 引擎与规则(见 openTeamRoutePicker)。 */
4110
- function addTeamRow(card, key, label, sub) {
4295
+ /* 上卡的一行:名字 + 右侧引擎。点开 = 只选引擎/模型。 */
4296
+ function addTeamRouteRow(card, key, label) {
4111
4297
  var rt = (TEAM.routes || {})[key] || {};
4112
4298
  var b = hEl('button', 'setrow');
4113
4299
  b.type = 'button';
4114
4300
  var main = hEl('span', 'setrow__main');
4115
4301
  main.appendChild(hEl('span', 'setrow__l', label));
4116
- if (sub) main.appendChild(hEl('span', 'setrow__sub', sub));
4117
4302
  b.appendChild(main);
4118
4303
  b.appendChild(hEl('span', 'setrow__v',
4119
4304
  rt.engine ? (rt.engine + (rt.model ? ' · ' + qaModelText(rt.model) : '')) : '跟随主引擎'));
4120
4305
  b.appendChild(hEl('span', 'setrow__chev', '›'));
4121
4306
  b.onclick = function () {
4122
- openTeamRoutePicker(key,
4123
- String(key).indexOf('role:') === 0 ? (label + ':适合做什么、交给谁') : '计划任务:怎么拆、交给谁',
4307
+ openTeamEnginePicker(key,
4308
+ String(key).indexOf('role:') === 0 ? (label + '交给谁') : '计划任务交给谁',
4124
4309
  teamRoleKindOf(key));
4125
4310
  };
4126
4311
  card.appendChild(b);
4127
4312
  }
4313
+ /* 下卡的一行:名字 + 当前规则。点开 = 只改规则(不含引擎)。 */
4314
+ function addTeamRuleRow(card, key, label, sub) {
4315
+ var b = hEl('button', 'setrow');
4316
+ b.type = 'button';
4317
+ var main = hEl('span', 'setrow__main');
4318
+ main.appendChild(hEl('span', 'setrow__l', label));
4319
+ if (sub) main.appendChild(hEl('span', 'setrow__sub', sub));
4320
+ b.appendChild(main);
4321
+ b.appendChild(hEl('span', 'setrow__chev', '›'));
4322
+ b.onclick = function () { openTeamRuleSheet(key, label); };
4323
+ card.appendChild(b);
4324
+ }
4128
4325
  function teamRoleKindOf(key) {
4129
4326
  if (String(key).indexOf('role:') !== 0) return 'plan';
4130
4327
  var n = String(key).slice(5);
@@ -4136,26 +4333,19 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4136
4333
  var ov = (TEAM.rules || {})['role:' + r.name];
4137
4334
  return (ov && String(ov).trim()) || r.duty || '';
4138
4335
  }
4139
- /* 点一行 → 抽屉:先「规则」,再「引擎 · 模型」,与「默认引擎」那套同一个交互。
4140
- 计划行:拆解粒度 + 简单代码 / 自动补任务(--team-split / --team-split-code /
4141
- --team-adaptive);角色行:「这类任务适合什么」(--team-rule role:<name>=…)。
4142
- 首项 = 不单独指定 / 跟随主引擎。
4336
+ /* 上卡点一行 → 抽屉:**只选引擎 · 模型**(与「默认引擎」那套同一个交互)。
4337
+ 规则不在这里 —— 见下卡与 openTeamRuleSheet(用户 2026-10-22:「配引擎和配规则要分开配置」)。
4338
+ 首项 = 跟随主引擎。
4143
4339
  2026-10-21 口径(用户:「图像视频不是说需要生成 是擅长做 比如有一个带了视频 skill 的引擎」):
4144
4340
  这里问的是「这类活谁擅长」,所以不写「交给 core 自动路由」—— 说「自动」会让人以为
4145
4341
  不配也能跑,而实际语义是「跟主引擎一致」。 */
4146
- function openTeamRoutePicker(key, title, kind) {
4342
+ function openTeamEnginePicker(key, title, kind) {
4147
4343
  var isRole = String(key).indexOf('role:') === 0;
4148
4344
  if (!kind) kind = key; // 计划键时 kind 就是键本身
4149
4345
  openHostSheet(title, function (body) {
4150
4346
  var hint = hEl('div', 'history__hint', '正在探测引擎与模型…');
4151
4347
  hint.style.textAlign = 'left';
4152
4348
  body.appendChild(hint);
4153
- /* 规则控件放在引擎清单**之前**:规则是这一行的主内容,引擎是「交给谁」。
4154
- 它们与探测无关,引擎清单拉失败也照样能改。分区标题必须显式给出 ——
4155
- 用户 2026-10-22:「入口在哪里 不能配置规则」:控件在,但没标题就读不出这里是配置区。 */
4156
- body.appendChild(hEl('div', 'pick__cap pick__cap--team', '规则'));
4157
- if (isRole) body.appendChild(buildTeamRuleEditor(key));
4158
- else if (kind === 'plan') body.appendChild(buildTeamSplitControls());
4159
4349
  fetchEngines().then(function (list) {
4160
4350
  if (hint.parentNode) hint.remove();
4161
4351
  var avail = list.filter(function (x) { return x.ok; });
@@ -4173,19 +4363,13 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4173
4363
  不是给 core 的配置(core 只认 engine / model)。
4174
4364
  ⚠️ 必须建在 paint() **外面**:paint 每次切引擎都会重画两列,放里面会一行行堆起来
4175
4365
  (2026-10-21 实测:点两次引擎,抽屉底部就多出三段同样的说明)。 */
4176
- var tipText;
4177
- if (isRole) {
4178
- var info = teamRoleList().filter(function (r) { return 'role:' + r.name === key; })[0];
4179
- tipText = '这个角色内置的职责:' + ((info && info.duty) || '—')
4180
- + '。上面写的规则会替掉它,进计划提示词;这里留空 = 跟随主引擎。';
4181
- } else {
4182
- tipText = '计划阶段负责把任务拆成子任务,交给更擅长规划、上下文更长的引擎更稳。';
4183
- }
4184
- body.appendChild(hEl('div', 'pick__cap pick__cap--team', '交给谁'));
4366
+ var tipText = isRole
4367
+ ? '只选这一类活交给谁;「这类任务适合什么」在团队模式页的「规则」卡里改。'
4368
+ : '计划阶段负责把任务拆成子任务,交给更擅长规划、上下文更长的引擎更稳;拆解规则在「规则」卡里改。';
4185
4369
  var tip = hEl('div', 'history__hint', tipText);
4186
4370
  tip.style.textAlign = 'left';
4187
4371
  body.appendChild(tip);
4188
- var wrap = hEl('div', 'pick__cols pick__cols--team');
4372
+ var wrap = hEl('div', 'pick__cols');
4189
4373
  var left = hEl('div', 'pick__col');
4190
4374
  var right = hEl('div', 'pick__col pick__col--r');
4191
4375
  wrap.appendChild(left); wrap.appendChild(right); body.appendChild(wrap);
@@ -4230,6 +4414,15 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4230
4414
  });
4231
4415
  });
4232
4416
  }
4417
+ /* 下卡点一行 → 抽屉:**只改规则**(不含引擎)。计划 = 拆解粒度与两个开关;
4418
+ 角色 = 「这类任务适合什么」。与上卡的引擎选择互不掺杂(用户 2026-10-22)。 */
4419
+ function openTeamRuleSheet(key, label) {
4420
+ var isRole = String(key).indexOf('role:') === 0;
4421
+ openHostSheet(label, function (body) {
4422
+ if (isRole) body.appendChild(buildTeamRuleEditor(key));
4423
+ else body.appendChild(buildTeamSplitControls());
4424
+ });
4425
+ }
4233
4426
  /* 角色行的「规则」:一句话写清这类任务适合什么。**默认值直接填内置职责**(用户
4234
4427
  2026-10-22:「也没看到默认规则啊」—— placeholder 在手机上几乎看不见),保存后覆盖它
4235
4428
  (core 的 --team-rule role:<name>=…);改回内置值等于没改,不落覆盖。 */
@@ -4461,7 +4654,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4461
4654
 
4462
4655
  avail.forEach(function (en) {
4463
4656
  var n = (en.models || []).length;
4464
- /* is-on = 已保存的默认(浅蓝实底);is-cur = 右列此刻在浏览谁(淡底)——
4657
+ /* is-on = 已保存的默认(浅蓝实底);is-cur = 右列此刻在浏览谁(左侧细条,无底色)——
4465
4658
  两者分开,默认是「自动」时不会出现两个一样的选中块。 */
4466
4659
  var b = hEl('button', 'pick__citem'
4467
4660
  + (RUN_PREF.engine === en.engine ? ' is-on' : '')
@@ -4599,6 +4792,10 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4599
4792
  归属只在用户显式选空间时确定,不能让上一次的 WS 悄悄粘过来。 */
4600
4793
  on('btnFabNew', function () { newConversation(); });
4601
4794
  on('btnMineBack', function () { showHistory(); });
4795
+ /* 我的 → 环境(多连接):列表(点行编辑)+ 新建;返回回「我的」。 */
4796
+ on('btnEnvPage', function () { showEnvPage('mine'); });
4797
+ on('btnEnvBack', function () { if (ENV_BACK === 'tasks') showHistory(); else showMine(); });
4798
+ on('btnEnvAdd', function () { openEnvEditor(null); });
4602
4799
  on('dscBrowser', function () { openBrowser(HOME_URL); });
4603
4800
  on('dscApps', function () { openComingSheet('应用', '暂无可用应用。桌面端发布的应用会出现在这里。'); });
4604
4801
  on('dscPlugins', function () { showPlugins(); });
@@ -4617,7 +4814,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
4617
4814
  on('btnDefaultEngine', openDefaultEnginePicker);
4618
4815
  on('btnDefaultModel', openDefaultEnginePicker);
4619
4816
  /* 团队模式配置(「我的」→ 子页面):计划 + 7 个角色,行由 paintTeamRoles 动态渲染,
4620
- 行的点击在渲染时绑定(见 addTeamRow / openTeamRoutePicker),这里只留入口与返回。 */
4817
+ 行的点击在渲染时绑定(见 addTeamRouteRow / addTeamRuleRow),这里只留入口与返回。 */
4621
4818
  on('btnTeamPage', showTeamPage);
4622
4819
  on('btnTeamBack', showMine);
4623
4820
  on('btnLogout', disconnect);
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.110",
3
+ "version": "1.0.111",
4
4
  "description": "专业的 agent CLI 代理工具 - 统一 claude / codebuddy / trae / llm / codex / openclaw 引擎",
5
5
  "license": "MIT",
6
6
  "bin": {