dsh-plugin-bridge 0.2.10 → 0.2.11
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.md +1 -0
- package/README.zh.md +1 -0
- package/lib/cli.js +3 -2
- package/lib/command.js +6 -3
- package/lib/migrate.d.ts +11 -5
- package/lib/migrate.js +23 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-plugin-bridge)
|
|
8
9
|
[](https://github.com/Totoro-qaq/dsh-plugin-bridge/actions/workflows/ci.yml)
|
|
9
10
|
[](LICENSE)
|
|
10
11
|
[](package.json)
|
package/README.zh.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-plugin-bridge)
|
|
8
9
|
[](https://github.com/Totoro-qaq/dsh-plugin-bridge/actions/workflows/ci.yml)
|
|
9
10
|
[](LICENSE)
|
|
10
11
|
[](package.json)
|
package/lib/cli.js
CHANGED
|
@@ -279,6 +279,7 @@ async function main(argv) {
|
|
|
279
279
|
const source = await findSession(rpc, sessionId).catch(() => undefined);
|
|
280
280
|
const result = await executeMigration(rpc, {
|
|
281
281
|
sessionId,
|
|
282
|
+
sourceSession: source,
|
|
282
283
|
to,
|
|
283
284
|
summary,
|
|
284
285
|
goalRounds: num(args, 'goal-rounds'),
|
|
@@ -322,9 +323,9 @@ async function main(argv) {
|
|
|
322
323
|
...(num(args, 'worker-timeout') === undefined ? {} : { workerTimeoutMs: num(args, 'worker-timeout') }),
|
|
323
324
|
onProgress: progress,
|
|
324
325
|
});
|
|
325
|
-
const source = await findSession(rpc, sessionId).catch(() => undefined);
|
|
326
326
|
const result = await executeMigration(rpc, {
|
|
327
327
|
sessionId,
|
|
328
|
+
sourceSession: preview.sourceSession,
|
|
328
329
|
to,
|
|
329
330
|
summary: preview.summary,
|
|
330
331
|
lang: preview.lang,
|
|
@@ -332,7 +333,7 @@ async function main(argv) {
|
|
|
332
333
|
inject: injectOf(args),
|
|
333
334
|
kickoff: !bool(args, 'no-kickoff'),
|
|
334
335
|
autoContinue: bool(args, 'continue'),
|
|
335
|
-
title: str(args, 'title') ?? migratedTitle(titleOf(
|
|
336
|
+
title: str(args, 'title') ?? migratedTitle(titleOf(preview.sourceSession), to),
|
|
336
337
|
onProgress: progress,
|
|
337
338
|
});
|
|
338
339
|
out({ ...result, summary: preview.summary, source: preview.source }, () => `已迁移到 ${result.agentPreset}:${result.sessionId}\n\n${preview.summary}\n`);
|
package/lib/command.js
CHANGED
|
@@ -171,10 +171,12 @@ export function createBridgeCommand(deps) {
|
|
|
171
171
|
const rpc = deps.rpcFor(invocation.signal);
|
|
172
172
|
const config = deps.config;
|
|
173
173
|
let presets;
|
|
174
|
+
let sourceSession;
|
|
174
175
|
let current;
|
|
175
176
|
try {
|
|
176
177
|
presets = await listPresets(rpc);
|
|
177
|
-
|
|
178
|
+
sourceSession = await findSession(rpc, sessionId);
|
|
179
|
+
current = sourceSession?.agentPreset;
|
|
178
180
|
}
|
|
179
181
|
catch (error) {
|
|
180
182
|
return { kind: 'error', text: describe(error) };
|
|
@@ -258,11 +260,11 @@ export function createBridgeCommand(deps) {
|
|
|
258
260
|
};
|
|
259
261
|
}
|
|
260
262
|
try {
|
|
261
|
-
const
|
|
262
|
-
const sourceTitle = titleOf(row);
|
|
263
|
+
const sourceTitle = titleOf(sourceSession);
|
|
263
264
|
const targetTitle = sourceTitle ? migratedTitle(sourceTitle, target) : (runLang === 'en' ? `Migrated to ${target}` : migratedTitle(sourceTitle, target));
|
|
264
265
|
const result = await executeMigration(rpc, {
|
|
265
266
|
sessionId,
|
|
267
|
+
sourceSession,
|
|
266
268
|
to: target,
|
|
267
269
|
summary,
|
|
268
270
|
goalRounds: parsed.goalRounds ?? config.goalRounds,
|
|
@@ -311,6 +313,7 @@ export function createBridgeCommand(deps) {
|
|
|
311
313
|
try {
|
|
312
314
|
const preview = await previewMigration(rpc, {
|
|
313
315
|
sessionId,
|
|
316
|
+
sourceSession,
|
|
314
317
|
tier: parsed.tier ?? config.modelTier,
|
|
315
318
|
sourceCharBudget: config.sourceCharBudget,
|
|
316
319
|
summaryCharBudget: config.summaryCharBudget,
|
package/lib/migrate.d.ts
CHANGED
|
@@ -69,16 +69,18 @@ export declare function resolveWorkerModel(rpc: Rpc, sessionId: string, tier: Mo
|
|
|
69
69
|
export declare function resolveWorkerPreset(rpc: Rpc): Promise<string | undefined>;
|
|
70
70
|
export interface WaitOptions {
|
|
71
71
|
timeoutMs?: number;
|
|
72
|
-
/**
|
|
72
|
+
/** 等待新 `turn/start` 的宽限期;超过仍未出现就按未启动处理。 */
|
|
73
73
|
startGraceMs?: number;
|
|
74
74
|
pollMs?: number;
|
|
75
|
+
/** 只观察这个事件序号之后的新一轮;worker 新建后通常为 0。 */
|
|
76
|
+
afterSeq?: number;
|
|
75
77
|
}
|
|
76
78
|
/**
|
|
77
|
-
*
|
|
79
|
+
* 等一个会话的新一轮写入 `turn/end`。
|
|
78
80
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
81
|
+
* `session.list` 是全局列表,拿它每两秒轮询一个 worker 会把会话总量放大成
|
|
82
|
+
* O(会话数 × 轮询次数)。`session.history` 则只读目标会话;用 prompt 前的事件
|
|
83
|
+
* 水位隔开旧轮次后,`turn/start` / `turn/end` 也比易过期的 running 快照更可靠。
|
|
82
84
|
*/
|
|
83
85
|
export declare function waitIdle(rpc: Rpc, sessionId: string, options?: WaitOptions): Promise<{
|
|
84
86
|
idle: boolean;
|
|
@@ -93,6 +95,8 @@ export declare function foldedHistory(rpc: Rpc, sessionId: string, options?: {
|
|
|
93
95
|
export declare function lastAssistantText(rpc: Rpc, sessionId: string): Promise<string>;
|
|
94
96
|
export interface PreviewOptions {
|
|
95
97
|
sessionId: string;
|
|
98
|
+
/** 同一命令已经读取过的源会话行,避免重复扫描全局列表。 */
|
|
99
|
+
sourceSession?: SessionRow;
|
|
96
100
|
tier?: ModelTier;
|
|
97
101
|
provider?: string;
|
|
98
102
|
model?: string;
|
|
@@ -125,6 +129,8 @@ export interface PreviewResult {
|
|
|
125
129
|
export declare function previewMigration(rpc: Rpc, options: PreviewOptions): Promise<PreviewResult>;
|
|
126
130
|
export interface MigrateOptions {
|
|
127
131
|
sessionId: string;
|
|
132
|
+
/** 同一流程已经读取过的源会话行,避免重复扫描全局列表。 */
|
|
133
|
+
sourceSession?: SessionRow;
|
|
128
134
|
to: string;
|
|
129
135
|
summary: string;
|
|
130
136
|
lang?: 'zh' | 'en';
|
package/lib/migrate.js
CHANGED
|
@@ -95,31 +95,40 @@ export async function resolveWorkerPreset(rpc) {
|
|
|
95
95
|
return presets.find((preset) => preset.isDefault)?.id;
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
98
|
+
* 等一个会话的新一轮写入 `turn/end`。
|
|
99
99
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
100
|
+
* `session.list` 是全局列表,拿它每两秒轮询一个 worker 会把会话总量放大成
|
|
101
|
+
* O(会话数 × 轮询次数)。`session.history` 则只读目标会话;用 prompt 前的事件
|
|
102
|
+
* 水位隔开旧轮次后,`turn/start` / `turn/end` 也比易过期的 running 快照更可靠。
|
|
103
103
|
*/
|
|
104
104
|
export async function waitIdle(rpc, sessionId, options = {}) {
|
|
105
105
|
const pollMs = options.pollMs ?? 2000;
|
|
106
106
|
const deadline = Date.now() + (options.timeoutMs ?? 360_000);
|
|
107
107
|
const startBy = Date.now() + (options.startGraceMs ?? 25_000);
|
|
108
|
+
const afterSeq = options.afterSeq ?? 0;
|
|
108
109
|
let started = false;
|
|
109
110
|
while (Date.now() < deadline) {
|
|
110
111
|
await sleep(pollMs);
|
|
111
|
-
const
|
|
112
|
-
|
|
112
|
+
const events = await tailSessionEvents(rpc, sessionId).catch(() => []);
|
|
113
|
+
const fresh = events.filter((event) => typeof event.seq === 'number' && event.seq > afterSeq);
|
|
114
|
+
if (fresh.some((event) => event.type === 'turn/start'))
|
|
113
115
|
started = true;
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
if (started)
|
|
116
|
+
if (fresh.some((event) => event.type === 'turn/end'))
|
|
117
117
|
return { idle: true, started: true };
|
|
118
|
-
if (Date.now() > startBy)
|
|
118
|
+
if (!started && Date.now() > startBy)
|
|
119
119
|
return { idle: true, started: false };
|
|
120
120
|
}
|
|
121
121
|
return { idle: false, started };
|
|
122
122
|
}
|
|
123
|
+
/** 读取单个会话的尾页;不触发全局 session.list 扫描。 */
|
|
124
|
+
async function tailSessionEvents(rpc, sessionId) {
|
|
125
|
+
const res = await rpc('session.history', { sessionId, maxMessages: 2 }, 60_000);
|
|
126
|
+
return (res.events ?? []).map((entry) => entry.event);
|
|
127
|
+
}
|
|
128
|
+
async function latestSessionSeq(rpc, sessionId) {
|
|
129
|
+
const events = await tailSessionEvents(rpc, sessionId);
|
|
130
|
+
return events.reduce((max, event) => (typeof event.seq === 'number' && event.seq > max ? event.seq : max), 0);
|
|
131
|
+
}
|
|
123
132
|
/** 拉取并折叠会话历史(按需翻页)。 */
|
|
124
133
|
export async function foldedHistory(rpc, sessionId, options = {}) {
|
|
125
134
|
const pageMessages = options.pageMessages ?? HISTORY_PAGE_MESSAGES;
|
|
@@ -149,7 +158,7 @@ export async function lastAssistantText(rpc, sessionId) {
|
|
|
149
158
|
/** 生成交接摘要:取材 → 起临时工人 → 收摘要 → 归档工人。 */
|
|
150
159
|
export async function previewMigration(rpc, options) {
|
|
151
160
|
const progress = options.onProgress ?? (() => { });
|
|
152
|
-
const sourceSession = await findSession(rpc, options.sessionId);
|
|
161
|
+
const sourceSession = options.sourceSession ?? await findSession(rpc, options.sessionId);
|
|
153
162
|
progress('拉取并折叠会话历史…');
|
|
154
163
|
const messages = await foldedHistory(rpc, options.sessionId);
|
|
155
164
|
const source = buildBridgeSource(messages, { sourceCharBudget: options.sourceCharBudget });
|
|
@@ -179,6 +188,7 @@ export async function previewMigration(rpc, options) {
|
|
|
179
188
|
});
|
|
180
189
|
}
|
|
181
190
|
const instruction = buildBridgeInstruction(lang, { summaryCharBudget: options.summaryCharBudget });
|
|
191
|
+
const workerBaselineSeq = await latestSessionSeq(rpc, worker.sessionId).catch(() => 0);
|
|
182
192
|
await rpc('session.prompt', {
|
|
183
193
|
sessionId: worker.sessionId,
|
|
184
194
|
mode: 'queue',
|
|
@@ -187,6 +197,7 @@ export async function previewMigration(rpc, options) {
|
|
|
187
197
|
progress('等待摘要…');
|
|
188
198
|
const settled = await waitIdle(rpc, worker.sessionId, {
|
|
189
199
|
timeoutMs: options.workerTimeoutMs ?? 360_000,
|
|
200
|
+
afterSeq: workerBaselineSeq,
|
|
190
201
|
...(options.pollMs === undefined ? {} : { pollMs: options.pollMs, startGraceMs: options.pollMs * 6 }),
|
|
191
202
|
});
|
|
192
203
|
if (!settled.idle) {
|
|
@@ -280,7 +291,7 @@ export async function executeMigration(rpc, options) {
|
|
|
280
291
|
const summary = options.summary.trim();
|
|
281
292
|
if (!summary)
|
|
282
293
|
throw new RpcError('bridge.migrate', 'empty-summary', '摘要为空,拒绝迁移。');
|
|
283
|
-
const sourceSession = await findSession(rpc, options.sessionId);
|
|
294
|
+
const sourceSession = options.sourceSession ?? await findSession(rpc, options.sessionId);
|
|
284
295
|
const where = await placement(rpc, sourceSession, options.sessionId);
|
|
285
296
|
let sourceModel;
|
|
286
297
|
try {
|
package/package.json
CHANGED