@zaofan/dsh-qqbot 1.9.2 → 1.10.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.md +646 -554
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/features/ext-capabilities.d.ts +9 -0
- package/dist/features/ext-capabilities.d.ts.map +1 -1
- package/dist/features/ext-capabilities.js +2 -0
- package/dist/features/ext-capabilities.js.map +1 -1
- package/dist/features/ext-tool-interaction.d.ts.map +1 -1
- package/dist/features/ext-tool-interaction.js +37 -5
- package/dist/features/ext-tool-interaction.js.map +1 -1
- package/dist/features/extension-store.d.ts.map +1 -1
- package/dist/features/extension-store.js +8 -0
- package/dist/features/extension-store.js.map +1 -1
- package/dist/gateway/middleware-setup.d.ts.map +1 -1
- package/dist/gateway/middleware-setup.js +6 -0
- package/dist/gateway/middleware-setup.js.map +1 -1
- package/docs/extension-contract.md +187 -0
- package/examples/ext-commands//345/255/220/344/274/232/350/257/235.mjs +598 -0
- package/examples/ext-hooks/session-watch-hook.mjs +53 -119
- package/examples/ext-tools/lib/session_watch_lib.mjs +767 -350
- package/examples/ext-tools/qq_send.mjs +201 -138
- package/examples/ext-tools/session_children.mjs +99 -0
- package/examples/ext-tools/session_create.mjs +219 -201
- package/examples/ext-tools/session_watch.mjs +139 -93
- package/examples/ext-tools/sessions_list.mjs +180 -180
- package/package.json +142 -141
|
@@ -1,201 +1,219 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* session_create.mjs — 创建**子会话**(跨工作区/跨会话),走宿主 sessionController
|
|
3
|
-
*
|
|
4
|
-
* ⚠️ 本文件的来历:前面几个版本(v1~v5)都在绕路 —— 试图用 `ctx.sessions.create()` +
|
|
5
|
-
* `ctx.agents.create()` 手工拼一个会话,结果一路撞墙(session 已存在 / agent id 不等于 session id /
|
|
6
|
-
* 选项读不到)。**权威范例是记忆插件 dsh-auto-memory**:它用宿主服务 `sessionController` 建会话,
|
|
7
|
-
* 源码原文(@a9i5k4/dsh-auto-memory/lib/index.js):
|
|
8
|
-
* created = await sc.create({
|
|
9
|
-
* ...(workspaceId ? { workspaceId } : { cwd }),
|
|
10
|
-
* ...(agentPreset ? { agentPreset } : {}),
|
|
11
|
-
* })
|
|
12
|
-
* const newId = String(created && (created.sessionId || created.id) || '')
|
|
13
|
-
* 两个坑(同文件注释):
|
|
14
|
-
* ① create() **只在传 workspaceId 时**才 attachSession;只传 cwd 仅设工作目录 → 会话落"未分组";
|
|
15
|
-
* ② 权限不在 create 里,走 `permissionPresets`(current 读 / set 写),完全权限预设名 = danger-full-access。
|
|
16
|
-
*
|
|
17
|
-
* ⚠️ 主人定(2026-10-12):**派活是"父"的职责** ⇒ 本工具只在 QQ 会话注册(`expose: 'qq'`),
|
|
18
|
-
* 子会话看不到,免得无限套娃。回执通道是 `session_send`(那个对子会话开放)。
|
|
19
|
-
*/
|
|
20
|
-
import { statSync } from 'node:fs';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 带版本号地加载共用库 —— **必须这么加载**。
|
|
25
|
-
*
|
|
26
|
-
* 背景:插件的 extension-store 只给**工具文件本身**的 import 加了 `?v=<mtime>`
|
|
27
|
-
* 来绕过 ESM 模块缓存;工具**内部**再 import 别的文件时,裸路径会被模块缓存永久命中。
|
|
28
|
-
* 后果:改了 `tools/lib/` 下的文件,热刷(tools_reload)**不会生效**,只有重启宿主才行 ——
|
|
29
|
-
* 这曾导致"以为改好了、实际跑的还是旧代码"(2026-10-12 实测踩到)。
|
|
30
|
-
*/
|
|
31
|
-
async function loadWatchLib() {
|
|
32
|
-
const url = new URL('./lib/session_watch_lib.mjs', import.meta.url);
|
|
33
|
-
let v = '0';
|
|
34
|
-
try { v = String(statSync(fileURLToPath(url)).mtimeMs); } catch { /* 拿不到 mtime 就退回裸 import */ }
|
|
35
|
-
return import(v === '0' ? url.href : `${url.href}?v=${v}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export default {
|
|
39
|
-
name: 'session_create',
|
|
40
|
-
// ★ 2026-10-12 主人定:父会话专用 ⇒ 只在 QQ 会话注册(子会话不可见)。
|
|
41
|
-
expose: 'qq',
|
|
42
|
-
description:
|
|
43
|
-
'创建对话(跨工作区/跨会话):可指定工作区(目录或标题片段,默认同工作区)、可给初始任务、默认完全权限;建好后自动给它挂"回合结束把末段正文回灌创建者"的钩子。',
|
|
44
|
-
inputSchema: {
|
|
45
|
-
task: { type: 'string', description: '初始任务文本(会自动送进新会话;不填=只建不派活)' },
|
|
46
|
-
workspace: { type: 'string', description: '目标工作区:目录路径片段或标题片段(可选;默认同工作区)' },
|
|
47
|
-
cwd: { type: 'string', description: '工作目录(可选;等价于指定工作区)' },
|
|
48
|
-
name: { type: 'string', description: '标签/备注(可选,建好后写进会话标题)' },
|
|
49
|
-
preset: { type: 'string', description: '权限预设(默认 danger-full-access 完全权限)' },
|
|
50
|
-
agent_preset: { type: 'string', description: '人设(agent 预设):收**任何字符** —— 先按预设名(显示名)反查 id,再按 id
|
|
51
|
-
wake: { type: 'boolean', description: 'true=建完立刻跑一轮(默认 false=只追加不唤醒,省 token)' },
|
|
52
|
-
watch: { type: 'boolean', description: 'true=挂"回合结束回灌"钩子(默认 true)' },
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
if (r && r.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
opts.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
['(request)
|
|
169
|
-
['(request
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
1
|
+
/**
|
|
2
|
+
* session_create.mjs — 创建**子会话**(跨工作区/跨会话),走宿主 sessionController
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ 本文件的来历:前面几个版本(v1~v5)都在绕路 —— 试图用 `ctx.sessions.create()` +
|
|
5
|
+
* `ctx.agents.create()` 手工拼一个会话,结果一路撞墙(session 已存在 / agent id 不等于 session id /
|
|
6
|
+
* 选项读不到)。**权威范例是记忆插件 dsh-auto-memory**:它用宿主服务 `sessionController` 建会话,
|
|
7
|
+
* 源码原文(@a9i5k4/dsh-auto-memory/lib/index.js):
|
|
8
|
+
* created = await sc.create({
|
|
9
|
+
* ...(workspaceId ? { workspaceId } : { cwd }),
|
|
10
|
+
* ...(agentPreset ? { agentPreset } : {}),
|
|
11
|
+
* })
|
|
12
|
+
* const newId = String(created && (created.sessionId || created.id) || '')
|
|
13
|
+
* 两个坑(同文件注释):
|
|
14
|
+
* ① create() **只在传 workspaceId 时**才 attachSession;只传 cwd 仅设工作目录 → 会话落"未分组";
|
|
15
|
+
* ② 权限不在 create 里,走 `permissionPresets`(current 读 / set 写),完全权限预设名 = danger-full-access。
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ 主人定(2026-10-12):**派活是"父"的职责** ⇒ 本工具只在 QQ 会话注册(`expose: 'qq'`),
|
|
18
|
+
* 子会话看不到,免得无限套娃。回执通道是 `session_send`(那个对子会话开放)。
|
|
19
|
+
*/
|
|
20
|
+
import { statSync } from 'node:fs';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 带版本号地加载共用库 —— **必须这么加载**。
|
|
25
|
+
*
|
|
26
|
+
* 背景:插件的 extension-store 只给**工具文件本身**的 import 加了 `?v=<mtime>`
|
|
27
|
+
* 来绕过 ESM 模块缓存;工具**内部**再 import 别的文件时,裸路径会被模块缓存永久命中。
|
|
28
|
+
* 后果:改了 `tools/lib/` 下的文件,热刷(tools_reload)**不会生效**,只有重启宿主才行 ——
|
|
29
|
+
* 这曾导致"以为改好了、实际跑的还是旧代码"(2026-10-12 实测踩到)。
|
|
30
|
+
*/
|
|
31
|
+
async function loadWatchLib() {
|
|
32
|
+
const url = new URL('./lib/session_watch_lib.mjs', import.meta.url);
|
|
33
|
+
let v = '0';
|
|
34
|
+
try { v = String(statSync(fileURLToPath(url)).mtimeMs); } catch { /* 拿不到 mtime 就退回裸 import */ }
|
|
35
|
+
return import(v === '0' ? url.href : `${url.href}?v=${v}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
name: 'session_create',
|
|
40
|
+
// ★ 2026-10-12 主人定:父会话专用 ⇒ 只在 QQ 会话注册(子会话不可见)。
|
|
41
|
+
expose: 'qq',
|
|
42
|
+
description:
|
|
43
|
+
'创建对话(跨工作区/跨会话):可指定工作区(目录或标题片段,默认同工作区)、可给初始任务、默认完全权限;建好后自动给它挂"回合结束把末段正文回灌创建者"的钩子。',
|
|
44
|
+
inputSchema: {
|
|
45
|
+
task: { type: 'string', description: '初始任务文本(会自动送进新会话;不填=只建不派活)' },
|
|
46
|
+
workspace: { type: 'string', description: '目标工作区:目录路径片段或标题片段(可选;默认同工作区)' },
|
|
47
|
+
cwd: { type: 'string', description: '工作目录(可选;等价于指定工作区)' },
|
|
48
|
+
name: { type: 'string', description: '标签/备注(可选,建好后写进会话标题)' },
|
|
49
|
+
preset: { type: 'string', description: '权限预设(默认 danger-full-access 完全权限)' },
|
|
50
|
+
agent_preset: { type: 'string', description: '人设(agent 预设):收**任何字符** —— 先按预设名(显示名)反查 id,再按 id 匹配(忽略大小写;唯一子串也认)。如「测试2」/standteste/「温柔鲸鱼娘」/softwhale。不填=继承我自己' },
|
|
51
|
+
wake: { type: 'boolean', description: 'true=建完立刻跑一轮(默认 false=只追加不唤醒,省 token)' },
|
|
52
|
+
watch: { type: 'boolean', description: 'true=挂"回合结束回灌"钩子(默认 true)' },
|
|
53
|
+
mode: { type: 'string', description: '回灌档位(挂钩子时才用):1/一档(默认)=末段正文回灌父会话 + 同时转发到父所在的QQ;2/二档=该回合所有正文+报错只转发QQ、不进父会话上下文;3/三档=所有正文+报错进父会话上下文但不唤醒、同时也转发QQ' },
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async run(args = {}, env = {}) {
|
|
57
|
+
const ctx = env?.ctx ?? null;
|
|
58
|
+
const root = ctx && typeof ctx === 'object' && ctx.root ? ctx.root : ctx;
|
|
59
|
+
if (!root?.get) return { ok: false, msg: '拿不到宿主 ctx(需要 expose:\'all\')' };
|
|
60
|
+
|
|
61
|
+
const me = String(env?.exec?.agent?.id ?? env?.exec?.sessionId ?? '').trim();
|
|
62
|
+
const svc = (k) => { try { return root.get(k); } catch { return undefined; } };
|
|
63
|
+
const sc = svc('sessionController');
|
|
64
|
+
if (!sc || typeof sc.create !== 'function') {
|
|
65
|
+
return { ok: false, msg: '宿主没有可用的 sessionController.create()(跨会话创建就靠它;请确认 dsh web 宿主版本)' };
|
|
66
|
+
}
|
|
67
|
+
const out = [`我=${me.slice(0, 8)}… 服务: sessionController ✓`];
|
|
68
|
+
|
|
69
|
+
// ── 解析目标工作区 → workspaceId(关键:只给 cwd 会落"未分组工作区")──
|
|
70
|
+
const wantWs = String(args?.workspace ?? args?.cwd ?? '').trim();
|
|
71
|
+
let workspaceId = '';
|
|
72
|
+
let wsPath = '';
|
|
73
|
+
let myCwd = '';
|
|
74
|
+
try {
|
|
75
|
+
const reg = svc('workspaceRegistry');
|
|
76
|
+
const list = (reg?.list?.() ?? []).filter(Boolean);
|
|
77
|
+
if (me) {
|
|
78
|
+
const mine = list.find((w) => (w.sessionIds ?? []).includes(me));
|
|
79
|
+
myCwd = String(mine?.path ?? '');
|
|
80
|
+
}
|
|
81
|
+
const key = wantWs || myCwd;
|
|
82
|
+
if (key) {
|
|
83
|
+
const k = key.toLowerCase();
|
|
84
|
+
const hit = list.find((w) => String(w?.path ?? '').toLowerCase().includes(k)) ??
|
|
85
|
+
list.find((w) => String(w?.title ?? '').toLowerCase().includes(k));
|
|
86
|
+
if (hit) { workspaceId = String(hit.id ?? ''); wsPath = String(hit.path ?? ''); }
|
|
87
|
+
else if (wantWs) out.push(`⚠ 没匹配到工作区「${wantWs}」,改用 cwd 直传(会落"未分组",可能不是你要的)`);
|
|
88
|
+
}
|
|
89
|
+
out.push(`工作区: ${workspaceId ? wsPath + ' (id=' + workspaceId.slice(0, 8) + '…)' : '(未指定,继承宿主默认)'}`);
|
|
90
|
+
} catch (e) { out.push('工作区解析失败(继续): ' + String(e?.message ?? e)); }
|
|
91
|
+
|
|
92
|
+
// ── ① 建会话 ──
|
|
93
|
+
// ⚠️ 2026-10-12 实测纠错:`agentPreset` 是 **agent 预设**(standard / whale-girl / softwhale…),
|
|
94
|
+
// **不是权限预设** —— 传 'danger-full-access' 会报 `Unknown agent preset: danger-full-access`。
|
|
95
|
+
// 权限是另一条链:permissionPresets.set(session, <权限预设名>),完全权限 = 'danger-full-access'。
|
|
96
|
+
let myPreset = 'standard';
|
|
97
|
+
try {
|
|
98
|
+
const list = (await svc('sessionQuery')?.listSessions?.()) ?? [];
|
|
99
|
+
const mine = list.find((s) => String(s?.header?.id ?? s?.id) === me);
|
|
100
|
+
if (mine?.header?.agentPreset) myPreset = String(mine.header.agentPreset);
|
|
101
|
+
} catch { /* ignore */ }
|
|
102
|
+
// ★ 2026-10-12 主人定:人设参数收**任何字符** —— 先按「预设名」反查 id,再按「id」匹配。
|
|
103
|
+
// 匹配顺序与容忍度全在 lib/preset_resolve.mjs(按 mtime 版本化加载 ⇒ 改库不必给工具改名)。
|
|
104
|
+
const wantPreset = String(args?.agent_preset ?? '').trim();
|
|
105
|
+
let agentPreset = wantPreset || myPreset;
|
|
106
|
+
if (wantPreset) {
|
|
107
|
+
try {
|
|
108
|
+
const pu = new URL('./lib/preset_resolve.mjs', import.meta.url);
|
|
109
|
+
let pv = '0';
|
|
110
|
+
try { pv = String(statSync(fileURLToPath(pu)).mtimeMs); } catch { /* ignore */ }
|
|
111
|
+
const presetLib = await import(pv === '0' ? pu.href : pu.href + '?v=' + pv);
|
|
112
|
+
const r = await presetLib.resolvePreset(root, wantPreset);
|
|
113
|
+
if (r && r.error) return { ok: false, msg: out.join('\n') + '\n❌ 人设解析失败: ' + r.error };
|
|
114
|
+
if (r && r.id) {
|
|
115
|
+
agentPreset = r.id;
|
|
116
|
+
if (r.via !== 'id') out.push('人设: 「' + wantPreset + '」→ id=' + agentPreset + '(按 ' + r.via + ' 匹配)');
|
|
117
|
+
}
|
|
118
|
+
} catch (e) { out.push('人设解析跳过(沿用原值): ' + String(e?.message ?? e).slice(0, 110)); }
|
|
119
|
+
}
|
|
120
|
+
const permPreset = String(args?.preset ?? 'danger-full-access').trim() || 'danger-full-access';
|
|
121
|
+
out.push('agent 预设 = ' + agentPreset + (wantPreset ? '' : '(继承我自己)') + ' 权限预设 = ' + permPreset);
|
|
122
|
+
let sid = '';
|
|
123
|
+
let created = null;
|
|
124
|
+
try {
|
|
125
|
+
const opts = {};
|
|
126
|
+
if (workspaceId) opts.workspaceId = workspaceId;
|
|
127
|
+
else if (wantWs || myCwd) opts.cwd = wantWs || myCwd;
|
|
128
|
+
opts.agentPreset = agentPreset;
|
|
129
|
+
created = await sc.create(opts);
|
|
130
|
+
sid = String(created?.sessionId ?? created?.id ?? '');
|
|
131
|
+
out.push(`① sessionController.create → ${sid ? 'ok ' + sid : '返回里没 sessionId/id(键: ' + Object.keys(created ?? {}).join(',') + ')'}`);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
out.push('① sessionController.create 抛错: ' + String(e?.message ?? e));
|
|
134
|
+
return { ok: false, msg: out.join('\n') };
|
|
135
|
+
}
|
|
136
|
+
if (!sid) return { ok: false, msg: out.join('\n') + '\n(没拿到会话 id,中止)' };
|
|
137
|
+
|
|
138
|
+
// ── ② 权限:完全权限(走 permissionPresets,不在 create 里)──
|
|
139
|
+
try {
|
|
140
|
+
const pp = svc('permissionPresets');
|
|
141
|
+
if (pp && typeof pp.set === 'function') {
|
|
142
|
+
let sess = null;
|
|
143
|
+
try { sess = svc('sessions')?.get?.(sid) ?? null; } catch { /* ignore */ }
|
|
144
|
+
if (!sess) {
|
|
145
|
+
try { const r = await sc.resolveAgent(sid); sess = r?.agent?.session ?? null; } catch { /* ignore */ }
|
|
146
|
+
}
|
|
147
|
+
if (sess) { await pp.set(sess, permPreset); out.push('② 权限预设 → ' + permPreset); }
|
|
148
|
+
else out.push(`② 权限预设跳过(没有 live session 对象;会话默认权限等它首次唤醒后由 preset=${preset} 生效)`);
|
|
149
|
+
} else {
|
|
150
|
+
out.push('② permissionPresets 服务不可用(跳过;create 时已带 agentPreset)');
|
|
151
|
+
}
|
|
152
|
+
} catch (e) { out.push('② 权限设置失败(继续): ' + String(e?.message ?? e)); }
|
|
153
|
+
|
|
154
|
+
// ── ③ 初始任务 ──
|
|
155
|
+
const task = String(args?.task ?? '').trim();
|
|
156
|
+
if (task && typeof sc.prompt === 'function') {
|
|
157
|
+
// ✅ 权威形状(dsh-api-session-controller/lib/types/types.d.ts:319 SessionPromptRequest):
|
|
158
|
+
// { requestId, sessionId, mode: 'queue'|'steer', content: PromptContentPart[], clientTimeZone? }
|
|
159
|
+
// 之前传 {sessionId, text} 全错 —— content 位置放字符串导致内部解析炸 `signal.throwIfAborted`。
|
|
160
|
+
// mode='queue' 排队(不打断);mode='steer' 插话(等于 qqbot 的 interject)。
|
|
161
|
+
const rid = (globalThis.crypto?.randomUUID?.() ?? ('req-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8)));
|
|
162
|
+
const body = [{ type: 'text', text: task }];
|
|
163
|
+
const sig = new AbortController().signal;
|
|
164
|
+
// ★ 关键:signal 是**独立的第二参数**(同文件 page(request, signal) / list(signal) / control(signal) 同款),
|
|
165
|
+
// 塞进 request 里它读不到 → 报 `undefined.throwIfAborted`。
|
|
166
|
+
const req = { requestId: rid, sessionId: sid, mode: 'queue', content: body };
|
|
167
|
+
const shapes = [
|
|
168
|
+
['(request, signal) mode=queue', () => sc.prompt(req, sig)],
|
|
169
|
+
['(request) mode=queue', () => sc.prompt(req)],
|
|
170
|
+
['(request, signal) mode=steer', () => sc.prompt({ ...req, mode: 'steer' }, sig)],
|
|
171
|
+
];
|
|
172
|
+
let via = '';
|
|
173
|
+
const errs = [];
|
|
174
|
+
for (const [lb, fn] of shapes) {
|
|
175
|
+
try { await fn(); via = lb; break; } catch (e) { errs.push(lb + '→' + String(e?.message ?? e).slice(0, 60)); }
|
|
176
|
+
}
|
|
177
|
+
out.push('③ 初始任务 → ' + (via ? 'ok(形式: ' + via + ')' : '全部形式被拒: ' + errs.join(' | ')));
|
|
178
|
+
} else if (task) {
|
|
179
|
+
out.push('③ 初始任务跳过(sessionController 没有 prompt())');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── ④ 标题(可选)──
|
|
183
|
+
const label = String(args?.name ?? '').trim();
|
|
184
|
+
if (label && typeof sc.rename === 'function') {
|
|
185
|
+
try { await sc.rename({ sessionId: sid, title: label }); out.push('④ 标题 → ' + label); }
|
|
186
|
+
catch (e) { out.push('④ 标题设置失败: ' + String(e?.message ?? e).slice(0, 80)); }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── ⑤ 回合结束回灌钩子 ──
|
|
190
|
+
let watchMsg = '未挂(watch=false)';
|
|
191
|
+
if (args?.watch !== false) {
|
|
192
|
+
try {
|
|
193
|
+
const w = await loadWatchLib();
|
|
194
|
+
// ★ 档位(主人 2026-10-12 定):1=末段回灌+转QQ / 2=只转QQ不进父 / 3=进父不唤醒+转QQ
|
|
195
|
+
const mv = String(args?.mode ?? '').trim();
|
|
196
|
+
const mode = /^(2|二)/.test(mv) ? 2 : (/^(3|三)/.test(mv) ? 3 : 1);
|
|
197
|
+
watchMsg = await w.register(root, {
|
|
198
|
+
parentAgentId: me,
|
|
199
|
+
childSessionId: sid,
|
|
200
|
+
label,
|
|
201
|
+
mode,
|
|
202
|
+
// ★ 归属行三件套(主人 2026-10-12 定:除了短 id,还要写会话名/预设名/工作区)
|
|
203
|
+
// 顺序有意义:路由键(【子会话 xxxx)在最前,后面这些即使被引用块的 60 字截掉也不影响回流。
|
|
204
|
+
name: label,
|
|
205
|
+
preset: agentPreset,
|
|
206
|
+
workspace: String(wsPath || myCwd || '').split(/[\\/]/).filter(Boolean).pop() ?? '',
|
|
207
|
+
// ★ QQ 转发用"我自己这个会话"的 sender + replyTarget:
|
|
208
|
+
// replyTarget 自带最近 msgId ⇒ 被动优先;插件的 sendMarkdown 自带 markdown→纯文本降级链。
|
|
209
|
+
sender: env?.sender,
|
|
210
|
+
replyTarget: env?.replyTarget,
|
|
211
|
+
peerName: String(env?.replyTarget?.name ?? ''),
|
|
212
|
+
}) + ' [lib=' + (w.__LIB_VERSION__ ?? '未知(旧版无版本号)') + ']';
|
|
213
|
+
} catch (e) { watchMsg = '未挂(' + String(e?.message ?? e) + ')'; }
|
|
214
|
+
}
|
|
215
|
+
out.push('⑤ 回合结束回灌 → ' + watchMsg);
|
|
216
|
+
|
|
217
|
+
return { ok: true, msg: out.join('\n') + `\n\n新会话 id = ${sid}\n(用 session_watch 看钩子名单;用 session_children 看血缘)` };
|
|
218
|
+
},
|
|
219
|
+
};
|