dsh-session-bridge 0.2.0 → 0.2.1
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/dsh.plugin.json +1 -1
- package/lib/index.js +39 -13
- package/package.json +1 -1
- package/src/core.ts +35 -6
- package/src/tools.ts +8 -7
package/dsh.plugin.json
CHANGED
package/lib/index.js
CHANGED
|
@@ -9098,6 +9098,29 @@ Service.init;
|
|
|
9098
9098
|
* dsh-session-bridge core: 会话创建 / 发消息 / 等待回复 / 读取 / 查找。
|
|
9099
9099
|
* 所有逻辑跑在插件宿主上下文(rootCtx),操作的是 DSH 真实的会话与 agent 注册表。
|
|
9100
9100
|
*/
|
|
9101
|
+
/**
|
|
9102
|
+
* 兼容读取不同 dsh-session 版本上的会话事件:
|
|
9103
|
+
* - 旧版 `Session` 暴露 `get events(): readonly SessionEvent[]`;
|
|
9104
|
+
* - 新版(如宿主实际运行的 0.1.2-rc.x)将 `get events` 改为
|
|
9105
|
+
* `snapshotEvents(fromSeq?, toSeqExclusive?)` 方法——`.events` 直接读取会
|
|
9106
|
+
* 得到 `undefined`,对 for...of 迭代即抛 `events is not iterable`。
|
|
9107
|
+
* 两者都读不到(或 session 不存在)时回退为空数组,绝不抛迭代错误。
|
|
9108
|
+
*/
|
|
9109
|
+
function sessionEvents(session) {
|
|
9110
|
+
const s = session;
|
|
9111
|
+
if (s === null || s === void 0) return [];
|
|
9112
|
+
if (Array.isArray(s.events)) return s.events;
|
|
9113
|
+
if (typeof s.snapshotEvents === "function") try {
|
|
9114
|
+
return s.snapshotEvents();
|
|
9115
|
+
} catch {
|
|
9116
|
+
return [];
|
|
9117
|
+
}
|
|
9118
|
+
return [];
|
|
9119
|
+
}
|
|
9120
|
+
/** 规约任意"事件列表"值为数组(非数组 → []),避免 events is not iterable。 */
|
|
9121
|
+
function asEventList(events) {
|
|
9122
|
+
return Array.isArray(events) ? events : [];
|
|
9123
|
+
}
|
|
9101
9124
|
/** agent.followup / steer 接受的用户消息值。 */
|
|
9102
9125
|
function userMessage(text) {
|
|
9103
9126
|
return {
|
|
@@ -9141,8 +9164,9 @@ function toolNamesOf(toolCalls) {
|
|
|
9141
9164
|
}
|
|
9142
9165
|
/** 折叠会话事件日志为可读消息行(跳过非 user 来源与空消息)。 */
|
|
9143
9166
|
function foldMessages(events) {
|
|
9167
|
+
const list = asEventList(events);
|
|
9144
9168
|
const rows = [];
|
|
9145
|
-
for (const event of
|
|
9169
|
+
for (const event of list) if (event.type === "user/message") {
|
|
9146
9170
|
const source = event.data.source;
|
|
9147
9171
|
if (source !== void 0 && source.kind !== "user") continue;
|
|
9148
9172
|
const text = blockText(event.data.content);
|
|
@@ -9179,8 +9203,9 @@ function foldMessages(events) {
|
|
|
9179
9203
|
* 无标题事件时回落为第一条用户消息(截断 80 字符)。
|
|
9180
9204
|
*/
|
|
9181
9205
|
function titleOf(events) {
|
|
9206
|
+
const list = asEventList(events);
|
|
9182
9207
|
let title;
|
|
9183
|
-
for (const event of
|
|
9208
|
+
for (const event of list) {
|
|
9184
9209
|
const raw = event;
|
|
9185
9210
|
if (raw.type === "session/title") {
|
|
9186
9211
|
const t = raw.data.title;
|
|
@@ -9188,7 +9213,7 @@ function titleOf(events) {
|
|
|
9188
9213
|
}
|
|
9189
9214
|
}
|
|
9190
9215
|
if (title !== void 0) return title;
|
|
9191
|
-
for (const event of
|
|
9216
|
+
for (const event of list) if (event.type === "user/message") {
|
|
9192
9217
|
const source = event.data.source;
|
|
9193
9218
|
if (source !== void 0 && source.kind !== "user") continue;
|
|
9194
9219
|
const text = blockText(event.data.content);
|
|
@@ -9200,8 +9225,9 @@ function titleOf(events) {
|
|
|
9200
9225
|
}
|
|
9201
9226
|
/** 日志最大事件序号。 */
|
|
9202
9227
|
function maxSeq(events) {
|
|
9228
|
+
const list = asEventList(events);
|
|
9203
9229
|
let m = -1;
|
|
9204
|
-
for (const event of
|
|
9230
|
+
for (const event of list) if (event.seq > m) m = event.seq;
|
|
9205
9231
|
return m;
|
|
9206
9232
|
}
|
|
9207
9233
|
function sleep(ms) {
|
|
@@ -9228,7 +9254,7 @@ async function waitForReply(opts) {
|
|
|
9228
9254
|
let turnEnded = false;
|
|
9229
9255
|
for (;;) {
|
|
9230
9256
|
if (opts.signal !== void 0 && opts.signal.aborted) break;
|
|
9231
|
-
const events = opts.session
|
|
9257
|
+
const events = sessionEvents(opts.session);
|
|
9232
9258
|
for (const row of foldMessages(events)) {
|
|
9233
9259
|
if (row.seq <= opts.baselineSeq || row.role !== "assistant") continue;
|
|
9234
9260
|
if (latest === null || row.seq > latest.seq) latest = row;
|
|
@@ -9309,7 +9335,7 @@ async function attachSessionToWorkspace(ctx, sessionId, cwd) {
|
|
|
9309
9335
|
* 纯读,无副作用。用于 `session_bridge_status`。
|
|
9310
9336
|
*/
|
|
9311
9337
|
function statusSnapshot(ctx, agent) {
|
|
9312
|
-
const events = agent.session
|
|
9338
|
+
const events = sessionEvents(agent.session);
|
|
9313
9339
|
const rows = foldMessages(events);
|
|
9314
9340
|
let lastActivityAt = null;
|
|
9315
9341
|
let lastTurn = 0;
|
|
@@ -13633,7 +13659,7 @@ function registerCreate(env) {
|
|
|
13633
13659
|
});
|
|
13634
13660
|
let reply;
|
|
13635
13661
|
if (typeof args.prompt === "string" && args.prompt.trim() !== "") {
|
|
13636
|
-
const baseline = maxSeq(agent.session
|
|
13662
|
+
const baseline = maxSeq(sessionEvents(agent.session));
|
|
13637
13663
|
agent.followup(userMessage(args.prompt.trim()));
|
|
13638
13664
|
env.registry.touch(sessionId);
|
|
13639
13665
|
if (args.waitForReply === true) reply = await maybeWait(env, agent.session, baseline, args, exec.signal);
|
|
@@ -13701,7 +13727,7 @@ function registerSend(env) {
|
|
|
13701
13727
|
if (typeof args.message !== "string" || args.message.trim() === "") throw new Error("invalid message: expected a non-empty string");
|
|
13702
13728
|
const agent = liveAgent(env, args.sessionId);
|
|
13703
13729
|
if (agent === void 0) throw new Error("session " + JSON.stringify(args.sessionId) + " is not live — call session_bridge_resume first to bring it online (or session_bridge_find to locate it)");
|
|
13704
|
-
const baseline = maxSeq(agent.session
|
|
13730
|
+
const baseline = maxSeq(sessionEvents(agent.session));
|
|
13705
13731
|
if (args.mode === "steer") agent.steer(userMessage(args.message.trim()));
|
|
13706
13732
|
else agent.followup(userMessage(args.message.trim()));
|
|
13707
13733
|
const headerCwd = agent.session.header.cwd;
|
|
@@ -13764,7 +13790,7 @@ function registerResume(env) {
|
|
|
13764
13790
|
sessionId: args.sessionId,
|
|
13765
13791
|
alreadyLive: true,
|
|
13766
13792
|
...existing.session.header.cwd === void 0 ? {} : { cwd: existing.session.header.cwd },
|
|
13767
|
-
title: titleOf(existing.session
|
|
13793
|
+
title: titleOf(sessionEvents(existing.session)) ?? null,
|
|
13768
13794
|
running: existing.status === "running"
|
|
13769
13795
|
});
|
|
13770
13796
|
}
|
|
@@ -13797,7 +13823,7 @@ function registerResume(env) {
|
|
|
13797
13823
|
sessionId: args.sessionId,
|
|
13798
13824
|
resumed: true,
|
|
13799
13825
|
...headerCwd === void 0 ? {} : { cwd: headerCwd },
|
|
13800
|
-
title: titleOf(handle.agent.session
|
|
13826
|
+
title: titleOf(sessionEvents(handle.agent.session)) ?? null
|
|
13801
13827
|
});
|
|
13802
13828
|
}
|
|
13803
13829
|
}));
|
|
@@ -13855,7 +13881,7 @@ function registerWait(env) {
|
|
|
13855
13881
|
if (typeof args.sinceSeq === "number" && Number.isInteger(args.sinceSeq) && args.sinceSeq >= 0) baseline = args.sinceSeq;
|
|
13856
13882
|
else {
|
|
13857
13883
|
let lastText = -1;
|
|
13858
|
-
for (const row of foldMessages(agent.session
|
|
13884
|
+
for (const row of foldMessages(sessionEvents(agent.session))) if (row.text !== void 0) lastText = row.seq;
|
|
13859
13885
|
baseline = lastText;
|
|
13860
13886
|
}
|
|
13861
13887
|
const result = await waitForReply({
|
|
@@ -13934,7 +13960,7 @@ function registerRead(env) {
|
|
|
13934
13960
|
let live;
|
|
13935
13961
|
let cwd;
|
|
13936
13962
|
if (agent !== void 0) {
|
|
13937
|
-
events = agent.session
|
|
13963
|
+
events = sessionEvents(agent.session);
|
|
13938
13964
|
live = true;
|
|
13939
13965
|
cwd = agent.session.header.cwd;
|
|
13940
13966
|
} else {
|
|
@@ -14051,7 +14077,7 @@ function registerFind(env) {
|
|
|
14051
14077
|
const wsId = wsById.get(agent.id) ?? workspaceByPath(env.ctx, headerCwd);
|
|
14052
14078
|
items.push({
|
|
14053
14079
|
sessionId: agent.id,
|
|
14054
|
-
title: titleOf(agent.session
|
|
14080
|
+
title: titleOf(sessionEvents(agent.session)),
|
|
14055
14081
|
...headerCwd === void 0 ? {} : { cwd: headerCwd },
|
|
14056
14082
|
...wsId === void 0 ? {} : { workspaceId: wsId },
|
|
14057
14083
|
live: true,
|
package/package.json
CHANGED
package/src/core.ts
CHANGED
|
@@ -60,6 +60,32 @@ export interface BridgeFindItem {
|
|
|
60
60
|
agentPreset?: string
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* 兼容读取不同 dsh-session 版本上的会话事件:
|
|
65
|
+
* - 旧版 `Session` 暴露 `get events(): readonly SessionEvent[]`;
|
|
66
|
+
* - 新版(如宿主实际运行的 0.1.2-rc.x)将 `get events` 改为
|
|
67
|
+
* `snapshotEvents(fromSeq?, toSeqExclusive?)` 方法——`.events` 直接读取会
|
|
68
|
+
* 得到 `undefined`,对 for...of 迭代即抛 `events is not iterable`。
|
|
69
|
+
* 两者都读不到(或 session 不存在)时回退为空数组,绝不抛迭代错误。
|
|
70
|
+
*/
|
|
71
|
+
export function sessionEvents(session: unknown): readonly SessionEvent[] {
|
|
72
|
+
const s = session as {
|
|
73
|
+
events?: readonly SessionEvent[]
|
|
74
|
+
snapshotEvents?: (fromSeq?: number, toSeqExclusive?: number) => readonly SessionEvent[]
|
|
75
|
+
} | null | undefined
|
|
76
|
+
if (s === null || s === undefined) return []
|
|
77
|
+
if (Array.isArray(s.events)) return s.events
|
|
78
|
+
if (typeof s.snapshotEvents === 'function') {
|
|
79
|
+
try { return s.snapshotEvents() } catch { return [] }
|
|
80
|
+
}
|
|
81
|
+
return []
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 规约任意"事件列表"值为数组(非数组 → []),避免 events is not iterable。 */
|
|
85
|
+
export function asEventList(events: unknown): readonly SessionEvent[] {
|
|
86
|
+
return Array.isArray(events) ? events : []
|
|
87
|
+
}
|
|
88
|
+
|
|
63
89
|
/** agent.followup / steer 接受的用户消息值。 */
|
|
64
90
|
export function userMessage(text: string): unknown {
|
|
65
91
|
return {
|
|
@@ -104,8 +130,9 @@ function toolNamesOf(toolCalls: unknown): string[] {
|
|
|
104
130
|
|
|
105
131
|
/** 折叠会话事件日志为可读消息行(跳过非 user 来源与空消息)。 */
|
|
106
132
|
export function foldMessages(events: readonly SessionEvent[]): BridgeMessageRow[] {
|
|
133
|
+
const list = asEventList(events)
|
|
107
134
|
const rows: BridgeMessageRow[] = []
|
|
108
|
-
for (const event of
|
|
135
|
+
for (const event of list) {
|
|
109
136
|
if (event.type === 'user/message') {
|
|
110
137
|
const source = (event.data as { source?: { kind?: string } }).source
|
|
111
138
|
if (source !== undefined && source.kind !== 'user') continue
|
|
@@ -134,8 +161,9 @@ export function foldMessages(events: readonly SessionEvent[]): BridgeMessageRow[
|
|
|
134
161
|
* 无标题事件时回落为第一条用户消息(截断 80 字符)。
|
|
135
162
|
*/
|
|
136
163
|
export function titleOf(events: readonly SessionEvent[]): string | undefined {
|
|
164
|
+
const list = asEventList(events)
|
|
137
165
|
let title: string | undefined
|
|
138
|
-
for (const event of
|
|
166
|
+
for (const event of list) {
|
|
139
167
|
const raw = event as unknown as { type: string; data: { title?: unknown } }
|
|
140
168
|
if (raw.type === 'session/title') {
|
|
141
169
|
const t = raw.data.title
|
|
@@ -143,7 +171,7 @@ export function titleOf(events: readonly SessionEvent[]): string | undefined {
|
|
|
143
171
|
}
|
|
144
172
|
}
|
|
145
173
|
if (title !== undefined) return title
|
|
146
|
-
for (const event of
|
|
174
|
+
for (const event of list) {
|
|
147
175
|
if (event.type === 'user/message') {
|
|
148
176
|
const source = (event.data as { source?: { kind?: string } }).source
|
|
149
177
|
if (source !== undefined && source.kind !== 'user') continue
|
|
@@ -159,8 +187,9 @@ export function titleOf(events: readonly SessionEvent[]): string | undefined {
|
|
|
159
187
|
|
|
160
188
|
/** 日志最大事件序号。 */
|
|
161
189
|
export function maxSeq(events: readonly SessionEvent[]): number {
|
|
190
|
+
const list = asEventList(events)
|
|
162
191
|
let m = -1
|
|
163
|
-
for (const event of
|
|
192
|
+
for (const event of list) if (event.seq > m) m = event.seq
|
|
164
193
|
return m
|
|
165
194
|
}
|
|
166
195
|
|
|
@@ -197,7 +226,7 @@ export async function waitForReply(opts: WaitForReplyOptions): Promise<BridgeWai
|
|
|
197
226
|
let turnEnded = false
|
|
198
227
|
for (;;) {
|
|
199
228
|
if (opts.signal !== undefined && opts.signal.aborted) break
|
|
200
|
-
const events = opts.session
|
|
229
|
+
const events = sessionEvents(opts.session)
|
|
201
230
|
for (const row of foldMessages(events)) {
|
|
202
231
|
if (row.seq <= opts.baselineSeq || row.role !== 'assistant') continue
|
|
203
232
|
if (latest === null || row.seq > latest.seq) latest = row
|
|
@@ -324,7 +353,7 @@ export interface BridgeStatusSnapshot {
|
|
|
324
353
|
* 纯读,无副作用。用于 `session_bridge_status`。
|
|
325
354
|
*/
|
|
326
355
|
export function statusSnapshot(ctx: Context, agent: LiveAgentLike): BridgeStatusSnapshot {
|
|
327
|
-
const events = agent.session
|
|
356
|
+
const events = sessionEvents(agent.session)
|
|
328
357
|
const rows = foldMessages(events)
|
|
329
358
|
let lastActivityAt: number | null = null
|
|
330
359
|
let lastTurn = 0
|
package/src/tools.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
foldMessages,
|
|
23
23
|
maxSeq,
|
|
24
24
|
resolveTargetCwd,
|
|
25
|
+
sessionEvents,
|
|
25
26
|
statusSnapshot,
|
|
26
27
|
titleOf,
|
|
27
28
|
userMessage,
|
|
@@ -236,7 +237,7 @@ function registerCreate(env: BridgeEnv): void {
|
|
|
236
237
|
|
|
237
238
|
let reply: ReturnType<typeof renderWait> | undefined
|
|
238
239
|
if (typeof args.prompt === 'string' && args.prompt.trim() !== '') {
|
|
239
|
-
const baseline = maxSeq(agent.session
|
|
240
|
+
const baseline = maxSeq(sessionEvents(agent.session))
|
|
240
241
|
agent.followup(userMessage(args.prompt.trim()))
|
|
241
242
|
env.registry.touch(sessionId)
|
|
242
243
|
if (args.waitForReply === true) {
|
|
@@ -294,7 +295,7 @@ function registerSend(env: BridgeEnv): void {
|
|
|
294
295
|
if (agent === undefined) {
|
|
295
296
|
throw new Error('session ' + JSON.stringify(args.sessionId) + ' is not live — call session_bridge_resume first to bring it online (or session_bridge_find to locate it)')
|
|
296
297
|
}
|
|
297
|
-
const baseline = maxSeq(agent.session
|
|
298
|
+
const baseline = maxSeq(sessionEvents(agent.session))
|
|
298
299
|
if (args.mode === 'steer') agent.steer(userMessage(args.message.trim()))
|
|
299
300
|
else agent.followup(userMessage(args.message.trim()))
|
|
300
301
|
const headerCwd = agent.session.header.cwd
|
|
@@ -346,7 +347,7 @@ function registerResume(env: BridgeEnv): void {
|
|
|
346
347
|
sessionId: args.sessionId,
|
|
347
348
|
alreadyLive: true,
|
|
348
349
|
...(existing.session.header.cwd === undefined ? {} : { cwd: existing.session.header.cwd }),
|
|
349
|
-
title: titleOf(existing.session
|
|
350
|
+
title: titleOf(sessionEvents(existing.session)) ?? null,
|
|
350
351
|
running: existing.status === 'running',
|
|
351
352
|
})
|
|
352
353
|
}
|
|
@@ -381,7 +382,7 @@ function registerResume(env: BridgeEnv): void {
|
|
|
381
382
|
sessionId: args.sessionId,
|
|
382
383
|
resumed: true,
|
|
383
384
|
...(headerCwd === undefined ? {} : { cwd: headerCwd }),
|
|
384
|
-
title: titleOf(handle.agent.session
|
|
385
|
+
title: titleOf(sessionEvents(handle.agent.session)) ?? null,
|
|
385
386
|
})
|
|
386
387
|
},
|
|
387
388
|
}))
|
|
@@ -432,7 +433,7 @@ function registerWait(env: BridgeEnv): void {
|
|
|
432
433
|
baseline = args.sinceSeq
|
|
433
434
|
} else {
|
|
434
435
|
let lastText = -1
|
|
435
|
-
for (const row of foldMessages(agent.session
|
|
436
|
+
for (const row of foldMessages(sessionEvents(agent.session))) {
|
|
436
437
|
if (row.text !== undefined) lastText = row.seq
|
|
437
438
|
}
|
|
438
439
|
baseline = lastText
|
|
@@ -494,7 +495,7 @@ function registerRead(env: BridgeEnv): void {
|
|
|
494
495
|
let live: boolean
|
|
495
496
|
let cwd: string | undefined
|
|
496
497
|
if (agent !== undefined) {
|
|
497
|
-
events = agent.session
|
|
498
|
+
events = sessionEvents(agent.session)
|
|
498
499
|
live = true
|
|
499
500
|
cwd = agent.session.header.cwd
|
|
500
501
|
} else {
|
|
@@ -592,7 +593,7 @@ function registerFind(env: BridgeEnv): void {
|
|
|
592
593
|
const wsId = wsById.get(agent.id) ?? workspaceByPath(env.ctx, headerCwd)
|
|
593
594
|
items.push({
|
|
594
595
|
sessionId: agent.id,
|
|
595
|
-
title: titleOf(agent.session
|
|
596
|
+
title: titleOf(sessionEvents(agent.session)),
|
|
596
597
|
...(headerCwd === undefined ? {} : { cwd: headerCwd }),
|
|
597
598
|
...(wsId === undefined ? {} : { workspaceId: wsId }),
|
|
598
599
|
live: true,
|