@agentdevjs/core 0.1.0 → 0.1.1

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.
Files changed (53) hide show
  1. package/dist/BasicAgent-UBGLABDV.js +12 -0
  2. package/dist/ExplorerAgent-C62LOUTX.js +12 -0
  3. package/dist/{chunk-BVF7RUXV.js → chunk-KCCLSQC4.js} +1 -1
  4. package/dist/chunk-KCCLSQC4.js.map +1 -0
  5. package/dist/{chunk-KO4DJKM6.js → chunk-OFBE43IS.js} +1 -1
  6. package/dist/{chunk-KO4DJKM6.js.map → chunk-OFBE43IS.js.map} +1 -1
  7. package/dist/{chunk-YFSM6IYU.js → chunk-TKVOWEJ2.js} +154 -49
  8. package/dist/chunk-TKVOWEJ2.js.map +1 -0
  9. package/dist/{chunk-574NG4UB.js → chunk-U3LWOJ3Q.js} +4 -4
  10. package/dist/chunk-U3LWOJ3Q.js.map +1 -0
  11. package/dist/{chunk-UCEIJEM5.js → chunk-V3WM6EII.js} +2 -2
  12. package/dist/chunk-V3WM6EII.js.map +1 -0
  13. package/dist/{chunk-TIJV6GJW.js → chunk-YYNP7MZC.js} +4 -3
  14. package/dist/chunk-YYNP7MZC.js.map +1 -0
  15. package/dist/features/example-feature/templates/example-tool.render.js.map +1 -1
  16. package/dist/features/opencode-basic/templates/edit.render.js +1 -1
  17. package/dist/features/opencode-basic/templates/edit.render.js.map +1 -1
  18. package/dist/features/opencode-basic/templates/glob.render.js.map +1 -1
  19. package/dist/features/opencode-basic/templates/grep.render.js.map +1 -1
  20. package/dist/features/opencode-basic/templates/ls.render.js.map +1 -1
  21. package/dist/features/opencode-basic/templates/read.render.js +1 -1
  22. package/dist/features/opencode-basic/templates/read.render.js.map +1 -1
  23. package/dist/features/opencode-basic/templates/write.render.js +1 -1
  24. package/dist/features/opencode-basic/templates/write.render.js.map +1 -1
  25. package/dist/features/skill/templates/skill.render.js.map +1 -1
  26. package/dist/features/subagent/templates/agent-close.render.js.map +1 -1
  27. package/dist/features/subagent/templates/agent-list.render.js.map +1 -1
  28. package/dist/features/subagent/templates/agent-send.render.js.map +1 -1
  29. package/dist/features/subagent/templates/agent-spawn.render.js.map +1 -1
  30. package/dist/features/subagent/templates/wait.render.js.map +1 -1
  31. package/dist/features/todo/templates/task-clear.render.js.map +1 -1
  32. package/dist/features/todo/templates/task-create.render.js.map +1 -1
  33. package/dist/features/todo/templates/task-list.render.js.map +1 -1
  34. package/dist/features/todo/templates/task-update.render.js.map +1 -1
  35. package/dist/index.d.ts +2276 -2024
  36. package/dist/index.js +519 -205
  37. package/dist/index.js.map +1 -1
  38. package/dist/{notification-5DFBYKKN.js → notification-CASV7QYT.js} +2 -2
  39. package/dist/{resolver-F5SM2RHQ.js → resolver-4JA7VPYP.js} +2 -2
  40. package/dist/{session-events-TETMQI45.js → session-events-S7LMJ6MM.js} +2 -2
  41. package/package.json +55 -55
  42. package/dist/BasicAgent-RPYJHYCM.js +0 -12
  43. package/dist/ExplorerAgent-ITTSJRJI.js +0 -12
  44. package/dist/chunk-574NG4UB.js.map +0 -1
  45. package/dist/chunk-BVF7RUXV.js.map +0 -1
  46. package/dist/chunk-TIJV6GJW.js.map +0 -1
  47. package/dist/chunk-UCEIJEM5.js.map +0 -1
  48. package/dist/chunk-YFSM6IYU.js.map +0 -1
  49. /package/dist/{BasicAgent-RPYJHYCM.js.map → BasicAgent-UBGLABDV.js.map} +0 -0
  50. /package/dist/{ExplorerAgent-ITTSJRJI.js.map → ExplorerAgent-C62LOUTX.js.map} +0 -0
  51. /package/dist/{notification-5DFBYKKN.js.map → notification-CASV7QYT.js.map} +0 -0
  52. /package/dist/{resolver-F5SM2RHQ.js.map → resolver-4JA7VPYP.js.map} +0 -0
  53. /package/dist/{session-events-TETMQI45.js.map → session-events-S7LMJ6MM.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  ExplorerAgent
3
- } from "./chunk-UCEIJEM5.js";
3
+ } from "./chunk-V3WM6EII.js";
4
4
  import {
5
5
  BasicAgent
6
- } from "./chunk-TIJV6GJW.js";
6
+ } from "./chunk-YYNP7MZC.js";
7
7
  import {
8
8
  AgentBase,
9
9
  CapabilityRegistry,
@@ -54,11 +54,11 @@ import {
54
54
  withDeadline,
55
55
  withDisplay,
56
56
  withImages
57
- } from "./chunk-YFSM6IYU.js";
57
+ } from "./chunk-TKVOWEJ2.js";
58
58
  import {
59
59
  emitSessionEvent,
60
60
  subscribeSessionEvents
61
- } from "./chunk-KO4DJKM6.js";
61
+ } from "./chunk-OFBE43IS.js";
62
62
  import {
63
63
  DebugHub,
64
64
  createLLMCharCount,
@@ -70,10 +70,10 @@ import {
70
70
  getDebugCapabilities,
71
71
  getDefaultUDSPath,
72
72
  resolveDebugTransportMode
73
- } from "./chunk-574NG4UB.js";
73
+ } from "./chunk-U3LWOJ3Q.js";
74
74
  import {
75
75
  PlaceholderResolver
76
- } from "./chunk-BVF7RUXV.js";
76
+ } from "./chunk-KCCLSQC4.js";
77
77
  import "./chunk-DGUM43GV.js";
78
78
 
79
79
  // src/features/skill/index.ts
@@ -1225,7 +1225,7 @@ var TodoToolFactory = class {
1225
1225
  },
1226
1226
  render: { call: "task-create", result: "task-create" },
