@marsken/dsh-studio 0.1.4 → 0.1.5

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.
@@ -6,7 +6,7 @@
6
6
 
7
7
  | 项 | 值 |
8
8
  | --- | --- |
9
- | dsh-studio | `package.json` `version: 0.1.4` |
9
+ | dsh-studio | `package.json` `version: 0.1.5` |
10
10
  | DeepSeek Harness | `0.2.2-rc.1`(`dsh --version`) |
11
11
  | Node | `v24.22.2`(Linux 目标机);本机 Windows `v24.19.0` |
12
12
  | 目标系统 | Ubuntu 24.04,`dsh web` 监听 `18080`,nginx `:80` 反代并注入 `dsh-auth-*` cookie |
package/docs/publish.md CHANGED
@@ -127,6 +127,7 @@ token 只写进临时 npmrc(放在仓库外或 `tmp/` 且不提交),发完
127
127
  | --- | --- | --- |
128
128
  | `0.1.0` | 2026-11-13 | 首个公开版:看板 / 需求 / 用例回归 / 效能 / 设计评审四期 |
129
129
  | `0.1.1` | 2026-11-13 | 补三张 README 截图(npm 页可看图);默认值与文档去掉公司内网信息 |
130
+ | `0.1.5` | 2026-11-14 | agent/failed 事件、控制面板(执行开关 / 上限 / webhook 按事件订阅)、抽屉里加数据库用例入口 |
130
131
  | `0.1.4` | 2026-11-14 | SQLite 用例、出站 webhook、可配置上限、执行器注册表 |
131
132
  | `0.1.3` | 2026-11-13 | shell 用例(命令 + 退出码/输出断言,默认关闭) |
132
133
  | `0.1.2` | 2026-11-13 | 修 5 个缺陷:卡片挂不上 MR、换列清空负责人、三处静默截断、还原度无历史、规范体检不定位元素 |
@@ -134,4 +135,5 @@ token 只写进临时 npmrc(放在仓库外或 `tmp/` 且不提交),发完
134
135
  两条硬规矩,每次发版都过一遍:
135
136
 
136
137
  1. `npm run check` + `pnpm build` + `cmp -s dist/client.js client.js`(产物必须逐字节一致);
137
- 2. 发完**在临时 profile 里装一遍**并跑 `dsh --profile <tmp> --dump-config | grep dsh-studio`,确认宿主真认到了,然后把 profile 删掉。
138
+ 2. 发完**在临时 profile 里装一遍**并跑 `dsh --profile <tmp> --dump-config | grep dsh-studio`,确认宿主真认到了,然后把 profile 删掉;
139
+ 3. 部署脚本退出码 0 **不等于服务活着**——它打印的 `state=` 要自己看一眼,再单独 `curl -o /dev/null -w '%{http_code}' <host>/studio-api/state` 确认 200(2026-11-13 有过一次 `state=404` 被当成成功,线上挂了十分钟)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marsken/dsh-studio",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "DSH Studio: role-based multi-agent workspace (product / architect / developer / QA) with a live work board, GitLab + Feishu connection settings, all configurable from the web GUI.",
6
6
  "main": "index.js",
@@ -1,5 +1,6 @@
1
1
  // ------------------------------------------------------------------ dispatcher
2
2
  import { DEFAULT_ROLES, FALLBACK_AGENT_MODEL, ROLE_IDS, SANDBOX_MODES, STATUSES, codingSummary, normalizeStrategy, worktreeRoot } from './domain.js'
3
+ import { emitEvent } from './events.js'
3
4
  import { loadTeamMemory, teamMemoryBlock } from './team-memory.js'
4
5
  import { mkdir } from 'node:fs/promises'
5
6
  import { randomBytes } from 'node:crypto'
@@ -345,6 +346,7 @@ export async function noteAgentFailure(store, taskId, error, by = '系统', opts
345
346
  host.INTEGRATION.lastPushError = String(cause?.message ?? cause).slice(0, 300)
346
347
  })
347
348
  }
349
+ emitEvent('agent/failed', { taskId, attempts: next, exhausted: plan.exhausted, error: message, by })
348
350
  return { failures: next, ...plan, message }
349
351
  }
350
352
 
@@ -333,6 +333,9 @@ export async function runTaskCases(store, taskId, options = {}) {
333
333
  for (const item of targets) {
334
334
  const outcome = await runOneCase(item, env, config)
335
335
  results.push({ id: item.id, title: item.title, ...outcome })
336
+ if (outcome.ok !== true && item.flaky !== true) {
337
+ emitEvent('case/failed', { taskId: task.id, code: task.code ?? '', caseId: item.id, title: item.title, error: String(outcome.error ?? '').slice(0, 200) })
338
+ }
336
339
  await store.mutate(draft => {
337
340
  const card = draft.tasks.find(entry => entry.id === task.id)
338
341
  if (card === undefined) return
@@ -1265,6 +1268,7 @@ export async function updateTask(store, id, patch) {
1265
1268
  const simulated = { ...current, ...normalized, blocked: current.blocked }
1266
1269
  const gate = taskExitGate(store.snapshot(), simulated)
1267
1270
  if (!gate.ready) {
1271
+ emitEvent('gate/blocked', { taskId: id, gate: 'exit', blockers: gate.blockers.slice(0, 3) })
1268
1272
  throw new Error(`准出没过:${gate.blockers.join(';')}。确实要收口就在推进时填 override 写明原因。`)
1269
1273
  }
1270
1274
  }
@@ -1274,7 +1278,10 @@ export async function updateTask(store, id, patch) {
1274
1278
  const current = store.snapshot().tasks.find(entry => entry.id === id)
1275
1279
  if (current !== undefined) {
1276
1280
  const gate = designGate(store.snapshot().config, current, normalized.status)
1277
- if (gate.needed === true && gate.ready !== true) throw new Error(`进开发被拦:${gate.blockers.join(';')}`)
1281
+ if (gate.needed === true && gate.ready !== true) {
1282
+ emitEvent('gate/blocked', { taskId: id, gate: 'design', blockers: gate.blockers.slice(0, 3) })
1283
+ throw new Error(`进开发被拦:${gate.blockers.join(';')}`)
1284
+ }
1278
1285
  }
1279
1286
  }
1280
1287
  let result
@@ -1,7 +1,7 @@
1
1
  // webhook 订阅者:把事件 POST 给配置里的地址。失败只记健康,不重试、不阻塞。
2
2
  import { emitEvent, subscribe } from './events.js'
3
3
 
4
- export const WEBHOOK_EVENTS = ['task/moved', 'case/failed', 'agent/failed']
4
+ export const WEBHOOK_EVENTS = ['task/moved', 'case/failed', 'gate/blocked', 'agent/failed']
5
5
 
6
6
  export function sanitizeWebhooks(raw) {
7
7
  if (!Array.isArray(raw)) return []