agentworkshop 0.7.12 → 0.7.14

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.
Files changed (100) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/CD1SlCoI.js +14 -0
  3. package/.output/public/_nuxt/CYBq3kS7.js +1 -0
  4. package/.output/public/_nuxt/Cr9DkD1d.js +1 -0
  5. package/.output/public/_nuxt/DGVAuScD.js +1 -0
  6. package/.output/public/_nuxt/DMdVWIGz.js +19 -0
  7. package/.output/public/_nuxt/DVoWWlUW.js +1 -0
  8. package/.output/public/_nuxt/agents.CB9Ck7VQ.css +1 -0
  9. package/.output/public/_nuxt/builds/latest.json +1 -1
  10. package/.output/public/_nuxt/builds/meta/23c30854-f9e1-4651-bc3a-b695ef5faca4.json +1 -0
  11. package/.output/public/_nuxt/channel-templates.C-M5nKcC.css +1 -0
  12. package/.output/public/_nuxt/{Ds8JbqGB.js → ed5qX-x5.js} +1 -1
  13. package/.output/public/_nuxt/{entry.Bv_GCmPP.css → entry.BNApuuEW.css} +1 -1
  14. package/.output/public/_nuxt/logs.BarkrvUp.css +1 -0
  15. package/.output/public/_nuxt/pages.CG8UJ2TK.css +1 -0
  16. package/.output/public/_nuxt/teams.DN0pAGGu.css +1 -0
  17. package/.output/public/_nuxt/tokens.BVXU1jOK.css +1 -0
  18. package/.output/server/chunks/_/nitro.mjs +849 -752
  19. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  20. package/.output/server/chunks/_/user.schema.mjs +1 -1
  21. package/.output/server/chunks/_/user.schema.mjs.map +1 -1
  22. package/.output/server/chunks/build/TownView-CunmEMzS.mjs +1 -1
  23. package/.output/server/chunks/build/_id_-CVnMdSeG.mjs +1 -1
  24. package/.output/server/chunks/build/_id_-D265Wh_k.mjs +1 -1
  25. package/.output/server/chunks/build/{agents-B10wmFri.mjs → agents--6bzGtES.mjs} +156 -80
  26. package/.output/server/chunks/build/agents--6bzGtES.mjs.map +1 -0
  27. package/.output/server/chunks/build/{channel-templates-CpWxVk5A.mjs → channel-templates-C1Bb-U75.mjs} +19 -20
  28. package/.output/server/chunks/build/channel-templates-C1Bb-U75.mjs.map +1 -0
  29. package/.output/server/chunks/build/daq-DxaXk_8N.mjs +1 -1
  30. package/.output/server/chunks/build/dcw-B0dtiHFG.mjs +1 -1
  31. package/.output/server/chunks/build/{logs-Crf6f1IC.mjs → logs-UWXeNKy_.mjs} +34 -34
  32. package/.output/server/chunks/build/{logs-Crf6f1IC.mjs.map → logs-UWXeNKy_.mjs.map} +1 -1
  33. package/.output/server/chunks/build/{pages-BmRUbGUi.mjs → pages-Aexs6LjD.mjs} +27 -15
  34. package/.output/server/chunks/build/pages-Aexs6LjD.mjs.map +1 -0
  35. package/.output/server/chunks/build/{teams-CNflUtPh.mjs → teams-DD8ZXTKL.mjs} +29 -32
  36. package/.output/server/chunks/build/teams-DD8ZXTKL.mjs.map +1 -0
  37. package/.output/server/chunks/build/{tokens-BKLzcdMg.mjs → tokens-Dxnuw96_.mjs} +14 -14
  38. package/.output/server/chunks/build/{tokens-BKLzcdMg.mjs.map → tokens-Dxnuw96_.mjs.map} +1 -1
  39. package/.output/server/chunks/build/useDaqStream-BkmyQT06.mjs +1 -1
  40. package/.output/server/chunks/build/useDcwStream-DWVoU96J.mjs +1 -1
  41. package/.output/server/chunks/build/useWorkshopApi-D2gq06N2.mjs.map +1 -1
  42. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  43. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +1 -1
  44. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  45. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +14 -2
  46. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs.map +1 -1
  47. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  48. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  49. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  50. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  51. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  52. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  53. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  54. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  55. package/.output/server/chunks/routes/renderer.mjs +2 -2
  56. package/.output/server/chunks/virtual/entry.mjs +25 -13
  57. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  58. package/.output/server/package.json +1 -1
  59. package/app/composables/workshop/useWorkshopApi.ts +11 -1
  60. package/app/pages/index.vue +72 -0
  61. package/app/pages/logs/index.vue +3 -1
  62. package/app/pages/tokens.vue +3 -1
  63. package/app/pages/workshop/agents.vue +71 -6
  64. package/app/pages/workshop/channel-templates.vue +2 -1
  65. package/app/pages/workshop/teams.vue +24 -2
  66. package/app/plugins/http.ts +4 -3
  67. package/i18n/locales/en.ts +8 -1
  68. package/i18n/locales/zh-CN.ts +8 -1
  69. package/package.json +148 -148
  70. package/scripts/_dbg-harness-e2e.mjs +92 -0
  71. package/scripts/_dbg-harness-ui-dbg.py +49 -0
  72. package/scripts/_dbg-harness-ui-e2e.py +87 -0
  73. package/scripts/_dbg-login-e2e.mjs +26 -0
  74. package/server/api/workshop/harnesses.get.ts +15 -2
  75. package/server/data/dcw-lines.json +14 -0
  76. package/server/data/dcw-rollback.json +6 -6
  77. package/server/data/dcws.json +198 -198
  78. package/server/repositories/user.repository.ts +4 -3
  79. package/server/schemas/user.schema.ts +2 -2
  80. package/server/services/workshop/agents/adapters/line-spawn.ts +3 -2
  81. package/server/services/workshop/agents/harness-availability.ts +127 -0
  82. package/server/services/workshop/agents/registry.ts +21 -0
  83. package/server/services/workshop/runtime/manager.ts +16 -1
  84. package/.output/public/_nuxt/CvYfzHYz.js +0 -1
  85. package/.output/public/_nuxt/D2JyEL28.js +0 -14
  86. package/.output/public/_nuxt/D8xY-Yjc.js +0 -19
  87. package/.output/public/_nuxt/QoCzfpo2.js +0 -1
  88. package/.output/public/_nuxt/agents.CEbox5-D.css +0 -1
  89. package/.output/public/_nuxt/builds/meta/54e22640-4fd0-4fee-9077-31d4d4bb7969.json +0 -1
  90. package/.output/public/_nuxt/channel-templates.De5ZvrGz.css +0 -1
  91. package/.output/public/_nuxt/eUGyJdiT.js +0 -1
  92. package/.output/public/_nuxt/hb_ceMJP.js +0 -1
  93. package/.output/public/_nuxt/logs.BS1sb41K.css +0 -1
  94. package/.output/public/_nuxt/pages.BkE-AfIb.css +0 -1
  95. package/.output/public/_nuxt/teams.CUMnbQGf.css +0 -1
  96. package/.output/public/_nuxt/tokens.rW1xHxRW.css +0 -1
  97. package/.output/server/chunks/build/agents-B10wmFri.mjs.map +0 -1
  98. package/.output/server/chunks/build/channel-templates-CpWxVk5A.mjs.map +0 -1
  99. package/.output/server/chunks/build/pages-BmRUbGUi.mjs.map +0 -1
  100. package/.output/server/chunks/build/teams-CNflUtPh.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop-prod",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "dependencies": {
