dsh-team-limits 1.0.1 → 1.3.4
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 +47 -0
- package/README.md +24 -24
- package/client.js +385 -0
- package/cordis.patch.yml +7 -18
- package/index.js +199 -6
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.4
|
|
4
|
+
|
|
5
|
+
- The panel description no longer explains where the values are stored. It names the four limits
|
|
6
|
+
and stops; the page itself shows how to edit them.
|
|
7
|
+
|
|
8
|
+
## 1.3.3
|
|
9
|
+
|
|
10
|
+
- Dropped the "nothing else is touched" clause from the panel description in both languages. The
|
|
11
|
+
description now states what the page does and stops there.
|
|
12
|
+
|
|
13
|
+
## 1.3.2
|
|
14
|
+
|
|
15
|
+
- The panel description now says what the bundle does (adjust the Agent Team limits from the plugin
|
|
16
|
+
page, written onto the agent-team row) instead of quoting one fixed set of numbers, which had
|
|
17
|
+
been stale since 1.2.0 turned the limits into editable fields.
|
|
18
|
+
- The README follows the same wording: it points at Settings → Plugins → Installed → Team limits
|
|
19
|
+
and no longer claims a Save button or a standalone Settings page.
|
|
20
|
+
|
|
21
|
+
## 1.3.1
|
|
22
|
+
|
|
23
|
+
- The form now lives on the plugin's own page: opening "Team limits" in Plugins → Installed shows
|
|
24
|
+
the four fields right below the bundle description, through the native `plugins.bundle.config`
|
|
25
|
+
slot keyed by the package name. The extra tab in the Plugins section is gone.
|
|
26
|
+
- The compact `summary` view of the same slot renders one line (members · tasks · bytes · ms)
|
|
27
|
+
instead of the whole form.
|
|
28
|
+
|
|
29
|
+
## 1.3.0
|
|
30
|
+
|
|
31
|
+
- The page now lives where the rest of a plugin's UI lives: **Settings → Plugins → Team limits**,
|
|
32
|
+
as a tab of the Plugins section, instead of a standalone row in the Settings sidebar.
|
|
33
|
+
- The page was rebuilt to match the shipped settings pages: a title and one-line introduction,
|
|
34
|
+
section headings, two columns, and per-field "Overridden" markers with a "Restore default"
|
|
35
|
+
action. Edits are applied as you type (debounced), so there is no Save button.
|
|
36
|
+
- The four numbers are grouped as "Runtime limits" (members, tasks) and "Messages and teardown"
|
|
37
|
+
(message bytes, disposal timeout).
|
|
38
|
+
|
|
39
|
+
## 1.2.0
|
|
40
|
+
|
|
41
|
+
- New: a real settings page, **Settings → Team limits**, with four number fields, Save and
|
|
42
|
+
Restore defaults. Values are written onto the shipped `agent-team` row through the host's
|
|
43
|
+
config editor instead of being frozen in the bundle patch.
|
|
44
|
+
- The bundle patch no longer overrides `agent-team`; it only mounts the row, so a DSH upgrade
|
|
45
|
+
that changes the shipped defaults no longer fights with stale numbers in this file.
|
|
46
|
+
- Added the browser half (`client.js`, `dsh.client`) and a pinned `maxMembers: 32` starting
|
|
47
|
+
point; the page still lives in the harness Settings shell, so nothing renders in
|
|
48
|
+
terminal-only profiles.
|
|
49
|
+
|
|
3
50
|
## 1.0.1
|
|
4
51
|
|
|
5
52
|
- Added a real entry artifact (`index.js`) and `main`/`exports`, fixing the plugin-manager
|
package/README.md
CHANGED
|
@@ -2,36 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
**English** · [简体中文](#简体中文)
|
|
4
4
|
|
|
5
|
-
Optional companion bundle for [dsh-subagent-control](https://github.com/SpaceRelay/dsh-subagent-control):
|
|
5
|
+
Optional companion bundle for [dsh-subagent-control](https://github.com/SpaceRelay/dsh-subagent-control): it adds editable **Team limits** fields to the plugin's own page (Settings → Plugins → Installed → Team limits), so the Agent Team limits are number fields you can change and reset — instead of values frozen in a patch file.
|
|
6
6
|
|
|
7
7
|
## English
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### What it does
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
DeepSeek Harness ships the Agent Team plugin with these limits:
|
|
12
|
+
|
|
13
|
+
| Setting | Shipped default | This page starts at |
|
|
12
14
|
| --- | --- | --- |
|
|
13
15
|
| `maxMembers` | 16 | **32** |
|
|
14
16
|
| `maxTasks` | 256 | **512** |
|
|
15
17
|
| `maxMessageBytes` | 65536 | **131072** |
|
|
16
|
-
| `disposalTimeoutMs` | 5000 | 5000
|
|
17
|
-
|
|
18
|
-
This is not a plugin with tools. It is a bundle whose patch targets the existing `agent-team` row and replaces its `config`. Nothing changes until you install it, which is the point: raising somebody else's limits is a deliberate choice, not a default.
|
|
18
|
+
| `disposalTimeoutMs` | 5000 | 5000 |
|
|
19
19
|
|
|
20
|
-
The
|
|
20
|
+
The page has four number fields. Edits are written onto the shipped `agent-team` row through the host's config editor as you type (debounced, no Save button), and each field offers **Restore default** once it differs from the shipped value. The config editor persists the values in the profile patch layer and applies them on the normal Loader path. The bundle's own patch therefore holds no numbers, so a DSH upgrade that changes the shipped defaults does not silently resurrect old values.
|
|
21
21
|
|
|
22
22
|
### Before you install
|
|
23
23
|
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **Raising limits is not free.** More members means more concurrent model sessions; each one costs tokens and a session directory. Raise only what you
|
|
27
|
-
-
|
|
24
|
+
- **Restart DSH after installing**, then open Settings → Plugins → Installed → Team limits; the fields sit right below the bundle description.
|
|
25
|
+
- **Values live in the profile patch layer.** Uninstalling the bundle does not roll them back; set the fields back to the shipped defaults (or remove the `agent-team` override in the profile patch) if you want the stock numbers again.
|
|
26
|
+
- **Raising limits is not free.** More members means more concurrent model sessions; each one costs tokens and a session directory. Raise only what you need.
|
|
27
|
+
- The client page needs the harness browser bundle; nothing is rendered in terminal-only profiles, but the limits still apply.
|
|
28
28
|
|
|
29
29
|
### Install
|
|
30
30
|
|
|
31
31
|
Through the plugin panel / market, or by asking the agent:
|
|
32
32
|
|
|
33
33
|
```
|
|
34
|
-
plugin_manager install_bundle target "dsh-team-limits@1.
|
|
34
|
+
plugin_manager install_bundle target "dsh-team-limits@1.3.2"
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
### License
|
|
@@ -42,34 +42,34 @@ MIT — see [LICENSE](./LICENSE). Issues: <https://github.com/SpaceRelay/dsh-sub
|
|
|
42
42
|
|
|
43
43
|
## 简体中文
|
|
44
44
|
|
|
45
|
-
这是 [dsh-subagent-control](https://github.com/SpaceRelay/dsh-subagent-control)
|
|
45
|
+
这是 [dsh-subagent-control](https://github.com/SpaceRelay/dsh-subagent-control) 的可选同伴包:在**插件自己的页面**上(设置 → 插件 → 已安装 → 团队限额)加一组可编辑的「团队限额」字段,把 Agent Team 的上限变成能直接填、能恢复默认的数字输入框,而不是写死在补丁文件里的值。
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
### 它做什么
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Agent Team 出厂限制如下:
|
|
50
|
+
|
|
51
|
+
| 配置 | 出厂默认 | 本页面初始值 |
|
|
50
52
|
| --- | --- | --- |
|
|
51
53
|
| `maxMembers` | 16 | **32** |
|
|
52
54
|
| `maxTasks` | 256 | **512** |
|
|
53
55
|
| `maxMessageBytes` | 65536 | **131072** |
|
|
54
|
-
| `disposalTimeoutMs` | 5000 | 5000
|
|
55
|
-
|
|
56
|
-
它不是一个带工具的插件,而是一个**只做覆盖的 bundle**:补丁指向现有的 `agent-team` 行,整体替换它的 `config`。不安装就完全不生效——把别人的上限改高应该是主动选择,不是默认行为。
|
|
56
|
+
| `disposalTimeoutMs` | 5000 | 5000 |
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
页面有四个数字输入框。改动边输边写(防抖,没有保存按钮),每个字段一旦偏离出厂值就会出现**恢复默认**。宿主侧的配置编辑器会把这些值写到自带的 `agent-team` 行上,持久化进 profile 的补丁层,并按正常加载流程生效。因此本包自己的补丁里不再存数值:DSH 升级改变了出厂默认,也不会悄悄把旧数字带回来。
|
|
59
59
|
|
|
60
60
|
### 安装前要知道
|
|
61
61
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
- **提高上限不是免费的。** 队友更多意味着更多并发的模型会话,每个都消耗 token
|
|
65
|
-
-
|
|
62
|
+
- **安装后重启 DSH**,然后打开 设置 → 插件 → 已安装 → 团队限额;输入框就在插件描述下面。
|
|
63
|
+
- **数值存在 profile 的补丁层里。** 卸载本包不会把它们撤回;要恢复出厂值,请在页面上点「恢复默认」,或删除 profile 补丁里 `agent-team` 那条覆盖。
|
|
64
|
+
- **提高上限不是免费的。** 队友更多意味着更多并发的模型会话,每个都消耗 token 与一个会话目录;按实际需要调。
|
|
65
|
+
- 这个页面依赖 Harness 的浏览器端;纯终端 profile 不渲染页面,但限额照样生效。
|
|
66
66
|
|
|
67
67
|
### 安装
|
|
68
68
|
|
|
69
69
|
在插件面板 / 市场中安装,或者让智能体执行:
|
|
70
70
|
|
|
71
71
|
```
|
|
72
|
-
plugin_manager install_bundle target "dsh-team-limits@1.
|
|
72
|
+
plugin_manager install_bundle target "dsh-team-limits@1.3.2"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
### 许可
|
package/client.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: 'dsh-team-limits',
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} }
|
|
5
|
+
var exports = module.exports
|
|
6
|
+
|
|
7
|
+
var React = require('react')
|
|
8
|
+
var h = React.createElement
|
|
9
|
+
|
|
10
|
+
var NS = 'team-limits'
|
|
11
|
+
|
|
12
|
+
var DEFAULTS = {
|
|
13
|
+
maxMembers: 32,
|
|
14
|
+
maxTasks: 512,
|
|
15
|
+
maxMessageBytes: 131072,
|
|
16
|
+
disposalTimeoutMs: 5000,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var SECTIONS = [
|
|
20
|
+
{
|
|
21
|
+
id: 'runtime',
|
|
22
|
+
zh: '运行限制',
|
|
23
|
+
en: 'Runtime limits',
|
|
24
|
+
zhNote: '一个团队同时能容纳的成员数与任务板容量。',
|
|
25
|
+
enNote: 'How many members a team holds and how many tasks its board keeps.',
|
|
26
|
+
fields: [
|
|
27
|
+
{
|
|
28
|
+
key: 'maxMembers',
|
|
29
|
+
zh: '团队人数上限',
|
|
30
|
+
en: 'Maximum team members',
|
|
31
|
+
zhHelp: '含你(Lead)在内的成员总数上限。',
|
|
32
|
+
enHelp: 'Members per team, the Lead included.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: 'maxTasks',
|
|
36
|
+
zh: '任务数上限',
|
|
37
|
+
en: 'Maximum tasks',
|
|
38
|
+
zhHelp: '共享任务板上同时保留的任务数。',
|
|
39
|
+
enHelp: 'Tasks the shared board keeps at once.',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'messaging',
|
|
45
|
+
zh: '消息与清理',
|
|
46
|
+
en: 'Messages and teardown',
|
|
47
|
+
zhNote: '单条消息的体积上限,以及成员停止时的等待时间。',
|
|
48
|
+
enNote: 'Upper bound for one message, and how long teardown waits.',
|
|
49
|
+
fields: [
|
|
50
|
+
{
|
|
51
|
+
key: 'maxMessageBytes',
|
|
52
|
+
zh: '单条消息上限(字节)',
|
|
53
|
+
en: 'Maximum message size (bytes)',
|
|
54
|
+
zhHelp: '131072 字节 = 128 KB。',
|
|
55
|
+
enHelp: '131072 bytes = 128 KB.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: 'disposalTimeoutMs',
|
|
59
|
+
zh: '清理超时(毫秒)',
|
|
60
|
+
en: 'Disposal timeout (ms)',
|
|
61
|
+
zhHelp: '成员停止时最多等待多久再强制结束。',
|
|
62
|
+
enHelp: 'How long teardown waits before forcing the stop.',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
function isZh() {
|
|
69
|
+
try {
|
|
70
|
+
return (navigator.language || '').toLowerCase().indexOf('zh') === 0
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var COPY = {
|
|
77
|
+
title: ['团队限额', 'Team limits'],
|
|
78
|
+
intro: [
|
|
79
|
+
'设置 Agent Team 的成员数、任务数、消息大小与清理超时。改动会写入 agent-team 行,按正常加载流程生效。',
|
|
80
|
+
'Team size, task count, message size and teardown timeout for Agent Team. Changes are written onto the agent-team row and applied on the normal load path.',
|
|
81
|
+
],
|
|
82
|
+
overridden: ['已覆盖', 'Overridden'],
|
|
83
|
+
reset: ['恢复默认', 'Restore default'],
|
|
84
|
+
loading: ['正在读取配置…', 'Loading configuration…'],
|
|
85
|
+
unavailable: [
|
|
86
|
+
'当前连接不支持写入设置,这一页只能查看。',
|
|
87
|
+
'This connection cannot write settings, so this page is read-only.',
|
|
88
|
+
],
|
|
89
|
+
saveFailed: ['保存失败:', 'Save failed: '],
|
|
90
|
+
note: [
|
|
91
|
+
'数值保存在当前 profile 的补丁层;卸载本插件不会自动撤回。',
|
|
92
|
+
'Values persist in this profile patch layer; uninstalling the plugin does not roll them back.',
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function t(key) {
|
|
97
|
+
var pair = COPY[key]
|
|
98
|
+
if (pair === undefined) return key
|
|
99
|
+
return isZh() ? pair[0] : pair[1]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var CSS = [
|
|
103
|
+
'.dtl { display: flex; flex-direction: column; gap: 26px; max-width: 820px; padding: 4px 2px 8px; }',
|
|
104
|
+
'.dtl-head { display: flex; flex-direction: column; gap: 6px; }',
|
|
105
|
+
'.dtl-title { font-size: 22px; font-weight: 650; letter-spacing: .01em; margin: 0; line-height: 1.3; }',
|
|
106
|
+
'.dtl-sub { font-size: 13px; opacity: .62; line-height: 1.6; margin: 0; max-width: 64ch; }',
|
|
107
|
+
'.dtl-group { display: flex; flex-direction: column; gap: 14px; }',
|
|
108
|
+
'.dtl-group-title { font-size: 14.5px; font-weight: 600; margin: 0; line-height: 1.4; }',
|
|
109
|
+
'.dtl-group-note { font-size: 12px; opacity: .55; margin: -8px 0 0; line-height: 1.6; }',
|
|
110
|
+
'.dtl-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 24px; }',
|
|
111
|
+
'@media (max-width: 720px) { .dtl-grid { grid-template-columns: minmax(0, 1fr); } }',
|
|
112
|
+
'.dtl-cell { display: flex; flex-direction: column; gap: 8px; min-width: 0; }',
|
|
113
|
+
'.dtl-labelrow { display: flex; align-items: center; gap: 8px; min-height: 20px; }',
|
|
114
|
+
'.dtl-label { font-size: 13px; font-weight: 500; }',
|
|
115
|
+
'.dtl-chip { font-size: 11.5px; line-height: 18px; padding: 0 8px; border-radius: 999px; background: rgba(128,128,128,.14); opacity: .8; }',
|
|
116
|
+
'.dtl-spacer { flex: 1 1 auto; }',
|
|
117
|
+
'.dtl-reset { font-size: 12.5px; color: inherit; opacity: .55; background: none; border: none; padding: 0; cursor: pointer; }',
|
|
118
|
+
'.dtl-reset:hover:not(:disabled) { opacity: .85; text-decoration: underline; }',
|
|
119
|
+
'.dtl-reset:disabled { opacity: .25; cursor: default; }',
|
|
120
|
+
'.dtl-input { width: 100%; box-sizing: border-box; height: 38px; padding: 0 12px; font-size: 13.5px; font-variant-numeric: tabular-nums; border-radius: 9px; border: 1px solid rgba(128,128,128,.36); background: transparent; color: inherit; outline: none; }',
|
|
121
|
+
'.dtl-input:focus { border-color: rgba(76,139,245,.85); }',
|
|
122
|
+
'.dtl-input:disabled { opacity: .45; }',
|
|
123
|
+
'.dtl-help { font-size: 12px; opacity: .5; line-height: 1.5; }',
|
|
124
|
+
'.dtl-foot { font-size: 12px; opacity: .48; line-height: 1.6; margin: 0; max-width: 64ch; }',
|
|
125
|
+
'.dtl-error { font-size: 12.5px; color: #d9534f; }',
|
|
126
|
+
'.dtl-summary { font-size: 13px; opacity: .78; font-variant-numeric: tabular-nums; }',
|
|
127
|
+
].join('\n')
|
|
128
|
+
|
|
129
|
+
function numberOr(value, fallback) {
|
|
130
|
+
var parsed = typeof value === 'number' ? value : Number(value)
|
|
131
|
+
return Number.isFinite(parsed) && parsed >= 1 ? Math.floor(parsed) : fallback
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function valuesOf(source) {
|
|
135
|
+
var result = {}
|
|
136
|
+
for (var index = 0; index < SECTIONS.length; index += 1) {
|
|
137
|
+
var fields = SECTIONS[index].fields
|
|
138
|
+
for (var inner = 0; inner < fields.length; inner += 1) {
|
|
139
|
+
var key = fields[inner].key
|
|
140
|
+
result[key] = numberOr(source === null || source === undefined ? undefined : source[key], DEFAULTS[key])
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function Field(props) {
|
|
147
|
+
var field = props.field
|
|
148
|
+
var overridden = props.value !== DEFAULTS[field.key]
|
|
149
|
+
return h(
|
|
150
|
+
'div',
|
|
151
|
+
{ className: 'dtl-cell' },
|
|
152
|
+
h(
|
|
153
|
+
'div',
|
|
154
|
+
{ className: 'dtl-labelrow' },
|
|
155
|
+
h('label', { className: 'dtl-label', htmlFor: 'dtl-' + field.key }, isZh() ? field.zh : field.en),
|
|
156
|
+
overridden ? h('span', { className: 'dtl-chip' }, t('overridden')) : null,
|
|
157
|
+
h('span', { className: 'dtl-spacer' }),
|
|
158
|
+
overridden
|
|
159
|
+
? h(
|
|
160
|
+
'button',
|
|
161
|
+
{
|
|
162
|
+
type: 'button',
|
|
163
|
+
className: 'dtl-reset',
|
|
164
|
+
disabled: props.disabled,
|
|
165
|
+
onClick: function () {
|
|
166
|
+
props.onReset(field.key)
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
t('reset'),
|
|
170
|
+
)
|
|
171
|
+
: null,
|
|
172
|
+
),
|
|
173
|
+
h('input', {
|
|
174
|
+
id: 'dtl-' + field.key,
|
|
175
|
+
className: 'dtl-input',
|
|
176
|
+
type: 'number',
|
|
177
|
+
min: 1,
|
|
178
|
+
step: 1,
|
|
179
|
+
inputMode: 'numeric',
|
|
180
|
+
disabled: props.disabled,
|
|
181
|
+
value: String(props.value),
|
|
182
|
+
onChange: function (event) {
|
|
183
|
+
props.onInput(field.key, event.target.value)
|
|
184
|
+
},
|
|
185
|
+
}),
|
|
186
|
+
h('span', { className: 'dtl-help' }, isZh() ? field.zhHelp : field.enHelp),
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function TeamLimitsPage(props) {
|
|
191
|
+
var form = props.configForm || props.form
|
|
192
|
+
var summary = props.view === 'summary'
|
|
193
|
+
|
|
194
|
+
if (!form) {
|
|
195
|
+
return h('p', { className: 'dtl-foot' }, t('loading'))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
var snapshotPair = React.useState(function () {
|
|
199
|
+
return form.getSnapshot()
|
|
200
|
+
})
|
|
201
|
+
var snapshot = snapshotPair[0]
|
|
202
|
+
var setSnapshot = snapshotPair[1]
|
|
203
|
+
|
|
204
|
+
var draftPair = React.useState(function () {
|
|
205
|
+
return valuesOf(snapshot.value)
|
|
206
|
+
})
|
|
207
|
+
var draft = draftPair[0]
|
|
208
|
+
var setDraft = draftPair[1]
|
|
209
|
+
|
|
210
|
+
var errorPair = React.useState('')
|
|
211
|
+
var error = errorPair[0]
|
|
212
|
+
var setError = errorPair[1]
|
|
213
|
+
|
|
214
|
+
var pendingPair = React.useState({})
|
|
215
|
+
var pending = pendingPair[0]
|
|
216
|
+
var setPending = pendingPair[1]
|
|
217
|
+
|
|
218
|
+
React.useEffect(function () {
|
|
219
|
+
var off = form.subscribe(function () {
|
|
220
|
+
setSnapshot(form.getSnapshot())
|
|
221
|
+
})
|
|
222
|
+
setSnapshot(form.getSnapshot())
|
|
223
|
+
return off
|
|
224
|
+
}, [form])
|
|
225
|
+
|
|
226
|
+
React.useEffect(function () {
|
|
227
|
+
var next = valuesOf(snapshot.value)
|
|
228
|
+
var keys = Object.keys(pending)
|
|
229
|
+
setDraft(function (previous) {
|
|
230
|
+
var merged = {}
|
|
231
|
+
for (var name in previous) merged[name] = previous[name]
|
|
232
|
+
for (var name in next) {
|
|
233
|
+
if (keys.indexOf(name) < 0) merged[name] = next[name]
|
|
234
|
+
}
|
|
235
|
+
return merged
|
|
236
|
+
})
|
|
237
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
238
|
+
}, [snapshot.value])
|
|
239
|
+
|
|
240
|
+
React.useEffect(function () {
|
|
241
|
+
var keys = Object.keys(pending)
|
|
242
|
+
if (keys.length === 0) return undefined
|
|
243
|
+
var timer = setTimeout(function () {
|
|
244
|
+
var chain = Promise.resolve()
|
|
245
|
+
keys.forEach(function (key) {
|
|
246
|
+
chain = chain.then(function () {
|
|
247
|
+
return form.set(key, pending[key])
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
chain
|
|
251
|
+
.then(function () {
|
|
252
|
+
setPending({})
|
|
253
|
+
})
|
|
254
|
+
.catch(function (caught) {
|
|
255
|
+
setPending({})
|
|
256
|
+
setError(t('saveFailed') + String((caught && caught.message) || caught))
|
|
257
|
+
})
|
|
258
|
+
}, 700)
|
|
259
|
+
return function () {
|
|
260
|
+
clearTimeout(timer)
|
|
261
|
+
}
|
|
262
|
+
}, [pending, form])
|
|
263
|
+
|
|
264
|
+
var ready = snapshot.status === 'ready'
|
|
265
|
+
var disabled = !ready
|
|
266
|
+
|
|
267
|
+
function write(key, value) {
|
|
268
|
+
setError('')
|
|
269
|
+
setPending(function (previous) {
|
|
270
|
+
var next = {}
|
|
271
|
+
for (var name in previous) next[name] = previous[name]
|
|
272
|
+
next[key] = value
|
|
273
|
+
return next
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function onInput(key, raw) {
|
|
278
|
+
var parsed = Number(raw)
|
|
279
|
+
var safe = Number.isFinite(parsed) ? Math.max(1, Math.floor(parsed)) : DEFAULTS[key]
|
|
280
|
+
setDraft(function (previous) {
|
|
281
|
+
var next = {}
|
|
282
|
+
for (var name in previous) next[name] = previous[name]
|
|
283
|
+
next[key] = safe
|
|
284
|
+
return next
|
|
285
|
+
})
|
|
286
|
+
if (raw === '') return
|
|
287
|
+
write(key, safe)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function onReset(key) {
|
|
291
|
+
setDraft(function (previous) {
|
|
292
|
+
var next = {}
|
|
293
|
+
for (var name in previous) next[name] = previous[name]
|
|
294
|
+
next[key] = DEFAULTS[key]
|
|
295
|
+
return next
|
|
296
|
+
})
|
|
297
|
+
write(key, DEFAULTS[key])
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (summary) {
|
|
301
|
+
return h(
|
|
302
|
+
'span',
|
|
303
|
+
{ className: 'dtl-summary' },
|
|
304
|
+
[
|
|
305
|
+
draft.maxMembers + (isZh() ? ' 成员' : ' members'),
|
|
306
|
+
draft.maxTasks + (isZh() ? ' 任务' : ' tasks'),
|
|
307
|
+
draft.maxMessageBytes + (isZh() ? ' 字节/条' : ' bytes/msg'),
|
|
308
|
+
draft.disposalTimeoutMs + ' ms',
|
|
309
|
+
].join(' · '),
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
var groups = SECTIONS.map(function (section) {
|
|
314
|
+
return h(
|
|
315
|
+
'section',
|
|
316
|
+
{ className: 'dtl-group', key: section.id },
|
|
317
|
+
h('h3', { className: 'dtl-group-title' }, isZh() ? section.zh : section.en),
|
|
318
|
+
h('p', { className: 'dtl-group-note' }, isZh() ? section.zhNote : section.enNote),
|
|
319
|
+
h(
|
|
320
|
+
'div',
|
|
321
|
+
{ className: 'dtl-grid' },
|
|
322
|
+
section.fields.map(function (field) {
|
|
323
|
+
return h(Field, {
|
|
324
|
+
key: field.key,
|
|
325
|
+
field: field,
|
|
326
|
+
value: draft[field.key],
|
|
327
|
+
disabled: disabled,
|
|
328
|
+
onInput: onInput,
|
|
329
|
+
onReset: onReset,
|
|
330
|
+
})
|
|
331
|
+
}),
|
|
332
|
+
),
|
|
333
|
+
)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
return h(
|
|
337
|
+
'div',
|
|
338
|
+
{ className: 'dtl' },
|
|
339
|
+
h('style', null, CSS),
|
|
340
|
+
ready ? null : h('p', { className: 'dtl-foot' }, t(snapshot.status === 'unavailable' ? 'unavailable' : 'loading')),
|
|
341
|
+
groups,
|
|
342
|
+
error ? h('p', { className: 'dtl-error' }, error) : null,
|
|
343
|
+
h('p', { className: 'dtl-foot' }, t('note')),
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function apply(ctx) {
|
|
348
|
+
try {
|
|
349
|
+
var forms = ctx.configForms
|
|
350
|
+
var form = forms ? forms.get(NS) : null
|
|
351
|
+
if (!form) {
|
|
352
|
+
console.error('[team-limits] configForms service is unavailable; no settings page is registered')
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
ctx.effect(
|
|
356
|
+
function () {
|
|
357
|
+
return forms.whileServed([NS], function () {
|
|
358
|
+
return ctx.slots.inject('plugins.bundle.config', function () {
|
|
359
|
+
return ctx.slots.register(
|
|
360
|
+
{
|
|
361
|
+
name: 'plugins.bundle.config',
|
|
362
|
+
key: 'dsh-team-limits',
|
|
363
|
+
inject: function () {
|
|
364
|
+
return { configForm: form }
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
TeamLimitsPage,
|
|
368
|
+
)
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
},
|
|
372
|
+
'team-limits: bundle config',
|
|
373
|
+
)
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error('[team-limits] client half failed to mount: ' + String((error && error.message) || error))
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
exports.name = 'dsh-team-limits'
|
|
380
|
+
exports.inject = ['slots', 'configForms']
|
|
381
|
+
exports.apply = apply
|
|
382
|
+
|
|
383
|
+
return module.exports
|
|
384
|
+
},
|
|
385
|
+
})
|
package/cordis.patch.yml
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
# A
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# A settings-page bundle. The inserted row is the host half: it declares the
|
|
2
|
+
# four limits as Config and pushes the chosen values onto the shipped
|
|
3
|
+
# `agent-team` row through the config editor. The client half (dsh.client)
|
|
4
|
+
# renders the page under Settings → Team limits.
|
|
4
5
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# default -> this bundle
|
|
10
|
-
# maxMembers 16 -> 32
|
|
11
|
-
# maxTasks 256 -> 512
|
|
12
|
-
# maxMessageBytes 65536 -> 131072
|
|
13
|
-
# disposalTimeoutMs 5000 -> 5000 (unchanged)
|
|
6
|
+
# The limits are no longer frozen here: everything the page saves is written
|
|
7
|
+
# into the profile patch layer by the config editor, so this file stays free of
|
|
8
|
+
# values and survives a DSH upgrade that changes the agent-team defaults.
|
|
14
9
|
- insert:
|
|
15
10
|
- id: team-limits
|
|
16
11
|
name: 'dsh-team-limits'
|
|
17
|
-
- id: agent-team
|
|
18
|
-
config:
|
|
19
|
-
maxMembers: 32
|
|
20
|
-
maxTasks: 512
|
|
21
|
-
maxMessageBytes: 131072
|
|
22
|
-
disposalTimeoutMs: 5000
|
package/index.js
CHANGED
|
@@ -1,11 +1,204 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-team-limits
|
|
3
|
-
* cordis.patch.yml, which overrides the shipped `agent-team` row.
|
|
2
|
+
* dsh-team-limits — Host half.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The four Agent Team limits are this plugin's Config, so the browser half can
|
|
5
|
+
* render them as a real settings page. `apply` pushes the chosen values onto
|
|
6
|
+
* the shipped `agent-team` row through the config editor, which persists them
|
|
7
|
+
* in the profile patch layer and applies them on the normal Loader path.
|
|
8
|
+
*
|
|
9
|
+
* The editor refuses to run inside an existing HMR transaction ("HMR
|
|
10
|
+
* transactions cannot be nested"). Our apply() runs inside the transaction
|
|
11
|
+
* that mounted this plugin, and every timer created there inherits that
|
|
12
|
+
* context through AsyncLocalStorage, so a plain setTimeout would still be
|
|
13
|
+
* nested. `escapeTransaction` runs the push through the HMR service's own
|
|
14
|
+
* AsyncLocalStorage.exit, which is exactly the escape hatch the service
|
|
15
|
+
* itself uses for its watchers.
|
|
8
16
|
*/
|
|
17
|
+
|
|
9
18
|
export const name = 'dsh-team-limits'
|
|
10
19
|
|
|
11
|
-
export
|
|
20
|
+
export const inject = ['configEditor']
|
|
21
|
+
|
|
22
|
+
const DEFAULTS = {
|
|
23
|
+
maxMembers: 32,
|
|
24
|
+
maxTasks: 512,
|
|
25
|
+
maxMessageBytes: 131072,
|
|
26
|
+
disposalTimeoutMs: 5000,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const RETRY_DELAYS = [1300, 4000, 8000]
|
|
30
|
+
|
|
31
|
+
const DEBUG = 'C:/Users/Administrator/.dsh/team-limits-debug.log'
|
|
32
|
+
|
|
33
|
+
async function trace(message) {
|
|
34
|
+
try {
|
|
35
|
+
const { appendFileSync } = await import('node:fs')
|
|
36
|
+
appendFileSync(DEBUG, new Date().toISOString() + ' ' + message + '\n')
|
|
37
|
+
} catch {
|
|
38
|
+
/* diagnostics must never break the plugin */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let Schema
|
|
43
|
+
try {
|
|
44
|
+
const loaded = await import('@deepseek-ai/schemastery')
|
|
45
|
+
Schema = loaded.default ?? loaded.Schema
|
|
46
|
+
} catch {
|
|
47
|
+
Schema = undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Config =
|
|
51
|
+
Schema === undefined
|
|
52
|
+
? undefined
|
|
53
|
+
: Schema.object({
|
|
54
|
+
maxMembers: Schema.natural()
|
|
55
|
+
.min(1)
|
|
56
|
+
.volatile()
|
|
57
|
+
.default(DEFAULTS.maxMembers)
|
|
58
|
+
.description('Maximum number of teammates in one team.'),
|
|
59
|
+
maxTasks: Schema.natural()
|
|
60
|
+
.min(1)
|
|
61
|
+
.volatile()
|
|
62
|
+
.default(DEFAULTS.maxTasks)
|
|
63
|
+
.description('Maximum number of tasks the shared task board keeps.'),
|
|
64
|
+
maxMessageBytes: Schema.natural()
|
|
65
|
+
.min(1)
|
|
66
|
+
.volatile()
|
|
67
|
+
.default(DEFAULTS.maxMessageBytes)
|
|
68
|
+
.description('Maximum size of a single team message, in bytes (131072 = 128 KB).'),
|
|
69
|
+
disposalTimeoutMs: Schema.natural()
|
|
70
|
+
.min(1)
|
|
71
|
+
.volatile()
|
|
72
|
+
.default(DEFAULTS.disposalTimeoutMs)
|
|
73
|
+
.description('How long teardown waits for a member before giving up, in milliseconds.'),
|
|
74
|
+
}).description('Limits applied to the shipped Agent Team row.')
|
|
75
|
+
|
|
76
|
+
function normalize(config) {
|
|
77
|
+
const source = config ?? {}
|
|
78
|
+
const result = {}
|
|
79
|
+
for (const key of Object.keys(DEFAULTS)) {
|
|
80
|
+
const value = source[key]
|
|
81
|
+
result[key] =
|
|
82
|
+
typeof value === 'number' && Number.isFinite(value) && value >= 1
|
|
83
|
+
? Math.floor(value)
|
|
84
|
+
: DEFAULTS[key]
|
|
85
|
+
}
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function entryId(entry) {
|
|
90
|
+
if (entry === null || entry === undefined) return ''
|
|
91
|
+
if (typeof entry === 'string') return entry
|
|
92
|
+
const candidates = [entry.options?.id, entry.id, entry.patch?.id, entry.entry?.id]
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
if (typeof candidate === 'string' && candidate.length > 0) return candidate
|
|
95
|
+
}
|
|
96
|
+
return ''
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function alreadyApplied(override, desired) {
|
|
100
|
+
if (override === null || override === undefined) return false
|
|
101
|
+
return Object.keys(desired).every((key) => override[key] === desired[key])
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run `work` outside any HMR transaction. The service keeps its flag in an
|
|
106
|
+
* AsyncLocalStorage; ours was inherited from the transaction that mounted this
|
|
107
|
+
* plugin, so only its own `exit` can clear it for a later asynchronous task.
|
|
108
|
+
*/
|
|
109
|
+
function escapeTransaction(ctx, work) {
|
|
110
|
+
let executing
|
|
111
|
+
try {
|
|
112
|
+
const hmr = ctx.get('hmr')
|
|
113
|
+
executing = hmr === undefined ? undefined : hmr.executing
|
|
114
|
+
} catch {
|
|
115
|
+
executing = undefined
|
|
116
|
+
}
|
|
117
|
+
if (executing !== undefined && typeof executing.exit === 'function') {
|
|
118
|
+
return executing.exit(work)
|
|
119
|
+
}
|
|
120
|
+
return work()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @returns {Promise<boolean>} true when another attempt is worth making. */
|
|
124
|
+
async function push(ctx, desired, attempt) {
|
|
125
|
+
return escapeTransaction(ctx, async () => {
|
|
126
|
+
try {
|
|
127
|
+
const rows = ctx.configEditor.configuration()
|
|
128
|
+
const row = rows.find((item) => entryId(item?.entry) === 'agent-team')
|
|
129
|
+
if (row === undefined) {
|
|
130
|
+
await trace('attempt ' + attempt + ': agent-team row not found')
|
|
131
|
+
return true
|
|
132
|
+
}
|
|
133
|
+
if (alreadyApplied(row.override, desired)) {
|
|
134
|
+
await trace('attempt ' + attempt + ': already applied')
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
await ctx.configEditor.edit(row.entry, () => ({ ...desired }))
|
|
138
|
+
await trace('attempt ' + attempt + ': applied ' + JSON.stringify(desired))
|
|
139
|
+
return false
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const message = String((error && error.message) || error)
|
|
142
|
+
await trace('attempt ' + attempt + ': error=' + message)
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function schedule(ctx, desired, attempt) {
|
|
149
|
+
const delay = RETRY_DELAYS[Math.min(attempt, RETRY_DELAYS.length - 1)]
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
void push(ctx, desired, attempt).then((retry) => {
|
|
152
|
+
if (retry && attempt + 1 < RETRY_DELAYS.length) schedule(ctx, desired, attempt + 1)
|
|
153
|
+
})
|
|
154
|
+
}, delay)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Volatile fields arrive as read-only references; ordinary ones as numbers. */
|
|
158
|
+
function readValue(value, fallback) {
|
|
159
|
+
if (value !== null && typeof value === 'object' && typeof value.get === 'function') {
|
|
160
|
+
const current = value.get()
|
|
161
|
+
return typeof current === 'number' ? current : fallback
|
|
162
|
+
}
|
|
163
|
+
return typeof value === 'number' ? value : fallback
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The limits the page has saved, read from the live Config references. */
|
|
167
|
+
function currentValues(config) {
|
|
168
|
+
const raw = {}
|
|
169
|
+
for (const key of Object.keys(DEFAULTS)) raw[key] = readValue(config === null || config === undefined ? undefined : config[key], DEFAULTS[key])
|
|
170
|
+
return normalize(raw)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function apply(ctx, config) {
|
|
174
|
+
/* This package ships its own page, so the harness must not auto-generate one. */
|
|
175
|
+
ctx.inject(['settings'], (child) => {
|
|
176
|
+
child.settings.configure({ auto: false }, ctx.fiber)
|
|
177
|
+
})
|
|
178
|
+
setTimeout(() => {
|
|
179
|
+
try {
|
|
180
|
+
const settings = ctx.get('settings')
|
|
181
|
+
const rows = settings === undefined ? [] : settings.describe()
|
|
182
|
+
void trace('namespaces=' + rows.map((row) => row.ns).join(','))
|
|
183
|
+
} catch (error) {
|
|
184
|
+
void trace('describe error=' + String((error && error.message) || error))
|
|
185
|
+
}
|
|
186
|
+
}, 3000)
|
|
187
|
+
schedule(ctx, currentValues(config), 0)
|
|
188
|
+
/*
|
|
189
|
+
* Volatile fields are edited in place without remounting this plugin, so the
|
|
190
|
+
* only way to learn about a saved form is to look at the references again.
|
|
191
|
+
* One timer, one comparison per tick, nothing while the values are unchanged.
|
|
192
|
+
*/
|
|
193
|
+
ctx.effect(() => {
|
|
194
|
+
let last = JSON.stringify(currentValues(config))
|
|
195
|
+
const timer = setInterval(() => {
|
|
196
|
+
const next = currentValues(config)
|
|
197
|
+
const serialized = JSON.stringify(next)
|
|
198
|
+
if (serialized === last) return
|
|
199
|
+
last = serialized
|
|
200
|
+
void push(ctx, next, 1)
|
|
201
|
+
}, 1500)
|
|
202
|
+
return () => clearInterval(timer)
|
|
203
|
+
})
|
|
204
|
+
}
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "Team Limits",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Adjust Agent Team's member count, task count, message size and teardown timeout."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-team-limits",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Optional companion bundle
|
|
3
|
+
"version": "1.3.4",
|
|
4
|
+
"description": "Optional companion bundle for the DeepSeek Harness Agent Team limits: members, tasks, message bytes and teardown timeout.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "SpaceRelay",
|
|
@@ -21,12 +21,14 @@
|
|
|
21
21
|
"icon": "./icon.svg",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": "./index.js",
|
|
24
|
+
"./client": "./client.js",
|
|
24
25
|
"./package.json": "./package.json",
|
|
25
26
|
"./locale/*.json": "./locale/*.json",
|
|
26
27
|
"./icon": "./icon.svg"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"index.js",
|
|
31
|
+
"client.js",
|
|
30
32
|
"cordis.patch.yml",
|
|
31
33
|
"locale/*.json",
|
|
32
34
|
"icon.svg",
|
|
@@ -45,6 +47,15 @@
|
|
|
45
47
|
"dsh": {
|
|
46
48
|
"bundle": {
|
|
47
49
|
"patch": "./cordis.patch.yml"
|
|
50
|
+
},
|
|
51
|
+
"client": {
|
|
52
|
+
"platform": "web",
|
|
53
|
+
"immediately": true,
|
|
54
|
+
"inject": [
|
|
55
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
56
|
+
"@deepseek-ai/dsh-client-locale",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
58
|
+
]
|
|
48
59
|
}
|
|
49
60
|
},
|
|
50
61
|
"publishConfig": {
|