@openma/deepseek-harness-acp 0.4.6 → 0.4.8
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 +4 -2
- package/dist/bridge.js +1020 -40
- package/dist/index.js +71 -7
- package/package.json +2 -1
package/dist/bridge.js
CHANGED
|
@@ -444,7 +444,7 @@ function extractMessageTexts(message) {
|
|
|
444
444
|
}
|
|
445
445
|
function planEntries(data) {
|
|
446
446
|
const todos = data["todos"];
|
|
447
|
-
if (!Array.isArray(todos)) return
|
|
447
|
+
if (!Array.isArray(todos)) return void 0;
|
|
448
448
|
const entries = [];
|
|
449
449
|
for (const todo of todos) {
|
|
450
450
|
if (todo === null || typeof todo !== "object") continue;
|
|
@@ -487,6 +487,27 @@ function emptyWindow() {
|
|
|
487
487
|
error: void 0
|
|
488
488
|
};
|
|
489
489
|
}
|
|
490
|
+
var TOOL_KINDS = /* @__PURE__ */ new Set([
|
|
491
|
+
"read",
|
|
492
|
+
"edit",
|
|
493
|
+
"delete",
|
|
494
|
+
"move",
|
|
495
|
+
"search",
|
|
496
|
+
"execute",
|
|
497
|
+
"think",
|
|
498
|
+
"fetch",
|
|
499
|
+
"switch_mode",
|
|
500
|
+
"other"
|
|
501
|
+
]);
|
|
502
|
+
function factsFromPresentCall(name3, rawArguments, presented) {
|
|
503
|
+
const fallback = classifyToolCall(name3, rawArguments);
|
|
504
|
+
const kind = presented.kind !== void 0 && TOOL_KINDS.has(presented.kind) ? presented.kind : fallback.kind;
|
|
505
|
+
const title = presented.title !== void 0 && presented.title.length > 0 ? presented.title : fallback.title;
|
|
506
|
+
const locations = presented.locations !== void 0 && presented.locations.length > 0 ? presented.locations.map(
|
|
507
|
+
(location) => location.line === void 0 ? { path: location.path } : { path: location.path, line: location.line }
|
|
508
|
+
) : fallback.locations;
|
|
509
|
+
return { kind, title, locations, rawInput: fallback.rawInput };
|
|
510
|
+
}
|
|
490
511
|
var SessionProjection = class {
|
|
491
512
|
/** Context capacity from the latest `request/context` event, when known. */
|
|
492
513
|
contextWindow;
|
|
@@ -501,10 +522,14 @@ var SessionProjection = class {
|
|
|
501
522
|
terminalOutput;
|
|
502
523
|
/** Session working directory, advertised on display terminals. */
|
|
503
524
|
cwd;
|
|
525
|
+
presentCall;
|
|
526
|
+
subagent;
|
|
504
527
|
constructor(contextWindow, options) {
|
|
505
528
|
this.contextWindow = contextWindow;
|
|
506
529
|
this.terminalOutput = options?.terminalOutput ?? false;
|
|
507
530
|
this.cwd = options?.cwd;
|
|
531
|
+
this.presentCall = options?.presentCall;
|
|
532
|
+
this.subagent = options?.subagent;
|
|
508
533
|
}
|
|
509
534
|
/** Reset per-prompt accumulators; call when a new `session/prompt` starts. */
|
|
510
535
|
beginPrompt() {
|
|
@@ -539,6 +564,11 @@ var SessionProjection = class {
|
|
|
539
564
|
* Project one session event onto zero or more ACP updates.
|
|
540
565
|
*/
|
|
541
566
|
onEvent(event) {
|
|
567
|
+
const updates = this.projectEvent(event);
|
|
568
|
+
return this.subagent === void 0 ? updates : updates.map((update) => this.withSubagentAttribution(update));
|
|
569
|
+
}
|
|
570
|
+
/** Project one event before optional subagent attribution is attached. */
|
|
571
|
+
projectEvent(event) {
|
|
542
572
|
const data = event.data ?? {};
|
|
543
573
|
switch (event.type) {
|
|
544
574
|
case "assistant/chunk":
|
|
@@ -549,9 +579,34 @@ var SessionProjection = class {
|
|
|
549
579
|
return this.onToolCall(data);
|
|
550
580
|
case "tool/result":
|
|
551
581
|
return this.onToolResult(data);
|
|
582
|
+
case "subagent/start":
|
|
583
|
+
return this.onSubagentStart(data);
|
|
584
|
+
case "subagent/end":
|
|
585
|
+
return this.onSubagentEnd(data);
|
|
552
586
|
case "todo/write": {
|
|
553
587
|
const entries = planEntries(data);
|
|
554
|
-
return entries
|
|
588
|
+
return entries !== void 0 ? [{ sessionUpdate: "plan", entries }] : [];
|
|
589
|
+
}
|
|
590
|
+
case "user/message": {
|
|
591
|
+
const source = data["source"];
|
|
592
|
+
const kind = source !== null && typeof source === "object" ? asString(source["kind"]) : void 0;
|
|
593
|
+
if (kind === void 0 || kind === "user") return [];
|
|
594
|
+
const content = data["content"];
|
|
595
|
+
const text = Array.isArray(content) ? content.filter(
|
|
596
|
+
(block) => block !== null && typeof block === "object" && block["type"] === "text" && typeof block["text"] === "string"
|
|
597
|
+
).map((block) => block["text"]).join("") : "";
|
|
598
|
+
return [
|
|
599
|
+
{
|
|
600
|
+
sessionUpdate: "session_info_update",
|
|
601
|
+
_meta: {
|
|
602
|
+
dsh: {
|
|
603
|
+
event: "user/message",
|
|
604
|
+
source: kind,
|
|
605
|
+
preview: Array.from(text).slice(0, 160).join("")
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
];
|
|
555
610
|
}
|
|
556
611
|
case "turn/end":
|
|
557
612
|
return this.onTurnEnd(data);
|
|
@@ -573,31 +628,117 @@ var SessionProjection = class {
|
|
|
573
628
|
stepKey(data) {
|
|
574
629
|
return `${String(data["turn"])}:${String(data["step"])}`;
|
|
575
630
|
}
|
|
631
|
+
withSubagentAttribution(update) {
|
|
632
|
+
const value = update;
|
|
633
|
+
const meta = value["_meta"] !== null && typeof value["_meta"] === "object" ? value["_meta"] : {};
|
|
634
|
+
const dsh = meta["dsh"] !== null && typeof meta["dsh"] === "object" ? meta["dsh"] : {};
|
|
635
|
+
return {
|
|
636
|
+
...value,
|
|
637
|
+
_meta: { ...meta, dsh: { ...dsh, subagent: this.subagent } }
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
subagentMeta(data, state) {
|
|
641
|
+
const runId = asString(data["runId"]);
|
|
642
|
+
const childSessionId = asString(data["id"]);
|
|
643
|
+
const provider = asString(data["provider"]);
|
|
644
|
+
if (runId === void 0 || childSessionId === void 0 || provider === void 0) return void 0;
|
|
645
|
+
const parentToolCallId = asString(data["parentToolCallId"]);
|
|
646
|
+
return {
|
|
647
|
+
state,
|
|
648
|
+
runId,
|
|
649
|
+
childSessionId,
|
|
650
|
+
provider,
|
|
651
|
+
local: data["local"] === true,
|
|
652
|
+
...parentToolCallId !== void 0 ? { parentToolCallId } : {}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
onSubagentStart(data) {
|
|
656
|
+
const subagent = this.subagentMeta(data, "started");
|
|
657
|
+
if (subagent === void 0) return [];
|
|
658
|
+
const runId = subagent["runId"];
|
|
659
|
+
const childSessionId = subagent["childSessionId"];
|
|
660
|
+
return [{
|
|
661
|
+
sessionUpdate: "tool_call",
|
|
662
|
+
toolCallId: `subagent:${runId}`,
|
|
663
|
+
title: `Start subagent ${childSessionId}`,
|
|
664
|
+
kind: "other",
|
|
665
|
+
status: "in_progress",
|
|
666
|
+
rawInput: {
|
|
667
|
+
childSessionId,
|
|
668
|
+
provider: subagent["provider"],
|
|
669
|
+
local: subagent["local"]
|
|
670
|
+
},
|
|
671
|
+
_meta: { dsh: { subagent } }
|
|
672
|
+
}];
|
|
673
|
+
}
|
|
674
|
+
onSubagentEnd(data) {
|
|
675
|
+
const subagent = this.subagentMeta(data, "finished");
|
|
676
|
+
if (subagent === void 0) return [];
|
|
677
|
+
const stopReason = asString(data["stopReason"]) ?? "error";
|
|
678
|
+
const lastAssistantMessage = data["lastAssistantMessage"];
|
|
679
|
+
return [{
|
|
680
|
+
sessionUpdate: "tool_call_update",
|
|
681
|
+
toolCallId: `subagent:${String(subagent["runId"])}`,
|
|
682
|
+
status: stopReason === "completed" ? "completed" : "failed",
|
|
683
|
+
rawOutput: {
|
|
684
|
+
stopReason,
|
|
685
|
+
...Array.isArray(lastAssistantMessage) ? { lastAssistantMessage } : {}
|
|
686
|
+
},
|
|
687
|
+
_meta: { dsh: { subagent } }
|
|
688
|
+
}];
|
|
689
|
+
}
|
|
576
690
|
onChunk(data) {
|
|
577
691
|
const chunk = data["chunk"];
|
|
578
692
|
if (chunk === null || typeof chunk === "undefined" || typeof chunk !== "object") return [];
|
|
579
693
|
const c = chunk;
|
|
580
694
|
const text = c["text"];
|
|
581
695
|
if (typeof text !== "string" || text.length === 0) return [];
|
|
696
|
+
const messageId = this.stepKey(data);
|
|
582
697
|
if (c["type"] === "text-delta") {
|
|
583
|
-
this.streamedText.add(
|
|
584
|
-
return [{ sessionUpdate: "agent_message_chunk", content: { type: "text", text } }];
|
|
698
|
+
this.streamedText.add(messageId);
|
|
699
|
+
return [{ sessionUpdate: "agent_message_chunk", content: { type: "text", text }, messageId }];
|
|
585
700
|
}
|
|
586
701
|
if (c["type"] === "reasoning-delta") {
|
|
587
|
-
this.streamedReasoning.add(
|
|
588
|
-
return [{ sessionUpdate: "agent_thought_chunk", content: { type: "text", text } }];
|
|
702
|
+
this.streamedReasoning.add(messageId);
|
|
703
|
+
return [{ sessionUpdate: "agent_thought_chunk", content: { type: "text", text }, messageId }];
|
|
589
704
|
}
|
|
590
705
|
return [];
|
|
591
706
|
}
|
|
592
707
|
onAssistantMessage(data) {
|
|
593
708
|
const updates = [];
|
|
594
709
|
const key = this.stepKey(data);
|
|
595
|
-
const
|
|
710
|
+
const message = data["message"];
|
|
711
|
+
const { text, reasoning } = extractMessageTexts(message);
|
|
712
|
+
const model = message !== null && typeof message === "object" ? asString(
|
|
713
|
+
message["source"] !== null && typeof message["source"] === "object" ? message["source"]["model"] : void 0
|
|
714
|
+
) : void 0;
|
|
715
|
+
const completionMeta = {
|
|
716
|
+
dsh: {
|
|
717
|
+
event: "assistant_message",
|
|
718
|
+
...model !== void 0 ? { model } : {}
|
|
719
|
+
}
|
|
720
|
+
};
|
|
596
721
|
if (reasoning.length > 0 && !this.streamedReasoning.has(key)) {
|
|
597
|
-
updates.push({
|
|
722
|
+
updates.push({
|
|
723
|
+
sessionUpdate: "agent_thought_chunk",
|
|
724
|
+
content: { type: "text", text: reasoning },
|
|
725
|
+
messageId: key
|
|
726
|
+
});
|
|
598
727
|
}
|
|
599
728
|
if (text.length > 0 && !this.streamedText.has(key)) {
|
|
600
|
-
updates.push({
|
|
729
|
+
updates.push({
|
|
730
|
+
sessionUpdate: "agent_message_chunk",
|
|
731
|
+
content: { type: "text", text },
|
|
732
|
+
messageId: key,
|
|
733
|
+
_meta: completionMeta
|
|
734
|
+
});
|
|
735
|
+
} else {
|
|
736
|
+
updates.push({
|
|
737
|
+
sessionUpdate: "agent_message_chunk",
|
|
738
|
+
content: { type: "text", text: "" },
|
|
739
|
+
messageId: key,
|
|
740
|
+
_meta: completionMeta
|
|
741
|
+
});
|
|
601
742
|
}
|
|
602
743
|
this.streamedText.delete(key);
|
|
603
744
|
this.streamedReasoning.delete(key);
|
|
@@ -631,7 +772,14 @@ var SessionProjection = class {
|
|
|
631
772
|
const name3 = asString(data["name"]) ?? "tool";
|
|
632
773
|
if (callId === void 0) return [];
|
|
633
774
|
const rawArguments = typeof data["arguments"] === "string" ? data["arguments"] : "{}";
|
|
634
|
-
|
|
775
|
+
let parsedArgs = {};
|
|
776
|
+
try {
|
|
777
|
+
parsedArgs = JSON.parse(rawArguments);
|
|
778
|
+
} catch {
|
|
779
|
+
parsedArgs = { arguments: rawArguments };
|
|
780
|
+
}
|
|
781
|
+
const presented = this.presentCall?.(name3, parsedArgs);
|
|
782
|
+
const facts = presented !== void 0 ? factsFromPresentCall(name3, rawArguments, presented) : classifyToolCall(name3, rawArguments);
|
|
635
783
|
this.toolCalls.set(callId, facts);
|
|
636
784
|
const displayTerminal = this.terminalOutput && facts.kind === "execute";
|
|
637
785
|
return [
|
|
@@ -736,7 +884,7 @@ ${text.replace(/\n+$/, "")}
|
|
|
736
884
|
};
|
|
737
885
|
|
|
738
886
|
// src/bridge/history.ts
|
|
739
|
-
function
|
|
887
|
+
function userMessage(data) {
|
|
740
888
|
if (data === void 0) return void 0;
|
|
741
889
|
const source = data["source"];
|
|
742
890
|
const kind = source !== null && typeof source === "object" ? source["kind"] : void 0;
|
|
@@ -751,20 +899,28 @@ function userText(data) {
|
|
|
751
899
|
}
|
|
752
900
|
}
|
|
753
901
|
const text = parts.join("");
|
|
754
|
-
return text.length > 0 ? text :
|
|
902
|
+
return text.length > 0 ? { text } : {};
|
|
755
903
|
}
|
|
756
904
|
function buildReplay(events) {
|
|
757
905
|
const projection = new SessionProjection();
|
|
758
906
|
const updates = [];
|
|
759
907
|
let plan;
|
|
760
908
|
let title;
|
|
909
|
+
let sawUserInteraction = false;
|
|
761
910
|
for (const event of events) {
|
|
762
911
|
if (event.type === "user/message") {
|
|
763
|
-
const
|
|
764
|
-
if (
|
|
765
|
-
|
|
912
|
+
const user = userMessage(event.data);
|
|
913
|
+
if (user !== void 0) {
|
|
914
|
+
projection.beginPrompt();
|
|
915
|
+
sawUserInteraction = true;
|
|
916
|
+
if (user.text !== void 0) {
|
|
917
|
+
updates.push({
|
|
918
|
+
sessionUpdate: "user_message_chunk",
|
|
919
|
+
content: { type: "text", text: user.text }
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
continue;
|
|
766
923
|
}
|
|
767
|
-
continue;
|
|
768
924
|
}
|
|
769
925
|
if (event.type === "session/title") {
|
|
770
926
|
const t = event.data?.["title"];
|
|
@@ -781,25 +937,132 @@ function buildReplay(events) {
|
|
|
781
937
|
}
|
|
782
938
|
}
|
|
783
939
|
if (plan !== void 0) updates.push(plan);
|
|
940
|
+
const usage = sawUserInteraction ? projection.promptUsage() : void 0;
|
|
941
|
+
if (usage !== void 0) {
|
|
942
|
+
updates.push({
|
|
943
|
+
sessionUpdate: "session_info_update",
|
|
944
|
+
_meta: { dsh: { event: "prompt/usage", usage } }
|
|
945
|
+
});
|
|
946
|
+
}
|
|
784
947
|
return { updates, title, contextWindow: projection.contextWindow };
|
|
785
948
|
}
|
|
786
949
|
|
|
787
950
|
// src/bridge/prompt.ts
|
|
951
|
+
function deliverPrompt(target, message, running) {
|
|
952
|
+
if (running) {
|
|
953
|
+
target.steer(message);
|
|
954
|
+
return "steer";
|
|
955
|
+
}
|
|
956
|
+
target.followup(message);
|
|
957
|
+
return "followup";
|
|
958
|
+
}
|
|
788
959
|
var UnsupportedPromptContentError = class extends Error {
|
|
789
960
|
constructor(contentType) {
|
|
790
961
|
super(`unsupported prompt content type: ${contentType}`);
|
|
791
962
|
this.contentType = contentType;
|
|
963
|
+
this.name = "UnsupportedPromptContentError";
|
|
792
964
|
}
|
|
793
965
|
contentType;
|
|
794
966
|
};
|
|
967
|
+
var PromptImageError = class extends Error {
|
|
968
|
+
constructor(message, options) {
|
|
969
|
+
super(message, options);
|
|
970
|
+
this.name = "PromptImageError";
|
|
971
|
+
}
|
|
972
|
+
};
|
|
795
973
|
function contextBlock(uri, text) {
|
|
796
974
|
return `<context ref=${JSON.stringify(uri)}>
|
|
797
975
|
${text}
|
|
798
976
|
</context>`;
|
|
799
977
|
}
|
|
800
|
-
|
|
978
|
+
var IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
979
|
+
function canonicalImageMediaType(mimeType) {
|
|
980
|
+
const lower = mimeType.trim().toLowerCase();
|
|
981
|
+
const mapped = lower === "image/jpg" ? "image/jpeg" : lower;
|
|
982
|
+
return IMAGE_MEDIA_TYPES.has(mapped) ? mapped : void 0;
|
|
983
|
+
}
|
|
984
|
+
function attachmentIngestOf(value) {
|
|
985
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
986
|
+
const candidate = value;
|
|
987
|
+
if (typeof candidate.validateImage !== "function" || typeof candidate.saveImage !== "function") {
|
|
988
|
+
return void 0;
|
|
989
|
+
}
|
|
990
|
+
const limits = candidate.imageLimits;
|
|
991
|
+
if (limits === void 0 || typeof limits.maxImagesPerMessage !== "number" || typeof limits.maxMessageImageBytes !== "number" || typeof limits.maxImageBytes !== "number") {
|
|
992
|
+
return void 0;
|
|
993
|
+
}
|
|
994
|
+
return candidate;
|
|
995
|
+
}
|
|
996
|
+
function decodeImageData(data) {
|
|
997
|
+
if (data.length === 0) throw new PromptImageError("image data is empty");
|
|
998
|
+
const decoded = Buffer.from(data, "base64");
|
|
999
|
+
if (decoded.byteLength === 0) throw new PromptImageError("image data is empty");
|
|
1000
|
+
return new Uint8Array(decoded);
|
|
1001
|
+
}
|
|
1002
|
+
function imageName(uri) {
|
|
1003
|
+
if (typeof uri !== "string" || uri.length === 0) return void 0;
|
|
1004
|
+
let leaf;
|
|
1005
|
+
try {
|
|
1006
|
+
leaf = new URL(uri).pathname.split("/").filter(Boolean).at(-1);
|
|
1007
|
+
} catch {
|
|
1008
|
+
leaf = uri.split(/[/\\]/).filter(Boolean).at(-1);
|
|
1009
|
+
}
|
|
1010
|
+
if (leaf === void 0 || leaf.length === 0) return void 0;
|
|
1011
|
+
try {
|
|
1012
|
+
return decodeURIComponent(leaf);
|
|
1013
|
+
} catch {
|
|
1014
|
+
return leaf;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function prepareImage(block) {
|
|
1018
|
+
const mediaType = canonicalImageMediaType(block.mimeType);
|
|
1019
|
+
if (mediaType === void 0) {
|
|
1020
|
+
throw new PromptImageError(`unsupported image media type: ${block.mimeType}`);
|
|
1021
|
+
}
|
|
1022
|
+
const data = decodeImageData(block.data);
|
|
1023
|
+
const name3 = imageName(block.uri);
|
|
1024
|
+
return name3 === void 0 ? { data, mediaType } : { data, mediaType, name: name3 };
|
|
1025
|
+
}
|
|
1026
|
+
function saveInput(image) {
|
|
1027
|
+
return image.name === void 0 ? { data: image.data, mediaType: image.mediaType } : { data: image.data, mediaType: image.mediaType, name: image.name };
|
|
1028
|
+
}
|
|
1029
|
+
function flushText(parts, blocks) {
|
|
1030
|
+
const text = parts.join("");
|
|
1031
|
+
parts.length = 0;
|
|
1032
|
+
if (text.length > 0) blocks.push({ type: "text", text });
|
|
1033
|
+
}
|
|
1034
|
+
async function convertPrompt(prompt, attachments) {
|
|
1035
|
+
const preparedImages = [];
|
|
1036
|
+
for (const block of prompt) {
|
|
1037
|
+
if (block.type !== "image") continue;
|
|
1038
|
+
if (attachments === void 0) throw new UnsupportedPromptContentError("image");
|
|
1039
|
+
preparedImages.push(prepareImage(block));
|
|
1040
|
+
}
|
|
1041
|
+
if (preparedImages.length > 0 && attachments !== void 0) {
|
|
1042
|
+
const { maxImagesPerMessage, maxMessageImageBytes, maxImageBytes } = attachments.imageLimits;
|
|
1043
|
+
if (preparedImages.length > maxImagesPerMessage) {
|
|
1044
|
+
throw new PromptImageError("prompt exceeds the configured image-count limit");
|
|
1045
|
+
}
|
|
1046
|
+
const totalBytes = preparedImages.reduce((sum, image) => sum + image.data.byteLength, 0);
|
|
1047
|
+
if (totalBytes > maxMessageImageBytes) {
|
|
1048
|
+
throw new PromptImageError("prompt exceeds the configured aggregate image-byte limit");
|
|
1049
|
+
}
|
|
1050
|
+
for (const image of preparedImages) {
|
|
1051
|
+
if (image.data.byteLength > maxImageBytes) {
|
|
1052
|
+
throw new PromptImageError("image exceeds the configured encoded-byte limit");
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
await attachments.validateImage(saveInput(image));
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1058
|
+
throw new PromptImageError(message, { cause: error });
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
801
1062
|
const parts = [];
|
|
802
1063
|
const display = [];
|
|
1064
|
+
const blocks = [];
|
|
1065
|
+
let imageIndex = 0;
|
|
803
1066
|
for (const block of prompt) {
|
|
804
1067
|
switch (block.type) {
|
|
805
1068
|
case "text":
|
|
@@ -823,20 +1086,542 @@ ${contextBlock(resource.uri, resource.text)}
|
|
|
823
1086
|
}
|
|
824
1087
|
throw new UnsupportedPromptContentError("resource (binary)");
|
|
825
1088
|
}
|
|
1089
|
+
case "image": {
|
|
1090
|
+
if (attachments === void 0) throw new UnsupportedPromptContentError("image");
|
|
1091
|
+
const prepared = preparedImages[imageIndex];
|
|
1092
|
+
imageIndex += 1;
|
|
1093
|
+
if (prepared === void 0) throw new PromptImageError("image block was not prepared");
|
|
1094
|
+
flushText(parts, blocks);
|
|
1095
|
+
let attachment;
|
|
1096
|
+
try {
|
|
1097
|
+
attachment = await attachments.saveImage(saveInput(prepared));
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1100
|
+
throw new PromptImageError(message, { cause: error });
|
|
1101
|
+
}
|
|
1102
|
+
blocks.push({ type: "image", attachment });
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
826
1105
|
default:
|
|
827
1106
|
throw new UnsupportedPromptContentError(block.type);
|
|
828
1107
|
}
|
|
829
1108
|
}
|
|
830
|
-
|
|
1109
|
+
flushText(parts, blocks);
|
|
831
1110
|
return {
|
|
832
|
-
blocks
|
|
1111
|
+
blocks,
|
|
833
1112
|
displayText: display.join(" ").trim()
|
|
834
1113
|
};
|
|
835
1114
|
}
|
|
836
1115
|
|
|
1116
|
+
// src/bridge/rpc.ts
|
|
1117
|
+
var AcpRpc = class {
|
|
1118
|
+
handlers = /* @__PURE__ */ new Map();
|
|
1119
|
+
writeJson;
|
|
1120
|
+
/**
|
|
1121
|
+
* Bind the mux writer. Called once from {@link muxAcpStream}.
|
|
1122
|
+
* @param writeJson - write one JSON-RPC message onto the ACP stream.
|
|
1123
|
+
*/
|
|
1124
|
+
attachWriter(writeJson) {
|
|
1125
|
+
this.writeJson = writeJson;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Register a bridge-owned request method intercepted before the ACP SDK.
|
|
1129
|
+
* @param method - JSON-RPC method name (e.g. `tui/refresh`).
|
|
1130
|
+
* @param handler - implementation.
|
|
1131
|
+
* @returns disposer.
|
|
1132
|
+
*/
|
|
1133
|
+
registerMethod(method, handler) {
|
|
1134
|
+
if (this.handlers.has(method)) {
|
|
1135
|
+
throw new Error(`acpRpc: method ${JSON.stringify(method)} is already registered`);
|
|
1136
|
+
}
|
|
1137
|
+
this.handlers.set(method, handler);
|
|
1138
|
+
return () => {
|
|
1139
|
+
this.handlers.delete(method);
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* @param method - JSON-RPC method name.
|
|
1144
|
+
* @returns whether a TUI/extension handler owns this request.
|
|
1145
|
+
*/
|
|
1146
|
+
has(method) {
|
|
1147
|
+
return this.handlers.has(method);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Dispatch one intercepted request and write the JSON-RPC response.
|
|
1151
|
+
* @param id - JSON-RPC id.
|
|
1152
|
+
* @param method - method name.
|
|
1153
|
+
* @param params - params object.
|
|
1154
|
+
*/
|
|
1155
|
+
async dispatch(id, method, params) {
|
|
1156
|
+
const handler = this.handlers.get(method);
|
|
1157
|
+
const write = this.writeJson;
|
|
1158
|
+
if (handler === void 0 || write === void 0) return;
|
|
1159
|
+
try {
|
|
1160
|
+
const result = await handler(params);
|
|
1161
|
+
write({ jsonrpc: "2.0", id, result: result ?? null });
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1164
|
+
write({ jsonrpc: "2.0", id, error: { code: -32e3, message } });
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Send a JSON-RPC notification on the ACP stream (e.g. `tui/snapshot`).
|
|
1169
|
+
* @param method - notification method.
|
|
1170
|
+
* @param params - payload.
|
|
1171
|
+
*/
|
|
1172
|
+
notify(method, params) {
|
|
1173
|
+
const write = this.writeJson;
|
|
1174
|
+
if (write === void 0) return;
|
|
1175
|
+
write(params === void 0 ? { jsonrpc: "2.0", method } : { jsonrpc: "2.0", method, params });
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
function isInterceptedRequest(message, rpc) {
|
|
1179
|
+
return "method" in message && typeof message.method === "string" && "id" in message && message.id !== void 0 && rpc.has(message.method);
|
|
1180
|
+
}
|
|
1181
|
+
function muxAcpStream(stream, rpc) {
|
|
1182
|
+
const dest = stream.writable.getWriter();
|
|
1183
|
+
const writeMessage = (value) => {
|
|
1184
|
+
void dest.write(value).catch(() => {
|
|
1185
|
+
});
|
|
1186
|
+
};
|
|
1187
|
+
rpc.attachWriter(writeMessage);
|
|
1188
|
+
const acpWritable = new WritableStream({
|
|
1189
|
+
write(chunk) {
|
|
1190
|
+
return dest.write(chunk);
|
|
1191
|
+
},
|
|
1192
|
+
close() {
|
|
1193
|
+
return dest.close();
|
|
1194
|
+
},
|
|
1195
|
+
abort(reason) {
|
|
1196
|
+
return dest.abort(reason);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
const { readable, writable } = new TransformStream({
|
|
1200
|
+
transform(message, controller) {
|
|
1201
|
+
if (isInterceptedRequest(message, rpc)) {
|
|
1202
|
+
void rpc.dispatch(message.id, message.method, message.params);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
controller.enqueue(message);
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
void stream.readable.pipeTo(writable).catch(() => {
|
|
1209
|
+
});
|
|
1210
|
+
return { readable, writable: acpWritable };
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/bridge/tui-client.ts
|
|
1214
|
+
function installTuiClientPlane(ctx, rpc, opts) {
|
|
1215
|
+
const { findAgent, advertisement } = opts;
|
|
1216
|
+
ctx.effect(
|
|
1217
|
+
() => rpc.registerMethod("tui/inspect-sync", (params) => {
|
|
1218
|
+
const inspect = optionalInspect(ctx);
|
|
1219
|
+
if (inspect === void 0) {
|
|
1220
|
+
throw new Error("this agent has no Cordis inspect registry");
|
|
1221
|
+
}
|
|
1222
|
+
const providers = readProviders(params);
|
|
1223
|
+
inspect.syncClientManifest(providers);
|
|
1224
|
+
advertisement.advertised = true;
|
|
1225
|
+
return { ok: true };
|
|
1226
|
+
}),
|
|
1227
|
+
"acp-bridge: tui/inspect-sync"
|
|
1228
|
+
);
|
|
1229
|
+
ctx.effect(
|
|
1230
|
+
() => rpc.registerMethod("tui/inspect-resolve", (params) => {
|
|
1231
|
+
const inspect = optionalInspect(ctx);
|
|
1232
|
+
if (inspect === void 0) {
|
|
1233
|
+
throw new Error("this agent has no Cordis inspect registry");
|
|
1234
|
+
}
|
|
1235
|
+
const body = asObject(params);
|
|
1236
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1237
|
+
const requestId = readString(body, "requestId");
|
|
1238
|
+
return inspect.resolveClientQuery(agent, requestId, body["resolution"]);
|
|
1239
|
+
}),
|
|
1240
|
+
"acp-bridge: tui/inspect-resolve"
|
|
1241
|
+
);
|
|
1242
|
+
ctx.effect(
|
|
1243
|
+
() => rpc.registerMethod("tui/run-host-half", async (params) => {
|
|
1244
|
+
const runner = optionalRunner(ctx);
|
|
1245
|
+
if (runner === void 0) {
|
|
1246
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1247
|
+
}
|
|
1248
|
+
const body = asObject(params);
|
|
1249
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1250
|
+
const requestId = body["requestId"];
|
|
1251
|
+
return await runner.runHostHalf(
|
|
1252
|
+
agent,
|
|
1253
|
+
readString(body, "pluginId"),
|
|
1254
|
+
readString(body, "packageId"),
|
|
1255
|
+
readString(body, "mode"),
|
|
1256
|
+
typeof requestId === "string" ? requestId : null,
|
|
1257
|
+
body["approveFutureVersions"] === true
|
|
1258
|
+
);
|
|
1259
|
+
}),
|
|
1260
|
+
"acp-bridge: tui/run-host-half"
|
|
1261
|
+
);
|
|
1262
|
+
ctx.effect(
|
|
1263
|
+
() => rpc.registerMethod("tui/get-client-code", (params) => {
|
|
1264
|
+
const runner = optionalRunner(ctx);
|
|
1265
|
+
if (runner === void 0) {
|
|
1266
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1267
|
+
}
|
|
1268
|
+
const body = asObject(params);
|
|
1269
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1270
|
+
return runner.getClientCode(
|
|
1271
|
+
agent,
|
|
1272
|
+
readString(body, "pluginId"),
|
|
1273
|
+
readString(body, "pluginRunId")
|
|
1274
|
+
);
|
|
1275
|
+
}),
|
|
1276
|
+
"acp-bridge: tui/get-client-code"
|
|
1277
|
+
);
|
|
1278
|
+
ctx.effect(
|
|
1279
|
+
() => rpc.registerMethod("tui/resolve-request-run", async (params) => {
|
|
1280
|
+
const runner = optionalRunner(ctx);
|
|
1281
|
+
if (runner === void 0) {
|
|
1282
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1283
|
+
}
|
|
1284
|
+
const body = asObject(params);
|
|
1285
|
+
return await runner.resolveRequestRun(
|
|
1286
|
+
readString(body, "requestId"),
|
|
1287
|
+
body["resolution"]
|
|
1288
|
+
);
|
|
1289
|
+
}),
|
|
1290
|
+
"acp-bridge: tui/resolve-request-run"
|
|
1291
|
+
);
|
|
1292
|
+
ctx.effect(
|
|
1293
|
+
() => rpc.registerMethod("tui/invoke", async (params) => {
|
|
1294
|
+
const runner = optionalRunner(ctx);
|
|
1295
|
+
if (runner === void 0) {
|
|
1296
|
+
throw new Error("this agent has no dynamic Cordis runner");
|
|
1297
|
+
}
|
|
1298
|
+
const body = asObject(params);
|
|
1299
|
+
requireAgent(findAgent, readString(body, "agentId"));
|
|
1300
|
+
return await runner.invoke(
|
|
1301
|
+
readString(body, "pluginId"),
|
|
1302
|
+
readString(body, "pluginRunId"),
|
|
1303
|
+
readString(body, "method"),
|
|
1304
|
+
body["args"] ?? null
|
|
1305
|
+
);
|
|
1306
|
+
}),
|
|
1307
|
+
"acp-bridge: tui/invoke"
|
|
1308
|
+
);
|
|
1309
|
+
ctx.effect(
|
|
1310
|
+
() => rpc.registerMethod("tui/list-plugins", (params) => {
|
|
1311
|
+
const runner = requireRunnerMethod(ctx, "listPlugins");
|
|
1312
|
+
const body = asObject(params);
|
|
1313
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1314
|
+
return runner.listPlugins(agent);
|
|
1315
|
+
}),
|
|
1316
|
+
"acp-bridge: tui/list-plugins"
|
|
1317
|
+
);
|
|
1318
|
+
ctx.effect(
|
|
1319
|
+
() => rpc.registerMethod("tui/stop-plugin", async (params) => {
|
|
1320
|
+
const runner = requireRunnerMethod(ctx, "stopFromPanel");
|
|
1321
|
+
const body = asObject(params);
|
|
1322
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1323
|
+
return await runner.stopFromPanel(agent, readString(body, "pluginId"));
|
|
1324
|
+
}),
|
|
1325
|
+
"acp-bridge: tui/stop-plugin"
|
|
1326
|
+
);
|
|
1327
|
+
ctx.effect(
|
|
1328
|
+
() => rpc.registerMethod("tui/start-plugin", async (params) => {
|
|
1329
|
+
const runner = requireRunnerMethod(ctx, "listPlugins");
|
|
1330
|
+
const body = asObject(params);
|
|
1331
|
+
const agentId = readString(body, "agentId");
|
|
1332
|
+
const agent = requireAgent(findAgent, agentId);
|
|
1333
|
+
const pluginId = readString(body, "pluginId");
|
|
1334
|
+
const plugin = runner.listPlugins(agent).find((row) => row.pluginId === pluginId);
|
|
1335
|
+
if (plugin === void 0) throw new Error(`dynamic plugin "${pluginId}" does not exist`);
|
|
1336
|
+
const packageId = plugin.currentPackageId;
|
|
1337
|
+
if (packageId === void 0) {
|
|
1338
|
+
throw new Error(`dynamic plugin "${pluginId}" has no current package to restore`);
|
|
1339
|
+
}
|
|
1340
|
+
const pkg = plugin.packages.find((row) => row.packageId === packageId);
|
|
1341
|
+
if (pkg === void 0) {
|
|
1342
|
+
throw new Error(`dynamic plugin "${pluginId}" current package "${packageId}" does not exist`);
|
|
1343
|
+
}
|
|
1344
|
+
const started = asObject(await runner.runHostHalf(
|
|
1345
|
+
agent,
|
|
1346
|
+
pluginId,
|
|
1347
|
+
packageId,
|
|
1348
|
+
"run",
|
|
1349
|
+
null,
|
|
1350
|
+
false
|
|
1351
|
+
));
|
|
1352
|
+
if (started["ok"] !== true) return started;
|
|
1353
|
+
const pluginRunId = readString(started, "pluginRunId");
|
|
1354
|
+
const startedHere = started["startedHere"] !== false;
|
|
1355
|
+
if (pkg.hasClientHalf) {
|
|
1356
|
+
rpc.notify("tui/user-run", {
|
|
1357
|
+
agentId,
|
|
1358
|
+
pluginId,
|
|
1359
|
+
packageId,
|
|
1360
|
+
pluginRunId,
|
|
1361
|
+
startedHere,
|
|
1362
|
+
mode: "run",
|
|
1363
|
+
hasClientHalf: true
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
return { ok: true, status: "starting", pluginId, packageId, pluginRunId };
|
|
1367
|
+
}),
|
|
1368
|
+
"acp-bridge: tui/start-plugin"
|
|
1369
|
+
);
|
|
1370
|
+
ctx.effect(
|
|
1371
|
+
() => rpc.registerMethod("tui/settle-user-run", async (params) => {
|
|
1372
|
+
const runner = requireRunnerMethod(ctx, "settleUserRun");
|
|
1373
|
+
const body = asObject(params);
|
|
1374
|
+
const agent = requireAgent(findAgent, readString(body, "agentId"));
|
|
1375
|
+
return await runner.settleUserRun(
|
|
1376
|
+
agent,
|
|
1377
|
+
readString(body, "pluginId"),
|
|
1378
|
+
body["resolution"]
|
|
1379
|
+
);
|
|
1380
|
+
}),
|
|
1381
|
+
"acp-bridge: tui/settle-user-run"
|
|
1382
|
+
);
|
|
1383
|
+
ctx.on("cordis/inspect-query", (request) => {
|
|
1384
|
+
if (!advertisement.advertised) return;
|
|
1385
|
+
rpc.notify("tui/inspect-query", request);
|
|
1386
|
+
});
|
|
1387
|
+
ctx.on("cordis/inspect-query-resolved", (resolved) => {
|
|
1388
|
+
if (!advertisement.advertised) return;
|
|
1389
|
+
rpc.notify("tui/inspect-query-resolved", resolved);
|
|
1390
|
+
});
|
|
1391
|
+
ctx.on("cordis/request-run", (request) => {
|
|
1392
|
+
if (!advertisement.advertised) return;
|
|
1393
|
+
rpc.notify("tui/request-run", request);
|
|
1394
|
+
});
|
|
1395
|
+
ctx.on("cordis/request-run-resolved", (resolved) => {
|
|
1396
|
+
if (!advertisement.advertised) return;
|
|
1397
|
+
rpc.notify("tui/request-run-resolved", resolved);
|
|
1398
|
+
});
|
|
1399
|
+
ctx.on("cordis/dynamic-retract", (retracted) => {
|
|
1400
|
+
if (!advertisement.advertised) return;
|
|
1401
|
+
rpc.notify("tui/dynamic-retract", retracted);
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
function optionalInspect(ctx) {
|
|
1405
|
+
try {
|
|
1406
|
+
const value = ctx.get("cordisInspect");
|
|
1407
|
+
return value !== void 0 && typeof value.syncClientManifest === "function" ? value : void 0;
|
|
1408
|
+
} catch {
|
|
1409
|
+
return void 0;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function optionalRunner(ctx) {
|
|
1413
|
+
try {
|
|
1414
|
+
const value = ctx.get("dynamicCordisRunner");
|
|
1415
|
+
return value !== void 0 && typeof value.runHostHalf === "function" ? value : void 0;
|
|
1416
|
+
} catch {
|
|
1417
|
+
return void 0;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
function requireRunnerMethod(ctx, method) {
|
|
1421
|
+
const runner = optionalRunner(ctx);
|
|
1422
|
+
if (runner === void 0 || typeof runner[method] !== "function") {
|
|
1423
|
+
throw new Error(`this agent has no dynamic Cordis runner method ${String(method)}`);
|
|
1424
|
+
}
|
|
1425
|
+
return runner;
|
|
1426
|
+
}
|
|
1427
|
+
function requireAgent(findAgent, agentId) {
|
|
1428
|
+
const agent = findAgent(agentId);
|
|
1429
|
+
if (agent === void 0) throw new Error(`unknown agent: ${agentId}`);
|
|
1430
|
+
return agent;
|
|
1431
|
+
}
|
|
1432
|
+
function asObject(value) {
|
|
1433
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
1434
|
+
throw new Error("params must be an object");
|
|
1435
|
+
}
|
|
1436
|
+
return value;
|
|
1437
|
+
}
|
|
1438
|
+
function readString(body, field) {
|
|
1439
|
+
const value = body[field];
|
|
1440
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1441
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
1442
|
+
}
|
|
1443
|
+
return value;
|
|
1444
|
+
}
|
|
1445
|
+
function readProviders(params) {
|
|
1446
|
+
const body = asObject(params);
|
|
1447
|
+
const providers = body["providers"];
|
|
1448
|
+
if (!Array.isArray(providers)) throw new Error("providers must be an array");
|
|
1449
|
+
return providers.map((provider, index) => {
|
|
1450
|
+
if (provider === null || typeof provider !== "object" || Array.isArray(provider)) {
|
|
1451
|
+
throw new Error(`providers[${index}] must be an object`);
|
|
1452
|
+
}
|
|
1453
|
+
const row = provider;
|
|
1454
|
+
if (typeof row["id"] !== "string" || row["id"].trim() === "") {
|
|
1455
|
+
throw new Error(`providers[${index}].id must be a non-empty string`);
|
|
1456
|
+
}
|
|
1457
|
+
if (typeof row["description"] !== "string" || row["description"].trim() === "") {
|
|
1458
|
+
throw new Error(`providers[${index}].description must be a non-empty string`);
|
|
1459
|
+
}
|
|
1460
|
+
if (!Array.isArray(row["methods"])) {
|
|
1461
|
+
throw new Error(`providers[${index}].methods must be an array`);
|
|
1462
|
+
}
|
|
1463
|
+
return provider;
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// src/bridge/user-questions.ts
|
|
1468
|
+
import {
|
|
1469
|
+
UserQuestionError
|
|
1470
|
+
} from "@deepseek-ai/dsh-user-questions";
|
|
1471
|
+
var META_KEY = "dsh.userQuestions";
|
|
1472
|
+
function fieldName(index) {
|
|
1473
|
+
return `question_${index}`;
|
|
1474
|
+
}
|
|
1475
|
+
function customFieldName(index) {
|
|
1476
|
+
return `${fieldName(index)}_custom`;
|
|
1477
|
+
}
|
|
1478
|
+
function optionValue(index) {
|
|
1479
|
+
return `option_${index}`;
|
|
1480
|
+
}
|
|
1481
|
+
function customValue(index) {
|
|
1482
|
+
return `custom_${index}`;
|
|
1483
|
+
}
|
|
1484
|
+
function descriptionOf(question) {
|
|
1485
|
+
const parts = [question.header === void 0 ? void 0 : question.question, question.detail].filter((part) => part !== void 0 && part.length > 0);
|
|
1486
|
+
return parts.length === 0 ? void 0 : parts.join("\n\n");
|
|
1487
|
+
}
|
|
1488
|
+
function propertyOf(question, index) {
|
|
1489
|
+
const title = question.header ?? question.question;
|
|
1490
|
+
const description = descriptionOf(question);
|
|
1491
|
+
const options = question.options ?? [];
|
|
1492
|
+
if (options.length === 0) {
|
|
1493
|
+
return {
|
|
1494
|
+
type: "string",
|
|
1495
|
+
title,
|
|
1496
|
+
...description === void 0 ? {} : { description }
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
const choices = [
|
|
1500
|
+
...options.map((option, optionIndex) => ({
|
|
1501
|
+
const: optionValue(optionIndex),
|
|
1502
|
+
title: option.label,
|
|
1503
|
+
...option.description === void 0 ? {} : { description: option.description }
|
|
1504
|
+
})),
|
|
1505
|
+
{ const: customValue(index), title: "Other" }
|
|
1506
|
+
];
|
|
1507
|
+
if (question.multiSelect === true) {
|
|
1508
|
+
return {
|
|
1509
|
+
type: "array",
|
|
1510
|
+
title,
|
|
1511
|
+
...description === void 0 ? {} : { description },
|
|
1512
|
+
minItems: 1,
|
|
1513
|
+
items: { anyOf: choices }
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
type: "string",
|
|
1518
|
+
title,
|
|
1519
|
+
...description === void 0 ? {} : { description },
|
|
1520
|
+
oneOf: choices
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
function questionsToElicitation(questions, sessionId) {
|
|
1524
|
+
const properties = {};
|
|
1525
|
+
const required = [];
|
|
1526
|
+
questions.forEach((question, index) => {
|
|
1527
|
+
const field = fieldName(index);
|
|
1528
|
+
properties[field] = propertyOf(question, index);
|
|
1529
|
+
if ((question.options?.length ?? 0) > 0) {
|
|
1530
|
+
properties[customFieldName(index)] = {
|
|
1531
|
+
type: "string",
|
|
1532
|
+
title: "Other",
|
|
1533
|
+
description: "Type a custom answer."
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1537
|
+
return {
|
|
1538
|
+
mode: "form",
|
|
1539
|
+
sessionId,
|
|
1540
|
+
message: "The agent needs your input.",
|
|
1541
|
+
requestedSchema: {
|
|
1542
|
+
type: "object",
|
|
1543
|
+
properties,
|
|
1544
|
+
required
|
|
1545
|
+
},
|
|
1546
|
+
_meta: {
|
|
1547
|
+
[META_KEY]: {
|
|
1548
|
+
version: 1,
|
|
1549
|
+
questions
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
function answerFromElicitation(questions, response) {
|
|
1555
|
+
if (response.action !== "accept") return void 0;
|
|
1556
|
+
const content = response.content ?? {};
|
|
1557
|
+
return {
|
|
1558
|
+
answers: questions.map((question, index) => {
|
|
1559
|
+
const options = question.options ?? [];
|
|
1560
|
+
const raw = content[fieldName(index)];
|
|
1561
|
+
if (options.length === 0) {
|
|
1562
|
+
const custom2 = typeof raw === "string" ? raw.trim() : "";
|
|
1563
|
+
return {
|
|
1564
|
+
id: question.id,
|
|
1565
|
+
selected: [],
|
|
1566
|
+
...custom2.length === 0 ? {} : { custom: custom2 }
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
const values = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
1570
|
+
const selected = values.flatMap((value) => {
|
|
1571
|
+
const match = /^option_(\d+)$/.exec(value);
|
|
1572
|
+
if (match === null) return [];
|
|
1573
|
+
const option = options[Number(match[1])];
|
|
1574
|
+
return option === void 0 ? [] : [option.label];
|
|
1575
|
+
});
|
|
1576
|
+
const customRaw = content[customFieldName(index)];
|
|
1577
|
+
const custom = typeof customRaw === "string" ? customRaw.trim() : "";
|
|
1578
|
+
return {
|
|
1579
|
+
id: question.id,
|
|
1580
|
+
selected: custom.length > 0 && question.multiSelect !== true ? [] : selected,
|
|
1581
|
+
...custom.length === 0 ? {} : { custom }
|
|
1582
|
+
};
|
|
1583
|
+
})
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
async function askUserQuestionsOverAcp(request, sessionId, create) {
|
|
1587
|
+
const response = await create(questionsToElicitation(request.questions, sessionId));
|
|
1588
|
+
const answer = answerFromElicitation(request.questions, response);
|
|
1589
|
+
if (answer === void 0) {
|
|
1590
|
+
throw new UserQuestionError("the user cancelled ask_user_question", "ASK_CANCELLED");
|
|
1591
|
+
}
|
|
1592
|
+
return answer;
|
|
1593
|
+
}
|
|
1594
|
+
function installAcpUserQuestionProvider(service, options) {
|
|
1595
|
+
return service.registerProvider({
|
|
1596
|
+
async ask(request) {
|
|
1597
|
+
if (!options.formSupported()) {
|
|
1598
|
+
throw new UserQuestionError(
|
|
1599
|
+
"the ACP client does not support form elicitation",
|
|
1600
|
+
"CLIENT_UNSUPPORTED"
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
const sessionId = options.sessionIdForRequest(request);
|
|
1604
|
+
if (sessionId === void 0) {
|
|
1605
|
+
throw new UserQuestionError(
|
|
1606
|
+
"ACP user interaction requires a live root session",
|
|
1607
|
+
"ASK_MISSING_AGENT"
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
return askUserQuestionsOverAcp(request, sessionId, options.create);
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// src/bridge/presets.ts
|
|
1616
|
+
function presetDisplayName(preset) {
|
|
1617
|
+
if (preset.id === "cordis") return "Creator";
|
|
1618
|
+
const name3 = preset.name?.trim();
|
|
1619
|
+
return name3 !== void 0 && name3.length > 0 ? name3 : preset.id;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
837
1622
|
// src/bridge/index.ts
|
|
838
1623
|
var name = "acp-bridge";
|
|
839
|
-
var inject = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents"];
|
|
1624
|
+
var inject = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents", "userQuestions"];
|
|
840
1625
|
function invalidParams(detail) {
|
|
841
1626
|
return RequestError.invalidParams(void 0, detail);
|
|
842
1627
|
}
|
|
@@ -869,13 +1654,30 @@ async function apply(ctx, config = {}) {
|
|
|
869
1654
|
const agentPresets = ctx.agentPresets;
|
|
870
1655
|
const skillRegistry = ctx.skills;
|
|
871
1656
|
const subagents = ctx.subagents;
|
|
1657
|
+
const userQuestions = ctx.userQuestions;
|
|
872
1658
|
const harness = config.harness ?? (await Promise.resolve().then(() => (init_self_harness(), self_harness_exports))).selfHarness();
|
|
873
1659
|
const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
|
|
874
1660
|
const SANDBOX_MODES2 = harness.sandboxModes;
|
|
875
1661
|
const sessions = /* @__PURE__ */ new Map();
|
|
1662
|
+
const subagentByChild = /* @__PURE__ */ new Map();
|
|
1663
|
+
const subagentByRun = /* @__PURE__ */ new Map();
|
|
1664
|
+
const watchedSubagentParents = /* @__PURE__ */ new WeakSet();
|
|
876
1665
|
let closed = false;
|
|
877
1666
|
let conn;
|
|
878
1667
|
let clientTerminalOutput = false;
|
|
1668
|
+
let clientSubagentTranscript = false;
|
|
1669
|
+
let clientElicitationForm = false;
|
|
1670
|
+
const tuiClient = { advertised: false };
|
|
1671
|
+
const rpc = new AcpRpc();
|
|
1672
|
+
const findAgent = (agentId) => {
|
|
1673
|
+
const fromRegistry = agents.get(agentId);
|
|
1674
|
+
if (fromRegistry !== void 0) return fromRegistry;
|
|
1675
|
+
for (const record of sessions.values()) {
|
|
1676
|
+
if (record.agent.id === agentId) return record.agent;
|
|
1677
|
+
}
|
|
1678
|
+
return void 0;
|
|
1679
|
+
};
|
|
1680
|
+
installTuiClientPlane(ctx, rpc, { findAgent, advertisement: tuiClient });
|
|
879
1681
|
const modelCandidates = () => {
|
|
880
1682
|
const seen = /* @__PURE__ */ new Set();
|
|
881
1683
|
if (config.model !== void 0) seen.add(config.model);
|
|
@@ -1062,9 +1864,9 @@ async function apply(ctx, config = {}) {
|
|
|
1062
1864
|
};
|
|
1063
1865
|
const listProviderRoutes = async () => {
|
|
1064
1866
|
try {
|
|
1065
|
-
const
|
|
1066
|
-
if (!Array.isArray(
|
|
1067
|
-
return
|
|
1867
|
+
const raw2 = llm.listProviders();
|
|
1868
|
+
if (!Array.isArray(raw2)) return [];
|
|
1869
|
+
return raw2.flatMap((entry) => {
|
|
1068
1870
|
if (!entry || typeof entry !== "object") return [];
|
|
1069
1871
|
const id = entry.id;
|
|
1070
1872
|
if (typeof id !== "string" || id.length === 0) return [];
|
|
@@ -1201,13 +2003,78 @@ async function apply(ctx, config = {}) {
|
|
|
1201
2003
|
record.inflight = void 0;
|
|
1202
2004
|
inflight.resolve(reason);
|
|
1203
2005
|
};
|
|
2006
|
+
const watchSubagentParent = (parent) => {
|
|
2007
|
+
if (watchedSubagentParents.has(parent)) return;
|
|
2008
|
+
watchedSubagentParents.add(parent);
|
|
2009
|
+
parent.ctx.on("subagent/start", (info) => {
|
|
2010
|
+
const parentLink = subagentByChild.get(String(parent.id));
|
|
2011
|
+
const rootSessionId = parentLink?.rootSessionId ?? String(parent.session.id);
|
|
2012
|
+
const record = sessions.get(rootSessionId);
|
|
2013
|
+
if (record === void 0) return;
|
|
2014
|
+
const runId = String(info.runId);
|
|
2015
|
+
const childSessionId = String(info.id);
|
|
2016
|
+
const toolCallId = `subagent:${runId}`;
|
|
2017
|
+
const payload = {
|
|
2018
|
+
runId,
|
|
2019
|
+
provider: info.provider,
|
|
2020
|
+
id: childSessionId,
|
|
2021
|
+
local: info.local,
|
|
2022
|
+
...parentLink !== void 0 ? { parentToolCallId: parentLink.toolCallId } : {}
|
|
2023
|
+
};
|
|
2024
|
+
for (const update of record.projection.onEvent({ type: "subagent/start", data: payload })) {
|
|
2025
|
+
notify(rootSessionId, update);
|
|
2026
|
+
}
|
|
2027
|
+
const child = agents.get(info.id);
|
|
2028
|
+
const link = {
|
|
2029
|
+
rootSessionId,
|
|
2030
|
+
childSessionId,
|
|
2031
|
+
runId,
|
|
2032
|
+
provider: info.provider,
|
|
2033
|
+
toolCallId,
|
|
2034
|
+
projection: new SessionProjection(void 0, {
|
|
2035
|
+
terminalOutput: clientTerminalOutput,
|
|
2036
|
+
...child?.session.header.cwd !== void 0 ? { cwd: child.session.header.cwd } : {},
|
|
2037
|
+
subagent: { childSessionId, parentToolCallId: toolCallId, provider: info.provider }
|
|
2038
|
+
})
|
|
2039
|
+
};
|
|
2040
|
+
subagentByChild.set(childSessionId, link);
|
|
2041
|
+
subagentByRun.set(runId, link);
|
|
2042
|
+
if (child !== void 0) watchSubagentParent(child);
|
|
2043
|
+
});
|
|
2044
|
+
parent.ctx.on("subagent/end", (info) => {
|
|
2045
|
+
const runId = String(info.runId);
|
|
2046
|
+
const link = subagentByRun.get(runId);
|
|
2047
|
+
if (link === void 0) return;
|
|
2048
|
+
const record = sessions.get(link.rootSessionId);
|
|
2049
|
+
if (record !== void 0) {
|
|
2050
|
+
const parentLink = subagentByChild.get(String(parent.id));
|
|
2051
|
+
const payload = {
|
|
2052
|
+
runId,
|
|
2053
|
+
provider: info.provider,
|
|
2054
|
+
id: String(info.id),
|
|
2055
|
+
local: info.local,
|
|
2056
|
+
stopReason: info.stopReason,
|
|
2057
|
+
...info.lastAssistantMessage !== void 0 ? { lastAssistantMessage: info.lastAssistantMessage } : {},
|
|
2058
|
+
...parentLink !== void 0 ? { parentToolCallId: parentLink.toolCallId } : {}
|
|
2059
|
+
};
|
|
2060
|
+
for (const update of record.projection.onEvent({ type: "subagent/end", data: payload })) {
|
|
2061
|
+
notify(link.rootSessionId, update);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
subagentByRun.delete(runId);
|
|
2065
|
+
if (subagentByChild.get(link.childSessionId)?.runId === runId) {
|
|
2066
|
+
subagentByChild.delete(link.childSessionId);
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
};
|
|
1204
2070
|
const presetsService = () => agentPresets;
|
|
1205
2071
|
const presetSetup = (presets, presetId) => {
|
|
1206
2072
|
if (presetId === void 0) return void 0;
|
|
1207
2073
|
return async (agentCtx) => {
|
|
2074
|
+
const started = performance.now();
|
|
1208
2075
|
logDebug(`preset setup: mounting "${presetId}"`);
|
|
1209
2076
|
await presets.mount(agentCtx, presetId);
|
|
1210
|
-
logDebug(`preset setup: mounted "${presetId}"`);
|
|
2077
|
+
logDebug(`preset setup: mounted "${presetId}" in ${(performance.now() - started).toFixed(1)}ms`);
|
|
1211
2078
|
};
|
|
1212
2079
|
};
|
|
1213
2080
|
const presetFromLog = (header, events) => {
|
|
@@ -1219,7 +2086,6 @@ async function apply(ctx, config = {}) {
|
|
|
1219
2086
|
}
|
|
1220
2087
|
return header?.agentPreset;
|
|
1221
2088
|
};
|
|
1222
|
-
const presetLabel = (id) => id.charAt(0).toUpperCase() + id.slice(1);
|
|
1223
2089
|
const modeState = (record) => {
|
|
1224
2090
|
const permission = permissionService();
|
|
1225
2091
|
if (permission.names.length > 0) {
|
|
@@ -1363,6 +2229,19 @@ async function apply(ctx, config = {}) {
|
|
|
1363
2229
|
options: levels
|
|
1364
2230
|
});
|
|
1365
2231
|
}
|
|
2232
|
+
if (commandRuntime.list(record.agent).some((command) => command.name === "plan")) {
|
|
2233
|
+
const active = [...record.agent.session.events].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
|
|
2234
|
+
result.push({
|
|
2235
|
+
type: "select",
|
|
2236
|
+
id: "collaboration_mode",
|
|
2237
|
+
name: "Collaboration mode",
|
|
2238
|
+
currentValue: active ? "plan" : "default",
|
|
2239
|
+
options: [
|
|
2240
|
+
{ value: "default", name: "Default" },
|
|
2241
|
+
{ value: "plan", name: "Plan" }
|
|
2242
|
+
]
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
1366
2245
|
const models = await modelChoices(record);
|
|
1367
2246
|
if (models.length >= 2) {
|
|
1368
2247
|
result.push({
|
|
@@ -1413,7 +2292,7 @@ async function apply(ctx, config = {}) {
|
|
|
1413
2292
|
currentValue: record.preset,
|
|
1414
2293
|
options: roster.map((preset) => ({
|
|
1415
2294
|
value: preset.id,
|
|
1416
|
-
name:
|
|
2295
|
+
name: presetDisplayName(preset),
|
|
1417
2296
|
description: `Agent preset \u201C${preset.id}\u201D`
|
|
1418
2297
|
}))
|
|
1419
2298
|
});
|
|
@@ -1442,7 +2321,18 @@ async function apply(ctx, config = {}) {
|
|
|
1442
2321
|
list.push({
|
|
1443
2322
|
name: descriptor.name,
|
|
1444
2323
|
description: descriptor.description,
|
|
1445
|
-
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
|
|
2324
|
+
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {},
|
|
2325
|
+
...descriptor.name === "plan" ? {
|
|
2326
|
+
_meta: {
|
|
2327
|
+
commandAction: {
|
|
2328
|
+
kind: "setConfigOption",
|
|
2329
|
+
configId: "collaboration_mode",
|
|
2330
|
+
value: "plan",
|
|
2331
|
+
resetValue: "default",
|
|
2332
|
+
presentation: "state"
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
} : {}
|
|
1446
2336
|
});
|
|
1447
2337
|
}
|
|
1448
2338
|
} catch (error) {
|
|
@@ -1618,16 +2508,27 @@ async function apply(ctx, config = {}) {
|
|
|
1618
2508
|
inflight: void 0
|
|
1619
2509
|
};
|
|
1620
2510
|
sessions.set(sessionId, record);
|
|
2511
|
+
watchSubagentParent(agent);
|
|
1621
2512
|
return record;
|
|
1622
2513
|
};
|
|
1623
2514
|
ctx.on("session/event", (session, event) => {
|
|
1624
2515
|
const sessionId = String(session.header.id);
|
|
1625
2516
|
const record = sessions.get(sessionId);
|
|
1626
|
-
if (record === void 0 || record.agent.session !== session)
|
|
2517
|
+
if (record === void 0 || record.agent.session !== session) {
|
|
2518
|
+
const child = subagentByChild.get(sessionId);
|
|
2519
|
+
if (child !== void 0 && clientSubagentTranscript) {
|
|
2520
|
+
for (const update of child.projection.onEvent(event)) {
|
|
2521
|
+
notify(child.rootSessionId, update);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
1627
2526
|
try {
|
|
1628
|
-
|
|
2527
|
+
const harnessEvent = event;
|
|
2528
|
+
for (const update of record.projection.onEvent(harnessEvent)) {
|
|
1629
2529
|
notify(sessionId, update);
|
|
1630
2530
|
}
|
|
2531
|
+
if (harnessEvent.type === "plan/mode") publishConfigOptions(sessionId, record);
|
|
1631
2532
|
} finally {
|
|
1632
2533
|
const inflight = record.inflight;
|
|
1633
2534
|
if (inflight !== void 0 && event.type === "turn/end" && inflight.turn === event.data.turn) {
|
|
@@ -1677,13 +2578,22 @@ async function apply(ctx, config = {}) {
|
|
|
1677
2578
|
const requested = params.protocolVersion;
|
|
1678
2579
|
const capsMeta = params.clientCapabilities?.["_meta"];
|
|
1679
2580
|
clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
|
|
2581
|
+
clientSubagentTranscript = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["subagent-transcript"] === true : false;
|
|
2582
|
+
clientElicitationForm = params.clientCapabilities?.elicitation?.form !== void 0 && params.clientCapabilities.elicitation.form !== null;
|
|
2583
|
+
if (capsMeta !== null && typeof capsMeta === "object" && capsMeta["tuiInspect"] === true) {
|
|
2584
|
+
tuiClient.advertised = true;
|
|
2585
|
+
}
|
|
1680
2586
|
const providers = await listProviderRoutes();
|
|
1681
2587
|
return {
|
|
1682
2588
|
protocolVersion: typeof requested === "number" && requested >= 1 && requested < PROTOCOL_VERSION ? requested : PROTOCOL_VERSION,
|
|
1683
2589
|
agentInfo: { name: "dsh-acp", title: "DeepSeek Harness", version: VERSION },
|
|
1684
2590
|
agentCapabilities: {
|
|
1685
2591
|
loadSession: true,
|
|
1686
|
-
promptCapabilities: {
|
|
2592
|
+
promptCapabilities: {
|
|
2593
|
+
image: attachmentIngestOf(ctx.get("attachments")) !== void 0,
|
|
2594
|
+
audio: false,
|
|
2595
|
+
embeddedContext: true
|
|
2596
|
+
},
|
|
1687
2597
|
// Stdio servers always work; streamable HTTP maps onto
|
|
1688
2598
|
// mcp-client's second transport. Legacy SSE does not.
|
|
1689
2599
|
mcpCapabilities: { http: true, sse: false },
|
|
@@ -1738,18 +2648,29 @@ async function apply(ctx, config = {}) {
|
|
|
1738
2648
|
},
|
|
1739
2649
|
async newSession(params) {
|
|
1740
2650
|
assertOpen();
|
|
2651
|
+
const started = performance.now();
|
|
2652
|
+
let checkpoint = started;
|
|
2653
|
+
const mark = (name3) => {
|
|
2654
|
+
const now = performance.now();
|
|
2655
|
+
logDebug(`session/new ${name3}: ${(now - checkpoint).toFixed(1)}ms (+${(now - started).toFixed(1)}ms)`);
|
|
2656
|
+
checkpoint = now;
|
|
2657
|
+
};
|
|
1741
2658
|
await requireCredential(config.provider);
|
|
2659
|
+
mark("credential");
|
|
1742
2660
|
validateCwd(params.cwd);
|
|
1743
2661
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
2662
|
+
mark("request setup");
|
|
1744
2663
|
const sessionId = SessionId2(randomUUID());
|
|
1745
2664
|
const presets = presetsService();
|
|
1746
2665
|
const presetId = (await presets.resolve(void 0)).id;
|
|
2666
|
+
mark("preset resolve");
|
|
1747
2667
|
const handle = await agents.create({
|
|
1748
2668
|
sessionId,
|
|
1749
2669
|
meta: { cwd: params.cwd, ...presetId !== void 0 ? { agentPreset: presetId } : {} },
|
|
1750
2670
|
agentOptions: agentOptionsFor(config.model),
|
|
1751
2671
|
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
1752
2672
|
});
|
|
2673
|
+
mark("agent create");
|
|
1753
2674
|
if (closed) {
|
|
1754
2675
|
await handle.dispose();
|
|
1755
2676
|
throw internalError("connection closed during session/new");
|
|
@@ -1765,6 +2686,7 @@ async function apply(ctx, config = {}) {
|
|
|
1765
2686
|
ensureSelection(record);
|
|
1766
2687
|
queueMicrotask(() => publishCommands(String(sessionId)));
|
|
1767
2688
|
const options = await configOptions(record);
|
|
2689
|
+
mark("response surface");
|
|
1768
2690
|
return {
|
|
1769
2691
|
sessionId: String(sessionId),
|
|
1770
2692
|
modes: modeState(record),
|
|
@@ -1812,17 +2734,36 @@ async function apply(ctx, config = {}) {
|
|
|
1812
2734
|
async prompt(params) {
|
|
1813
2735
|
assertOpen();
|
|
1814
2736
|
const record = await requireOrRestoreSession(params.sessionId);
|
|
1815
|
-
if (record.inflight !== void 0) {
|
|
1816
|
-
throw invalidParams("a prompt is already in flight for this session");
|
|
1817
|
-
}
|
|
1818
2737
|
let converted;
|
|
1819
2738
|
try {
|
|
1820
|
-
converted = convertPrompt(
|
|
2739
|
+
converted = await convertPrompt(
|
|
2740
|
+
params.prompt,
|
|
2741
|
+
attachmentIngestOf(ctx.get("attachments"))
|
|
2742
|
+
);
|
|
1821
2743
|
} catch (error) {
|
|
1822
|
-
if (error instanceof UnsupportedPromptContentError
|
|
2744
|
+
if (error instanceof UnsupportedPromptContentError || error instanceof PromptImageError) {
|
|
2745
|
+
throw invalidParams(error.message);
|
|
2746
|
+
}
|
|
1823
2747
|
throw error;
|
|
1824
2748
|
}
|
|
1825
2749
|
if (converted.blocks.length === 0) throw invalidParams("empty prompt");
|
|
2750
|
+
if (record.inflight !== void 0) {
|
|
2751
|
+
await requireCredential(record.provider ?? config.provider);
|
|
2752
|
+
if (agents.get(record.agent.id) !== record.agent) {
|
|
2753
|
+
throw internalError("prompt was not steered: the agent was disposed outside the bridge");
|
|
2754
|
+
}
|
|
2755
|
+
const message2 = createUserMessage2({
|
|
2756
|
+
content: converted.blocks,
|
|
2757
|
+
source: { kind: "user" }
|
|
2758
|
+
});
|
|
2759
|
+
try {
|
|
2760
|
+
deliverPrompt(record.agent, message2, true);
|
|
2761
|
+
} catch (error) {
|
|
2762
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2763
|
+
throw internalError(`prompt was not steered: ${detail}`);
|
|
2764
|
+
}
|
|
2765
|
+
return { stopReason: "end_turn" };
|
|
2766
|
+
}
|
|
1826
2767
|
const trimmed = converted.displayText.trim();
|
|
1827
2768
|
const commandMatch = trimmed.match(/^\/(\w[\w-]*)\b/);
|
|
1828
2769
|
const respond = (text) => {
|
|
@@ -1867,16 +2808,25 @@ async function apply(ctx, config = {}) {
|
|
|
1867
2808
|
throw internalError("prompt was not queued: the agent was disposed outside the bridge");
|
|
1868
2809
|
}
|
|
1869
2810
|
record.cancelled = false;
|
|
1870
|
-
record.projection.beginPrompt();
|
|
1871
2811
|
const message = createUserMessage2({
|
|
1872
2812
|
content: converted.blocks,
|
|
1873
2813
|
source: { kind: "user" }
|
|
1874
2814
|
});
|
|
2815
|
+
if (record.inflight !== void 0) {
|
|
2816
|
+
try {
|
|
2817
|
+
deliverPrompt(record.agent, message, true);
|
|
2818
|
+
} catch (error) {
|
|
2819
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2820
|
+
throw internalError(`prompt was not steered: ${detail}`);
|
|
2821
|
+
}
|
|
2822
|
+
return { stopReason: "end_turn" };
|
|
2823
|
+
}
|
|
2824
|
+
record.projection.beginPrompt();
|
|
1875
2825
|
const stopReason = await new Promise((resolve, reject) => {
|
|
1876
2826
|
const inflight = { resolve, reject, messageId: message.id, turn: void 0 };
|
|
1877
2827
|
record.inflight = inflight;
|
|
1878
2828
|
try {
|
|
1879
|
-
record.agent
|
|
2829
|
+
deliverPrompt(record.agent, message, false);
|
|
1880
2830
|
} catch (error) {
|
|
1881
2831
|
record.inflight = void 0;
|
|
1882
2832
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -1937,6 +2887,23 @@ async function apply(ctx, config = {}) {
|
|
|
1937
2887
|
record.effort = value;
|
|
1938
2888
|
break;
|
|
1939
2889
|
}
|
|
2890
|
+
case "collaboration_mode": {
|
|
2891
|
+
if (value !== "default" && value !== "plan") {
|
|
2892
|
+
throw invalidParams(`unknown collaboration mode: ${value}`);
|
|
2893
|
+
}
|
|
2894
|
+
const execution = await commandRuntime.execute(
|
|
2895
|
+
record.agent,
|
|
2896
|
+
value === "plan" ? "/plan" : "/plan off",
|
|
2897
|
+
new AbortController().signal
|
|
2898
|
+
);
|
|
2899
|
+
if (execution === void 0) {
|
|
2900
|
+
throw invalidParams("plan mode is unavailable on this session");
|
|
2901
|
+
}
|
|
2902
|
+
if (execution.result.kind === "error") {
|
|
2903
|
+
throw invalidParams(execution.result.text);
|
|
2904
|
+
}
|
|
2905
|
+
break;
|
|
2906
|
+
}
|
|
1940
2907
|
case "model": {
|
|
1941
2908
|
await switchModel(record, params.sessionId, value);
|
|
1942
2909
|
break;
|
|
@@ -1974,8 +2941,8 @@ async function apply(ctx, config = {}) {
|
|
|
1974
2941
|
}
|
|
1975
2942
|
const mcpMounts = /* @__PURE__ */ new Map();
|
|
1976
2943
|
let warnedNoMcpClient = false;
|
|
1977
|
-
const sanitizeServerName = (
|
|
1978
|
-
const cleaned =
|
|
2944
|
+
const sanitizeServerName = (raw2) => {
|
|
2945
|
+
const cleaned = raw2.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 32);
|
|
1979
2946
|
return cleaned.length > 0 ? cleaned : "server";
|
|
1980
2947
|
};
|
|
1981
2948
|
const mcpConfigFor = (server, cwd, serverName) => {
|
|
@@ -2056,17 +3023,29 @@ async function apply(ctx, config = {}) {
|
|
|
2056
3023
|
}
|
|
2057
3024
|
}
|
|
2058
3025
|
};
|
|
2059
|
-
const
|
|
3026
|
+
const raw = config.stream ?? ndJsonStream(
|
|
2060
3027
|
Writable.toWeb(process.stdout),
|
|
2061
3028
|
Readable.toWeb(process.stdin)
|
|
2062
3029
|
);
|
|
3030
|
+
const stream = muxAcpStream(raw, rpc);
|
|
2063
3031
|
conn = new AgentSideConnection(makeAgent, stream);
|
|
3032
|
+
const disposeUserQuestions = installAcpUserQuestionProvider(userQuestions, {
|
|
3033
|
+
formSupported: () => clientElicitationForm,
|
|
3034
|
+
sessionIdForRequest: (request) => {
|
|
3035
|
+
if (request.agent === void 0) return void 0;
|
|
3036
|
+
const record = ownedRecord(request.agent);
|
|
3037
|
+
return record === void 0 ? void 0 : String(record.agent.session.id);
|
|
3038
|
+
},
|
|
3039
|
+
create: (request) => conn.unstable_createElicitation(request)
|
|
3040
|
+
});
|
|
2064
3041
|
let quiescing;
|
|
2065
3042
|
const quiesce = () => {
|
|
2066
3043
|
if (quiescing !== void 0) return quiescing;
|
|
2067
3044
|
closed = true;
|
|
2068
3045
|
const records = [...sessions.values()];
|
|
2069
3046
|
sessions.clear();
|
|
3047
|
+
subagentByChild.clear();
|
|
3048
|
+
subagentByRun.clear();
|
|
2070
3049
|
for (const record of records) {
|
|
2071
3050
|
record.agent.cancel({ kind: "user" });
|
|
2072
3051
|
settlePrompt(record, "cancelled");
|
|
@@ -2097,6 +3076,7 @@ async function apply(ctx, config = {}) {
|
|
|
2097
3076
|
logWarn(`connection-close teardown failed: ${String(error)}`);
|
|
2098
3077
|
});
|
|
2099
3078
|
ctx.effect(() => quiesce, "acp-bridge.connection");
|
|
3079
|
+
ctx.effect(() => disposeUserQuestions, "acp-bridge.user-questions");
|
|
2100
3080
|
}
|
|
2101
3081
|
|
|
2102
3082
|
// src/bundle.ts
|