@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/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.length > 0 ? [{ sessionUpdate: "plan", 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(this.stepKey(data));
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(this.stepKey(data));
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 { text, reasoning } = extractMessageTexts(data["message"]);
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({ sessionUpdate: "agent_thought_chunk", content: { type: "text", text: reasoning } });
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({ sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
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
- const facts = classifyToolCall(name3, rawArguments);
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 userText(data) {
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 : void 0;
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 text = userText(event.data);
764
- if (text !== void 0) {
765
- updates.push({ sessionUpdate: "user_message_chunk", content: { type: "text", text } });
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
- function convertPrompt(prompt) {
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
- const text = parts.join("");
1109
+ flushText(parts, blocks);
831
1110
  return {
832
- blocks: text.length > 0 ? [{ type: "text", text }] : [],
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 raw = llm.listProviders();
1066
- if (!Array.isArray(raw)) return [];
1067
- return raw.flatMap((entry) => {
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: presetLabel(preset.id),
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) return;
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
- for (const update of record.projection.onEvent(event)) {
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: { image: false, audio: false, embeddedContext: true },
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(params.prompt);
2739
+ converted = await convertPrompt(
2740
+ params.prompt,
2741
+ attachmentIngestOf(ctx.get("attachments"))
2742
+ );
1821
2743
  } catch (error) {
1822
- if (error instanceof UnsupportedPromptContentError) throw invalidParams(error.message);
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.followup(message);
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 = (raw) => {
1978
- const cleaned = raw.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 32);
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 stream = config.stream ?? ndJsonStream(
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