dsh-collab-workbench 0.1.2 → 0.1.3

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.

Potentially problematic release.


This version of dsh-collab-workbench might be problematic. Click here for more details.

package/README.md CHANGED
@@ -60,13 +60,15 @@ dsh-collab-workbench
60
60
 
61
61
  在工作台侧栏底部打开“关于”,点击“检查更新”。
62
62
 
63
- - npm registry 来源的安装可查询新版本;有更新时出现侧栏云朵和更新入口。
64
- - 更新需用户点击并确认,不会自动安装。
65
- - 安装成功后按提示重启 Harness,再确认当前版本。
66
- - 本地 tgz 或目录安装不提供 npm 在线更新,也不会擅自切换来源。
63
+ - npm registry 来源的安装可查询新版本;有更新时出现侧栏云朵和“关于”页入口。
64
+ - **插件不会自动安装更新**,也不会卸载自身。当前 Harness 拒绝用同名包覆盖运行中的插件,因此“关于”页只显示新版本号和升级步骤。
65
+ - 升级步骤:在 Harness 的插件页面卸载本插件,再安装“关于”页给出的版本(例如 `dsh-collab-workbench@0.1.3`),然后重启 Harness 并刷新页面。
66
+ - 卸载后若安装失败,工作台会暂时不可用;请先备份数据并确认网络与 registry 可用。
67
+ - 升级只替换程序,`$DSH_HOME/dsh-workbench` 中的角色、群组和任务数据会保留。
68
+ - 本地 tgz 或目录安装不提供 npm 在线检查,也不会擅自切换来源。
67
69
  - 网络或 registry 异常只影响检查;不会因此删除工作台数据。
68
70
 
69
- 验证升级需先从 npm 安装较早版本,例如 `dsh-collab-workbench@0.1.1`,再在发布 0.1.2 后检查更新。同版本安装不会显示“发现新版”。
71
+ 验证升级需先从 npm 安装较早版本,例如 `dsh-collab-workbench@0.1.2`,再在发布更高版本后检查更新。同版本安装不会显示“发现新版”。
70
72
 
71
73
  ## 数据与权限边界
72
74
 
@@ -86,6 +88,12 @@ dsh-collab-workbench
86
88
 
87
89
  ## 版本记录
88
90
 
91
+ ### 0.1.3
92
+
93
+ - 修正更新入口:当前 Harness 不支持覆盖运行中的插件,因此移除一键安装按钮与安装接口,改为显示新版本号和手动升级步骤。
94
+ - 检查更新仍为只读;有新版时保留侧栏云朵提示。
95
+ - 增加升级风险说明与数据保留说明;不更改任务调度逻辑。
96
+
89
97
  ### 0.1.2
90
98
 
91
99
  - 重写面向使用者的说明:补充安装、快速开始、角色模板、更新方式和数据权限边界。
