dsh-session-guard 0.1.5-beta.1 → 0.3.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.ja.md +23 -0
- package/CHANGELOG.ko.md +23 -0
- package/CHANGELOG.md +52 -0
- package/README.en.md +75 -13
- package/README.ja.md +34 -6
- package/README.ko.md +34 -6
- package/README.md +78 -16
- package/dsh.plugin.json +2 -2
- package/lib/client.js +656 -462
- package/lib/client.js.map +1 -1
- package/package.json +6 -1
- package/src/bridge.js +61 -1
- package/src/client/badge-text.ts +6 -1
- package/src/client/index.ts +11 -1
- package/src/client/pause-button-text.ts +60 -0
- package/src/client/pause-button.tsx +129 -0
- package/src/client/settings-card.tsx +21 -0
- package/src/client/status-badge.tsx +4 -0
- package/src/client/styles.ts +30 -0
- package/src/index.js +106 -4
- package/src/pause-gate.js +22 -1
- package/src/settings.js +4 -0
- package/src/step-gate.js +399 -0
- package/src/wiring.js +46 -4
package/src/wiring.js
CHANGED
|
@@ -21,6 +21,7 @@ import { msUntilOffPeak, shouldPause } from './time.js'
|
|
|
21
21
|
* @param {object} deps.ctx host context
|
|
22
22
|
* @param {()=>object} deps.getSettings 读实时配置
|
|
23
23
|
* @param {{stopNextTurn:Function, resume:Function}} deps.gate 会话门
|
|
24
|
+
* @param {ReturnType<import('./step-gate.js').createStepGate>} [deps.stepGate] step 级门控(v0.2.0)
|
|
24
25
|
* @param {object} [deps.logger]
|
|
25
26
|
* @param {()=>Date} [deps.clock]
|
|
26
27
|
* @param {typeof setTimeout} [deps.setTimer]
|
|
@@ -31,6 +32,7 @@ export function createWiring({
|
|
|
31
32
|
ctx,
|
|
32
33
|
getSettings,
|
|
33
34
|
gate,
|
|
35
|
+
stepGate,
|
|
34
36
|
logger,
|
|
35
37
|
clock = () => new Date(),
|
|
36
38
|
setTimer = setTimeout,
|
|
@@ -110,16 +112,24 @@ export function createWiring({
|
|
|
110
112
|
|
|
111
113
|
/**
|
|
112
114
|
* 高峰期目标从官方变为非官方 → 恢复本插件因入峰而暂停的会话(spec 需求 5)。
|
|
113
|
-
*
|
|
115
|
+
* v0.2.0:先释放 step 门控(幂等,无门则 no-op);turn 级只对 `pausedByPeak` 里的
|
|
116
|
+
* 会话生效(不碰用户手动暂停的);受 `deferredResume` 约束;
|
|
114
117
|
* 用 queueMicrotask 跳出当前事件派发,避免 resume→followup→事件 的重入。
|
|
115
118
|
*/
|
|
116
119
|
function maybeAutoResume(sessionId, rec) {
|
|
117
|
-
if (!pausedByPeak.has(sessionId)) return
|
|
118
120
|
const cfg = safeCfg()
|
|
119
121
|
if (cfg.providerGuard !== true || cfg.deferredResume === false) return
|
|
120
122
|
if (!shouldPause(cfg, clock()).pause) return
|
|
121
123
|
const cls = directory.classify(rec.provider)
|
|
122
124
|
if (cls.official) return
|
|
125
|
+
// step 门控:目标转非官方 → 放行被挂起的 step(step gate 自身按目标判定,无需 pausedByPeak)
|
|
126
|
+
if (stepGate) {
|
|
127
|
+
const r = stepGate.release(sessionId, 'non-official')
|
|
128
|
+
if (r && r.released === true) {
|
|
129
|
+
logger?.info?.(`[session-guard] target switched to non-official "${rec.provider}" (matchedBy=${cls.matchedBy}) — releasing step gate for ${sessionId}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!pausedByPeak.has(sessionId)) return
|
|
123
133
|
pausedByPeak.delete(sessionId)
|
|
124
134
|
logger?.info?.(`[session-guard] target switched to non-official "${rec.provider}" (matchedBy=${cls.matchedBy}) — auto-resuming ${sessionId}`)
|
|
125
135
|
queueMicrotask(() => {
|
|
@@ -143,6 +153,8 @@ export function createWiring({
|
|
|
143
153
|
const agents = ctx && ctx.agents
|
|
144
154
|
if (!agents) return { paused: [], skipped: [] }
|
|
145
155
|
const roots = typeof agents.roots === 'function' ? agents.roots() : typeof agents.list === 'function' ? agents.list() : []
|
|
156
|
+
// Q1-A:step 级门控开启时,入峰不再 turn 级暂停——会话跑到下一个 pre-step 边界自动拉门。
|
|
157
|
+
const stepLevel = cfg.stepLevelPause === true && !!stepGate
|
|
146
158
|
const paused = []
|
|
147
159
|
const skipped = []
|
|
148
160
|
for (const agent of Array.isArray(roots) ? roots : []) {
|
|
@@ -157,6 +169,12 @@ export function createWiring({
|
|
|
157
169
|
skipped.push({ sessionId: id, why: 'non-official', provider: targets.providerOf(id) })
|
|
158
170
|
continue
|
|
159
171
|
}
|
|
172
|
+
if (stepLevel) {
|
|
173
|
+
// 新一轮高峰:清掉上一轮的 bypass,让 step 门重新生效。
|
|
174
|
+
stepGate.clearBypass(id)
|
|
175
|
+
paused.push({ sessionId: id, via: 'stepGate', ok: true })
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
160
178
|
try {
|
|
161
179
|
const r = await gate.stopNextTurn(id, { mode: cfg.pauseMode, reason: cfg.pauseReason })
|
|
162
180
|
if (!r || r.ok !== false) pausedByPeak.add(id)
|
|
@@ -165,7 +183,7 @@ export function createWiring({
|
|
|
165
183
|
warn(`stopNextTurn failed for ${id}: ${String(e && e.message || e)}`)
|
|
166
184
|
}
|
|
167
185
|
}
|
|
168
|
-
logger?.info?.(`[session-guard] peak entered — paused ${paused.length} session(s): ${JSON.stringify(paused)}; skipped ${JSON.stringify(skipped)}`)
|
|
186
|
+
logger?.info?.(`[session-guard] peak entered — ${stepLevel ? 'step-level gate armed for' : 'paused'} ${paused.length} session(s): ${JSON.stringify(paused)}; skipped ${JSON.stringify(skipped)}`)
|
|
169
187
|
return { paused, skipped }
|
|
170
188
|
}
|
|
171
189
|
|
|
@@ -188,12 +206,18 @@ export function createWiring({
|
|
|
188
206
|
}
|
|
189
207
|
}
|
|
190
208
|
|
|
191
|
-
/**
|
|
209
|
+
/** 退峰:先释放 step 门(回合原地续跑),再放行挂起的请求、恢复暂停的会话。 */
|
|
192
210
|
async function onLeavePeak(cfg) {
|
|
193
211
|
if (releaseTimer !== null) {
|
|
194
212
|
clearTimer(releaseTimer)
|
|
195
213
|
releaseTimer = null
|
|
196
214
|
}
|
|
215
|
+
// 0) step 级门控:先放行被挂起的 step —— 回合本来就还开着,无需 followup。
|
|
216
|
+
if (stepGate) {
|
|
217
|
+
const releasedSteps = stepGate.releaseAll('off-peak')
|
|
218
|
+
stepGate.clearAllBypass()
|
|
219
|
+
if (releasedSteps.length > 0) logger?.info?.(`[session-guard] peak left — released ${releasedSteps.length} step gate(s): ${JSON.stringify(releasedSteps)}`)
|
|
220
|
+
}
|
|
197
221
|
// 1) 先取延后记录快照(releaseAll 会清空),再放行全部挂起。
|
|
198
222
|
const deferredSnapshot = deferrals.deferredList()
|
|
199
223
|
const { released } = deferrals.releaseAll('off-peak')
|
|
@@ -237,6 +261,19 @@ export function createWiring({
|
|
|
237
261
|
return guard.install()
|
|
238
262
|
}
|
|
239
263
|
|
|
264
|
+
/**
|
|
265
|
+
* 安装 step 级门控(`agent/pre-step` waterfall,v0.2.0)。
|
|
266
|
+
* disposer 释放全部挂起门,避免卸载后 Promise 悬挂。
|
|
267
|
+
*/
|
|
268
|
+
function installStepGuard() {
|
|
269
|
+
if (!stepGate || !ctx || typeof ctx.on !== 'function') return () => {}
|
|
270
|
+
const off = ctx.on('agent/pre-step', (payload, next) => stepGate.hold(payload, next))
|
|
271
|
+
return () => {
|
|
272
|
+
if (typeof off === 'function') off()
|
|
273
|
+
stepGate.releaseAll('disposed')
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
240
277
|
/** 插件卸载:清定时器 + 拒绝全部挂起(不泄漏 promise)。 */
|
|
241
278
|
function dispose() {
|
|
242
279
|
if (releaseTimer !== null) {
|
|
@@ -244,6 +281,10 @@ export function createWiring({
|
|
|
244
281
|
releaseTimer = null
|
|
245
282
|
}
|
|
246
283
|
pausedByPeak.clear()
|
|
284
|
+
if (stepGate) {
|
|
285
|
+
stepGate.releaseAll('disposed')
|
|
286
|
+
stepGate.clearAllBypass()
|
|
287
|
+
}
|
|
247
288
|
deferrals.rejectAll('disposed')
|
|
248
289
|
}
|
|
249
290
|
|
|
@@ -258,6 +299,7 @@ export function createWiring({
|
|
|
258
299
|
scheduleRelease,
|
|
259
300
|
shouldPauseSession,
|
|
260
301
|
installGuard,
|
|
302
|
+
installStepGuard,
|
|
261
303
|
dispose,
|
|
262
304
|
}
|
|
263
305
|
}
|