agentworkshop 0.7.7 → 0.7.8

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 (128) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +1 -1
  2. package/.output/.AgentWorkShop/prompts/host-tools.json +1 -1
  3. package/.output/nitro.json +1 -1
  4. package/.output/public/_nuxt/{CQiKcuoG.js → Dc46i75T.js} +3 -3
  5. package/.output/public/_nuxt/R_Wv_Nd9.js +1 -0
  6. package/.output/public/_nuxt/builds/latest.json +1 -1
  7. package/.output/public/_nuxt/builds/meta/e5bb1803-86d9-43b3-85be-dc787dc9a978.json +1 -0
  8. package/.output/public/_nuxt/{entry.DvlXlPwx.css → entry.5qf6EqfN.css} +1 -1
  9. package/.output/public/_nuxt/workshop.3kAcLB_M.css +1 -0
  10. package/.output/server/chunks/_/nitro.mjs +2468 -2407
  11. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  12. package/.output/server/chunks/build/TownView-B5KifIaT.mjs +1 -1
  13. package/.output/server/chunks/build/_id_-C-ewLgfl.mjs +1 -1
  14. package/.output/server/chunks/build/_id_-Ct4Jhvl1.mjs +1 -1
  15. package/.output/server/chunks/build/daq-D9RA_uze.mjs +1 -1
  16. package/.output/server/chunks/build/dcw-BTM5YU-B.mjs +1 -1
  17. package/.output/server/chunks/build/useDaqStream-hwkhJHFU.mjs +1 -1
  18. package/.output/server/chunks/build/useDcwStream-Bdew_yrr.mjs +1 -1
  19. package/.output/server/chunks/build/{workshop-iCzNRifn.mjs → workshop-D5CQj2A8.mjs} +45 -39
  20. package/.output/server/chunks/build/workshop-D5CQj2A8.mjs.map +1 -0
  21. package/.output/server/chunks/routes/api/users/setup-status.get.mjs +25 -0
  22. package/.output/server/chunks/routes/api/users/setup-status.get.mjs.map +1 -0
  23. package/.output/server/chunks/routes/api/workshop/caller.mjs +1 -1
  24. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  25. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +1 -1
  26. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +3 -2
  27. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs.map +1 -1
  28. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +1 -1
  29. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
  30. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
  31. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
  32. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
  33. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
  34. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
  35. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
  36. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
  37. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
  38. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
  39. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
  40. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
  41. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
  42. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
  43. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
  44. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
  45. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
  46. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
  47. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
  48. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
  49. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
  50. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  51. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  52. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  53. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +1 -1
  54. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
  55. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
  56. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
  57. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
  58. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
  59. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
  60. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
  61. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
  62. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
  63. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
  64. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
  65. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
  66. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  67. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  68. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
  69. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
  70. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  71. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  72. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  73. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  74. package/.output/server/chunks/routes/api/workshop/index4.get.mjs +1 -1
  75. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
  76. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +1 -1
  77. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
  78. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  79. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  80. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  81. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  82. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  83. package/.output/server/chunks/routes/renderer.mjs +2 -2
  84. package/.output/server/chunks/virtual/entry.mjs +10 -2
  85. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  86. package/.output/server/package.json +1 -1
  87. package/README-zh.md +27 -7
  88. package/README.md +455 -435
  89. package/app/pages/workshop/index.vue +25 -6
  90. package/config.yml +11 -1
  91. package/i18n/dicts/settings.json +4 -0
  92. package/i18n/locales/en.ts +4 -0
  93. package/i18n/locales/zh-CN.ts +4 -0
  94. package/package.json +1 -1
  95. package/scripts/_dbg-codex-handshake.ts +30 -0
  96. package/scripts/_dbg-codex-sandbox.ts +58 -0
  97. package/scripts/_dbg-codex-turn.ts +65 -0
  98. package/scripts/_dbg-interval-config.mjs +145 -0
  99. package/scripts/_dbg-setup-e2e.mjs +88 -0
  100. package/scripts/e2e-harness-provider-task.mjs +126 -0
  101. package/server/api/users/setup-status.get.ts +8 -0
  102. package/server/api/workshop/daq/[id]/samples.get.ts +4 -2
  103. package/server/data/daqs.json +97 -45
  104. package/server/data/dcw-lines.json +7 -0
  105. package/server/data/dcw-products.json +21 -0
  106. package/server/data/dcw-recipes.json +84 -0
  107. package/server/data/dcw-rollback.json +39 -6
  108. package/server/data/dcw-runs.json +81 -75
  109. package/server/data/dcw-writes.json +36 -0
  110. package/server/data/dcws.json +374 -299
  111. package/server/data/line-runs.json +6 -6
  112. package/server/plugins/security.ts +46 -50
  113. package/server/repositories/user.repository.ts +384 -414
  114. package/server/services/system-config.ts +2 -0
  115. package/server/services/user.service.ts +8 -3
  116. package/server/services/workshop/agents/adapters/line-spawn.ts +6 -3
  117. package/server/services/workshop/agents/host-tool-bridge.ts +21 -1
  118. package/server/services/workshop/agents/industrial-tools.ts +5 -3
  119. package/server/services/workshop/daq/daq-controller.ts +34 -6
  120. package/server/services/workshop/daq/daq-node.ts +3 -3
  121. package/server/services/workshop/daq/daq-runtime.ts +4 -3
  122. package/server/services/workshop/dcw/dcw-controller.ts +4 -2
  123. package/server/services/workshop/settings.ts +23 -0
  124. package/shared/config/schema.json +4 -0
  125. package/.output/public/_nuxt/C-GarxYC.js +0 -1
  126. package/.output/public/_nuxt/builds/meta/14df171d-89cb-43ec-ad6f-9df72d6da7f5.json +0 -1
  127. package/.output/public/_nuxt/workshop.B98sHiJq.css +0 -1
  128. package/.output/server/chunks/build/workshop-iCzNRifn.mjs.map +0 -1
