@harmonyos-arkts/opencode-acp 0.0.1 → 0.0.3

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/index.cjs CHANGED
@@ -396,7 +396,7 @@ var require_parse = __commonJS({
396
396
  var resolveCommand = require_resolveCommand();
397
397
  var escape = require_escape();
398
398
  var readShebang = require_readShebang();
399
- var isWin = process.platform === "win32";
399
+ var isWin2 = process.platform === "win32";
400
400
  var isExecutableRegExp = /\.(?:com|exe)$/i;
401
401
  var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
402
402
  function detectShebang(parsed) {
@@ -410,7 +410,7 @@ var require_parse = __commonJS({
410
410
  return parsed.file;
411
411
  }
412
412
  function parseNonShell(parsed) {
413
- if (!isWin) {
413
+ if (!isWin2) {
414
414
  return parsed;
415
415
  }
416
416
  const commandFile = detectShebang(parsed);
@@ -454,7 +454,7 @@ var require_parse = __commonJS({
454
454
  var require_enoent = __commonJS({
455
455
  "node_modules/cross-spawn/lib/enoent.js"(exports2, module2) {
456
456
  "use strict";
457
- var isWin = process.platform === "win32";
457
+ var isWin2 = process.platform === "win32";
458
458
  function notFoundError(original, syscall) {
459
459
  return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
460
460
  code: "ENOENT",
@@ -465,7 +465,7 @@ var require_enoent = __commonJS({
465
465
  });
466
466
  }
467
467
  function hookChildProcess(cp, parsed) {
468
- if (!isWin) {
468
+ if (!isWin2) {
469
469
  return;
470
470
  }
471
471
  const originalEmit = cp.emit;
@@ -480,13 +480,13 @@ var require_enoent = __commonJS({
480
480
  };
481
481
  }
482
482
  function verifyENOENT(status, parsed) {
483
- if (isWin && status === 1 && !parsed.file) {
483
+ if (isWin2 && status === 1 && !parsed.file) {
484
484
  return notFoundError(parsed.original, "spawn");
485
485
  }
486
486
  return null;
487
487
  }
488
488
  function verifyENOENTSync(status, parsed) {
489
- if (isWin && status === 1 && !parsed.file) {
489
+ if (isWin2 && status === 1 && !parsed.file) {
490
490
  return notFoundError(parsed.original, "spawnSync");
491
491
  }
492
492
  return null;
@@ -19586,6 +19586,9 @@ function createOpencodeClient(config2) {
19586
19586
  // node_modules/@opencode-ai/sdk/dist/v2/server.js
19587
19587
  var import_cross_spawn = __toESM(require_cross_spawn(), 1);
19588
19588
 
19589
+ // src/agent.ts
19590
+ var import_url2 = require("url");
19591
+
19589
19592
  // src/session-manager.ts
19590
19593
  var SessionManager = class {
19591
19594
  sessions = /* @__PURE__ */ new Map();
@@ -19613,10 +19616,7 @@ var SessionManager = class {
19613
19616
  * Called by agent.newSession() / agent.loadSession().
19614
19617
  */
19615
19618
  async create(cwd, mcpServers, model) {
19616
- const response = await this.sdk.session.create(
19617
- { directory: cwd },
19618
- { throwOnError: true }
19619
- );
19619
+ const response = await this.sdk.session.create({ directory: cwd }, { throwOnError: true });
19620
19620
  const session = response.data;
19621
19621
  const state = {
19622
19622
  id: session.id,
@@ -19633,10 +19633,7 @@ var SessionManager = class {
19633
19633
  * Called by agent.loadSession() / agent.unstable_resumeSession().
19634
19634
  */
19635
19635
  async load(sessionId, cwd, mcpServers, model) {
19636
- await this.sdk.session.get(
19637
- { sessionID: sessionId, directory: cwd },
19638
- { throwOnError: true }
19639
- );
19636
+ await this.sdk.session.get({ sessionID: sessionId, directory: cwd }, { throwOnError: true });
19640
19637
  const state = {
19641
19638
  id: sessionId,
19642
19639
  cwd,
@@ -19724,7 +19721,562 @@ var SessionManager = class {
19724
19721
  }
19725
19722
  };
19726
19723
 
19724
+ // node_modules/diff/libesm/util/string.js
19725
+ function hasOnlyWinLineEndings(string4) {
19726
+ return string4.includes("\r\n") && !string4.startsWith("\n") && !string4.match(/[^\r]\n/);
19727
+ }
19728
+ function hasOnlyUnixLineEndings(string4) {
19729
+ return !string4.includes("\r\n") && string4.includes("\n");
19730
+ }
19731
+
19732
+ // node_modules/diff/libesm/patch/line-endings.js
19733
+ function unixToWin(patch) {
19734
+ if (Array.isArray(patch)) {
19735
+ return patch.map((p) => unixToWin(p));
19736
+ }
19737
+ return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map((hunk) => Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map((line, i) => {
19738
+ var _a2;
19739
+ return line.startsWith("\\") || line.endsWith("\r") || ((_a2 = hunk.lines[i + 1]) === null || _a2 === void 0 ? void 0 : _a2.startsWith("\\")) ? line : line + "\r";
19740
+ }) })) });
19741
+ }
19742
+ function winToUnix(patch) {
19743
+ if (Array.isArray(patch)) {
19744
+ return patch.map((p) => winToUnix(p));
19745
+ }
19746
+ return Object.assign(Object.assign({}, patch), { hunks: patch.hunks.map((hunk) => Object.assign(Object.assign({}, hunk), { lines: hunk.lines.map((line) => line.endsWith("\r") ? line.substring(0, line.length - 1) : line) })) });
19747
+ }
19748
+ function isUnix(patch) {
19749
+ if (!Array.isArray(patch)) {
19750
+ patch = [patch];
19751
+ }
19752
+ return !patch.some((index) => index.hunks.some((hunk) => hunk.lines.some((line) => !line.startsWith("\\") && line.endsWith("\r"))));
19753
+ }
19754
+ function isWin(patch) {
19755
+ if (!Array.isArray(patch)) {
19756
+ patch = [patch];
19757
+ }
19758
+ return patch.some((index) => index.hunks.some((hunk) => hunk.lines.some((line) => line.endsWith("\r")))) && patch.every((index) => index.hunks.every((hunk) => hunk.lines.every((line, i) => {
19759
+ var _a2;
19760
+ return line.startsWith("\\") || line.endsWith("\r") || ((_a2 = hunk.lines[i + 1]) === null || _a2 === void 0 ? void 0 : _a2.startsWith("\\"));
19761
+ })));
19762
+ }
19763
+
19764
+ // node_modules/diff/libesm/patch/parse.js
19765
+ function parsePatch(uniDiff) {
19766
+ const diffstr = uniDiff.split(/\n/), list = [];
19767
+ let i = 0;
19768
+ function isGitDiffHeader(line) {
19769
+ return /^diff --git /.test(line);
19770
+ }
19771
+ function isDiffHeader(line) {
19772
+ return isGitDiffHeader(line) || /^Index:\s/.test(line) || /^diff(?: -r \w+)+\s/.test(line);
19773
+ }
19774
+ function isFileHeader(line) {
19775
+ return /^(---|\+\+\+)\s/.test(line);
19776
+ }
19777
+ function isHunkHeader(line) {
19778
+ return /^@@\s/.test(line);
19779
+ }
19780
+ function parseIndex() {
19781
+ var _a2;
19782
+ const index = {};
19783
+ index.hunks = [];
19784
+ list.push(index);
19785
+ let seenDiffHeader = false;
19786
+ while (i < diffstr.length) {
19787
+ const line = diffstr[i];
19788
+ if (isFileHeader(line) || isHunkHeader(line)) {
19789
+ break;
19790
+ }
19791
+ if (isGitDiffHeader(line)) {
19792
+ if (seenDiffHeader) {
19793
+ return;
19794
+ }
19795
+ seenDiffHeader = true;
19796
+ index.isGit = true;
19797
+ const paths = parseGitDiffHeader(line);
19798
+ if (paths) {
19799
+ index.oldFileName = paths.oldFileName;
19800
+ index.newFileName = paths.newFileName;
19801
+ }
19802
+ i++;
19803
+ while (i < diffstr.length) {
19804
+ const extLine = diffstr[i];
19805
+ if (isFileHeader(extLine) || isHunkHeader(extLine) || isDiffHeader(extLine)) {
19806
+ break;
19807
+ }
19808
+ const renameFromMatch = /^rename from (.*)/.exec(extLine);
19809
+ if (renameFromMatch) {
19810
+ index.oldFileName = "a/" + unquoteIfQuoted(renameFromMatch[1]);
19811
+ index.isRename = true;
19812
+ }
19813
+ const renameToMatch = /^rename to (.*)/.exec(extLine);
19814
+ if (renameToMatch) {
19815
+ index.newFileName = "b/" + unquoteIfQuoted(renameToMatch[1]);
19816
+ index.isRename = true;
19817
+ }
19818
+ const copyFromMatch = /^copy from (.*)/.exec(extLine);
19819
+ if (copyFromMatch) {
19820
+ index.oldFileName = "a/" + unquoteIfQuoted(copyFromMatch[1]);
19821
+ index.isCopy = true;
19822
+ }
19823
+ const copyToMatch = /^copy to (.*)/.exec(extLine);
19824
+ if (copyToMatch) {
19825
+ index.newFileName = "b/" + unquoteIfQuoted(copyToMatch[1]);
19826
+ index.isCopy = true;
19827
+ }
19828
+ const newFileModeMatch = /^new file mode (\d+)/.exec(extLine);
19829
+ if (newFileModeMatch) {
19830
+ index.isCreate = true;
19831
+ index.newMode = newFileModeMatch[1];
19832
+ }
19833
+ const deletedFileModeMatch = /^deleted file mode (\d+)/.exec(extLine);
19834
+ if (deletedFileModeMatch) {
19835
+ index.isDelete = true;
19836
+ index.oldMode = deletedFileModeMatch[1];
19837
+ }
19838
+ const oldModeMatch = /^old mode (\d+)/.exec(extLine);
19839
+ if (oldModeMatch) {
19840
+ index.oldMode = oldModeMatch[1];
19841
+ }
19842
+ const newModeMatch = /^new mode (\d+)/.exec(extLine);
19843
+ if (newModeMatch) {
19844
+ index.newMode = newModeMatch[1];
19845
+ }
19846
+ if (/^Binary files /.test(extLine)) {
19847
+ index.isBinary = true;
19848
+ }
19849
+ i++;
19850
+ }
19851
+ continue;
19852
+ } else if (isDiffHeader(line)) {
19853
+ if (seenDiffHeader) {
19854
+ return;
19855
+ }
19856
+ seenDiffHeader = true;
19857
+ const headerMatch = /^(?:Index:|diff(?: -r \w+)+)\s+/.exec(line);
19858
+ if (headerMatch) {
19859
+ index.index = line.substring(headerMatch[0].length).trim();
19860
+ }
19861
+ }
19862
+ i++;
19863
+ }
19864
+ parseFileHeader(index);
19865
+ parseFileHeader(index);
19866
+ if (index.oldFileName === void 0 !== (index.newFileName === void 0)) {
19867
+ throw new Error("Missing " + (index.oldFileName !== void 0 ? '"+++ ..."' : '"--- ..."') + " file header for " + ((_a2 = index.oldFileName) !== null && _a2 !== void 0 ? _a2 : index.newFileName));
19868
+ }
19869
+ while (i < diffstr.length) {
19870
+ const line = diffstr[i];
19871
+ if (isDiffHeader(line) || isFileHeader(line) || /^===================================================================/.test(line)) {
19872
+ break;
19873
+ } else if (isHunkHeader(line)) {
19874
+ index.hunks.push(parseHunk());
19875
+ } else {
19876
+ i++;
19877
+ }
19878
+ }
19879
+ }
19880
+ function parseGitDiffHeader(line) {
19881
+ const rest = line.substring("diff --git ".length);
19882
+ if (rest.startsWith('"')) {
19883
+ const oldPath = parseQuotedFileName(rest);
19884
+ if (oldPath === null) {
19885
+ return null;
19886
+ }
19887
+ const afterOld = rest.substring(oldPath.rawLength + 1);
19888
+ let newFileName;
19889
+ if (afterOld.startsWith('"')) {
19890
+ const newPath = parseQuotedFileName(afterOld);
19891
+ if (newPath === null) {
19892
+ return null;
19893
+ }
19894
+ newFileName = newPath.fileName;
19895
+ } else {
19896
+ newFileName = afterOld;
19897
+ }
19898
+ return {
19899
+ oldFileName: oldPath.fileName,
19900
+ newFileName
19901
+ };
19902
+ }
19903
+ const quoteIdx = rest.indexOf('"');
19904
+ if (quoteIdx > 0) {
19905
+ const oldFileName = rest.substring(0, quoteIdx - 1);
19906
+ const newPath = parseQuotedFileName(rest.substring(quoteIdx));
19907
+ if (newPath === null) {
19908
+ return null;
19909
+ }
19910
+ return {
19911
+ oldFileName,
19912
+ newFileName: newPath.fileName
19913
+ };
19914
+ }
19915
+ if (rest.startsWith("a/")) {
19916
+ const splits = [];
19917
+ let idx = 0;
19918
+ while (true) {
19919
+ idx = rest.indexOf(" b/", idx + 1);
19920
+ if (idx === -1) {
19921
+ break;
19922
+ }
19923
+ splits.push(idx);
19924
+ }
19925
+ if (splits.length > 0) {
19926
+ const mid = splits[Math.floor(splits.length / 2)];
19927
+ return {
19928
+ oldFileName: rest.substring(0, mid),
19929
+ newFileName: rest.substring(mid + 1)
19930
+ };
19931
+ }
19932
+ }
19933
+ return null;
19934
+ }
19935
+ function unquoteIfQuoted(s) {
19936
+ if (s.startsWith('"')) {
19937
+ const parsed = parseQuotedFileName(s);
19938
+ if (parsed) {
19939
+ return parsed.fileName;
19940
+ }
19941
+ }
19942
+ return s;
19943
+ }
19944
+ function parseQuotedFileName(s) {
19945
+ if (!s.startsWith('"')) {
19946
+ return null;
19947
+ }
19948
+ let result = "";
19949
+ let j = 1;
19950
+ while (j < s.length) {
19951
+ if (s[j] === '"') {
19952
+ return { fileName: result, rawLength: j + 1 };
19953
+ }
19954
+ if (s[j] === "\\" && j + 1 < s.length) {
19955
+ j++;
19956
+ switch (s[j]) {
19957
+ case "a":
19958
+ result += "\x07";
19959
+ break;
19960
+ case "b":
19961
+ result += "\b";
19962
+ break;
19963
+ case "f":
19964
+ result += "\f";
19965
+ break;
19966
+ case "n":
19967
+ result += "\n";
19968
+ break;
19969
+ case "r":
19970
+ result += "\r";
19971
+ break;
19972
+ case "t":
19973
+ result += " ";
19974
+ break;
19975
+ case "v":
19976
+ result += "\v";
19977
+ break;
19978
+ case "\\":
19979
+ result += "\\";
19980
+ break;
19981
+ case '"':
19982
+ result += '"';
19983
+ break;
19984
+ case "0":
19985
+ case "1":
19986
+ case "2":
19987
+ case "3":
19988
+ case "4":
19989
+ case "5":
19990
+ case "6":
19991
+ case "7": {
19992
+ if (j + 2 >= s.length || s[j + 1] < "0" || s[j + 1] > "7" || s[j + 2] < "0" || s[j + 2] > "7") {
19993
+ return null;
19994
+ }
19995
+ const bytes = [parseInt(s.substring(j, j + 3), 8)];
19996
+ j += 3;
19997
+ while (s[j] === "\\" && s[j + 1] >= "0" && s[j + 1] <= "7") {
19998
+ if (j + 3 >= s.length || s[j + 2] < "0" || s[j + 2] > "7" || s[j + 3] < "0" || s[j + 3] > "7") {
19999
+ return null;
20000
+ }
20001
+ bytes.push(parseInt(s.substring(j + 1, j + 4), 8));
20002
+ j += 4;
20003
+ }
20004
+ result += new TextDecoder("utf-8").decode(new Uint8Array(bytes));
20005
+ continue;
20006
+ }
20007
+ // Note that in C, there are also three kinds of hex escape sequences:
20008
+ // - \xhh
20009
+ // - \uhhhh
20010
+ // - \Uhhhhhhhh
20011
+ // We do not bother to parse them here because, so far as we know,
20012
+ // they are never emitted by any tools that generate unified diff
20013
+ // format diffs, and so for now jsdiff does not consider them legal.
20014
+ default:
20015
+ return null;
20016
+ }
20017
+ } else {
20018
+ result += s[j];
20019
+ }
20020
+ j++;
20021
+ }
20022
+ return null;
20023
+ }
20024
+ function parseFileHeader(index) {
20025
+ const fileHeaderMatch = /^(---|\+\+\+)\s+/.exec(diffstr[i]);
20026
+ if (fileHeaderMatch) {
20027
+ const prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split(" ", 2), header = (data[1] || "").trim();
20028
+ let fileName = data[0];
20029
+ if (fileName.startsWith('"')) {
20030
+ fileName = unquoteIfQuoted(fileName);
20031
+ } else {
20032
+ fileName = fileName.replace(/\\\\/g, "\\");
20033
+ }
20034
+ if (prefix === "---") {
20035
+ index.oldFileName = fileName;
20036
+ index.oldHeader = header;
20037
+ } else {
20038
+ index.newFileName = fileName;
20039
+ index.newHeader = header;
20040
+ }
20041
+ i++;
20042
+ }
20043
+ }
20044
+ function parseHunk() {
20045
+ var _a2;
20046
+ const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
20047
+ const hunk = {
20048
+ oldStart: +chunkHeader[1],
20049
+ oldLines: typeof chunkHeader[2] === "undefined" ? 1 : +chunkHeader[2],
20050
+ newStart: +chunkHeader[3],
20051
+ newLines: typeof chunkHeader[4] === "undefined" ? 1 : +chunkHeader[4],
20052
+ lines: []
20053
+ };
20054
+ if (hunk.oldLines === 0) {
20055
+ hunk.oldStart += 1;
20056
+ }
20057
+ if (hunk.newLines === 0) {
20058
+ hunk.newStart += 1;
20059
+ }
20060
+ let addCount = 0, removeCount = 0;
20061
+ for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a2 = diffstr[i]) === null || _a2 === void 0 ? void 0 : _a2.startsWith("\\"))); i++) {
20062
+ const operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? " " : diffstr[i][0];
20063
+ if (operation === "+" || operation === "-" || operation === " " || operation === "\\") {
20064
+ hunk.lines.push(diffstr[i]);
20065
+ if (operation === "+") {
20066
+ addCount++;
20067
+ } else if (operation === "-") {
20068
+ removeCount++;
20069
+ } else if (operation === " ") {
20070
+ addCount++;
20071
+ removeCount++;
20072
+ }
20073
+ } else {
20074
+ throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`);
20075
+ }
20076
+ }
20077
+ if (!addCount && hunk.newLines === 1) {
20078
+ hunk.newLines = 0;
20079
+ }
20080
+ if (!removeCount && hunk.oldLines === 1) {
20081
+ hunk.oldLines = 0;
20082
+ }
20083
+ if (addCount !== hunk.newLines) {
20084
+ throw new Error("Added line count did not match for hunk at line " + (chunkHeaderIndex + 1));
20085
+ }
20086
+ if (removeCount !== hunk.oldLines) {
20087
+ throw new Error("Removed line count did not match for hunk at line " + (chunkHeaderIndex + 1));
20088
+ }
20089
+ if (i < diffstr.length && diffstr[i] && /^[+ -]/.test(diffstr[i]) && !isFileHeader(diffstr[i])) {
20090
+ throw new Error("Hunk at line " + (chunkHeaderIndex + 1) + " has more lines than expected (expected " + hunk.oldLines + " old lines and " + hunk.newLines + " new lines)");
20091
+ }
20092
+ return hunk;
20093
+ }
20094
+ while (i < diffstr.length) {
20095
+ parseIndex();
20096
+ }
20097
+ return list;
20098
+ }
20099
+
20100
+ // node_modules/diff/libesm/util/distance-iterator.js
20101
+ function distance_iterator_default(start, minLine, maxLine) {
20102
+ let wantForward = true, backwardExhausted = false, forwardExhausted = false, localOffset = 1;
20103
+ return function iterator() {
20104
+ if (wantForward && !forwardExhausted) {
20105
+ if (backwardExhausted) {
20106
+ localOffset++;
20107
+ } else {
20108
+ wantForward = false;
20109
+ }
20110
+ if (start + localOffset <= maxLine) {
20111
+ return start + localOffset;
20112
+ }
20113
+ forwardExhausted = true;
20114
+ }
20115
+ if (!backwardExhausted) {
20116
+ if (!forwardExhausted) {
20117
+ wantForward = true;
20118
+ }
20119
+ if (minLine <= start - localOffset) {
20120
+ return start - localOffset++;
20121
+ }
20122
+ backwardExhausted = true;
20123
+ return iterator();
20124
+ }
20125
+ return void 0;
20126
+ };
20127
+ }
20128
+
20129
+ // node_modules/diff/libesm/patch/apply.js
20130
+ function applyPatch(source, patch, options = {}) {
20131
+ let patches;
20132
+ if (typeof patch === "string") {
20133
+ patches = parsePatch(patch);
20134
+ } else if (Array.isArray(patch)) {
20135
+ patches = patch;
20136
+ } else {
20137
+ patches = [patch];
20138
+ }
20139
+ if (patches.length > 1) {
20140
+ throw new Error("applyPatch only works with a single input.");
20141
+ }
20142
+ return applyStructuredPatch(source, patches[0], options);
20143
+ }
20144
+ function applyStructuredPatch(source, patch, options = {}) {
20145
+ if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) {
20146
+ if (hasOnlyWinLineEndings(source) && isUnix(patch)) {
20147
+ patch = unixToWin(patch);
20148
+ } else if (hasOnlyUnixLineEndings(source) && isWin(patch)) {
20149
+ patch = winToUnix(patch);
20150
+ }
20151
+ }
20152
+ const lines = source.split("\n"), hunks = patch.hunks, compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), fuzzFactor = options.fuzzFactor || 0;
20153
+ let minLine = 0;
20154
+ if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) {
20155
+ throw new Error("fuzzFactor must be a non-negative integer");
20156
+ }
20157
+ if (!hunks.length) {
20158
+ return source;
20159
+ }
20160
+ let prevLine = "", removeEOFNL = false, addEOFNL = false;
20161
+ for (let i = 0; i < hunks[hunks.length - 1].lines.length; i++) {
20162
+ const line = hunks[hunks.length - 1].lines[i];
20163
+ if (line[0] == "\\") {
20164
+ if (prevLine[0] == "+") {
20165
+ removeEOFNL = true;
20166
+ } else if (prevLine[0] == "-") {
20167
+ addEOFNL = true;
20168
+ }
20169
+ }
20170
+ prevLine = line;
20171
+ }
20172
+ if (removeEOFNL) {
20173
+ if (addEOFNL) {
20174
+ if (!fuzzFactor && lines[lines.length - 1] == "") {
20175
+ return false;
20176
+ }
20177
+ } else if (lines[lines.length - 1] == "") {
20178
+ lines.pop();
20179
+ } else if (!fuzzFactor) {
20180
+ return false;
20181
+ }
20182
+ } else if (addEOFNL) {
20183
+ if (lines[lines.length - 1] != "") {
20184
+ lines.push("");
20185
+ } else if (!fuzzFactor) {
20186
+ return false;
20187
+ }
20188
+ }
20189
+ function applyHunk(hunkLines, toPos, maxErrors, hunkLinesI = 0, lastContextLineMatched = true, patchedLines = [], patchedLinesLength = 0) {
20190
+ let nConsecutiveOldContextLines = 0;
20191
+ let nextContextLineMustMatch = false;
20192
+ for (; hunkLinesI < hunkLines.length; hunkLinesI++) {
20193
+ const hunkLine = hunkLines[hunkLinesI], operation = hunkLine.length > 0 ? hunkLine[0] : " ", content = hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine;
20194
+ if (operation === "-") {
20195
+ if (compareLine(toPos + 1, lines[toPos], operation, content)) {
20196
+ toPos++;
20197
+ nConsecutiveOldContextLines = 0;
20198
+ } else {
20199
+ if (!maxErrors || lines[toPos] == null) {
20200
+ return null;
20201
+ }
20202
+ patchedLines[patchedLinesLength] = lines[toPos];
20203
+ return applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1);
20204
+ }
20205
+ }
20206
+ if (operation === "+") {
20207
+ if (!lastContextLineMatched) {
20208
+ return null;
20209
+ }
20210
+ patchedLines[patchedLinesLength] = content;
20211
+ patchedLinesLength++;
20212
+ nConsecutiveOldContextLines = 0;
20213
+ nextContextLineMustMatch = true;
20214
+ }
20215
+ if (operation === " ") {
20216
+ nConsecutiveOldContextLines++;
20217
+ patchedLines[patchedLinesLength] = lines[toPos];
20218
+ if (compareLine(toPos + 1, lines[toPos], operation, content)) {
20219
+ patchedLinesLength++;
20220
+ lastContextLineMatched = true;
20221
+ nextContextLineMustMatch = false;
20222
+ toPos++;
20223
+ } else {
20224
+ if (nextContextLineMustMatch || !maxErrors) {
20225
+ return null;
20226
+ }
20227
+ return lines[toPos] && (applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength + 1) || applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1)) || applyHunk(hunkLines, toPos, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength);
20228
+ }
20229
+ }
20230
+ }
20231
+ patchedLinesLength -= nConsecutiveOldContextLines;
20232
+ toPos -= nConsecutiveOldContextLines;
20233
+ patchedLines.length = patchedLinesLength;
20234
+ return {
20235
+ patchedLines,
20236
+ oldLineLastI: toPos - 1
20237
+ };
20238
+ }
20239
+ const resultLines = [];
20240
+ let prevHunkOffset = 0;
20241
+ for (let i = 0; i < hunks.length; i++) {
20242
+ const hunk = hunks[i];
20243
+ let hunkResult;
20244
+ const maxLine = lines.length - hunk.oldLines + fuzzFactor;
20245
+ let toPos;
20246
+ for (let maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) {
20247
+ toPos = hunk.oldStart + prevHunkOffset - 1;
20248
+ const iterator = distance_iterator_default(toPos, minLine, maxLine);
20249
+ for (; toPos !== void 0; toPos = iterator()) {
20250
+ hunkResult = applyHunk(hunk.lines, toPos, maxErrors);
20251
+ if (hunkResult) {
20252
+ break;
20253
+ }
20254
+ }
20255
+ if (hunkResult) {
20256
+ break;
20257
+ }
20258
+ }
20259
+ if (!hunkResult) {
20260
+ return false;
20261
+ }
20262
+ for (let i2 = minLine; i2 < toPos; i2++) {
20263
+ resultLines.push(lines[i2]);
20264
+ }
20265
+ for (let i2 = 0; i2 < hunkResult.patchedLines.length; i2++) {
20266
+ const line = hunkResult.patchedLines[i2];
20267
+ resultLines.push(line);
20268
+ }
20269
+ minLine = hunkResult.oldLineLastI + 1;
20270
+ prevHunkOffset = toPos + 1 - hunk.oldStart;
20271
+ }
20272
+ for (let i = minLine; i < lines.length; i++) {
20273
+ resultLines.push(lines[i]);
20274
+ }
20275
+ return resultLines.join("\n");
20276
+ }
20277
+
19727
20278
  // src/utils.ts
20279
+ var import_url = require("url");
19728
20280
  function toToolKind(toolName) {
19729
20281
  const tool = toolName.toLowerCase();
19730
20282
  switch (tool) {
@@ -19764,6 +20316,26 @@ function toLocations(toolName, input) {
19764
20316
  return [];
19765
20317
  }
19766
20318
  }
20319
+ function parseUri(uri) {
20320
+ try {
20321
+ if (uri.startsWith("file://")) {
20322
+ const path = uri.slice(7);
20323
+ const name = path.split("/").pop() || path;
20324
+ return { type: "file", url: uri, filename: name, mime: "text/plain" };
20325
+ }
20326
+ if (uri.startsWith("zed://")) {
20327
+ const url2 = new URL(uri);
20328
+ const path = url2.searchParams.get("path");
20329
+ if (path) {
20330
+ const name = path.split("/").pop() || path;
20331
+ return { type: "file", url: (0, import_url.pathToFileURL)(path).href, filename: name, mime: "text/plain" };
20332
+ }
20333
+ }
20334
+ return { type: "text", text: uri };
20335
+ } catch {
20336
+ return { type: "text", text: uri };
20337
+ }
20338
+ }
19767
20339
 
19768
20340
  // src/logger.ts
19769
20341
  var import_fs = require("fs");
@@ -19785,7 +20357,11 @@ function setLogEnabled(v) {
19785
20357
  }
19786
20358
  function cleanOldLogs() {
19787
20359
  try {
19788
- const files = (0, import_fs.readdirSync)(LOG_DIR).filter((f) => f.endsWith(".log")).map((f) => ({ name: f, path: (0, import_path.join)(LOG_DIR, f), mtime: (0, import_fs.statSync)((0, import_path.join)(LOG_DIR, f)).mtime.getTime() })).sort((a, b) => b.mtime - a.mtime);
20360
+ const files = (0, import_fs.readdirSync)(LOG_DIR).filter((f) => f.endsWith(".log")).map((f) => ({
20361
+ name: f,
20362
+ path: (0, import_path.join)(LOG_DIR, f),
20363
+ mtime: (0, import_fs.statSync)((0, import_path.join)(LOG_DIR, f)).mtime.getTime()
20364
+ })).sort((a, b) => b.mtime - a.mtime);
19789
20365
  for (let i = MAX_LOG_FILES; i < files.length; i++) {
19790
20366
  try {
19791
20367
  const { unlinkSync } = require("fs");
@@ -19867,6 +20443,7 @@ function sanitizeAssembled(obj, depth = 0) {
19867
20443
  }
19868
20444
 
19869
20445
  // src/event-handler.ts
20446
+ var QUESTION_TIMEOUT_MS = 6e4;
19870
20447
  var permissionOptions = [
19871
20448
  { optionId: "once", kind: "allow_once", name: "Allow once" },
19872
20449
  { optionId: "always", kind: "allow_always", name: "Always allow" },
@@ -19883,6 +20460,11 @@ var EventHandler = class {
19883
20460
  questionQueues = /* @__PURE__ */ new Map();
19884
20461
  started = false;
19885
20462
  messageBuffers = /* @__PURE__ */ new Map();
20463
+ /** Index: sessionId → Set<messageId> for O(1) flush lookup. */
20464
+ sessionBufferIndex = /* @__PURE__ */ new Map();
20465
+ messageMetaCache = /* @__PURE__ */ new Map();
20466
+ /** Track tool call counts per child session to detect completion. */
20467
+ childToolCounts = /* @__PURE__ */ new Map();
19886
20468
  /**
19887
20469
  * Wrapper around connection.sessionUpdate that logs what's sent to the ACP client.
19888
20470
  * This is the single point where we record all outgoing traffic.
@@ -19893,7 +20475,15 @@ var EventHandler = class {
19893
20475
  this.flushMessageBuffer(params.sessionId, `boundary:${updateType}`);
19894
20476
  acpOut(`sessionUpdate.${updateType}`, {
19895
20477
  sessionId: params.sessionId.slice(0, 12),
19896
- ...updateType === "tool_call" || updateType === "tool_call_update" ? { toolCallId: params.update.toolCallId, tool: params.update.title, kind: params.update.kind, status: params.update.status } : updateType === "session_info_update" ? { title: params.update.title, _meta: params.update._meta } : {}
20478
+ ...updateType === "tool_call" || updateType === "tool_call_update" ? {
20479
+ toolCallId: params.update.toolCallId,
20480
+ tool: params.update.title,
20481
+ kind: params.update.kind,
20482
+ status: params.update.status
20483
+ } : updateType === "session_info_update" ? {
20484
+ title: params.update.title,
20485
+ _meta: params.update._meta
20486
+ } : {}
19897
20487
  });
19898
20488
  }
19899
20489
  return this.connection.sessionUpdate(params);
@@ -19908,6 +20498,12 @@ var EventHandler = class {
19908
20498
  if (!buf) {
19909
20499
  buf = { sessionId, text: "", thought: "" };
19910
20500
  this.messageBuffers.set(messageId, buf);
20501
+ const msgSet = this.sessionBufferIndex.get(sessionId);
20502
+ if (msgSet) {
20503
+ msgSet.add(messageId);
20504
+ } else {
20505
+ this.sessionBufferIndex.set(sessionId, /* @__PURE__ */ new Set([messageId]));
20506
+ }
19911
20507
  }
19912
20508
  buf[kind] += delta;
19913
20509
  }
@@ -19917,8 +20513,11 @@ var EventHandler = class {
19917
20513
  * text/thought phase is complete.
19918
20514
  */
19919
20515
  flushMessageBuffer(sessionId, reason) {
19920
- for (const [messageId, buf] of this.messageBuffers) {
19921
- if (buf.sessionId !== sessionId) continue;
20516
+ const msgIds = this.sessionBufferIndex.get(sessionId);
20517
+ if (!msgIds) return;
20518
+ for (const messageId of msgIds) {
20519
+ const buf = this.messageBuffers.get(messageId);
20520
+ if (!buf) continue;
19922
20521
  if (!buf.text && !buf.thought) {
19923
20522
  this.messageBuffers.delete(messageId);
19924
20523
  continue;
@@ -19943,11 +20542,46 @@ var EventHandler = class {
19943
20542
  }
19944
20543
  this.messageBuffers.delete(messageId);
19945
20544
  }
20545
+ this.sessionBufferIndex.delete(sessionId);
19946
20546
  }
19947
20547
  /** Flush all remaining buffers (e.g. on stop) */
19948
20548
  flushAllBuffers() {
19949
- for (const [messageId, buf] of this.messageBuffers) {
19950
- this.flushMessageBuffer(buf.sessionId, "stop");
20549
+ for (const sessionId of this.sessionBufferIndex.keys()) {
20550
+ this.flushMessageBuffer(sessionId, "stop");
20551
+ }
20552
+ }
20553
+ /**
20554
+ * Get or cache message metadata (role + parts) for a message.
20555
+ * Fetches from SDK only on first delta for a message, then serves from cache.
20556
+ */
20557
+ async getOrCacheMessageMeta(sessionID, messageID, cwd) {
20558
+ const cached2 = this.messageMetaCache.get(messageID);
20559
+ if (cached2) return cached2;
20560
+ const message = await this.sdk.session.message({ sessionID, messageID, directory: cwd }, { throwOnError: true }).then((x) => x.data).catch(() => void 0);
20561
+ if (!message) return void 0;
20562
+ const parts = /* @__PURE__ */ new Map();
20563
+ for (const p of message.parts) {
20564
+ parts.set(p.id, { type: p.type, ignored: p.ignored });
20565
+ }
20566
+ const meta3 = { role: message.info.role, parts };
20567
+ this.messageMetaCache.set(messageID, meta3);
20568
+ if (this.messageMetaCache.size > 50) {
20569
+ const oldest = this.messageMetaCache.keys().next().value;
20570
+ if (oldest) this.messageMetaCache.delete(oldest);
20571
+ }
20572
+ return meta3;
20573
+ }
20574
+ /**
20575
+ * Flush previous message buffer for a session when messageId changes.
20576
+ */
20577
+ flushPreviousBuffer(sessionId, currentMessageId) {
20578
+ const msgIds = this.sessionBufferIndex.get(sessionId);
20579
+ if (!msgIds) return;
20580
+ for (const messageId of msgIds) {
20581
+ if (messageId !== currentMessageId) {
20582
+ this.flushMessageBuffer(sessionId, "new_message");
20583
+ return;
20584
+ }
19951
20585
  }
19952
20586
  }
19953
20587
  constructor(deps) {
@@ -19991,12 +20625,8 @@ var EventHandler = class {
19991
20625
  */
19992
20626
  resolveSession(sessionId) {
19993
20627
  const session = this.sessionManager.tryGet(sessionId);
19994
- if (session) return { sessionId: session.id, cwd: session.cwd };
19995
- const root = this.sessionManager.findRootSession(sessionId);
19996
- if (!root) return void 0;
19997
- const child = this.sessionManager.tryGet(sessionId);
19998
- if (child) return { sessionId: child.id, cwd: child.cwd };
19999
- return void 0;
20628
+ if (!session) return void 0;
20629
+ return { sessionId: session.id, cwd: session.cwd };
20000
20630
  }
20001
20631
  // ─── Event Handling ──────────────────────────────────────────────
20002
20632
  async handleEvent(event) {
@@ -20013,7 +20643,13 @@ var EventHandler = class {
20013
20643
  this.sessionManager.registerDiscovered(id, parentID, title);
20014
20644
  if (isNew) {
20015
20645
  ocEvent("session.created.child", { id, parentID, title });
20016
- await this.announceChildSession(id, parentID, title);
20646
+ const agentMatch = title?.match(/@(\w+)\s+subagent/);
20647
+ const agentType = agentMatch?.[1];
20648
+ const description = title?.replace(/\s*\(@\w+\s+subagent\)\s*$/, "") ?? title;
20649
+ await this.announceChildSession(id, parentID, title, {
20650
+ agentType,
20651
+ description
20652
+ });
20017
20653
  }
20018
20654
  return;
20019
20655
  }
@@ -20058,11 +20694,16 @@ var EventHandler = class {
20058
20694
  const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : "";
20059
20695
  const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : "";
20060
20696
  if (filepath && diff) {
20061
- this.connection.writeTextFile({
20062
- sessionId,
20063
- path: filepath,
20064
- content: ""
20065
- });
20697
+ const fileResult = await this.sdk.file.read({ path: filepath, directory }).then((x) => x.data).catch(() => void 0);
20698
+ const original = typeof fileResult?.content === "string" ? fileResult.content : "";
20699
+ const newContent = applyPatch(original, diff);
20700
+ if (newContent !== false) {
20701
+ this.connection.writeTextFile({
20702
+ sessionId,
20703
+ path: filepath,
20704
+ content: newContent
20705
+ });
20706
+ }
20066
20707
  }
20067
20708
  }
20068
20709
  await this.sdk.permission.reply({
@@ -20086,26 +20727,50 @@ var EventHandler = class {
20086
20727
  if (!resolved) return;
20087
20728
  const directory = resolved.cwd;
20088
20729
  const sessionId = resolved.sessionId;
20089
- ocEvent("question.asked", { id: q.id, sessionID: q.sessionID, questions: q.questions.length });
20730
+ ocEvent("question.asked", {
20731
+ id: q.id,
20732
+ sessionID: q.sessionID,
20733
+ questions: q.questions.length
20734
+ });
20090
20735
  const prev = this.questionQueues.get(q.sessionID) ?? Promise.resolve();
20091
20736
  const next = prev.then(async () => {
20092
- const res = await this.connection.extMethod("questionAsked", {
20093
- sessionId,
20094
- questionId: q.id,
20095
- questions: q.questions
20096
- }).catch(() => void 0);
20097
- if (!res || !res.answers) {
20737
+ const extResult = await Promise.race([
20738
+ this.connection.extMethod("questionAsked", {
20739
+ sessionId,
20740
+ questionId: q.id,
20741
+ questions: q.questions,
20742
+ ...q.tool && { tool: q.tool }
20743
+ }),
20744
+ new Promise(
20745
+ (_, reject) => setTimeout(() => reject(new Error("questionAsked timeout")), QUESTION_TIMEOUT_MS)
20746
+ )
20747
+ ]).catch((err) => {
20748
+ console.error("[event-handler] extMethod questionAsked failed:", err?.message ?? err);
20749
+ return void 0;
20750
+ });
20751
+ if (!extResult || !extResult.answers) {
20098
20752
  await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
20099
20753
  });
20100
20754
  ocEvent("question.rejected", { requestID: q.id });
20101
20755
  return;
20102
20756
  }
20757
+ const answers = extResult.answers;
20758
+ if (!Array.isArray(answers) || !answers.every((a) => Array.isArray(a) && a.every((v) => typeof v === "string"))) {
20759
+ console.error("[event-handler] invalid answers shape from client:", JSON.stringify(answers));
20760
+ await this.sdk.question.reject({ requestID: q.id, directory }).catch(() => {
20761
+ });
20762
+ ocEvent("question.rejected", {
20763
+ requestID: q.id,
20764
+ reason: "invalid_answers"
20765
+ });
20766
+ return;
20767
+ }
20103
20768
  await this.sdk.question.reply({
20104
20769
  requestID: q.id,
20105
20770
  directory,
20106
- answers: res.answers
20771
+ answers
20107
20772
  });
20108
- ocEvent("question.reply", { requestID: q.id, answers: res.answers });
20773
+ ocEvent("question.reply", { requestID: q.id, answers });
20109
20774
  }).catch((err) => {
20110
20775
  console.error("[event-handler] question handling error:", err);
20111
20776
  }).finally(() => {
@@ -20131,24 +20796,12 @@ var EventHandler = class {
20131
20796
  const resolved = this.resolveSession(props.sessionID);
20132
20797
  if (!resolved) return;
20133
20798
  const sessionId = resolved.sessionId;
20134
- const currentBufKey = [...this.messageBuffers.keys()].find(
20135
- (k) => this.messageBuffers.get(k)?.sessionId === sessionId && k !== props.messageID
20136
- );
20137
- if (currentBufKey) {
20138
- this.flushMessageBuffer(sessionId, "new_message");
20139
- }
20140
- const message = await this.sdk.session.message(
20141
- {
20142
- sessionID: props.sessionID,
20143
- messageID: props.messageID,
20144
- directory: resolved.cwd
20145
- },
20146
- { throwOnError: true }
20147
- ).then((x) => x.data).catch(() => void 0);
20148
- if (!message || message.info.role !== "assistant") return;
20149
- const part = message.parts.find((p) => p.id === props.partID);
20150
- if (!part) return;
20151
- if (part.type === "text" && props.field === "text" && part.ignored !== true) {
20799
+ this.flushPreviousBuffer(sessionId, props.messageID);
20800
+ const meta3 = await this.getOrCacheMessageMeta(props.sessionID, props.messageID, resolved.cwd);
20801
+ if (!meta3 || meta3.role !== "assistant") return;
20802
+ const partMeta = meta3.parts.get(props.partID);
20803
+ if (!partMeta) return;
20804
+ if (partMeta.type === "text" && props.field === "text" && partMeta.ignored !== true) {
20152
20805
  this.accumulateDelta(props.messageID, sessionId, "text", props.delta);
20153
20806
  await this.sendToClient({
20154
20807
  sessionId,
@@ -20161,7 +20814,7 @@ var EventHandler = class {
20161
20814
  });
20162
20815
  return;
20163
20816
  }
20164
- if (part.type === "reasoning" && props.field === "text") {
20817
+ if (partMeta.type === "reasoning" && props.field === "text") {
20165
20818
  this.accumulateDelta(props.messageID, sessionId, "thought", props.delta);
20166
20819
  await this.sendToClient({
20167
20820
  sessionId,
@@ -20182,19 +20835,20 @@ var EventHandler = class {
20182
20835
  * Announce a child session to the ACP client.
20183
20836
  *
20184
20837
  * Sends a session_info_update with the child's own sessionId,
20185
- * and _meta containing the parent relationship so the client
20186
- * can display it as a linked sub-session.
20838
+ * and _meta containing structured metadata about the subagent.
20187
20839
  */
20188
- async announceChildSession(childSessionId, parentSessionId, title) {
20840
+ async announceChildSession(childSessionId, parentSessionId, title, meta3) {
20189
20841
  await this.sendToClient({
20190
20842
  sessionId: childSessionId,
20191
20843
  update: {
20192
20844
  sessionUpdate: "session_info_update",
20193
- title,
20845
+ title: title ?? "Subagent",
20194
20846
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
20195
20847
  _meta: {
20196
20848
  parentSessionId,
20197
- isSubagent: true
20849
+ isSubagent: true,
20850
+ ...meta3?.agentType && { agentType: meta3.agentType },
20851
+ ...meta3?.description && { description: meta3.description }
20198
20852
  }
20199
20853
  }
20200
20854
  }).catch(() => {
@@ -20204,6 +20858,19 @@ var EventHandler = class {
20204
20858
  async handleToolPart(sessionId, part) {
20205
20859
  if (!this.toolStarts.has(part.callID)) {
20206
20860
  this.toolStarts.add(part.callID);
20861
+ const session = this.sessionManager.tryGet(sessionId);
20862
+ if (session?.parentID) {
20863
+ const tracker = this.childToolCounts.get(sessionId);
20864
+ if (tracker) {
20865
+ tracker.total++;
20866
+ } else {
20867
+ this.childToolCounts.set(sessionId, {
20868
+ completed: 0,
20869
+ total: 1,
20870
+ startTime: Date.now()
20871
+ });
20872
+ }
20873
+ }
20207
20874
  await this.sendToClient({
20208
20875
  sessionId,
20209
20876
  update: {
@@ -20255,6 +20922,27 @@ var EventHandler = class {
20255
20922
  case "completed": {
20256
20923
  this.toolStarts.delete(part.callID);
20257
20924
  this.bashSnapshots.delete(part.callID);
20925
+ if (part.tool === "task" && part.state.metadata) {
20926
+ const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
20927
+ if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
20928
+ const tracker = this.childToolCounts.get(childSessionId);
20929
+ await this.sendChildSessionCompleted(
20930
+ childSessionId,
20931
+ sessionId,
20932
+ tracker?.completed ?? 0,
20933
+ tracker ? Date.now() - tracker.startTime : 0
20934
+ ).catch(() => {
20935
+ });
20936
+ this.childToolCounts.delete(childSessionId);
20937
+ }
20938
+ }
20939
+ const session = this.sessionManager.tryGet(sessionId);
20940
+ if (session?.parentID) {
20941
+ const tracker = this.childToolCounts.get(sessionId);
20942
+ if (tracker) {
20943
+ tracker.completed++;
20944
+ }
20945
+ }
20258
20946
  const kind = toToolKind(part.tool);
20259
20947
  const content = [
20260
20948
  {
@@ -20269,6 +20957,26 @@ var EventHandler = class {
20269
20957
  const newText = typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : "";
20270
20958
  content.push({ type: "diff", path: filePath, oldText, newText });
20271
20959
  }
20960
+ if (part.tool === "todowrite") {
20961
+ try {
20962
+ const todos = JSON.parse(part.state.output);
20963
+ if (Array.isArray(todos)) {
20964
+ await this.sendToClient({
20965
+ sessionId,
20966
+ update: {
20967
+ sessionUpdate: "plan",
20968
+ entries: todos.map((todo) => ({
20969
+ priority: "medium",
20970
+ status: todo.status === "cancelled" ? "completed" : todo.status,
20971
+ content: todo.content
20972
+ }))
20973
+ }
20974
+ }).catch(() => {
20975
+ });
20976
+ }
20977
+ } catch {
20978
+ }
20979
+ }
20272
20980
  await this.sendToClient({
20273
20981
  sessionId,
20274
20982
  update: {
@@ -20317,6 +21025,32 @@ var EventHandler = class {
20317
21025
  }
20318
21026
  }
20319
21027
  }
21028
+ /**
21029
+ * Notify the ACP client that a child session has completed.
21030
+ */
21031
+ async sendChildSessionCompleted(childSessionId, parentSessionId, toolCallCount, durationMs) {
21032
+ const child = this.sessionManager.tryGet(childSessionId);
21033
+ const title = child?.title ?? "Subagent";
21034
+ const agentMatch = title.match(/@(\w+)\s+subagent/);
21035
+ const agentType = agentMatch?.[1];
21036
+ const description = title.replace(/\s*\(@\w+\s+subagent\)\s*$/, "");
21037
+ await this.sendToClient({
21038
+ sessionId: childSessionId,
21039
+ update: {
21040
+ sessionUpdate: "session_info_update",
21041
+ title,
21042
+ status: "completed",
21043
+ _meta: {
21044
+ parentSessionId,
21045
+ isSubagent: true,
21046
+ toolCallCount,
21047
+ durationMs,
21048
+ ...agentType && { agentType },
21049
+ ...description && { description }
21050
+ }
21051
+ }
21052
+ });
21053
+ }
20320
21054
  bashOutput(part) {
20321
21055
  if (part.tool !== "bash") return;
20322
21056
  if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return;
@@ -20334,13 +21068,55 @@ function simpleHash(str) {
20334
21068
  return hash2.toString(36);
20335
21069
  }
20336
21070
 
21071
+ // src/auth-provider.ts
21072
+ var AuthProviderManager = class {
21073
+ sdk;
21074
+ constructor(sdk) {
21075
+ this.sdk = sdk;
21076
+ }
21077
+ async listProviders(directory) {
21078
+ ocCall("provider.list", { directory });
21079
+ const res = await this.sdk.provider.list({ directory });
21080
+ return res.data;
21081
+ }
21082
+ async authMethods(directory) {
21083
+ ocCall("provider.auth", { directory });
21084
+ const res = await this.sdk.provider.auth({ directory });
21085
+ return res.data;
21086
+ }
21087
+ async setApiKey(providerID, auth) {
21088
+ ocCall("auth.set", { providerID });
21089
+ const res = await this.sdk.auth.set({ providerID, auth });
21090
+ return res.data;
21091
+ }
21092
+ async removeAuth(providerID) {
21093
+ ocCall("auth.remove", { providerID });
21094
+ const res = await this.sdk.auth.remove({ providerID });
21095
+ return res.data;
21096
+ }
21097
+ async oauthAuthorize(providerID, method, inputs, directory) {
21098
+ ocCall("provider.oauth.authorize", { providerID, method });
21099
+ const res = await this.sdk.provider.oauth.authorize({ providerID, method, inputs, directory });
21100
+ return res.data;
21101
+ }
21102
+ async oauthCallback(providerID, method, code, directory) {
21103
+ ocCall("provider.oauth.callback", { providerID, method });
21104
+ const res = await this.sdk.provider.oauth.callback({ providerID, method, code, directory });
21105
+ return res.data;
21106
+ }
21107
+ };
21108
+
20337
21109
  // src/agent.ts
21110
+ function isApiKeyError(err) {
21111
+ return err instanceof Error && err.name === "AI_LoadAPIKeyError";
21112
+ }
20338
21113
  var Agent = class {
20339
21114
  connection;
20340
21115
  config;
20341
21116
  sdk;
20342
21117
  sessionManager;
20343
21118
  eventHandler;
21119
+ authProvider;
20344
21120
  /**
20345
21121
  * Wrapper around connection.sessionUpdate that logs what's sent to the ACP client.
20346
21122
  */
@@ -20349,7 +21125,12 @@ var Agent = class {
20349
21125
  if (updateType !== "agent_message_chunk" && updateType !== "agent_thought_chunk") {
20350
21126
  acpOut(`sessionUpdate.${updateType}`, {
20351
21127
  sessionId: params.sessionId.slice(0, 12),
20352
- ...updateType === "tool_call" || updateType === "tool_call_update" ? { toolCallId: params.update.toolCallId, tool: params.update.title, kind: params.update.kind, status: params.update.status } : {}
21128
+ ...updateType === "tool_call" || updateType === "tool_call_update" ? {
21129
+ toolCallId: params.update.toolCallId,
21130
+ tool: params.update.title,
21131
+ kind: params.update.kind,
21132
+ status: params.update.status
21133
+ } : {}
20353
21134
  });
20354
21135
  }
20355
21136
  return this.connection.sessionUpdate(params);
@@ -20358,6 +21139,7 @@ var Agent = class {
20358
21139
  this.config = config2;
20359
21140
  this.sdk = config2.sdk;
20360
21141
  this.sessionManager = new SessionManager(config2.sdk);
21142
+ this.authProvider = new AuthProviderManager(config2.sdk);
20361
21143
  }
20362
21144
  init(connection) {
20363
21145
  this.connection = connection;
@@ -20369,8 +21151,22 @@ var Agent = class {
20369
21151
  this.eventHandler.start();
20370
21152
  }
20371
21153
  // ─── Protocol Lifecycle ───────────────────────────────────────────
20372
- async initialize(_params) {
20373
- acpIn("initialize", { protocolVersion: _params.protocolVersion });
21154
+ async initialize(params) {
21155
+ acpIn("initialize", { protocolVersion: params.protocolVersion });
21156
+ const authMethod = {
21157
+ description: "Run `opencode auth login` in the terminal",
21158
+ name: "Login with opencode",
21159
+ id: "opencode-login"
21160
+ };
21161
+ if (params.clientCapabilities?._meta?.["terminal-auth"] === true) {
21162
+ authMethod._meta = {
21163
+ "terminal-auth": {
21164
+ command: "opencode",
21165
+ args: ["auth", "login"],
21166
+ label: "OpenCode Login"
21167
+ }
21168
+ };
21169
+ }
20374
21170
  return {
20375
21171
  protocolVersion: 1,
20376
21172
  agentCapabilities: {
@@ -20379,76 +21175,159 @@ var Agent = class {
20379
21175
  promptCapabilities: { embeddedContext: true, image: true },
20380
21176
  sessionCapabilities: { fork: {}, list: {}, resume: {} }
20381
21177
  },
20382
- authMethods: [
20383
- {
20384
- description: "Run `opencode auth login` in the terminal",
20385
- name: "Login with opencode",
20386
- id: "opencode-login"
20387
- }
20388
- ],
21178
+ authMethods: [authMethod],
20389
21179
  agentInfo: {
20390
21180
  name: "Harmony-ACP",
20391
21181
  version: "0.1.0"
20392
- }
21182
+ },
21183
+ _meta: await this.buildProviderMeta()
20393
21184
  };
20394
21185
  }
20395
21186
  async authenticate(_params) {
20396
21187
  throw new Error("Authentication not implemented");
20397
21188
  }
21189
+ // ─── Extension Methods ────────────────────────────────────────────
21190
+ async extMethod(method, params) {
21191
+ acpIn("extMethod", { method });
21192
+ if (!method.startsWith("provider/")) {
21193
+ throw new Error(`Unknown extMethod: ${method}`);
21194
+ }
21195
+ return this.handleProviderMethod(method, params);
21196
+ }
21197
+ async handleProviderMethod(method, params) {
21198
+ const sessionId = params.sessionId;
21199
+ const cwd = sessionId ? this.sessionManager.get(sessionId)?.cwd : this.config.cwd;
21200
+ switch (method) {
21201
+ case "provider/list": {
21202
+ if (!cwd) throw new Error("Session not found");
21203
+ const data = await this.authProvider.listProviders(cwd);
21204
+ acpOut("extMethod.response", { method: "provider/list" });
21205
+ return data;
21206
+ }
21207
+ case "provider/auth/methods": {
21208
+ if (!cwd) throw new Error("Session not found");
21209
+ const data = await this.authProvider.authMethods(cwd);
21210
+ acpOut("extMethod.response", { method: "provider/auth/methods" });
21211
+ return data;
21212
+ }
21213
+ case "provider/auth/set": {
21214
+ const providerID = params.providerID;
21215
+ const auth = params.auth;
21216
+ if (!providerID) throw new Error("Missing required parameter: providerID");
21217
+ if (!auth?.key) throw new Error("Missing required parameter: auth.key");
21218
+ await this.authProvider.setApiKey(providerID, auth);
21219
+ acpOut("extMethod.response", { method: "provider/auth/set", providerID });
21220
+ return { success: true };
21221
+ }
21222
+ case "provider/auth/remove": {
21223
+ const providerID = params.providerID;
21224
+ if (!providerID) throw new Error("Missing required parameter: providerID");
21225
+ await this.authProvider.removeAuth(providerID);
21226
+ acpOut("extMethod.response", { method: "provider/auth/remove", providerID });
21227
+ return { success: true };
21228
+ }
21229
+ case "provider/oauth/authorize": {
21230
+ const providerID = params.providerID;
21231
+ const oauthMethod = params.method;
21232
+ if (!providerID) throw new Error("Missing required parameter: providerID");
21233
+ const inputs = params.inputs;
21234
+ const data = await this.authProvider.oauthAuthorize(providerID, oauthMethod ?? 0, inputs, cwd);
21235
+ acpOut("extMethod.response", { method: "provider/oauth/authorize", providerID });
21236
+ return data;
21237
+ }
21238
+ case "provider/oauth/callback": {
21239
+ const providerID = params.providerID;
21240
+ const oauthMethod = params.method;
21241
+ const code = params.code;
21242
+ if (!providerID) throw new Error("Missing required parameter: providerID");
21243
+ await this.authProvider.oauthCallback(providerID, oauthMethod ?? 0, code, cwd);
21244
+ acpOut("extMethod.response", { method: "provider/oauth/callback", providerID });
21245
+ return { success: true };
21246
+ }
21247
+ default:
21248
+ throw new Error(`Unknown provider extMethod: ${method}`);
21249
+ }
21250
+ }
21251
+ async buildProviderMeta() {
21252
+ try {
21253
+ const data = await this.authProvider.listProviders(this.config.cwd);
21254
+ if (!data?.all) return void 0;
21255
+ const providers = data.all.map((p) => ({
21256
+ id: p.id,
21257
+ name: p.name,
21258
+ connected: data.connected?.includes(p.id) ?? false
21259
+ }));
21260
+ return { providers };
21261
+ } catch {
21262
+ return void 0;
21263
+ }
21264
+ }
20398
21265
  // ─── Session Management ───────────────────────────────────────────
20399
21266
  async newSession(params) {
20400
21267
  acpIn("newSession", { cwd: params.cwd, mcpServers: params.mcpServers.length });
20401
- const directory = params.cwd;
20402
- ocCall("session.create", { directory });
20403
- const model = await this.defaultModel(directory);
20404
- const state = await this.sessionManager.create(
20405
- params.cwd,
20406
- params.mcpServers,
20407
- model
20408
- );
20409
- const sessionId = state.id;
20410
- const result = await this.loadSessionMode({
20411
- cwd: directory,
20412
- mcpServers: params.mcpServers,
20413
- sessionId
20414
- });
20415
- acpOut("newSession.response", { sessionId });
20416
- return {
20417
- sessionId,
20418
- configOptions: result.configOptions,
20419
- models: result.models,
20420
- modes: result.modes,
20421
- _meta: result._meta
20422
- };
21268
+ try {
21269
+ const directory = params.cwd;
21270
+ ocCall("session.create", { directory });
21271
+ const model = await this.defaultModel(directory);
21272
+ const state = await this.sessionManager.create(params.cwd, params.mcpServers, model);
21273
+ const sessionId = state.id;
21274
+ const result = await this.loadSessionMode({
21275
+ cwd: directory,
21276
+ mcpServers: params.mcpServers,
21277
+ sessionId
21278
+ });
21279
+ acpOut("newSession.response", { sessionId });
21280
+ return {
21281
+ sessionId,
21282
+ configOptions: result.configOptions,
21283
+ models: result.models,
21284
+ modes: result.modes,
21285
+ _meta: result._meta
21286
+ };
21287
+ } catch (e) {
21288
+ if (isApiKeyError(e)) throw RequestError.authRequired();
21289
+ throw e;
21290
+ }
20423
21291
  }
20424
21292
  async loadSession(params) {
20425
- const directory = params.cwd;
20426
- const sessionId = params.sessionId;
20427
- const model = await this.defaultModel(directory);
20428
- await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model);
20429
- const result = await this.loadSessionMode({
20430
- cwd: directory,
20431
- mcpServers: params.mcpServers,
20432
- sessionId
20433
- });
20434
- const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
20435
- const lastUser = messages?.findLast((m) => m.info.role === "user")?.info;
20436
- if (lastUser?.role === "user") {
20437
- result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`;
20438
- this.sessionManager.setModel(sessionId, {
20439
- providerID: lastUser.model.providerID,
20440
- modelID: lastUser.model.modelID
21293
+ try {
21294
+ const directory = params.cwd;
21295
+ const sessionId = params.sessionId;
21296
+ const model = await this.defaultModel(directory);
21297
+ await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model);
21298
+ const result = await this.loadSessionMode({
21299
+ cwd: directory,
21300
+ mcpServers: params.mcpServers,
21301
+ sessionId
20441
21302
  });
20442
- if (result.modes?.availableModes.some((m) => m.id === lastUser.agent)) {
20443
- result.modes.currentModeId = lastUser.agent;
20444
- this.sessionManager.setMode(sessionId, lastUser.agent);
21303
+ const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
21304
+ const lastUser = messages?.findLast((m) => m.info.role === "user")?.info;
21305
+ if (lastUser?.role === "user") {
21306
+ result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`;
21307
+ this.sessionManager.setModel(sessionId, {
21308
+ providerID: lastUser.model.providerID,
21309
+ modelID: lastUser.model.modelID
21310
+ });
21311
+ if (result.modes?.availableModes.some((m) => m.id === lastUser.agent)) {
21312
+ result.modes.currentModeId = lastUser.agent;
21313
+ this.sessionManager.setMode(sessionId, lastUser.agent);
21314
+ }
20445
21315
  }
21316
+ for (const msg of messages ?? []) {
21317
+ await this.processMessage(msg);
21318
+ for (const part of msg.parts ?? []) {
21319
+ if (part.type === "tool" && part.tool === "task" && part.state?.status === "completed" && part.state?.metadata?.sessionId) {
21320
+ const childSessionId = part.state.metadata.sessionId;
21321
+ await this.replayChildSession(childSessionId, sessionId, directory);
21322
+ }
21323
+ }
21324
+ }
21325
+ await this.sendUsageUpdate(sessionId, directory);
21326
+ return result;
21327
+ } catch (e) {
21328
+ if (isApiKeyError(e)) throw RequestError.authRequired();
21329
+ throw e;
20446
21330
  }
20447
- for (const msg of messages ?? []) {
20448
- await this.processMessage(msg);
20449
- }
20450
- await this.sendUsageUpdate(sessionId, directory);
20451
- return result;
20452
21331
  }
20453
21332
  async listSessions(params) {
20454
21333
  const limit = 100;
@@ -20470,38 +21349,48 @@ var Agent = class {
20470
21349
  return response;
20471
21350
  }
20472
21351
  async unstable_forkSession(params) {
20473
- const directory = params.cwd;
20474
- const mcpServers = params.mcpServers ?? [];
20475
- const model = await this.defaultModel(directory);
20476
- const forked = await this.sdk.session.fork({ sessionID: params.sessionId, directory }).then((x) => x.data);
20477
- if (!forked) throw new Error("Fork session returned no data");
20478
- const sessionId = forked.id;
20479
- await this.sessionManager.load(sessionId, directory, mcpServers, model);
20480
- const result = await this.loadSessionMode({
20481
- cwd: directory,
20482
- mcpServers,
20483
- sessionId
20484
- });
20485
- const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
20486
- for (const msg of messages ?? []) {
20487
- await this.processMessage(msg);
21352
+ try {
21353
+ const directory = params.cwd;
21354
+ const mcpServers = params.mcpServers ?? [];
21355
+ const model = await this.defaultModel(directory);
21356
+ const forked = await this.sdk.session.fork({ sessionID: params.sessionId, directory }).then((x) => x.data);
21357
+ if (!forked) throw new Error("Fork session returned no data");
21358
+ const sessionId = forked.id;
21359
+ await this.sessionManager.load(sessionId, directory, mcpServers, model);
21360
+ const result = await this.loadSessionMode({
21361
+ cwd: directory,
21362
+ mcpServers,
21363
+ sessionId
21364
+ });
21365
+ const messages = await this.sdk.session.messages({ sessionID: sessionId, directory }).then((x) => x.data).catch(() => void 0);
21366
+ for (const msg of messages ?? []) {
21367
+ await this.processMessage(msg);
21368
+ }
21369
+ await this.sendUsageUpdate(sessionId, directory);
21370
+ return result;
21371
+ } catch (e) {
21372
+ if (isApiKeyError(e)) throw RequestError.authRequired();
21373
+ throw e;
20488
21374
  }
20489
- await this.sendUsageUpdate(sessionId, directory);
20490
- return result;
20491
21375
  }
20492
21376
  async unstable_resumeSession(params) {
20493
- const directory = params.cwd;
20494
- const sessionId = params.sessionId;
20495
- const mcpServers = params.mcpServers ?? [];
20496
- const model = await this.defaultModel(directory);
20497
- await this.sessionManager.load(sessionId, directory, mcpServers, model);
20498
- const result = await this.loadSessionMode({
20499
- cwd: directory,
20500
- mcpServers,
20501
- sessionId
20502
- });
20503
- await this.sendUsageUpdate(sessionId, directory);
20504
- return result;
21377
+ try {
21378
+ const directory = params.cwd;
21379
+ const sessionId = params.sessionId;
21380
+ const mcpServers = params.mcpServers ?? [];
21381
+ const model = await this.defaultModel(directory);
21382
+ await this.sessionManager.load(sessionId, directory, mcpServers, model);
21383
+ const result = await this.loadSessionMode({
21384
+ cwd: directory,
21385
+ mcpServers,
21386
+ sessionId
21387
+ });
21388
+ await this.sendUsageUpdate(sessionId, directory);
21389
+ return result;
21390
+ } catch (e) {
21391
+ if (isApiKeyError(e)) throw RequestError.authRequired();
21392
+ throw e;
21393
+ }
20505
21394
  }
20506
21395
  // ─── Prompt Execution ─────────────────────────────────────────────
20507
21396
  async prompt(params) {
@@ -20514,7 +21403,7 @@ var Agent = class {
20514
21403
  if (!session.model) {
20515
21404
  this.sessionManager.setModel(session.id, model);
20516
21405
  }
20517
- const agent = session.modeId ?? "build";
21406
+ const agent = session.modeId ?? await this.defaultAgent(directory);
20518
21407
  const parts = this.convertPromptParts(params.prompt);
20519
21408
  const cmd = this.parseCommand(parts);
20520
21409
  ocCall("session.prompt", { sessionID, agent, model: `${model.providerID}/${model.modelID}` });
@@ -20571,6 +21460,23 @@ var Agent = class {
20571
21460
  _meta: {}
20572
21461
  };
20573
21462
  }
21463
+ if (cmd.name === "compact") {
21464
+ await this.sdk.session.summarize(
21465
+ {
21466
+ sessionID,
21467
+ directory,
21468
+ providerID: model.providerID,
21469
+ modelID: model.modelID
21470
+ },
21471
+ { throwOnError: true }
21472
+ ).catch(() => {
21473
+ });
21474
+ await this.sendUsageUpdate(sessionID, directory);
21475
+ return {
21476
+ stopReason: "end_turn",
21477
+ _meta: {}
21478
+ };
21479
+ }
20574
21480
  const response = await this.sdk.session.prompt({
20575
21481
  sessionID,
20576
21482
  model: { providerID: model.providerID, modelID: model.modelID },
@@ -20599,38 +21505,129 @@ var Agent = class {
20599
21505
  }
20600
21506
  // ─── Configuration ────────────────────────────────────────────────
20601
21507
  async setSessionMode(params) {
21508
+ const session = this.sessionManager.get(params.sessionId);
21509
+ const agents = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
21510
+ const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden);
21511
+ if (!availableModes.some((a) => a.name === params.modeId)) {
21512
+ throw new Error(`Agent not found: ${params.modeId}`);
21513
+ }
20602
21514
  this.sessionManager.setMode(params.sessionId, params.modeId);
20603
21515
  }
20604
21516
  async unstable_setSessionModel(params) {
20605
21517
  const session = this.sessionManager.get(params.sessionId);
20606
- const [providerID, modelID] = params.modelId.split("/");
20607
- this.sessionManager.setModel(params.sessionId, { providerID, modelID });
21518
+ const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
21519
+ const selection = parseModelSelection(params.modelId, providers);
21520
+ this.sessionManager.setModel(params.sessionId, selection.model);
21521
+ this.sessionManager.setVariant(params.sessionId, selection.variant);
21522
+ const currentProvider = providers.find((p) => p.id === selection.model.providerID);
21523
+ const currentModelInfo = currentProvider?.models?.[selection.model.modelID];
21524
+ const availableVariants = currentModelInfo?.variants ? Object.keys(currentModelInfo.variants) : [];
21525
+ return {
21526
+ _meta: buildVariantMeta(selection.model, selection.variant, availableVariants)
21527
+ };
20608
21528
  }
20609
21529
  async setSessionConfigOption(params) {
20610
21530
  const session = this.sessionManager.get(params.sessionId);
20611
- if (params.configId === "model" && typeof params.value === "string") {
20612
- const [providerID, modelID] = params.value.split("/");
20613
- this.sessionManager.setModel(params.sessionId, { providerID, modelID });
20614
- } else if (params.configId === "mode" && typeof params.value === "string") {
21531
+ const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
21532
+ const sortedProviders = [...providers].sort(
21533
+ (a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0
21534
+ );
21535
+ if (params.configId === "model") {
21536
+ if (typeof params.value !== "string") throw RequestError.invalidParams("model value must be a string");
21537
+ const selection = parseModelSelection(params.value, providers);
21538
+ this.sessionManager.setModel(params.sessionId, selection.model);
21539
+ this.sessionManager.setVariant(params.sessionId, selection.variant);
21540
+ } else if (params.configId === "mode") {
21541
+ if (typeof params.value !== "string") throw RequestError.invalidParams("mode value must be a string");
21542
+ const agents2 = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
21543
+ const availableModes2 = agents2.filter((a) => a.mode !== "subagent" && !a.hidden);
21544
+ if (!availableModes2.some((a) => a.name === params.value)) {
21545
+ throw RequestError.invalidParams(JSON.stringify({ error: `Mode not found: ${params.value}` }));
21546
+ }
20615
21547
  this.sessionManager.setMode(params.sessionId, params.value);
21548
+ } else {
21549
+ throw RequestError.invalidParams(JSON.stringify({ error: `Unknown config option: ${params.configId}` }));
21550
+ }
21551
+ const updatedSession = this.sessionManager.get(params.sessionId);
21552
+ const model = updatedSession.model ?? await this.defaultModel(session.cwd);
21553
+ const currentProvider = sortedProviders.find((p) => p.id === model.providerID);
21554
+ const currentModelInfo = currentProvider?.models?.[model.modelID];
21555
+ const availableVariants = currentModelInfo?.variants ? Object.keys(currentModelInfo.variants) : [];
21556
+ const variant = updatedSession.variant;
21557
+ const currentModelId = formatModelIdWithVariant(model, variant, availableVariants);
21558
+ const availableModels = sortedProviders.flatMap(
21559
+ (provider) => Object.values(provider.models).flatMap((modelInfo) => {
21560
+ const base = {
21561
+ modelId: `${provider.id}/${modelInfo.id}`,
21562
+ name: `${provider.name}/${modelInfo.name}`
21563
+ };
21564
+ if (!modelInfo.variants) return [base];
21565
+ const variants = Object.keys(modelInfo.variants).filter((v) => v !== "default");
21566
+ return [
21567
+ base,
21568
+ ...variants.map((v) => ({
21569
+ modelId: `${provider.id}/${modelInfo.id}/${v}`,
21570
+ name: `${provider.name}/${modelInfo.name} (${v})`
21571
+ }))
21572
+ ];
21573
+ })
21574
+ );
21575
+ const agents = await this.sdk.app.agents({ directory: session.cwd }).then((x) => x.data ?? []).catch(() => []);
21576
+ const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden).map((a) => ({ id: a.name, name: a.name, description: a.description }));
21577
+ const currentModeId = updatedSession.modeId ?? availableModes[0]?.id;
21578
+ const configOptions = [
21579
+ {
21580
+ id: "model",
21581
+ name: "Model",
21582
+ category: "model",
21583
+ type: "select",
21584
+ currentValue: currentModelId,
21585
+ options: availableModels.map((m) => ({ value: m.modelId, name: m.name }))
21586
+ }
21587
+ ];
21588
+ if (currentModeId && availableModes.length > 0) {
21589
+ configOptions.push({
21590
+ id: "mode",
21591
+ name: "Session Mode",
21592
+ category: "mode",
21593
+ type: "select",
21594
+ currentValue: currentModeId,
21595
+ options: availableModes.map((m) => ({
21596
+ value: m.id,
21597
+ name: m.name,
21598
+ ...m.description ? { description: m.description } : {}
21599
+ }))
21600
+ });
20616
21601
  }
20617
- const model = this.sessionManager.getModel(params.sessionId) ?? await this.defaultModel(session.cwd);
20618
- const currentModelId = `${model.providerID}/${model.modelID}`;
20619
- return {
20620
- configOptions: [
20621
- {
20622
- id: "model",
20623
- name: "Model",
20624
- category: "model",
20625
- type: "select",
20626
- currentValue: currentModelId,
20627
- options: []
20628
- // TODO: populate from providers
20629
- }
20630
- ]
20631
- };
21602
+ return { configOptions };
20632
21603
  }
20633
21604
  // ─── Internal Helpers ─────────────────────────────────────────────
21605
+ /**
21606
+ * Replay a child subagent session by fetching its messages and sending
21607
+ * them to the ACP client. Registers the child with the session manager.
21608
+ */
21609
+ async replayChildSession(childSessionId, parentSessionId, directory) {
21610
+ if (this.sessionManager.tryGet(childSessionId)) return;
21611
+ const childMessages = await this.sdk.session.messages({ sessionID: childSessionId, directory }).then((x) => x.data).catch(() => void 0);
21612
+ if (!childMessages?.length) return;
21613
+ const title = `Subagent (${childSessionId.slice(0, 8)})`;
21614
+ this.sessionManager.registerDiscovered(childSessionId, parentSessionId, title);
21615
+ await this.sendToClient({
21616
+ sessionId: childSessionId,
21617
+ update: {
21618
+ sessionUpdate: "session_info_update",
21619
+ title,
21620
+ _meta: {
21621
+ parentSessionId,
21622
+ isSubagent: true
21623
+ }
21624
+ }
21625
+ }).catch(() => {
21626
+ });
21627
+ for (const msg of childMessages) {
21628
+ await this.processMessage({ ...msg, info: { ...msg.info, sessionID: childSessionId } });
21629
+ }
21630
+ }
20634
21631
  async defaultModel(cwd) {
20635
21632
  const configured = this.config.defaultModel;
20636
21633
  if (configured) return configured;
@@ -20649,65 +21646,137 @@ var Agent = class {
20649
21646
  return { providerID: "opencode", modelID: models[0] };
20650
21647
  }
20651
21648
  }
20652
- const allModels = providers.flatMap(
20653
- (p) => Object.keys(p.models).map((m) => ({ providerID: p.id, modelID: m }))
20654
- );
21649
+ const allModels = providers.flatMap((p) => Object.keys(p.models).map((m) => ({ providerID: p.id, modelID: m })));
20655
21650
  if (allModels.length > 0) return allModels[0];
20656
21651
  return { providerID: "opencode", modelID: "big-pickle" };
20657
21652
  }
21653
+ async defaultAgent(cwd) {
21654
+ const agents = await this.sdk.app.agents({ directory: cwd }).then((x) => x.data ?? []).catch(() => []);
21655
+ const primary = agents.filter((a) => a.mode !== "subagent" && !a.hidden);
21656
+ return primary.length > 0 ? primary[0].name : "build";
21657
+ }
20658
21658
  async loadSessionMode(params) {
20659
21659
  const model = await this.defaultModel(params.cwd);
20660
21660
  const providers = await this.sdk.config.providers({ directory: params.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
20661
- const availableModels = providers.flatMap(
20662
- (provider) => Object.keys(provider.models).map((modelId) => ({
20663
- modelId: `${provider.id}/${modelId}`,
20664
- name: `${provider.name}/${provider.models[modelId].name}`
20665
- }))
21661
+ const sortedProviders = [...providers].sort(
21662
+ (a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0
21663
+ );
21664
+ const availableModels = sortedProviders.flatMap(
21665
+ (provider) => Object.values(provider.models).flatMap((modelInfo) => {
21666
+ const base = {
21667
+ modelId: `${provider.id}/${modelInfo.id}`,
21668
+ name: `${provider.name}/${modelInfo.name}`
21669
+ };
21670
+ if (!modelInfo.variants) return [base];
21671
+ const variants = Object.keys(modelInfo.variants).filter((v) => v !== "default");
21672
+ return [
21673
+ base,
21674
+ ...variants.map((v) => ({
21675
+ modelId: `${provider.id}/${modelInfo.id}/${v}`,
21676
+ name: `${provider.name}/${modelInfo.name} (${v})`
21677
+ }))
21678
+ ];
21679
+ })
21680
+ );
21681
+ const currentProvider = sortedProviders.find((p) => p.id === model.providerID);
21682
+ const currentModelInfo = currentProvider?.models[model.modelID];
21683
+ const availableVariants = currentModelInfo?.variants ? Object.keys(currentModelInfo.variants) : [];
21684
+ const variant = this.sessionManager.getVariant(params.sessionId);
21685
+ if (variant && !availableVariants.includes(variant)) {
21686
+ this.sessionManager.setVariant(params.sessionId, void 0);
21687
+ }
21688
+ const currentModelId = formatModelIdWithVariant(model, variant, availableVariants);
21689
+ const agents = await this.sdk.app.agents({ directory: params.cwd }).then((x) => x.data ?? []).catch(() => []);
21690
+ const availableModes = agents.filter((a) => a.mode !== "subagent" && !a.hidden).map((a) => ({ id: a.name, name: a.name, description: a.description }));
21691
+ const currentModeId = this.sessionManager.getMode(params.sessionId) ?? (availableModes.length > 0 ? availableModes[0].id : void 0);
21692
+ if (currentModeId && !this.sessionManager.getMode(params.sessionId)) {
21693
+ this.sessionManager.setMode(params.sessionId, currentModeId);
21694
+ }
21695
+ const modes = currentModeId ? { availableModes, currentModeId } : void 0;
21696
+ await Promise.all(
21697
+ params.mcpServers.map(async (server) => {
21698
+ const config2 = "url" in server ? {
21699
+ url: server.url,
21700
+ type: "remote",
21701
+ headers: (server.headers ?? []).reduce(
21702
+ (acc, h) => {
21703
+ acc[h.name] = h.value;
21704
+ return acc;
21705
+ },
21706
+ {}
21707
+ )
21708
+ } : {
21709
+ type: "local",
21710
+ command: [server.command, ...server.args],
21711
+ environment: (server.env ?? []).reduce(
21712
+ (acc, v) => {
21713
+ acc[v.name] = v.value;
21714
+ return acc;
21715
+ },
21716
+ {}
21717
+ )
21718
+ };
21719
+ await this.sdk.mcp.add({ directory: params.cwd, name: server.name, config: config2 }).catch(() => {
21720
+ });
21721
+ })
20666
21722
  );
20667
- const currentModelId = `${model.providerID}/${model.modelID}`;
20668
- for (const server of params.mcpServers) {
20669
- const config2 = "type" in server ? { url: server.url, type: "remote", headers: {} } : { type: "local", command: [server.command, ...server.args] };
20670
- await this.sdk.mcp.add({ directory: params.cwd, name: server.name, config: config2 }).catch(() => {
20671
- });
20672
- }
20673
21723
  const commands = await this.sdk.command.list({ directory: params.cwd }).then((x) => x.data ?? []).catch(() => []);
21724
+ const availableCommands = commands.map((c) => ({
21725
+ name: c.name,
21726
+ description: c.description ?? ""
21727
+ }));
21728
+ if (!availableCommands.some((c) => c.name === "compact")) {
21729
+ availableCommands.push({ name: "compact", description: "compact the session" });
21730
+ }
20674
21731
  setTimeout(() => {
20675
21732
  this.sendToClient({
20676
21733
  sessionId: params.sessionId,
20677
21734
  update: {
20678
21735
  sessionUpdate: "available_commands_update",
20679
- availableCommands: commands.map((c) => ({
20680
- name: c.name,
20681
- description: c.description ?? ""
20682
- }))
21736
+ availableCommands
20683
21737
  }
20684
21738
  }).catch(() => {
20685
21739
  });
20686
21740
  }, 0);
21741
+ const configOptions = [
21742
+ {
21743
+ id: "model",
21744
+ name: "Model",
21745
+ category: "model",
21746
+ type: "select",
21747
+ currentValue: currentModelId,
21748
+ options: availableModels.map((m) => ({
21749
+ value: m.modelId,
21750
+ name: m.name
21751
+ }))
21752
+ }
21753
+ ];
21754
+ if (modes) {
21755
+ configOptions.push({
21756
+ id: "mode",
21757
+ name: "Session Mode",
21758
+ category: "mode",
21759
+ type: "select",
21760
+ currentValue: modes.currentModeId,
21761
+ options: modes.availableModes.map((m) => ({
21762
+ value: m.id,
21763
+ name: m.name,
21764
+ ...m.description ? { description: m.description } : {}
21765
+ }))
21766
+ });
21767
+ }
20687
21768
  return {
20688
21769
  sessionId: params.sessionId,
20689
21770
  models: { currentModelId, availableModels },
20690
- modes: void 0,
20691
- // TODO: implement mode loading
20692
- configOptions: [
20693
- {
20694
- id: "model",
20695
- name: "Model",
20696
- category: "model",
20697
- type: "select",
20698
- currentValue: currentModelId,
20699
- options: availableModels.map((m) => ({
20700
- value: m.modelId,
20701
- name: m.name
20702
- }))
20703
- }
20704
- ],
20705
- _meta: {}
21771
+ modes,
21772
+ configOptions,
21773
+ _meta: buildVariantMeta(model, variant, availableVariants)
20706
21774
  };
20707
21775
  }
20708
21776
  async processMessage(message) {
20709
21777
  if (message.info.role !== "assistant" && message.info.role !== "user") return;
20710
21778
  const sessionId = message.info.sessionID;
21779
+ const messageChunk = message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk";
20711
21780
  for (const part of message.parts) {
20712
21781
  if (part.type === "tool") {
20713
21782
  await this.replayToolPart(sessionId, part);
@@ -20716,7 +21785,7 @@ var Agent = class {
20716
21785
  await this.sendToClient({
20717
21786
  sessionId,
20718
21787
  update: {
20719
- sessionUpdate: message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk",
21788
+ sessionUpdate: messageChunk,
20720
21789
  messageId: message.info.id,
20721
21790
  content: {
20722
21791
  type: "text",
@@ -20726,6 +21795,59 @@ var Agent = class {
20726
21795
  }
20727
21796
  }).catch(() => {
20728
21797
  });
21798
+ } else if (part.type === "file" && part.url) {
21799
+ const url2 = part.url;
21800
+ const filename = part.filename ?? "file";
21801
+ const mime = part.mime || "application/octet-stream";
21802
+ if (url2.startsWith("file://")) {
21803
+ await this.sendToClient({
21804
+ sessionId,
21805
+ update: {
21806
+ sessionUpdate: messageChunk,
21807
+ messageId: message.info.id,
21808
+ content: { type: "resource_link", uri: url2, name: filename, mimeType: mime }
21809
+ }
21810
+ }).catch(() => {
21811
+ });
21812
+ } else if (url2.startsWith("data:")) {
21813
+ const base64Match = url2.match(/^data:([^;]+);base64,(.*)$/);
21814
+ const dataMime = base64Match?.[1];
21815
+ const base64Data = base64Match?.[2] ?? "";
21816
+ const effectiveMime = dataMime || mime;
21817
+ if (effectiveMime.startsWith("image/")) {
21818
+ await this.sendToClient({
21819
+ sessionId,
21820
+ update: {
21821
+ sessionUpdate: messageChunk,
21822
+ messageId: message.info.id,
21823
+ content: {
21824
+ type: "image",
21825
+ mimeType: effectiveMime,
21826
+ data: base64Data,
21827
+ uri: (0, import_url2.pathToFileURL)(filename).href
21828
+ }
21829
+ }
21830
+ }).catch(() => {
21831
+ });
21832
+ } else {
21833
+ const isText = effectiveMime.startsWith("text/") || effectiveMime === "application/json";
21834
+ const fileUri = (0, import_url2.pathToFileURL)(filename).href;
21835
+ const resource = isText ? {
21836
+ uri: fileUri,
21837
+ mimeType: effectiveMime,
21838
+ text: Buffer.from(base64Data, "base64").toString("utf-8")
21839
+ } : { uri: fileUri, mimeType: effectiveMime, blob: base64Data };
21840
+ await this.sendToClient({
21841
+ sessionId,
21842
+ update: {
21843
+ sessionUpdate: messageChunk,
21844
+ messageId: message.info.id,
21845
+ content: { type: "resource", resource }
21846
+ }
21847
+ }).catch(() => {
21848
+ });
21849
+ }
21850
+ }
20729
21851
  } else if (part.type === "reasoning" && part.text) {
20730
21852
  await this.sendToClient({
20731
21853
  sessionId,
@@ -20756,9 +21878,7 @@ var Agent = class {
20756
21878
  switch (part.state.status) {
20757
21879
  case "completed": {
20758
21880
  const kind = toToolKind(part.tool);
20759
- const content = [
20760
- { type: "content", content: { type: "text", text: part.state.output } }
20761
- ];
21881
+ const content = [{ type: "content", content: { type: "text", text: part.state.output } }];
20762
21882
  if (kind === "edit") {
20763
21883
  const input = part.state.input;
20764
21884
  content.push({
@@ -20768,6 +21888,26 @@ var Agent = class {
20768
21888
  newText: typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : ""
20769
21889
  });
20770
21890
  }
21891
+ if (part.tool === "todowrite") {
21892
+ try {
21893
+ const todos = JSON.parse(part.state.output);
21894
+ if (Array.isArray(todos)) {
21895
+ await this.sendToClient({
21896
+ sessionId,
21897
+ update: {
21898
+ sessionUpdate: "plan",
21899
+ entries: todos.map((todo) => ({
21900
+ priority: "medium",
21901
+ status: todo.status === "cancelled" ? "completed" : todo.status,
21902
+ content: todo.content
21903
+ }))
21904
+ }
21905
+ }).catch(() => {
21906
+ });
21907
+ }
21908
+ } catch {
21909
+ }
21910
+ }
20771
21911
  await this.sendToClient({
20772
21912
  sessionId,
20773
21913
  update: {
@@ -20813,14 +21953,32 @@ var Agent = class {
20813
21953
  const msg = lastAssistant.info;
20814
21954
  if (!msg.providerID || !msg.modelID) return;
20815
21955
  const totalCost = assistantMessages.reduce((sum, m) => sum + m.info.cost, 0);
21956
+ const children = this.sessionManager.getChildren(sessionId);
21957
+ let childCost = 0;
21958
+ for (const childId of children) {
21959
+ const childMessages = await this.sdk.session.messages({ sessionID: childId, directory }).then((x) => x.data).catch(() => void 0);
21960
+ if (childMessages) {
21961
+ childCost += childMessages.filter((m) => m.info.role === "assistant").reduce((sum, m) => sum + (m.info.cost ?? 0), 0);
21962
+ }
21963
+ }
21964
+ const providers = await this.sdk.config.providers({ directory }).then((x) => x.data?.providers ?? []).catch(() => []);
21965
+ const provider = providers.find((p) => p.id === msg.providerID);
21966
+ const model = provider?.models[msg.modelID];
21967
+ const size = model?.limit?.context ?? 0;
20816
21968
  await this.sendToClient({
20817
21969
  sessionId,
20818
21970
  update: {
20819
21971
  sessionUpdate: "usage_update",
20820
21972
  used: msg.tokens.input + (msg.tokens.cache?.read ?? 0),
20821
- size: 0,
20822
- // TODO: get from model config
20823
- cost: { amount: totalCost, currency: "USD" }
21973
+ size,
21974
+ cost: { amount: totalCost + childCost, currency: "USD" },
21975
+ ...childCost > 0 && {
21976
+ _meta: {
21977
+ parentCost: totalCost,
21978
+ childCost,
21979
+ childSessionCount: children.length
21980
+ }
21981
+ }
20824
21982
  }
20825
21983
  }).catch(() => {
20826
21984
  });
@@ -20856,11 +22014,26 @@ var Agent = class {
20856
22014
  const resource = part.resource;
20857
22015
  if ("text" in resource && resource.text) {
20858
22016
  result.push({ type: "text", text: resource.text });
22017
+ } else if ("blob" in resource && resource.blob && resource.mimeType) {
22018
+ const parsed = parseUri(resource.uri ?? "");
22019
+ const filename = parsed.type === "file" ? parsed.filename : "file";
22020
+ result.push({
22021
+ type: "file",
22022
+ url: `data:${resource.mimeType};base64,${resource.blob}`,
22023
+ filename,
22024
+ mime: resource.mimeType
22025
+ });
20859
22026
  }
20860
22027
  break;
20861
22028
  }
20862
- case "resource_link":
22029
+ case "resource_link": {
22030
+ const parsed = parseUri(part.uri);
22031
+ if (parsed.type === "file") {
22032
+ if (part.name) parsed.filename = part.name;
22033
+ result.push(parsed);
22034
+ }
20863
22035
  break;
22036
+ }
20864
22037
  }
20865
22038
  }
20866
22039
  return result;
@@ -20921,6 +22094,38 @@ var Agent = class {
20921
22094
  };
20922
22095
  }
20923
22096
  };
22097
+ function formatModelIdWithVariant(model, variant, availableVariants) {
22098
+ const base = `${model.providerID}/${model.modelID}`;
22099
+ if (!variant || !availableVariants.includes(variant)) return base;
22100
+ return `${base}/${variant}`;
22101
+ }
22102
+ function buildVariantMeta(model, variant, availableVariants) {
22103
+ return {
22104
+ opencode: {
22105
+ modelId: `${model.providerID}/${model.modelID}`,
22106
+ variant: variant ?? null,
22107
+ availableVariants
22108
+ }
22109
+ };
22110
+ }
22111
+ function parseModelSelection(modelId, providers) {
22112
+ const segments = modelId.split("/");
22113
+ const providerID = segments[0];
22114
+ const rest = segments.slice(1).join("/");
22115
+ const provider = providers.find((p) => p.id === providerID);
22116
+ if (!provider) return { model: { providerID, modelID: rest } };
22117
+ if (provider.models[rest]) return { model: { providerID, modelID: rest } };
22118
+ const lastSlash = rest.lastIndexOf("/");
22119
+ if (lastSlash > 0) {
22120
+ const candidateBase = rest.slice(0, lastSlash);
22121
+ const candidateVariant = rest.slice(lastSlash + 1);
22122
+ const baseInfo = provider.models[candidateBase];
22123
+ if (baseInfo?.variants && candidateVariant in baseInfo.variants) {
22124
+ return { model: { providerID, modelID: candidateBase }, variant: candidateVariant };
22125
+ }
22126
+ }
22127
+ return { model: { providerID, modelID: rest } };
22128
+ }
20924
22129
 
20925
22130
  // src/index.ts
20926
22131
  function parseArgs(args) {