@@ -33,7 +33,7 @@ export interface AgentInfoDto {
33
33
  enabled?: number
34
34
  }
35
35
 
36
- /** harness 注册表元信息(GET /workshop/harnesses) */
36
+ /** harness 注册表元信息(GET /workshop/harnesses;含环境可用性探测结果) */
37
37
  export interface HarnessMetaDto {
38
38
  id: string
39
39
  label: string
@@ -46,6 +46,16 @@ export interface HarnessMetaDto {
46
46
  contextStats: boolean
47
47
  compact: boolean
48
48
  }
49
+ /** 环境探测:引擎可立即使用(进程内引擎恒 true;进程型引擎 = CLI 在 PATH 中) */
50
+ available?: boolean
51
+ /** 进程内引擎(无外部 CLI) */
52
+ inprocess?: boolean
53
+ /** 进程型引擎声明将拉起的命令 */
54
+ command?: string | null
55
+ /** PATH 解析出的可执行文件绝对路径 */
56
+ resolvedPath?: string | null
57
+ /** 不可用原因(available=false 时的人话提示) */
58
+ error?: string | null
49
59
  }
50
60
 
51
61
  export interface TaskDto {
@@ -9,6 +9,7 @@
9
9
  import { useDaqStream } from '@/app/composables/workshop/useDaqStream'
10
10
  import { useDcwStream } from '@/app/composables/workshop/useDcwStream'
11
11
  import { useVisibleInterval } from '@/app/composables/workshop/useVisibleInterval'
12
+ import { useWorkshopApi, type HarnessMetaDto } from '@/app/composables/workshop/useWorkshopApi'
12
13
  import AwChart from '@/app/components/AwChart.vue'
13
14
  import type { EChartsOption } from 'echarts'
14
15
 
@@ -30,13 +31,27 @@ onMounted(() => {
30
31
  daq.ensureWsFeed()
31
32
  dcw.ensureWsFeed()
32
33
  void Promise.all([daq.load(), dcw.load()]).then(() => pushTrend())
34
+ void loadHarnesses()
33
35
  useVisibleInterval(() => {
34
36
  void daq.load()
35
37
  void dcw.load()
38
+ void loadHarnesses()
36
39
  pushTrend()
37
40
  }, 5000, { bgMs: 30000 })
38
41
  })
39
42
 
43
+ // ---------- Harness 可用性(引擎 CLI 环境探测;服务端 30s 探测缓存,随兜底节拍刷新) ----------
44
+ const api = useWorkshopApi()
45
+ const harnesses = ref<HarnessMetaDto[]>([])
46
+ const loadHarnesses = async (): Promise<void> => {
47
+ try {
48
+ const res = await api.listHarnesses()
49
+ harnesses.value = (res as unknown as { data?: { harnesses?: HarnessMetaDto[] } })?.data?.harnesses ?? []
50
+ }
51
+ catch { /* 探测不可得时面板留空,不阻塞大屏 */ }
52
+ }
53
+ const harnessOk = computed(() => harnesses.value.filter(h => h.available !== false).length)
54
+
40
55
  // ---------- KPI ----------
41
56
  const linesActive = computed(() => dcw.lines.filter(l => dcw.lineStateOf(l.id).active))
42
57
  const daqOnline = computed(() => daq.nodes.filter(n => n.enabled && n.state !== 'offline').length)
@@ -351,6 +366,31 @@ const fleetOverflow = computed(() => Math.max(lineCards.value.length - FLEET_CAP
351
366
  </div>
352
367
 
353
368
  <!-- 大屏图阵 -->
369
+ <!-- 执行引擎可用性(Harness CLI 环境探测;未安装不可选,派工前强校验) -->
370
+ <section
371
+ v-if="harnesses.length > 0"
372
+ class="dpanel harness aw-stagger"
373
+ >
374
+ <header class="dp-hd">
375
+ <h3>{{ t('home.harness.title') }}</h3>
376
+ <small>{{ t('home.harness.sub') }}</small>
377
+ <small class="mono fleet-total">{{ harnessOk }}/{{ harnesses.length }}</small>
378
+ </header>
379
+ <div class="harness-row">
380
+ <div
381
+ v-for="h in harnesses"
382
+ :key="h.id"
383
+ class="h-item"
384
+ :class="{ off: h.available === false }"
385
+ :title="h.available === false ? (h.error ?? '') : (h.resolvedPath ?? h.command ?? '')"
386
+ >
387
+ <span class="h-dot" />
388
+ <span class="h-name">{{ h.label }}</span>
389
+ <span class="h-cmd mono">{{ h.available === false ? t('home.harness.missing') : (h.inprocess ? 'in-process' : h.command) }}</span>
390
+ </div>
391
+ </div>
392
+ </section>
393
+
354
394
  <div class="grid aw-stagger">
355
395
  <section class="dpanel span8">
356
396
  <header class="dp-hd">
@@ -790,6 +830,38 @@ const fleetOverflow = computed(() => Math.max(lineCards.value.length - FLEET_CAP
790
830
  font-size: 11px;
791
831
  color: var(--ink-faint);
792
832
  }
833
+ /* ---------- Harness 可用性条:墨色药丸式引擎清单,未安装灰化 ---------- */
834
+ .harness-row {
835
+ display: flex;
836
+ flex-wrap: wrap;
837
+ gap: 8px;
838
+ padding: 4px 0 6px;
839
+ }
840
+ .h-item {
841
+ display: flex;
842
+ gap: 7px;
843
+ align-items: center;
844
+ padding: 5px 12px;
845
+ font-size: 11.5px;
846
+ cursor: default;
847
+ background: color-mix(in srgb, var(--ink) 3%, transparent);
848
+ border: 1px solid var(--divider-hair);
849
+ border-radius: var(--radius-pill);
850
+ transition: border-color 0.15s, background 0.15s;
851
+ }
852
+ .h-item:hover { border-color: var(--line-strong); }
853
+ .h-dot {
854
+ flex: none;
855
+ width: 6px;
856
+ height: 6px;
857
+ border-radius: 50%;
858
+ background: var(--tone-success-dot, #4a6b57);
859
+ }
860
+ .h-item.off { opacity: 0.55; }
861
+ .h-item.off .h-dot { background: var(--tone-danger-dot, #c25a4e); }
862
+ .h-name { font-weight: 600; color: var(--ink); }
863
+ .h-item.off .h-name { color: var(--ink-soft); }
864
+ .h-cmd { font-size: 10px; color: var(--ink-faint); }
793
865
  /* 全部产线入口:虚线幽灵卡,聚合清单外的余量 */
794
866
  .fleet-all {
795
867
  display: flex;
@@ -3,7 +3,9 @@
3
3
  <!-- 页头:标题 + 手动记录入口 -->
4
4
  <header class="head">
5
5
  <div>
6
- <p class="aw-kicker">agentworkshop / audit log</p>
6
+ <p class="aw-kicker">
7
+ agentworkshop / audit log
8
+ </p>
7
9
  <h1 class="h1">
8
10
  {{ $t('logs.title') }}
9
11
  </h1>
@@ -272,7 +272,9 @@ useHead({ title: () => tt('titles.tokens') })
272
272
  <template v-else>
273
273
  <div class="head">
274
274
  <div>
275
- <p class="aw-kicker">agentworkshop / api tokens</p>
275
+ <p class="aw-kicker">
276
+ agentworkshop / api tokens
277
+ </p>
276
278
  <h2>API Token</h2>
277
279
  <p class="sub">
278
280
  {{ userStore.user?.name }} · {{ $t('tokens.k1upppaw026') }}
@@ -26,7 +26,9 @@ const load = async (): Promise<void> => {
26
26
  loading.value = false
27
27
  }
28
28
  }
29
- void load()
29
+ // SSR 安全:setup 期 $http(axios)无法在服务端发相对地址请求,拒绝会变成未处理
30
+ // rejection 直杀渲染进程;页面数据一律客户端装载
31
+ if (import.meta.client) void load()
30
32
 
31
33
  // ===== 过滤(全部/我的/公开/内置;admin 另有"他人私有") =====
32
34
  type Filter = 'all' | 'mine' | 'public' | 'builtin' | 'others'
@@ -64,8 +66,10 @@ const visTag = (t: AgentTemplateDto): { text: string, color: string, icon?: stri
64
66
  return { text: tt('agents.k447jj019'), color: 'default' }
65
67
  }
66
68
 
67
- // ===== harness 选项(引擎注册表动态拉取;失败回退静态表) =====
69
+ // ===== harness 选项(引擎注册表动态拉取,含环境可用性探测;失败回退静态表) =====
68
70
  const harnesses = ref<HarnessMetaDto[]>([])
71
+ const harnessById = computed(() => new Map(harnesses.value.map(h => [h.id, h])))
72
+ const isUnavailable = (id: string): boolean => harnessById.value.get(id)?.available === false
69
73
  const harnessOptions = computed(() =>
70
74
  (harnesses.value.length > 0
71
75
  ? harnesses.value
@@ -73,7 +77,10 @@ const harnessOptions = computed(() =>
73
77
  { id: 'mock', label: 'mock(测试)', description: '', capabilities: { steer: true, supervise: false, hitl: false, terminal: false, contextStats: false, compact: false } },
74
78
  { id: 'omp', label: 'omp(真实 LLM)', description: '', capabilities: { steer: true, supervise: true, hitl: true, terminal: true, contextStats: true, compact: true } },
75
79
  { id: 'claude', label: 'claude', description: '', capabilities: { steer: false, supervise: false, hitl: false, terminal: false, contextStats: false, compact: false } },
76
- ] as HarnessMetaDto[]).map(h => ({ value: h.id, label: h.label })),
80
+ ] as HarnessMetaDto[]).map((h) => {
81
+ const unavailable = h.available === false
82
+ return { value: h.id, label: unavailable ? `${h.label}(${tt('agents.notInstalled')})` : h.label, disabled: unavailable }
83
+ }),
77
84
  )
78
85
  const capBadges = (id: string): string[] => {
79
86
  const caps = harnesses.value.find(h => h.id === id)?.capabilities
@@ -93,7 +100,7 @@ const loadHarnesses = async (): Promise<void> => {
93
100
  }
94
101
  catch { /* 回退静态表 */ }
95
102
  }
96
- void loadHarnesses()
103
+ if (import.meta.client) void loadHarnesses()
97
104
 
98
105
  const editOpen = ref(false)
99
106
  const editing = ref<AgentTemplateDto | null>(null)
@@ -123,6 +130,11 @@ const save = async (): Promise<void> => {
123
130
  message.error('config 不是合法 JSON')
124
131
  return
125
132
  }
133
+ // 前端兜底:引擎未安装禁止保存(与后端 assertHarnessUsable 同判据)
134
+ if (isUnavailable(form.harness)) {
135
+ message.error(harnessById.value.get(form.harness)?.error ?? tt('agents.notInstalled'))
136
+ return
137
+ }
126
138
  try {
127
139
  if (editing.value) {
128
140
  await api.updateTemplate(editing.value.id, { name: form.name, harness: form.harness, config, visibility: form.visibility })
@@ -220,8 +232,21 @@ useHead({ title: () => tt('titles.agents') })
220
232
  <a-table-column
221
233
  title="harness"
222
234
  data-index="harness"
223
- :width="100"
224
- />
235
+ :width="120"
236
+ >
237
+ <template #default="{ record }">
238
+ <span
239
+ class="h-cell"
240
+ :class="{ off: isUnavailable(record.harness) }"
241
+ :title="isUnavailable(record.harness) ? (harnessById.get(record.harness)?.error ?? '') : (harnessById.get(record.harness)?.resolvedPath ?? '')"
242
+ >
243
+ <span class="h-dot" />{{ record.harness }}<span
244
+ v-if="isUnavailable(record.harness)"
245
+ class="h-miss"
246
+ >({{ tt('agents.notInstalled') }})</span>
247
+ </span>
248
+ </template>
249
+ </a-table-column>
225
250
  <a-table-column
226
251
  :title="$t('agents.k3lrqn0002')"
227
252
  :width="120"
@@ -346,6 +371,21 @@ useHead({ title: () => tt('titles.agents') })
346
371
  v-model:value="form.harness"
347
372
  :options="harnessOptions"
348
373
  />
374
+ <div
375
+ v-if="harnesses.length > 0"
376
+ class="harness-status"
377
+ :class="{ off: isUnavailable(form.harness) }"
378
+ >
379
+ <template v-if="isUnavailable(form.harness)">
380
+ <span class="i-tabler-plug-off" /> {{ harnessById.get(form.harness)?.error }}
381
+ </template>
382
+ <template v-else-if="harnessById.get(form.harness)?.inprocess">
383
+ <span class="i-tabler-plug-connected" /> {{ tt('agents.harnessInprocess') }}
384
+ </template>
385
+ <template v-else-if="harnessById.get(form.harness)?.resolvedPath">
386
+ <span class="i-tabler-plug-connected" /> <span class="mono">{{ harnessById.get(form.harness)?.resolvedPath }}</span>
387
+ </template>
388
+ </div>
349
389
  <div
350
390
  v-if="capBadges(form.harness).length"
351
391
  class="harness-caps"
@@ -388,6 +428,31 @@ useHead({ title: () => tt('titles.agents') })
388
428
  border-radius: 3px;
389
429
  opacity: 0.75;
390
430
  }
431
+ .harness-status {
432
+ display: flex;
433
+ gap: 6px;
434
+ align-items: center;
435
+ margin-top: 6px;
436
+ font-size: 11px;
437
+ color: var(--ink-faint);
438
+ }
439
+ .harness-status.off { color: var(--danger, #c25a4e); }
440
+ .h-cell {
441
+ display: inline-flex;
442
+ gap: 6px;
443
+ align-items: center;
444
+ font-family: var(--font-mono);
445
+ font-size: 12px;
446
+ }
447
+ .h-dot {
448
+ width: 6px;
449
+ height: 6px;
450
+ border-radius: 50%;
451
+ background: var(--ok, #4a6b57);
452
+ }
453
+ .h-cell.off .h-dot { background: var(--danger, #c25a4e); }
454
+ .h-cell.off { opacity: 0.75; }
455
+ .h-miss { color: var(--danger, #c25a4e); font-family: var(--font-sans); font-size: 11px; }
391
456
  .page { padding: 4px; }
392
457
  .head {
393
458
  display: flex;
@@ -31,7 +31,8 @@ const load = async (): Promise<void> => {
31
31
  loading.value = false
32
32
  }
33
33
  }
34
- void load()
34
+ // SSR 安全:setup 期 $http(axios)无法在服务端发相对地址请求(同 agents 页注释)
35
+ if (import.meta.client) void load()
35
36
 
36
37
  type Filter = 'all' | 'mine' | 'public' | 'builtin'
37
38
  const filter = ref<Filter>('all')
@@ -31,7 +31,29 @@ const load = async (): Promise<void> => {
31
31
  loading.value = false
32
32
  }
33
33
  }
34
- void load()
34
+ // SSR 安全:setup 期 $http(axios)无法在服务端发相对地址请求(同 agents 页注释)
35
+ if (import.meta.client) void load()
36
+
37
+ // ===== harness 可用性(成员选择禁用引擎未安装的模板;deploy 由后端 assert 兜底) =====
38
+ const harnessAvail = ref<Record<string, boolean>>({})
39
+ const loadHarnessAvail = async (): Promise<void> => {
40
+ try {
41
+ const res = await api.listHarnesses()
42
+ const list = (res as unknown as { data?: { harnesses?: Array<{ id: string, available?: boolean }> } })?.data?.harnesses
43
+ if (Array.isArray(list)) {
44
+ const m: Record<string, boolean> = {}
45
+ for (const h of list) m[h.id] = h.available !== false
46
+ harnessAvail.value = m
47
+ }
48
+ }
49
+ catch { /* 探测不可得时不限制选项(后端仍有强校验) */ }
50
+ }
51
+ if (import.meta.client) void loadHarnessAvail()
52
+ const tplUnavailable = (tpl: AgentTemplateDto): boolean => harnessAvail.value[tpl.harness] === false
53
+ const memberOptions = computed(() => templates.value.map((t) => {
54
+ const un = tplUnavailable(t)
55
+ return { value: t.id, label: un ? `${t.name}(${t.harness} · ${t('agents.notInstalled')})` : `${t.name}(${t.harness})`, disabled: un }
56
+ }))
35
57
 
36
58
  // ===== 过滤 =====
37
59
  type Filter = 'all' | 'mine' | 'public' | 'builtin'
@@ -323,7 +345,7 @@ useHead({ title: () => t('titles.teams') })
323
345
  <a-form-item :label="$t('teams.k41ds5006')">
324
346
  <a-select
325
347
  v-model:value="addTemplateId"
326
- :options="templates.map(t => ({ value: t.id, label: `${t.name}(${t.harness})` }))"
348
+ :options="memberOptions"
327
349
  />
328
350
  </a-form-item>
329
351
  <a-form-item :label="$t('teams.k479op007')">
@@ -52,17 +52,18 @@ export default defineNuxtPlugin((nuxtApp) => {
52
52
  catch { /* 无 pinia 上下文(极端时序) */ }
53
53
  if (!g.__awShown401) {
54
54
  g.__awShown401 = true
55
- message.error('登录已过期,请重新登录')
55
+ // SSR 安全:ant-design message 依赖 DOM,服务端调用会炸掉渲染进程
56
+ if (import.meta.client) message.error('登录已过期,请重新登录')
56
57
  setTimeout(() => {
57
58
  g.__awShown401 = false
58
59
  }, 3000)
59
60
  }
60
61
  }
61
62
  else if (status >= 500) {
62
- message.error('服务器异常,请稍后重试')
63
+ if (import.meta.client) message.error('服务器异常,请稍后重试')
63
64
  }
64
65
  else {
65
- message.error(msg)
66
+ if (import.meta.client) message.error(msg)
66
67
  }
67
68
 
68
69
  return Promise.reject(error)
@@ -81,6 +81,11 @@ export default {
81
81
  fleetAll: 'View all lines',
82
82
  fleetHint: 'Running first · top 8 shown',
83
83
  writesFailed: 'failed',
84
+ harness: {
85
+ title: 'Agent Harnesses',
86
+ sub: 'CLI availability probe · ready to dispatch',
87
+ missing: 'not installed',
88
+ },
84
89
  fields: {
85
90
  name: 'App Name',
86
91
  version: 'Version',
@@ -342,6 +347,8 @@ export default {
342
347
  hitlSubmit: 'Submit',
343
348
  },
344
349
  agents: {
350
+ notInstalled: 'not installed',
351
+ harnessInprocess: 'In-process engine · always available',
345
352
  k3pa5h4025: 'Templates',
346
353
  k3xhia001: 'Name',
347
354
  k3lrqn0002: 'Visibility',
@@ -1119,7 +1126,7 @@ export default {
1119
1126
  ke16e53026: '\'s resources (user-isolated; persisted server-side)',
1120
1127
  k41ds5027: 'Template',
1121
1128
  k1ggoa45028: 'Members / Busy',
1122
- k48h2c001: 'Email',
1129
+ k48h2c001: 'Email or username',
1123
1130
  k3yvgs002: 'Password',
1124
1131
  kwgbai9003: 'Username (unique)',
1125
1132
  kizjkbo004: 'Email (unique)',
@@ -81,6 +81,11 @@ export default {
81
81
  fleetAll: '查看全部产线',
82
82
  fleetHint: '运行中优先 · 最多展示 8 条',
83
83
  writesFailed: '失败',
84
+ harness: {
85
+ title: '执行引擎',
86
+ sub: 'Harness CLI 环境探测 · 就绪即可派工',
87
+ missing: '未安装',
88
+ },
84
89
  fields: {
85
90
  name: '应用名称',
86
91
  version: '版本',
@@ -342,6 +347,8 @@ export default {
342
347
  hitlSubmit: '提交',
343
348
  },
344
349
  agents: {
350
+ notInstalled: '未安装',
351
+ harnessInprocess: '进程内引擎 · 恒可用',
345
352
  k3pa5h4025: '模板库',
346
353
  k3xhia001: '名称',
347
354
  k3lrqn0002: '可见性',
@@ -1118,7 +1125,7 @@ export default {
1118
1125
  ke16e53026: '的资源(用户级隔离;服务端持久化)',
1119
1126
  k41ds5027: '模板',
1120
1127
  k1ggoa45028: '成员 / 忙',
1121
- k48h2c001: '邮箱',
1128
+ k48h2c001: '邮箱 / 用户名',
1122
1129
  k3yvgs002: '密码',
1123
1130
  kwgbai9003: '用户名(唯一)',
1124
1131
  kizjkbo004: '邮箱(唯一)',