@jeik/dingtalk-connector 0.8.21-fix7 → 0.8.21-fix9
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/bin/dingtalk-connector.js +20 -15
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{gateway-methods-D7aACzCW.mjs → gateway-methods-B9kuAOFf.mjs} +1 -1
- package/dist/{gateway-methods-DM8lWI4F.mjs → gateway-methods-DfW2oZ6c.mjs} +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{message-handler-CqC0-7FJ.mjs → message-handler-CWW2dKz0.mjs} +8 -11
- package/dist/{messaging-CBQl0D00.mjs → messaging-CvZBBMgv.mjs} +28 -27
- package/dist/{runtime-5-KOSMI9.mjs → runtime-CUSz0JLV.mjs} +2 -2
- package/package.json +1 -1
- package/src/reply-dispatcher.ts +17 -24
- package/src/services/reply-markers.ts +63 -65
|
@@ -294,16 +294,14 @@ function saveCredentials(clientId, clientSecret, { isLocal = false, pluginInstal
|
|
|
294
294
|
|
|
295
295
|
// ── plugin install ─────────────────────────────────────────────
|
|
296
296
|
function getInstallSpec() {
|
|
297
|
-
//
|
|
297
|
+
// 永远钉住"本包自己的精确版本",让 openclaw 内部装的版本 == 你 npx 实际下到的版本。
|
|
298
|
+
// npx @fix / @0.8.21-fix7 / @latest / 不带 → 各自下到对应包 → 这里读到的就是那个版本。
|
|
299
|
+
// 不带版本号会让 openclaw 装 latest,导致"跑 fix 向导却装了正式版"那个坑。
|
|
298
300
|
try {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
// 任何预发布版(含 -)都钉精确版本,否则 openclaw 会装 latest(如 -fix6 被当稳定版误装成 0.8.21)
|
|
302
|
-
if (version && version.includes('-')) {
|
|
303
|
-
return `${PKG_NAME}@${version}`;
|
|
304
|
-
}
|
|
301
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
302
|
+
if (version) return `${PKG_NAME}@${version}`;
|
|
305
303
|
} catch {}
|
|
306
|
-
return PKG_NAME;
|
|
304
|
+
return PKG_NAME; // 兜底:读不到版本才退回不带版本(极少见)
|
|
307
305
|
}
|
|
308
306
|
|
|
309
307
|
// 在 plugins.load.paths 扫描范围内找会"遮蔽"npm 版的本地 dingtalk 插件副本。
|
|
@@ -665,14 +663,21 @@ async function ensureDwsCli() {
|
|
|
665
663
|
const versionDisplay = installedVersion ? `v${installedVersion}` : 'unknown version';
|
|
666
664
|
|
|
667
665
|
if (!targetVersion) {
|
|
668
|
-
// @latest
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
console.log(
|
|
666
|
+
// @latest 模式:已安装则询问是否跳过更新(默认跳过,保留当前版本)
|
|
667
|
+
const ans = await askUserConfirmation(
|
|
668
|
+
` 检测到 dws CLI 已安装 (${versionDisplay}),是否跳过更新?(skip dws update?) [Y/n] `,
|
|
669
|
+
);
|
|
670
|
+
if (ans === 'n' || ans === 'no') {
|
|
671
|
+
console.log(dim(` 正在更新 dws CLI 到最新版...`) + '\n');
|
|
672
|
+
const ok = installDwsCli();
|
|
673
|
+
if (ok) {
|
|
674
|
+
const nv = getInstalledDwsVersion();
|
|
675
|
+
console.log(green(` ✔ dws CLI is now ${nv ? 'v' + nv : 'latest'}`) + '\n');
|
|
676
|
+
} else {
|
|
677
|
+
console.log(red(' ⚠ Update failed. Continuing with current version.') + '\n');
|
|
678
|
+
}
|
|
674
679
|
} else {
|
|
675
|
-
console.log(
|
|
680
|
+
console.log(dim(` 已跳过更新,保留当前版本 ${versionDisplay}。`) + '\n');
|
|
676
681
|
}
|
|
677
682
|
// 鉴权状态检查(与下方同逻辑)
|
|
678
683
|
if (isDwsAuthenticated()) {
|
package/dist/entry-bundled.mjs
CHANGED
|
@@ -23,7 +23,7 @@ var entry_bundled_default = defineBundledChannelEntry({
|
|
|
23
23
|
exportName: "setDingtalkRuntime"
|
|
24
24
|
},
|
|
25
25
|
async registerFull(api) {
|
|
26
|
-
const { registerGatewayMethods } = await import("./gateway-methods-
|
|
26
|
+
const { registerGatewayMethods } = await import("./gateway-methods-B9kuAOFf.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { t as registerGatewayMethods } from "./gateway-methods-DfW2oZ6c.mjs";
|
|
2
2
|
export { registerGatewayMethods };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-BAzdqkAV.mjs";
|
|
2
2
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
3
3
|
import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
4
|
-
import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-
|
|
4
|
+
import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-CvZBBMgv.mjs";
|
|
5
5
|
import { c as getUnionId, d as recallEmotionReply } from "./utils-legacy-CALCPP1t.mjs";
|
|
6
6
|
//#region src/docs.ts
|
|
7
7
|
var DingtalkDocsClient = class {
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-
|
|
2
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-CUSz0JLV.mjs";
|
|
2
|
+
import { t as registerGatewayMethods } from "./gateway-methods-DfW2oZ6c.mjs";
|
|
3
3
|
//#region index.ts
|
|
4
4
|
/**
|
|
5
5
|
* 检测同一 plugin id 在多个路径被加载的情况。
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { u as uploadMediaToDingTalk } from "./media-cz72EVS3.mjs";
|
|
2
2
|
import { a as resolveDingtalkAccount } from "./accounts-BAzdqkAV.mjs";
|
|
3
|
-
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-
|
|
3
|
+
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-CUSz0JLV.mjs";
|
|
4
4
|
import { n as createLoggerFromConfig } from "./logger-mZ9OSbmD.mjs";
|
|
5
5
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
6
6
|
import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
|
|
7
|
-
import { _ as PROCESS_TAG, a as sendTextMessage, b as finalClean, f as isQpsLimitError, g as FINAL_TAG, h as unregisterActiveCard, i as sendProactive, l as createAICardForTarget, m as streamAICard, p as registerActiveCard, r as sendMessage, t as sendMarkdownMessage, u as finishAICard, v as displayClean, y as extractFinal } from "./messaging-
|
|
7
|
+
import { _ as PROCESS_TAG, a as sendTextMessage, b as finalClean, f as isQpsLimitError, g as FINAL_TAG, h as unregisterActiveCard, i as sendProactive, l as createAICardForTarget, m as streamAICard, p as registerActiveCard, r as sendMessage, t as sendMarkdownMessage, u as finishAICard, v as displayClean, y as extractFinal } from "./messaging-CvZBBMgv.mjs";
|
|
8
8
|
import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
|
|
9
9
|
import { d as recallEmotionReply, o as getAccessToken, r as addEmotionReply, s as getOapiAccessToken$1, t as DINGTALK_API } from "./utils-legacy-CALCPP1t.mjs";
|
|
10
10
|
import "./chunk-upload-BBQgGtcZ.mjs";
|
|
@@ -233,22 +233,20 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
233
233
|
const observeReply = (raw, isReasoning) => {
|
|
234
234
|
const text = raw ?? "";
|
|
235
235
|
if (!text) return;
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
const abnormal = headTag !== null && tailTag !== null && headTag !== tailTag;
|
|
239
|
-
if (!abnormal && (headTag === "final" || tailTag === "final")) {
|
|
236
|
+
const fin = extractFinal(text);
|
|
237
|
+
if (fin !== null) {
|
|
240
238
|
if (!finalMarkerLogged) {
|
|
241
239
|
finalMarkerLogged = true;
|
|
242
|
-
log.info(`[DingTalk][marker]
|
|
240
|
+
log.info(`[DingTalk][marker] 检测到 ${FINAL_TAG},最终答案取最后一个 ${PROCESS_TAG} 之后内容,跳过 OpenClaw 兜底`);
|
|
243
241
|
}
|
|
244
|
-
finalMarkedText =
|
|
242
|
+
finalMarkedText = fin;
|
|
245
243
|
markerSystemActive = true;
|
|
246
244
|
return;
|
|
247
245
|
}
|
|
248
|
-
if (
|
|
246
|
+
if (text.includes("[-process-]")) {
|
|
249
247
|
if (!processMarkerLogged) {
|
|
250
248
|
processMarkerLogged = true;
|
|
251
|
-
log.info(`[DingTalk][marker]
|
|
249
|
+
log.info(`[DingTalk][marker] 检测到 ${PROCESS_TAG}(过程段),抑制 OpenClaw 默认兜底`);
|
|
252
250
|
}
|
|
253
251
|
markerSystemActive = true;
|
|
254
252
|
return;
|
|
@@ -485,7 +483,6 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
485
483
|
processMarkerLogged = false;
|
|
486
484
|
finalMarkerLogged = false;
|
|
487
485
|
markerSystemActive = false;
|
|
488
|
-
if (streamingEnabled) startStreaming();
|
|
489
486
|
typingCallbacks.onActive?.();
|
|
490
487
|
},
|
|
491
488
|
deliver: async (payload, info) => {
|
|
@@ -7,43 +7,44 @@ import { r as MEDIA_MSG_TYPES } from "./session-DJ4jYqPv.mjs";
|
|
|
7
7
|
const PROCESS_TAG = "[-process-]";
|
|
8
8
|
const FINAL_TAG = "[-final-]";
|
|
9
9
|
const EDGE_WS = /^[ \t\r\n]+|[ \t\r\n]+$/g;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const HR_LINE = /^[ \t]*(?:[-*_][ \t]*){3,}(?:\r?\n|$)/;
|
|
11
|
+
/** 剥掉文本中所有 [-process-] / [-final-] 标记(含中间的),首尾空白清掉。 */
|
|
12
|
+
function stripAllMarkers(text) {
|
|
13
|
+
if (!text) return "";
|
|
14
|
+
return text.split(PROCESS_TAG).join("").split(FINAL_TAG).join("").replace(EDGE_WS, "");
|
|
15
|
+
}
|
|
16
|
+
/** 清掉段落开头的空白行 + markdown 分割横线(反复剥,处理 "\n\n---\n" 这种)。 */
|
|
17
|
+
function stripLeadingSeparators(text) {
|
|
18
|
+
let s = text.replace(/^[ \t\r\n]+/, "");
|
|
19
|
+
let m;
|
|
20
|
+
while ((m = s.match(HR_LINE)) && m[0].length > 0) s = s.slice(m[0].length).replace(/^[ \t\r\n]+/, "");
|
|
21
|
+
return s;
|
|
22
|
+
}
|
|
23
|
+
/** 取最终答案:取「最后一个 [-process-] 右边、[-final-] 左边」那段,剥残留标记 + 开头分隔,首尾清白。
|
|
24
|
+
* 没有 [-final-] → null(不是最终答案,交给 OpenClaw 兜底)。 */
|
|
13
25
|
function extractFinal(text) {
|
|
14
26
|
if (!text) return null;
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
/** 剥掉开头和结尾的一个标记 token([-process-] 或 [-final-],头尾优先,中间的保留)。 */
|
|
25
|
-
function stripOneMarker(text) {
|
|
26
|
-
if (!text) return "";
|
|
27
|
-
let result = text;
|
|
28
|
-
if (result.startsWith("[-process-]")) result = result.slice(11);
|
|
29
|
-
else if (result.startsWith("[-final-]")) result = result.slice(9);
|
|
30
|
-
if (result.endsWith("[-process-]")) result = result.slice(0, -11);
|
|
31
|
-
else if (result.endsWith("[-final-]")) result = result.slice(0, -9);
|
|
32
|
-
return result.replace(EDGE_WS, "");
|
|
27
|
+
const fi = text.lastIndexOf(FINAL_TAG);
|
|
28
|
+
if (fi < 0) return null;
|
|
29
|
+
const before = text.slice(0, fi);
|
|
30
|
+
const pi = before.lastIndexOf(PROCESS_TAG);
|
|
31
|
+
let seg = pi >= 0 ? before.slice(pi + 11) : before;
|
|
32
|
+
seg = seg.split(PROCESS_TAG).join("").split(FINAL_TAG).join("");
|
|
33
|
+
seg = stripLeadingSeparators(seg).replace(EDGE_WS, "");
|
|
34
|
+
return seg || stripAllMarkers(text);
|
|
33
35
|
}
|
|
34
|
-
/** 去掉流式途中末尾未闭合的半截标记(如 "[-fin",缺右 "]"
|
|
36
|
+
/** 去掉流式途中末尾未闭合的半截标记(如 "[-fin",缺右 "]")。完整标记交给 stripAllMarkers。 */
|
|
35
37
|
function stripPartialTail(text) {
|
|
36
38
|
if (!text) return "";
|
|
37
39
|
return text.replace(/\[-[a-z]*-?$/i, "");
|
|
38
40
|
}
|
|
39
|
-
/**
|
|
41
|
+
/** 定稿/单条消息用:剥光所有标记,保留完整正文(message 工具单条消息走这里)。 */
|
|
40
42
|
function finalClean(text) {
|
|
41
|
-
return
|
|
43
|
+
return stripAllMarkers(text);
|
|
42
44
|
}
|
|
43
|
-
/**
|
|
45
|
+
/** 流式展示用:剥光所有标记 + 末尾半截标记。 */
|
|
44
46
|
function displayClean(text) {
|
|
45
|
-
|
|
46
|
-
return fin !== null ? fin : stripPartialTail(stripOneMarker(text));
|
|
47
|
+
return stripPartialTail(stripAllMarkers(text));
|
|
47
48
|
}
|
|
48
49
|
//#endregion
|
|
49
50
|
//#region src/services/messaging/card.ts
|
|
@@ -3,7 +3,7 @@ import { a as resolveDingtalkAccount, c as addWildcardAllowFrom, d as hasConfigu
|
|
|
3
3
|
import { t as createLogger } from "./logger-mZ9OSbmD.mjs";
|
|
4
4
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
5
5
|
import "./utils-CIfI_3Jh.mjs";
|
|
6
|
-
import { b as finalClean, d as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-
|
|
6
|
+
import { b as finalClean, d as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-CvZBBMgv.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { z, z as z$1 } from "zod";
|
|
9
9
|
//#region src/secret-input.ts
|
|
@@ -919,7 +919,7 @@ async function monitorDingtalkProvider(opts = {}) {
|
|
|
919
919
|
const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
|
|
920
920
|
const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
|
|
921
921
|
import("./accounts-BQptOmgB.mjs"),
|
|
922
|
-
import("./message-handler-
|
|
922
|
+
import("./message-handler-CWW2dKz0.mjs"),
|
|
923
923
|
import("./connection-DHHFFNQJ.mjs")
|
|
924
924
|
]);
|
|
925
925
|
const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
|
package/package.json
CHANGED
package/src/reply-dispatcher.ts
CHANGED
|
@@ -116,50 +116,44 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
116
116
|
let finalMarkerLogged = false;
|
|
117
117
|
|
|
118
118
|
// 观察每段到达的文本,更新标记认定状态。
|
|
119
|
-
//
|
|
120
|
-
// [-final-]
|
|
121
|
-
//
|
|
122
|
-
// 无标记 / 头尾标记类型不同 → 走 OpenClaw 默认兜底
|
|
119
|
+
// 含 [-final-] → 最终答案 = 最后一个 [-process-] 右边、[-final-] 左边那段(extractFinal),跳过兜底
|
|
120
|
+
// 含 [-process-](无 [-final-])→ 过程段,激活标记系统抑制 OpenClaw 兜底
|
|
121
|
+
// 无标记 → 走 OpenClaw 默认兜底
|
|
123
122
|
let markerSystemActive = false;
|
|
124
123
|
|
|
125
124
|
const observeReply = (raw: string | undefined, isReasoning: boolean | undefined) => {
|
|
126
125
|
const text = raw ?? "";
|
|
127
126
|
if (!text) return;
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const abnormal = headTag !== null && tailTag !== null && headTag !== tailTag;
|
|
133
|
-
|
|
134
|
-
// [-final-] 在头/尾(非异常)→ 标记系统激活,提取最终答案,跳过 OpenClaw 兜底
|
|
135
|
-
if (!abnormal && (headTag === "final" || tailTag === "final")) {
|
|
128
|
+
// [-final-] 出现 → 提取最终段(最后一个 [-process-] 之后),跳过 OpenClaw 兜底
|
|
129
|
+
const fin = extractFinal(text);
|
|
130
|
+
if (fin !== null) {
|
|
136
131
|
if (!finalMarkerLogged) {
|
|
137
132
|
finalMarkerLogged = true;
|
|
138
|
-
log.info(`[DingTalk][marker]
|
|
133
|
+
log.info(`[DingTalk][marker] 检测到 ${FINAL_TAG},最终答案取最后一个 ${PROCESS_TAG} 之后内容,跳过 OpenClaw 兜底`);
|
|
139
134
|
}
|
|
140
|
-
finalMarkedText =
|
|
135
|
+
finalMarkedText = fin;
|
|
141
136
|
markerSystemActive = true;
|
|
142
137
|
return;
|
|
143
138
|
}
|
|
144
139
|
|
|
145
|
-
// [-process-]
|
|
146
|
-
if (
|
|
140
|
+
// 只有 [-process-](无 [-final-])→ 过程段,激活标记系统抑制兜底
|
|
141
|
+
if (text.includes(PROCESS_TAG)) {
|
|
147
142
|
if (!processMarkerLogged) {
|
|
148
143
|
processMarkerLogged = true;
|
|
149
|
-
log.info(`[DingTalk][marker]
|
|
144
|
+
log.info(`[DingTalk][marker] 检测到 ${PROCESS_TAG}(过程段),抑制 OpenClaw 默认兜底`);
|
|
150
145
|
}
|
|
151
146
|
markerSystemActive = true;
|
|
152
147
|
return;
|
|
153
148
|
}
|
|
154
149
|
|
|
155
|
-
//
|
|
156
|
-
// 已激活则保持沉默,让标记系统独占最终答案判定
|
|
150
|
+
// 无标记:标记系统未激活时走 OpenClaw 兜底;已激活则沉默,让标记系统独占判定
|
|
157
151
|
if (!markerSystemActive && !isReasoning && text.trim()) lastAnswerText = text;
|
|
158
152
|
};
|
|
159
153
|
|
|
160
154
|
// 选定本轮最终答案:
|
|
161
|
-
// 标记系统激活 → finalMarkedText
|
|
162
|
-
//
|
|
155
|
+
// 标记系统激活 → finalMarkedText(最终段)优先,accumulatedText 流式兜底(跳过 OpenClaw)
|
|
156
|
+
// 未激活(无标记)→ lastAnswerText(OpenClaw isReasoning 兜底),accumulatedText 兜底
|
|
163
157
|
const pickFinalText = (): string =>
|
|
164
158
|
markerSystemActive ? (finalMarkedText ?? accumulatedText) : (lastAnswerText || accumulatedText);
|
|
165
159
|
|
|
@@ -556,10 +550,9 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
556
550
|
processMarkerLogged = false;
|
|
557
551
|
finalMarkerLogged = false;
|
|
558
552
|
markerSystemActive = false;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
}
|
|
553
|
+
// 延迟建卡:不在 onReplyStart 抢先建卡,改由第一段真正的对话内容(onPartialReply /
|
|
554
|
+
// deliver block/final)按需创建。否则纯 message 工具轮次(无对话回复,deliver 计数全 0)
|
|
555
|
+
// 会留下一张没人喂的孤儿卡,收尾兜底成"无文本输出"。
|
|
563
556
|
typingCallbacks.onActive?.();
|
|
564
557
|
},
|
|
565
558
|
deliver: async (payload, info) => {
|
|
@@ -1,65 +1,63 @@
|
|
|
1
|
-
// 回复标记工具 —— 配合 prompt-rewriter 插件注入的"打标记"指令使用。
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// 最终答案 =
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return fin !== null ? fin : stripPartialTail(stripOneMarker(text));
|
|
65
|
-
}
|
|
1
|
+
// 回复标记工具 —— 配合 prompt-rewriter 插件注入的"打标记"指令使用。
|
|
2
|
+
//
|
|
3
|
+
// 【段落方案】过程段末尾打 [-process-],最终段末尾打 [-final-]。agent 常把整轮写成一段:
|
|
4
|
+
// 过程1…[-process-] 过程2…[-process-] … 最终汇总…[-final-]
|
|
5
|
+
// 最终答案 = 「最后一个 [-process-] 右边、[-final-] 左边」那段(即最终汇总),剥掉残留标记,
|
|
6
|
+
// 并清掉该段开头的换行 / markdown 分割横线(---、***、___)。
|
|
7
|
+
// 没有 [-process-] 时 = [-final-] 左边的全部。没有 [-final-] → 不是最终答案。
|
|
8
|
+
//
|
|
9
|
+
// ⚠️ 标记字面量必须与 prompt-rewriter 的 rewriter.ts 保持一致(两处重复,改一处要同步另一处)。
|
|
10
|
+
|
|
11
|
+
export const PROCESS_TAG = "[-process-]";
|
|
12
|
+
export const FINAL_TAG = "[-final-]";
|
|
13
|
+
|
|
14
|
+
const EDGE_WS = /^[ \t\r\n]+|[ \t\r\n]+$/g;
|
|
15
|
+
// markdown 分割横线整行:3+ 个 - / * / _(其间可有空格),如 ---、***、- - -
|
|
16
|
+
const HR_LINE = /^[ \t]*(?:[-*_][ \t]*){3,}(?:\r?\n|$)/;
|
|
17
|
+
|
|
18
|
+
/** 剥掉文本中所有 [-process-] / [-final-] 标记(含中间的),首尾空白清掉。 */
|
|
19
|
+
export function stripAllMarkers(text: string | undefined | null): string {
|
|
20
|
+
if (!text) return "";
|
|
21
|
+
return text.split(PROCESS_TAG).join("").split(FINAL_TAG).join("").replace(EDGE_WS, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 清掉段落开头的空白行 + markdown 分割横线(反复剥,处理 "\n\n---\n" 这种)。 */
|
|
25
|
+
function stripLeadingSeparators(text: string): string {
|
|
26
|
+
let s = text.replace(/^[ \t\r\n]+/, "");
|
|
27
|
+
let m: RegExpMatchArray | null;
|
|
28
|
+
while ((m = s.match(HR_LINE)) && m[0].length > 0) {
|
|
29
|
+
s = s.slice(m[0].length).replace(/^[ \t\r\n]+/, "");
|
|
30
|
+
}
|
|
31
|
+
return s;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 取最终答案:取「最后一个 [-process-] 右边、[-final-] 左边」那段,剥残留标记 + 开头分隔,首尾清白。
|
|
35
|
+
* 没有 [-final-] → null(不是最终答案,交给 OpenClaw 兜底)。 */
|
|
36
|
+
export function extractFinal(text: string | undefined | null): string | null {
|
|
37
|
+
if (!text) return null;
|
|
38
|
+
const fi = text.lastIndexOf(FINAL_TAG); // [-final-] = 最终段右边界
|
|
39
|
+
if (fi < 0) return null;
|
|
40
|
+
const before = text.slice(0, fi);
|
|
41
|
+
const pi = before.lastIndexOf(PROCESS_TAG); // 最后一个 [-process-] = 最终段左边界
|
|
42
|
+
let seg = pi >= 0 ? before.slice(pi + PROCESS_TAG.length) : before;
|
|
43
|
+
seg = seg.split(PROCESS_TAG).join("").split(FINAL_TAG).join(""); // 残留标记保险
|
|
44
|
+
seg = stripLeadingSeparators(seg).replace(EDGE_WS, "");
|
|
45
|
+
// 段为空(如 [-final-] 在最前)→ 退回剥光全文,避免空答案
|
|
46
|
+
return seg || stripAllMarkers(text);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 去掉流式途中末尾未闭合的半截标记(如 "[-fin",缺右 "]")。完整标记交给 stripAllMarkers。 */
|
|
50
|
+
export function stripPartialTail(text: string | undefined | null): string {
|
|
51
|
+
if (!text) return "";
|
|
52
|
+
return text.replace(/\[-[a-z]*-?$/i, "");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 定稿/单条消息用:剥光所有标记,保留完整正文(message 工具单条消息走这里)。 */
|
|
56
|
+
export function finalClean(text: string | undefined | null): string {
|
|
57
|
+
return stripAllMarkers(text);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 流式展示用:剥光所有标记 + 末尾半截标记。 */
|
|
61
|
+
export function displayClean(text: string | undefined | null): string {
|
|
62
|
+
return stripPartialTail(stripAllMarkers(text));
|
|
63
|
+
}
|