1227
1227
  execute: ({ subject, description = "" }) => {
1228
- const task = self.createTaskFn(subject, description);
1228
+ const task = self.createTaskFn(String(subject), String(description));
1229
1229
  return Promise.resolve(withDisplay(
1230
1230
  JSON.stringify({ ok: true, taskId: task.id, status: task.status }),
1231
1231
  {
@@ -1321,8 +1321,9 @@ var TodoToolFactory = class {
1321
1321
  required: ["taskId"]
1322
1322
  },
1323
1323
  render: { call: "task-update", result: "task-update" },
1324
- execute: ({ taskId, ...updates }) => {
1325
- const task = self.updateTaskFn(taskId, updates);
1324
+ execute: (rawArgs) => {
1325
+ const { taskId, ...rest } = rawArgs;
1326
+ const task = self.updateTaskFn(taskId, rest);
1326
1327
  if (!task) {
1327
1328
  return Promise.resolve({ ok: false, error: `\u4EFB\u52A1 ${taskId} \u4E0D\u5B58\u5728` });
1328
1329
  }
@@ -1449,7 +1450,7 @@ var TodoFeature = class {
1449
1450
  try {
1450
1451
  this.reminderContent = await readFile3(templatePath, "utf-8");
1451
1452
  console.log("[TodoFeature] Loaded reminder template from: " + templatePath);
1452
- } catch (e) {
1453
+ } catch {
1453
1454
  console.log("[TodoFeature] Failed to load template, using default reminder");
1454
1455
  }
1455
1456
  }
@@ -2026,18 +2027,18 @@ import {
2026
2027
  stat,
2027
2028
  unlink
2028
2029
  } from "fs/promises";
2029
- import { dirname as dirname6, isAbsolute, join as join4, relative } from "path";
2030
+ import { dirname as dirname6, isAbsolute, join as join2, relative } from "path";
2030
2031
  import { homedir } from "os";
2031
2032
  var MAX_SNAPSHOTS = 100;
2032
2033
  function getBackupDir(sessionId) {
2033
- return join4(homedir(), ".agentdev", "file-history", sessionId);
2034
+ return join2(homedir(), ".agentdev", "file-history", sessionId);
2034
2035
  }
2035
2036
  function getBackupFileName(filePath, version) {
2036
2037
  const hash = createHash("sha256").update(filePath).digest("hex").slice(0, 16);
2037
2038
  return `${hash}@v${version}`;
2038
2039
  }
2039
2040
  function resolveBackupPath(backupFileName, sessionId) {
2040
- return join4(getBackupDir(sessionId), backupFileName);
2041
+ return join2(getBackupDir(sessionId), backupFileName);
2041
2042
  }
2042
2043
  function toTrackingPath(filePath, workspaceDir) {
2043
2044
  if (!isAbsolute(filePath)) return filePath;
@@ -2048,7 +2049,7 @@ function toTrackingPath(filePath, workspaceDir) {
2048
2049
  }
2049
2050
  function toAbsolutePath(trackingPath, workspaceDir) {
2050
2051
  if (isAbsolute(trackingPath)) return trackingPath;
2051
- return join4(workspaceDir, trackingPath);
2052
+ return join2(workspaceDir, trackingPath);
2052
2053
  }
2053
2054
  async function createBackup(filePath, version, sessionId) {
2054
2055
  const backupFileName = getBackupFileName(filePath, version);
@@ -2158,9 +2159,8 @@ async function makeSnapshot(state) {
2158
2159
  const absolutePath = toAbsolutePath(trackingPath, state.workspaceDir);
2159
2160
  const lastBackup = latestSnapshot.trackedFileBackups[trackingPath];
2160
2161
  const nextVersion = lastBackup ? lastBackup.version + 1 : 1;
2161
- let fileStats;
2162
2162
  try {
2163
- fileStats = await stat(absolutePath);
2163
+ await stat(absolutePath);
2164
2164
  } catch (e) {
2165
2165
  if (e.code === "ENOENT") {
2166
2166
  newBackups[trackingPath] = { backupFileName: null, version: nextVersion };
@@ -2475,7 +2475,7 @@ var FileHistoryFeature = class {
2475
2475
  // src/features/handoff-seed/index.ts
2476
2476
  import { fileURLToPath as fileURLToPath7 } from "url";
2477
2477
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2478
- import { join as join5, resolve, normalize as normalize3 } from "path";
2478
+ import { join as join3, resolve, normalize as normalize3 } from "path";
2479
2479
  var __filename4 = fileURLToPath7(import.meta.url);
2480
2480
  var MAX_FILE_CHARS = 8e3;
2481
2481
  var MAX_TOTAL_FILE_CHARS = 3e4;
@@ -2692,8 +2692,8 @@ var HandoffSeedFeature = class {
2692
2692
  const nameOnlyList = [];
2693
2693
  let totalChars = 0;
2694
2694
  for (const skillName of skills) {
2695
- const skillDir = join5(projectRoot, ".agentdev", "skills", skillName);
2696
- const skillMdPath = join5(skillDir, "SKILL.md");
2695
+ const skillDir = join3(projectRoot, ".agentdev", "skills", skillName);
2696
+ const skillMdPath = join3(skillDir, "SKILL.md");
2697
2697
  const content = this.tryReadFile(skillMdPath);
2698
2698
  const basePath = normalize3(skillDir);
2699
2699
  if (content === null) {
@@ -2762,8 +2762,8 @@ import { fileURLToPath as fileURLToPath8 } from "url";
2762
2762
  import { promises as fs } from "fs";
2763
2763
 
2764
2764
  // src/features/opencode-basic/tools.ts
2765
- import { readFile as readFile5, writeFile, readdir, stat as stat2, mkdir as mkdir2 } from "fs/promises";
2766
- import { glob } from "glob";
2765
+ import { open, readFile as readFile5, writeFile, opendir, stat as stat2, mkdir as mkdir2 } from "fs/promises";
2766
+ import { globIterate } from "glob";
2767
2767
  import { spawn } from "child_process";
2768
2768
  import { createTwoFilesPatch, diffLines } from "diff";
2769
2769
  import path from "path";
@@ -2771,7 +2771,6 @@ import { homedir as homedir2 } from "os";
2771
2771
  var DEFAULT_READ_LIMIT = 2e3;
2772
2772
  var MAX_LINE_LENGTH = 2e3;
2773
2773
  var MAX_BYTES = 50 * 1024;
2774
- var LS_LIMIT = 100;
2775
2774
  var SEARCH_LIMIT = 100;
2776
2775
  var DEFAULT_WORKSPACE_DIR = process.cwd();
2777
2776
  var IGNORE_PATTERNS = [
@@ -2911,20 +2910,51 @@ function writeTextContent(filePath, content, encoding = "utf8", lineEndings = "L
2911
2910
  }
2912
2911
  return writeFile(filePath, toWrite, encoding);
2913
2912
  }
2914
- var CURLY_QUOTES = [
2913
+ var EQUIVALENT_CHARS = /* @__PURE__ */ new Map([
2915
2914
  ["\u2018", "'"],
2916
- // LEFT SINGLE
2915
+ // LEFT SINGLE QUOTATION MARK
2917
2916
  ["\u2019", "'"],
2918
- // RIGHT SINGLE
2917
+ // RIGHT SINGLE QUOTATION MARK
2919
2918
  ["\u201C", '"'],
2920
- // LEFT DOUBLE
2921
- ["\u201D", '"']
2922
- // RIGHT DOUBLE
2923
- ];
2924
- function normalizeQuotes(str) {
2925
- let result = str;
2926
- for (const [curly, straight] of CURLY_QUOTES) {
2927
- result = result.replaceAll(curly, straight);
2919
+ // LEFT DOUBLE QUOTATION MARK
2920
+ ["\u201D", '"'],
2921
+ // RIGHT DOUBLE QUOTATION MARK
2922
+ ["\xA0", " "],
2923
+ // NO-BREAK SPACE
2924
+ ["\u1680", " "],
2925
+ // OGHAM SPACE MARK
2926
+ ["\u2002", " "],
2927
+ // EN QUAD
2928
+ ["\u2003", " "],
2929
+ // EM QUAD
2930
+ ["\u2004", " "],
2931
+ // THREE-PER-EM SPACE
2932
+ ["\u2005", " "],
2933
+ // FOUR-PER-EM SPACE
2934
+ ["\u2006", " "],
2935
+ // SIX-PER-EM SPACE
2936
+ ["\u2007", " "],
2937
+ // FIGURE SPACE
2938
+ ["\u2008", " "],
2939
+ // PUNCTUATION SPACE
2940
+ ["\u2009", " "],
2941
+ // THIN SPACE
2942
+ ["\u200A", " "],
2943
+ // HAIR SPACE
2944
+ ["\u202F", " "],
2945
+ // NARROW NO-BREAK SPACE
2946
+ ["\u205F", " "],
2947
+ // MEDIUM MATHEMATICAL SPACE
2948
+ ["\u3000", " "]
2949
+ // IDEOGRAPHIC SPACE(全角空格)
2950
+ ]);
2951
+ var INVISIBLE_CHARS = /* @__PURE__ */ new Set(["\u200B", "\u200C", "\u200D", "\uFEFF"]);
2952
+ var SPECIAL_CHARS_RE = /[\u00A0\u1680\u2002-\u200A\u200B-\u200D\u2018\u2019\u201C\u201D\u202F\u205F\u3000\uFEFF]/;
2953
+ function normalizeForMatch(str) {
2954
+ let result = "";
2955
+ for (const ch of str) {
2956
+ if (INVISIBLE_CHARS.has(ch)) continue;
2957
+ result += EQUIVALENT_CHARS.get(ch) ?? ch;
2928
2958
  }
2929
2959
  return result;
2930
2960
  }
@@ -2932,13 +2962,28 @@ function findActualString(fileContent, searchString) {
2932
2962
  if (fileContent.includes(searchString)) {
2933
2963
  return searchString;
2934
2964
  }
2935
- const normalizedSearch = normalizeQuotes(searchString);
2936
- const normalizedFile = normalizeQuotes(fileContent);
2965
+ if (!SPECIAL_CHARS_RE.test(fileContent) && !SPECIAL_CHARS_RE.test(searchString)) {
2966
+ return null;
2967
+ }
2968
+ let normalizedFile = "";
2969
+ const sourceIndex = [];
2970
+ for (let i = 0; i < fileContent.length; i++) {
2971
+ const ch = fileContent[i];
2972
+ if (INVISIBLE_CHARS.has(ch)) continue;
2973
+ normalizedFile += EQUIVALENT_CHARS.get(ch) ?? ch;
2974
+ sourceIndex.push(i);
2975
+ }
2976
+ const normalizedSearch = normalizeForMatch(searchString);
2977
+ if (!normalizedSearch) {
2978
+ return null;
2979
+ }
2937
2980
  const index = normalizedFile.indexOf(normalizedSearch);
2938
- if (index !== -1) {
2939
- return fileContent.substring(index, index + searchString.length);
2981
+ if (index === -1) {
2982
+ return null;
2940
2983
  }
2941
- return null;
2984
+ const start = sourceIndex[index];
2985
+ const end = sourceIndex[index + normalizedSearch.length - 1] + 1;
2986
+ return fileContent.substring(start, end);
2942
2987
  }
2943
2988
  async function isBinaryFile(filepath) {
2944
2989
  const ext = path.extname(filepath).toLowerCase();
@@ -2975,21 +3020,25 @@ async function isBinaryFile(filepath) {
2975
3020
  if (binaryExts.includes(ext)) {
2976
3021
  return true;
2977
3022
  }
3023
+ let handle;
2978
3024
  try {
2979
- const content = await readFile5(filepath, { encoding: null });
2980
- if (content.length === 0) return false;
2981
- const bufferSize = Math.min(4096, content.length);
2982
- const bytes = content.subarray(0, bufferSize);
3025
+ handle = await open(filepath, "r");
3026
+ const buffer = Buffer.allocUnsafe(4096);
3027
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
3028
+ if (bytesRead === 0) return false;
2983
3029
  let nonPrintableCount = 0;
2984
- for (let i = 0; i < bytes.length; i++) {
2985
- if (bytes[i] === 0) return true;
2986
- if (bytes[i] < 9 || bytes[i] > 13 && bytes[i] < 32) {
2987
- nonPrintableCount++;
3030
+ for (let i = 0; i < bytesRead; i += 1) {
3031
+ if (buffer[i] === 0) return true;
3032
+ if (buffer[i] < 9 || buffer[i] > 13 && buffer[i] < 32) {
3033
+ nonPrintableCount += 1;
2988
3034
  }
2989
3035
  }
2990
- return nonPrintableCount / bytes.length > 0.3;
3036
+ return nonPrintableCount / bytesRead > 0.3;
2991
3037
  } catch {
2992
3038
  return false;
3039
+ } finally {
3040
+ await handle?.close().catch(() => {
3041
+ });
2993
3042
  }
2994
3043
  }
2995
3044
  function createReadTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
@@ -3022,7 +3071,6 @@ function createReadTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3022
3071
  const offsetParam = typeof args.offset === "number" ? args.offset : void 0;
3023
3072
  const limitParam = typeof args.limit === "number" ? args.limit : void 0;
3024
3073
  const resolvedFilePath = resolveWorkspacePath(filePath, workspaceDir);
3025
- console.log(`[read] ${resolvedFilePath}`);
3026
3074
  if (offsetParam !== void 0 && offsetParam < 1) {
3027
3075
  throw new Error("offset must be greater than or equal to 1");
3028
3076
  }
@@ -3034,36 +3082,53 @@ function createReadTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3034
3082
  throw new Error(`File not found: ${resolvedFilePath}`);
3035
3083
  }
3036
3084
  if (stats.isDirectory()) {
3037
- const dirents = await readdir(resolvedFilePath, { withFileTypes: true });
3085
+ const limit2 = limitParam ?? DEFAULT_READ_LIMIT;
3086
+ const offset2 = offsetParam ?? 1;
3087
+ const start2 = offset2 - 1;
3088
+ const end = start2 + limit2;
3089
+ const candidates = [];
3090
+ let totalEntries = 0;
3091
+ const directory = await opendir(resolvedFilePath);
3092
+ try {
3093
+ for await (const dirent of directory) {
3094
+ totalEntries += 1;
3095
+ candidates.push({
3096
+ name: dirent.name,
3097
+ isDirectory: dirent.isDirectory(),
3098
+ isSymbolicLink: dirent.isSymbolicLink()
3099
+ });
3100
+ candidates.sort((left, right) => left.name.localeCompare(right.name));
3101
+ if (candidates.length > end) candidates.pop();
3102
+ }
3103
+ } finally {
3104
+ await directory.close().catch(() => {
3105
+ });
3106
+ }
3107
+ const visibleCandidates = candidates.slice(start2, end);
3038
3108
  const entries = [];
3039
- for (const dirent of dirents) {
3040
- if (dirent.isDirectory()) {
3041
- entries.push(dirent.name + path.sep);
3042
- } else if (dirent.isSymbolicLink()) {
3109
+ for (const candidate of visibleCandidates) {
3110
+ if (candidate.isDirectory) {
3111
+ entries.push(candidate.name + path.sep);
3112
+ } else if (candidate.isSymbolicLink) {
3043
3113
  try {
3044
- const targetStats = await stat2(path.join(resolvedFilePath, dirent.name));
3045
- entries.push(targetStats.isDirectory() ? dirent.name + path.sep : dirent.name);
3114
+ const targetStats = await stat2(path.join(resolvedFilePath, candidate.name));
3115
+ entries.push(targetStats.isDirectory() ? candidate.name + path.sep : candidate.name);
3046
3116
  } catch {
3047
- entries.push(dirent.name);
3117
+ entries.push(candidate.name);
3048
3118
  }
3049
3119
  } else {
3050
- entries.push(dirent.name);
3120
+ entries.push(candidate.name);
3051
3121
  }
3052
3122
  }
3053
- entries.sort((a, b) => a.localeCompare(b));
3054
- const limit2 = limitParam ?? DEFAULT_READ_LIMIT;
3055
- const offset2 = offsetParam ?? 1;
3056
- const start2 = offset2 - 1;
3057
- const sliced = entries.slice(start2, start2 + limit2);
3058
- const truncated2 = start2 + sliced.length < entries.length;
3123
+ const truncated2 = totalEntries > end;
3059
3124
  return {
3060
3125
  type: "directory",
3061
3126
  path: resolvedFilePath,
3062
- totalEntries: entries.length,
3127
+ totalEntries,
3063
3128
  offset: offset2,
3064
3129
  limit: limit2,
3065
3130
  truncated: truncated2,
3066
- entries: sliced
3131
+ entries
3067
3132
  };
3068
3133
  }
3069
3134
  const isBinary = await isBinaryFile(resolvedFilePath);
@@ -3142,7 +3207,6 @@ function createWriteTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3142
3207
  const filePath = normalizeNamedPathArg(args, "filePath", "filepath", "path");
3143
3208
  const content = args.content;
3144
3209
  const resolvedFilePath = resolveWorkspacePath(filePath, workspaceDir);
3145
- console.log(`[write] ${resolvedFilePath}`);
3146
3210
  let encoding = "utf8";
3147
3211
  const exists = await stat2(resolvedFilePath).then(() => true).catch(() => false);
3148
3212
  let contentOld = "";
@@ -3556,7 +3620,6 @@ function createEditTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3556
3620
  const newString = args.newString;
3557
3621
  const replaceAll = args.replaceAll;
3558
3622
  const resolvedFilePath = resolveWorkspacePath(filePath, workspaceDir);
3559
- console.log(`[edit] ${resolvedFilePath}`);
3560
3623
  if (oldString === newString) {
3561
3624
  throw new Error("No changes to apply: oldString and newString are identical.");
3562
3625
  }
@@ -3629,10 +3692,122 @@ function createEditTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3629
3692
  });
3630
3693
  }
3631
3694
  var editTool = createEditTool();
3695
+ var LS_MAX_DEPTH = 2;
3696
+ var LS_ROOT_LIMIT = 100;
3697
+ var LS_CHILD_LIMIT = 12;
3698
+ function compileIgnorePattern(pattern) {
3699
+ let normalized = pattern.trim().replaceAll("\\", "/").replace(/^\.\//, "");
3700
+ if (!normalized) return null;
3701
+ if (normalized.endsWith("/**")) {
3702
+ normalized = normalized.slice(0, -3).replace(/\/$/, "");
3703
+ if (!normalized) return null;
3704
+ return new RegExp(`^(?:${globPatternToRegexSource(normalized)})(?:/.*)?$`);
3705
+ }
3706
+ if (!/[?*[]/.test(normalized)) {
3707
+ return new RegExp(`^(?:${globPatternToRegexSource(normalized)})(?:/.*)?$`);
3708
+ }
3709
+ return new RegExp(`^${globPatternToRegexSource(normalized)}$`);
3710
+ }
3711
+ function globPatternToRegexSource(pattern) {
3712
+ let source = "";
3713
+ for (let index = 0; index < pattern.length; index += 1) {
3714
+ const char = pattern[index];
3715
+ if (char === "*") {
3716
+ if (pattern[index + 1] === "*") {
3717
+ source += ".*";
3718
+ index += 1;
3719
+ } else {
3720
+ source += "[^/]*";
3721
+ }
3722
+ } else if (char === "?") {
3723
+ source += "[^/]";
3724
+ } else {
3725
+ source += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3726
+ }
3727
+ }
3728
+ return source;
3729
+ }
3730
+ function shouldIgnoreDirectoryEntry(relativePath, patterns) {
3731
+ return patterns.some((pattern) => pattern.test(relativePath));
3732
+ }
3733
+ async function buildLimitedDirectoryTree(rootPath, ignorePatterns, maxDepth = LS_MAX_DEPTH, rootLimit = LS_ROOT_LIMIT, childLimit = LS_CHILD_LIMIT) {
3734
+ const compiledPatterns = ignorePatterns.map(compileIgnorePattern).filter((pattern) => pattern !== null);
3735
+ let fileCount = 0;
3736
+ let truncated = false;
3737
+ const visit = async (absolutePath, relativePath, depth) => {
3738
+ const limit = depth === 0 ? rootLimit : childLimit;
3739
+ const entries = [];
3740
+ let droppedCount = 0;
3741
+ const directory = await opendir(absolutePath).catch(() => null);
3742
+ if (directory === null) return entries;
3743
+ const compareEntries = (left, right) => {
3744
+ if (left.isDirectory !== right.isDirectory) return left.isDirectory ? -1 : 1;
3745
+ return left.name.localeCompare(right.name);
3746
+ };
3747
+ try {
3748
+ for await (const dirent of directory) {
3749
+ const childRelativePath = relativePath ? `${relativePath}/${dirent.name}` : dirent.name;
3750
+ if (shouldIgnoreDirectoryEntry(childRelativePath, compiledPatterns)) continue;
3751
+ const entry = {
3752
+ name: dirent.name,
3753
+ relativePath: childRelativePath,
3754
+ isDirectory: dirent.isDirectory(),
3755
+ children: [],
3756
+ droppedCount: 0
3757
+ };
3758
+ const insertAt = entries.findIndex((existing) => compareEntries(entry, existing) < 0);
3759
+ if (insertAt === -1) entries.push(entry);
3760
+ else entries.splice(insertAt, 0, entry);
3761
+ if (entries.length > limit) {
3762
+ entries.pop();
3763
+ droppedCount += 1;
3764
+ }
3765
+ }
3766
+ } finally {
3767
+ await directory.close().catch(() => {
3768
+ });
3769
+ }
3770
+ if (droppedCount > 0) {
3771
+ truncated = true;
3772
+ if (entries.length > 0) entries[entries.length - 1].droppedCount = droppedCount;
3773
+ }
3774
+ for (const entry of entries) {
3775
+ if (!entry.isDirectory) {
3776
+ fileCount += 1;
3777
+ continue;
3778
+ }
3779
+ if (depth >= maxDepth) continue;
3780
+ entry.children = await visit(path.join(rootPath, entry.relativePath), entry.relativePath, depth + 1);
3781
+ }
3782
+ return entries;
3783
+ };
3784
+ const root = {
3785
+ name: ".",
3786
+ relativePath: "",
3787
+ isDirectory: true,
3788
+ children: await visit(rootPath, "", 0),
3789
+ droppedCount: 0
3790
+ };
3791
+ return { root, fileCount, truncated };
3792
+ }
3793
+ function renderLimitedDirectoryTree(root, rootPath) {
3794
+ const lines = [`${rootPath}${path.sep}`];
3795
+ const render = (entry, depth) => {
3796
+ const indent = " ".repeat(depth);
3797
+ lines.push(`${indent}${entry.name}${entry.isDirectory ? "/" : ""}`);
3798
+ if (entry.droppedCount > 0) {
3799
+ lines.push(`${" ".repeat(depth + 1)}\u2026 ${entry.droppedCount} more`);
3800
+ }
3801
+ for (const child of entry.children) render(child, depth + 1);
3802
+ };
3803
+ for (const entry of root.children) render(entry, 0);
3804
+ return `${lines.join("\n")}
3805
+ `;
3806
+ }
3632
3807
  function createLsTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3633
3808
  return createTool({
3634
3809
  name: "ls",
3635
- description: "List files in a directory with tree structure output. Automatically ignores common build/cache directories.",
3810
+ description: "List a bounded directory tree. It shows up to two levels, ignores common build/cache directories, and truncates large directories per level.",
3636
3811
  render: "ls",
3637
3812
  parallelizable: true,
3638
3813
  parameters: {
@@ -3653,61 +3828,21 @@ function createLsTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3653
3828
  },
3654
3829
  execute: async (args = {}) => {
3655
3830
  const dirPath = normalizeNamedPathArg(args, "dirPath", "dirpath", "path", "filePath", "filepath");
3656
- const ignore = args.ignore ?? [];
3831
+ const rawIgnore = args.ignore;
3832
+ const ignore = Array.isArray(rawIgnore) ? rawIgnore.filter((item) => typeof item === "string") : [];
3657
3833
  const resolvedDirPath = resolveWorkspacePath(dirPath, workspaceDir);
3658
- console.log(`[ls] ${resolvedDirPath}`);
3659
- const ignoreGlobs = [...IGNORE_PATTERNS, ...ignore.map((p) => `${p}/**`)];
3660
- const matches = await glob("**/*", {
3661
- cwd: resolvedDirPath,
3662
- absolute: false,
3663
- dot: true,
3664
- ignore: ignoreGlobs,
3665
- nodir: true
3666
- });
3667
- const files = [];
3668
- for (const file of matches) {
3669
- files.push(file);
3670
- if (files.length >= LS_LIMIT) break;
3671
- }
3672
- const dirs = /* @__PURE__ */ new Set();
3673
- const filesByDir = /* @__PURE__ */ new Map();
3674
- for (const file of files) {
3675
- const dir = path.dirname(file);
3676
- const parts = dir === "." ? [] : dir.split(path.sep);
3677
- for (let i = 0; i <= parts.length; i++) {
3678
- const dirPath2 = i === 0 ? "." : parts.slice(0, i).join(path.sep);
3679
- dirs.add(dirPath2);
3680
- }
3681
- if (!filesByDir.has(dir)) {
3682
- filesByDir.set(dir, []);
3683
- }
3684
- filesByDir.get(dir).push(path.basename(file));
3685
- }
3686
- function renderDir(dirPath2, depth) {
3687
- const indent = " ".repeat(depth);
3688
- let output = "";
3689
- if (depth > 0) {
3690
- output += `${indent}${path.basename(dirPath2)}/
3691
- `;
3692
- }
3693
- const children = Array.from(dirs).filter((d) => path.dirname(d) === dirPath2 && d !== dirPath2).sort();
3694
- for (const child of children) {
3695
- output += renderDir(child, depth + 1);
3696
- }
3697
- const dirFiles = filesByDir.get(dirPath2) || [];
3698
- for (const file of dirFiles.sort()) {
3699
- output += `${" ".repeat(depth + 1)}${file}
3700
- `;
3701
- }
3702
- return output;
3703
- }
3704
- const treeOutput = `${resolvedDirPath}${path.sep}
3705
- ${renderDir(".", 0)}`;
3834
+ const stats = await stat2(resolvedDirPath).catch(() => null);
3835
+ if (!stats) throw new Error(`Directory not found: ${resolvedDirPath}`);
3836
+ if (!stats.isDirectory()) throw new Error(`Path is not a directory: ${resolvedDirPath}`);
3837
+ const result = await buildLimitedDirectoryTree(
3838
+ resolvedDirPath,
3839
+ [...IGNORE_PATTERNS, ...ignore.map((pattern) => pattern.endsWith("/**") ? pattern : `${pattern}/**`)]
3840
+ );
3706
3841
  return {
3707
3842
  path: resolvedDirPath,
3708
- count: files.length,
3709
- truncated: files.length >= LS_LIMIT,
3710
- tree: treeOutput
3843
+ count: result.fileCount,
3844
+ truncated: result.truncated,
3845
+ tree: renderLimitedDirectoryTree(result.root, resolvedDirPath)
3711
3846
  };
3712
3847
  }
3713
3848
  });
@@ -3738,30 +3873,30 @@ function createGlobTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3738
3873
  const pattern = args.pattern;
3739
3874
  const searchPath = tryNamedPathArg(args, "searchPath", "searchpath", "path", "filePath", "filepath");
3740
3875
  const resolvedSearchPath = resolveWorkspaceSearchPath(searchPath, workspaceDir);
3741
- console.log(`[glob] ${pattern} in ${resolvedSearchPath}`);
3742
3876
  const files = [];
3743
- const matches = await glob(pattern, {
3877
+ let truncated = false;
3878
+ for await (const file of globIterate(pattern, {
3744
3879
  cwd: resolvedSearchPath,
3745
3880
  absolute: true,
3746
3881
  windowsPathsNoEscape: true,
3747
- nodir: true
3748
- });
3749
- for (const file of matches) {
3750
- if (files.length >= SEARCH_LIMIT) break;
3882
+ nodir: true,
3883
+ ignore: IGNORE_PATTERNS
3884
+ })) {
3885
+ if (files.length >= SEARCH_LIMIT) {
3886
+ truncated = true;
3887
+ break;
3888
+ }
3751
3889
  try {
3752
3890
  const stats = await stat2(file);
3753
- files.push({
3754
- path: file,
3755
- mtime: stats.mtimeMs
3756
- });
3891
+ files.push({ path: file, mtime: stats.mtimeMs });
3757
3892
  } catch {
3758
3893
  }
3759
3894
  }
3760
- files.sort((a, b) => b.mtime - a.mtime);
3895
+ files.sort((a, b) => b.mtime - a.mtime || a.path.localeCompare(b.path));
3761
3896
  return {
3762
3897
  count: files.length,
3763
- truncated: files.length >= SEARCH_LIMIT,
3764
- files: files.map((f) => f.path)
3898
+ truncated,
3899
+ files: files.map((file) => file.path)
3765
3900
  };
3766
3901
  }
3767
3902
  });
@@ -3819,7 +3954,6 @@ function createGrepTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3819
3954
  const searchPath = tryNamedPathArg(args, "searchPath", "searchpath", "path", "filePath", "filepath");
3820
3955
  const include = args.include;
3821
3956
  const resolvedSearchPath = resolveWorkspaceSearchPath(searchPath, workspaceDir);
3822
- console.log(`[grep] ${pattern} in ${resolvedSearchPath}`);
3823
3957
  const rgPath = await getRipgrepPath();
3824
3958
  const rgArgs = ["-nH", "--hidden", "--no-messages", "--field-match-separator=|", "--regexp", pattern];
3825
3959
  if (include) {
@@ -3827,18 +3961,54 @@ function createGrepTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3827
3961
  }
3828
3962
  rgArgs.push(resolvedSearchPath);
3829
3963
  try {
3830
- const stdout = await new Promise((resolve3, reject) => {
3831
- const chunks = [];
3832
- const child = spawn(rgPath, rgArgs, {
3833
- windowsHide: true
3834
- });
3835
- child.stdout.on("data", (chunk) => chunks.push(chunk));
3836
- child.stderr.on("data", (chunk) => {
3837
- });
3838
- const onSearchAbort = () => child.kill();
3964
+ const { matches, truncated } = await new Promise((resolve3, reject) => {
3965
+ const child = spawn(rgPath, rgArgs, { windowsHide: true });
3966
+ const collected = [];
3967
+ let pending = "";
3968
+ let truncated2 = false;
3969
+ let killRequested = false;
3970
+ const stopSearch = () => {
3971
+ if (killRequested) return;
3972
+ killRequested = true;
3973
+ child.kill();
3974
+ };
3975
+ const processLine = (line) => {
3976
+ if (!line || truncated2) return;
3977
+ const parts = line.split("|");
3978
+ if (parts.length < 3) return;
3979
+ const [filePath, lineNumStr, ...lineTextParts] = parts;
3980
+ const lineNum = parseInt(lineNumStr, 10);
3981
+ if (!filePath || !Number.isFinite(lineNum)) return;
3982
+ const lineText = lineTextParts.join("|");
3983
+ if (collected.length >= SEARCH_LIMIT) {
3984
+ truncated2 = true;
3985
+ stopSearch();
3986
+ return;
3987
+ }
3988
+ collected.push({
3989
+ path: filePath,
3990
+ lineNum,
3991
+ lineText: lineText.length > MAX_LINE_LENGTH ? `${lineText.substring(0, MAX_LINE_LENGTH)}...` : lineText
3992
+ });
3993
+ };
3994
+ const onSearchAbort = () => {
3995
+ stopSearch();
3996
+ };
3839
3997
  if (context?.signal) {
3840
3998
  context.signal.addEventListener("abort", onSearchAbort, { once: true });
3841
3999
  }
4000
+ child.stdout.setEncoding("utf8");
4001
+ child.stdout.on("data", (chunk) => {
4002
+ pending += chunk;
4003
+ let newlineIndex = pending.indexOf(String.fromCharCode(10));
4004
+ while (newlineIndex !== -1) {
4005
+ processLine(pending.slice(0, newlineIndex).replace(new RegExp(String.fromCharCode(13) + "$"), ""));
4006
+ pending = pending.slice(newlineIndex + 1);
4007
+ if (killRequested) return;
4008
+ newlineIndex = pending.indexOf(String.fromCharCode(10));
4009
+ }
4010
+ });
4011
+ child.stderr.resume();
3842
4012
  child.on("error", (err) => {
3843
4013
  context?.signal?.removeEventListener("abort", onSearchAbort);
3844
4014
  reject(err);
@@ -3846,41 +4016,29 @@ function createGrepTool(workspaceDir = DEFAULT_WORKSPACE_DIR) {
3846
4016
  child.on("close", (code) => {
3847
4017
  context?.signal?.removeEventListener("abort", onSearchAbort);
3848
4018
  if (context?.signal?.aborted) {
3849
- return reject(new Error("Search was aborted"));
4019
+ reject(new Error("Search was aborted"));
4020
+ return;
4021
+ }
4022
+ if (!killRequested) {
4023
+ processLine(pending);
3850
4024
  }
3851
- if (code !== 0 && code !== 1) {
3852
- return reject(new Error(`rg exited with code ${code}`));
4025
+ if (!killRequested && code !== 0 && code !== 1) {
4026
+ reject(new Error(`rg exited with code ${code}`));
4027
+ return;
3853
4028
  }
3854
- resolve3(Buffer.concat(chunks).toString("utf-8"));
4029
+ resolve3({ matches: collected, truncated: truncated2 });
3855
4030
  });
3856
4031
  });
3857
- const lines = stdout.trim().split(/\r?\n/);
3858
- const matches = [];
3859
- for (const line of lines) {
3860
- if (!line) continue;
3861
- const parts = line.split("|");
3862
- if (parts.length < 3) continue;
3863
- const [filePath, lineNumStr, ...lineTextParts] = parts;
3864
- const lineNum = parseInt(lineNumStr, 10);
3865
- const lineText = lineTextParts.join("|");
3866
- matches.push({
3867
- path: filePath,
3868
- lineNum,
3869
- lineText: lineText.length > MAX_LINE_LENGTH ? lineText.substring(0, MAX_LINE_LENGTH) + "..." : lineText
3870
- });
3871
- }
3872
4032
  matches.sort((a, b) => a.path === b.path ? a.lineNum - b.lineNum : a.path < b.path ? -1 : 1);
3873
- const truncated = matches.length > SEARCH_LIMIT;
3874
- const finalMatches = truncated ? matches.slice(0, SEARCH_LIMIT) : matches;
3875
4033
  return {
3876
4034
  pattern,
3877
4035
  matches: matches.length,
3878
4036
  truncated,
3879
- results: finalMatches
4037
+ results: matches
3880
4038
  };
3881
4039
  } catch (error) {
3882
4040
  if (error.message?.includes("Search was aborted") || context?.signal?.aborted) {
3883
- throw new Error("Search was aborted");
4041
+ throw new Error("Search was aborted", { cause: error });
3884
4042
  }
3885
4043
  throw error;
3886
4044
  }
@@ -4214,7 +4372,7 @@ var ExampleFeature = class {
4214
4372
  }
4215
4373
  return "continue" /* Continue */;
4216
4374
  }
4217
- async handleStepFinish(ctx) {
4375
+ async handleStepFinish(_ctx) {
4218
4376
  return "continue" /* Continue */;
4219
4377
  }
4220
4378
  };
@@ -5008,6 +5166,7 @@ var LspFeature = class {
5008
5166
  broken = /* @__PURE__ */ new Set();
5009
5167
  constructor(config = {}) {
5010
5168
  this.config = {
5169
+ // eslint-disable-next-line no-restricted-syntax -- 防御性默认值,onInitiate 会用 ctx.config.workspaceDir 覆盖
5011
5170
  workdir: config.workdir || process.cwd(),
5012
5171
  binDir: config.binDir || path4.join(os.homedir(), ".agentdev", "lsp-bin"),
5013
5172
  disableDownload: config.disableDownload ?? false,
@@ -5309,7 +5468,7 @@ var LspFeature = class {
5309
5468
 
5310
5469
  // src/features/output-guard/index.ts
5311
5470
  import { fileURLToPath as fileURLToPath12 } from "url";
5312
- import { dirname as dirname8, join as join7 } from "path";
5471
+ import { dirname as dirname8, join as join4 } from "path";
5313
5472
  import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
5314
5473
 
5315
5474
  // src/features/output-guard/truncate.ts
@@ -5525,6 +5684,7 @@ var OutputGuardFeature = class {
5525
5684
  this.config = {
5526
5685
  hardLimit: config.hardLimit ?? DEFAULT_HARD_LIMIT,
5527
5686
  fieldLimit: config.fieldLimit ?? DEFAULT_FIELD_LIMIT,
5687
+ // eslint-disable-next-line no-restricted-syntax -- 防御性默认值,仅用于临时文件落盘目录;宿主应显式传 workdir
5528
5688
  workdir: config.workdir ?? process.cwd(),
5529
5689
  toolOverrides: config.toolOverrides ?? {}
5530
5690
  };
@@ -5616,13 +5776,13 @@ var OutputGuardFeature = class {
5616
5776
  let filePath;
5617
5777
  let diskContent = fullOutput;
5618
5778
  try {
5619
- const tempDir = join7(this.config.workdir, ".agentdev", "temp");
5779
+ const tempDir = join4(this.config.workdir, ".agentdev", "temp");
5620
5780
  const now = /* @__PURE__ */ new Date();
5621
5781
  const ts = now.getFullYear().toString() + String(now.getMonth() + 1).padStart(2, "0") + String(now.getDate()).padStart(2, "0") + "-" + String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0");
5622
5782
  const suffix = Math.random().toString(36).slice(2, 8);
5623
5783
  const safeToolName = toolName.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 30);
5624
5784
  const fileName = `tool-output-${safeToolName}-${ts}-${suffix}.log`;
5625
- filePath = join7(tempDir, fileName);
5785
+ filePath = join4(tempDir, fileName);
5626
5786
  try {
5627
5787
  const parsed = JSON.parse(fullOutput);
5628
5788
  diskContent = JSON.stringify(parsed, null, 2);
@@ -5661,9 +5821,9 @@ Use the read tool with offset/limit to access specific portions.
5661
5821
  };
5662
5822
 
5663
5823
  // src/core/config.ts
5664
- import { readFile as readFile7, readdir as readdir2 } from "fs/promises";
5824
+ import { readFile as readFile7, readdir } from "fs/promises";
5665
5825
  import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
5666
- import { resolve as resolve2, join as join8 } from "path";
5826
+ import { resolve as resolve2, join as join5 } from "path";
5667
5827
  import { cwd } from "process";
5668
5828
  var OPENAI_THINKING_EFFORTS = [
5669
5829
  "none",
@@ -5686,7 +5846,7 @@ function getProjectRoot() {
5686
5846
  }
5687
5847
  async function loadConfig(name = "default") {
5688
5848
  const projectRoot = getProjectRoot();
5689
- const configDir = join8(projectRoot, "config");
5849
+ const configDir = join5(projectRoot, "config");
5690
5850
  const configPath = resolve2(configDir, `${name}.json`);
5691
5851
  if (!existsSync3(configPath)) {
5692
5852
  throw new Error(
@@ -5701,7 +5861,7 @@ async function loadConfig(name = "default") {
5701
5861
  }
5702
5862
  function loadConfigSync(name = "default") {
5703
5863
  const projectRoot = getProjectRoot();
5704
- const configDir = join8(projectRoot, "config");
5864
+ const configDir = join5(projectRoot, "config");
5705
5865
  const configPath = resolve2(configDir, `${name}.json`);
5706
5866
  if (!existsSync3(configPath)) {
5707
5867
  throw new Error(
@@ -5716,9 +5876,9 @@ function loadConfigSync(name = "default") {
5716
5876
  }
5717
5877
  async function listConfigs() {
5718
5878
  const projectRoot = getProjectRoot();
5719
- const configDir = join8(projectRoot, "config");
5879
+ const configDir = join5(projectRoot, "config");
5720
5880
  try {
5721
- const files = await readdir2(configDir);
5881
+ const files = await readdir(configDir);
5722
5882
  return files.filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
5723
5883
  } catch {
5724
5884
  return [];
@@ -5820,7 +5980,7 @@ var RENDER_TEMPLATES = {
5820
5980
  },
5821
5981
  "file-write": {
5822
5982
  call: '<div class="bash-command">Write <span class="file-path">{{path}}</span></div>',
5823
- result: (data) => `<div style="color:var(--success-color)">\u2713 File written successfully</div>`
5983
+ result: (_data) => `<div style="color:var(--success-color)">\u2713 File written successfully</div>`
5824
5984
  },
5825
5985
  "file-list": {
5826
5986
  call: '<div class="bash-command">List <span class="file-path">{{path}}</span></div>',
@@ -5926,7 +6086,6 @@ var RENDER_TEMPLATES = {
5926
6086
  call: '<div class="bash-command">List trash</div>',
5927
6087
  result: (data) => {
5928
6088
  const total = data.total || 0;
5929
- const files = data.files || [];
5930
6089
  if (total === 0) {
5931
6090
  return `<div style="color:var(--text-secondary)">Trash is empty</div>`;
5932
6091
  }
@@ -6974,7 +7133,7 @@ function declareContinuity(Base, descriptor) {
6974
7133
 
6975
7134
  // src/core/workthread/store.ts
6976
7135
  import { copyFile as copyFile2, mkdir as mkdir4, readFile as readFile8, rename, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
6977
- import { join as join9 } from "path";
7136
+ import { join as join6 } from "path";
6978
7137
  import { randomUUID as randomUUID2 } from "crypto";
6979
7138
  var WorkThreadNotFoundError = class extends Error {
6980
7139
  code = "workthread_not_found";
@@ -7030,12 +7189,12 @@ var WorkThreadStore = class {
7030
7189
  throw new Error("WorkThreadStore requires a rootDir");
7031
7190
  }
7032
7191
  this.rootDir = rootDir;
7033
- this.threadsDir = join9(rootDir, "threads");
7034
- this.indexPath = join9(rootDir, "index.json");
7192
+ this.threadsDir = join6(rootDir, "threads");
7193
+ this.indexPath = join6(rootDir, "index.json");
7035
7194
  }
7036
7195
  // ── 路径 ──────────────────────────────────────────────────────────
7037
7196
  threadFilePath(threadId) {
7038
- return join9(this.threadsDir, `${sanitizeWorkThreadFragment(threadId)}.json`);
7197
+ return join6(this.threadsDir, `${sanitizeWorkThreadFragment(threadId)}.json`);
7039
7198
  }
7040
7199
  // ── 原子写(对齐 FileSessionStore 模式,兼容 Windows EPERM/EXDEV)─
7041
7200
  async atomicWriteJson(filePath, data) {
@@ -7203,6 +7362,54 @@ var WorkThreadStore = class {
7203
7362
  if (this._threadLocks.get(threadId) === next) this._threadLocks.delete(threadId);
7204
7363
  }
7205
7364
  }
7365
+ /**
7366
+ * 删除线程记录文件与 index 条目(破坏性生命周期操作的存储原语)。
7367
+ *
7368
+ * 幂等:记录不存在时返回 { removed: false, alreadyAbsent: true }——
7369
+ * 已删除对象视为删除成功,供宿主的级联删除重试收敛。
7370
+ * 走与 create / update 相同的 per-thread 串行锁;index 条目移除走
7371
+ * _indexLock 串行链(与 updateIndexEntry 同链,并发创建 / 更新其它
7372
+ * 线程时双方的 index 快照不会互相覆盖丢失对方的条目)。
7373
+ */
7374
+ async remove(threadId) {
7375
+ if (!threadId || typeof threadId !== "string") {
7376
+ throw new WorkThreadNotFoundError(String(threadId || ""));
7377
+ }
7378
+ const prev = this._threadLocks.get(threadId) || Promise.resolve();
7379
+ let release;
7380
+ const next = new Promise((r) => release = r);
7381
+ this._threadLocks.set(threadId, next);
7382
+ await prev.catch(() => {
7383
+ });
7384
+ try {
7385
+ const record = await this.get(threadId);
7386
+ if (!record) return { removed: false, alreadyAbsent: true };
7387
+ await unlink2(this.threadFilePath(threadId)).catch(() => {
7388
+ });
7389
+ const indexPrev = this._indexLock;
7390
+ let indexRelease;
7391
+ const indexNext = new Promise((r) => indexRelease = r);
7392
+ this._indexLock = indexNext;
7393
+ try {
7394
+ await indexPrev.catch(() => {
7395
+ });
7396
+ const index = await this.readIndex();
7397
+ if (index.threads.some((t) => t?.threadId === threadId)) {
7398
+ index.threads = index.threads.filter(
7399
+ (t) => t?.threadId !== threadId
7400
+ );
7401
+ index.revision = (Number(index.revision) || 0) + 1;
7402
+ await this.writeIndex(index);
7403
+ }
7404
+ } finally {
7405
+ indexRelease();
7406
+ }
7407
+ return { removed: true, alreadyAbsent: false };
7408
+ } finally {
7409
+ release();
7410
+ if (this._threadLocks.get(threadId) === next) this._threadLocks.delete(threadId);
7411
+ }
7412
+ }
7206
7413
  };
7207
7414
  function buildChainEdges(record) {
7208
7415
  const chain = Array.isArray(record.sessionChain) ? record.sessionChain : [];
@@ -7226,7 +7433,6 @@ function buildChainEdges(record) {
7226
7433
  import { randomUUID as randomUUID3 } from "crypto";
7227
7434
  var WorkThreadCommandStatus = Object.freeze({
7228
7435
  PENDING: "pending",
7229
- IN_FLIGHT: "in_flight",
7230
7436
  DELIVERED: "delivered",
7231
7437
  FAILED: "failed",
7232
7438
  CANCELLED: "cancelled"
@@ -7261,7 +7467,8 @@ function createCommandRecord(opts) {
7261
7467
  createdAt: now,
7262
7468
  updatedAt: now,
7263
7469
  deliveredAt: null,
7264
- ...Array.isArray(opts.capabilityActivations) && opts.capabilityActivations.length > 0 ? { capabilityActivations: opts.capabilityActivations.filter((a) => typeof a === "string") } : {}
7470
+ ...Array.isArray(opts.capabilityActivations) && opts.capabilityActivations.length > 0 ? { capabilityActivations: opts.capabilityActivations.filter((a) => typeof a === "string") } : {},
7471
+ ...Array.isArray(opts.images) && opts.images.filter((i) => typeof i === "string" && i.length > 0).length > 0 ? { images: opts.images.filter((i) => typeof i === "string" && i.length > 0) } : {}
7265
7472
  };
7266
7473
  }
7267
7474
  function appendCommand(record, command) {
@@ -7269,7 +7476,7 @@ function appendCommand(record, command) {
7269
7476
  record.commands = commands;
7270
7477
  if (command.idempotencyKey) {
7271
7478
  const existing = commands.find(
7272
- (c) => c.idempotencyKey === command.idempotencyKey && (c.status === WorkThreadCommandStatus.PENDING || c.status === WorkThreadCommandStatus.IN_FLIGHT || c.status === WorkThreadCommandStatus.DELIVERED)
7479
+ (c) => c.idempotencyKey === command.idempotencyKey && (c.status === WorkThreadCommandStatus.PENDING || c.status === WorkThreadCommandStatus.DELIVERED)
7273
7480
  );
7274
7481
  if (existing) {
7275
7482
  return { command: existing, duplicate: true };
@@ -7334,7 +7541,8 @@ var WorkThreadRuntimeBridge = class {
7334
7541
  text: command.text || "",
7335
7542
  source: "thread",
7336
7543
  sourceRef: command.commandId || "",
7337
- ...Array.isArray(command.capabilityActivations) && command.capabilityActivations.length > 0 ? { capabilityActivations: command.capabilityActivations } : {}
7544
+ ...Array.isArray(command.capabilityActivations) && command.capabilityActivations.length > 0 ? { capabilityActivations: command.capabilityActivations } : {},
7545
+ ...Array.isArray(command.images) && command.images.length > 0 ? { images: command.images } : {}
7338
7546
  });
7339
7547
  } catch (error) {
7340
7548
  if (isUserTurnDeliveryError(error)) {
@@ -7385,6 +7593,11 @@ function pushLifecycleEvent(record, event, maxEvents = 200) {
7385
7593
  }
7386
7594
  record.lastLifecycleEvent = event;
7387
7595
  }
7596
+ var DEFAULT_SUCCESSION_INSTRUCTION = [
7597
+ "\u4E0A\u4E0B\u6587\u5DF2\u7CBE\u7B80\u63A5\u529B\u3002\u5148\u68C0\u67E5\u5F53\u524D\u5DE5\u4F5C\u6811\u3001\u5DF2\u6709\u53D8\u66F4\u3001\u6D4B\u8BD5\u7ED3\u679C\u548C\u4E0A\u4E00\u68D2\u6458\u8981\uFF0C",
7598
+ "\u786E\u8BA4\u54EA\u4E9B\u6B65\u9AA4\u5DF2\u7ECF\u5B8C\u6210\uFF1B\u4E0D\u8981\u91CD\u590D\u53EF\u80FD\u5DF2\u6709\u526F\u4F5C\u7528\u7684\u64CD\u4F5C\uFF0C\u7136\u540E\u7EE7\u7EED\u5F53\u524D\u4EFB\u52A1\u3002",
7599
+ "\u9700\u8981\u4EBA\u5DE5\u51B3\u7B56\u6216\u65E0\u6CD5\u5B89\u5168\u5224\u65AD\u65F6\uFF0C\u660E\u786E\u8BF4\u660E\u539F\u56E0\u3002"
7600
+ ].join("");
7388
7601
  function threadIdentityError(message, code, status) {
7389
7602
  return Object.assign(new Error(message), { code, status });
7390
7603
  }
@@ -7392,10 +7605,12 @@ var WorkThread = class {
7392
7605
  store;
7393
7606
  _bridge;
7394
7607
  _identitySource;
7395
- constructor({ store, bridge, identitySource }) {
7608
+ _continuationPolicy;
7609
+ constructor({ store, bridge, identitySource, continuationPolicy }) {
7396
7610
  this.store = store;
7397
7611
  this._bridge = bridge || new WorkThreadRuntimeBridge();
7398
7612
  this._identitySource = identitySource && typeof identitySource === "function" ? identitySource : void 0;
7613
+ this._continuationPolicy = continuationPolicy && typeof continuationPolicy.composeSuccessionInstruction === "function" ? continuationPolicy : { composeSuccessionInstruction: () => DEFAULT_SUCCESSION_INSTRUCTION };
7399
7614
  }
7400
7615
  // ── 查询 ─────────────────────────────────────────────────────────
7401
7616
  async listThreads(opts = {}) {
@@ -7509,8 +7724,16 @@ var WorkThread = class {
7509
7724
  return Date.now() - pending.startedAt < HANDOFF_STALE_MS;
7510
7725
  }
7511
7726
  /**
7512
- * 标记线程进入交接:写入 pendingSuccession(幂等,重复调用刷新时间戳)。
7513
- * 同步置 status=rotating(接续编排状态归锚点层)。
7727
+ * 标记线程进入交接:原子写入 pendingSuccession + status=rotating,并在同
7728
+ * 一笔事务内播种接续恢复指令(R3:createdAt 与挡板同拍,早于一切交接期
7729
+ * 积压;幂等键绑移交易 (threadId, fromSessionId),失败恢复重走不会重复)。
7730
+ *
7731
+ * 门禁(按序):
7732
+ * - closed → thread_closed(硬终态)
7733
+ * - hold → thread_held(归档/行政冻结期不开新交接,K9)
7734
+ * - 已有 fresh 交接在办 → handoff_in_progress(并发竞争显式拒绝而非
7735
+ * 幂等刷新续命,R8 跨入口 single-flight 锚点;stale 残卷可覆写重开)
7736
+ * - fromSessionId ≠ 当前 head → head_mismatch
7514
7737
  */
7515
7738
  async beginSessionHandoff(opts) {
7516
7739
  const threadId = validateId(opts.threadId, "threadId");
@@ -7523,6 +7746,18 @@ var WorkThread = class {
7523
7746
  status: 409
7524
7747
  });
7525
7748
  }
7749
+ if (draft.hold === true) {
7750
+ throw Object.assign(new Error(`WorkThread "${threadId}" is held (administrative freeze)`), {
7751
+ code: "thread_held",
7752
+ status: 409
7753
+ });
7754
+ }
7755
+ if (draft.status === "rotating" && draft.pendingSuccession?.startedAt && Date.now() - draft.pendingSuccession.startedAt < HANDOFF_STALE_MS) {
7756
+ throw Object.assign(
7757
+ new Error(`WorkThread "${threadId}" already has a handoff in progress; concurrent succession requests are rejected`),
7758
+ { code: "handoff_in_progress", status: 409 }
7759
+ );
7760
+ }
7526
7761
  if (draft.headSessionId !== normalizedFrom) {
7527
7762
  throw Object.assign(
7528
7763
  new Error(`Handoff source is not the current head of workthread "${threadId}"`),
@@ -7537,6 +7772,23 @@ var WorkThread = class {
7537
7772
  stage: "started",
7538
7773
  startedAt: now
7539
7774
  };
7775
+ const instructionText = this._continuationPolicy.composeSuccessionInstruction({
7776
+ threadId,
7777
+ fromSessionId: normalizedFrom,
7778
+ reason: normalizedReason
7779
+ });
7780
+ if (typeof instructionText === "string" && instructionText.trim()) {
7781
+ const instruction = createCommandRecord({
7782
+ threadId,
7783
+ kind: WorkThreadCommandKind.SYSTEM_CONTINUATION,
7784
+ text: instructionText,
7785
+ source: "thread-succession",
7786
+ idempotencyKey: `succession:${threadId}:${normalizedFrom}`
7787
+ });
7788
+ instruction.createdAt = now;
7789
+ appendCommand(draft, instruction);
7790
+ pruneCommands(draft);
7791
+ }
7540
7792
  pushLifecycleEvent(draft, {
7541
7793
  type: "handoff_started",
7542
7794
  status: "rotating",
@@ -7563,15 +7815,21 @@ var WorkThread = class {
7563
7815
  /**
7564
7816
  * 标记交接失败为接续编排状态 rotation_failed,不折叠成通用执行失败。
7565
7817
  * pendingSuccession 保留在盘上,供后续 resume 检查被打断的确切 stage。
7818
+ *
7819
+ * K3 守卫(事务内):失败记录的资格来自「在办移交」本身——
7820
+ * - closed 线程:硬终态,任何迟到失败不得覆写(返回原记录,零写入);
7821
+ * - 无 pendingSuccession(移交已成功推进 / 从未开始):迟到的失败者
7822
+ * 没有立卷资格,no-op 返回。这两条共同保证:只有「挡板仍在」的在办
7823
+ * 移交失败才会落 rotation_failed。
7566
7824
  */
7567
7825
  async failSessionHandoff(threadId, opts = {}) {
7568
- const thread = await this.store.get(threadId);
7569
- if (!thread) throw new WorkThreadNotFoundError(threadId);
7570
- const { record } = await this.store.update(threadId, (draft) => {
7571
- draft.status = "rotation_failed";
7572
- if (draft.pendingSuccession) {
7573
- draft.pendingSuccession.stage = cleanText(opts.stage) || draft.pendingSuccession.stage || "unknown";
7826
+ const tid = validateId(threadId, "threadId");
7827
+ const { record } = await this.store.update(tid, (draft) => {
7828
+ if (draft.status === WORKTHREAD_TERMINAL_STATUS || !draft.pendingSuccession) {
7829
+ return draft;
7574
7830
  }
7831
+ draft.status = "rotation_failed";
7832
+ draft.pendingSuccession.stage = cleanText(opts.stage) || draft.pendingSuccession.stage || "unknown";
7575
7833
  pushLifecycleEvent(draft, {
7576
7834
  type: "handoff_failed",
7577
7835
  status: "rotation_failed",
@@ -7591,9 +7849,18 @@ var WorkThread = class {
7591
7849
  */
7592
7850
  async appendCommand(opts) {
7593
7851
  const threadId = validateId(opts.threadId, "threadId");
7594
- const normalizedKind = opts.kind && Object.values(WorkThreadCommandKind).includes(opts.kind) ? opts.kind : WorkThreadCommandKind.USER_MESSAGE;
7852
+ const normalizedKind = opts.kind === void 0 || opts.kind === "" ? WorkThreadCommandKind.USER_MESSAGE : opts.kind;
7853
+ if (!Object.values(WorkThreadCommandKind).includes(normalizedKind)) {
7854
+ throw Object.assign(
7855
+ new Error(
7856
+ `Unknown command kind "${normalizedKind}" (expected one of: ${Object.values(WorkThreadCommandKind).join(", ")})`
7857
+ ),
7858
+ { code: "invalid_request", status: 400 }
7859
+ );
7860
+ }
7595
7861
  const normalizedText = String(opts.text || "");
7596
- if (!normalizedText.trim()) {
7862
+ const normalizedImages = Array.isArray(opts.images) ? opts.images.filter((entry) => typeof entry === "string" && entry.trim()) : [];
7863
+ if (!normalizedText.trim() && normalizedImages.length === 0) {
7597
7864
  throw Object.assign(new Error("Command text must be non-empty"), {
7598
7865
  code: "invalid_request",
7599
7866
  status: 400
@@ -7611,10 +7878,17 @@ var WorkThread = class {
7611
7878
  text: normalizedText,
7612
7879
  source: cleanText(opts.source) || "ui",
7613
7880
  idempotencyKey: cleanText(opts.idempotencyKey),
7614
- ...Array.isArray(opts.capabilityActivations) ? { capabilityActivations: opts.capabilityActivations } : {}
7881
+ ...Array.isArray(opts.capabilityActivations) ? { capabilityActivations: opts.capabilityActivations } : {},
7882
+ ...normalizedImages.length > 0 ? { images: normalizedImages } : {}
7615
7883
  });
7616
7884
  let appendOutcome = { command, duplicate: false };
7617
7885
  const { record } = await this.store.update(threadId, (draft) => {
7886
+ if (draft.status === WORKTHREAD_TERMINAL_STATUS) {
7887
+ throw Object.assign(new Error(`WorkThread "${threadId}" is closed; terminal threads hold no new commands`), {
7888
+ code: "thread_closed",
7889
+ status: 409
7890
+ });
7891
+ }
7618
7892
  appendOutcome = appendCommand(draft, command);
7619
7893
  pruneCommands(draft);
7620
7894
  return draft;
@@ -7678,7 +7952,29 @@ var WorkThread = class {
7678
7952
  let deliveredCount = 0;
7679
7953
  let stopReason = null;
7680
7954
  for (const command of pending) {
7681
- const outcome = await this._bridge.deliver({ thread, command });
7955
+ const fresh = await this.store.get(tid);
7956
+ if (!fresh) throw new WorkThreadNotFoundError(tid);
7957
+ if (this.isTerminal(fresh)) {
7958
+ stopReason = "thread_closed";
7959
+ break;
7960
+ }
7961
+ if (fresh.hold === true) {
7962
+ stopReason = "thread_held";
7963
+ break;
7964
+ }
7965
+ if (fresh.pendingSuccession && this.isHandoffActive(fresh)) {
7966
+ stopReason = "handoff_in_progress";
7967
+ break;
7968
+ }
7969
+ if (fresh.headSessionId !== thread.headSessionId) {
7970
+ stopReason = "head_changed";
7971
+ break;
7972
+ }
7973
+ const freshCommand = (fresh.commands || []).find((c) => c?.commandId === command.commandId) || null;
7974
+ if (!freshCommand || freshCommand.status !== WorkThreadCommandStatus.PENDING) {
7975
+ continue;
7976
+ }
7977
+ const outcome = await this._bridge.deliver({ thread: fresh, command: freshCommand });
7682
7978
  results.push({ commandId: command.commandId, ...outcome });
7683
7979
  if (outcome.accepted) {
7684
7980
  deliveredCount += 1;
@@ -7696,6 +7992,7 @@ var WorkThread = class {
7696
7992
  if (deliveredIds.size > 0 || failedResults.size > 0) {
7697
7993
  const { record } = await this.store.update(tid, (draft) => {
7698
7994
  for (const c of draft.commands || []) {
7995
+ if (c.status !== WorkThreadCommandStatus.PENDING) continue;
7699
7996
  if (deliveredIds.has(c.commandId)) {
7700
7997
  c.status = WorkThreadCommandStatus.DELIVERED;
7701
7998
  c.deliveryRef = results.find((r) => r.commandId === c.commandId)?.deliveryRef || null;
@@ -7739,6 +8036,10 @@ var WorkThread = class {
7739
8036
  /**
7740
8037
  * 推进线程 head:headSessionId: fromSessionId → toSessionId。
7741
8038
  *
8039
+ * fromSessionId 必填(K23):它是调用方声明的 CAS 期望值——不传即
8040
+ * 「接受任意当前 head」,幽灵任务防串台将被合法绕过。调用方若不知道
8041
+ * 当前 head,应先读线程再用读到的值调用(CLI 的自动填充即此语义)。
8042
+ *
7742
8043
  * 关键不变量(与 store 原子写共同保证):任一时刻线程要么明确指向旧 head,
7743
8044
  * 要么明确指向新 head;推进与指令状态 / 清挡板在同一次落盘中变更。
7744
8045
  *
@@ -7756,7 +8057,7 @@ var WorkThread = class {
7756
8057
  async advanceHead(opts) {
7757
8058
  const threadId = validateId(opts.threadId, "threadId");
7758
8059
  const normalizedTo = validateId(opts.toSessionId, "toSessionId");
7759
- const normalizedFrom = opts.fromSessionId ? validateId(opts.fromSessionId, "fromSessionId") : null;
8060
+ const normalizedFrom = validateId(opts.fromSessionId, "fromSessionId");
7760
8061
  const { record } = await this.store.update(
7761
8062
  threadId,
7762
8063
  async (draft) => {
@@ -7766,7 +8067,13 @@ var WorkThread = class {
7766
8067
  status: 409
7767
8068
  });
7768
8069
  }
7769
- if (normalizedFrom && draft.headSessionId !== normalizedFrom) {
8070
+ if (draft.hold === true) {
8071
+ throw Object.assign(
8072
+ new Error(`WorkThread "${threadId}" is held (administrative freeze); head cannot advance`),
8073
+ { code: "thread_held", status: 409 }
8074
+ );
8075
+ }
8076
+ if (draft.headSessionId !== normalizedFrom) {
7770
8077
  throw Object.assign(
7771
8078
  new Error(
7772
8079
  `Head mismatch on workthread "${threadId}": expected ${normalizedFrom}, current ${draft.headSessionId}`
@@ -7854,7 +8161,7 @@ var WorkThread = class {
7854
8161
  type: "handoff_completed",
7855
8162
  status: "open",
7856
8163
  at: now,
7857
- fromSessionId: normalizedFrom || currentHead?.sessionId || null,
8164
+ fromSessionId: normalizedFrom,
7858
8165
  toSessionId: normalizedTo,
7859
8166
  reason: cleanText(opts.endKind) || "manual"
7860
8167
  });
@@ -7902,7 +8209,7 @@ var WorkThread = class {
7902
8209
 
7903
8210
  // src/core/workthread/board.ts
7904
8211
  import { copyFile as copyFile3, mkdir as mkdir5, readFile as readFile9, rename as rename2, unlink as unlink3, writeFile as writeFile4 } from "fs/promises";
7905
- import { join as join10 } from "path";
8212
+ import { join as join7 } from "path";
7906
8213
  var BOARD_TRANSITIONS = {
7907
8214
  idle: /* @__PURE__ */ new Set(["running", "waiting_input", "failed", "closed"]),
7908
8215
  running: /* @__PURE__ */ new Set(["idle", "waiting_input", "failed", "closed"]),
@@ -7929,11 +8236,11 @@ var WorkThreadBoard = class {
7929
8236
  throw new Error("WorkThreadBoard requires a rootDir");
7930
8237
  }
7931
8238
  this.core = core;
7932
- this.boardsDir = join10(rootDir, "boards");
8239
+ this.boardsDir = join7(rootDir, "boards");
7933
8240
  }
7934
8241
  // ── 内部持久化(独立看板域,与锚点 store 分离)──────────────
7935
8242
  boardFilePath(workThreadId) {
7936
- return join10(this.boardsDir, `${sanitizeWorkThreadFragment(workThreadId)}.board.json`);
8243
+ return join7(this.boardsDir, `${sanitizeWorkThreadFragment(workThreadId)}.board.json`);
7937
8244
  }
7938
8245
  async getBoardId(workThreadId) {
7939
8246
  return workThreadId;
@@ -8273,6 +8580,7 @@ export {
8273
8580
  HandoffSeedFeature,
8274
8581
  HooksRegistry,
8275
8582
  LspFeature,
8583
+ MAX_RETAINED_TERMINAL_COMMANDS,
8276
8584
  OPENAI_THINKING_EFFORTS,
8277
8585
  OPENCODE_BASIC_CONTINUITY_PROTOCOL,
8278
8586
  OpencodeBasicFeature,
@@ -8300,6 +8608,7 @@ export {
8300
8608
  WorkThreadRevisionConflictError,
8301
8609
  WorkThreadRuntimeBridge,
8302
8610
  WorkThreadStore,
8611
+ appendCommand,
8303
8612
  applyTemplate,
8304
8613
  assistant,
8305
8614
  buildSummaryPrompt,
@@ -8307,6 +8616,7 @@ export {
8307
8616
  buildTrimmedSeedMessages,
8308
8617
  classifyAPIError,
8309
8618
  classifyAndWrapError,
8619
+ createCommandRecord,
8310
8620
  createLLMCharCount,
8311
8621
  createLLMComplete,
8312
8622
  createLLMRetry,
@@ -8322,6 +8632,8 @@ export {
8322
8632
  emitSessionEvent,
8323
8633
  extractConnectionErrorDetails,
8324
8634
  extractErrorCode,
8635
+ findCommand,
8636
+ generateCommandId,
8325
8637
  generateSummaryText,
8326
8638
  generateWorkThreadId,
8327
8639
  getClawRuntimeUrl,
@@ -8345,7 +8657,9 @@ export {
8345
8657
  normalizeExportPolicy,
8346
8658
  normalizeSummaryPolicy,
8347
8659
  parseRetryAfter,
8660
+ pendingCommands,
8348
8661
  preflightAssembly,
8662
+ pruneCommands,
8349
8663
  readContinuityDescriptor,
8350
8664
  readHookDeclarations,
8351
8665
  resolveDebugTransportMode,