package/client.js CHANGED
@@ -153,20 +153,38 @@ function createApprovalUI(React, ctx, call) {
153
153
  // END WORKBENCH APPROVAL UI
154
154
 
155
155
  // BEGIN WORKBENCH UPDATE UI
156
- // Update controls for the existing Workbench panel; embedded into client.js at build time.
157
- // All styles live in the components that render them, so no other page owns their CSS.
156
+ // Update check and manual-upgrade guidance for the existing Workbench panel;
157
+ // embedded into client.js at build time. Styles are owned by these components.
158
158
  function createUpdateUI(React, ctx, call) {
159
159
  const h = React.createElement
160
160
  const NS = 'dwbUpdates'
161
161
  const zh = {
162
- about:'关于', cloud:'有新版本', title:'协作工作台', current:'当前版本', latest:'最新版本', check:'检查更新', checking:'正在检查…', update:'更新', updating:'正在安装…', restart:'更新已安装,请重启 DSH 后使用', ready:'发现新版本', currentState:'已是最新版本', local:'当前从本地文件安装,请从原安装来源更新。', other:'当前安装来源不支持在线更新。', pending:'尚未检查更新', failed:'检查或安装失败', confirm:'确认安装协作工作台 {version}?安装后需要重启 DSH。', release:'版本信息', inaccessible:'此版本暂不提供更新检查。', error:'无法检查更新,请检查网络后重试。'
162
+ about:'关于', cloud:'有新版本', title:'协作工作台', current:'当前版本', latest:'最新版本',
163
+ check:'检查更新', checking:'正在检查…', update:'可升级',
164
+ ready:'发现新版本,可按下方步骤手动升级。', currentState:'已是最新版本',
165
+ local:'当前从本地文件安装,请从原安装来源更新。', other:'当前安装来源不支持在线检查更新。',
166
+ pending:'尚未检查更新', failed:'检查更新失败', error:'无法检查更新,请检查网络后重试。',
167
+ guideTitle:'升级步骤', note:'当前 Harness 不允许覆盖运行中的插件,因此不会自动安装。',
168
+ step1:'打开 Harness 的插件(管理)页面。', step2:'卸载「协作工作台」。',
169
+ step3:'安装下面这个版本,然后重启 Harness 并刷新页面。', spec:'安装版本',
170
+ warning:'卸载后若安装失败,工作台会暂时不可用;请先备份工作台数据和项目文件,并确认网络与 registry 可用。',
171
+ dataSafe:'升级只替换插件程序;$DSH_HOME/dsh-workbench 中的角色、群组和任务数据会保留。',
163
172
  }
164
173
  const en = {
165
- about:'About', cloud:'Update available', title:'Collaboration Workbench', current:'Installed version', latest:'Latest version', check:'Check for updates', checking:'Checking…', update:'Update', updating:'Installing…', restart:'Update installed. Restart DSH to use it.', ready:'New version available', currentState:'Up to date', local:'Installed from a local file; update it from the original source.', other:'Updates are not available for this install source.', pending:'Not checked yet', failed:'Update check or install failed', confirm:'Install Collaboration Workbench {version}? Restart DSH afterwards.', release:'Release information', inaccessible:'Update checks are unavailable in this version.', error:'Could not check for updates. Check your network and try again.'
174
+ about:'About', cloud:'Update available', title:'Collaboration Workbench', current:'Installed version', latest:'Latest version',
175
+ check:'Check for updates', checking:'Checking…', update:'Upgrade',
176
+ ready:'A new version is available. Follow the steps below to upgrade manually.', currentState:'Up to date',
177
+ local:'Installed from a local file; update it from the original source.', other:'This install source cannot check for updates.',
178
+ pending:'Not checked yet', failed:'Update check failed', error:'Could not check for updates. Check your network and try again.',
179
+ guideTitle:'Upgrade steps', note:'This Harness does not allow replacing a running plugin, so nothing is installed automatically.',
180
+ step1:'Open the Harness plugins page.', step2:'Uninstall Collaboration Workbench.',
181
+ step3:'Install the version below, then restart Harness and refresh the page.', spec:'Install version',
182
+ warning:'If the install fails after uninstalling, the workbench stays unavailable until it succeeds. Back up workbench data and project files, and make sure the network and registry work first.',
183
+ dataSafe:'Upgrading only replaces the plugin program; roles, groups and tasks in $DSH_HOME/dsh-workbench are preserved.',
166
184
  }
167
185
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'collaboration workbench: update locales')
168
186
  const t = ctx.locale.bind(NS)
169
- const css = `.dwb-update-cloud{width:52px;min-height:42px;padding:5px 0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-brand-primary);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:2px}.dwb-update-cloud:hover{background:var(--dsw-alias-bg-layer-2)}.dwb-update-cloud:focus-visible,.dwb-update-about button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dwb-update-cloud span{font-size:10px}.dwb-update-about{flex:1;min-width:0;overflow:auto;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);padding:56px 24px 24px}.dwb-update-card{max-width:640px;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dwb-update-about h2{font-size:18px;margin:0 0 18px}.dwb-update-about dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:10px 20px;margin:0 0 18px}.dwb-update-about dt{color:var(--dsw-alias-label-secondary)}.dwb-update-about dd{margin:0;overflow-wrap:anywhere}.dwb-update-state{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-secondary);margin:0 0 16px}.dwb-update-error{color:var(--dsw-alias-state-error-primary)}.dwb-update-actions{display:flex;gap:10px;flex-wrap:wrap}.dwb-update-about button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:7px 12px;min-height:34px;cursor:pointer}.dwb-update-about button:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.dwb-update-about button:disabled{opacity:.5;cursor:default}.dwb-update-about button.dwb-update-primary{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.dwb-update-about button.dwb-update-primary:hover:not(:disabled){filter:brightness(1.06)}`
187
+ const css = `.dwb-update-cloud{width:52px;min-height:42px;padding:5px 0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-brand-primary);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:2px}.dwb-update-cloud:hover{background:var(--dsw-alias-bg-layer-2)}.dwb-update-cloud:focus-visible,.dwb-update-about button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dwb-update-cloud span{font-size:10px}.dwb-update-about{flex:1;min-width:0;overflow:auto;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);padding:56px 24px 24px}.dwb-update-card{max-width:640px;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dwb-update-about h2{font-size:18px;margin:0 0 18px}.dwb-update-about h3{font-size:14px;margin:18px 0 8px}.dwb-update-about dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:10px 20px;margin:0 0 18px}.dwb-update-about dt{color:var(--dsw-alias-label-secondary)}.dwb-update-about dd{margin:0;overflow-wrap:anywhere}.dwb-update-state{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-secondary);margin:0 0 16px}.dwb-update-error{color:var(--dsw-alias-state-error-primary)}.dwb-update-actions{display:flex;gap:10px;flex-wrap:wrap}.dwb-update-about button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:7px 12px;min-height:34px;cursor:pointer}.dwb-update-about button:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.dwb-update-about button:disabled{opacity:.5;cursor:default}.dwb-update-steps{margin:0;padding-left:20px;font-size:13px;line-height:1.9}.dwb-update-spec{display:inline-block;margin-top:8px;padding:6px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;user-select:all;overflow-wrap:anywhere}.dwb-update-note,.dwb-update-safe{font-size:12px;line-height:1.7;color:var(--dsw-alias-label-secondary);margin:10px 0 0}.dwb-update-safe{color:var(--dsw-alias-state-success-primary)}`
170
188
  function useUpdates() {
171
189
  const [status, setStatus] = React.useState({ phase:'idle', source:'unknown', current:null, latest:null, available:false })
172
190
  const [error, setError] = React.useState(null)
@@ -190,18 +208,10 @@ function createUpdateUI(React, ctx, call) {
190
208
  catch (failure) { if (active) setError(String(failure?.message ?? failure)) }
191
209
  }
192
210
  void check()
193
- const timer = setInterval(check, status.phase === 'installing' ? 2_000 : 60_000)
211
+ const timer = setInterval(check, 60_000)
194
212
  return () => { active = false; clearInterval(timer) }
195
- }, [status.phase])
196
- const install = React.useCallback(async () => {
197
- if (!status.available || busy || !window.confirm(t('confirm').replace('{version}', status.latest))) return
198
- setBusy(true)
199
- setError(null)
200
- try { setStatus(await call('updates.install')) }
201
- catch (failure) { setError(String(failure?.message ?? failure)) }
202
- finally { setBusy(false) }
203
- }, [status.available, status.latest, busy])
204
- return {status, error, busy, refresh, install}
213
+ }, [])
214
+ return {status, error, busy, refresh}
205
215
  }
