@openma/deepseek-harness-acp 0.4.6 → 0.4.7
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 +1012 -38
- 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,535 @@ ${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
|
+
|
|
837
1615
|
// src/bridge/index.ts
|
|
838
1616
|
var name = "acp-bridge";
|
|
839
|
-
var inject = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents"];
|
|
1617
|
+
var inject = ["agents", "credentials", "llm", "agentDefaultModel", "sessionPersistence", "approval", "permissionPresets", "commands", "agentPresets", "skills", "subagents", "userQuestions"];
|
|
840
1618
|
function invalidParams(detail) {
|
|
841
1619
|
return RequestError.invalidParams(void 0, detail);
|
|
842
1620
|
}
|
|
@@ -869,13 +1647,30 @@ async function apply(ctx, config = {}) {
|
|
|
869
1647
|
const agentPresets = ctx.agentPresets;
|
|
870
1648
|
const skillRegistry = ctx.skills;
|
|
871
1649
|
const subagents = ctx.subagents;
|
|
1650
|
+
const userQuestions = ctx.userQuestions;
|
|
872
1651
|
const harness = config.harness ?? (await Promise.resolve().then(() => (init_self_harness(), self_harness_exports))).selfHarness();
|
|
873
1652
|
const { createUserMessage: createUserMessage2, errorChain: errorChain2, sessionId: SessionId2, foldSessionTitle: foldSessionTitle2, setSandboxMode: setSandboxMode2 } = harness;
|
|
874
1653
|
const SANDBOX_MODES2 = harness.sandboxModes;
|
|
875
1654
|
const sessions = /* @__PURE__ */ new Map();
|
|
1655
|
+
const subagentByChild = /* @__PURE__ */ new Map();
|
|
1656
|
+
const subagentByRun = /* @__PURE__ */ new Map();
|
|
1657
|
+
const watchedSubagentParents = /* @__PURE__ */ new WeakSet();
|
|
876
1658
|
let closed = false;
|
|
877
1659
|
let conn;
|
|
878
1660
|
let clientTerminalOutput = false;
|
|
1661
|
+
let clientSubagentTranscript = false;
|
|
1662
|
+
let clientElicitationForm = false;
|
|
1663
|
+
const tuiClient = { advertised: false };
|
|
1664
|
+
const rpc = new AcpRpc();
|
|
1665
|
+
const findAgent = (agentId) => {
|
|
1666
|
+
const fromRegistry = agents.get(agentId);
|
|
1667
|
+
if (fromRegistry !== void 0) return fromRegistry;
|
|
1668
|
+
for (const record of sessions.values()) {
|
|
1669
|
+
if (record.agent.id === agentId) return record.agent;
|
|
1670
|
+
}
|
|
1671
|
+
return void 0;
|
|
1672
|
+
};
|
|
1673
|
+
installTuiClientPlane(ctx, rpc, { findAgent, advertisement: tuiClient });
|
|
879
1674
|
const modelCandidates = () => {
|
|
880
1675
|
const seen = /* @__PURE__ */ new Set();
|
|
881
1676
|
if (config.model !== void 0) seen.add(config.model);
|
|
@@ -1062,9 +1857,9 @@ async function apply(ctx, config = {}) {
|
|
|
1062
1857
|
};
|
|
1063
1858
|
const listProviderRoutes = async () => {
|
|
1064
1859
|
try {
|
|
1065
|
-
const
|
|
1066
|
-
if (!Array.isArray(
|
|
1067
|
-
return
|
|
1860
|
+
const raw2 = llm.listProviders();
|
|
1861
|
+
if (!Array.isArray(raw2)) return [];
|
|
1862
|
+
return raw2.flatMap((entry) => {
|
|
1068
1863
|
if (!entry || typeof entry !== "object") return [];
|
|
1069
1864
|
const id = entry.id;
|
|
1070
1865
|
if (typeof id !== "string" || id.length === 0) return [];
|
|
@@ -1201,13 +1996,78 @@ async function apply(ctx, config = {}) {
|
|
|
1201
1996
|
record.inflight = void 0;
|
|
1202
1997
|
inflight.resolve(reason);
|
|
1203
1998
|
};
|
|
1999
|
+
const watchSubagentParent = (parent) => {
|
|
2000
|
+
if (watchedSubagentParents.has(parent)) return;
|
|
2001
|
+
watchedSubagentParents.add(parent);
|
|
2002
|
+
parent.ctx.on("subagent/start", (info) => {
|
|
2003
|
+
const parentLink = subagentByChild.get(String(parent.id));
|
|
2004
|
+
const rootSessionId = parentLink?.rootSessionId ?? String(parent.session.id);
|
|
2005
|
+
const record = sessions.get(rootSessionId);
|
|
2006
|
+
if (record === void 0) return;
|
|
2007
|
+
const runId = String(info.runId);
|
|
2008
|
+
const childSessionId = String(info.id);
|
|
2009
|
+
const toolCallId = `subagent:${runId}`;
|
|
2010
|
+
const payload = {
|
|
2011
|
+
runId,
|
|
2012
|
+
provider: info.provider,
|
|
2013
|
+
id: childSessionId,
|
|
2014
|
+
local: info.local,
|
|
2015
|
+
...parentLink !== void 0 ? { parentToolCallId: parentLink.toolCallId } : {}
|
|
2016
|
+
};
|
|
2017
|
+
for (const update of record.projection.onEvent({ type: "subagent/start", data: payload })) {
|
|
2018
|
+
notify(rootSessionId, update);
|
|
2019
|
+
}
|
|
2020
|
+
const child = agents.get(info.id);
|
|
2021
|
+
const link = {
|
|
2022
|
+
rootSessionId,
|
|
2023
|
+
childSessionId,
|
|
2024
|
+
runId,
|
|
2025
|
+
provider: info.provider,
|
|
2026
|
+
toolCallId,
|
|
2027
|
+
projection: new SessionProjection(void 0, {
|
|
2028
|
+
terminalOutput: clientTerminalOutput,
|
|
2029
|
+
...child?.session.header.cwd !== void 0 ? { cwd: child.session.header.cwd } : {},
|
|
2030
|
+
subagent: { childSessionId, parentToolCallId: toolCallId, provider: info.provider }
|
|
2031
|
+
})
|
|
2032
|
+
};
|
|
2033
|
+
subagentByChild.set(childSessionId, link);
|
|
2034
|
+
subagentByRun.set(runId, link);
|
|
2035
|
+
if (child !== void 0) watchSubagentParent(child);
|
|
2036
|
+
});
|
|
2037
|
+
parent.ctx.on("subagent/end", (info) => {
|
|
2038
|
+
const runId = String(info.runId);
|
|
2039
|
+
const link = subagentByRun.get(runId);
|
|
2040
|
+
if (link === void 0) return;
|
|
2041
|
+
const record = sessions.get(link.rootSessionId);
|
|
2042
|
+
if (record !== void 0) {
|
|
2043
|
+
const parentLink = subagentByChild.get(String(parent.id));
|
|
2044
|
+
const payload = {
|
|
2045
|
+
runId,
|
|
2046
|
+
provider: info.provider,
|
|
2047
|
+
id: String(info.id),
|
|
2048
|
+
local: info.local,
|
|
2049
|
+
stopReason: info.stopReason,
|
|
2050
|
+
...info.lastAssistantMessage !== void 0 ? { lastAssistantMessage: info.lastAssistantMessage } : {},
|
|
2051
|
+
...parentLink !== void 0 ? { parentToolCallId: parentLink.toolCallId } : {}
|
|
2052
|
+
};
|
|
2053
|
+
for (const update of record.projection.onEvent({ type: "subagent/end", data: payload })) {
|
|
2054
|
+
notify(link.rootSessionId, update);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
subagentByRun.delete(runId);
|
|
2058
|
+
if (subagentByChild.get(link.childSessionId)?.runId === runId) {
|
|
2059
|
+
subagentByChild.delete(link.childSessionId);
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
};
|
|
1204
2063
|
const presetsService = () => agentPresets;
|
|
1205
2064
|
const presetSetup = (presets, presetId) => {
|
|
1206
2065
|
if (presetId === void 0) return void 0;
|
|
1207
2066
|
return async (agentCtx) => {
|
|
2067
|
+
const started = performance.now();
|
|
1208
2068
|
logDebug(`preset setup: mounting "${presetId}"`);
|
|
1209
2069
|
await presets.mount(agentCtx, presetId);
|
|
1210
|
-
logDebug(`preset setup: mounted "${presetId}"`);
|
|
2070
|
+
logDebug(`preset setup: mounted "${presetId}" in ${(performance.now() - started).toFixed(1)}ms`);
|
|
1211
2071
|
};
|
|
1212
2072
|
};
|
|
1213
2073
|
const presetFromLog = (header, events) => {
|
|
@@ -1363,6 +2223,19 @@ async function apply(ctx, config = {}) {
|
|
|
1363
2223
|
options: levels
|
|
1364
2224
|
});
|
|
1365
2225
|
}
|
|
2226
|
+
if (commandRuntime.list(record.agent).some((command) => command.name === "plan")) {
|
|
2227
|
+
const active = [...record.agent.session.events].reverse().map((event) => event).find((event) => event.type === "plan/mode")?.data?.["active"] === true;
|
|
2228
|
+
result.push({
|
|
2229
|
+
type: "select",
|
|
2230
|
+
id: "collaboration_mode",
|
|
2231
|
+
name: "Collaboration mode",
|
|
2232
|
+
currentValue: active ? "plan" : "default",
|
|
2233
|
+
options: [
|
|
2234
|
+
{ value: "default", name: "Default" },
|
|
2235
|
+
{ value: "plan", name: "Plan" }
|
|
2236
|
+
]
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
1366
2239
|
const models = await modelChoices(record);
|
|
1367
2240
|
if (models.length >= 2) {
|
|
1368
2241
|
result.push({
|
|
@@ -1442,7 +2315,18 @@ async function apply(ctx, config = {}) {
|
|
|
1442
2315
|
list.push({
|
|
1443
2316
|
name: descriptor.name,
|
|
1444
2317
|
description: descriptor.description,
|
|
1445
|
-
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {}
|
|
2318
|
+
...descriptor.input !== void 0 ? { input: { hint: descriptor.input.hint } } : {},
|
|
2319
|
+
...descriptor.name === "plan" ? {
|
|
2320
|
+
_meta: {
|
|
2321
|
+
commandAction: {
|
|
2322
|
+
kind: "setConfigOption",
|
|
2323
|
+
configId: "collaboration_mode",
|
|
2324
|
+
value: "plan",
|
|
2325
|
+
resetValue: "default",
|
|
2326
|
+
presentation: "state"
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
} : {}
|
|
1446
2330
|
});
|
|
1447
2331
|
}
|
|
1448
2332
|
} catch (error) {
|
|
@@ -1618,16 +2502,27 @@ async function apply(ctx, config = {}) {
|
|
|
1618
2502
|
inflight: void 0
|
|
1619
2503
|
};
|
|
1620
2504
|
sessions.set(sessionId, record);
|
|
2505
|
+
watchSubagentParent(agent);
|
|
1621
2506
|
return record;
|
|
1622
2507
|
};
|
|
1623
2508
|
ctx.on("session/event", (session, event) => {
|
|
1624
2509
|
const sessionId = String(session.header.id);
|
|
1625
2510
|
const record = sessions.get(sessionId);
|
|
1626
|
-
if (record === void 0 || record.agent.session !== session)
|
|
2511
|
+
if (record === void 0 || record.agent.session !== session) {
|
|
2512
|
+
const child = subagentByChild.get(sessionId);
|
|
2513
|
+
if (child !== void 0 && clientSubagentTranscript) {
|
|
2514
|
+
for (const update of child.projection.onEvent(event)) {
|
|
2515
|
+
notify(child.rootSessionId, update);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
1627
2520
|
try {
|
|
1628
|
-
|
|
2521
|
+
const harnessEvent = event;
|
|
2522
|
+
for (const update of record.projection.onEvent(harnessEvent)) {
|
|
1629
2523
|
notify(sessionId, update);
|
|
1630
2524
|
}
|
|
2525
|
+
if (harnessEvent.type === "plan/mode") publishConfigOptions(sessionId, record);
|
|
1631
2526
|
} finally {
|
|
1632
2527
|
const inflight = record.inflight;
|
|
1633
2528
|
if (inflight !== void 0 && event.type === "turn/end" && inflight.turn === event.data.turn) {
|
|
@@ -1677,13 +2572,22 @@ async function apply(ctx, config = {}) {
|
|
|
1677
2572
|
const requested = params.protocolVersion;
|
|
1678
2573
|
const capsMeta = params.clientCapabilities?.["_meta"];
|
|
1679
2574
|
clientTerminalOutput = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["terminal_output"] === true : false;
|
|
2575
|
+
clientSubagentTranscript = capsMeta !== null && typeof capsMeta === "object" ? capsMeta["subagent-transcript"] === true : false;
|
|
2576
|
+
clientElicitationForm = params.clientCapabilities?.elicitation?.form !== void 0 && params.clientCapabilities.elicitation.form !== null;
|
|
2577
|
+
if (capsMeta !== null && typeof capsMeta === "object" && capsMeta["tuiInspect"] === true) {
|
|
2578
|
+
tuiClient.advertised = true;
|
|
2579
|
+
}
|
|
1680
2580
|
const providers = await listProviderRoutes();
|
|
1681
2581
|
return {
|
|
1682
2582
|
protocolVersion: typeof requested === "number" && requested >= 1 && requested < PROTOCOL_VERSION ? requested : PROTOCOL_VERSION,
|
|
1683
2583
|
agentInfo: { name: "dsh-acp", title: "DeepSeek Harness", version: VERSION },
|
|
1684
2584
|
agentCapabilities: {
|
|
1685
2585
|
loadSession: true,
|
|
1686
|
-
promptCapabilities: {
|
|
2586
|
+
promptCapabilities: {
|
|
2587
|
+
image: attachmentIngestOf(ctx.get("attachments")) !== void 0,
|
|
2588
|
+
audio: false,
|
|
2589
|
+
embeddedContext: true
|
|
2590
|
+
},
|
|
1687
2591
|
// Stdio servers always work; streamable HTTP maps onto
|
|
1688
2592
|
// mcp-client's second transport. Legacy SSE does not.
|
|
1689
2593
|
mcpCapabilities: { http: true, sse: false },
|
|
@@ -1738,18 +2642,29 @@ async function apply(ctx, config = {}) {
|
|
|
1738
2642
|
},
|
|
1739
2643
|
async newSession(params) {
|
|
1740
2644
|
assertOpen();
|
|
2645
|
+
const started = performance.now();
|
|
2646
|
+
let checkpoint = started;
|
|
2647
|
+
const mark = (name3) => {
|
|
2648
|
+
const now = performance.now();
|
|
2649
|
+
logDebug(`session/new ${name3}: ${(now - checkpoint).toFixed(1)}ms (+${(now - started).toFixed(1)}ms)`);
|
|
2650
|
+
checkpoint = now;
|
|
2651
|
+
};
|
|
1741
2652
|
await requireCredential(config.provider);
|
|
2653
|
+
mark("credential");
|
|
1742
2654
|
validateCwd(params.cwd);
|
|
1743
2655
|
syncMcpServers(params.mcpServers, params.cwd);
|
|
2656
|
+
mark("request setup");
|
|
1744
2657
|
const sessionId = SessionId2(randomUUID());
|
|
1745
2658
|
const presets = presetsService();
|
|
1746
2659
|
const presetId = (await presets.resolve(void 0)).id;
|
|
2660
|
+
mark("preset resolve");
|
|
1747
2661
|
const handle = await agents.create({
|
|
1748
2662
|
sessionId,
|
|
1749
2663
|
meta: { cwd: params.cwd, ...presetId !== void 0 ? { agentPreset: presetId } : {} },
|
|
1750
2664
|
agentOptions: agentOptionsFor(config.model),
|
|
1751
2665
|
...presetSetup(presets, presetId) !== void 0 ? { setup: presetSetup(presets, presetId) } : {}
|
|
1752
2666
|
});
|
|
2667
|
+
mark("agent create");
|
|
1753
2668
|
if (closed) {
|
|
1754
2669
|
await handle.dispose();
|
|
1755
2670
|
throw internalError("connection closed during session/new");
|
|
@@ -1765,6 +2680,7 @@ async function apply(ctx, config = {}) {
|
|
|
1765
2680
|
ensureSelection(record);
|
|
1766
2681
|
queueMicrotask(() => publishCommands(String(sessionId)));
|
|
1767
2682
|
const options = await configOptions(record);
|
|
2683
|
+
mark("response surface");
|
|
1768
2684
|
return {
|
|
1769
2685
|
sessionId: String(sessionId),
|
|
1770
2686
|
modes: modeState(record),
|
|
@@ -1812,17 +2728,36 @@ async function apply(ctx, config = {}) {
|
|
|
1812
2728
|
async prompt(params) {
|
|
1813
2729
|
assertOpen();
|
|
1814
2730
|
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
2731
|
let converted;
|
|
1819
2732
|
try {
|
|
1820
|
-
converted = convertPrompt(
|
|
2733
|
+
converted = await convertPrompt(
|
|
2734
|
+
params.prompt,
|
|
2735
|
+
attachmentIngestOf(ctx.get("attachments"))
|
|
2736
|
+
);
|
|
1821
2737
|
} catch (error) {
|
|
1822
|
-
if (error instanceof UnsupportedPromptContentError
|
|
2738
|
+
if (error instanceof UnsupportedPromptContentError || error instanceof PromptImageError) {
|
|
2739
|
+
throw invalidParams(error.message);
|
|
2740
|
+
}
|
|
1823
2741
|
throw error;
|
|
1824
2742
|
}
|
|
1825
2743
|
if (converted.blocks.length === 0) throw invalidParams("empty prompt");
|
|
2744
|
+
if (record.inflight !== void 0) {
|
|
2745
|
+
await requireCredential(record.provider ?? config.provider);
|
|
2746
|
+
if (agents.get(record.agent.id) !== record.agent) {
|
|
2747
|
+
throw internalError("prompt was not steered: the agent was disposed outside the bridge");
|
|
2748
|
+
}
|
|
2749
|
+
const message2 = createUserMessage2({
|
|
2750
|
+
content: converted.blocks,
|
|
2751
|
+
source: { kind: "user" }
|
|
2752
|
+
});
|
|
2753
|
+
try {
|
|
2754
|
+
deliverPrompt(record.agent, message2, true);
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2757
|
+
throw internalError(`prompt was not steered: ${detail}`);
|
|
2758
|
+
}
|
|
2759
|
+
return { stopReason: "end_turn" };
|
|
2760
|
+
}
|
|
1826
2761
|
const trimmed = converted.displayText.trim();
|
|
1827
2762
|
const commandMatch = trimmed.match(/^\/(\w[\w-]*)\b/);
|
|
1828
2763
|
const respond = (text) => {
|
|
@@ -1867,16 +2802,25 @@ async function apply(ctx, config = {}) {
|
|
|
1867
2802
|
throw internalError("prompt was not queued: the agent was disposed outside the bridge");
|
|
1868
2803
|
}
|
|
1869
2804
|
record.cancelled = false;
|
|
1870
|
-
record.projection.beginPrompt();
|
|
1871
2805
|
const message = createUserMessage2({
|
|
1872
2806
|
content: converted.blocks,
|
|
1873
2807
|
source: { kind: "user" }
|
|
1874
2808
|
});
|
|
2809
|
+
if (record.inflight !== void 0) {
|
|
2810
|
+
try {
|
|
2811
|
+
deliverPrompt(record.agent, message, true);
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2814
|
+
throw internalError(`prompt was not steered: ${detail}`);
|
|
2815
|
+
}
|
|
2816
|
+
return { stopReason: "end_turn" };
|
|
2817
|
+
}
|
|
2818
|
+
record.projection.beginPrompt();
|
|
1875
2819
|
const stopReason = await new Promise((resolve, reject) => {
|
|
1876
2820
|
const inflight = { resolve, reject, messageId: message.id, turn: void 0 };
|
|
1877
2821
|
record.inflight = inflight;
|
|
1878
2822
|
try {
|
|
1879
|
-
record.agent
|
|
2823
|
+
deliverPrompt(record.agent, message, false);
|
|
1880
2824
|
} catch (error) {
|
|
1881
2825
|
record.inflight = void 0;
|
|
1882
2826
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -1937,6 +2881,23 @@ async function apply(ctx, config = {}) {
|
|
|
1937
2881
|
record.effort = value;
|
|
1938
2882
|
break;
|
|
1939
2883
|
}
|
|
2884
|
+
case "collaboration_mode": {
|
|
2885
|
+
if (value !== "default" && value !== "plan") {
|
|
2886
|
+
throw invalidParams(`unknown collaboration mode: ${value}`);
|
|
2887
|
+
}
|
|
2888
|
+
const execution = await commandRuntime.execute(
|
|
2889
|
+
record.agent,
|
|
2890
|
+
value === "plan" ? "/plan" : "/plan off",
|
|
2891
|
+
new AbortController().signal
|
|
2892
|
+
);
|
|
2893
|
+
if (execution === void 0) {
|
|
2894
|
+
throw invalidParams("plan mode is unavailable on this session");
|
|
2895
|
+
}
|
|
2896
|
+
if (execution.result.kind === "error") {
|
|
2897
|
+
throw invalidParams(execution.result.text);
|
|
2898
|
+
}
|
|
2899
|
+
break;
|
|
2900
|
+
}
|
|
1940
2901
|
case "model": {
|
|
1941
2902
|
await switchModel(record, params.sessionId, value);
|
|
1942
2903
|
break;
|
|
@@ -1974,8 +2935,8 @@ async function apply(ctx, config = {}) {
|
|
|
1974
2935
|
}
|
|
1975
2936
|
const mcpMounts = /* @__PURE__ */ new Map();
|
|
1976
2937
|
let warnedNoMcpClient = false;
|
|
1977
|
-
const sanitizeServerName = (
|
|
1978
|
-
const cleaned =
|
|
2938
|
+
const sanitizeServerName = (raw2) => {
|
|
2939
|
+
const cleaned = raw2.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 32);
|
|
1979
2940
|
return cleaned.length > 0 ? cleaned : "server";
|
|
1980
2941
|
};
|
|
1981
2942
|
const mcpConfigFor = (server, cwd, serverName) => {
|
|
@@ -2056,17 +3017,29 @@ async function apply(ctx, config = {}) {
|
|
|
2056
3017
|
}
|
|
2057
3018
|
}
|
|
2058
3019
|
};
|
|
2059
|
-
const
|
|
3020
|
+
const raw = config.stream ?? ndJsonStream(
|
|
2060
3021
|
Writable.toWeb(process.stdout),
|
|
2061
3022
|
Readable.toWeb(process.stdin)
|
|
2062
3023
|
);
|
|
3024
|
+
const stream = muxAcpStream(raw, rpc);
|
|
2063
3025
|
conn = new AgentSideConnection(makeAgent, stream);
|
|
3026
|
+
const disposeUserQuestions = installAcpUserQuestionProvider(userQuestions, {
|
|
3027
|
+
formSupported: () => clientElicitationForm,
|
|
3028
|
+
sessionIdForRequest: (request) => {
|
|
3029
|
+
if (request.agent === void 0) return void 0;
|
|
3030
|
+
const record = ownedRecord(request.agent);
|
|
3031
|
+
return record === void 0 ? void 0 : String(record.agent.session.id);
|
|
3032
|
+
},
|
|
3033
|
+
create: (request) => conn.unstable_createElicitation(request)
|
|
3034
|
+
});
|
|
2064
3035
|
let quiescing;
|
|
2065
3036
|
const quiesce = () => {
|
|
2066
3037
|
if (quiescing !== void 0) return quiescing;
|
|
2067
3038
|
closed = true;
|
|
2068
3039
|
const records = [...sessions.values()];
|
|
2069
3040
|
sessions.clear();
|
|
3041
|
+
subagentByChild.clear();
|
|
3042
|
+
subagentByRun.clear();
|
|
2070
3043
|
for (const record of records) {
|
|
2071
3044
|
record.agent.cancel({ kind: "user" });
|
|
2072
3045
|
settlePrompt(record, "cancelled");
|
|
@@ -2097,6 +3070,7 @@ async function apply(ctx, config = {}) {
|
|
|
2097
3070
|
logWarn(`connection-close teardown failed: ${String(error)}`);
|
|
2098
3071
|
});
|
|
2099
3072
|
ctx.effect(() => quiesce, "acp-bridge.connection");
|
|
3073
|
+
ctx.effect(() => disposeUserQuestions, "acp-bridge.user-questions");
|
|
2100
3074
|
}
|
|
2101
3075
|
|
|
2102
3076
|
// src/bundle.ts
|