@yangdcm/dsh-expert-team 1.3.3 → 1.3.5
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/CHANGELOG.md +77 -0
- package/client.js +121 -17
- package/lib/command-parse.js +9 -2
- package/lib/command.js +160 -22
- package/lib/settings.js +17 -24
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,83 @@
|
|
|
3
3
|
本包遵循[语义化版本](https://semver.org/lang/zh-CN/)。dsh 宿主版本线的对应关系写在
|
|
4
4
|
`package.json` 的 `engines.dsh` 与 `dsh.compatibility` 里,插件市场按它判断"这个插件跟你的宿主兼不兼容"。
|
|
5
5
|
|
|
6
|
+
## 1.3.5
|
|
7
|
+
|
|
8
|
+
**性能:修掉 `/state` 的 30 秒级阻塞(它会把整个 `dsh web` 一起拖慢)**,另带两件早已排定的小事。
|
|
9
|
+
|
|
10
|
+
### 症状与根因(都是实测值,不是估算)
|
|
11
|
+
|
|
12
|
+
- `GET /plugins/dsh-expert-team/state` 实测 **热态 7.1–9.8 s、冷态 283.6 s**,而面板**绘制只要 52 ms**。
|
|
13
|
+
- **根因 D(最大头)**:端点对**每个**子会话 `await childSessionTiming`,而它对"活存储里查不到"的子代理
|
|
14
|
+
退回 `sessionQuery.readSession(id)` —— 那是**全量读该子会话日志**。本机实测:单条最大日志
|
|
15
|
+
**23.4 MiB(70,008 个 zstd frame)→ 解压 0.44 s / 45.4 MiB / 94,820 个事件**;21 条 >1MB 的会话日志
|
|
16
|
+
仅"解压 + 过管道"就要 **1.11 s**(纯磁盘下限;host 还要逐行 `JSON.parse` + `structuredClone` + replay)。
|
|
17
|
+
- **根因 B**:`WF_EVENT_TTL_MS` 原来是 **3000 ms —— 恰好等于默认轮询间隔 3000 ms** ⇒ **每个 tick 必失效**,
|
|
18
|
+
父会话日志(多 MB)被一遍遍全量重读。
|
|
19
|
+
- **根因 H3**:客户端**没有 single-flight** —— 实测 8 秒内发出 5 个 `/state`、其中 **4 个重叠**(上一个没回就发下一个)。
|
|
20
|
+
- **后果(这才最要紧)**:这些同步 CPU(≈2.3 s/请求)压在事件循环上,把**整个 web** 拖慢 ——
|
|
21
|
+
轻量端点 `/settings` 实测被拖到 **20.7 s / 42.5 s**(关掉面板后仍在排队,因为积压已经形成)。
|
|
22
|
+
|
|
23
|
+
### 修复
|
|
24
|
+
|
|
25
|
+
- **热点 D**:新增 `subHeaderIndex()` —— 子会话时间**只查表、永不读日志**。三个来源都便宜:
|
|
26
|
+
① `listSubagentStatusBySession` 的 `listSessions()` 分支本来就顺带带出了 `createdAt/parentId/depth`;
|
|
27
|
+
② 活存储 `ctx.sessions.get(id).header`;③ workflow 事件流的 `startedAt`(父会话日志已按 TTL 缓存)。
|
|
28
|
+
`/state` 的两个计时调用点全部改用它。三源都拿不到仍**如实降级** `0/''/0`(UI 显示「无时间记录」),**不臆造**。
|
|
29
|
+
- **热点 B**:`WF_EVENT_TTL_MS` 3000 → **30000**(TTL 只是兜底;代价是"run 在会话中途结束"最多晚 30 秒反映到面板)。
|
|
30
|
+
- **客户端**:`load()` 加 **single-flight**(未回绝不发下一个)+ **自适应退避**
|
|
31
|
+
`clamp(max(基础间隔, 上次耗时×2), 基础间隔, 30000)`;`display.pollMs` 仍是基础节奏(设置说了算)。
|
|
32
|
+
- **写侧守卫**:`/state` 只在**该 session 真实存在**时才 `rememberSessionRun` 落盘 —— 诊断时一个**假 id**
|
|
33
|
+
曾被写进 `$DSH_HOME/expert-team/session-runs.json`(污染用户数据)。
|
|
34
|
+
|
|
35
|
+
预计效果:热态从 7.1–9.8 s 降到 **0.15–0.4 s** 量级(以实机复测为准)。
|
|
36
|
+
|
|
37
|
+
### 另两件小事
|
|
38
|
+
|
|
39
|
+
- 设置页页头那句「标着「暂未生效」的项尚未接线…」改为**条件渲染**:`INERT_SETTINGS` 现在是空的,
|
|
40
|
+
无条件渲染会让用户去找一个不存在的标注。判据直接取 hint 里的标记(与后端**同一真源**)。
|
|
41
|
+
- 新增反向参数 **`--one-shot`** / **`--code`**:1.3.4 把设置接成"无 flag 时的默认"之后出现不对称 ——
|
|
42
|
+
设置成 `persist=true` / `artifacts-only` 后,单条命令**没法反悔**。解析改为**三态**
|
|
43
|
+
(`true` 显式要 / `false` 显式不要 / `null` 未表态听设置),优先级仍是 **flag > 设置 > 常量**。
|
|
44
|
+
(曾加过一个 `--no-persist` 别名,被本仓「实现了就必须有文档」的双向一致性断言挡下 ⇒ 去掉别名,一概念一名。)
|
|
45
|
+
|
|
46
|
+
### 护栏(进 `test:all`)
|
|
47
|
+
|
|
48
|
+
新增 `state-perf-guard.test.mjs`:热路径**零次 `readSession`**(spy 计数)、源码里不再有
|
|
49
|
+
`await childSessionTiming(ctx, s.id)`、客户端 single-flight 与退避公式、假 id 不落盘、三态反向参数与优先级。
|
|
50
|
+
|
|
51
|
+
### 1.3.6 待办(诊断列出、本版**有意不做**,避免扩大范围)
|
|
52
|
+
|
|
53
|
+
懒加载分节(`?section=people`)、`roleFromChildLog` 流式首帧、`listPersisted` 475 个文件的 header 缓存、
|
|
54
|
+
`maxSubs` / deadline 截断。
|
|
55
|
+
## 1.3.4
|
|
56
|
+
|
|
57
|
+
**把 1.3.2 留下的 7 个"有持久化、无消费者"的设置真的接上**(display 4 + roster 3),并清掉当时的假承诺。
|
|
58
|
+
|
|
59
|
+
- **display 四项**(此前只有存储层、零消费者;根因是客户端**全仓只有一处 `/settings` 拉取**、且只在设置表单里):
|
|
60
|
+
- `pollMs`:浮层/面板轮询与画布 `--watch` 轮询都读它(此前分别写死 3000/1200 与 3000);
|
|
61
|
+
- `capsuleMs`:**真正实现**"胶囊 N 毫秒后自动消失"。此前只入队、**永不自动出队** —— 旧代码里的
|
|
62
|
+
`4000` 是**同文本去重窗口**、被误当成停留时长,所以这个功能**压根不存在**;`0 = 不自动消失` 保留;
|
|
63
|
+
- `panelWidth`:设置值 = **默认宽度**;拖动结束**防抖写回设置**(单一真源,localStorage 只作首帧缓存),
|
|
64
|
+
并把三处互不一致的默认/值域对齐(设置 280..900/420、拖动 clamp 640→900、CSS 与画布回退 360→420);
|
|
65
|
+
- `defaultTab`:面板初始页签取设置(用户点过就以点击为准)。
|
|
66
|
+
- 新增**客户端设置桥** `useDisplaySettings()`/`EXPERT_DISPLAY`:挂载时拉一次,**保存成功即当场生效**
|
|
67
|
+
(不必重启/刷新);失败静默回默认值,不影响面板工作。
|
|
68
|
+
- **roster 三项**:命令行**没给**对应参数时用设置兜底,优先级 **flag > 设置 > 常量**:
|
|
69
|
+
- `deliverable`(`artifacts-only` ⇒ `noCode`)、`persist`、`defaultRoles`;
|
|
70
|
+
- **档位收窄的基线也跟着设置走**(否则"设了默认班底"会让 `--tier` 静默失效);
|
|
71
|
+
- 落点是 `createRun` 的**唯一一处**(覆盖 `/team`、浮层决策、`/decide` 等所有建 run 入口),
|
|
72
|
+
判定抽成纯函数 `resolveRosterDefaults()` 便于单测;`--profile` 点过名的仍优先于设置。
|
|
73
|
+
- **清掉假承诺**:`INERT_SETTINGS` 现为**空对象**(7 项全部接线,机制保留作棘轮落点);
|
|
74
|
+
标记文案改为**版本中立**(`(暂未生效:尚未接线 —— …)`),并**移除用户可见文案里的行号**
|
|
75
|
+
(行号会腐烂、且对用户无意义 —— 证据一律写进代码注释)。
|
|
76
|
+
- **新增护栏** `settings-wiring.test.mjs`(进 `npm run test:all`):roster 三项**行为级**(真跑 `createRun`,
|
|
77
|
+
断言 `STATE.json`/`ROSTER.json`/`RUN.log` 三处一致 + flag 优先级 + 档位收窄基线)、画布轮询**行为级**
|
|
78
|
+
(生成的 live 脚本里就是设置值;越界回默认)、`gates.loopGuard` 的 **getter 语义**(同一钩子实例下改设置
|
|
79
|
+
⇒ 行为**双向即时翻转**、**无需重建钩子**),display 四项为**源码级**断言(本包无 DOM、不引依赖)。
|
|
80
|
+
- **更正 1.3.2 的描述**:1.3.2 曾写"待 1.3.3 接线",而 1.3.3 实际发的是宿主 schema 的枚举中文描述,
|
|
81
|
+
接线在本版完成 —— 因此标记文案改为**不承诺版本号**。
|
|
82
|
+
|
|
6
83
|
## 1.3.3
|
|
7
84
|
|
|
8
85
|
**宿主 schema 的枚举成员带上中文描述**(1.3.2 记录为"跳过"的那条,实测后补上)。
|
package/client.js
CHANGED
|
@@ -22,7 +22,7 @@ window.__ModuleLoader__.load({
|
|
|
22
22
|
var h = createElement
|
|
23
23
|
|
|
24
24
|
var CSS =
|
|
25
|
-
'.exp-panel{position:fixed;top:0;right:0;bottom:0;width:
|
|
25
|
+
'.exp-panel{position:fixed;top:0;right:0;bottom:0;width:420px;max-width:100vw;display:flex;flex-direction:column;background:var(--dsw-alias-bg-base,var(--bg,#fff));border-left:1px solid var(--dsw-alias-border-l2,var(--border,#d0d7de));box-shadow:-10px 0 32px rgba(0,0,0,.16);z-index:120;font-size:13px;color:var(--dsw-alias-label-primary,var(--text,#1f2328));font-family:-apple-system,"Segoe UI","PingFang SC",sans-serif}' +
|
|
26
26
|
'.exp-head{padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,var(--border,#d0d7de));background:linear-gradient(180deg,var(--dsw-alias-bg-module-platform,var(--bg-subtle,#f6f8fa)),var(--dsw-alias-bg-base,var(--bg,#fff)));display:flex;align-items:center;justify-content:space-between;flex:none}' +
|
|
27
27
|
'.exp-head.exp-grab{cursor:move;user-select:none}' +
|
|
28
28
|
'.exp-dock-handle{position:absolute;left:-2px;top:0;bottom:0;width:9px;cursor:col-resize;z-index:3;transition:background .12s}' +
|
|
@@ -293,6 +293,74 @@ window.__ModuleLoader__.load({
|
|
|
293
293
|
function setOpen() { open = !open; notify() }
|
|
294
294
|
function subscribe(f) { listeners.add(f); return function () { listeners.delete(f) } }
|
|
295
295
|
function useExternal(getValue) { var s = useState(getValue()); useEffect(function () { return subscribe(function () { s[1](getValue()) }) }, []); return s[0] }
|
|
296
|
+
|
|
297
|
+
// ── 设置桥(1.3.4):`display.*` 的**唯一**运行态来源 ───────────────────────────
|
|
298
|
+
// 为什么必须有它:此前全仓只有一处 `/settings` 拉取,而且只在设置表单里 —— display 四项因此
|
|
299
|
+
// 永远流不到运行态(轮询间隔/胶囊停留/面板宽度/默认页签各自硬编码或读 localStorage)。
|
|
300
|
+
// 这里建**一条**共享的路:面板挂载时拉一次;设置页保存成功后复用同一条路把新值通知出去
|
|
301
|
+
// ⇒ 改设置**当场生效**(同一 client、进程内),不必重启。
|
|
302
|
+
var EXPERT_DISPLAY = { pollMs: 3000, capsuleMs: 4000, panelWidth: 420, defaultTab: 'team' }
|
|
303
|
+
var DISPLAY_TABS = ['team', 'tasks', 'board', 'info']
|
|
304
|
+
var DISPLAY_RANGE = { pollMs: [1000, 60000], capsuleMs: [0, 60000], panelWidth: [280, 900] }
|
|
305
|
+
var displayLoaded = false
|
|
306
|
+
var displayLoading = null
|
|
307
|
+
function clampInt(v, lo, hi, def) { var n = Number(v); if (!isFinite(n)) return def; n = Math.round(n); return Math.max(lo, Math.min(hi, n)) }
|
|
308
|
+
/** 从 `/settings` 响应挑出 display 四项并按值域夹紧;缺项/越界一律回退当前值(不制造 NaN/空白)。 */
|
|
309
|
+
function pickDisplay(settings) {
|
|
310
|
+
var d = (settings && settings.display) || {}
|
|
311
|
+
return {
|
|
312
|
+
pollMs: clampInt(d.pollMs, DISPLAY_RANGE.pollMs[0], DISPLAY_RANGE.pollMs[1], EXPERT_DISPLAY.pollMs),
|
|
313
|
+
capsuleMs: clampInt(d.capsuleMs, DISPLAY_RANGE.capsuleMs[0], DISPLAY_RANGE.capsuleMs[1], EXPERT_DISPLAY.capsuleMs),
|
|
314
|
+
panelWidth: clampInt(d.panelWidth, DISPLAY_RANGE.panelWidth[0], DISPLAY_RANGE.panelWidth[1], EXPERT_DISPLAY.panelWidth),
|
|
315
|
+
defaultTab: DISPLAY_TABS.indexOf(String(d.defaultTab)) >= 0 ? String(d.defaultTab) : EXPERT_DISPLAY.defaultTab
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** 应用一份设置:换对象(useExternal 才看得见变化)+ 未拖动过宽度时采纳设置值 + 通知重渲染。 */
|
|
319
|
+
function applyDisplaySettings(settings) {
|
|
320
|
+
var next = pickDisplay(settings)
|
|
321
|
+
var changed = next.pollMs !== EXPERT_DISPLAY.pollMs || next.capsuleMs !== EXPERT_DISPLAY.capsuleMs || next.defaultTab !== EXPERT_DISPLAY.defaultTab
|
|
322
|
+
EXPERT_DISPLAY = next
|
|
323
|
+
// 宽度:设置值是**默认宽度**;用户拖过就以拖动结果为准(拖动结束会把宽度写回设置)。
|
|
324
|
+
if (!panelWidthUserSet && next.panelWidth !== panelWidth) { panelWidth = clampW(next.panelWidth); changed = true }
|
|
325
|
+
if (changed) notify()
|
|
326
|
+
return next
|
|
327
|
+
}
|
|
328
|
+
function fetchSettingsPayload() {
|
|
329
|
+
return fetch('/plugins/dsh-expert-team/settings').then(function (r) {
|
|
330
|
+
return r.json().catch(function () { return null }).then(function (d) { return { ok: r.ok, status: r.status, d: d } })
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
/** 拉一次设置(幂等:同一次加载只拉一次;失败静默 —— 面板照旧用默认值工作)。 */
|
|
334
|
+
function loadDisplaySettings() {
|
|
335
|
+
if (displayLoaded) return displayLoading || Promise.resolve(EXPERT_DISPLAY)
|
|
336
|
+
displayLoading = fetchSettingsPayload().then(function (res) {
|
|
337
|
+
displayLoaded = true
|
|
338
|
+
if (res.ok && res.d && res.d.ok && res.d.settings) applyDisplaySettings(res.d.settings)
|
|
339
|
+
return EXPERT_DISPLAY
|
|
340
|
+
}).catch(function () { displayLoaded = true; return EXPERT_DISPLAY })
|
|
341
|
+
return displayLoading
|
|
342
|
+
}
|
|
343
|
+
/** 运行态取 display 配置(挂载时拉一次;保存后由 applyDisplaySettings 通知更新)。 */
|
|
344
|
+
function useDisplaySettings() {
|
|
345
|
+
var v = useExternal(function () { return EXPERT_DISPLAY })
|
|
346
|
+
useEffect(function () { loadDisplaySettings() }, [])
|
|
347
|
+
return v
|
|
348
|
+
}
|
|
349
|
+
/** 拖动结束把宽度**防抖**写回设置(单一真源);localStorage 只作首帧缓存。 */
|
|
350
|
+
var widthSaveTimer = null
|
|
351
|
+
function persistDisplayWidth(w) {
|
|
352
|
+
if (widthSaveTimer) clearTimeout(widthSaveTimer)
|
|
353
|
+
widthSaveTimer = setTimeout(function () {
|
|
354
|
+
widthSaveTimer = null
|
|
355
|
+
try {
|
|
356
|
+
fetch('/plugins/dsh-expert-team/settings', {
|
|
357
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ 'display.panelWidth': w })
|
|
358
|
+
}).then(function (r) { return r.json().catch(function () { return null }) }).then(function (d) {
|
|
359
|
+
if (d && d.ok && d.settings) applyDisplaySettings(d.settings)
|
|
360
|
+
}).catch(function () {})
|
|
361
|
+
} catch (e) {}
|
|
362
|
+
}, 400)
|
|
363
|
+
}
|
|
296
364
|
/** 对话流 workflow 卡是否已回退到 dsh 原生(localStorage 开关,刷新生效)。 */
|
|
297
365
|
function nativeWfOn() { try { return window.localStorage.getItem('et-native-workflow') === '1' } catch (e) { return false } }
|
|
298
366
|
function useCurrentSession() { return useExternal(function () { return currentSessionId }) }
|
|
@@ -306,8 +374,12 @@ window.__ModuleLoader__.load({
|
|
|
306
374
|
function saveLS(key, v) { try { window.localStorage.setItem(key, JSON.stringify(v)) } catch (e) {} }
|
|
307
375
|
var dockState = loadLS('et-dock', true)
|
|
308
376
|
var panelPos = loadLS('et-pos', { x: 0, y: 0 })
|
|
309
|
-
|
|
310
|
-
|
|
377
|
+
// 1.3.4:默认宽度来自设置 `display.panelWidth`(首帧用 localStorage 缓存避免闪烁);
|
|
378
|
+
// 值域与设置对齐 280..900(此前 clamp 到 640、CSS 写死 360 —— 三处互不一致)。
|
|
379
|
+
var panelWidthLS = loadLS('et-width', null)
|
|
380
|
+
var panelWidthUserSet = typeof panelWidthLS === 'number'
|
|
381
|
+
var panelWidth = clampW(typeof panelWidthLS === 'number' ? panelWidthLS : EXPERT_DISPLAY.panelWidth)
|
|
382
|
+
function clampW(w) { return Math.max(280, Math.min(900, w)) }
|
|
311
383
|
function setDock(v) { if (dockState === v) return; dockState = v; if (!v) { panelPos = { x: ((window && window.innerWidth) || 1280) - panelWidth, y: 80 }; saveLS('et-pos', panelPos) } saveLS('et-dock', dockState); notify() }
|
|
312
384
|
function setPanelPos(x, y) { panelPos = { x: x, y: y }; saveLS('et-pos', panelPos); notify() }
|
|
313
385
|
/**
|
|
@@ -323,7 +395,7 @@ window.__ModuleLoader__.load({
|
|
|
323
395
|
if (changed) notify()
|
|
324
396
|
} catch (e) {}
|
|
325
397
|
}
|
|
326
|
-
function setPanelWidth(w) { panelWidth = clampW(w); saveLS('et-width', panelWidth); notify() }
|
|
398
|
+
function setPanelWidth(w) { panelWidth = clampW(w); panelWidthUserSet = true; saveLS('et-width', panelWidth); notify(); persistDisplayWidth(panelWidth) }
|
|
327
399
|
|
|
328
400
|
/**
|
|
329
401
|
* ── 对话流卡片 → 右侧面板的「聚焦意图」通道 ──
|
|
@@ -1081,12 +1153,10 @@ window.__ModuleLoader__.load({
|
|
|
1081
1153
|
useEffect(function () {
|
|
1082
1154
|
var alive = true
|
|
1083
1155
|
setLoading(true); setLoadErr('')
|
|
1084
|
-
|
|
1085
|
-
return r.json().catch(function () { return null }).then(function (d) { return { ok: r.ok, status: r.status, d: d } })
|
|
1086
|
-
}).then(function (res) {
|
|
1156
|
+
fetchSettingsPayload().then(function (res) {
|
|
1087
1157
|
if (!alive) return
|
|
1088
1158
|
setLoading(false)
|
|
1089
|
-
if (res.ok && res.d && res.d.ok && res.d.schema) { setData(res.d); return }
|
|
1159
|
+
if (res.ok && res.d && res.d.ok && res.d.schema) { applyDisplaySettings(res.d.settings); setData(res.d); return }
|
|
1090
1160
|
var detail = (res.d && (res.d.error || (res.d.errors || []).join(';'))) || ''
|
|
1091
1161
|
setLoadErr(t('读取设置失败:HTTP ', 'Reading settings failed: HTTP ') + res.status + (detail ? ' ' + t('(', '(') + detail + t(')', ')') : ''))
|
|
1092
1162
|
}).catch(function (e) {
|
|
@@ -1107,6 +1177,7 @@ window.__ModuleLoader__.load({
|
|
|
1107
1177
|
return
|
|
1108
1178
|
}
|
|
1109
1179
|
setData(function (prev) { return { schema: (prev && prev.schema) || [], settings: res.d.settings, ok: true } })
|
|
1180
|
+
applyDisplaySettings(res.d.settings) // 1.3.4:设置页改完 display 四项**当场生效**(不必重启/刷新)
|
|
1110
1181
|
setMsg('已保存') // 1.3.2:经逐项核查,没有任何设置需要重启(见 lib/command.js 的 needsRestart 注释)⇒ 死分支已删
|
|
1111
1182
|
}).catch(function (e) { setErr(String(e && e.message ? e.message : e)); setMsg('') })
|
|
1112
1183
|
}
|
|
@@ -1119,6 +1190,7 @@ window.__ModuleLoader__.load({
|
|
|
1119
1190
|
h('button', { className: 'exp-settings-retry', onClick: function () { setRetry(retry + 1) } }, esc(t('重试', 'Retry'))))
|
|
1120
1191
|
if (!data || !data.schema) return h('div', { className: 'exp-settings' }, h('div', { className: 'exp-empty' }, t('(正在读取设置…)', '(loading settings…)')))
|
|
1121
1192
|
var model = settingsFormModel(data.schema, data.settings)
|
|
1193
|
+
var hasInertMark = model.some(function (g) { return (g.rows || []).some(function (r) { return /暂未生效/.test(String(r.hint || '')) }) })
|
|
1122
1194
|
var rows = []
|
|
1123
1195
|
model.forEach(function (g) {
|
|
1124
1196
|
rows.push(h('div', { key: 'h-' + g.group, className: 'exp-settings-group' }, esc(g.label) + (g.hint ? ' · ' + esc(g.hint) : '')))
|
|
@@ -1151,7 +1223,11 @@ window.__ModuleLoader__.load({
|
|
|
1151
1223
|
// ③ **没有任何一项需要重启** ⇒ 回执只说"已保存"(`needsRestart` 恒 false)。
|
|
1152
1224
|
// ④ 标着「暂未生效」的项 = **还没接线**(`INERT_SETTINGS`,见 lib/settings.js):写在这里
|
|
1153
1225
|
// 不是承诺,而是如实告知;逐项标记由 per-item hint 携带,不在这里重复。
|
|
1154
|
-
|
|
1226
|
+
// 「暂未生效」那句**只在真有这种项时**才渲染(1.3.5):INERT_SETTINGS 现在是空的,
|
|
1227
|
+
// 无条件渲染会让用户去找一个不存在的标注。判据直接取 hint 里的标记 ⇒ 与后端单一真源一致。
|
|
1228
|
+
h('div', { className: 'exp-settings-head' }, esc(hasInertMark
|
|
1229
|
+
? t('改动即保存并即时生效(上限 / 轮次 / 档位门 / 振荡检测开关在进程内重算)。标着「暂未生效」的项尚未接线,改了不会有作用。', 'Saved on change and applied immediately (caps, rounds, the tier gate and the oscillation switch are recomputed in-process). Items marked as not yet in effect are not wired up — changing them does nothing.')
|
|
1230
|
+
: t('改动即保存并即时生效(上限 / 轮次 / 档位门 / 振荡检测开关在进程内重算)。', 'Saved on change and applied immediately (caps, rounds, the tier gate and the oscillation switch are recomputed in-process).'))),
|
|
1155
1231
|
rows,
|
|
1156
1232
|
h('div', { className: 'exp-settings-msg' + (err ? ' bad' : '') }, esc(err ? '✗ ' + err : (msg || ''))))
|
|
1157
1233
|
}
|
|
@@ -1179,6 +1255,8 @@ window.__ModuleLoader__.load({
|
|
|
1179
1255
|
var pos = usePanelPos()
|
|
1180
1256
|
useLang() // re-render when the shell language changes
|
|
1181
1257
|
var pw = usePanelWidth()
|
|
1258
|
+
// 1.3.4:display 四项在这里取(挂载时拉一次设置;保存后即时通知更新)
|
|
1259
|
+
var dispCfg = useDisplaySettings()
|
|
1182
1260
|
var dataS = useState(null), data = dataS[0], setData = dataS[1]
|
|
1183
1261
|
var runsMetaS = useState([]); var runsMeta = runsMetaS[0], setRunsMeta = runsMetaS[1]
|
|
1184
1262
|
var selTask = useState(null); var selTaskV = selTask[0], setSelTask = selTask[1]
|
|
@@ -1196,7 +1274,7 @@ window.__ModuleLoader__.load({
|
|
|
1196
1274
|
var prevDecRef = useRef(0)
|
|
1197
1275
|
var hoverS = useState(null); var hoverId = hoverS[0], setHover = hoverS[1]
|
|
1198
1276
|
var view = useState('dag'); var viewV = view[0], setView = view[1]
|
|
1199
|
-
var tabS = useState(
|
|
1277
|
+
var tabS = useState(null); var tab = tabS[0] || dispCfg.defaultTab; var setTab = tabS[1] // 1.3.4:初始页签来自设置 display.defaultTab
|
|
1200
1278
|
var listOpenS = useState(false); var listOpen = listOpenS[0], setListOpen = listOpenS[1]
|
|
1201
1279
|
// 流转视图(波次带)自己的 UI 状态:宽视图 / 波折叠 / 未绑定任务展开
|
|
1202
1280
|
var wideS = useState(false); var wideV = wideS[0], setWide = wideS[1]
|
|
@@ -1272,7 +1350,22 @@ window.__ModuleLoader__.load({
|
|
|
1272
1350
|
if (selRunV && selRunV.workspace && selRunV.runId) { q.push('workspace=' + encodeURIComponent(selRunV.workspace)); q.push('run=' + encodeURIComponent(selRunV.runId)) }
|
|
1273
1351
|
return url + (q.length ? ('?' + q.join('&')) : '')
|
|
1274
1352
|
}
|
|
1353
|
+
// ── single-flight + 自适应退避(2026-09-15 性能修复)──────────────────────────
|
|
1354
|
+
// 实测:8 秒内曾发 5 个 /state、其中 4 个重叠,而单发可达 7–283 秒 ⇒ 队列只增不减,
|
|
1355
|
+
// 服务端被压垮(连轻量端点 /settings 都涨到 20.7 s / 42.5 s,整个 dsh web 一起卡)。
|
|
1356
|
+
// 两条纪律:① **未回绝不发下一个**(晚一点看到状态,好过把 web 拖死);
|
|
1357
|
+
// ② 间隔随上次耗时**退避**(见 reschedule),重活端点不该 1.2 秒一发。
|
|
1358
|
+
var inFlightRef = useRef(false)
|
|
1359
|
+
var lastMsRef = useRef(0)
|
|
1275
1360
|
function load() {
|
|
1361
|
+
if (inFlightRef.current) return
|
|
1362
|
+
inFlightRef.current = true
|
|
1363
|
+
var t0 = (window.performance && performance.now) ? performance.now() : Date.now()
|
|
1364
|
+
var done = function () {
|
|
1365
|
+
inFlightRef.current = false
|
|
1366
|
+
var now = (window.performance && performance.now) ? performance.now() : Date.now()
|
|
1367
|
+
lastMsRef.current = Math.max(0, now - t0)
|
|
1368
|
+
}
|
|
1276
1369
|
fetch(stateUrl()).then(function (r) { return r.ok ? r.json() : null }).then(function (d) {
|
|
1277
1370
|
if (d && d.ok) {
|
|
1278
1371
|
setData(d); setErr(''); reschedule(d)
|
|
@@ -1307,26 +1400,31 @@ window.__ModuleLoader__.load({
|
|
|
1307
1400
|
} catch (e) {}
|
|
1308
1401
|
} else if (d && !d.ok && d.runsAvailable === 0) { setData(null); if (d.runs) setRunsMeta(d.runs); setErr(t('还没有专家团 run。先运行 /team <task> 开一个。', 'No team run yet — run /team <task> first.')) }
|
|
1309
1402
|
else if (d && !d.ok) { if (d.runs) setRunsMeta(d.runs); setErr(d.error || ''); }
|
|
1310
|
-
}).catch(function () {})
|
|
1403
|
+
}).catch(function () {}).then(done, done)
|
|
1311
1404
|
}
|
|
1312
1405
|
// Adaptive refresh: while any member is running, poll fast (workers are
|
|
1313
|
-
// touching files right now); otherwise
|
|
1406
|
+
// touching files right now); otherwise the configured cadence.
|
|
1407
|
+
// 2026-09-15:再叠一层**退避** —— 间隔 = clamp(max(基础间隔, 上次耗时×2), 基础间隔, 30000)。
|
|
1408
|
+
// 单发越慢 ⇒ 下次越晚;display.pollMs 仍是基础节奏(设置里那一项依然说了算)。
|
|
1314
1409
|
function reschedule(d) {
|
|
1315
1410
|
var m = (d && d.members) || {}
|
|
1316
1411
|
var busy = Object.keys(m).some(function (k) { var v = m[k]; return v && typeof v === 'object' && (v.activity === 'running' || v.shortStatus === 'running') })
|
|
1317
1412
|
clearInterval(pollRef.current)
|
|
1318
|
-
|
|
1413
|
+
// 1.3.4:间隔来自设置 display.pollMs(忙碌时按同一意图加速到 40%,下限 300ms)
|
|
1414
|
+
var base = busy ? Math.max(300, Math.round(dispCfg.pollMs * 0.4)) : dispCfg.pollMs
|
|
1415
|
+
var backoff = Math.max(base, Math.min(30000, Math.round((lastMsRef.current || 0) * 2)))
|
|
1416
|
+
pollRef.current = setInterval(load, backoff)
|
|
1319
1417
|
}
|
|
1320
1418
|
useEffect(function () {
|
|
1321
1419
|
if (!isOpen && !viewMode) return undefined // panel closed & not canvas → no polling
|
|
1322
|
-
load(); pollRef.current = setInterval(load,
|
|
1420
|
+
load(); pollRef.current = setInterval(load, dispCfg.pollMs)
|
|
1323
1421
|
// catch up instantly when the user returns to the tab / window
|
|
1324
1422
|
var onVis = function () { if (document.visibilityState === 'visible') load() }
|
|
1325
1423
|
var onFocus = function () { load() }
|
|
1326
1424
|
document.addEventListener('visibilitychange', onVis)
|
|
1327
1425
|
window.addEventListener('focus', onFocus)
|
|
1328
1426
|
return function () { clearInterval(pollRef.current); document.removeEventListener('visibilitychange', onVis); window.removeEventListener('focus', onFocus) }
|
|
1329
|
-
}, [sessionId, selRunV, isOpen, viewMode])
|
|
1427
|
+
}, [sessionId, selRunV, isOpen, viewMode, dispCfg.pollMs])
|
|
1330
1428
|
|
|
1331
1429
|
// Make the docked panel reserve space (shift the shell frame) instead of
|
|
1332
1430
|
// covering the workspace; clears when closed or floating (md-preview trick).
|
|
@@ -1710,7 +1808,7 @@ window.__ModuleLoader__.load({
|
|
|
1710
1808
|
// 可画宽度三源:实测容器 → 全屏画布按窗口估 → 停靠侧栏宽度偏好。
|
|
1711
1809
|
// 「宽视图」开关终于有作用了:放宽单卡上限(否则 12 波里每波只有 2–3 人时,
|
|
1712
1810
|
// 卡片被 176px 封顶、右侧大片空白)。
|
|
1713
|
-
var baseW = flowW || (viewMode ? Math.max(320, ((typeof window !== 'undefined' && window.innerWidth) || 1200) - 340) : (typeof pw === 'number' ? pw :
|
|
1811
|
+
var baseW = flowW || (viewMode ? Math.max(320, ((typeof window !== 'undefined' && window.innerWidth) || 1200) - 340) : (typeof pw === 'number' ? pw : 420))
|
|
1714
1812
|
var avail = Math.max(240, baseW - 26)
|
|
1715
1813
|
var widest = 1
|
|
1716
1814
|
W.waves.forEach(function (w) { widest = Math.max(widest, w.members.length) })
|
|
@@ -2335,8 +2433,14 @@ window.__ModuleLoader__.load({
|
|
|
2335
2433
|
try {
|
|
2336
2434
|
var last = activityQ[0]
|
|
2337
2435
|
if (last && last.text === text && Date.now() - (last.ts || 0) < 4000) return
|
|
2338
|
-
|
|
2436
|
+
var entry = { icon: icon, text: String(text || '').slice(0, 90), ts: Date.now() }
|
|
2437
|
+
activityQ.unshift(entry)
|
|
2339
2438
|
if (activityQ.length > 8) activityQ.length = 8
|
|
2439
|
+
// 1.3.4:停留时长来自设置 `display.capsuleMs`(0 = 不自动消失)。
|
|
2440
|
+
// ⚠️ 上面那个 4000 是**同文本去重窗口**,不是停留时长 —— 旧实现把它当停留时长,
|
|
2441
|
+
// 于是"胶囊 N 毫秒后消失"这个功能**压根不存在**(只入队、永不自动出队,>8 才截断)。
|
|
2442
|
+
var ttl = EXPERT_DISPLAY.capsuleMs
|
|
2443
|
+
if (ttl > 0) setTimeout(function () { var i = activityQ.indexOf(entry); if (i >= 0) { activityQ.splice(i, 1); notify() } }, ttl)
|
|
2340
2444
|
notify()
|
|
2341
2445
|
} catch (e) {}
|
|
2342
2446
|
}
|
package/lib/command-parse.js
CHANGED
|
@@ -29,6 +29,8 @@ export const USAGE = [
|
|
|
29
29
|
' /team help 显示本用法(等价:? / -h / --help)',
|
|
30
30
|
' /team --persist <task> 启动持久化活团队(成员可反复指挥,跨会话恢复)',
|
|
31
31
|
' /team --no-code <task> 只产出计划/评审/测试工件,不改代码',
|
|
32
|
+
' /team --code <task> 显式要「代码 + 工件」(压过设置里的「交付口径 = 只出工件」)',
|
|
33
|
+
' /team --one-shot <task> 显式要「一次性组队」(压过设置里的「默认持久化活团队」)',
|
|
32
34
|
' /team --confirm <task> 大需求先确认:建 run 但**不自动派工**,面板点「执行」才开工',
|
|
33
35
|
' /team --tier <档位> <task> 指定流程档位:快速档 / 标准档 / 严格档(不写则在浮层三选一,每次都问)',
|
|
34
36
|
' /team tier <档位> [--run <run>] 运行中**升档**(快速档→标准档→严格档;不允许降档)',
|
|
@@ -145,8 +147,11 @@ export function parseTeamCommand(rawInput) {
|
|
|
145
147
|
// Consume leading flags; everything after the last flag is the task text.
|
|
146
148
|
const tokens = input.split(/\s+/);
|
|
147
149
|
let i = 0;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
// 三态(1.3.5):null = 未表态(听设置);true = 显式要;false = **显式不要**(--one-shot / --code)。
|
|
151
|
+
// 为什么必须三态:1.3.4 把设置接进"无 flag 时的默认"后就出现不对称 —— 设置成 persist=true 之后,
|
|
152
|
+
// 单条命令没有任何办法要一次性运行(旧代码的布尔默认把"没给 flag"和"显式否"混成同一个 false)。
|
|
153
|
+
let persist = null;
|
|
154
|
+
let noCode = null;
|
|
150
155
|
let roles = null;
|
|
151
156
|
let runName = null;
|
|
152
157
|
let profile = null;
|
|
@@ -156,7 +161,9 @@ export function parseTeamCommand(rawInput) {
|
|
|
156
161
|
while (i < tokens.length && tokens[i].startsWith('--')) {
|
|
157
162
|
const flag = tokens[i];
|
|
158
163
|
if (flag === '--persist') persist = true;
|
|
164
|
+
else if (flag === '--one-shot') persist = false;
|
|
159
165
|
else if (flag === '--no-code') noCode = true;
|
|
166
|
+
else if (flag === '--code') noCode = false;
|
|
160
167
|
else if (flag === '--allow-rebuild') allowRebuild = true;
|
|
161
168
|
else if (flag.startsWith('--roles')) {
|
|
162
169
|
const eq = flag.indexOf('=');
|
package/lib/command.js
CHANGED
|
@@ -1414,6 +1414,21 @@ function sessionRunsPath() { return join(dshHome(), 'expert-team', 'session-runs
|
|
|
1414
1414
|
* 旧数据没有 `via`;`runOwnerSession()` 会把"最早那条"当作归属兜底,并对
|
|
1415
1415
|
* 是否真的可信如实回报(见 `ownerResolved`)。
|
|
1416
1416
|
*/
|
|
1417
|
+
/**
|
|
1418
|
+
* 该 session id 是否**真实存在**(写侧守卫,2026-09-15)。
|
|
1419
|
+
*
|
|
1420
|
+
* 为什么需要:/state 允许带任意 `sessionId` 查询,而旧实现在"显式指定 run+workspace"时
|
|
1421
|
+
* **无条件** rememberSessionRun(sid, …) 落盘 —— 性能诊断时用一个**假 id** 就被写进了
|
|
1422
|
+
* `$DSH_HOME/expert-team/session-runs.json`(污染用户数据)。
|
|
1423
|
+
* 只查活存储(便宜、不读日志);查不到 ⇒ 当只读、**不写盘**。
|
|
1424
|
+
* 代价:已结束的会话"查看某 run"不再被记住(面板退回"按当前工作区自动选 run")——
|
|
1425
|
+
* 这是**有意**的取舍:宁可不记,也不写假 id。
|
|
1426
|
+
*/
|
|
1427
|
+
function sessionExists(ctx, sid) {
|
|
1428
|
+
const id = String(sid || '');
|
|
1429
|
+
if (!id) return false;
|
|
1430
|
+
try { return Boolean(ctx?.get?.('sessions')?.get?.(id)); } catch { return false; }
|
|
1431
|
+
}
|
|
1417
1432
|
function rememberSessionRun(sid, ws, runId, via) {
|
|
1418
1433
|
if (!sid || !ws || !runId) return;
|
|
1419
1434
|
const prev = SESSION_RUNS.get(sid);
|
|
@@ -1704,6 +1719,59 @@ function resolveLoopGuard(config, base) {
|
|
|
1704
1719
|
return LOOP_GUARD_ENABLED;
|
|
1705
1720
|
}
|
|
1706
1721
|
|
|
1722
|
+
// ── roster 三项:命令行**未显式给出**时用设置兜底(1.3.4 接线)──────────────────────────
|
|
1723
|
+
//
|
|
1724
|
+
// 为什么放在"建 run 的入口"而不是解析器里:`parseTeamCommand` 是**纯解析**(拿不到设置,也不该拿 ——
|
|
1725
|
+
// 它是被测得很细的纯函数)。把"flag 没给 ⇒ 取设置"放在 `createRun` 的**唯一落点**,才能同时覆盖
|
|
1726
|
+
// `/team`、浮层决策、`/decide` 等所有建 run 的入口,而不是每个入口各接一遍(那就有了第二个真源)。
|
|
1727
|
+
//
|
|
1728
|
+
// ⚠️ 键写成**限定路径**(`roster.persist` 等)不是装饰:`settings-consumers.test.mjs` 对**歧义叶子**
|
|
1729
|
+
// (`persist`/`deliverable`/`defaultRoles` 在本仓另有同名的不同概念,如 `mode.persist`、`--deliverable`)
|
|
1730
|
+
// **只认限定路径**命中的消费者 —— 写成 `r.persist` 会被判成"没有消费者",而那正是本仓最想避免的假绿。
|
|
1731
|
+
/** 读设置的 roster 三项(键 = spec 的限定路径;值已按 spec 归一)。 */
|
|
1732
|
+
export function rosterSettings() {
|
|
1733
|
+
const r = (currentSettings() || {}).roster || {};
|
|
1734
|
+
return {
|
|
1735
|
+
'roster.defaultRoles': Array.isArray(r.defaultRoles) && r.defaultRoles.length ? r.defaultRoles.slice() : null,
|
|
1736
|
+
'roster.deliverable': r.deliverable === 'artifacts-only' ? 'artifacts-only' : 'code+artifacts',
|
|
1737
|
+
'roster.persist': r.persist === true,
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
/**
|
|
1742
|
+
* 把设置落到一次建 run 的 `mode` 上:**flag > 设置 > 常量**。
|
|
1743
|
+
* 纯函数(设置与常量都由参数传入)⇒ 可单测;`createRun` 只负责调用它。
|
|
1744
|
+
*
|
|
1745
|
+
* @param mode - `parseTeamCommand` 的产物(`persist`/`noCode`/`roles` 都是"flag 给了才为真/非空")
|
|
1746
|
+
* @param rosterSet - `rosterSettings()` 的产物(可为 undefined)
|
|
1747
|
+
* @param fallbackRoles - 常量兜底班底(默认 `DEFAULT_ROLES`)
|
|
1748
|
+
* @returns `{ persist, noCode, roles, rolesBase, rolesFromSettings }`
|
|
1749
|
+
* - `rolesBase`:**档位收窄的基线**(设置了 `defaultRoles` 就用它,否则用常量);
|
|
1750
|
+
* - `rolesFromSettings`:告知调用方"这个班底来自设置、不是用户点名" ⇒ 档位收窄仍应生效。
|
|
1751
|
+
*/
|
|
1752
|
+
export function resolveRosterDefaults(mode, rosterSet, fallbackRoles) {
|
|
1753
|
+
const m = mode && typeof mode === 'object' ? mode : {};
|
|
1754
|
+
const base = Array.isArray(fallbackRoles) && fallbackRoles.length ? fallbackRoles.slice() : DEFAULT_ROLES.slice();
|
|
1755
|
+
const settingsRoles = rosterSet && Array.isArray(rosterSet['roster.defaultRoles']) && rosterSet['roster.defaultRoles'].length
|
|
1756
|
+
? rosterSet['roster.defaultRoles'].slice() : null;
|
|
1757
|
+
const explicitRoles = Array.isArray(m.roles) && m.roles.length ? m.roles.slice() : null;
|
|
1758
|
+
return {
|
|
1759
|
+
// 三态(1.3.5):true = 显式要;false = **显式不要**(--one-shot / --code 压过设置);
|
|
1760
|
+
// null/undefined = 未表态 ⇒ 听设置(见 command-parse.js 的三态注释)。
|
|
1761
|
+
persist: m.persist === true ? true : (m.persist === false ? false : Boolean(rosterSet && rosterSet['roster.persist'] === true)),
|
|
1762
|
+
noCode: m.noCode === true ? true : (m.noCode === false ? false : Boolean(rosterSet && rosterSet['roster.deliverable'] === 'artifacts-only')),
|
|
1763
|
+
roles: explicitRoles || settingsRoles,
|
|
1764
|
+
rolesBase: settingsRoles || base,
|
|
1765
|
+
rolesFromSettings: !explicitRoles && Boolean(settingsRoles),
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
/** 画布 live 模式的轮询间隔(毫秒):来自设置 `display.pollMs`(1.3.4 接线),越界回默认 3000。 */
|
|
1770
|
+
function canvasPollMs() {
|
|
1771
|
+
const n = Number(((currentSettings() || {}).display || {}).pollMs);
|
|
1772
|
+
return Number.isFinite(n) && n >= 1000 && n <= 60000 ? Math.round(n) : 3000;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1707
1775
|
/**
|
|
1708
1776
|
* 解析档位门是软还是硬。**非法值一律回默认(soft)**,不猜也不报错 ——
|
|
1709
1777
|
* 一个拼错的开关值把流程卡死(hard)比静默放行更糟,而"卡死"正是最贵的失败模式。
|
|
@@ -2672,6 +2740,53 @@ async function childSessionTiming(ctx, id) {
|
|
|
2672
2740
|
return out;
|
|
2673
2741
|
}
|
|
2674
2742
|
|
|
2743
|
+
/**
|
|
2744
|
+
* 子会话 header **索引**:只查表,**永不读日志**。
|
|
2745
|
+
*
|
|
2746
|
+
* 为什么必须有它(2026-09-15 性能诊断):/state 曾对**每个**子会话调 childSessionTiming,
|
|
2747
|
+
* 而它对"活存储里已查不到"的子代理会退回 sessionQuery.readSession(id) —— 那是**全量读该子会话日志**
|
|
2748
|
+
* (本机实测 21 条 = 23.6 MiB zstd / ~31,000 事件;别的 run 测到 75/92 条)。结果:端点热态 7.1–9.8 s、
|
|
2749
|
+
* 冷态 **283.6 s**,而面板绘制只要 52 ms;请求还压在事件循环上,把整个 dsh web 一起拖慢
|
|
2750
|
+
* (轻量端点 /settings 因此涨到 20.7 s / 42.5 s)。
|
|
2751
|
+
*
|
|
2752
|
+
* 三个来源都**便宜**,按"准且省"排序:
|
|
2753
|
+
* ① listSubagentStatusBySession 已经带出的 durable header 字段(createdAt/parentId/depth)
|
|
2754
|
+
* —— 那一支本来就要 listSessions(),字段是**顺带**拿到的,不额外读任何日志;
|
|
2755
|
+
* ② 活存储 ctx.sessions.get(id).header(子代理还在跑时最省);
|
|
2756
|
+
* ③ workflow 事件流的 startedAt(父会话日志已按 WF_EVENT_TTL_MS 缓存,见 workflowEventIndex)。
|
|
2757
|
+
* 三源都拿不到 ⇒ 保持 0/''/0,UI 按「无时间记录」**如实降级**(hasTimestamp:false)——
|
|
2758
|
+
* **不臆造**,也**绝不**为了"看起来有数据"退回全量读日志。
|
|
2759
|
+
*
|
|
2760
|
+
* @returns {Map<string, {createdAt:number, parentId:string, depth:number}>}
|
|
2761
|
+
*/
|
|
2762
|
+
function subHeaderIndex(ctx, subs, wfLabels) {
|
|
2763
|
+
const out = new Map();
|
|
2764
|
+
for (const s of Array.isArray(subs) ? subs : []) {
|
|
2765
|
+
const id = String((s && s.id) || '');
|
|
2766
|
+
if (!id) continue;
|
|
2767
|
+
let createdAt = Number(s.createdAt) || 0;
|
|
2768
|
+
let parentId = String(s.parentId || '');
|
|
2769
|
+
let depth = Number(s.depth) || 0;
|
|
2770
|
+
if (!createdAt) {
|
|
2771
|
+
try {
|
|
2772
|
+
const hdr = ctx?.get?.('sessions')?.get?.(id)?.header;
|
|
2773
|
+
if (hdr) {
|
|
2774
|
+
createdAt = Number(hdr.createdAt) || 0;
|
|
2775
|
+
if (!parentId) parentId = String(hdr.parentSession || '');
|
|
2776
|
+
if (!depth) depth = Number(hdr.delegationDepth) || 0;
|
|
2777
|
+
}
|
|
2778
|
+
} catch { /* best-effort */ }
|
|
2779
|
+
}
|
|
2780
|
+
if (!createdAt && wfLabels) {
|
|
2781
|
+
const w = typeof wfLabels.get === 'function' ? wfLabels.get(id) : wfLabels[id];
|
|
2782
|
+
const at = w && Number(w.startedAt);
|
|
2783
|
+
if (at) createdAt = at;
|
|
2784
|
+
}
|
|
2785
|
+
out.set(id, { createdAt, parentId, depth });
|
|
2786
|
+
}
|
|
2787
|
+
return out;
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2675
2790
|
async function listSubagentStatusBySession(ctx, sid, knownIds) {
|
|
2676
2791
|
const root = sid ? await rootSessionId(ctx, sid) : '';
|
|
2677
2792
|
let rows = [];
|
|
@@ -2849,7 +2964,11 @@ async function roleFromChildLog(ctx, id) {
|
|
|
2849
2964
|
* tick, while a run that finishes mid-session still converges within a tick.
|
|
2850
2965
|
* Best-effort: any read failure yields empty results (never throws).
|
|
2851
2966
|
*/
|
|
2852
|
-
|
|
2967
|
+
// ⚠️ 2026-09-15 性能诊断:TTL 原为 **3000ms,恰好等于默认轮询间隔 3000ms** ⇒ **每个 tick 必失效**,
|
|
2968
|
+
// 父会话日志(多 MB)被一遍遍全量重读,这是热态 7.1–9.8 s 的主要来源之一。现改为 30000ms:
|
|
2969
|
+
// TTL 只是**兜底**(理想是按日志 mtime/size 失效,留待后续),代价是"run 在会话中途结束"最多晚
|
|
2970
|
+
// 30 秒反映到面板 —— 与"每 3 秒重读整个日志"相比,这个交换值得。
|
|
2971
|
+
const WF_EVENT_TTL_MS = 30000;
|
|
2853
2972
|
const WF_EVENT_CACHE = new Map(); // sessionId -> { at, labels: Map, runs: [] }
|
|
2854
2973
|
async function workflowEventIndex(ctx, sid) {
|
|
2855
2974
|
const key = String(sid || '');
|
|
@@ -3092,9 +3211,9 @@ function buildCanvasHtml({ runId, goal, state, roster, tasks, subagents, dir, cw
|
|
|
3092
3211
|
<ol class="path" id="path">${stepper}</ol>
|
|
3093
3212
|
<section><h2>团队编制 <span class="live" id="live-ts"></span></h2><div class="cards" id="roster">${rosterCards || '<div class="empty">(暂无成员)</div>'}</div></section>
|
|
3094
3213
|
<section><h2>任务看板</h2><div class="board" id="board">${boardHtml || '<div class="empty">(暂无任务)</div>'}</div></section>
|
|
3095
|
-
<div class="hint">本画布由 <code>/team canvas</code> 动态生成(数据源:${esc(dir)})。任务/成员状态随各阶段推进更新;想刷新就再跑一次 <code>/team canvas</code>。${watch ?
|
|
3214
|
+
<div class="hint">本画布由 <code>/team canvas</code> 动态生成(数据源:${esc(dir)})。任务/成员状态随各阶段推进更新;想刷新就再跑一次 <code>/team canvas</code>。${watch ? ` <b>── 当前为 live 模式:每 ${Math.round(canvasPollMs() / 1000)} 秒轮询 host 状态接口自动刷新。</b>` : ''}</div>
|
|
3096
3215
|
</div>
|
|
3097
|
-
${watch ? watchScript(runId, cwd) : ''}
|
|
3216
|
+
${watch ? watchScript(runId, cwd, canvasPollMs()) : ''}
|
|
3098
3217
|
</body>
|
|
3099
3218
|
</html>`;
|
|
3100
3219
|
}
|
|
@@ -3103,7 +3222,9 @@ ${watch ? watchScript(runId, cwd) : ''}
|
|
|
3103
3222
|
// (md-preview-style host HTTP endpoint, avoiding typert Remotes) and live-updates
|
|
3104
3223
|
// the phase stepper, roster cards and task board. Falls back silently if the
|
|
3105
3224
|
// route is unavailable (e.g. webServer not mounted).
|
|
3106
|
-
function watchScript(runId, cwd) {
|
|
3225
|
+
function watchScript(runId, cwd, pollMs) {
|
|
3226
|
+
// 1.3.4:轮询间隔来自设置 `display.pollMs`(此前是写死的 3000)。
|
|
3227
|
+
const per = Number.isFinite(Number(pollMs)) && Number(pollMs) >= 1000 ? Math.round(Number(pollMs)) : 3000;
|
|
3107
3228
|
const base = '"/plugins/dsh-expert-team/state"';
|
|
3108
3229
|
const stateUrl = `(${base}+"?cwd="+encodeURIComponent(${JSON.stringify(cwd)})+"&run="+encodeURIComponent(${JSON.stringify(runId)}))`;
|
|
3109
3230
|
return `<script>
|
|
@@ -3134,7 +3255,7 @@ function watchScript(runId, cwd) {
|
|
|
3134
3255
|
}catch(e){}
|
|
3135
3256
|
}
|
|
3136
3257
|
function poll(){ fetch(URL).then(function(r){if(!r.ok)return null;return r.json();}).then(function(d){if(d)render(d);}).catch(function(){}); }
|
|
3137
|
-
poll(); setInterval(poll,
|
|
3258
|
+
poll(); setInterval(poll, ${per});
|
|
3138
3259
|
})();
|
|
3139
3260
|
</script>`;
|
|
3140
3261
|
}
|
|
@@ -4252,6 +4373,16 @@ async function settleRuns(cwd, runArg, ctx) {
|
|
|
4252
4373
|
|
|
4253
4374
|
async function createRun(ctx, invocation, cwd, mode) {
|
|
4254
4375
|
if (mode.profile) mode = resolveProfile(mode.profile, mode); // apply a named profile (roles/deliverable/note)
|
|
4376
|
+
// ── roster 三项兜底(1.3.4 接线):**flag > 设置 > 常量**,唯一落点 ────────────────────
|
|
4377
|
+
// 放在 `--profile` 之后:命名档位(`resolveProfile`)显式点过的 roles/deliverable 仍优先于设置,
|
|
4378
|
+
// 与"用户在命令里点名 > 控制台偏好"同一条纪律。
|
|
4379
|
+
const rosterSet = rosterSettings();
|
|
4380
|
+
const rd = resolveRosterDefaults(mode, rosterSet, DEFAULT_ROLES);
|
|
4381
|
+
mode.persist = rd.persist;
|
|
4382
|
+
mode.noCode = rd.noCode;
|
|
4383
|
+
mode.rolesBase = rd.rolesBase;
|
|
4384
|
+
mode.rolesFromSettings = rd.rolesFromSettings;
|
|
4385
|
+
if (rd.roles && !(mode.roles && mode.roles.length)) mode.roles = rd.roles;
|
|
4255
4386
|
// ── G 线(档位):先定档位,再建 run(档位要写进 TASK/ROSTER/STATE 三份工件)──────────
|
|
4256
4387
|
// ① 显式 `--tier` **认不出来就拒绝**:静默退回默认会让用户以为自己选的那个生效了 ——
|
|
4257
4388
|
// 这正是本仓反复复发的"沉默 ≠ 允许"(SKILL §7.29)。
|
|
@@ -4275,8 +4406,11 @@ async function createRun(ctx, invocation, cwd, mode) {
|
|
|
4275
4406
|
// ③ **档位真正省派工的地方**:显式 `--tier` 且用户没点过角色名 ⇒ 按档定班底。
|
|
4276
4407
|
// (没显式指定时**不**在这里收窄:档位选择发生在浮层,用户选完那一刻才收窄 —— 见 /decide 路由。)
|
|
4277
4408
|
// `--roles` / `--profile` 点过名的,一律不动(`narrowedRoles` 只看"是不是基线班底")。
|
|
4278
|
-
|
|
4279
|
-
|
|
4409
|
+
// 1.3.4:班底来自**设置**(`roster.defaultRoles`)时也算"基线" ⇒ 收窄仍应生效,
|
|
4410
|
+
// 只是基线换成设置里那套(否则"设了默认班底"会让档位收窄静默失效)。
|
|
4411
|
+
if (pickedTier && (!(mode.roles && mode.roles.length) || mode.rolesFromSettings)) {
|
|
4412
|
+
const narrowBase = mode.rolesBase || DEFAULT_ROLES;
|
|
4413
|
+
const next = narrowedRoles(pickedTier, (mode.roles && mode.roles.length ? mode.roles : narrowBase), narrowBase);
|
|
4280
4414
|
if (next) { mode.roles = next; mode.rolesNarrowedBy = pickedTier; }
|
|
4281
4415
|
}
|
|
4282
4416
|
// 批 0-3:planDiscarded **代码强制** —— 用户明确丢弃过的同目标团队,不得由 lead 自动重建。
|
|
@@ -4515,7 +4649,7 @@ function warnLeadToolFaceOnce(status, detail) {
|
|
|
4515
4649
|
LEAD_TOOLFACE_WARNED.add(status);
|
|
4516
4650
|
console.warn(`[expert-team] lead 工具面**未**收窄(${status}):${detail}`);
|
|
4517
4651
|
}
|
|
4518
|
-
export const _live = { pushActivity, phaseAccountingViolations, loggedPhases, authorityViolations, WRITE_TRACER, createWriteTracer, formatConflict, summarizeTool, parseLogLine, roleOfSub, mapRoleToSub, membersFromState, buildRoleSubMap, resolveSubRoles, childSessionTiming, SUB_HEADER_CACHE, workflowEventIndex, workflowChildLabels, workflowChildMeta, workflowRuns, WF_EVENT_CACHE, rememberSessionRun, sessionRunFor, runOwnerSession, SESSION_RUNS, parseTeamCommand, deriveMemberEntries, schemaViolations, runHealth, RUN_STALL_MS, scaffoldFingerprint, SCAFFOLD_REQUIRED, strandedTasks, settleStranded, IN_FLIGHT_STATUSES, normalizeCoverage, SCHEMA_WARN_SEEN, pushActivityEvent, DEFAULT_LIMITS, LIMITS, resolveLimits, capacityViolations, DEFAULT_ROUND_LIMITS, ROUND_LIMITS, ROUND_LIMIT_ENV, resolveRoundLimits, ROUND_LIMIT_OF_KIND, roundOf, isQualityTask, normTitle, roundLimitViolations, reworkLoopWriteGuard, mutateTasks, readStandingRules, appendStandingRule, rulesRun, scopeOverlapWarnings, applyTaskStatus, waitRun, eventFamily, verdictFromToken, normalizeRoleName, truncateCodepoints, filterRunScopedSubs, runCreatedAtMs, runLogTail, liveFiles, LIVE_FILES_CACHE, DEFAULT_ROLES, resolveTierGate, TIER_GATE_ENV, snapshotRun, settingsPath, loadSettingsSync, currentSettings, limitsBaseFromSettings, roundLimitsBaseFromSettings, effectiveTierGate: () => TIER_GATE, ensureSkillInstalled, ensurePresetInstalled, uninstallInstalled, buildSkillRegistration, parseSkillMarkdown, PLUGIN_VERSION, INSTALL_STAMP, runtimeSkillRegistered: () => RUNTIME_SKILL_REGISTERED, agentScopedToolNames, warnLeadToolFaceOnce, resolveLeadToolFace, LEAD_TOOLFACE_ENV, effectiveLeadToolFace: () => LEAD_TOOLFACE, resolveLoopGuard, LOOP_GUARD_ENV, effectiveLoopGuard: () => LOOP_GUARD_ENABLED, installHostSettings, hostValues, hostScope, hostSettingsNote, updateHostSettings, pickFileOnly, pickHostExpressible, buildHostSchema, hostBase, hostSchemaPaths, reapplySettingsDerived, currentSettings, mergeSettings };
|
|
4652
|
+
export const _live = { pushActivity, phaseAccountingViolations, loggedPhases, authorityViolations, WRITE_TRACER, createWriteTracer, formatConflict, summarizeTool, parseLogLine, roleOfSub, mapRoleToSub, membersFromState, buildRoleSubMap, resolveSubRoles, childSessionTiming, subHeaderIndex, sessionExists, SUB_HEADER_CACHE, workflowEventIndex, workflowChildLabels, workflowChildMeta, workflowRuns, WF_EVENT_CACHE, rememberSessionRun, sessionRunFor, runOwnerSession, SESSION_RUNS, parseTeamCommand, deriveMemberEntries, schemaViolations, runHealth, RUN_STALL_MS, scaffoldFingerprint, SCAFFOLD_REQUIRED, strandedTasks, settleStranded, IN_FLIGHT_STATUSES, normalizeCoverage, SCHEMA_WARN_SEEN, pushActivityEvent, DEFAULT_LIMITS, LIMITS, resolveLimits, capacityViolations, DEFAULT_ROUND_LIMITS, ROUND_LIMITS, ROUND_LIMIT_ENV, resolveRoundLimits, ROUND_LIMIT_OF_KIND, roundOf, isQualityTask, normTitle, roundLimitViolations, reworkLoopWriteGuard, mutateTasks, readStandingRules, appendStandingRule, rulesRun, scopeOverlapWarnings, applyTaskStatus, waitRun, eventFamily, verdictFromToken, normalizeRoleName, truncateCodepoints, filterRunScopedSubs, runCreatedAtMs, runLogTail, liveFiles, LIVE_FILES_CACHE, DEFAULT_ROLES, resolveTierGate, TIER_GATE_ENV, snapshotRun, settingsPath, loadSettingsSync, currentSettings, limitsBaseFromSettings, roundLimitsBaseFromSettings, effectiveTierGate: () => TIER_GATE, ensureSkillInstalled, ensurePresetInstalled, uninstallInstalled, buildSkillRegistration, parseSkillMarkdown, PLUGIN_VERSION, INSTALL_STAMP, runtimeSkillRegistered: () => RUNTIME_SKILL_REGISTERED, agentScopedToolNames, warnLeadToolFaceOnce, resolveLeadToolFace, LEAD_TOOLFACE_ENV, effectiveLeadToolFace: () => LEAD_TOOLFACE, resolveLoopGuard, LOOP_GUARD_ENV, effectiveLoopGuard: () => LOOP_GUARD_ENABLED, resolveRosterDefaults, rosterSettings, createRun, watchScript, canvasPollMs, installHostSettings, hostValues, hostScope, hostSettingsNote, updateHostSettings, pickFileOnly, pickHostExpressible, buildHostSchema, hostBase, hostSchemaPaths, reapplySettingsDerived, currentSettings, mergeSettings };
|
|
4519
4653
|
|
|
4520
4654
|
export function apply(ctx, config) {
|
|
4521
4655
|
// 留一份 config:设置在运行时改变(官方面板 / 浮层)时要**用同一份 config** 重算上限与档位门,
|
|
@@ -4804,7 +4938,8 @@ export function apply(ctx, config) {
|
|
|
4804
4938
|
let sel = null;
|
|
4805
4939
|
if (runParam && wsParam) {
|
|
4806
4940
|
sel = await snapshotRun(wsParam, runParam);
|
|
4807
|
-
|
|
4941
|
+
// 只在**该会话真实存在**时才记忆(假 id 不得落盘,见 sessionExists)
|
|
4942
|
+
if (sel && sessionExists(ctx, sid)) rememberSessionRun(sid, wsParam, runParam, 'view'); // user deliberately picked a run
|
|
4808
4943
|
} else if (runParam && workspaces.length === 1) {
|
|
4809
4944
|
sel = await snapshotRun(workspaces[0], runParam);
|
|
4810
4945
|
} else {
|
|
@@ -4881,10 +5016,8 @@ export function apply(ctx, config) {
|
|
|
4881
5016
|
if (membersFromState(sel.stateMembers).byRole.size) {
|
|
4882
5017
|
subById = buildRoleSubMap(subs, sel.stateMembers, wfLabels); // 已登记:不过滤、不丢
|
|
4883
5018
|
} else {
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
try { timingOfSubs.set(String(s.id || ''), await childSessionTiming(ctx, s.id)); } catch { /* best-effort */ }
|
|
4887
|
-
}
|
|
5019
|
+
// 只查表、不读日志(热点 D 修复):见 subHeaderIndex 的注释。
|
|
5020
|
+
const timingOfSubs = subHeaderIndex(ctx, subs, wfLabels);
|
|
4888
5021
|
const scoped = filterRunScopedSubs(subs, {
|
|
4889
5022
|
wfLabels,
|
|
4890
5023
|
runId: sel.runId,
|
|
@@ -4944,12 +5077,14 @@ export function apply(ctx, config) {
|
|
|
4944
5077
|
}
|
|
4945
5078
|
// 时间维度(任何"分批/波次"视图都需要):createdAt 取自子会话 header,
|
|
4946
5079
|
// lastEventAt 复用下面已收集的 per-agent feed 最后一条事件时间(零额外开销)。
|
|
4947
|
-
//
|
|
4948
|
-
//
|
|
4949
|
-
//
|
|
4950
|
-
//
|
|
5080
|
+
// 走 subHeaderIndex(**只查表**):已结束的子代理会被从活存储里清掉,但它们的
|
|
5081
|
+
// durable header 字段由 listSubagentStatusBySession 的 listSessions() 分支
|
|
5082
|
+
// **顺带**带出来了(createdAt/parentId/depth),所以不需要再读一次日志。
|
|
5083
|
+
// 2026-09-15:此前这里对每个子会话 await childSessionTiming(会全量 readSession),
|
|
5084
|
+
// 是 /state 冷态 283.6 s 的根因。不臆造:三源都拿不到就保持 0,UI 如实降级。
|
|
5085
|
+
const timingIdx = subHeaderIndex(ctx, subs, wfLabels);
|
|
4951
5086
|
const timingOf = {};
|
|
4952
|
-
for (const s of subs) timingOf[String(s.id || '')] =
|
|
5087
|
+
for (const s of subs) timingOf[String(s.id || '')] = timingIdx.get(String(s.id || '')) || { createdAt: 0, parentId: '', depth: 0 };
|
|
4953
5088
|
sel.agents = subs.map((s) => {
|
|
4954
5089
|
const sid2 = String(s.id || '');
|
|
4955
5090
|
const t = timingOf[sid2] || { createdAt: 0, parentId: '', depth: 0 };
|
|
@@ -5469,10 +5604,13 @@ export function apply(ctx, config) {
|
|
|
5469
5604
|
|
|
5470
5605
|
// ── F 线:设置控制台路由(GET 读 / POST 写)──────────────────────────────
|
|
5471
5606
|
// 走 `withRoute`(B 线第 9 项抽出的共享件):方法校验 / 405 空体 / 500 兜底一条不落。
|
|
5472
|
-
// **诚实边界(2026-09-15
|
|
5473
|
-
// 类即时生效"——
|
|
5474
|
-
//
|
|
5475
|
-
//
|
|
5607
|
+
// **诚实边界(2026-09-15 更正 → 1.3.4 接线完成)**:此前这里写"`roster`/`gates` 两类改完要
|
|
5608
|
+
// 重启、`display` 类即时生效"—— 当时两句都不实。现状(都已在测试里钉住):
|
|
5609
|
+
// · `roster`/`gates`:由 `reapplySettingsDerived()` 当场重算,`roster.defaultRoles` 等在建 run
|
|
5610
|
+
// 时读(见 `resolveRosterDefaults()`)⇒ **无需重启**;
|
|
5611
|
+
// · `display.*`:1.3.4 起**真的有消费者** —— 浮层/面板由 `client.js` 的 `useDisplaySettings()`
|
|
5612
|
+
// (单一设置桥 `EXPERT_DISPLAY`)读,画布 live 轮询由本文件的 `canvasPollMs()` 读。
|
|
5613
|
+
// 响应里不声称任何一类需要重启(1.3.2 起 `needsRestart` 恒 false)。
|
|
5476
5614
|
const disposeSettings = registerLocal({
|
|
5477
5615
|
kind: 'exact',
|
|
5478
5616
|
path: '/plugins/dsh-expert-team/settings',
|
package/lib/settings.js
CHANGED
|
@@ -62,36 +62,29 @@ export const SETTINGS_SPEC = {
|
|
|
62
62
|
},
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
// ── 「暂未生效」的项:**单一真源**(
|
|
65
|
+
// ── 「暂未生效」的项:**单一真源**(1.3.2 建立 → 1.3.4 接线完成,故现在为空)──────────
|
|
66
66
|
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
67
|
+
// 这张表是**两处消费的同一个真源**:
|
|
68
|
+
// ① `decorateHint()` 把标记追加到 hint(设置页里用户可见);
|
|
69
|
+
// ② `settings-consumers.test.mjs` 用它当白名单,语义是**集合相等** ——
|
|
70
|
+
// 新增一个没有消费者的设置项 ⇒ 红;接线后忘了删这一行 ⇒ 也红(清单只减不增)。
|
|
71
71
|
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
72
|
+
// 历史:1.3.2 的审计(两轮独立只读核验)发现 20 个设置项里 **10 项的设置值没有任何消费者**。
|
|
73
|
+
// 当时处理了 3 项(`gates.boundaryTasks`/`gates.boundarySpec` 的行为本就**无条件强制**,直接删 spec;
|
|
74
|
+
// `gates.loopGuard` 是"接错了源",已接线),剩下 7 项(display 4 + roster 3)登记在此、界面标注。
|
|
75
|
+
// **1.3.4 把这 7 项全部接线** ⇒ 表空 = "零装饰性开关"的证明。机制保留:将来若又要"先上表盘、后做行为",
|
|
76
|
+
// 把限定路径写进来即可(界面标注与 ratchet 白名单会自动跟上;接线后删掉那一行,两处同时消失)。
|
|
75
77
|
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
export const INERT_SETTINGS = {
|
|
82
|
-
'display.pollMs': '现值硬编码在浮层(client.js:1317 的 1200/3000)',
|
|
83
|
-
'display.capsuleMs': '现值硬编码在浮层(client.js:2336 的 4000)',
|
|
84
|
-
'display.panelWidth': '现值来自 localStorage 的 `et-width`(client.js:309),与设置无关',
|
|
85
|
-
'display.defaultTab': '现值硬编码为 team(client.js:1198)',
|
|
86
|
-
'roster.deliverable': '现由 `/team --no-code` 派生(mode.noCode),设置值不参与',
|
|
87
|
-
'roster.persist': '现仅由 `/team --persist` 置位(mode.persist),设置值不参与',
|
|
88
|
-
'roster.defaultRoles': '现用常量 DEFAULT_ROLES 或 `--roles`,设置值不参与',
|
|
89
|
-
};
|
|
78
|
+
// ⚠️ 两条文案纪律(1.3.4 的事故换来的):
|
|
79
|
+
// · **不承诺版本号** —— 1.3.2 写"待 1.3.3 接线",而 1.3.3 发布时并未接线 ⇒ 那句话本身成了假承诺;
|
|
80
|
+
// · **不写行号** —— 行号会腐烂(实测登记后不久就偏移),且对用户毫无意义;
|
|
81
|
+
// 要留证据就写进紧邻的**代码注释**(本仓注释里保留完整出处)。
|
|
82
|
+
export const INERT_SETTINGS = {};
|
|
90
83
|
|
|
91
|
-
/** 「暂未生效」标记文本(只此一份,`decorateHint`
|
|
84
|
+
/** 「暂未生效」标记文本(只此一份,`decorateHint` 是唯一加工点)。**版本中立**。 */
|
|
92
85
|
export function inertMark(path) {
|
|
93
86
|
const why = INERT_SETTINGS[String(path)];
|
|
94
|
-
return why ?
|
|
87
|
+
return why ? `(暂未生效:尚未接线 —— ${why})` : '';
|
|
95
88
|
}
|
|
96
89
|
|
|
97
90
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yangdcm/dsh-expert-team",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "dsh「专家团」bundle:一句自然语言自动组建/持久化一支 12 角色多智能体团队,共享工作区协议 + 阶段门控编排 + 结构化交接 + 质量门禁/自动调度,实现者直接改代码并产出持久工件;带 live 团队浮层(质量门禁/覆盖率/工件预览)。 · Role-based multi-agent expert team for DeepSeek Harness: one sentence in, a staged and gated team delivery out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"check:name": "node scripts/rename-package.mjs --check",
|
|
85
85
|
"test:regression": "node regression.test.mjs",
|
|
86
86
|
"test:e2e": "node e2e.test.mjs",
|
|
87
|
-
"test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs",
|
|
87
|
+
"test:all": "node bootstrap.test.mjs && node host-settings.test.mjs && node smoke.test.mjs && node regression.test.mjs && node e2e.test.mjs && node flow.test.mjs && node plan-decide.test.mjs && node plan-discard.test.mjs && node models-honesty.test.mjs && node evidence-gate.test.mjs && node sync-gate.test.mjs && node artifact-writer.test.mjs && node dag.test.mjs && node metrics.test.mjs && node broken-chain.test.mjs && node schema-warn.test.mjs && node template-copy.test.mjs && node task-binding.test.mjs && node role-merge.test.mjs && node wf-recovery.test.mjs && node panorama.test.mjs && node run-ownership.test.mjs && node role-identity.test.mjs && node command-parse.test.mjs && node member-registry.test.mjs && node schema-check.test.mjs && node run-health.test.mjs && node cordis-fs-port.test.mjs && node scaffold-fingerprint.test.mjs && node preset-lint.test.mjs && node stranded-tasks.test.mjs && node agent-scope-guard.test.mjs && node schema-warn-noise.test.mjs && node tool-card-status.test.mjs && node dag-status.test.mjs && node capacity-limits.test.mjs && node task-cas.test.mjs && node write-lock.test.mjs && node card-stall.test.mjs && node multi-session-and-confirm.test.mjs && node standing-rules.test.mjs && node scope-overlap.test.mjs && node task-cancel-protect.test.mjs && node wait-run.test.mjs && node rework-loop.test.mjs && node client-css-integrity.test.mjs && node interception.test.mjs && node mutation-catalog.test.mjs && node write-bypass-ratchet.test.mjs && node asi-hazards.test.mjs && node dispatch-contract.test.mjs && node closure-ledger.test.mjs && node rework-nature.test.mjs && node loop-guard.test.mjs && node vocab-consistency.test.mjs && node state-no-write.test.mjs && node routes-shared.test.mjs && node metrics-render.test.mjs && node metrics-collect.test.mjs && node first-runnable.test.mjs && node closing-budget.test.mjs && node scan-single-source.test.mjs && node tier.test.mjs && node dispatch-ledger.test.mjs && node tier-gate.test.mjs && node tier-badge.test.mjs && node validate-module.test.mjs && node settings.test.mjs && node settings-page.test.mjs && node policy.test.mjs && node phase-accounting.test.mjs && node log-parse-module.test.mjs && node command-parse-module.test.mjs && node authority.test.mjs && node write-tracer.test.mjs && node token-accounting.test.mjs && node lead-toolface.test.mjs && node settings-consumers.test.mjs && node settings-wiring.test.mjs && node state-perf-guard.test.mjs",
|
|
88
88
|
"test:toolcard": "node tool-card-status.test.mjs",
|
|
89
89
|
"test:flow": "node flow.test.mjs",
|
|
90
90
|
"test:decide": "node plan-decide.test.mjs",
|