@botcord/daemon 0.2.47 → 0.2.48
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.
|
@@ -612,12 +612,14 @@ function stripLeadingBoundaryResidue(text) {
|
|
|
612
612
|
// Keep real HTML/XML-ish tags and common comparison operators. A lone
|
|
613
613
|
// leading "<" before normal prose can be left behind when ACP streams a
|
|
614
614
|
// structural marker boundary separately from the final assistant text.
|
|
615
|
-
if (
|
|
616
|
-
return text;
|
|
617
|
-
if (/^<(?:\s|=|<)/.test(text))
|
|
615
|
+
if (startsWithRealAngleSyntax(text))
|
|
618
616
|
return text;
|
|
619
617
|
return text.slice(1).trimStart();
|
|
620
618
|
}
|
|
619
|
+
function startsWithRealAngleSyntax(text) {
|
|
620
|
+
return (/^<\/?[A-Za-z][A-Za-z0-9:-]*(?:\s|>|\/>)/.test(text) ||
|
|
621
|
+
/^<(?:\s|=|<)/.test(text));
|
|
622
|
+
}
|
|
621
623
|
function createAssistantTextFilter() {
|
|
622
624
|
let pending = "";
|
|
623
625
|
let inThink = false;
|
|
@@ -721,7 +723,19 @@ function createAssistantTextFilter() {
|
|
|
721
723
|
if (!flush && knownPrefixes.some((prefix) => prefix.startsWith(lower))) {
|
|
722
724
|
return out;
|
|
723
725
|
}
|
|
724
|
-
|
|
726
|
+
if (!flush && pending === "<") {
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
if (!startsWithRealAngleSyntax(pending)) {
|
|
730
|
+
pending = pending.slice(1).trimStart();
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (seenFinal) {
|
|
734
|
+
out += "<";
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
fallback += "<";
|
|
738
|
+
}
|
|
725
739
|
pending = pending.slice(1);
|
|
726
740
|
}
|
|
727
741
|
if (flush && !seenFinal && fallback) {
|
package/package.json
CHANGED
|
@@ -334,6 +334,7 @@ describe("OpenclawAcpAdapter.run", () => {
|
|
|
334
334
|
}
|
|
335
335
|
});
|
|
336
336
|
|
|
337
|
+
const blocks: any[] = [];
|
|
337
338
|
const res = await adapter.run({
|
|
338
339
|
text: "hi",
|
|
339
340
|
sessionId: null,
|
|
@@ -342,9 +343,98 @@ describe("OpenclawAcpAdapter.run", () => {
|
|
|
342
343
|
signal: new AbortController().signal,
|
|
343
344
|
trustLevel: "owner",
|
|
344
345
|
gateway,
|
|
346
|
+
onBlock: (b) => blocks.push(b),
|
|
345
347
|
});
|
|
346
348
|
|
|
347
349
|
expect(res.text).toBe("好!终于可以正常交流了。");
|
|
350
|
+
const assistantChunks = blocks
|
|
351
|
+
.filter((b) => b.kind === "assistant_text")
|
|
352
|
+
.map((b) => b.raw.params.update.content[0].text);
|
|
353
|
+
expect(assistantChunks).toEqual(["好!终于可以正常交流了。"]);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("preserves real leading angle syntax in streamed fallback text", 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-angle" } }) + "\n");
|
|
372
|
+
} else if (frame.method === "session/prompt") {
|
|
373
|
+
for (const text of ["<", "b>bold</b> and ", "<", " 5"]) {
|
|
374
|
+
child.stdout.write(
|
|
375
|
+
JSON.stringify({
|
|
376
|
+
jsonrpc: "2.0",
|
|
377
|
+
method: "session/update",
|
|
378
|
+
params: {
|
|
379
|
+
sessionId: "sid-angle",
|
|
380
|
+
update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text } },
|
|
381
|
+
},
|
|
382
|
+
}) + "\n",
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
child.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: frame.id, result: { stopReason: "end_turn" } }) + "\n");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const res = await adapter.run({
|
|
391
|
+
text: "hi",
|
|
392
|
+
sessionId: null,
|
|
393
|
+
cwd: "/tmp",
|
|
394
|
+
accountId: "ag_alice",
|
|
395
|
+
signal: new AbortController().signal,
|
|
396
|
+
trustLevel: "owner",
|
|
397
|
+
gateway,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
expect(res.text).toBe("<b>bold</b> and < 5");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("forwards slash-command user text to session/prompt unchanged", async () => {
|
|
404
|
+
const child = new FakeChild();
|
|
405
|
+
const adapter = new OpenclawAcpAdapter({ spawnFn: makeSpawn(child) });
|
|
406
|
+
const gateway: ResolvedOpenclawGateway = {
|
|
407
|
+
name: "local",
|
|
408
|
+
url: "ws://127.0.0.1:1",
|
|
409
|
+
openclawAgent: "main",
|
|
410
|
+
};
|
|
411
|
+
let promptPayload: any = null;
|
|
412
|
+
|
|
413
|
+
child.stdin.on("data", (chunk: Buffer) => {
|
|
414
|
+
for (const line of chunk.toString("utf8").split("\n").filter(Boolean)) {
|
|
415
|
+
const frame = JSON.parse(line);
|
|
416
|
+
if (frame.method === "initialize") {
|
|
417
|
+
child.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: frame.id, result: { protocolVersion: 1 } }) + "\n");
|
|
418
|
+
} else if (frame.method === "session/new") {
|
|
419
|
+
child.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: frame.id, result: { sessionId: "sid-slash" } }) + "\n");
|
|
420
|
+
} else if (frame.method === "session/prompt") {
|
|
421
|
+
promptPayload = frame.params.prompt;
|
|
422
|
+
child.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: frame.id, result: { text: "ok" } }) + "\n");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
await adapter.run({
|
|
428
|
+
text: "/start",
|
|
429
|
+
sessionId: null,
|
|
430
|
+
cwd: "/tmp",
|
|
431
|
+
accountId: "ag_alice",
|
|
432
|
+
signal: new AbortController().signal,
|
|
433
|
+
trustLevel: "owner",
|
|
434
|
+
gateway,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(promptPayload).toEqual([{ type: "text", text: "/start" }]);
|
|
348
438
|
});
|
|
349
439
|
|
|
350
440
|
it("respawns the pooled child when gateway.url or gateway.token changes under the same name", async () => {
|
|
@@ -720,11 +720,17 @@ function stripLeadingBoundaryResidue(text: string): string {
|
|
|
720
720
|
// Keep real HTML/XML-ish tags and common comparison operators. A lone
|
|
721
721
|
// leading "<" before normal prose can be left behind when ACP streams a
|
|
722
722
|
// structural marker boundary separately from the final assistant text.
|
|
723
|
-
if (
|
|
724
|
-
if (/^<(?:\s|=|<)/.test(text)) return text;
|
|
723
|
+
if (startsWithRealAngleSyntax(text)) return text;
|
|
725
724
|
return text.slice(1).trimStart();
|
|
726
725
|
}
|
|
727
726
|
|
|
727
|
+
function startsWithRealAngleSyntax(text: string): boolean {
|
|
728
|
+
return (
|
|
729
|
+
/^<\/?[A-Za-z][A-Za-z0-9:-]*(?:\s|>|\/>)/.test(text) ||
|
|
730
|
+
/^<(?:\s|=|<)/.test(text)
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
728
734
|
function createAssistantTextFilter(): {
|
|
729
735
|
push(text: string): string;
|
|
730
736
|
flush(): string;
|
|
@@ -830,7 +836,18 @@ function createAssistantTextFilter(): {
|
|
|
830
836
|
return out;
|
|
831
837
|
}
|
|
832
838
|
|
|
833
|
-
|
|
839
|
+
if (!flush && pending === "<") {
|
|
840
|
+
return out;
|
|
841
|
+
}
|
|
842
|
+
if (!startsWithRealAngleSyntax(pending)) {
|
|
843
|
+
pending = pending.slice(1).trimStart();
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (seenFinal) {
|
|
847
|
+
out += "<";
|
|
848
|
+
} else {
|
|
849
|
+
fallback += "<";
|
|
850
|
+
}
|
|
834
851
|
pending = pending.slice(1);
|
|
835
852
|
}
|
|
836
853
|
if (flush && !seenFinal && fallback) {
|