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
package/package.json CHANGED
@@ -1,148 +1,148 @@
1
- {
2
- "name": "agentworkshop",
3
- "version": "0.7.12",
4
- "type": "module",
5
- "description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
6
- "license": "PolyForm-Noncommercial-1.0.0",
7
- "exports": {
8
- ".": {
9
- "types": "./sdk/index.d.mts",
10
- "default": "./sdk/index.mjs"
11
- },
12
- "./sdk": {
13
- "types": "./sdk/index.d.mts",
14
- "default": "./sdk/index.mjs"
15
- },
16
- "./sdk/client": {
17
- "types": "./sdk/client.d.mts",
18
- "default": "./sdk/client.mjs"
19
- },
20
- "./package.json": "./package.json"
21
- },
22
- "bin": {
23
- "aw": "bin/aw.mjs",
24
- "agentworkshop": "bin/aw.mjs"
25
- },
26
- "files": [
27
- ".output",
28
- "bin",
29
- "cli",
30
- "sdk",
31
- "app",
32
- "server",
33
- "shared",
34
- "i18n",
35
- "public",
36
- "scripts",
37
- ".AgentWorkShop/prompts",
38
- "config.yml",
39
- "nuxt.config.ts",
40
- "tsconfig.json",
41
- "uno.config.ts",
42
- "docker-compose.yml",
43
- ".env.example",
44
- "README-zh.md",
45
- "docs/cli.md",
46
- "docs/plugins.md",
47
- "docs/sdk.md",
48
- "docs/tui.md",
49
- "tui"
50
- ],
51
- "packageManager": "pnpm@11.9.0",
52
- "engines": {
53
- "node": ">=23.4.0"
54
- },
55
- "scripts": {
56
- "dev": "node bin/aw.mjs dev",
57
- "build": "node bin/aw.mjs build",
58
- "start": "node bin/aw.mjs start",
59
- "generate": "nuxt generate",
60
- "preview": "node bin/aw.mjs start",
61
- "cli": "node bin/aw.mjs",
62
- "tui": "node tui/aw-tui.mjs",
63
- "postinstall": "node scripts/home-bootstrap.mjs",
64
- "prepublishOnly": "node bin/aw.mjs build",
65
- "lint": "eslint .",
66
- "lint:fix": "eslint . --fix",
67
- "typecheck": "nuxt typecheck",
68
- "prepare": "husky",
69
- "game:test": "tsx scripts/test-agent-brain.ts && tsx scripts/test-game-session.ts",
70
- "test:api-live": "node scripts/api-live-e2e.mjs",
71
- "game:verify": "node scripts/verify-game.mjs",
72
- "game:build-assets": "node scripts/build-player-sheet.mjs",
73
- "game:build-wuwa": "node scripts/build-wuwa-assets.mjs",
74
- "game:build-models": "node scripts/build-character-models.mjs",
75
- "game:build-world": "node scripts/build-world-map.mjs",
76
- "game:build-model3d": "node scripts/build-model-3d.mjs"
77
- },
78
- "lint-staged": {
79
- "*.{js,mjs,cjs,ts,vue}": "eslint --fix"
80
- },
81
- "dependencies": {
82
- "@ant-design-vue/nuxt": "^1.4.6",
83
- "@ant-design/icons-vue": "^7.0.1",
84
- "@earendil-works/pi-tui": "^0.84.4",
85
- "@fontsource-variable/fraunces": "^5.3.0",
86
- "@fontsource-variable/geist": "^5.3.0",
87
- "@fontsource-variable/geist-mono": "^5.3.0",
88
- "@fontsource-variable/inter": "^5.3.0",
89
- "@fontsource/eb-garamond": "^5.3.0",
90
- "@fontsource/ibm-plex-mono": "^5.3.0",
91
- "@iconify-json/tabler": "^1.2.0",
92
- "@modelcontextprotocol/sdk": "^1.30.0",
93
- "@nuxtjs/i18n": "^9.0.0",
94
- "@pinia/nuxt": "^0.11.0",
95
- "@unocss/nuxt": "^66.0.0",
96
- "@unocss/preset-attributify": "^66.0.0",
97
- "@unocss/preset-icons": "^66.7.5",
98
- "@unocss/preset-typography": "^66.0.0",
99
- "@vueuse/core": "^13.0.0",
100
- "@vueuse/nuxt": "^13.0.0",
101
- "@xterm/addon-fit": "^0.11.0",
102
- "@xterm/xterm": "^6.0.0",
103
- "ant-design-vue": "^4.2.6",
104
- "axios": "^1.7.9",
105
- "echarts": "^5.6.0",
106
- "h3": "2.0.1-rc.26",
107
- "js-yaml": "^4.1.0",
108
- "minio": "^8.0.7",
109
- "modbus-serial": "^8.0.25",
110
- "mqtt": "^5.15.2",
111
- "node-opcua": "^2.178.0",
112
- "nuxt": "^4.0.0",
113
- "nuxt-auth-utils": "^0.5.0",
114
- "pg": "^8.23.0",
115
- "phaser": "^4.0.0",
116
- "pinia": "^3.0.0",
117
- "pinia-plugin-persistedstate": "^4.2.0",
118
- "sqlite-vec": "^0.1.9",
119
- "three": "^0.185.1",
120
- "unocss": "^66.0.0",
121
- "unplugin-icons": "^22.0.0",
122
- "vue": "^3.5.13",
123
- "vue-echarts": "^7.0.3",
124
- "vue-router": "^4.5.0",
125
- "zod": "^3.24.1"
126
- },
127
- "devDependencies": {
128
- "@commitlint/cli": "^19.6.1",
129
- "@commitlint/config-conventional": "^19.6.0",
130
- "@nuxt/eslint": "^1.0.0",
131
- "@types/js-yaml": "^4.0.9",
132
- "@types/node": "^26.2.0",
133
- "@types/three": "^0.185.4",
134
- "eslint": "^9.17.0",
135
- "husky": "^9.1.7",
136
- "lint-staged": "^15.3.0",
137
- "pngjs": "^7.0.0",
138
- "puppeteer-core": "^25.5.0",
139
- "tsx": "^4.23.12",
140
- "typescript": "^5.7.3",
141
- "vue-tsc": "^3.3.10"
142
- },
143
- "pnpm": {
144
- "ignoredBuiltDependencies": [
145
- "@serialport/bindings-cpp"
146
- ]
147
- }
148
- }
1
+ {
2
+ "name": "agentworkshop",
3
+ "version": "0.7.14",
4
+ "type": "module",
5
+ "description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
6
+ "license": "PolyForm-Noncommercial-1.0.0",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./sdk/index.d.mts",
10
+ "default": "./sdk/index.mjs"
11
+ },
12
+ "./sdk": {
13
+ "types": "./sdk/index.d.mts",
14
+ "default": "./sdk/index.mjs"
15
+ },
16
+ "./sdk/client": {
17
+ "types": "./sdk/client.d.mts",
18
+ "default": "./sdk/client.mjs"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "bin": {
23
+ "aw": "bin/aw.mjs",
24
+ "agentworkshop": "bin/aw.mjs"
25
+ },
26
+ "files": [
27
+ ".output",
28
+ "bin",
29
+ "cli",
30
+ "sdk",
31
+ "app",
32
+ "server",
33
+ "shared",
34
+ "i18n",
35
+ "public",
36
+ "scripts",
37
+ ".AgentWorkShop/prompts",
38
+ "config.yml",
39
+ "nuxt.config.ts",
40
+ "tsconfig.json",
41
+ "uno.config.ts",
42
+ "docker-compose.yml",
43
+ ".env.example",
44
+ "README-zh.md",
45
+ "docs/cli.md",
46
+ "docs/plugins.md",
47
+ "docs/sdk.md",
48
+ "docs/tui.md",
49
+ "tui"
50
+ ],
51
+ "packageManager": "pnpm@11.9.0",
52
+ "engines": {
53
+ "node": ">=23.4.0"
54
+ },
55
+ "scripts": {
56
+ "dev": "node bin/aw.mjs dev",
57
+ "build": "node bin/aw.mjs build",
58
+ "start": "node bin/aw.mjs start",
59
+ "generate": "nuxt generate",
60
+ "preview": "node bin/aw.mjs start",
61
+ "cli": "node bin/aw.mjs",
62
+ "tui": "node tui/aw-tui.mjs",
63
+ "postinstall": "node scripts/home-bootstrap.mjs",
64
+ "prepublishOnly": "node bin/aw.mjs build",
65
+ "lint": "eslint .",
66
+ "lint:fix": "eslint . --fix",
67
+ "typecheck": "nuxt typecheck",
68
+ "prepare": "husky",
69
+ "game:test": "tsx scripts/test-agent-brain.ts && tsx scripts/test-game-session.ts",
70
+ "test:api-live": "node scripts/api-live-e2e.mjs",
71
+ "game:verify": "node scripts/verify-game.mjs",
72
+ "game:build-assets": "node scripts/build-player-sheet.mjs",
73
+ "game:build-wuwa": "node scripts/build-wuwa-assets.mjs",
74
+ "game:build-models": "node scripts/build-character-models.mjs",
75
+ "game:build-world": "node scripts/build-world-map.mjs",
76
+ "game:build-model3d": "node scripts/build-model-3d.mjs"
77
+ },
78
+ "lint-staged": {
79
+ "*.{js,mjs,cjs,ts,vue}": "eslint --fix"
80
+ },
81
+ "dependencies": {
82
+ "@ant-design-vue/nuxt": "^1.4.6",
83
+ "@ant-design/icons-vue": "^7.0.1",
84
+ "@earendil-works/pi-tui": "^0.84.4",
85
+ "@fontsource-variable/fraunces": "^5.3.0",
86
+ "@fontsource-variable/geist": "^5.3.0",
87
+ "@fontsource-variable/geist-mono": "^5.3.0",
88
+ "@fontsource-variable/inter": "^5.3.0",
89
+ "@fontsource/eb-garamond": "^5.3.0",
90
+ "@fontsource/ibm-plex-mono": "^5.3.0",
91
+ "@iconify-json/tabler": "^1.2.0",
92
+ "@modelcontextprotocol/sdk": "^1.30.0",
93
+ "@nuxtjs/i18n": "^9.0.0",
94
+ "@pinia/nuxt": "^0.11.0",
95
+ "@unocss/nuxt": "^66.0.0",
96
+ "@unocss/preset-attributify": "^66.0.0",
97
+ "@unocss/preset-icons": "^66.7.5",
98
+ "@unocss/preset-typography": "^66.0.0",
99
+ "@vueuse/core": "^13.0.0",
100
+ "@vueuse/nuxt": "^13.0.0",
101
+ "@xterm/addon-fit": "^0.11.0",
102
+ "@xterm/xterm": "^6.0.0",
103
+ "ant-design-vue": "^4.2.6",
104
+ "axios": "^1.7.9",
105
+ "echarts": "^5.6.0",
106
+ "h3": "2.0.1-rc.26",
107
+ "js-yaml": "^4.1.0",
108
+ "minio": "^8.0.7",
109
+ "modbus-serial": "^8.0.25",
110
+ "mqtt": "^5.15.2",
111
+ "node-opcua": "^2.178.0",
112
+ "nuxt": "^4.0.0",
113
+ "nuxt-auth-utils": "^0.5.0",
114
+ "pg": "^8.23.0",
115
+ "phaser": "^4.0.0",
116
+ "pinia": "^3.0.0",
117
+ "pinia-plugin-persistedstate": "^4.2.0",
118
+ "sqlite-vec": "^0.1.9",
119
+ "three": "^0.185.1",
120
+ "unocss": "^66.0.0",
121
+ "unplugin-icons": "^22.0.0",
122
+ "vue": "^3.5.13",
123
+ "vue-echarts": "^7.0.3",
124
+ "vue-router": "^4.5.0",
125
+ "zod": "^3.24.1"
126
+ },
127
+ "devDependencies": {
128
+ "@commitlint/cli": "^19.6.1",
129
+ "@commitlint/config-conventional": "^19.6.0",
130
+ "@nuxt/eslint": "^1.0.0",
131
+ "@types/js-yaml": "^4.0.9",
132
+ "@types/node": "^26.2.0",
133
+ "@types/three": "^0.185.4",
134
+ "eslint": "^9.17.0",
135
+ "husky": "^9.1.7",
136
+ "lint-staged": "^15.3.0",
137
+ "pngjs": "^7.0.0",
138
+ "puppeteer-core": "^25.5.0",
139
+ "tsx": "^4.23.12",
140
+ "typescript": "^5.7.3",
141
+ "vue-tsc": "^3.3.10"
142
+ },
143
+ "pnpm": {
144
+ "ignoredBuiltDependencies": [
145
+ "@serialport/bindings-cpp"
146
+ ]
147
+ }
148
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Harness 可用性检查 E2E(隔离实例 :3021)
3
+ * 1) GET /harnesses:6 引擎 + available 探测(进程型有 resolvedPath)
4
+ * 2) ?refresh=1 强制重探
5
+ * 3) 负向:config.command 指向不存在的二进制 → 模板创建 409 HARNESS_UNAVAILABLE
6
+ * 4) 负向:settings 热改 harness.dsh_command → dsh available=false;恢复后 true
7
+ * 5) 未知 harness → 400 UNKNOWN_HARNESS
8
+ * 6) 正向:模板创建/更新/克隆(harness 可用)照常
9
+ */
10
+ const BASE = 'http://127.0.0.1:3021'
11
+ let token = process.argv[2] ?? ''
12
+ let pass = 0
13
+ let fail = 0
14
+
15
+ const ok = (cond, name, extra = '') => {
16
+ if (cond) { pass++; console.log(` ✓ ${name}`) }
17
+ else { fail++; console.log(` ✗ ${name} ${extra}`) }
18
+ }
19
+ const api = async (method, path, body) => {
20
+ const res = await fetch(BASE + path, {
21
+ method,
22
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
23
+ body: body === undefined ? undefined : JSON.stringify(body),
24
+ })
25
+ let json = null
26
+ try { json = await res.json() } catch { /* ignore */ }
27
+ return { status: res.status, json }
28
+ }
29
+
30
+ console.log('== 1. GET /api/workshop/harnesses ==')
31
+ {
32
+ const { status, json } = await api('GET', '/api/workshop/harnesses')
33
+ ok(status === 200, '200')
34
+ const hs = json?.data?.harnesses ?? []
35
+ ok(hs.length === 6, `6 harnesses(实际 ${hs.length})`)
36
+ const byId = Object.fromEntries(hs.map(h => [h.id, h]))
37
+ for (const id of ['omp', 'opencode', 'codex', 'dsh']) {
38
+ ok(byId[id]?.available === true, `${id} available=true`)
39
+ ok(!!byId[id]?.resolvedPath, `${id} resolvedPath=${byId[id]?.resolvedPath ?? '∅'}`)
40
+ ok(byId[id]?.command === id, `${id} command=${byId[id]?.command}`)
41
+ }
42
+ ok(byId.mock?.available === true && byId.mock?.inprocess === true, 'mock in-process 恒可用')
43
+ ok(byId.claude?.available === true && byId.claude?.inprocess === true, 'claude in-process 恒可用')
44
+ ok(typeof byId.omp?.label === 'string' && typeof byId.omp?.capabilities?.steer === 'boolean', 'meta 能力面齐全')
45
+ }
46
+
47
+ console.log('== 2. ?refresh=1 强制重探 ==')
48
+ {
49
+ const { json } = await api('GET', '/api/workshop/harnesses?refresh=1')
50
+ ok(json?.data?.harnesses?.every(h => typeof h.available === 'boolean'), 'refresh 结果完整')
51
+ }
52
+
53
+ console.log('== 3. 负向:模板创建(引擎命令不存在)==')
54
+ {
55
+ const r1 = await api('POST', '/api/workshop/agents', { name: 'bad-dsh', harness: 'dsh', config: { command: 'aw-definitely-missing-xyz' } })
56
+ ok(r1.status === 409 && r1.json?.code === 'HARNESS_UNAVAILABLE', `409 HARNESS_UNAVAILABLE(实际 ${r1.status}/${r1.json?.code})`)
57
+ ok(String(r1.json?.message ?? '').includes('aw-definitely-missing-xyz'), `人话报错含命令名:${r1.json?.message}`)
58
+ // 同 harness、同错误命令的探测缓存 → 克隆路径同样拒绝
59
+ const tpl = await api('POST', '/api/workshop/agents', { name: 'good-omp', harness: 'omp', config: {} })
60
+ ok(tpl.status === 200, '正向:omp 模板创建成功')
61
+ if (tpl.status === 200) await api('DELETE', `/api/workshop/agents/${tpl.json?.data?.id}`)
62
+ }
63
+
64
+ console.log('== 4. settings 热改 dsh 命令 → 探测联动 ==')
65
+ {
66
+ await api('PATCH', '/api/system/settings', { override: { 'harness.dsh_command': 'aw-bogus-dsh-bin' } })
67
+ await new Promise(r => setTimeout(r, 4000))
68
+ const q = await api('GET', '/api/workshop/harnesses?refresh=1')
69
+ const dsh = q.json?.data?.harnesses?.find(h => h.id === 'dsh')
70
+ ok(dsh?.available === false, 'dsh available=false')
71
+ ok(String(dsh?.command ?? '').includes('aw-bogus-dsh-bin'), `command 联动=${dsh?.command}`)
72
+ await api('PATCH', '/api/system/settings', { override: { 'harness.dsh_command': null } })
73
+ await new Promise(r => setTimeout(r, 4000))
74
+ const q2 = await api('GET', '/api/workshop/harnesses?refresh=1')
75
+ const dsh2 = q2.json?.data?.harnesses?.find(h => h.id === 'dsh')
76
+ ok(dsh2?.available === true, '恢复后 dsh available=true')
77
+ }
78
+
79
+ console.log('== 5. 未知 harness ==')
80
+ {
81
+ const r = await api('POST', '/api/workshop/agents', { name: 'u', harness: 'nope', config: {} })
82
+ ok(r.status === 400 && r.json?.code === 'UNKNOWN_HARNESS', `400 UNKNOWN_HARNESS(实际 ${r.status}/${r.json?.code})`)
83
+ }
84
+
85
+ console.log('== 6. 未认证 ==')
86
+ {
87
+ const res = await fetch(`${BASE}/api/workshop/harnesses`)
88
+ ok(res.status === 401, `401(实际 ${res.status})`)
89
+ }
90
+
91
+ console.log(`\nRESULT: ${pass} pass / ${fail} fail`)
92
+ process.exit(fail > 0 ? 1 : 0)
@@ -0,0 +1,49 @@
1
+ """调试 v2:登录 → 仪表盘,全程 flush 输出"""
2
+ import asyncio, sys
3
+ from playwright.async_api import async_playwright
4
+
5
+ BASE = 'http://127.0.0.1:3021'
6
+ P = lambda *a: print(*a, flush=True)
7
+
8
+ async def main():
9
+ async with async_playwright() as p:
10
+ browser = await p.chromium.launch()
11
+ page = await browser.new_page(viewport={'width': 1440, 'height': 900})
12
+ page.on('pageerror', lambda e: P('[pageerror]', str(e)[:200]))
13
+
14
+ P('goto /workshop ...')
15
+ await page.goto(BASE + '/workshop', wait_until='domcontentloaded', timeout=90000)
16
+ await page.reload(wait_until='domcontentloaded')
17
+ P('url:', page.url)
18
+ await page.wait_for_selector('input[placeholder="邮箱 / 用户名"]', timeout=60000)
19
+ P('login form ok')
20
+ await page.wait_for_timeout(2500) # 等 Vue 水合(dev SSR 下事件监听滞后)
21
+ await page.fill('input[placeholder="邮箱 / 用户名"]', 'admin')
22
+ await page.locator('.ant-input-password input').first.fill('admin123')
23
+ P('filled, submit via Enter')
24
+ async with page.expect_response(lambda r: '/api/users/login' in r.url, timeout=20000) as ri:
25
+ await page.locator('.ant-input-password input').first.press('Enter')
26
+ resp = await ri.value
27
+ P('login resp:', resp.status)
28
+ P('after login url:', page.url)
29
+ vis = await page.locator('input[placeholder="邮箱 / 用户名"]').is_visible()
30
+ P('login input still visible:', vis)
31
+ msg = await page.locator('.ant-message').inner_text() if await page.locator('.ant-message').count() else ''
32
+ P('toast:', repr(msg[:120]))
33
+ await page.screenshot(path='.e2e-shots/harness-dbg-after-login.png')
34
+
35
+ P('goto / ...')
36
+ await page.goto(BASE + '/', wait_until='domcontentloaded', timeout=90000)
37
+ P('dashboard url:', page.url)
38
+ try:
39
+ await page.wait_for_selector('.harness-row .h-item', timeout=30000)
40
+ P('h-item count:', await page.locator('.harness-row .h-item').count())
41
+ P('off count:', await page.locator('.harness-row .h-item.off').count())
42
+ except Exception as e:
43
+ P('harness panel timeout:', str(e)[:150])
44
+ r = await page.request.get(BASE + '/api/workshop/harnesses')
45
+ P('in-page api status:', r.status, (await r.text())[:160])
46
+ await page.screenshot(path='.e2e-shots/harness-dbg-dashboard.png')
47
+ await browser.close()
48
+
49
+ asyncio.run(main())
@@ -0,0 +1,87 @@
1
+ """Harness 可用性 UI E2E:用户名登录 → 仪表盘 Harness 面板(dsh=未安装灰化) → agents 模板选择禁用 → teams 页截图。
2
+ dsh 未安装态由外部脚本预置;恢复也由外部执行(本脚本不发起 HTTP)。"""
3
+ import asyncio, sys
4
+ from playwright.async_api import async_playwright
5
+
6
+ BASE = 'http://127.0.0.1:3021'
7
+ SHOT = '.e2e-shots'
8
+
9
+ async def main():
10
+ results = []
11
+ def ok(cond, name, extra=''):
12
+ results.append((cond, name))
13
+ print((' ✓ ' if cond else ' ✗ ') + name + (f' {extra}' if extra and not cond else ''), flush=True)
14
+
15
+ async with async_playwright() as p:
16
+ browser = await p.chromium.launch()
17
+ page = await browser.new_page(viewport={'width': 1440, 'height': 900})
18
+
19
+ # ---- 1. 用户名登录(admin/admin123;水合时机在 dev 下有抖动 → 重试 3 轮)----
20
+ await page.goto(BASE + '/workshop', wait_until='domcontentloaded', timeout=90000)
21
+ await page.reload(wait_until='domcontentloaded', timeout=90000)
22
+ await page.wait_for_selector('input[placeholder="邮箱 / 用户名"]', timeout=60000)
23
+ login_status = None
24
+ for attempt in range(3):
25
+ await page.wait_for_timeout(2500 + attempt * 2000) # 等 Vue 水合
26
+ await page.fill('input[placeholder="邮箱 / 用户名"]', 'admin')
27
+ await page.fill('.ant-input-password input', 'admin123')
28
+ try:
29
+ async with page.expect_response(lambda r: '/api/users/login' in r.url, timeout=15000) as ri:
30
+ await page.locator('.ant-input-password input').first.press('Enter')
31
+ login_status = (await ri.value).status
32
+ except Exception:
33
+ login_status = None
34
+ login_input = page.locator('input[placeholder="邮箱 / 用户名"]')
35
+ gone = (await login_input.count()) == 0 or not await login_input.first.is_visible()
36
+ if login_status == 200 and gone:
37
+ break
38
+ ok(login_status == 200, f'登录 POST 200(实际 {login_status})')
39
+ login_input = page.locator('input[placeholder="邮箱 / 用户名"]')
40
+ logged = (await login_input.count()) == 0 or not await login_input.first.is_visible()
41
+ ok(logged, '用户名登录进入系统')
42
+
43
+ # ---- 2. 仪表盘 Harness 面板(dsh 当前为未安装态)----
44
+ await page.goto(BASE + '/', wait_until='domcontentloaded', timeout=90000)
45
+ ok('/workshop' not in page.url, f'仪表盘未跳登录门(url={page.url})')
46
+ await page.wait_for_selector('.harness-row .h-item', timeout=90000)
47
+ await page.wait_for_timeout(1500)
48
+ items = await page.locator('.harness-row .h-item').count()
49
+ ok(items == 6, f'仪表盘 6 引擎条目(实际 {items})')
50
+ off = page.locator('.harness-row .h-item.off')
51
+ off_n = await off.count()
52
+ ok(off_n == 1, f'未安装灰化条目=1(实际 {off_n})')
53
+ if off_n == 1:
54
+ txt = await off.first.inner_text()
55
+ ok('未安装' in txt and 'dsh' in txt, f'灰化条目为 dsh:{txt!r}')
56
+ total = await page.locator('.dp-hd:has-text("执行引擎") .fleet-total').inner_text()
57
+ ok('5/6' in total, f'就绪计数 5/6(实际 {total!r})')
58
+ await page.screenshot(path=f'{SHOT}/harness-1-dashboard-off.png', full_page=False)
59
+
60
+ # ---- 3. agents 模板页:选择器禁用未安装项 ----
61
+ await page.goto(BASE + '/workshop/agents', wait_until='domcontentloaded', timeout=90000)
62
+ await page.click('button:has-text("新建模板")')
63
+ await page.wait_for_selector('.ant-modal .ant-select', timeout=10000)
64
+ await page.click('.ant-modal .ant-select')
65
+ await page.wait_for_selector('.ant-select-dropdown .ant-select-item-option', timeout=10000)
66
+ opts = await page.locator('.ant-select-dropdown .ant-select-item-option').all_inner_texts()
67
+ ok(any('未安装' in o for o in opts), f'下拉含(未安装)标记:{[o for o in opts if "未安装" in o]!r}')
68
+ disabled = page.locator('.ant-select-dropdown .ant-select-item-option-disabled')
69
+ dis_n = await disabled.count()
70
+ ok(dis_n == 1, f'禁用选项=1(实际 {dis_n})')
71
+ if dis_n == 1:
72
+ ok('dsh' in (await disabled.first.inner_text()), '禁用项为 dsh')
73
+ await page.screenshot(path=f'{SHOT}/harness-2-agent-select-disabled.png', full_page=False)
74
+ await page.keyboard.press('Escape')
75
+
76
+ # ---- 4. teams 页:成员选择禁用 dsh 模板(视觉记录)----
77
+ await page.goto(BASE + '/workshop/teams', wait_until='domcontentloaded', timeout=90000)
78
+ await page.wait_for_timeout(1500)
79
+ await page.screenshot(path=f'{SHOT}/harness-3-teams.png', full_page=False)
80
+
81
+ await browser.close()
82
+
83
+ fails = [r for r in results if not r[0]]
84
+ print(f'\nUI RESULT: {len(results) - len(fails)} pass / {len(fails)} fail')
85
+ sys.exit(1 if fails else 0)
86
+
87
+ asyncio.run(main())
@@ -0,0 +1,26 @@
1
+ import puppeteer from 'puppeteer-core'
2
+ const BASE = 'http://127.0.0.1:3001'
3
+ const browser = await puppeteer.launch({ executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', headless: 'new', args: ['--no-sandbox', '--disable-gpu', '--no-proxy-server', '--window-size=1600,1000'] })
4
+ const page = await browser.newPage()
5
+ await page.setViewport({ width: 1600, height: 1000, deviceScaleFactor: 1.25 })
6
+
7
+ // 登出态访问 /workshop → 登录门 → 用「用户名 admin」走账号登录页签
8
+ await page.goto(`${BASE}/workshop`, { waitUntil: 'networkidle2', timeout: 60000 })
9
+ await new Promise(r => setTimeout(r, 2000))
10
+ await page.screenshot({ path: '.e2e-shots/login-1-gate.png' })
11
+ const ph = await page.evaluate(() => [...document.querySelectorAll('input')].map(i => i.placeholder).filter(Boolean).join(' | '))
12
+ console.log('placeholders:', ph)
13
+
14
+ // 用户名登录
15
+ const inputs = await page.$$('.auth-card input')
16
+ const emailInput = await page.$('.auth-card input[type="email"]') ?? (await page.$$('.auth-card input'))[0]
17
+ await emailInput.click({ clickCount: 3 })
18
+ await emailInput.type('admin')
19
+ const pw = await page.$('.auth-card input[type="password"]')
20
+ await pw.click({ clickCount: 3 })
21
+ await pw.type('admin123')
22
+ await page.keyboard.press('Enter')
23
+ await page.waitForFunction(() => document.body.innerText.includes('Workshop'), { timeout: 30000 })
24
+ console.log('✔ 用户名 admin 登录成功,已进入项目')
25
+ await page.screenshot({ path: '.e2e-shots/login-2-username-ok.png' })
26
+ await browser.close()
@@ -1,12 +1,25 @@
1
1
  /**
2
- * GET /api/workshop/harnesses —— 可用执行引擎注册表(前端下拉/能力徽标)。
2
+ * GET /api/workshop/harnesses —— 可用执行引擎注册表(前端下拉/能力徽标/可用性)。
3
3
  * 数据源 HARNESS_REGISTRY(单一事实源;与 factory/manager 校验同源)。
4
+ * available = 环境探测:进程内引擎恒可用;进程型引擎按 PATH 探测外部 CLI。
5
+ * ?refresh=1 跳过 30s 探测缓存强制重探(装包/改命令后立即见效)。
4
6
  */
7
+ import { getQuery } from 'h3'
5
8
  import { resolveUser } from '@/server/api/workshop/caller'
6
9
  import { defineApiHandler } from '@/server/utils/response'
10
+ import { checkAllHarnessAvailability } from '@/server/services/workshop/agents/harness-availability'
7
11
  import { harnessMetas } from '@/server/services/workshop/agents/registry'
8
12
 
9
13
  export default defineApiHandler(async (event) => {
10
14
  resolveUser(event)
11
- return { harnesses: harnessMetas() }
15
+ const q = getQuery(event)
16
+ const refresh = q.refresh === '1' || q.refresh === 'true'
17
+ const avail = checkAllHarnessAvailability({ refresh })
18
+ const byId = new Map(avail.map(a => [a.id, a]))
19
+ return {
20
+ harnesses: harnessMetas().map(m => ({
21
+ ...m,
22
+ ...(byId.get(m.id) ?? { available: false, inprocess: false, command: null, resolvedPath: null, error: '探测失败' }),
23
+ })),
24
+ }
12
25
  })
@@ -1391,5 +1391,19 @@
1391
1391
  "color": "#3aa0ff",
1392
1392
  "description": "",
1393
1393
  "createdAt": "2026-09-06T02:24:50.518+08:00"
1394
+ },
1395
+ {
1396
+ "id": "ln-8a32e91f",
1397
+ "name": "模拟器产线-mtpp41np",
1398
+ "color": "#f4c542",
1399
+ "description": "",
1400
+ "createdAt": "2026-09-06T18:55:51.506+08:00"
1401
+ },
1402
+ {
1403
+ "id": "ln-d8e727b7",
1404
+ "name": "admin���Բ���",
1405
+ "color": "#35e0a0",
1406
+ "description": "",
1407
+ "createdAt": "2026-09-06T18:56:52.853+08:00"
1394
1408
  }
1395
1409
  ]