206
216
  function UpdateCloud({ onClick }) {
207
217
  return h(React.Fragment, null, h('style', null, css),
@@ -211,18 +221,28 @@ function createUpdateUI(React, ctx, call) {
211
221
  h('path',{d:'m12 15 0-6m-2.5 2.5L12 9l2.5 2.5'})),
212
222
  h('span',null,t('update'))))
213
223
  }
214
- function UpdateAbout({ status, error, busy, refresh, install }) {
215
- const state = status.phase === 'installed' ? t('restart') : status.phase === 'installing' ? t('updating') : error || status.phase === 'failed' ? t('failed') : status.source === 'local' ? t('local') : status.source !== 'registry' ? t('other') : status.available ? t('ready') : status.checkedAt ? status.latest ? t('currentState') : t('error') : t('pending')
216
- const canUpdate = status.available && status.phase !== 'installing' && status.phase !== 'installed'
224
+ function UpdateAbout({ status, error, busy, refresh }) {
225
+ const failed = Boolean(error) || status.phase === 'failed'
226
+ const state = failed ? t('failed') : status.source === 'local' ? t('local') : status.source !== 'registry' ? t('other')
227
+ : status.available ? t('ready') : status.checkedAt ? status.latest ? t('currentState') : t('error') : t('pending')
228
+ const spec = status.latest ? `dsh-collab-workbench@${status.latest}` : null
217
229
  return h('section', {className:'dwb-update-about', 'aria-label':t('title')},
218
230
  h('style',null,css),
219
231
  h('div',{className:'dwb-update-card'},
220
232
  h('h2',null,t('title')),
221
233
  h('dl',null,h('dt',null,t('current')),h('dd',null,status.current ?? '—'),h('dt',null,t('latest')),h('dd',null,status.latest ?? '—')),
222
- h('p',{className:`dwb-update-state ${error || status.phase === 'failed' ? 'dwb-update-error' : ''}`,role:error || status.phase === 'failed' ? 'alert':undefined}, state, error ? `: ${error}` : status.phase === 'failed' && status.error ? `: ${status.error}` : ''),
234
+ h('p',{className:`dwb-update-state ${failed ? 'dwb-update-error' : ''}`,role:failed ? 'alert':undefined},
235
+ state, failed ? `: ${error ?? status.error ?? t('error')}` : ''),
223
236
  h('div',{className:'dwb-update-actions'},
224
- h('button',{type:'button',onClick:()=>refresh(true),disabled:busy || status.phase === 'installing'},busy?t('checking'):t('check')),
225
- canUpdate ? h('button',{type:'button',className:'dwb-update-primary',onClick:install,disabled:busy},t('update')) : null)))
237
+ h('button',{type:'button',onClick:()=>refresh(true),disabled:busy},busy?t('checking'):t('check'))),
238
+ status.available ? h(React.Fragment, null,
239
+ h('h3',null,t('guideTitle')),
240
+ h('ol',{className:'dwb-update-steps'},
241
+ h('li',null,t('step1')), h('li',null,t('step2')),
242
+ h('li',null,t('step3'), spec ? h('div',null,h('code',{className:'dwb-update-spec'},spec)) : null)),
243
+ h('p',{className:'dwb-update-note'},t('note')),
244
+ h('p',{className:'dwb-update-note'},t('warning')),
245
+ h('p',{className:'dwb-update-safe'},t('dataSafe'))) : null))
226
246
  }
