magic-agent 1.0.108 → 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.
- package/client-ui-mobile/index.html +308 -47
- package/internal/team/planner.go +2 -1
- package/internal/team/role.go +2 -2
- package/npm/dist/darwin-arm64/magic-agent +0 -0
- package/npm/dist/darwin-arm64/magic-client +0 -0
- package/npm/dist/darwin-x64/magic-agent +0 -0
- package/npm/dist/darwin-x64/magic-client +0 -0
- package/npm/dist/linux-arm64/magic-agent +0 -0
- package/npm/dist/linux-arm64/magic-client +0 -0
- package/npm/dist/linux-x64/magic-agent +0 -0
- package/npm/dist/linux-x64/magic-client +0 -0
- package/npm/dist/win32-x64/magic-agent.exe +0 -0
- package/npm/dist/win32-x64/magic-client.exe +0 -0
- package/package.json +1 -1
|
@@ -191,6 +191,11 @@ textarea{font:inherit}
|
|
|
191
191
|
padding:calc(env(safe-area-inset-top) + 20px) 20px calc(20px + env(safe-area-inset-bottom))}
|
|
192
192
|
/* headlineSmall ≈ 24sp */
|
|
193
193
|
.launcher__title{font-size:24px;font-weight:400;line-height:32px;margin:0;color:var(--ink)}
|
|
194
|
+
/* 从「切换环境 → 添加连接」进来时的返回键:已连着桌面就别把用户困在启动页 */
|
|
195
|
+
.launcher__back{position:absolute;top:calc(env(safe-area-inset-top) + 10px);left:12px;
|
|
196
|
+
display:flex;align-items:center;gap:2px;height:40px;padding:0 10px;border-radius:var(--r-full);
|
|
197
|
+
color:var(--primary-action);font-size:15px;font-weight:500}
|
|
198
|
+
.launcher__back:active{background:var(--subtle)}
|
|
194
199
|
/* 说明 bodyMedium(onSurfaceVariant) */
|
|
195
200
|
.launcher__desc{margin:8px 0 0;font-size:14px;line-height:20px;color:var(--muted)}
|
|
196
201
|
/* OutlinedTextField:1dp 描边 · 4dp 圆角(Material3 默认) */
|
|
@@ -208,15 +213,19 @@ textarea{font:inherit}
|
|
|
208
213
|
.btn--filled{margin-top:12px;background:var(--primary-action);color:var(--on-primary-action)}
|
|
209
214
|
.btn--outlined{margin-top:8px;border:1px solid var(--border);color:var(--primary-action)}
|
|
210
215
|
.btn:active{opacity:.9}
|
|
211
|
-
/*
|
|
216
|
+
/* 已保存的连接(2026-10-22:由「最近连接」升级而来,可保存多台桌面并逐个删除) */
|
|
212
217
|
.recent{margin-top:20px}
|
|
213
218
|
.recent__head{display:flex;align-items:center}
|
|
214
219
|
.recent__title{flex:1;font-size:14px;font-weight:600;color:var(--ink)}
|
|
215
220
|
.recent__clear{color:var(--primary-action);font-size:13px;font-weight:600;padding:8px 12px}
|
|
216
|
-
.
|
|
221
|
+
.recent__row{display:flex;align-items:stretch;gap:8px;margin-top:8px}
|
|
222
|
+
.recent__item{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0;text-align:left;
|
|
217
223
|
border:1px solid var(--border);border-radius:20px;padding:10px 16px}
|
|
218
224
|
.recent__t{font-size:14px;line-height:20px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
219
225
|
.recent__s{font-size:12px;line-height:16px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
226
|
+
.recent__del{flex:0 0 auto;width:44px;border-radius:20px;border:1px solid var(--border);
|
|
227
|
+
display:flex;align-items:center;justify-content:center;color:var(--muted)}
|
|
228
|
+
.recent__del:active{background:var(--subtle);color:var(--error-text)}
|
|
220
229
|
|
|
221
230
|
/* ══════════ 会话页 RemoteControlWebViewScreen + TopBar(:517-644)══════════ */
|
|
222
231
|
.session{flex:1;min-height:0;display:flex;flex-direction:column}
|
|
@@ -244,6 +253,13 @@ textarea{font:inherit}
|
|
|
244
253
|
padding:calc(env(safe-area-inset-top) + 10px) 16px 8px}
|
|
245
254
|
.history__title{flex:1;min-width:0;font-size:22px;font-weight:700;color:var(--ink);
|
|
246
255
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
256
|
+
/* 环境切换(2026-10-22 用户:「移动端要支持环境切换 可以有多个连接」):
|
|
257
|
+
任务页标题本身就是切换入口 —— 点标题弹出已保存的环境,选中即切过去。
|
|
258
|
+
标题装的是**当前连接**的名字(扫码时带的 name > 桌面 health 报的 app > host)。 */
|
|
259
|
+
.history__title--btn{display:flex;align-items:center;gap:6px;text-align:left}
|
|
260
|
+
.history__title--btn>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
261
|
+
.history__title-chev{flex:0 0 auto;color:var(--muted);transition:transform .15s}
|
|
262
|
+
.history__title--btn.is-open .history__title-chev{transform:rotate(180deg)}
|
|
247
263
|
.history__disconnect{color:var(--primary-action);font-size:14px;font-weight:500;padding:8px 10px}
|
|
248
264
|
.history__disconnect:disabled{color:var(--muted);opacity:.6}
|
|
249
265
|
.history__list{flex:1;min-height:0;overflow-y:auto;padding:4px 10px calc(96px + env(safe-area-inset-bottom))}
|
|
@@ -369,6 +385,31 @@ textarea{font:inherit}
|
|
|
369
385
|
.rowmenu__item:active{background:var(--subtle)}
|
|
370
386
|
.rowmenu__item--danger{color:var(--error-text)}
|
|
371
387
|
.rowmenu__l{flex:1;min-width:0}
|
|
388
|
+
/* 环境切换菜单(挂在任务页标题下):已保存的连接 + 「添加连接」。
|
|
389
|
+
与会话行长按菜单同一套浮层范式(scrim 挡住下层,点空白即关)。 */
|
|
390
|
+
.envmenu{position:fixed;z-index:1201;width:min(320px,calc(100vw - 24px));
|
|
391
|
+
max-height:min(70vh,560px);overflow-y:auto;
|
|
392
|
+
background:var(--raised);border:1px solid var(--border);border-radius:18px;
|
|
393
|
+
box-shadow:0 18px 44px -14px var(--app-shadow);padding:6px}
|
|
394
|
+
.envmenu__scrim{position:fixed;inset:0;z-index:1200}
|
|
395
|
+
.envmenu__head{display:block;padding:10px 12px 6px;font-size:12px;font-weight:700;letter-spacing:.04em;color:var(--muted)}
|
|
396
|
+
.envmenu__item{display:flex;align-items:center;gap:12px;width:100%;padding:11px 12px;
|
|
397
|
+
border-radius:12px;text-align:left}
|
|
398
|
+
.envmenu__item:active{background:var(--subtle)}
|
|
399
|
+
.envmenu__ico{flex:0 0 auto;width:38px;height:38px;border-radius:50%;background:var(--subtle);
|
|
400
|
+
display:flex;align-items:center;justify-content:center;color:var(--ink)}
|
|
401
|
+
.envmenu__col{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
402
|
+
.envmenu__t{display:flex;align-items:center;gap:8px;min-width:0;font-size:15px;font-weight:600;color:var(--ink)}
|
|
403
|
+
.envmenu__name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
404
|
+
.envmenu__badge{flex:0 0 auto;padding:1px 8px;border-radius:var(--r-full);
|
|
405
|
+
font-size:11px;font-weight:600;line-height:17px;color:var(--primary-action);background:var(--subtle)}
|
|
406
|
+
.envmenu__s{font-size:12px;line-height:16px;color:var(--muted);
|
|
407
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
408
|
+
.envmenu__check{flex:0 0 auto;color:var(--primary-action)}
|
|
409
|
+
.envmenu__sep{display:block;height:1px;margin:6px 4px;background:var(--border)}
|
|
410
|
+
.envmenu__add{display:flex;align-items:center;gap:10px;width:100%;padding:12px;border-radius:12px;
|
|
411
|
+
font-size:15px;font-weight:500;color:var(--primary-action);text-align:left}
|
|
412
|
+
.envmenu__add:active{background:var(--subtle)}
|
|
372
413
|
/* toast(没有通知能力时的兜底提示) */
|
|
373
414
|
.toast{position:fixed;left:50%;bottom:calc(92px + env(safe-area-inset-bottom));transform:translateX(-50%);
|
|
374
415
|
max-width:82vw;padding:10px 14px;border-radius:12px;background:var(--raised);color:var(--ink);
|
|
@@ -432,6 +473,10 @@ button.setrow{font-family:inherit}
|
|
|
432
473
|
font:13px/1.55 -apple-system,BlinkMacSystemFont,"PingFang SC",sans-serif;-webkit-appearance:none}
|
|
433
474
|
.teamedit::placeholder{color:var(--faint)}
|
|
434
475
|
.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)}
|
|
478
|
+
.setrow__chev{flex:0 0 auto;color:var(--faint);font-size:16px;line-height:1}
|
|
479
|
+
.pick__cols--team{height:min(36vh,36dvh,300px)}
|
|
435
480
|
.seg{display:inline-flex;background:var(--subtle);border-radius:var(--r-full);padding:2px;gap:2px}
|
|
436
481
|
.seg__b{height:28px;padding:0 12px;border-radius:var(--r-full);font-size:13px;color:var(--muted)}
|
|
437
482
|
.seg__b.is-on{background:var(--raised);color:var(--ink);font-weight:600}
|
|
@@ -799,6 +844,9 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
799
844
|
|
|
800
845
|
<!-- ══════════ 启动页 ══════════ -->
|
|
801
846
|
<div class="launcher" id="launcher" hidden>
|
|
847
|
+
<button class="launcher__back" id="btnLauncherBack" type="button" hidden>
|
|
848
|
+
<svg width="18" height="18"><use href="#i-back"/></svg><span>返回</span>
|
|
849
|
+
</button>
|
|
802
850
|
<h1 class="launcher__title">magic-agent · 远程控制</h1>
|
|
803
851
|
<p class="launcher__desc">打开桌面配对页里的连接地址,远程继续你的会话。</p>
|
|
804
852
|
|
|
@@ -816,7 +864,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
816
864
|
|
|
817
865
|
<div class="recent" id="recent" hidden>
|
|
818
866
|
<div class="recent__head">
|
|
819
|
-
<span class="recent__title"
|
|
867
|
+
<span class="recent__title">已保存的连接</span>
|
|
820
868
|
<button class="recent__clear" id="btnClear">清空</button>
|
|
821
869
|
</div>
|
|
822
870
|
<div id="recentList"></div>
|
|
@@ -826,7 +874,11 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
826
874
|
<!-- ══════════ 任务列表(页头右侧:搜索 + 我的)══════════ -->
|
|
827
875
|
<div class="history" id="history" hidden>
|
|
828
876
|
<div class="history__top">
|
|
829
|
-
|
|
877
|
+
<!-- 标题即环境切换入口:点开列出已保存的多台桌面(2026-10-22) -->
|
|
878
|
+
<button class="history__title history__title--btn" type="button" id="btnEnvSwitch" aria-label="切换环境">
|
|
879
|
+
<span id="historyTitle">任务</span>
|
|
880
|
+
<svg class="history__title-chev" width="16" height="16"><use href="#i-chev-d"/></svg>
|
|
881
|
+
</button>
|
|
830
882
|
<span class="tasks__tools">
|
|
831
883
|
<button class="topbar__icon" type="button" id="btnTaskSearch" aria-label="搜索任务"><svg width="21" height="21"><use href="#i-search"/></svg></button>
|
|
832
884
|
<button class="topbar__icon" type="button" id="btnTaskMe" aria-label="我的"><svg width="21" height="21"><use href="#i-user"/></svg></button>
|
|
@@ -1199,7 +1251,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
1199
1251
|
老 core 没有这个字段 → 空 → 回落到下面这份内置表(与 internal/team 一致)。 */
|
|
1200
1252
|
var TEAM_ROLE_LABELS = {
|
|
1201
1253
|
writer: '写作', researcher: '调研', reviewer: '评审', coder: '编码',
|
|
1202
|
-
designer: '视觉', director: '影像', builder: '
|
|
1254
|
+
designer: '视觉', director: '影像', builder: '游戏'
|
|
1203
1255
|
};
|
|
1204
1256
|
var TEAM_ROLE_BUILTIN = [
|
|
1205
1257
|
{ name: 'writer', kind: 'text', duty: '把材料写成结构清晰、可直接交付的文档' },
|
|
@@ -1208,7 +1260,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
1208
1260
|
{ name: 'coder', kind: 'code', duty: '把复杂代码改动落到可构建、可测试的文件上' },
|
|
1209
1261
|
{ name: 'designer', kind: 'image', duty: '产出可直接使用的画面资产' },
|
|
1210
1262
|
{ name: 'director', kind: 'video', duty: '把脚本 / 分镜变成可播放的影像' },
|
|
1211
|
-
{ name: 'builder', kind: 'game', duty: '
|
|
1263
|
+
{ name: 'builder', kind: 'game', duty: '游戏内玩法 / 系统开发,交付能跑起来、可复现启动的工程' }
|
|
1212
1264
|
];
|
|
1213
1265
|
var TEAM_ROLES = []; // core 上报的角色清单;空 = 老 core(或还没连上)→ 用内置表
|
|
1214
1266
|
function teamRoleList() { return TEAM_ROLES.length ? TEAM_ROLES : TEAM_ROLE_BUILTIN; }
|
|
@@ -1300,47 +1352,221 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
1300
1352
|
saveTeamPref();
|
|
1301
1353
|
}
|
|
1302
1354
|
|
|
1303
|
-
/* ══════════
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1355
|
+
/* ══════════ 环境(多连接)══════════
|
|
1356
|
+
2026-10-22 用户:「移动端要支持环境切换 可以有多个连接」。
|
|
1357
|
+
一台手机可以记住多台桌面(家里 / 公司 / 另一台机器),任务页标题就是切换入口。
|
|
1358
|
+
键 = 地址 + 令牌:令牌是 magic-client 落盘复用、跨重启不变的机器身份(与排队队列的
|
|
1359
|
+
devKey 同一口径),地址会随 IP / 热点变化,所以不能只用地址。 */
|
|
1360
|
+
var CONN_KEY = 'magic.remote.conns.v1', CONN_MAX = 8;
|
|
1361
|
+
var LS_KEY = 'magic.remote.recent.v1'; // 旧版「最近连接」:只在首次升级时搬一次
|
|
1362
|
+
var ACTIVE_ID = '';
|
|
1363
|
+
var CONN_SEQ = 0; // 每连一次 +1:上一台桌面迟到的响应不许画到这一台上面
|
|
1364
|
+
|
|
1365
|
+
function connHost(url) {
|
|
1366
|
+
try { return new URL(url).host; }
|
|
1367
|
+
catch (e) { return String(url || '').replace(/^https?:\/\//, '').replace(/\/.*$/, ''); }
|
|
1368
|
+
}
|
|
1369
|
+
function connIdOf(t) { return String(t.url || '') + '|' + String(t.token || ''); }
|
|
1370
|
+
function loadConns() {
|
|
1371
|
+
var list;
|
|
1372
|
+
try { list = JSON.parse(localStorage.getItem(CONN_KEY) || '[]'); } catch (e) { list = []; }
|
|
1373
|
+
if (!Array.isArray(list)) list = [];
|
|
1374
|
+
list = list.filter(function (c) { return c && c.url; });
|
|
1375
|
+
if (!list.length) {
|
|
1376
|
+
/* 老版本只记最近 5 条连接:升级后并过来,用户不必重新扫一次码。 */
|
|
1377
|
+
try {
|
|
1378
|
+
var old = JSON.parse(localStorage.getItem(LS_KEY) || '[]');
|
|
1379
|
+
if (Array.isArray(old) && old.length) {
|
|
1380
|
+
list = old.filter(function (c) { return c && c.url; }).map(function (c) {
|
|
1381
|
+
return { id: connIdOf(c), url: c.url, token: c.token || '', name: c.name || '',
|
|
1382
|
+
host: connHost(c.url), app: '', at: c.at || 0 };
|
|
1383
|
+
});
|
|
1384
|
+
saveConns(list);
|
|
1385
|
+
/* 搬完就删旧键:否则用户「清空」后新列表为空,下次又会被旧记录搬回来。 */
|
|
1386
|
+
try { localStorage.removeItem(LS_KEY); } catch (e) {}
|
|
1387
|
+
}
|
|
1388
|
+
} catch (e) {}
|
|
1389
|
+
}
|
|
1390
|
+
return list;
|
|
1311
1391
|
}
|
|
1312
|
-
function
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1392
|
+
function saveConns(list) {
|
|
1393
|
+
try { localStorage.setItem(CONN_KEY, JSON.stringify((list || []).slice(0, CONN_MAX))); } catch (e) {}
|
|
1394
|
+
}
|
|
1395
|
+
function findConn(id) {
|
|
1396
|
+
var list = loadConns();
|
|
1397
|
+
for (var i = 0; i < list.length; i++) if (list[i].id === id) return list[i];
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
/* 记一条连接(连过就记住):扫码带的 name 与 health 报的 app 都比地址更适合当名字。 */
|
|
1401
|
+
function upsertConn(t) {
|
|
1402
|
+
var id = connIdOf(t);
|
|
1403
|
+
var old = findConn(id) || {};
|
|
1404
|
+
var list = loadConns().filter(function (c) { return c.id !== id; });
|
|
1405
|
+
var next = {
|
|
1406
|
+
id: id, url: t.url || '', token: t.token || '',
|
|
1407
|
+
name: (t.name != null && t.name !== '') ? t.name : (old.name || ''),
|
|
1408
|
+
host: connHost(t.url), app: old.app || '', at: Date.now()
|
|
1409
|
+
};
|
|
1410
|
+
list.unshift(next);
|
|
1411
|
+
saveConns(list);
|
|
1412
|
+
return next;
|
|
1413
|
+
}
|
|
1414
|
+
function removeConn(id) { saveConns(loadConns().filter(function (c) { return c.id !== id; })); }
|
|
1415
|
+
function clearConns() {
|
|
1416
|
+
saveConns([]);
|
|
1417
|
+
try { localStorage.removeItem(LS_KEY); } catch (e) {}
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/* 当前连接:先按地址 + 令牌找;App 壳注入的连接还没落盘时给一条临时记录,
|
|
1421
|
+
这样标题与菜单里仍然看得见「现在连的是谁」。 */
|
|
1422
|
+
function activeConn() {
|
|
1423
|
+
var list = loadConns(), i;
|
|
1424
|
+
for (i = 0; i < list.length; i++) {
|
|
1425
|
+
if (list[i].url === BASE && String(list[i].token || '') === String(TOKEN || '')) return list[i];
|
|
1426
|
+
}
|
|
1427
|
+
for (i = 0; i < list.length; i++) if (ACTIVE_ID && list[i].id === ACTIVE_ID) return list[i];
|
|
1428
|
+
if (BASE || TOKEN) {
|
|
1429
|
+
return { id: connIdOf({ url: BASE, token: TOKEN }), url: BASE, token: TOKEN, name: '', host: connHost(BASE), app: '' };
|
|
1430
|
+
}
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
/* 环境显示名:扫码带的 name > 桌面 health 报的 app > host。 */
|
|
1434
|
+
function connLabel(c) {
|
|
1435
|
+
if (!c) return '';
|
|
1436
|
+
return c.name || c.app || c.host || connHost(c.url);
|
|
1437
|
+
}
|
|
1438
|
+
function envLabel() { return connLabel(activeConn()) || histSubtitle || ''; }
|
|
1439
|
+
|
|
1440
|
+
/* 桌面 health 回来的应用名(app)补进当前连接:它比 host 更像「这台设备是谁」。 */
|
|
1441
|
+
function noteActiveMeta(meta) {
|
|
1442
|
+
var c = activeConn();
|
|
1443
|
+
if (!c || !c.id) return;
|
|
1444
|
+
var list = loadConns(), changed = false;
|
|
1445
|
+
for (var i = 0; i < list.length; i++) {
|
|
1446
|
+
if (list[i].id !== c.id) continue;
|
|
1447
|
+
if (meta.app && list[i].app !== meta.app) { list[i].app = meta.app; changed = true; }
|
|
1448
|
+
if (!list[i].host) { list[i].host = connHost(list[i].url); changed = true; }
|
|
1449
|
+
}
|
|
1450
|
+
if (!changed) return;
|
|
1451
|
+
saveConns(list);
|
|
1452
|
+
if (!historyEl.hidden) historyTitle.textContent = envLabel() || '任务';
|
|
1317
1453
|
}
|
|
1318
|
-
function clearRecent() { saveRecent([]); }
|
|
1319
1454
|
|
|
1320
1455
|
function fmtTime(ms) {
|
|
1321
1456
|
try { return new Date(ms).toLocaleString(); } catch (e) { return ''; }
|
|
1322
1457
|
}
|
|
1323
1458
|
function renderRecent() {
|
|
1324
|
-
var list =
|
|
1459
|
+
var list = loadConns();
|
|
1325
1460
|
recentListEl.innerHTML = '';
|
|
1326
1461
|
if (!list.length) { recentEl.hidden = true; return; }
|
|
1327
1462
|
recentEl.hidden = false;
|
|
1328
1463
|
list.forEach(function (item) {
|
|
1329
|
-
var
|
|
1330
|
-
|
|
1464
|
+
var row = document.createElement('div');
|
|
1465
|
+
row.className = 'recent__row';
|
|
1331
1466
|
var b = document.createElement('button');
|
|
1332
1467
|
b.type = 'button';
|
|
1333
1468
|
b.className = 'recent__item';
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1469
|
+
b.appendChild(elSpan('recent__t', connLabel(item) || item.host || item.url));
|
|
1470
|
+
b.appendChild(elSpan('recent__s', (item.host || connHost(item.url)) + ' · ' + fmtTime(item.at)));
|
|
1471
|
+
b.onclick = function () { connect({ url: item.url, token: item.token, name: item.name, host: item.host || connHost(item.url) }); };
|
|
1472
|
+
/* 多台机器就必然要能删:删掉只是忘掉这条记录,不打断当前连接。 */
|
|
1473
|
+
var del = document.createElement('button');
|
|
1474
|
+
del.type = 'button';
|
|
1475
|
+
del.className = 'recent__del';
|
|
1476
|
+
del.setAttribute('aria-label', '删除连接');
|
|
1477
|
+
del.appendChild(svgUse('#i-trash', 18));
|
|
1478
|
+
del.onclick = function () { removeConn(item.id); renderRecent(); };
|
|
1479
|
+
row.appendChild(b); row.appendChild(del);
|
|
1480
|
+
recentListEl.appendChild(row);
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
/* ── 环境切换菜单(任务页标题点开)── */
|
|
1485
|
+
var envMenuEl = null, envScrimEl = null;
|
|
1486
|
+
function closeEnvMenu() {
|
|
1487
|
+
if (envMenuEl) { envMenuEl.remove(); envMenuEl = null; }
|
|
1488
|
+
if (envScrimEl) { envScrimEl.remove(); envScrimEl = null; }
|
|
1489
|
+
var btn = document.getElementById('btnEnvSwitch');
|
|
1490
|
+
if (btn) btn.classList.remove('is-open');
|
|
1491
|
+
}
|
|
1492
|
+
/* 换环境 = 换一台桌面:对话、轮询、运行态都属于旧连接,必须一起收掉。
|
|
1493
|
+
排队任务按令牌分别落库(见 QUEUE_KEY),旧环境那批留在原地,切回来再接着跑。 */
|
|
1494
|
+
function switchEnv(c) {
|
|
1495
|
+
if (!c) return;
|
|
1496
|
+
if (c.url === BASE && String(c.token || '') === String(TOKEN || '')) {
|
|
1497
|
+
toast('已在「' + (connLabel(c) || '当前环境') + '」');
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (conv) { try { conv.destroy(); } catch (e) {} conv = null; }
|
|
1501
|
+
convEl.innerHTML = '';
|
|
1502
|
+
stopPoll();
|
|
1503
|
+
PENDING = []; ASK_WAITING = []; SERIAL = {}; QUEUE_RESTORED = false;
|
|
1504
|
+
SERVER_VERSION = ''; CORE_FEATURES = []; TEAM_ROLES = []; ENGINES = []; ENGINES_STALE_AT = '';
|
|
1505
|
+
TITLES = {}; LAST_SESSIONS = []; DW = ''; DW_READY = false; ROOT_DIRS = [];
|
|
1506
|
+
/* 工作空间属于某台机器的目录树:沿用旧路径会落到不存在的目录,一律回到默认。 */
|
|
1507
|
+
WS = '';
|
|
1508
|
+
saveWs();
|
|
1509
|
+
connect({ url: c.url, token: c.token, name: c.name, host: c.host || connHost(c.url) });
|
|
1510
|
+
toast('已切换到「' + (connLabel(c) || connHost(c.url)) + '」');
|
|
1511
|
+
}
|
|
1512
|
+
function openEnvMenu() {
|
|
1513
|
+
if (envMenuEl) { closeEnvMenu(); return; }
|
|
1514
|
+
var cur = activeConn();
|
|
1515
|
+
var list = loadConns();
|
|
1516
|
+
if (cur && !list.some(function (c) { return c.id === cur.id; })) list = [cur].concat(list);
|
|
1517
|
+
envScrimEl = document.createElement('div');
|
|
1518
|
+
envScrimEl.className = 'envmenu__scrim';
|
|
1519
|
+
envScrimEl.onclick = closeEnvMenu;
|
|
1520
|
+
envMenuEl = document.createElement('div');
|
|
1521
|
+
envMenuEl.className = 'envmenu';
|
|
1522
|
+
envMenuEl.appendChild(elSpan('envmenu__head', '切换环境'));
|
|
1523
|
+
list.forEach(function (c) {
|
|
1524
|
+
var active = !!(cur && c.id === cur.id);
|
|
1525
|
+
var b = document.createElement('button');
|
|
1526
|
+
b.type = 'button';
|
|
1527
|
+
b.className = 'envmenu__item';
|
|
1528
|
+
var ico = document.createElement('span');
|
|
1529
|
+
ico.className = 'envmenu__ico';
|
|
1530
|
+
ico.appendChild(svgUse('#i-laptop', 20));
|
|
1531
|
+
b.appendChild(ico);
|
|
1532
|
+
var col = document.createElement('span');
|
|
1533
|
+
col.className = 'envmenu__col';
|
|
1534
|
+
var tl = document.createElement('span');
|
|
1535
|
+
tl.className = 'envmenu__t';
|
|
1536
|
+
tl.appendChild(elSpan('envmenu__name', connLabel(c) || c.host || '未命名'));
|
|
1537
|
+
if (c.app) tl.appendChild(elSpan('envmenu__badge', c.app));
|
|
1538
|
+
col.appendChild(tl);
|
|
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 ? '当前连接' : '点击切换')));
|
|
1544
|
+
b.appendChild(col);
|
|
1545
|
+
if (active) {
|
|
1546
|
+
var ck = document.createElement('span');
|
|
1547
|
+
ck.className = 'envmenu__check';
|
|
1548
|
+
ck.appendChild(svgUse('#i-check', 18));
|
|
1549
|
+
b.appendChild(ck);
|
|
1550
|
+
}
|
|
1551
|
+
b.onclick = function () { closeEnvMenu(); switchEnv(c); };
|
|
1552
|
+
envMenuEl.appendChild(b);
|
|
1343
1553
|
});
|
|
1554
|
+
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);
|
|
1562
|
+
document.body.appendChild(envScrimEl);
|
|
1563
|
+
document.body.appendChild(envMenuEl);
|
|
1564
|
+
var btn = document.getElementById('btnEnvSwitch');
|
|
1565
|
+
var r = btn.getBoundingClientRect();
|
|
1566
|
+
var mw = envMenuEl.offsetWidth;
|
|
1567
|
+
envMenuEl.style.top = (r.bottom + 8) + 'px';
|
|
1568
|
+
envMenuEl.style.left = Math.max(12, Math.min(r.left, window.innerWidth - mw - 12)) + 'px';
|
|
1569
|
+
btn.classList.add('is-open');
|
|
1344
1570
|
}
|
|
1345
1571
|
|
|
1346
1572
|
/* ══════════ 解析 + 校验连接地址 ══════════
|
|
@@ -1377,11 +1603,16 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
1377
1603
|
b.classList.toggle('is-on', b.getAttribute('data-tab') === name);
|
|
1378
1604
|
});
|
|
1379
1605
|
}
|
|
1380
|
-
function showLauncher() {
|
|
1606
|
+
function showLauncher() {
|
|
1607
|
+
hideAll(); launcherEl.hidden = false; tabbarEl.hidden = true; renderRecent();
|
|
1608
|
+
/* 已连着桌面时进来(切换环境 → 添加连接)必须有路可退,否则用户被困在启动页。 */
|
|
1609
|
+
var back = document.getElementById('btnLauncherBack');
|
|
1610
|
+
if (back) back.hidden = !(BASE || TOKEN);
|
|
1611
|
+
}
|
|
1381
1612
|
function showHistory(address) {
|
|
1382
1613
|
hideAll(); historyEl.hidden = false; tabbarEl.hidden = false; setActive('tasks');
|
|
1383
1614
|
if (address != null) histSubtitle = address;
|
|
1384
|
-
historyTitle.textContent =
|
|
1615
|
+
historyTitle.textContent = envLabel() || '任务';
|
|
1385
1616
|
loadSessions();
|
|
1386
1617
|
}
|
|
1387
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) || ''; }
|
|
@@ -1423,9 +1654,12 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
1423
1654
|
function refreshMineVersion() {
|
|
1424
1655
|
if (!TOKEN) { paintMineVersion(); return; }
|
|
1425
1656
|
var hdr = { 'X-Magic-Token': TOKEN };
|
|
1657
|
+
var seq = CONN_SEQ; // 切环境后旧桌面的 health 不许写到新连接上
|
|
1426
1658
|
fetch(BASE + '/desk/health', { headers: hdr })
|
|
1427
1659
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
1428
1660
|
.then(function (h) {
|
|
1661
|
+
if (seq !== CONN_SEQ) return;
|
|
1662
|
+
if (h && h.app) noteActiveMeta({ app: h.app });
|
|
1429
1663
|
if (h && typeof h.version === 'string' && h.version) { SERVER_VERSION = h.version; return; }
|
|
1430
1664
|
return fetch(BASE + '/desk/contract', { headers: hdr })
|
|
1431
1665
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
@@ -1994,17 +2228,22 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
1994
2228
|
function connect(target) {
|
|
1995
2229
|
BASE = target.url || '';
|
|
1996
2230
|
TOKEN = target.token || '';
|
|
1997
|
-
|
|
2231
|
+
CONN_SEQ++; // 上一台桌面还在飞的请求:回来时直接丢弃(见 loadSessions)
|
|
2232
|
+
var c = upsertConn(target);
|
|
2233
|
+
ACTIVE_ID = c.id;
|
|
1998
2234
|
showError('');
|
|
1999
|
-
showHistory(
|
|
2235
|
+
showHistory(c.host || '');
|
|
2000
2236
|
startPoll();
|
|
2001
2237
|
restoreQueue(); // 上次没轮到的排队任务:连上就接着排(见 restoreQueue)
|
|
2002
2238
|
}
|
|
2003
2239
|
function disconnect() {
|
|
2004
2240
|
stopPoll();
|
|
2241
|
+
closeEnvMenu();
|
|
2242
|
+
CONN_SEQ++;
|
|
2005
2243
|
if (conv) { try { conv.destroy(); } catch (e) {} conv = null; }
|
|
2006
2244
|
convEl.innerHTML = '';
|
|
2007
2245
|
BASE = ''; TOKEN = '';
|
|
2246
|
+
ACTIVE_ID = '';
|
|
2008
2247
|
SERVER_VERSION = ''; // 版本号属于「那一条连接」:断开就清掉,避免下次连到别的机器还显示旧值
|
|
2009
2248
|
/* 排队任务**不跟着断连消失**(它们已经落库):只清内存里的等待态,下次连上由
|
|
2010
2249
|
restoreQueue 从磁盘接回来。串行门的 FIFO 也一并重置 —— 里面的 tail 是挂在一条
|
|
@@ -2050,9 +2289,11 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
2050
2289
|
if (historyEl.hidden) return; // 只在这张页可见时拉
|
|
2051
2290
|
if (!TOKEN) return; // 还没配对,拉也拉不到
|
|
2052
2291
|
var hdr = TOKEN ? { 'X-Magic-Token': TOKEN } : {};
|
|
2292
|
+
var seq = CONN_SEQ;
|
|
2053
2293
|
fetch(BASE + '/desk/sessions', { headers: hdr })
|
|
2054
2294
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
2055
2295
|
.then(function (list) {
|
|
2296
|
+
if (seq !== CONN_SEQ) return; // 响应回来时已经切到另一台桌面了
|
|
2056
2297
|
if (!Array.isArray(list)) return;
|
|
2057
2298
|
if (sessionSig(list) === sessionSig(LAST_SESSIONS)) return; // 没变 → 不动 DOM
|
|
2058
2299
|
loadSessions(true); // 变了才走完整刷新(含标题归纳 / 健康),且不许闪
|
|
@@ -2061,6 +2302,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
2061
2302
|
}
|
|
2062
2303
|
function loadSessions(quiet) {
|
|
2063
2304
|
var hdr = TOKEN ? { 'X-Magic-Token': TOKEN } : {};
|
|
2305
|
+
var seq = CONN_SEQ; // 切环境后旧请求可能才回来:序号对不上就不许改这一台的画面
|
|
2064
2306
|
/* quiet(由 startListPoll 的轮询触发):不塞「读取中…」占位、失败也不覆盖现有列表 ——
|
|
2065
2307
|
轮询是**后台行为**,不能因为一次网络抖动就把用户正看着的列表换成一行错误,
|
|
2066
2308
|
也不该让他每次看到整页闪一下「读取中…」(这正是 deleteSessionRow 绕开它的原因)。 */
|
|
@@ -2076,6 +2318,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
2076
2318
|
var pDirs = fetch(BASE + '/desk/dirs', { headers: hdr })
|
|
2077
2319
|
.then(function (r) { return r.ok ? r.json() : {}; }).catch(function () { return {}; });
|
|
2078
2320
|
Promise.all([pList, pTitles, pHealth, pDirs]).then(function (a) {
|
|
2321
|
+
if (seq !== CONN_SEQ) return;
|
|
2079
2322
|
TITLES = a[1] || {};
|
|
2080
2323
|
DW = (a[2] && a[2].defaultWorkspace) || '';
|
|
2081
2324
|
DW_READY = true;
|
|
@@ -2087,6 +2330,8 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
2087
2330
|
/* core 能力位(feature.*):团队开关要不要显示就看它。连上桌面后才有,
|
|
2088
2331
|
所以「我的」那张卡片与运行设置里的那一行都要在这里刷一次。 */
|
|
2089
2332
|
CORE_FEATURES = ((a[2] && a[2].features) || []);
|
|
2333
|
+
/* 连接的身份(app):连着哪台设备就记到哪条连接上,环境菜单里当类型标签用。 */
|
|
2334
|
+
if (a[2] && a[2].app) noteActiveMeta({ app: a[2].app });
|
|
2090
2335
|
/* 团队角色清单(2026-10-22):core 上报什么就画什么,客户端不硬编码角色名。
|
|
2091
2336
|
老 core 没有 teamRoles → 空数组 → paintTeamPref 用内置表兜底。 */
|
|
2092
2337
|
TEAM_ROLES = ((a[2] && a[2].teamRoles) || []).filter(function (r) {
|
|
@@ -2107,6 +2352,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
2107
2352
|
var pending = LAST_SESSIONS.filter(function (s) { return !s.title_summarized; });
|
|
2108
2353
|
pending.slice(0, TITLE_SUMMARIZE_CAP).forEach(function (s) { setTimeout(function () { ensureTitle(s); }, 0); });
|
|
2109
2354
|
}).catch(function (e) {
|
|
2355
|
+
if (seq !== CONN_SEQ) return;
|
|
2110
2356
|
if (quiet) return; // 后台轮询失败:保留现有列表(见上面的说明)
|
|
2111
2357
|
historyList.innerHTML = '<div class="history__hint">读取会话失败:' + (e && e.message ? e.message : e) + '</div>';
|
|
2112
2358
|
});
|
|
@@ -3871,6 +4117,7 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
3871
4117
|
b.appendChild(main);
|
|
3872
4118
|
b.appendChild(hEl('span', 'setrow__v',
|
|
3873
4119
|
rt.engine ? (rt.engine + (rt.model ? ' · ' + qaModelText(rt.model) : '')) : '跟随主引擎'));
|
|
4120
|
+
b.appendChild(hEl('span', 'setrow__chev', '›'));
|
|
3874
4121
|
b.onclick = function () {
|
|
3875
4122
|
openTeamRoutePicker(key,
|
|
3876
4123
|
String(key).indexOf('role:') === 0 ? (label + ':适合做什么、交给谁') : '计划任务:怎么拆、交给谁',
|
|
@@ -3904,7 +4151,9 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
3904
4151
|
hint.style.textAlign = 'left';
|
|
3905
4152
|
body.appendChild(hint);
|
|
3906
4153
|
/* 规则控件放在引擎清单**之前**:规则是这一行的主内容,引擎是「交给谁」。
|
|
3907
|
-
|
|
4154
|
+
它们与探测无关,引擎清单拉失败也照样能改。分区标题必须显式给出 ——
|
|
4155
|
+
用户 2026-10-22:「入口在哪里 不能配置规则」:控件在,但没标题就读不出这里是配置区。 */
|
|
4156
|
+
body.appendChild(hEl('div', 'pick__cap pick__cap--team', '规则'));
|
|
3908
4157
|
if (isRole) body.appendChild(buildTeamRuleEditor(key));
|
|
3909
4158
|
else if (kind === 'plan') body.appendChild(buildTeamSplitControls());
|
|
3910
4159
|
fetchEngines().then(function (list) {
|
|
@@ -3932,10 +4181,11 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
3932
4181
|
} else {
|
|
3933
4182
|
tipText = '计划阶段负责把任务拆成子任务,交给更擅长规划、上下文更长的引擎更稳。';
|
|
3934
4183
|
}
|
|
4184
|
+
body.appendChild(hEl('div', 'pick__cap pick__cap--team', '交给谁'));
|
|
3935
4185
|
var tip = hEl('div', 'history__hint', tipText);
|
|
3936
4186
|
tip.style.textAlign = 'left';
|
|
3937
4187
|
body.appendChild(tip);
|
|
3938
|
-
var wrap = hEl('div', 'pick__cols');
|
|
4188
|
+
var wrap = hEl('div', 'pick__cols pick__cols--team');
|
|
3939
4189
|
var left = hEl('div', 'pick__col');
|
|
3940
4190
|
var right = hEl('div', 'pick__col pick__col--r');
|
|
3941
4191
|
wrap.appendChild(left); wrap.appendChild(right); body.appendChild(wrap);
|
|
@@ -3980,18 +4230,25 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
3980
4230
|
});
|
|
3981
4231
|
});
|
|
3982
4232
|
}
|
|
3983
|
-
/*
|
|
3984
|
-
|
|
4233
|
+
/* 角色行的「规则」:一句话写清这类任务适合什么。**默认值直接填内置职责**(用户
|
|
4234
|
+
2026-10-22:「也没看到默认规则啊」—— placeholder 在手机上几乎看不见),保存后覆盖它
|
|
4235
|
+
(core 的 --team-rule role:<name>=…);改回内置值等于没改,不落覆盖。 */
|
|
3985
4236
|
function buildTeamRuleEditor(key) {
|
|
3986
4237
|
var box = hEl('div', 'teamedit__box');
|
|
3987
4238
|
var lab = hEl('div', 'history__hint', '这类任务适合什么(写进拆解提示词)');
|
|
3988
4239
|
lab.style.textAlign = 'left';
|
|
3989
4240
|
box.appendChild(lab);
|
|
4241
|
+
var sub = hEl('div', 'history__hint', '下面填的就是内置默认规则,可直接改;改完点保存才生效。');
|
|
4242
|
+
sub.style.textAlign = 'left';
|
|
4243
|
+
sub.style.padding = '0 12px 2px';
|
|
4244
|
+
sub.style.fontSize = '12px';
|
|
4245
|
+
box.appendChild(sub);
|
|
3990
4246
|
var ta = hEl('textarea', 'teamedit');
|
|
3991
4247
|
var name = String(key).slice(5);
|
|
3992
4248
|
var info = teamRoleList().filter(function (r) { return r.name === name; })[0] || {};
|
|
3993
|
-
|
|
3994
|
-
ta.
|
|
4249
|
+
var builtin = String(info.duty || '').trim();
|
|
4250
|
+
ta.placeholder = builtin || '用一句话写清这类任务承接什么';
|
|
4251
|
+
ta.value = String((TEAM.rules || {})[key] || '').trim() || builtin;
|
|
3995
4252
|
box.appendChild(ta);
|
|
3996
4253
|
var acts = hEl('div', 'teamedit__acts');
|
|
3997
4254
|
var save = hEl('button', 'btn btn--filled', '保存规则');
|
|
@@ -4001,18 +4258,19 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
4001
4258
|
/* 500 字上限与 core 同口径(core 会再判一次,这里是提前拦,省一次往返)。 */
|
|
4002
4259
|
if (v.length > 500) { toast('规则最多 500 字'); return; }
|
|
4003
4260
|
TEAM.rules = TEAM.rules || {};
|
|
4004
|
-
if (v
|
|
4261
|
+
if (!v || v === builtin) delete TEAM.rules[key];
|
|
4262
|
+
else TEAM.rules[key] = v;
|
|
4005
4263
|
saveTeamPref();
|
|
4006
|
-
toast(v ? '规则已保存' : '
|
|
4264
|
+
toast((v && v !== builtin) ? '规则已保存' : '已使用内置规则');
|
|
4007
4265
|
};
|
|
4008
4266
|
acts.appendChild(save);
|
|
4009
4267
|
var reset = hEl('button', 'btn btn--outlined', '恢复默认');
|
|
4010
4268
|
reset.type = 'button';
|
|
4011
4269
|
reset.onclick = function () {
|
|
4012
4270
|
if (TEAM.rules) delete TEAM.rules[key];
|
|
4013
|
-
ta.value =
|
|
4271
|
+
ta.value = builtin;
|
|
4014
4272
|
saveTeamPref();
|
|
4015
|
-
toast('
|
|
4273
|
+
toast('已恢复内置规则');
|
|
4016
4274
|
};
|
|
4017
4275
|
acts.appendChild(reset);
|
|
4018
4276
|
box.appendChild(acts);
|
|
@@ -4299,7 +4557,10 @@ body.is-conv .tabfab__b{width:56px;height:56px;bottom:calc(env(safe-area-inset-b
|
|
|
4299
4557
|
connect(target);
|
|
4300
4558
|
};
|
|
4301
4559
|
addrEl.addEventListener('input', function () { showError(''); });
|
|
4302
|
-
on('btnClear', function () {
|
|
4560
|
+
on('btnClear', function () { clearConns(); renderRecent(); });
|
|
4561
|
+
/* 任务页标题 = 环境切换菜单(多连接)。已连着桌面时,从启动页进来也能退回去。 */
|
|
4562
|
+
on('btnEnvSwitch', openEnvMenu);
|
|
4563
|
+
on('btnLauncherBack', function () { if (BASE || TOKEN) showHistory(); });
|
|
4303
4564
|
/* 别处起的任务要能自己冒出来(列表页可见时静默轮询;见 startListPoll 的注释) */
|
|
4304
4565
|
startListPoll();
|
|
4305
4566
|
on('btnList', function () { showHistory(); });
|
package/internal/team/planner.go
CHANGED
|
@@ -103,7 +103,8 @@ const plannerPromptTemplate = `你是任务规划器。把用户的诉求拆成
|
|
|
103
103
|
- code:代码产出(脚本、程序、构建 / 测试相关的改动)→ coder。
|
|
104
104
|
- image:画面类产出(位图 / 矢量:封面、插画、海报、图标)→ designer。
|
|
105
105
|
- video:影像类产出(分镜、剪辑、成片)→ director。
|
|
106
|
-
- game
|
|
106
|
+
- game:游戏相关产出 —— 游戏内玩法 / 系统开发(在现有工程里改代码、加功能),以及从零交付
|
|
107
|
+
可运行的游戏工程(代码 + 素材 + 启动方式)→ builder。
|
|
107
108
|
- {{CODE_GATE}}
|
|
108
109
|
- **需求里出现了对应类别的产物,就必须单独拆出该类任务**,哪怕用户没明说「配图 / 做成视频」。
|
|
109
110
|
例:「写 100 字游戏介绍并配 1 个封面」→ 1 个 writer + 1 个 designer;
|
package/internal/team/role.go
CHANGED
|
@@ -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
|