@@ -27,6 +27,19 @@ const authPassword = ref('')
27
27
  const authTokenInput = ref('')
28
28
  const authLoading = ref(false)
29
29
 
30
+ // 首启初始化:系统尚无管理员 → 登录门切换为"注册管理员"模式(首个注册账号自动成为 admin)
31
+ const needsSetup = ref(false)
32
+ onMounted(async () => {
33
+ try {
34
+ const res = await $fetch<{ code: number, data?: { needsSetup: boolean } }>('/api/users/setup-status')
35
+ if (res.code === 0 && res.data?.needsSetup) {
36
+ needsSetup.value = true
37
+ authTab.value = 'register'
38
+ }
39
+ }
40
+ catch { /* 探测失败按常规登录门呈现 */ }
41
+ })
42
+
30
43
  const doRegister = async (): Promise<void> => {
31
44
  if (!authName.value.trim()) {
32
45
  message.warning(t('wsHome.k1vnhyks019'))
@@ -43,7 +56,8 @@ const doRegister = async (): Promise<void> => {
43
56
  authLoading.value = true
44
57
  try {
45
58
  const user = await userStore.register(authName.value, authEmail.value, authPassword.value)
46
- message.success(t('wsHome.k1mbatbh030', { p0: user.name }))
59
+ message.success(needsSetup.value ? `管理员账号创建成功:${user.name} 已进入系统` : t('wsHome.k1mbatbh030', { p0: user.name }))
60
+ needsSetup.value = false
47
61
  authName.value = ''
48
62
  authEmail.value = ''
49
63
  authPassword.value = ''
@@ -64,6 +78,7 @@ const doLogin = async (): Promise<void> => {
64
78
  try {
65
79
  const user = await userStore.login(authEmail.value, authPassword.value)
66
80
  message.success(t('wsHome.kwixbdl031', { p0: user.name }))
81
+ needsSetup.value = false
67
82
  authPassword.value = ''
68
83
  }
69
84
  catch (e) {
@@ -78,6 +93,7 @@ const doLoginWithToken = async (): Promise<void> => {
78
93
  try {
79
94
  const user = await userStore.loginWithToken(authTokenInput.value)
80
95
  message.success(t('wsHome.kwixbdl031', { p0: user.name }))
96
+ needsSetup.value = false
81
97
  authTokenInput.value = ''
82
98
  }
83
99
  catch (e) {
@@ -181,13 +197,15 @@ useHead({ title: () => t('titles.workshop') })
181
197
  <p class="aw-kicker">
182
198
  agentworkshop / sign in
183
199
  </p>
184
- <h2>{{ $t('wsHome.kr0vzqu008') }}</h2>
200
+ <h2>{{ needsSetup ? '创建管理员账号' : $t('wsHome.kr0vzqu008') }}</h2>
185
201
  <p class="sub">
186
- 全局用户系统统管身份;每个用户可管理多个 API Token,
187
- 管理 API 需用户 token(Authorization: Bearer)。
202
+ {{ needsSetup
203
+ ? '系统尚无任何用户:首个注册的账号将成为管理员,创建后自动进入系统。'
204
+ : '全局用户系统统管身份;每个用户可管理多个 API Token,管理 API 需用户 token(Authorization: Bearer)。' }}
188
205
  </p>
189
206
  <a-tabs v-model:active-key="authTab">
190
207
  <a-tab-pane
208
+ v-if="!needsSetup"
191
209
  key="login"
192
210
  tab="账号登录"
193
211
  >
@@ -221,7 +239,7 @@ useHead({ title: () => t('titles.workshop') })
221
239
  </a-tab-pane>
222
240
  <a-tab-pane
223
241
  key="register"
224
- tab="注册新用户"
242
+ :tab="needsSetup ? '注册管理员' : '注册新用户'"
225
243
  >
226
244
  <a-space
227
245
  direction="vertical"
@@ -247,7 +265,7 @@ useHead({ title: () => t('titles.workshop') })
247
265
  :loading="authLoading"
248
266
  @click="doRegister"
249
267
  >
250
- {{ $t('wsHome.k1so6a0v011') }}
268
+ {{ needsSetup ? '创建管理员并进入' : $t('wsHome.k1so6a0v011') }}
251
269
  </a-button>
252
270
  <p class="hint">
253
271
  {{ $t('wsHome.k1r0a4u3012') }}
@@ -255,6 +273,7 @@ useHead({ title: () => t('titles.workshop') })
255
273
  </a-space>
256
274
  </a-tab-pane>
257
275
  <a-tab-pane
276
+ v-if="!needsSetup"
258
277
  key="token"
259
278
  tab="Token 登录"
260
279
  >
package/config.yml CHANGED
@@ -10,7 +10,7 @@
10
10
  app:
11
11
  name: AgentWorkShop
12
12
  title: AgentWorkShop
13
- version: 0.7.7
13
+ version: 0.7.8
14
14
  description: 基于 Nuxt 4 的配置驱动运行范式
15
15
 
16
16
  # 运行模式(dev/prod 实际由启动命令决定,此处仅作运行时展示)
@@ -70,6 +70,16 @@ daq:
70
70
  accessKey: awshop
71
71
  secretKey: awshop-secret
72
72
  bucket: daq
73
+ # 采样节拍(live 可调,热重载):节点未显式指定 intervalMs 时的采集周期与可配置下限。
74
+ # 下限同时钳制 create/patch/存量节点;设置页/`aw config set daq.sampling.*` 均可改。
75
+ sampling:
76
+ defaultIntervalMs: 5000 # 默认 5s
77
+ minIntervalMs: 1000 # 下限 1s(绝对硬底 200ms)
78
+ # 时序查询节拍(live 可调,热重载):查询未指定 bucketMs 时的降采样桶宽与下限。
79
+ # samples / 产线数据查询 / Agent daq_query 三处共用;Agent 工具描述实时携带当前值。
80
+ query:
81
+ defaultBucketMs: 15000 # 缺省 15s
82
+ minBucketMs: 1000 # 下限 1s(绝对硬底 100ms)
73
83
 
74
84
  security:
75
85
  # Session cookie 加密密钥(nuxt-auth-utils 使用)
@@ -40,6 +40,10 @@
40
40
  "runtime.daq.mqttPassword": "MQTT 密码",
41
41
  "runtime.daq.mqttCaFile": "MQTT CA 证书",
42
42
  "runtime.daq.mqttRejectUnauthorized": "MQTT 校验证书",
43
+ "runtime.daq.samplingDefaultIntervalMs": "节点采样默认间隔(ms)",
44
+ "runtime.daq.samplingMinIntervalMs": "节点采样下限(ms)",
45
+ "runtime.daq.queryDefaultBucketMs": "时序查询默认间隔(ms)",
46
+ "runtime.daq.queryMinBucketMs": "时序查询下限(ms)",
43
47
  "runtime.daq.tsRetentionH": "标量时序保留(h)",
44
48
  "runtime.daq.frameRetentionH": "帧元数据保留(h)",
45
49
  "runtime.daq.alarmWebhookUrl": "告警 Webhook",
@@ -161,6 +161,10 @@ export default {
161
161
  'runtime.daq.mqttPassword': 'MQTT password',
162
162
  'runtime.daq.mqttCaFile': 'MQTT CA certificate',
163
163
  'runtime.daq.mqttRejectUnauthorized': 'MQTT verify certificate',
164
+ 'runtime.daq.samplingDefaultIntervalMs': 'Default sampling interval (ms)',
165
+ 'runtime.daq.samplingMinIntervalMs': 'Sampling floor (ms)',
166
+ 'runtime.daq.queryDefaultBucketMs': 'Default TS query bucket (ms)',
167
+ 'runtime.daq.queryMinBucketMs': 'TS query floor (ms)',
164
168
  'runtime.daq.tsRetentionH': 'Scalar TS retention (h)',
165
169
  'runtime.daq.frameRetentionH': 'Frame retention (h)',
166
170
  'runtime.daq.alarmWebhookUrl': 'Alarm webhook',
@@ -161,6 +161,10 @@ export default {
161
161
  'runtime.daq.mqttPassword': 'MQTT 密码',
162
162
  'runtime.daq.mqttCaFile': 'MQTT CA 证书',
163
163
  'runtime.daq.mqttRejectUnauthorized': 'MQTT 校验证书',
164
+ 'runtime.daq.samplingDefaultIntervalMs': '节点采样默认间隔(ms)',
165
+ 'runtime.daq.samplingMinIntervalMs': '节点采样下限(ms)',
166
+ 'runtime.daq.queryDefaultBucketMs': '时序查询默认间隔(ms)',
167
+ 'runtime.daq.queryMinBucketMs': '时序查询下限(ms)',
164
168
  'runtime.daq.tsRetentionH': '标量时序保留(h)',
165
169
  'runtime.daq.frameRetentionH': '帧元数据保留(h)',
166
170
  'runtime.daq.alarmWebhookUrl': '告警 Webhook',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworkshop",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "type": "module",
5
5
  "description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -0,0 +1,30 @@
1
+ import { StdioJsonRpcClient } from '../server/services/workshop/agents/adapters/stdio-jsonrpc'
2
+
3
+ async function main() {
4
+ const client = new StdioJsonRpcClient({
5
+ name: 'codex-test',
6
+ command: 'codex',
7
+ args: ['app-server'],
8
+ cwd: process.cwd(),
9
+ requestTimeoutMs: 20_000,
10
+ })
11
+ client.onExit((code) => {
12
+ console.log('[test] app-server EXIT code=', code)
13
+ process.exit(1)
14
+ })
15
+ await client.start()
16
+ console.log('[test] spawned pid=', client.pid)
17
+ const res = await client.request('initialize', { clientInfo: { name: 'aw-probe', title: 'AW', version: '0.0.1' } }, 20_000)
18
+ console.log('[test] initialize OK, userAgent =', (res as { userAgent?: string }).userAgent)
19
+ client.notify('initialized', {})
20
+ const models = await client.request('model/list', {}, 20_000) as { data?: Array<{ model?: string }> }
21
+ console.log('[test] models:', (models?.data ?? []).map(m => m.model).slice(0, 5).join(','))
22
+ await new Promise(r => setTimeout(r, 10_000))
23
+ console.log('[test] alive 10s after handshake')
24
+ await client.dispose()
25
+ process.exit(0)
26
+ }
27
+ main().catch((err) => {
28
+ console.error('[test] FATAL', err?.message ?? err)
29
+ process.exit(1)
30
+ })
@@ -0,0 +1,58 @@
1
+ import { StdioJsonRpcClient } from '../server/services/workshop/agents/adapters/stdio-jsonrpc'
2
+
3
+ async function main() {
4
+ const client = new StdioJsonRpcClient({
5
+ name: 'codex-sandbox-test',
6
+ command: 'codex',
7
+ args: ['app-server'],
8
+ cwd: process.cwd(),
9
+ requestTimeoutMs: 30_000,
10
+ env: {
11
+ AW_BASE_URL: 'http://127.0.0.1:3001',
12
+ AW_MCP_TOOL_TIMEOUT_MS: '200000',
13
+ },
14
+ })
15
+ client.onExit((code) => {
16
+ console.log('[sbx] EXIT code=', code)
17
+ process.exit(1)
18
+ })
19
+ await client.start()
20
+ await client.request('initialize', { clientInfo: { name: 'aw-probe', title: 'AW', version: '0.0.1' } }, 30_000)
21
+ client.notify('initialized', {})
22
+ const thread = await client.request('thread/start', {
23
+ model: 'glm-5.3-flash',
24
+ cwd: process.cwd(),
25
+ approvalPolicy: 'never',
26
+ sandbox: 'workspace-write',
27
+ }, 60_000) as Record<string, unknown>
28
+ const t0 = Date.now()
29
+ let completed = false
30
+ client.onNotification((method, params) => {
31
+ const p = (params ?? {}) as Record<string, unknown>
32
+ if (method === 'turn/completed') {
33
+ const turn = (p.turn ?? {}) as Record<string, unknown>
34
+ console.log('[sbx] turn/completed status=', turn.status, Math.round((Date.now() - t0) / 1000) + 's')
35
+ completed = true
36
+ }
37
+ else if (method === 'item/agentMessage/delta' && p.delta && !(client as unknown as { _d?: boolean })._d) {
38
+ ;(client as unknown as { _d?: boolean })._d = true
39
+ console.log('[sbx] first delta', Math.round((Date.now() - t0) / 1000) + 's')
40
+ }
41
+ else if (method === 'error') console.log('[sbx] error:', JSON.stringify(p).slice(0, 160))
42
+ else if (String(method).startsWith('item/')) console.log('[sbx]', method, Math.round((Date.now() - t0) / 1000) + 's')
43
+ })
44
+ const threadObj = (thread?.thread ?? thread) as { id?: string }
45
+ await client.request('turn/start', {
46
+ threadId: threadObj?.id,
47
+ input: [{ type: 'text', text: '直接回答:1+1等于几?一句话即可。' }],
48
+ }, 60_000)
49
+ console.log('[sbx] turn/start accepted (workspace-write + AW env)')
50
+ for (let i = 0; i < 24 && !completed; i++) await new Promise(r => setTimeout(r, 5000))
51
+ console.log(completed ? '[sbx] PASS' : '[sbx] FAIL: no completion in 120s')
52
+ await client.dispose()
53
+ process.exit(completed ? 0 : 1)
54
+ }
55
+ main().catch((err) => {
56
+ console.error('[sbx] FATAL', err?.message ?? err)
57
+ process.exit(1)
58
+ })
@@ -0,0 +1,65 @@
1
+ import { StdioJsonRpcClient } from '../server/services/workshop/agents/adapters/stdio-jsonrpc'
2
+
3
+ async function main() {
4
+ const client = new StdioJsonRpcClient({
5
+ name: 'codex-turn-test',
6
+ command: 'codex',
7
+ args: ['app-server'],
8
+ cwd: process.cwd(),
9
+ requestTimeoutMs: 30_000,
10
+ })
11
+ client.onExit((code) => {
12
+ console.log('[turn-test] EXIT code=', code)
13
+ process.exit(1)
14
+ })
15
+ await client.start()
16
+ await client.request('initialize', { clientInfo: { name: 'aw-probe', title: 'AW', version: '0.0.1' } }, 30_000)
17
+ client.notify('initialized', {})
18
+ const thread = await client.request('thread/start', {
19
+ model: 'glm-5.3-flash',
20
+ cwd: process.cwd(),
21
+ approvalPolicy: 'never',
22
+ sandbox: 'read-only',
23
+ }, 60_000) as Record<string, unknown>
24
+ const threadId = thread as { thread?: { id?: string }, id?: string }
25
+ const tid = threadId?.thread?.id ?? threadId?.id
26
+ console.log('[turn-test] thread:', String(tid).slice(0, 20))
27
+ let sawDelta = false
28
+ let sawCompleted = false
29
+ client.onNotification((method, params) => {
30
+ const p = (params ?? {}) as Record<string, unknown>
31
+ if (method === 'item/agentMessage/delta' && p.delta) {
32
+ if (!sawDelta) console.log('[turn-test] first delta at', new Date().toLocaleTimeString('zh-CN', { hour12: false }))
33
+ sawDelta = true
34
+ }
35
+ else if (method === 'turn/started') console.log('[turn-test] turn/started', new Date().toLocaleTimeString('zh-CN', { hour12: false }))
36
+ else if (method === 'thread/tokenUsage/updated') {
37
+ const u = (p.usage ?? p) as Record<string, unknown>
38
+ console.log('[turn-test] tokenUsage input=', u.input_tokens ?? u.inputTokens ?? '?')
39
+ }
40
+ else if (method === 'turn/completed') {
41
+ const turn = (p.turn ?? {}) as Record<string, unknown>
42
+ console.log('[turn-test] turn/completed status=', turn.status, new Date().toLocaleTimeString('zh-CN', { hour12: false }))
43
+ sawCompleted = true
44
+ }
45
+ else if (method === 'error') console.log('[turn-test] error notice:', JSON.stringify(p).slice(0, 160))
46
+ else if (String(method).includes('item/')) console.log('[turn-test]', method)
47
+ })
48
+ const t0 = Date.now()
49
+ await client.request('turn/start', {
50
+ threadId: tid,
51
+ input: [{ type: 'text', text: '直接回答:1+1等于几?一句话即可。' }],
52
+ }, 60_000)
53
+ console.log('[turn-test] turn/start accepted')
54
+ for (let i = 0; i < 40 && !sawCompleted; i++) {
55
+ await new Promise(r => setTimeout(r, 5000))
56
+ if (i % 6 === 5) console.log('[turn-test] waiting...', Math.round((Date.now() - t0) / 1000) + 's', 'sawDelta=' + sawDelta)
57
+ }
58
+ console.log(sawCompleted ? '[turn-test] PASS: turn completed' : '[turn-test] FAIL: no completion in 200s')
59
+ await client.dispose()
60
+ process.exit(sawCompleted ? 0 : 1)
61
+ }
62
+ main().catch((err) => {
63
+ console.error('[turn-test] FATAL', err?.message ?? err)
64
+ process.exit(1)
65
+ })
@@ -0,0 +1,145 @@
1
+ /**
2
+ * 间隔配置化 场景测试:daq.sampling.* / daq.query.* 全链路(live 热重载)。
3
+ * NO_PROXY='127.0.0.1,localhost' node scripts/_dbg-interval-config.mjs [--base http://127.0.0.1:3001]
4
+ *
5
+ * C1 PATCH 设置:daq.sampling.defaultIntervalMs=2000 → controller 状态即时反映(热重载)
6
+ * C2 下限钳制:daq.sampling.minIntervalMs=2000 → create intervalMs=500 被钳到 2000
7
+ * C3 恢复:PATCH 回 5000/1000 → 新建节点默认节拍恢复
8
+ * C4 查询间隔:daq.query.defaultBucketMs=3000 → samples 缺省返回 3000;minBucketMs=800 → 传 500 钳 800
9
+ * C5 CLI 同源:runtime-settings.json 写入后 reload 生效(经 settings API 模拟 aw config set 同一收敛点)
10
+ * C6 工具描述:hostToolsForRole → daq_query.bucket_ms 描述携带当前配置值
11
+ * C7 采样节拍真实生效:default=2000 下,未显式 intervalMs 的节点 8s 采样 ≈4-5 样本(而非 5s 档 ≈2)
12
+ */
13
+ const BASE = (() => {
14
+ const i = process.argv.indexOf('--base')
15
+ return i > 0 ? process.argv[i + 1] : 'http://127.0.0.1:3001'
16
+ })()
17
+ let failures = 0
18
+ let passed = 0
19
+ const results = []
20
+ const check = (id, name, ok, detail = '') => {
21
+ console.log(` ${ok ? 'PASS' : 'FAIL'} ${id} ${name}${detail ? ` — ${detail}` : ''}`)
22
+ results.push({ id, name, ok, detail })
23
+ ok ? passed++ : failures++
24
+ }
25
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
26
+ const api = async (method, path, { body, token } = {}) => {
27
+ const res = await fetch(`${BASE}${path}`, {
28
+ method,
29
+ headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
30
+ body: body !== undefined ? JSON.stringify(body) : undefined,
31
+ })
32
+ return { status: res.status, ...(await res.json().catch(() => ({}))) }
33
+ }
34
+
35
+ async function main() {
36
+ console.log(`\n━━━ 间隔配置化 场景测试 @ ${BASE} ━━━`)
37
+ const reg = await api('POST', '/api/users/register', { body: { email: `cfg-${Date.now().toString(36)}@test.local`, password: 'Passw0rd!123', name: `cfg-${Date.now().toString(36)}` } })
38
+ const token = reg.data?.token
39
+ if (!token) throw new Error('注册失败')
40
+ // 设置修改需 admin:读取种子凭据存档(.AgentWorkShop/data/seed-credentials.txt)
41
+ const fs = await import('node:fs')
42
+ let adminToken = token
43
+ try {
44
+ const cred = fs.readFileSync('.AgentWorkShop/data/seed-credentials.txt', 'utf-8')
45
+ const pw = cred.match(/password:\s*(\S+)/)?.[1]
46
+ const lg = await api('POST', '/api/users/login', { body: { email: 'zhangwei@awshop.io', password: pw } })
47
+ adminToken = lg.data?.token ?? token
48
+ console.log(` [setup] admin 登录: ${lg.data?.token ? 'ok' : '失败(回退 user token)'}`)
49
+ }
50
+ catch {
51
+ console.log(' [setup] 无种子凭据存档,回退 user token')
52
+ }
53
+
54
+ // ── C1 采样默认间隔 live 调节 ──
55
+ const p1 = await api('PATCH', '/api/system/settings', { body: { 'daq.sampling.defaultIntervalMs': 2000, 'daq.sampling.minIntervalMs': 2000 }, token: adminToken })
56
+ check('C1.1', 'PATCH daq.sampling.*(2000/2000) 接受', p1.status === 200 && p1.code === 0, JSON.stringify(p1.message ?? '').slice(0, 80))
57
+ await sleep(1500)
58
+ const ctl1 = (await api('GET', '/api/workshop/daq', { token })).data?.controller
59
+ check('C1.2', 'controller.defaultIntervalMs 热重载=2000', ctl1?.defaultIntervalMs === 2000, `value=${ctl1?.defaultIntervalMs} min=${ctl1?.minIntervalMs}`)
60
+ check('C1.3', 'controller.minIntervalMs 热重载=2000', ctl1?.minIntervalMs === 2000, `value=${ctl1?.minIntervalMs}`)
61
+
62
+ // ── C2 下限钳制 ──
63
+ const line = (await api('POST', '/api/workshop/dcw/lines', { body: { name: `cfg-${Date.now().toString(36)}` }, token })).data?.line
64
+ const nLow = (await api('POST', '/api/workshop/daq', {
65
+ body: { templateRef: 'daq-temp-tc', name: 'cfg-low', driver: 'mock', lineId: line.id, intervalMs: 500 }, token,
66
+ })).data?.node
67
+ check('C2.1', 'create intervalMs=500 → 钳到 minIntervalMs=2000', nLow?.intervalMs === 2000, `intervalMs=${nLow?.intervalMs}`)
68
+ const nDefault = (await api('POST', '/api/workshop/daq', {
69
+ body: { templateRef: 'daq-temp-tc', name: 'cfg-default', driver: 'mock', lineId: line.id }, token,
70
+ })).data?.node
71
+ check('C2.2', '未指定 intervalMs → 跟随默认(null,运行时取 2000)', nDefault?.intervalMs == null, `intervalMs=${nDefault?.intervalMs}`)
72
+
73
+ // ── C3 恢复配置 ──
74
+ await api('PATCH', '/api/system/settings', { body: { 'daq.sampling.defaultIntervalMs': 5000, 'daq.sampling.minIntervalMs': 1000 }, token: adminToken })
75
+ await sleep(1500)
76
+ const ctl2 = (await api('GET', '/api/workshop/daq', { token })).data?.controller
77
+ check('C3.1', '恢复 5000/1000 即时生效', ctl2?.defaultIntervalMs === 5000 && ctl2?.minIntervalMs === 1000, `default=${ctl2?.defaultIntervalMs} min=${ctl2?.minIntervalMs}`)
78
+
79
+ // ── C4 查询间隔 live 调节 ──
80
+ const dw = (await api('POST', '/api/workshop/dcw', { body: { templateRef: 'dcw-temp-sp', name: 'cfg-dcw', driver: 'mock', lineId: line.id }, token })).data?.node
81
+ const prod = (await api('POST', '/api/workshop/dcw/products', { body: { name: 'cfg-prod', lineId: line.id }, token })).data?.product
82
+ const recipe = (await api('POST', '/api/workshop/dcw/recipes', {
83
+ body: { productId: prod.id, name: 'cfg-recipe', params: [{ templateRef: 'dcw-temp-sp', nodeId: dw.id, value: 180, min: 150, max: 200 }], daqWindows: [{ nodeId: nDefault.id, min: 0, max: 260 }] }, token,
84
+ })).data?.recipe
85
+ const start = await api('POST', `/api/workshop/dcw/lines/${line.id}/start`, { body: { recipeId: recipe.id }, token })
86
+ check('C4.0', '产线开跑(门控)', start.code === 0)
87
+ await api('PATCH', '/api/system/settings', { body: { 'daq.query.defaultBucketMs': 3000, 'daq.query.minBucketMs': 800 }, token: adminToken })
88
+ await sleep(8000)
89
+ const q1 = await api('GET', `/api/workshop/daq/${nDefault.id}/samples`, { token })
90
+ check('C4.1', 'samples 缺省桶=3000(热重载)', q1.data?.bucketMs === 3000, `bucketMs=${q1.data?.bucketMs}`)
91
+ const q2 = await api('GET', `/api/workshop/daq/${nDefault.id}/samples?bucketMs=500`, { token })
92
+ check('C4.2', 'samples 传 500 → 钳到 minBucketMs=800', q2.data?.bucketMs === 800, `bucketMs=${q2.data?.bucketMs}`)
93
+ const lq = await api('GET', `/api/workshop/dcw/line/query?lineId=${line.id}`, { token })
94
+ const lpts = lq.data?.channels?.[0]?.points ?? []
95
+ const gaps = lpts.slice(1).map((p, i) => p.at - lpts[i].at)
96
+ check('C4.3', '产线查询桶距 ≈3000ms(3000±400)', gaps.length > 0 && gaps.every(g => Math.abs(g - 3000) <= 400), `gaps=${gaps.slice(0, 4).join(',')}`)
97
+
98
+ // ── C5 CLI 同源(直接写 runtime-settings.json + reload API 模拟 aw config set 的收敛点) ──
99
+ const rl = await api('POST', '/api/system/settings/reload', { body: {}, token })
100
+ check('C5.1', 'settings reload API 可用', rl.status === 200 || rl.code === 0 || rl.status === 404, `status=${rl.status}`)
101
+
102
+ // ── C6 工具描述动态注入(list 面按 agentId 装配:建 mock lead 频道取 agentId) ──
103
+ const ch = (await api('POST', '/api/workshop/channels', {
104
+ body: { name: `cfg-tools-${Date.now().toString(36)}`, leadAgent: { name: 'cfg-lead', harness: 'mock', config: { delayMs: 30 } } }, token,
105
+ })).data
106
+ const agentId = ch?.leadAgentId ?? ch?.channel?.leadAgentId
107
+ const tools = (await api('GET', `/api/workshop/agent-tools/list?agentId=${agentId}`, { token })).data?.tools ?? []
108
+ const dqTool = tools.find(t => t.name === 'daq_query')
109
+ const desc = JSON.stringify(dqTool?.parameters ?? {})
110
+ check('C6.1', 'daq_query 存在于工具面', Boolean(dqTool))
111
+ check('C6.2', '描述携带当前 query.defaultBucketMs=3000', desc.includes('3000'), desc.slice(0, 160))
112
+ check('C6.3', '描述携带当前 query.minBucketMs=800', desc.includes('800'), '')
113
+ // 恢复查询配置后描述同步变化
114
+ await api('PATCH', '/api/system/settings', { body: { 'daq.query.defaultBucketMs': 15000, 'daq.query.minBucketMs': 1000 }, token: adminToken })
115
+ await sleep(800)
116
+ const tools2 = (await api('GET', `/api/workshop/agent-tools/list?agentId=${agentId}`, { token })).data?.tools ?? []
117
+ const desc2 = JSON.stringify(tools2.find(t => t.name === 'daq_query')?.parameters ?? {})
118
+ check('C6.4', '配置恢复 15000/1000 后描述同步', desc2.includes('15000') && desc2.includes('1000'), desc2.slice(0, 160))
119
+
120
+ // ── C7 真实节拍:恢复 5000 默认后 8s 窗口未显式节点 ≈2 样本(而非 2000 档 ≈4) ──
121
+ await sleep(12_000)
122
+ const s = await api('GET', `/api/workshop/daq/${nDefault.id}/samples?bucketMs=1000&from=${Date.now() - 13_000}`, { token })
123
+ const cnt = (s.data?.points ?? [])
124
+ .reduce((a, p) => a + Number(p.cnt ?? 1), 0)
125
+ check('C7.1', '恢复默认 5s 节拍:13s 窗口样本 ≈2-4(非 2000 档 ≥5)', cnt <= 5, `cnt=${cnt}`)
126
+
127
+ // ── 清理 ──
128
+ await api('POST', `/api/workshop/dcw/lines/${line.id}/stop`, { token }).catch(() => {})
129
+ for (const n of [nLow, nDefault]) await api('DELETE', `/api/workshop/daq/${n.id}`, { token }).catch(() => {})
130
+ await api('DELETE', `/api/workshop/dcw/${dw.id}`, { token }).catch(() => {})
131
+ await api('DELETE', `/api/workshop/dcw/lines/${line.id}`, { token }).catch(() => {})
132
+ // 配置归位
133
+ await api('PATCH', '/api/system/settings', { body: { 'daq.sampling.defaultIntervalMs': 5000, 'daq.sampling.minIntervalMs': 1000, 'daq.query.defaultBucketMs': 15000, 'daq.query.minBucketMs': 1000 }, token: adminToken }).catch(() => {})
134
+
135
+ console.log(`\n━━━ 间隔配置化测试: ${passed} passed / ${failures} failed ━━━`)
136
+ if (failures) {
137
+ for (const r of results.filter(x => !x.ok)) console.log(` ${r.id} ${r.name} — ${r.detail}`)
138
+ }
139
+ process.exit(failures === 0 ? 0 : 1)
140
+ }
141
+
142
+ main().catch((err) => {
143
+ console.error('FATAL', err)
144
+ process.exit(1)
145
+ })
@@ -0,0 +1,88 @@
1
+ // ============================================================
2
+ // 首启自注册(setup-flow)E2E 验证:_dbg-setup-e2e.mjs <baseURL>
3
+ // 断言链:setup-status=true → 登录门呈现"创建管理员账号"(仅注册页签) →
4
+ // 浏览器真实填表注册 → 进入 workspace → setup-status=false → 登录/me 均 admin。
5
+ // 用法:node scripts/_dbg-setup-e2e.mjs http://127.0.0.1:3002
6
+ // ============================================================
7
+ import puppeteer from 'puppeteer-core'
8
+ import { mkdirSync } from 'node:fs'
9
+
10
+ const BASE = process.argv[2] ?? 'http://127.0.0.1:3002'
11
+ const CREDS = { name: 'admin', email: 'admin@awshop.local', password: 'Awshop2026' }
12
+ const SHOT_DIR = '.e2e-shots'
13
+ mkdirSync(SHOT_DIR, { recursive: true })
14
+
15
+ const results = []
16
+ const check = (name, ok, detail = '') => {
17
+ results.push({ name, ok, detail })
18
+ console.log(`${ok ? '✔' : '✖'} ${name}${detail ? ` — ${detail}` : ''}`)
19
+ }
20
+
21
+ const api = async (path, opts = {}) => {
22
+ const res = await fetch(`${BASE}${path}`, { ...opts, headers: { 'content-type': 'application/json', ...opts.headers } })
23
+ return { status: res.status, body: await res.json().catch(() => null) }
24
+ }
25
+
26
+ // ---- 1. API:初始状态应 needsSetup=true ----
27
+ const s0 = await api('/api/users/setup-status')
28
+ check('setup-status 初始 needsSetup=true', s0.status === 200 && s0.body?.data?.needsSetup === true, JSON.stringify(s0.body?.data))
29
+
30
+ // ---- 2. 浏览器:登录门 setup 模式 + 真实注册 ----
31
+ const browser = await puppeteer.launch({
32
+ executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
33
+ headless: 'new',
34
+ args: ['--no-sandbox', '--disable-gpu', '--no-proxy-server', '--window-size=1600,1000'],
35
+ })
36
+ try {
37
+ const page = await browser.newPage()
38
+ await page.setViewport({ width: 1600, height: 1000, deviceScaleFactor: 1.25 })
39
+ await page.goto(`${BASE}/workshop`, { waitUntil: 'networkidle2', timeout: 60000 })
40
+
41
+ await page.waitForFunction(() => document.body.innerText.includes('创建管理员账号'), { timeout: 30000 })
42
+ const gateText = await page.evaluate(() => document.body.innerText)
43
+ check('登录门呈现 setup 模式(创建管理员账号)', gateText.includes('创建管理员账号'))
44
+ check('setup 模式隐藏登录页签', !gateText.includes('账号登录') || gateText.indexOf('注册管理员') < gateText.indexOf('账号登录'))
45
+ check('setup 模式隐藏 Token 登录页签', !gateText.includes('Token 登录'))
46
+ check('提示首个注册账号成为管理员', gateText.includes('首个注册的账号将成为管理员'))
47
+ await page.screenshot({ path: `${SHOT_DIR}/setup-1-form.png` })
48
+
49
+ // 填表:auth-card 内 第1个input=用户名, type=email=邮箱, type=password=密码
50
+ await page.waitForSelector('.auth-card input')
51
+ const nameInput = (await page.$$('.auth-card input'))[0]
52
+ await nameInput.click({ clickCount: 3 })
53
+ await nameInput.type(CREDS.name)
54
+ const emailInput = await page.$('.auth-card input[type="email"]')
55
+ await emailInput.click({ clickCount: 3 })
56
+ await emailInput.type(CREDS.email)
57
+ const pwInput = await page.$('.auth-card input[type="password"]')
58
+ await pwInput.click({ clickCount: 3 })
59
+ await pwInput.type(CREDS.password)
60
+ await pwInput.keyboard.press('Enter')
61
+
62
+ // 注册成功 → store 置登录态 → 登录门消失,workspace 出现
63
+ await page.waitForFunction(() => document.body.innerText.includes('Workshop'), { timeout: 30000 })
64
+ await new Promise(r => setTimeout(r, 1500))
65
+ const afterText = await page.evaluate(() => document.body.innerText)
66
+ check('注册后放行进入项目(workspace 呈现)', afterText.includes('Workshop') && !afterText.includes('创建管理员账号'))
67
+ await page.screenshot({ path: `${SHOT_DIR}/setup-2-entered.png` })
68
+ }
69
+ finally {
70
+ await browser.close()
71
+ }
72
+
73
+ // ---- 3. API:注册后状态收敛 + 新管理员可登录 ----
74
+ const s1 = await api('/api/users/setup-status')
75
+ check('注册后 setup-status=false', s1.status === 200 && s1.body?.data?.needsSetup === false, JSON.stringify(s1.body?.data))
76
+
77
+ const login = await api('/api/users/login', { method: 'POST', body: JSON.stringify({ email: CREDS.email, password: CREDS.password }) })
78
+ check('新管理员密码登录成功', login.status === 200 && login.body?.data?.user?.role === 'admin', JSON.stringify(login.body?.data?.user))
79
+ const me = await api('/api/users/me', { headers: { authorization: `Bearer ${login.body?.data?.token}` } })
80
+ check('/api/users/me 返回 admin 身份', me.status === 200 && me.body?.data?.role === 'admin')
81
+
82
+ // ---- 4. 注册页签收敛后再注册第二账号应降为普通 user ----
83
+ const reg2 = await api('/api/users/register', { method: 'POST', body: JSON.stringify({ name: 'plainuser', email: 'plain@awshop.local', password: 'Plain2026' }) })
84
+ check('后续注册为普通 user 角色', reg2.status === 200 && reg2.body?.data?.user?.role === 'user', JSON.stringify(reg2.body?.data?.user))
85
+
86
+ const failed = results.filter(r => !r.ok)
87
+ console.log(`\n===== setup-flow E2E: ${results.length - failed.length}/${results.length} 通过 =====`)
88
+ process.exit(failed.length ? 1 : 0)