227
247
  return {useUpdates, UpdateCloud, UpdateAbout, t}
228
248
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-collab-workbench",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "DeepSeek Harness 多 Agent 协作工作台:自定义角色、项目群聊、任务自动调度、群内权限审批与执行恢复",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -1,17 +1,35 @@
1
- // Update controls for the existing Workbench panel; embedded into client.js at build time.
2
- // All styles live in the components that render them, so no other page owns their CSS.
1
+ // Update check and manual-upgrade guidance for the existing Workbench panel;
2
+ // embedded into client.js at build time. Styles are owned by these components.
3
3
  export function createUpdateUI(React, ctx, call) {
4
4
  const h = React.createElement
5
5
  const NS = 'dwbUpdates'
6
6
  const zh = {
7
- about:'关于', cloud:'有新版本', title:'协作工作台', current:'当前版本', latest:'最新版本', check:'检查更新', checking:'正在检查…', update:'更新', updating:'正在安装…', restart:'更新已安装,请重启 DSH 后使用', ready:'发现新版本', currentState:'已是最新版本', local:'当前从本地文件安装,请从原安装来源更新。', other:'当前安装来源不支持在线更新。', pending:'尚未检查更新', failed:'检查或安装失败', confirm:'确认安装协作工作台 {version}?安装后需要重启 DSH。', release:'版本信息', inaccessible:'此版本暂不提供更新检查。', error:'无法检查更新,请检查网络后重试。'
7
+ about:'关于', cloud:'有新版本', title:'协作工作台', current:'当前版本', latest:'最新版本',
8
+ check:'检查更新', checking:'正在检查…', update:'可升级',
9
+ ready:'发现新版本,可按下方步骤手动升级。', currentState:'已是最新版本',
10
+ local:'当前从本地文件安装,请从原安装来源更新。', other:'当前安装来源不支持在线检查更新。',
11
+ pending:'尚未检查更新', failed:'检查更新失败', error:'无法检查更新,请检查网络后重试。',
12
+ guideTitle:'升级步骤', note:'当前 Harness 不允许覆盖运行中的插件,因此不会自动安装。',
13
+ step1:'打开 Harness 的插件(管理)页面。', step2:'卸载「协作工作台」。',
14
+ step3:'安装下面这个版本,然后重启 Harness 并刷新页面。', spec:'安装版本',
15
+ warning:'卸载后若安装失败,工作台会暂时不可用;请先备份工作台数据和项目文件,并确认网络与 registry 可用。',
16
+ dataSafe:'升级只替换插件程序;$DSH_HOME/dsh-workbench 中的角色、群组和任务数据会保留。',
8
17
  }
9
18
  const en = {
10
- about:'About', cloud:'Update available', title:'Collaboration Workbench', current:'Installed version', latest:'Latest version', check:'Check for updates', checking:'Checking…', update:'Update', updating:'Installing…', restart:'Update installed. Restart DSH to use it.', ready:'New version available', currentState:'Up to date', local:'Installed from a local file; update it from the original source.', other:'Updates are not available for this install source.', pending:'Not checked yet', failed:'Update check or install failed', confirm:'Install Collaboration Workbench {version}? Restart DSH afterwards.', release:'Release information', inaccessible:'Update checks are unavailable in this version.', error:'Could not check for updates. Check your network and try again.'
19
+ about:'About', cloud:'Update available', title:'Collaboration Workbench', current:'Installed version', latest:'Latest version',
20
+ check:'Check for updates', checking:'Checking…', update:'Upgrade',
21
+ ready:'A new version is available. Follow the steps below to upgrade manually.', currentState:'Up to date',
22
+ local:'Installed from a local file; update it from the original source.', other:'This install source cannot check for updates.',
23
+ pending:'Not checked yet', failed:'Update check failed', error:'Could not check for updates. Check your network and try again.',
24
+ guideTitle:'Upgrade steps', note:'This Harness does not allow replacing a running plugin, so nothing is installed automatically.',
25
+ step1:'Open the Harness plugins page.', step2:'Uninstall Collaboration Workbench.',
26
+ step3:'Install the version below, then restart Harness and refresh the page.', spec:'Install version',
27
+ warning:'If the install fails after uninstalling, the workbench stays unavailable until it succeeds. Back up workbench data and project files, and make sure the network and registry work first.',
28
+ dataSafe:'Upgrading only replaces the plugin program; roles, groups and tasks in $DSH_HOME/dsh-workbench are preserved.',
11
29
  }
12
30
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'collaboration workbench: update locales')
13
31
  const t = ctx.locale.bind(NS)
