cs-bots 0.7.4 → 0.8.0
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.
- package/CHANGELOG.md +114 -0
- package/README.md +47 -174
- package/docs/01-/346/236/266/346/236/204/346/226/207/346/241/243.md +14 -13
- package/docs/CONTRACT-/345/215/207/347/272/24709-Boss/344/275/223/351/252/214.md +280 -0
- package/docs//345/215/207/347/272/24708-/346/216/245/347/273/255/344/270/216/345/233/236/346/273/232.md +298 -0
- package/package.json +2 -2
- package/src/api.js +1005 -7
- package/src/board.js +43 -11
- package/src/client.js +537 -82
- package/src/settings.js +2 -2
- package/src/teams.js +34 -1
- package/src/tools.js +76 -1
package/src/settings.js
CHANGED
|
@@ -16,8 +16,8 @@ import { resolve, sep, isAbsolute, join } from 'node:path'
|
|
|
16
16
|
|
|
17
17
|
/** 设置项定义:默认值 + 校验。 */
|
|
18
18
|
export const SETTING_SPECS = Object.freeze({
|
|
19
|
-
/**
|
|
20
|
-
defaultLeaseMs: { def: 5 * 60_000, min: 10_000, max: 24 * 60 * 60_000, label: '
|
|
19
|
+
/** 任务默认占用时长(ms):owner claim 后多久未心跳即视为失联。 */
|
|
20
|
+
defaultLeaseMs: { def: 5 * 60_000, min: 10_000, max: 24 * 60 * 60_000, label: '任务占用默认时长(ms)' },
|
|
21
21
|
/** 唤醒节流窗口(ms):同一目标在窗内最多唤醒一次。 */
|
|
22
22
|
throttleMs: { def: 5_000, min: 0, max: 60 * 60_000, label: '唤醒节流窗口(ms)' },
|
|
23
23
|
/** 每目标唤醒队列上限(背压阈值)。 */
|
package/src/teams.js
CHANGED
|
@@ -64,7 +64,7 @@ export const DEFAULT_TEAM_ADMIN_POLICY = 'open'
|
|
|
64
64
|
const HUMAN_IDS = Object.freeze(['ui', 'user', 'person'])
|
|
65
65
|
|
|
66
66
|
/** 合法动作(含 setPolicy:策略变更本身也是要留痕的事件)。 */
|
|
67
|
-
const ACTIONS = Object.freeze(['create', 'addMember', 'removeMember', 'setRole', 'close', 'note', 'setPolicy'])
|
|
67
|
+
const ACTIONS = Object.freeze(['create', 'addMember', 'removeMember', 'setRole', 'close', 'note', 'setPolicy', 'attachRoom'])
|
|
68
68
|
|
|
69
69
|
/** 结构化错误。 */
|
|
70
70
|
export class TeamError extends Error {
|
|
@@ -474,6 +474,36 @@ export function createTeams(ctx, config = {}) {
|
|
|
474
474
|
|
|
475
475
|
// ── API:读 ────────────────────────────────────────────────────────────────
|
|
476
476
|
|
|
477
|
+
/**
|
|
478
|
+
* 给团队挂上一个房间(契约 §3:团队自动配议事厅)。
|
|
479
|
+
*
|
|
480
|
+
* 幂等:同一个 roomId 再调一次 ⇒ 直接返回现有团队(rev 不变)。
|
|
481
|
+
* 房间已经存在、只是想补关联时**不重复写事件** —— 否则 `/teams` 反复保存
|
|
482
|
+
* 会把事件流刷成一条重复行,rev 也跟着虚增,页面上的「谁改了团队」就失真了。
|
|
483
|
+
* @param {{ id: string, expectedRev?: number, roomId: string, by: string, now?: number }} input
|
|
484
|
+
*/
|
|
485
|
+
function attachRoom(input = {}) {
|
|
486
|
+
assertAlive('attachRoom')
|
|
487
|
+
const { id, expectedRev, roomId, by, now } = input ?? {}
|
|
488
|
+
const author = requireString(by, 'by', 'attachRoom')
|
|
489
|
+
const at = normalizeNow(now, 'attachRoom')
|
|
490
|
+
const room = requireString(roomId, 'roomId', 'attachRoom')
|
|
491
|
+
const current = cas(id, expectedRev, 'attachRoom')
|
|
492
|
+
if (current.roomId === room) {
|
|
493
|
+
return clone(current) // 幂等:已关联同一房间,不重复落事件
|
|
494
|
+
}
|
|
495
|
+
const next = clone(current)
|
|
496
|
+
next.rev = current.rev + 1
|
|
497
|
+
next.roomId = room
|
|
498
|
+
next.updatedAt = at
|
|
499
|
+
next.updatedBy = author
|
|
500
|
+
commit('attachRoom', at, author, clone(current), next)
|
|
501
|
+
info(`[cs-bots/teams] 团队 ${id} 关联房间 ${room}(by ${author})`)
|
|
502
|
+
return clone(next)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ── API:读 ────────────────────────────────────────────────────────────────
|
|
506
|
+
|
|
477
507
|
function list(query = {}) {
|
|
478
508
|
assertAlive('list')
|
|
479
509
|
let rows = [...entries.values()]
|
|
@@ -515,6 +545,9 @@ export function createTeams(ctx, config = {}) {
|
|
|
515
545
|
note,
|
|
516
546
|
close,
|
|
517
547
|
setPolicy,
|
|
548
|
+
// 契约 §3:把「议事厅」绑到团队上。api.js 的 ensureTeamRoom 会调它;
|
|
549
|
+
// 不导出 ⇒ 房间建好了但团队记不住 roomId,重启后自动配厅就永远重配一遍。
|
|
550
|
+
attachRoom,
|
|
518
551
|
list,
|
|
519
552
|
get,
|
|
520
553
|
stats,
|
package/src/tools.js
CHANGED
|
@@ -30,7 +30,8 @@ function success(payload) {
|
|
|
30
30
|
* 从宿主上下文取本平台的域服务。
|
|
31
31
|
* @param {object} ctx
|
|
32
32
|
* @returns {{ board?: object, roster?: object, room?: object, memory?: object, dispatcher?: object,
|
|
33
|
-
* todo?: object, scratch?: object, prompts?: object, discipline?: object, suggest?: object
|
|
33
|
+
* todo?: object, scratch?: object, prompts?: object, discipline?: object, suggest?: object,
|
|
34
|
+
* phaseWrite?: (taskId: string, patch: object) => object }}
|
|
34
35
|
*/
|
|
35
36
|
function servicesOf(ctx) {
|
|
36
37
|
return getServices(ctx)
|
|
@@ -258,6 +259,80 @@ const DEFS = [
|
|
|
258
259
|
},
|
|
259
260
|
},
|
|
260
261
|
|
|
262
|
+
// ── 阶段产出物(契约 §2.4,纯增量)──────────────────────────────────────────
|
|
263
|
+
{
|
|
264
|
+
name: 'bot_task_phase',
|
|
265
|
+
description:
|
|
266
|
+
'边做边交阶段产出物:每完成一个阶段就调用一次,带上这一阶段交出的文件路径/链接。' +
|
|
267
|
+
'【重要】不要等到整条任务做完才汇报 —— 阶段产物是给 Boss 点开就能看到的东西,' +
|
|
268
|
+
'分阶段交付(doing = 正在做这一阶段 / done = 这一阶段交完 / blocked = 卡在这一阶段)。' +
|
|
269
|
+
'需要 B 侧的 `services.phaseWrite(taskId, patch)` 能力;后端未注入时返回 failure,' +
|
|
270
|
+
'不会静默成功。',
|
|
271
|
+
parameters: {
|
|
272
|
+
type: 'object',
|
|
273
|
+
properties: {
|
|
274
|
+
id: { type: 'string', description: '任务 id' },
|
|
275
|
+
index: { type: 'number', description: '阶段下标(从 0 开始,必填;越界会被后端拒)' },
|
|
276
|
+
state: {
|
|
277
|
+
type: 'string',
|
|
278
|
+
enum: ['doing', 'done', 'blocked'],
|
|
279
|
+
description: '该阶段状态(默认 done)',
|
|
280
|
+
},
|
|
281
|
+
artifacts: {
|
|
282
|
+
type: 'array',
|
|
283
|
+
items: { type: 'string' },
|
|
284
|
+
description: '这一阶段交出的文件路径/URL(可为空数组,表示「这一阶段没产出物」)',
|
|
285
|
+
},
|
|
286
|
+
note: { type: 'string', description: '一句话说明' },
|
|
287
|
+
expectedRev: { type: 'number', description: '当前任务 rev(可选,CAS)' },
|
|
288
|
+
},
|
|
289
|
+
required: ['id', 'index'],
|
|
290
|
+
additionalProperties: false,
|
|
291
|
+
},
|
|
292
|
+
run: (s, a, exec) => {
|
|
293
|
+
// 契约 §2.4:阶段写入能力由 B 侧 installApi 挂到 services.phaseWrite。
|
|
294
|
+
// 拿不到就是拿不到 —— **绝不静默成功**,返回 failure 让调用方看见。
|
|
295
|
+
const phaseWrite = s?.phaseWrite
|
|
296
|
+
if (typeof phaseWrite !== 'function') {
|
|
297
|
+
return failure('phase-unavailable', {
|
|
298
|
+
hint: '后端未就绪:services.phaseWrite 未注入(应由 api.js 挂载)。'
|
|
299
|
+
+ '请稍后重试;或先用 bot_task_update 交付总产出物。',
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
if (typeof a?.id !== 'string' || a.id === '') {
|
|
303
|
+
return failure('bad-argument', { field: 'id', hint: 'id 必填' })
|
|
304
|
+
}
|
|
305
|
+
const index = a?.index
|
|
306
|
+
if (typeof index !== 'number' || !Number.isInteger(index) || index < 0) {
|
|
307
|
+
return failure('bad-argument', { field: 'index', hint: 'index 必填,且必须是 >=0 的整数' })
|
|
308
|
+
}
|
|
309
|
+
const state = a?.state === undefined ? 'done' : a.state
|
|
310
|
+
if (state !== 'doing' && state !== 'done' && state !== 'blocked') {
|
|
311
|
+
return failure('bad-argument', { field: 'state', hint: 'state 只允许 doing | done | blocked' })
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
// `by` 由调用者身份得出(事实留痕,不猜);`expectedRev` 若给了就带上做 CAS。
|
|
315
|
+
const res = phaseWrite(a.id, {
|
|
316
|
+
index,
|
|
317
|
+
state,
|
|
318
|
+
artifacts: a?.artifacts,
|
|
319
|
+
note: a?.note,
|
|
320
|
+
by: callerOf(exec),
|
|
321
|
+
expectedRev: a?.expectedRev,
|
|
322
|
+
})
|
|
323
|
+
if (!res || typeof res !== 'object') {
|
|
324
|
+
return failure('phase-write-failed', { message: 'phaseWrite 返回了非法结果' })
|
|
325
|
+
}
|
|
326
|
+
return res
|
|
327
|
+
} catch (error) {
|
|
328
|
+
// 写不进 task-meta.jsonl 必须让模型看见(不许吞)
|
|
329
|
+
return failure(error?.code ?? 'phase-write-failed', {
|
|
330
|
+
message: error?.message ?? String(error),
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
|
|
261
336
|
// ── 唤醒桥(Dispatcher)────────────────────────────────────────────────────
|
|
262
337
|
{
|
|
263
338
|
name: 'bot_wake',
|