@botcord/daemon 0.2.53 → 0.2.54
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.
|
@@ -276,7 +276,7 @@ export class OpenclawAcpAdapter {
|
|
|
276
276
|
}
|
|
277
277
|
const pickedText = normalizeAssistantText(pickFinalText(promptResult));
|
|
278
278
|
const streamedText = normalizeAssistantText(assistantText);
|
|
279
|
-
finalText =
|
|
279
|
+
finalText = pickSafeAssistantText(pickedText) || pickSafeAssistantText(streamedText);
|
|
280
280
|
if (capped) {
|
|
281
281
|
log.warn("openclaw-acp.assistant-text-capped", { sessionId: acpSessionId });
|
|
282
282
|
}
|
|
@@ -741,8 +741,7 @@ function createAssistantTextFilter() {
|
|
|
741
741
|
if (flush && !seenFinal && fallback) {
|
|
742
742
|
const text = normalizeAssistantText(fallback);
|
|
743
743
|
fallback = "";
|
|
744
|
-
|
|
745
|
-
return text;
|
|
744
|
+
return pickSafeAssistantText(text);
|
|
746
745
|
}
|
|
747
746
|
return out;
|
|
748
747
|
};
|
|
@@ -800,6 +799,37 @@ function looksLikeReasoningLeak(text) {
|
|
|
800
799
|
/\bi('|’)ll respond\b/i.test(t) ||
|
|
801
800
|
/\bi need to\b/i.test(t));
|
|
802
801
|
}
|
|
802
|
+
function pickSafeAssistantText(text) {
|
|
803
|
+
if (!text)
|
|
804
|
+
return "";
|
|
805
|
+
const trimmed = text.trim();
|
|
806
|
+
if (!looksLikeReasoningLeak(trimmed))
|
|
807
|
+
return trimmed;
|
|
808
|
+
return stripLeadingReasoningLeak(trimmed);
|
|
809
|
+
}
|
|
810
|
+
function stripLeadingReasoningLeak(text) {
|
|
811
|
+
const paragraphs = text
|
|
812
|
+
.replace(/\r\n/g, "\n")
|
|
813
|
+
.split(/\n{2,}/)
|
|
814
|
+
.map((p) => p.trim())
|
|
815
|
+
.filter(Boolean);
|
|
816
|
+
while (paragraphs.length > 0 && isReasoningNarration(paragraphs[0])) {
|
|
817
|
+
paragraphs.shift();
|
|
818
|
+
}
|
|
819
|
+
const candidate = paragraphs.join("\n\n").trim();
|
|
820
|
+
return candidate && !looksLikeReasoningLeak(candidate) ? candidate : "";
|
|
821
|
+
}
|
|
822
|
+
function isReasoningNarration(text) {
|
|
823
|
+
const t = text.trim();
|
|
824
|
+
if (!t)
|
|
825
|
+
return false;
|
|
826
|
+
return (looksLikeReasoningLeak(t) ||
|
|
827
|
+
/^let me\b/i.test(t) ||
|
|
828
|
+
/^i('|’)ll\b/i.test(t) ||
|
|
829
|
+
/^i will\b/i.test(t) ||
|
|
830
|
+
/^i should\b/i.test(t) ||
|
|
831
|
+
/^i need to\b/i.test(t));
|
|
832
|
+
}
|
|
803
833
|
function stringField(bag, key) {
|
|
804
834
|
if (!bag)
|
|
805
835
|
return undefined;
|
package/package.json
CHANGED
|
@@ -353,6 +353,62 @@ describe("OpenclawAcpAdapter.run", () => {
|
|
|
353
353
|
expect(assistantChunks).toEqual(["好!终于可以正常交流了。"]);
|
|
354
354
|
});
|
|
355
355
|
|
|
356
|
+
it("keeps final streamed text after an untagged reasoning preamble", async () => {
|
|
357
|
+
const child = new FakeChild();
|
|
358
|
+
const adapter = new OpenclawAcpAdapter({ spawnFn: makeSpawn(child) });
|
|
359
|
+
const gateway: ResolvedOpenclawGateway = {
|
|
360
|
+
name: "local",
|
|
361
|
+
url: "ws://127.0.0.1:1",
|
|
362
|
+
openclawAgent: "main",
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
child.stdin.on("data", (chunk: Buffer) => {
|
|
366
|
+
for (const line of chunk.toString("utf8").split("\n").filter(Boolean)) {
|
|
367
|
+
const frame = JSON.parse(line);
|
|
368
|
+
if (frame.method === "initialize") {
|
|
369
|
+
child.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: frame.id, result: { protocolVersion: 1 } }) + "\n");
|
|
370
|
+
} else if (frame.method === "session/new") {
|
|
371
|
+
child.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: frame.id, result: { sessionId: "sid-reasoning-preamble" } }) + "\n");
|
|
372
|
+
} else if (frame.method === "session/prompt") {
|
|
373
|
+
child.stdout.write(
|
|
374
|
+
JSON.stringify({
|
|
375
|
+
jsonrpc: "2.0",
|
|
376
|
+
method: "session/update",
|
|
377
|
+
params: {
|
|
378
|
+
sessionId: "sid-reasoning-preamble",
|
|
379
|
+
update: {
|
|
380
|
+
sessionUpdate: "agent_message_chunk",
|
|
381
|
+
content: {
|
|
382
|
+
type: "text",
|
|
383
|
+
text: "The user is asking about today's weather in Chinese. Let me check the weather skill to see how to get this info.\n\n今天上海天气不错!\n\n- 气温:29°C\n- 湿度:52%",
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
}) + "\n",
|
|
388
|
+
);
|
|
389
|
+
child.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: frame.id, result: { stopReason: "end_turn" } }) + "\n");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const blocks: any[] = [];
|
|
395
|
+
const res = await adapter.run({
|
|
396
|
+
text: "今天天气咋样",
|
|
397
|
+
sessionId: null,
|
|
398
|
+
cwd: "/tmp",
|
|
399
|
+
accountId: "ag_alice",
|
|
400
|
+
signal: new AbortController().signal,
|
|
401
|
+
trustLevel: "owner",
|
|
402
|
+
gateway,
|
|
403
|
+
onBlock: (b) => blocks.push(b),
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
expect(res.error).toBeUndefined();
|
|
407
|
+
expect(res.text).toBe("今天上海天气不错!\n\n- 气温:29°C\n- 湿度:52%");
|
|
408
|
+
expect(blocks.filter((b) => b.kind === "assistant_text")).toHaveLength(1);
|
|
409
|
+
expect(JSON.stringify(blocks)).not.toContain("The user is asking");
|
|
410
|
+
});
|
|
411
|
+
|
|
356
412
|
it("preserves real leading angle syntax in streamed fallback text", async () => {
|
|
357
413
|
const child = new FakeChild();
|
|
358
414
|
const adapter = new OpenclawAcpAdapter({ spawnFn: makeSpawn(child) });
|
|
@@ -350,7 +350,7 @@ export class OpenclawAcpAdapter implements RuntimeAdapter {
|
|
|
350
350
|
}
|
|
351
351
|
const pickedText = normalizeAssistantText(pickFinalText(promptResult));
|
|
352
352
|
const streamedText = normalizeAssistantText(assistantText);
|
|
353
|
-
finalText =
|
|
353
|
+
finalText = pickSafeAssistantText(pickedText) || pickSafeAssistantText(streamedText);
|
|
354
354
|
|
|
355
355
|
if (capped) {
|
|
356
356
|
log.warn("openclaw-acp.assistant-text-capped", { sessionId: acpSessionId });
|
|
@@ -853,7 +853,7 @@ function createAssistantTextFilter(): {
|
|
|
853
853
|
if (flush && !seenFinal && fallback) {
|
|
854
854
|
const text = normalizeAssistantText(fallback);
|
|
855
855
|
fallback = "";
|
|
856
|
-
|
|
856
|
+
return pickSafeAssistantText(text);
|
|
857
857
|
}
|
|
858
858
|
return out;
|
|
859
859
|
};
|
|
@@ -909,6 +909,41 @@ function looksLikeReasoningLeak(text: string): boolean {
|
|
|
909
909
|
);
|
|
910
910
|
}
|
|
911
911
|
|
|
912
|
+
function pickSafeAssistantText(text: string | undefined): string {
|
|
913
|
+
if (!text) return "";
|
|
914
|
+
const trimmed = text.trim();
|
|
915
|
+
if (!looksLikeReasoningLeak(trimmed)) return trimmed;
|
|
916
|
+
return stripLeadingReasoningLeak(trimmed);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function stripLeadingReasoningLeak(text: string): string {
|
|
920
|
+
const paragraphs = text
|
|
921
|
+
.replace(/\r\n/g, "\n")
|
|
922
|
+
.split(/\n{2,}/)
|
|
923
|
+
.map((p) => p.trim())
|
|
924
|
+
.filter(Boolean);
|
|
925
|
+
|
|
926
|
+
while (paragraphs.length > 0 && isReasoningNarration(paragraphs[0])) {
|
|
927
|
+
paragraphs.shift();
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const candidate = paragraphs.join("\n\n").trim();
|
|
931
|
+
return candidate && !looksLikeReasoningLeak(candidate) ? candidate : "";
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function isReasoningNarration(text: string): boolean {
|
|
935
|
+
const t = text.trim();
|
|
936
|
+
if (!t) return false;
|
|
937
|
+
return (
|
|
938
|
+
looksLikeReasoningLeak(t) ||
|
|
939
|
+
/^let me\b/i.test(t) ||
|
|
940
|
+
/^i('|’)ll\b/i.test(t) ||
|
|
941
|
+
/^i will\b/i.test(t) ||
|
|
942
|
+
/^i should\b/i.test(t) ||
|
|
943
|
+
/^i need to\b/i.test(t)
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
|
|
912
947
|
function stringField(bag: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
913
948
|
if (!bag) return undefined;
|
|
914
949
|
const v = bag[key];
|