14
- const css = `.dwb-update-cloud{width:52px;min-height:42px;padding:5px 0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-brand-primary);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:2px}.dwb-update-cloud:hover{background:var(--dsw-alias-bg-layer-2)}.dwb-update-cloud:focus-visible,.dwb-update-about button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dwb-update-cloud span{font-size:10px}.dwb-update-about{flex:1;min-width:0;overflow:auto;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);padding:56px 24px 24px}.dwb-update-card{max-width:640px;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dwb-update-about h2{font-size:18px;margin:0 0 18px}.dwb-update-about dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:10px 20px;margin:0 0 18px}.dwb-update-about dt{color:var(--dsw-alias-label-secondary)}.dwb-update-about dd{margin:0;overflow-wrap:anywhere}.dwb-update-state{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-secondary);margin:0 0 16px}.dwb-update-error{color:var(--dsw-alias-state-error-primary)}.dwb-update-actions{display:flex;gap:10px;flex-wrap:wrap}.dwb-update-about button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:7px 12px;min-height:34px;cursor:pointer}.dwb-update-about button:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.dwb-update-about button:disabled{opacity:.5;cursor:default}.dwb-update-about button.dwb-update-primary{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.dwb-update-about button.dwb-update-primary:hover:not(:disabled){filter:brightness(1.06)}`
32
+ const css = `.dwb-update-cloud{width:52px;min-height:42px;padding:5px 0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-brand-primary);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:2px}.dwb-update-cloud:hover{background:var(--dsw-alias-bg-layer-2)}.dwb-update-cloud:focus-visible,.dwb-update-about button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dwb-update-cloud span{font-size:10px}.dwb-update-about{flex:1;min-width:0;overflow:auto;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);padding:56px 24px 24px}.dwb-update-card{max-width:640px;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}.dwb-update-about h2{font-size:18px;margin:0 0 18px}.dwb-update-about h3{font-size:14px;margin:18px 0 8px}.dwb-update-about dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:10px 20px;margin:0 0 18px}.dwb-update-about dt{color:var(--dsw-alias-label-secondary)}.dwb-update-about dd{margin:0;overflow-wrap:anywhere}.dwb-update-state{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-secondary);margin:0 0 16px}.dwb-update-error{color:var(--dsw-alias-state-error-primary)}.dwb-update-actions{display:flex;gap:10px;flex-wrap:wrap}.dwb-update-about button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:7px 12px;min-height:34px;cursor:pointer}.dwb-update-about button:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2)}.dwb-update-about button:disabled{opacity:.5;cursor:default}.dwb-update-steps{margin:0;padding-left:20px;font-size:13px;line-height:1.9}.dwb-update-spec{display:inline-block;margin-top:8px;padding:6px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;user-select:all;overflow-wrap:anywhere}.dwb-update-note,.dwb-update-safe{font-size:12px;line-height:1.7;color:var(--dsw-alias-label-secondary);margin:10px 0 0}.dwb-update-safe{color:var(--dsw-alias-state-success-primary)}`
15
33
  function useUpdates() {
16
34
  const [status, setStatus] = React.useState({ phase:'idle', source:'unknown', current:null, latest:null, available:false })
17
35
  const [error, setError] = React.useState(null)
@@ -35,18 +53,10 @@ export function createUpdateUI(React, ctx, call) {
35
53
  catch (failure) { if (active) setError(String(failure?.message ?? failure)) }
36
54
  }
37
55
  void check()
38
- const timer = setInterval(check, status.phase === 'installing' ? 2_000 : 60_000)
56
+ const timer = setInterval(check, 60_000)
39
57
  return () => { active = false; clearInterval(timer) }
40
- }, [status.phase])
41
- const install = React.useCallback(async () => {
42
- if (!status.available || busy || !window.confirm(t('confirm').replace('{version}', status.latest))) return
43
- setBusy(true)
44
- setError(null)
45
- try { setStatus(await call('updates.install')) }
46
- catch (failure) { setError(String(failure?.message ?? failure)) }
47
- finally { setBusy(false) }
48
- }, [status.available, status.latest, busy])
49
- return {status, error, busy, refresh, install}
58
+ }, [])
59
+ return {status, error, busy, refresh}
50
60
  }
