@chenkai114/dsh-team-admin 0.1.9 → 0.2.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/package.json +2 -2
- package/src/api.mjs +1 -0
- package/src/index.js +53 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chenkai114/dsh-team-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DSH 插件:GUI 管理任意会话的智能体团队 —— 查看成员、删除角色并清理其持久会话(进回收站,可恢复)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -55,4 +55,4 @@
|
|
|
55
55
|
"test:real": "node tests/e2e/real-data.mjs",
|
|
56
56
|
"test:pack": "npm pack --dry-run"
|
|
57
57
|
}
|
|
58
|
-
}
|
|
58
|
+
}
|
package/src/api.mjs
CHANGED
|
@@ -132,6 +132,7 @@ export function createApi({ ctx, getStore, version, logSystem, services }) {
|
|
|
132
132
|
{ sessionId, name, deleteSession: body.deleteSession !== false },
|
|
133
133
|
{ store, logSystem },
|
|
134
134
|
)
|
|
135
|
+
if (typeof services.onMemberDismissed === 'function') services.onMemberDismissed()
|
|
135
136
|
sendJson(res, 200, { ok: true, ...result })
|
|
136
137
|
return
|
|
137
138
|
}
|
package/src/index.js
CHANGED
|
@@ -21,12 +21,15 @@ const ROUTE_PREFIX = '/api/team-admin'
|
|
|
21
21
|
const DISMISSED_TTL_MS = 2000
|
|
22
22
|
/** 包装标记:apply 与延迟注入可能都执行一次,避免重复包装。 */
|
|
23
23
|
const FILTER_MARK = Symbol.for('dsh.team-admin.projection-filter')
|
|
24
|
+
/** roster 预算标记与刷新句柄:名额 = 配置上限 + 已删除成员数。 */
|
|
25
|
+
const ROSTER_MARK = Symbol.for('dsh.team-admin.roster-budget')
|
|
26
|
+
const ROSTER_REFRESH = Symbol.for('dsh.team-admin.roster-refresh')
|
|
24
27
|
/** 单值出口:一次请求/一次流的快照。 */
|
|
25
28
|
const UNARY_METHODS = ['list', 'projections']
|
|
26
29
|
/** 流出口:control 基线携带全部投影,follow 携带单个会话的开场快照。 */
|
|
27
30
|
const STREAM_METHODS = ['control', 'follow']
|
|
28
31
|
/** 与 package.json 版本保持一致(发布前由测试校验)。 */
|
|
29
|
-
const PLUGIN_VERSION = '0.
|
|
32
|
+
const PLUGIN_VERSION = '0.2.0'
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* 解析插件数据目录。
|
|
@@ -64,12 +67,23 @@ export function apply(ctx) {
|
|
|
64
67
|
getStore,
|
|
65
68
|
version: PLUGIN_VERSION,
|
|
66
69
|
logSystem,
|
|
67
|
-
services: {
|
|
70
|
+
services: {
|
|
71
|
+
listTeamSessions,
|
|
72
|
+
readTeam,
|
|
73
|
+
dismissMember,
|
|
74
|
+
// 删除成功后释放名额:名额外加数 = 墓碑数。
|
|
75
|
+
onMemberDismissed: () => { void ensureBudget()?.() },
|
|
76
|
+
},
|
|
68
77
|
})
|
|
69
78
|
ctx.effect(
|
|
70
79
|
() => ctx.webServer.register({ kind: 'prefix', path: ROUTE_PREFIX, handler }),
|
|
71
80
|
'team-admin routes',
|
|
72
81
|
)
|
|
82
|
+
const ensureBudget = () => installRosterBudget(ctx, { getStore, logSystem, silent: true })
|
|
83
|
+
ensureBudget()
|
|
84
|
+
if (typeof ctx.inject === 'function') {
|
|
85
|
+
ctx.inject(['agentTeams'], (sub) => installRosterBudget(sub, { getStore, logSystem }))
|
|
86
|
+
}
|
|
73
87
|
if (!installSessionListFilter(ctx, { getStore, logSystem, silent: true }) && typeof ctx.inject === 'function') {
|
|
74
88
|
// 插件可能早于 sessionController 注册;等服务可用后再包一次。
|
|
75
89
|
ctx.inject(['sessionController'], (sub) => installSessionListFilter(sub, { getStore, logSystem }))
|
|
@@ -148,3 +162,40 @@ export function installSessionListFilter(ctx, { getStore, logSystem, silent = fa
|
|
|
148
162
|
service[FILTER_MARK] = true
|
|
149
163
|
return true
|
|
150
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 把「已删除成员」占掉的名额还给 roster:宿主用 roster 的 append-only 成员数判断 maxMembers,
|
|
168
|
+
* 删除只写墓碑不删记录,所以这里把上限动态抬到 `配置上限 + 墓碑数`。
|
|
169
|
+
* @param {object} ctx - Cordis 插件上下文。
|
|
170
|
+
* @param {{ getStore: () => Promise<object>, logSystem: (line: string) => void, silent?: boolean }} deps - 存储与日志。
|
|
171
|
+
* @returns {null | (() => Promise<number>)} 刷新预算的函数;宿主没有 agentTeams 时返回 null。
|
|
172
|
+
*/
|
|
173
|
+
export function installRosterBudget(ctx, { getStore, logSystem, silent = false }) {
|
|
174
|
+
const get = ctx !== null && typeof ctx?.get === 'function' ? ctx.get.bind(ctx) : null
|
|
175
|
+
const service = get === null ? undefined : get('agentTeams')
|
|
176
|
+
const roster = service === null || service === undefined ? undefined : service.roster
|
|
177
|
+
if (roster === undefined || roster === null || typeof roster.maxMembers !== 'number') {
|
|
178
|
+
if (silent !== true) logSystem('team-admin:未找到 agentTeams.roster,跳过名额回收')
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
if (roster[ROSTER_MARK] === true) return roster[ROSTER_REFRESH]
|
|
182
|
+
const base = roster.maxMembers
|
|
183
|
+
const budget = { extra: 0 }
|
|
184
|
+
Object.defineProperty(roster, 'maxMembers', {
|
|
185
|
+
configurable: true,
|
|
186
|
+
enumerable: true,
|
|
187
|
+
get: () => base + budget.extra,
|
|
188
|
+
})
|
|
189
|
+
roster[ROSTER_MARK] = true
|
|
190
|
+
roster[ROSTER_REFRESH] = async () => {
|
|
191
|
+
try {
|
|
192
|
+
const store = await getStore()
|
|
193
|
+
budget.extra = collectDismissedIds(await store.readAll()).size
|
|
194
|
+
} catch (error) {
|
|
195
|
+
logSystem(`team-admin:读取墓碑失败,名额不回收(${error instanceof Error ? error.message : String(error)})`)
|
|
196
|
+
}
|
|
197
|
+
return budget.extra
|
|
198
|
+
}
|
|
199
|
+
void roster[ROSTER_REFRESH]()
|
|
200
|
+
return roster[ROSTER_REFRESH]
|
|
201
|
+
}
|