dsh-plugin-rollout-scout 1.4.0 → 1.5.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/README.en.md +11 -1
- package/README.md +11 -1
- package/_wrap-client.mjs +1 -1
- package/lib/client.js +4 -3
- package/lib/index.js +72 -46
- package/package.json +20 -4
- package/plugin.client.js +4 -3
package/README.en.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
English | [简体中文](README.md)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-plugin-rollout-scout)
|
|
6
|
+
[](https://github.com/SpookySandwich/dsh-plugin-rollout-scout/actions/workflows/ci.yml)
|
|
6
7
|
[](LICENSE)
|
|
7
|
-
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
8
9
|
[](https://github.com/SpookySandwich/dsh-plugin-rollout-scout/stargazers)
|
|
9
10
|
|
|
10
11
|
Providers sometimes roll a new conversation model out gradually, so which one you get is luck of the draw. Rollout Scout opens throwaway conversations on your own account, reads each one's chain-of-thought **as it streams**, and scores how the reasoning is written — cancelling the ones that read like the model you already have, and keeping the ones that don't.
|
|
@@ -134,6 +135,15 @@ Implementation notes live in [`docs/`](docs/) — [architecture](docs/architectu
|
|
|
134
135
|
|
|
135
136
|
## Compatibility
|
|
136
137
|
|
|
138
|
+
Version `1.5.0`: Consume transient agent/assistant-stream frames on DSH 0.1.5, retain the legacy feed without duplicate counting, and unwrap persistence snapshot headers for cleanup.
|
|
139
|
+
|
|
140
|
+
The declared host range is `>=0.1.5-rc.2 <0.1.6-0`; the official `0.1.5-rc.2` runtime was verified. DSH `0.1.6` alpha is not claimed compatible. Keep the previous plugin release on older DSH. [Validation record](.github/reviews/dsh-0.1.5.md).
|
|
141
|
+
|
|
142
|
+
Download the archive from the [GitHub Release](https://github.com/SpookySandwich/dsh-plugin-rollout-scout/releases/tag/v1.5.0), then run `dsh plugin --profile desktop add ./dsh-plugin-rollout-scout-1.5.0.tgz`.
|
|
143
|
+
|
|
144
|
+
This release targets DSH `0.1.5-rc.2`. Run `npm ci`, `npm test`, and `npm run check:package` to verify the build and package. Restart DSH after updating.
|
|
145
|
+
|
|
146
|
+
|
|
137
147
|
The launcher takes a `sidebar.footer.action` seat (a list slot, so it sits beside any other footer action rather than displacing one) and the console renders on the frame-wide `shell.overlay` layer. Neither is per-session, so it conflicts with no session plugin. Requires a DSH whose sidebar declares that seat; without it the console has no way in. Part of the same family as [dsh-plugin-smooth-stream](https://github.com/SpookySandwich/dsh-plugin-smooth-stream), [dsh-plugin-no-workspace](https://github.com/SpookySandwich/dsh-plugin-no-workspace) and [dsh-plugin-message-edit](https://github.com/SpookySandwich/dsh-plugin-message-edit).
|
|
138
148
|
|
|
139
149
|
## A note on cost
|
package/README.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
[English](README.en.md) | 简体中文
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-plugin-rollout-scout)
|
|
6
|
+
[](https://github.com/SpookySandwich/dsh-plugin-rollout-scout/actions/workflows/ci.yml)
|
|
6
7
|
[](LICENSE)
|
|
7
|
-
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
8
9
|
[](https://github.com/SpookySandwich/dsh-plugin-rollout-scout/stargazers)
|
|
9
10
|
|
|
10
11
|
服务商有时会灰度发布新的对话模型,你分到哪一个全看运气。灰度侦察会用你自己的账号开启一批临时会话,**在思维链流式输出的同时**读取它,并按「推理是怎么写的」打分——读起来像你手上这个旧模型的立刻中止,不像的留下来。
|
|
@@ -132,6 +133,15 @@ dsh plugin --profile web add github:SpookySandwich/dsh-plugin-rollout-scout
|
|
|
132
133
|
|
|
133
134
|
## 兼容性
|
|
134
135
|
|
|
136
|
+
版本 `1.5.0`:适配 DSH 0.1.5 的 agent/assistant-stream 瞬态流式事件,保留旧事件支持并防止重复计数,兼容持久化列表的新快照结构。
|
|
137
|
+
|
|
138
|
+
声明兼容范围为 `>=0.1.5-rc.2 <0.1.6-0`;已验证官方 `0.1.5-rc.2`,不声明兼容 `0.1.6` alpha。旧版 DSH 请保留上一插件版本。[验证记录](.github/reviews/dsh-0.1.5.md)。
|
|
139
|
+
|
|
140
|
+
可从 [GitHub Release](https://github.com/SpookySandwich/dsh-plugin-rollout-scout/releases/tag/v1.5.0) 下载发布包,然后执行 `dsh plugin --profile desktop add ./dsh-plugin-rollout-scout-1.5.0.tgz`。
|
|
141
|
+
|
|
142
|
+
本次兼容目标为 DSH `0.1.5-rc.2`。运行 `npm ci`、`npm test` 和 `npm run check:package` 可验证构建及发布包。更新后请重启 DSH。
|
|
143
|
+
|
|
144
|
+
|
|
135
145
|
入口占用 `sidebar.footer.action` 座位(list 类型,会与其它底部操作并排,而不是把谁挤掉),控制台本体渲染在全局 `shell.overlay` 层。两者都不属于会话作用域,因此不与任何会话内插件冲突。需要侧边栏声明了该座位的 DSH 版本;否则控制台将没有入口。与 [dsh-plugin-smooth-stream](https://github.com/SpookySandwich/dsh-plugin-smooth-stream)、[dsh-plugin-no-workspace](https://github.com/SpookySandwich/dsh-plugin-no-workspace)、[dsh-plugin-message-edit](https://github.com/SpookySandwich/dsh-plugin-message-edit) 同族。
|
|
136
146
|
|
|
137
147
|
## 关于额度
|
package/_wrap-client.mjs
CHANGED
package/lib/client.js
CHANGED
|
@@ -489,13 +489,14 @@ const CSS = [
|
|
|
489
489
|
].join('');
|
|
490
490
|
|
|
491
491
|
return {
|
|
492
|
+
inject: ['slots', 'sessions', 'locale'],
|
|
492
493
|
apply(ctx) {
|
|
493
494
|
const slots = ctx.get('slots');
|
|
494
|
-
if (slots === undefined)
|
|
495
|
+
if (slots === undefined) throw new Error('[dsh-plugin-rollout-scout] Missing DSH slots service; check client dependencies.');
|
|
495
496
|
ctx.effect(function () { return styles.insert(CSS); });
|
|
496
497
|
|
|
497
|
-
|
|
498
|
-
|
|
498
|
+
const sessions = ctx.get('sessions');
|
|
499
|
+
if (!sessions || typeof sessions.open !== 'function') throw new Error('[dsh-plugin-rollout-scout] Missing DSH session navigation service.');
|
|
499
500
|
|
|
500
501
|
const I18N_NS = 'dsh-plugin-rollout-scout';
|
|
501
502
|
const I18N = {
|
package/lib/index.js
CHANGED
|
@@ -1192,20 +1192,7 @@ function pump(ctx) {
|
|
|
1192
1192
|
state.attempts.unshift(attempt);
|
|
1193
1193
|
trimHistory();
|
|
1194
1194
|
const task = launch(ctx, attempt).catch((error) => {
|
|
1195
|
-
|
|
1196
|
-
if (attempt.stopReason === null) attempt.error = message;
|
|
1197
|
-
// Only a launch that never reached 'streaming' counts towards the
|
|
1198
|
-
// breaker: a failure after the turn started is the probe's problem,
|
|
1199
|
-
// not a sign that launching itself is broken.
|
|
1200
|
-
if (!attempt.streamed && attempt.stopReason === null) {
|
|
1201
|
-
state.launchFailures += 1;
|
|
1202
|
-
if (state.launchFailures >= LAUNCH_FAILURE_LIMIT) {
|
|
1203
|
-
state.running = false;
|
|
1204
|
-
state.paused = true;
|
|
1205
|
-
state.note = 'launch-failed';
|
|
1206
|
-
state.lastError = message;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1195
|
+
if (attempt.stopReason === null) recordProbeFailure(attempt, error);
|
|
1209
1196
|
finish(ctx, attempt);
|
|
1210
1197
|
}).finally(() => {
|
|
1211
1198
|
attempt.launching = false;
|
|
@@ -1314,7 +1301,7 @@ async function launch(ctx, attempt) {
|
|
|
1314
1301
|
const handle = await ctx.agents.create({
|
|
1315
1302
|
sessionId,
|
|
1316
1303
|
meta: { cwd: workspace.path },
|
|
1317
|
-
agentOptions:
|
|
1304
|
+
agentOptions: selection,
|
|
1318
1305
|
signal: attempt.launchController.signal,
|
|
1319
1306
|
setup: (agentCtx) => {
|
|
1320
1307
|
if (installModelSelection) {
|
|
@@ -1322,6 +1309,11 @@ async function launch(ctx, attempt) {
|
|
|
1322
1309
|
}
|
|
1323
1310
|
// Scoped: only this agent's events reach this listener.
|
|
1324
1311
|
agentCtx.on('session/event', (session, event) => onSessionEvent(ctx, attempt, event));
|
|
1312
|
+
// DSH 0.1.5 keeps chunks transient instead of persisting assistant/chunk.
|
|
1313
|
+
// Subscribe before publication so even a fast first chunk is observed.
|
|
1314
|
+
agentCtx.on('agent/assistant-stream', ({ frame }) => {
|
|
1315
|
+
if (frame?.type === 'chunk') onAssistantChunk(ctx, attempt, frame.chunk, 'agent');
|
|
1316
|
+
});
|
|
1325
1317
|
},
|
|
1326
1318
|
});
|
|
1327
1319
|
attempt.handle = handle;
|
|
@@ -1340,51 +1332,85 @@ async function launch(ctx, attempt) {
|
|
|
1340
1332
|
}
|
|
1341
1333
|
|
|
1342
1334
|
attempt.execution = 'running';
|
|
1343
|
-
attempt.streamed = true;
|
|
1344
|
-
// One probe that got as far as its first turn clears the breaker.
|
|
1345
|
-
state.launchFailures = 0;
|
|
1346
1335
|
titleAttempt(ctx, attempt, 'probe');
|
|
1347
1336
|
attempt.promptSentAt = Date.now();
|
|
1348
1337
|
armWatchdog(ctx, attempt);
|
|
1349
1338
|
handle.agent.followup(probeMessage(config.prompt));
|
|
1350
1339
|
}
|
|
1351
1340
|
|
|
1352
|
-
function
|
|
1341
|
+
function onAssistantChunk(ctx, attempt, chunk, source) {
|
|
1353
1342
|
if (isEnded(attempt)) return;
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
attempt.chunkCount = (attempt.chunkCount || 0) + 1;
|
|
1366
|
-
const elapsedSec = (now - attempt.firstChunkAt) / 1000;
|
|
1367
|
-
if (attempt.chunkCount >= 8 && elapsedSec >= 0.4) {
|
|
1368
|
-
attempt.tps = Math.round((attempt.chunkCount / elapsedSec) * 10) / 10;
|
|
1369
|
-
}
|
|
1370
|
-
if (attempt.lastChunkAt !== null && text.length > 0
|
|
1371
|
-
&& now - attempt.lastChunkAt >= PAUSE_MS
|
|
1372
|
-
&& attempt.burstChars >= BURST_MIN_CHARS) {
|
|
1373
|
-
attempt.pauses += 1;
|
|
1374
|
-
attempt.burstChars = 0;
|
|
1343
|
+
// Some transitional hosts may expose both feeds. Consume exactly one so
|
|
1344
|
+
// scores, timing and paragraph counts cannot double-count the same text.
|
|
1345
|
+
if (attempt.chunkSource !== undefined && attempt.chunkSource !== source) return;
|
|
1346
|
+
attempt.chunkSource = source;
|
|
1347
|
+
if (chunk.type === 'reasoning-delta') {
|
|
1348
|
+
const now = Date.now();
|
|
1349
|
+
const text = chunk.text || '';
|
|
1350
|
+
if (attempt.firstChunkAt === null) {
|
|
1351
|
+
attempt.firstChunkAt = now;
|
|
1352
|
+
if (attempt.promptSentAt !== null) {
|
|
1353
|
+
attempt.ttft = Math.round((now - attempt.promptSentAt) / 100) / 10;
|
|
1375
1354
|
}
|
|
1376
|
-
attempt.lastChunkAt = now;
|
|
1377
|
-
attempt.burstChars += text.length;
|
|
1378
|
-
attempt.reasoning += text;
|
|
1379
|
-
evaluate(ctx, attempt);
|
|
1380
1355
|
}
|
|
1356
|
+
attempt.chunkCount = (attempt.chunkCount || 0) + 1;
|
|
1357
|
+
const elapsedSec = (now - attempt.firstChunkAt) / 1000;
|
|
1358
|
+
if (attempt.chunkCount >= 8 && elapsedSec >= 0.4) {
|
|
1359
|
+
attempt.tps = Math.round((attempt.chunkCount / elapsedSec) * 10) / 10;
|
|
1360
|
+
}
|
|
1361
|
+
if (attempt.lastChunkAt !== null && text.length > 0
|
|
1362
|
+
&& now - attempt.lastChunkAt >= PAUSE_MS
|
|
1363
|
+
&& attempt.burstChars >= BURST_MIN_CHARS) {
|
|
1364
|
+
attempt.pauses += 1;
|
|
1365
|
+
attempt.burstChars = 0;
|
|
1366
|
+
}
|
|
1367
|
+
attempt.lastChunkAt = now;
|
|
1368
|
+
attempt.burstChars += text.length;
|
|
1369
|
+
attempt.reasoning += text;
|
|
1370
|
+
evaluate(ctx, attempt);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function onSessionEvent(ctx, attempt, event) {
|
|
1375
|
+
if (isEnded(attempt)) return;
|
|
1376
|
+
if (event.type === 'assistant/chunk') {
|
|
1377
|
+
onAssistantChunk(ctx, attempt, event.data.chunk, 'session');
|
|
1381
1378
|
return;
|
|
1382
1379
|
}
|
|
1383
1380
|
if (event.type === 'turn/end') {
|
|
1381
|
+
// Current DSH reports request/model failures as terminal session events,
|
|
1382
|
+
// after create() and followup() have succeeded. Treating those as normal
|
|
1383
|
+
// empty replies misclassifies them and can launch replacements forever.
|
|
1384
|
+
if (attempt.stopReason === null) {
|
|
1385
|
+
if (event.data?.reason?.kind === 'error') {
|
|
1386
|
+
recordProbeFailure(attempt, event.data.reason.error);
|
|
1387
|
+
} else if (!attempt.error) {
|
|
1388
|
+
state.launchFailures = 0;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1384
1391
|
finish(ctx, attempt);
|
|
1385
1392
|
}
|
|
1386
1393
|
}
|
|
1387
1394
|
|
|
1395
|
+
function recordProbeFailure(attempt, error) {
|
|
1396
|
+
attempt.error = error?.message ?? String(error);
|
|
1397
|
+
if (!retained(attempt)) {
|
|
1398
|
+
attempt.verdict = 'unknown';
|
|
1399
|
+
attempt.reason = 'error';
|
|
1400
|
+
attempt.discard = 'none';
|
|
1401
|
+
resetFadeGrace(attempt);
|
|
1402
|
+
}
|
|
1403
|
+
if (attempt.failureCounted) return;
|
|
1404
|
+
attempt.failureCounted = true;
|
|
1405
|
+
state.launchFailures += 1;
|
|
1406
|
+
if (state.launchFailures >= LAUNCH_FAILURE_LIMIT) {
|
|
1407
|
+
state.running = false;
|
|
1408
|
+
state.paused = true;
|
|
1409
|
+
state.note = 'launch-failed';
|
|
1410
|
+
state.lastError = attempt.error;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1388
1414
|
/**
|
|
1389
1415
|
* The fade grace has one owner and three derived phases:
|
|
1390
1416
|
* idle — no timer, deadline or saved remainder
|
|
@@ -2115,7 +2141,7 @@ function finish(ctx, attempt) {
|
|
|
2115
2141
|
attempt.endedAt = Date.now();
|
|
2116
2142
|
clearWatchdog(attempt);
|
|
2117
2143
|
clearCancelTimer(attempt);
|
|
2118
|
-
if (attempt.stopReason === null) {
|
|
2144
|
+
if (attempt.stopReason === null && !attempt.error) {
|
|
2119
2145
|
evaluate(ctx, attempt, true);
|
|
2120
2146
|
}
|
|
2121
2147
|
if (attempt.verdict === null) {
|
|
@@ -2169,7 +2195,7 @@ async function removeSessionLog(ctx, sessionId, headers, cwd) {
|
|
|
2169
2195
|
// computation over `{ id, cwd }`. Depending on it alone left behind the log
|
|
2170
2196
|
// of any session it had not caught up with — and a log whose workspace slot
|
|
2171
2197
|
// is gone is an ungrouped sidebar row with no Delete in its menu.
|
|
2172
|
-
const header = headers.find((h) => h.id === sessionId)
|
|
2198
|
+
const header = headers.map((snapshot) => snapshot.header ?? snapshot).find((h) => h.id === sessionId)
|
|
2173
2199
|
?? (cwd === undefined ? undefined : { id: sessionId, cwd });
|
|
2174
2200
|
if (header === undefined) throw new Error(`找不到会话记录 ${sessionId} / session record not found`);
|
|
2175
2201
|
let location;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-rollout-scout",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Detect which conversation model your DeepSeek Harness account is served: score probe conversations' live chain-of-thought, cancel old-model probes in seconds, keep confident catches.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -40,7 +40,13 @@
|
|
|
40
40
|
},
|
|
41
41
|
"client": {
|
|
42
42
|
"platform": "web",
|
|
43
|
-
"inject": [
|
|
43
|
+
"inject": [
|
|
44
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
45
|
+
"@deepseek-ai/dsh-client-locale",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
48
|
+
"@deepseek-ai/dsh-client-modules"
|
|
49
|
+
]
|
|
44
50
|
}
|
|
45
51
|
},
|
|
46
52
|
"scripts": {
|
|
@@ -48,7 +54,17 @@
|
|
|
48
54
|
"prepare": "node _wrap-client.mjs",
|
|
49
55
|
"prepack": "node _wrap-client.mjs",
|
|
50
56
|
"pretest": "node _wrap-client.mjs",
|
|
51
|
-
"test": "node test/classifier.test.mjs && node test/route.test.mjs && node test/pump.test.mjs && node test/delete.test.mjs && node test/lifecycle.test.mjs && node test/ownership.test.mjs && node test/manifest.test.mjs && node test/manifest-concurrency.test.mjs && node test/manifest-process.test.mjs && node test/history.test.mjs && node test/state-machine.test.mjs && node test/client-review.test.mjs && node test/client-anchor.test.mjs && node test/client-css.test.mjs",
|
|
52
|
-
"check:client": "node _wrap-client.mjs && git diff --exit-code -- lib/client.js"
|
|
57
|
+
"test": "node test/classifier.test.mjs && node test/route.test.mjs && node test/pump.test.mjs && node test/delete.test.mjs && node test/lifecycle.test.mjs && node test/ownership.test.mjs && node test/manifest.test.mjs && node test/manifest-concurrency.test.mjs && node test/manifest-process.test.mjs && node test/history.test.mjs && node test/state-machine.test.mjs && node test/client-review.test.mjs && node test/client-anchor.test.mjs && node test/client-css.test.mjs && node --test test/client-loading.test.mjs && node --test test/probe-errors.test.mjs && node --test test/stream-events.test.mjs",
|
|
58
|
+
"check:client": "node _wrap-client.mjs && git diff --exit-code -- lib/client.js",
|
|
59
|
+
"check:package": "node scripts/check-package.mjs"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
63
|
+
"jsdom": "^29.1.1",
|
|
64
|
+
"react": "^18.3.1",
|
|
65
|
+
"react-dom": "^18.3.1"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"dsh": ">=0.1.5-rc.2 <0.1.6-0"
|
|
53
69
|
}
|
|
54
70
|
}
|
package/plugin.client.js
CHANGED
|
@@ -469,13 +469,14 @@ const CSS = [
|
|
|
469
469
|
].join('');
|
|
470
470
|
|
|
471
471
|
return {
|
|
472
|
+
inject: ['slots', 'sessions', 'locale'],
|
|
472
473
|
apply(ctx) {
|
|
473
474
|
const slots = ctx.get('slots');
|
|
474
|
-
if (slots === undefined)
|
|
475
|
+
if (slots === undefined) throw new Error('[dsh-plugin-rollout-scout] Missing DSH slots service; check client dependencies.');
|
|
475
476
|
ctx.effect(function () { return styles.insert(CSS); });
|
|
476
477
|
|
|
477
|
-
|
|
478
|
-
|
|
478
|
+
const sessions = ctx.get('sessions');
|
|
479
|
+
if (!sessions || typeof sessions.open !== 'function') throw new Error('[dsh-plugin-rollout-scout] Missing DSH session navigation service.');
|
|
479
480
|
|
|
480
481
|
const I18N_NS = 'dsh-plugin-rollout-scout';
|
|
481
482
|
const I18N = {
|