51
61
  function UpdateCloud({ onClick }) {
52
62
  return h(React.Fragment, null, h('style', null, css),
@@ -56,18 +66,28 @@ export function createUpdateUI(React, ctx, call) {
56
66
  h('path',{d:'m12 15 0-6m-2.5 2.5L12 9l2.5 2.5'})),
57
67
  h('span',null,t('update'))))
58
68
  }
59
- function UpdateAbout({ status, error, busy, refresh, install }) {
60
- const state = status.phase === 'installed' ? t('restart') : status.phase === 'installing' ? t('updating') : error || status.phase === 'failed' ? t('failed') : status.source === 'local' ? t('local') : status.source !== 'registry' ? t('other') : status.available ? t('ready') : status.checkedAt ? status.latest ? t('currentState') : t('error') : t('pending')
61
- const canUpdate = status.available && status.phase !== 'installing' && status.phase !== 'installed'
69
+ function UpdateAbout({ status, error, busy, refresh }) {
70
+ const failed = Boolean(error) || status.phase === 'failed'
71
+ const state = failed ? t('failed') : status.source === 'local' ? t('local') : status.source !== 'registry' ? t('other')
72
+ : status.available ? t('ready') : status.checkedAt ? status.latest ? t('currentState') : t('error') : t('pending')
73
+ const spec = status.latest ? `dsh-collab-workbench@${status.latest}` : null
62
74
  return h('section', {className:'dwb-update-about', 'aria-label':t('title')},
63
75
  h('style',null,css),
64
76
  h('div',{className:'dwb-update-card'},
65
77
  h('h2',null,t('title')),
66
78
  h('dl',null,h('dt',null,t('current')),h('dd',null,status.current ?? '—'),h('dt',null,t('latest')),h('dd',null,status.latest ?? '—')),
67
- h('p',{className:`dwb-update-state ${error || status.phase === 'failed' ? 'dwb-update-error' : ''}`,role:error || status.phase === 'failed' ? 'alert':undefined}, state, error ? `: ${error}` : status.phase === 'failed' && status.error ? `: ${status.error}` : ''),
79
+ h('p',{className:`dwb-update-state ${failed ? 'dwb-update-error' : ''}`,role:failed ? 'alert':undefined},
80
+ state, failed ? `: ${error ?? status.error ?? t('error')}` : ''),
68
81
  h('div',{className:'dwb-update-actions'},
69
- h('button',{type:'button',onClick:()=>refresh(true),disabled:busy || status.phase === 'installing'},busy?t('checking'):t('check')),
70
- canUpdate ? h('button',{type:'button',className:'dwb-update-primary',onClick:install,disabled:busy},t('update')) : null)))
82
+ h('button',{type:'button',onClick:()=>refresh(true),disabled:busy},busy?t('checking'):t('check'))),
83
+ status.available ? h(React.Fragment, null,
84
+ h('h3',null,t('guideTitle')),
85
+ h('ol',{className:'dwb-update-steps'},
86
+ h('li',null,t('step1')), h('li',null,t('step2')),
87
+ h('li',null,t('step3'), spec ? h('div',null,h('code',{className:'dwb-update-spec'},spec)) : null)),
88
+ h('p',{className:'dwb-update-note'},t('note')),
89
+ h('p',{className:'dwb-update-note'},t('warning')),
90
+ h('p',{className:'dwb-update-safe'},t('dataSafe'))) : null))
71
91
  }
72
92
  return {useUpdates, UpdateCloud, UpdateAbout, t}
73
93
  }
package/src/host/api.js CHANGED
@@ -192,9 +192,8 @@ export function install(rt) {
192
192
  'execution.stop': async (payload) => ({runs:await rt.stopTaskExecution(payload.taskId)}),
193
193
  'decisions.resolve': async (payload) => ({decision:await rt.resolveDecision(payload.groupId,payload.answer,payload.seq)}),
194
194
 
195
- // ---- 协作工作台版本更新(仅浏览器用户界面调用;不注册 Agent 工具) ----
195
+ // ---- 协作工作台版本检查(只读;不注册 Agent 工具,也没有安装接口) ----
196
196
  'updates.check': async (payload) => rt.updateCheck({ fresh: payload.fresh === true }),
197
- 'updates.install': async () => rt.updateStart(),
198
197
 
199
198
  // ---- 群消息 ----
200
199
  'messages.post': async (payload) => ({
@@ -1,4 +1,5 @@
1
- // Update checks are read-only until a user invokes updateStart from the UI.
1
+ // Read-only update checks: newest published version versus this installed one.
2
+ // Installation is never performed here (this Harness refuses in-place replacement).
2
3
  // Keep npm identity separate from the historical workbench data and RPC paths.
3
4
  import { readFileSync } from 'node:fs'
4
5
 
@@ -47,17 +48,16 @@ async function registries(manager) {
47
48
  return publicRegistry ? [...new Set([own, ...fallback])] : [own]
48
49
  }
49
50
 
50
- /** Install into the existing runtime in index.js; no tool or automatic installer is registered. */
51
+ /** Read-only update check installed by index.js; no installer and no agent tool are registered. */
51
52
  export function install(rt, { fetchImpl = globalThis.fetch, now = Date.now } = {}) {
52
53
  const manager = () => rt.ctx.get?.('pluginManager')
53
54
  const status = {
54
55
  name: PKG.name, current: PKG.version, latest: null, source: 'unknown',
55
- checkedAt: 0, phase: 'idle', installed: null, error: null,
56
+ checkedAt: 0, phase: 'idle', error: null,
56
57
  }
57
58
  let target = null
58
59
  let checking = null
59
- const view = () => ({ ...status, available: target !== null && newer(status.latest, status.current)
60
- && status.phase !== 'installing' && status.phase !== 'installed' })
60
+ const view = () => ({ ...status, available: target !== null && newer(status.latest, status.current) })
61
61
 
62
62
  async function runCheck() {
63
63
  const service = manager()
@@ -92,7 +92,6 @@ export function install(rt, { fetchImpl = globalThis.fetch, now = Date.now } = {
92
92
  }
93
93
 
94
94
  function check({ fresh = false } = {}) {
95
- if (status.phase === 'installing' || status.phase === 'installed') return Promise.resolve(view())
96
95
  if (checking) return checking
97
96
  if (!fresh && status.checkedAt && now() - status.checkedAt < CHECK_TTL_MS) return Promise.resolve(view())
98
97
  status.phase = 'checking'
@@ -106,30 +105,9 @@ export function install(rt, { fetchImpl = globalThis.fetch, now = Date.now } = {
106
105
  return checking
107
106
  }
108
107
 
109
- function start() {
110
- if (checking || status.phase === 'installing') throw new Error('Update operation is already in progress')
111
- if (!target || status.source !== 'registry' || now() - status.checkedAt >= CHECK_TTL_MS) throw new Error('No checked npm update is available')
112
- const service = manager()
113
- if (typeof service?.installBundle !== 'function') throw new Error('The plugin manager is unavailable')
114
- const chosen = target
115
- status.phase = 'installing'
116
- status.error = null
117
- // The plugin can unload while installing itself; never let that reject a detached promise.
118
- void Promise.resolve().then(() => service.installBundle(chosen.spec, { registry: chosen.registry }))
119
- .then(result => {
120
- if (result?.error || !['applied', 'restart-required'].includes(result?.application)
121
- || (result.bundle && result.bundle !== PKG.name)
122
- || (result.version && result.version !== chosen.version)) {
123
- throw new Error(result?.error?.diagnostic ?? result?.error?.code ?? `Install ${result?.application ?? 'failed'}`)
124
- }
125
- status.phase = 'installed'
126
- status.installed = chosen.version
127
- })
128
- .catch(error => {
129
- status.phase = 'failed'
130
- status.error = String(error?.message ?? error).slice(0, 300)
131
- })
132
- return view()
133
- }
134
- Object.assign(rt, { updateView: view, updateCheck: check, updateStart: start })
108
+ // Checking is read-only. Installing is deliberately not exposed: this Harness
109
+ // refuses to replace a running plugin in place, and removing the bundle first
110
+ // would unload this code before the reinstall could run. The About page shows
111
+ // the manual upgrade steps instead.
112
+ Object.assign(rt, { updateView: view, updateCheck: check })
135
113
  }