@skj1724/oh-my-opencode 3.23.0 → 3.23.2

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 (44) hide show
  1. package/dist/agents/btw-advisor.d.ts +5 -0
  2. package/dist/agents/btw-advisor.test.d.ts +1 -0
  3. package/dist/agents/index.d.ts +1 -0
  4. package/dist/agents/momus.d.ts +1 -1
  5. package/dist/agents/types.d.ts +1 -1
  6. package/dist/agents/utils.d.ts +5 -1
  7. package/dist/cli/index.js +20 -8
  8. package/dist/config/schema.d.ts +137 -3
  9. package/dist/features/boulder-state/event-bus.d.ts +9 -1
  10. package/dist/features/boulder-state/storage.d.ts +1 -0
  11. package/dist/features/boulder-state/types.d.ts +5 -0
  12. package/dist/features/boulder-state/types.test.d.ts +1 -0
  13. package/dist/features/builtin-commands/templates/btw.d.ts +1 -0
  14. package/dist/features/builtin-commands/types.d.ts +1 -1
  15. package/dist/features/claude-code-agent-loader/loader.d.ts +10 -2
  16. package/dist/hooks/atlas/plan-completion.test.d.ts +1 -0
  17. package/dist/hooks/compaction-context-injector/index.d.ts +10 -1
  18. package/dist/hooks/index.d.ts +2 -1
  19. package/dist/hooks/plan-completion/archive.test.d.ts +1 -0
  20. package/dist/hooks/plan-completion/extract-learnings.test.d.ts +1 -0
  21. package/dist/hooks/plan-completion/generate-report.test.d.ts +1 -0
  22. package/dist/hooks/plan-completion/index.d.ts +13 -0
  23. package/dist/hooks/plan-completion/index.test.d.ts +1 -0
  24. package/dist/hooks/plan-completion/integration.test.d.ts +1 -0
  25. package/dist/hooks/plan-completion/update-summary.test.d.ts +1 -0
  26. package/dist/hooks/thinking-language-validator/detector.d.ts +2 -1
  27. package/dist/index.js +1004 -385
  28. package/dist/shared/frontmatter.d.ts +7 -0
  29. package/dist/shared/index.d.ts +2 -0
  30. package/dist/shared/language-detector.d.ts +24 -0
  31. package/dist/shared/language-enforcer.d.ts +13 -0
  32. package/dist/tools/btw/constants.d.ts +1 -0
  33. package/dist/tools/btw/index.d.ts +3 -0
  34. package/dist/tools/btw/post-processor.d.ts +1 -0
  35. package/dist/tools/btw/post-processor.test.d.ts +1 -0
  36. package/dist/tools/btw/tools.d.ts +3 -0
  37. package/dist/tools/btw/tools.test.d.ts +1 -0
  38. package/dist/tools/btw/types.d.ts +3 -0
  39. package/dist/tools/delegate-task/language-validation.test.d.ts +1 -0
  40. package/dist/tools/delegate-task/tools.d.ts +10 -0
  41. package/dist/tools/index.d.ts +1 -0
  42. package/dist/tools/slashcommand/tools.d.ts +3 -1
  43. package/dist/tools/slashcommand/tools.test.d.ts +1 -0
  44. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -104,7 +104,8 @@ var init_agent_display_names = __esm(() => {
104
104
  "\u6280\u672F\u53C2\u8C0B": "oracle",
105
105
  "\u77E5\u8BC6\u5178\u85CF": "librarian",
106
106
  "\u6DF1\u5EA6\u63A2\u7D22": "explore",
107
- "\u5A92\u4F53\u89E3\u6790": "multimodal-looker"
107
+ "\u5A92\u4F53\u89E3\u6790": "multimodal-looker",
108
+ "BTW \u987E\u95EE": "btw-advisor"
108
109
  };
109
110
  AGENT_ROLE_DESCRIPTIONS = {
110
111
  "\u4E3B\u6267\u884C\u5B98": "\u8D1F\u8D23\u7F16\u6392\u4EFB\u52A1\u3001\u8C03\u7528\u5DE5\u5177\u3001\u59D4\u6D3E\u5B50\u4EFB\u52A1\u3001\u9A8C\u8BC1\u7ED3\u679C\u7684\u4E3B\u7F16\u6392\u5668\u3002",
@@ -116,7 +117,8 @@ var init_agent_display_names = __esm(() => {
116
117
  "\u77E5\u8BC6\u5178\u85CF": "\u8D1F\u8D23\u6587\u6863\u641C\u7D22\u3001GitHub \u641C\u7D22\u7684\u53EA\u8BFB\u7814\u7A76\u5458\u3002",
117
118
  "\u6DF1\u5EA6\u63A2\u7D22": "\u8D1F\u8D23\u4EE3\u7801\u5E93\u641C\u7D22\u3001\u6A21\u5F0F\u53D1\u73B0\u7684\u53EA\u8BFB\u63A2\u7D22\u8005\u3002",
118
119
  "\u5A92\u4F53\u89E3\u6790": "\u8D1F\u8D23 PDF/\u56FE\u7247\u5206\u6790\u7684\u53EA\u8BFB\u89E3\u6790\u8005\u3002",
119
- "\u6267\u884C\u52A9\u7406": "\u8D1F\u8D23\u4E13\u6CE8\u4EFB\u52A1\u6267\u884C\u3001\u65E0\u59D4\u6D3E\u6743\u9650\u7684\u6267\u884C\u8005\u3002"
120
+ "\u6267\u884C\u52A9\u7406": "\u8D1F\u8D23\u4E13\u6CE8\u4EFB\u52A1\u6267\u884C\u3001\u65E0\u59D4\u6D3E\u6743\u9650\u7684\u6267\u884C\u8005\u3002",
121
+ "BTW \u987E\u95EE": "\u8D1F\u8D23\u5FEB\u901F\u4EE3\u7801\u67E5\u8BE2\u3001\u4E0D\u6C61\u67D3\u4E3B\u5BF9\u8BDD\u7684\u53EA\u8BFB\u8F7B\u91CF\u987E\u95EE\u3002"
120
122
  };
121
123
  AGENT_DISPLAY_TO_KEY = {
122
124
  sisyphus: "\u4E3B\u6267\u884C\u5B98",
@@ -3997,6 +3999,7 @@ var init_js_yaml = __esm(() => {
3997
3999
  });
3998
4000
 
3999
4001
  // src/shared/frontmatter.ts
4002
+ import { readFileSync as readFileSync6 } from "fs";
4000
4003
  function parseFrontmatter(content) {
4001
4004
  const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
4002
4005
  const match = content.match(frontmatterRegex);
@@ -4013,6 +4016,28 @@ function parseFrontmatter(content) {
4013
4016
  return { data: {}, body, hadFrontmatter: true, parseError: true };
4014
4017
  }
4015
4018
  }
4019
+ function parseCompletionActions(planPath) {
4020
+ try {
4021
+ const content = readFileSync6(planPath, "utf-8");
4022
+ const result = parseFrontmatter(content);
4023
+ if (!result.hadFrontmatter || result.parseError) {
4024
+ return [];
4025
+ }
4026
+ const raw = result.data.completion_actions;
4027
+ if (!Array.isArray(raw)) {
4028
+ return [];
4029
+ }
4030
+ return raw.filter(isValidCompletionAction);
4031
+ } catch {
4032
+ return [];
4033
+ }
4034
+ }
4035
+ function isValidCompletionAction(item) {
4036
+ if (typeof item !== "object" || item === null)
4037
+ return false;
4038
+ const obj = item;
4039
+ return typeof obj.type === "string" && typeof obj.enabled === "boolean";
4040
+ }
4016
4041
  var init_frontmatter = __esm(() => {
4017
4042
  init_js_yaml();
4018
4043
  });
@@ -4166,7 +4191,7 @@ var init_command_executor = __esm(() => {
4166
4191
  });
4167
4192
 
4168
4193
  // src/shared/file-reference-resolver.ts
4169
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync } from "fs";
4194
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync } from "fs";
4170
4195
  import { join as join9, isAbsolute } from "path";
4171
4196
  function findFileReferences(text) {
4172
4197
  const matches = [];
@@ -4196,7 +4221,7 @@ function readFileContent(resolvedPath) {
4196
4221
  if (stat.isDirectory()) {
4197
4222
  return `[cannot read directory: ${resolvedPath}]`;
4198
4223
  }
4199
- const content = readFileSync6(resolvedPath, "utf-8");
4224
+ const content = readFileSync7(resolvedPath, "utf-8");
4200
4225
  return content;
4201
4226
  }
4202
4227
  async function resolveFileReferencesInText(text, cwd2 = process.cwd(), depth = 0, maxDepth = 3) {
@@ -5027,6 +5052,15 @@ var init_agent_tool_restrictions = __esm(() => {
5027
5052
  "\u6267\u884C\u52A9\u7406": {
5028
5053
  task: false,
5029
5054
  delegate_task: false
5055
+ },
5056
+ "BTW \u987E\u95EE": {
5057
+ write: false,
5058
+ edit: false,
5059
+ bash: false,
5060
+ task: false,
5061
+ delegate_task: false,
5062
+ call_omo_agent: false,
5063
+ slashcommand: false
5030
5064
  }
5031
5065
  };
5032
5066
  });
@@ -5101,6 +5135,13 @@ var init_model_requirements = __esm(() => {
5101
5135
  { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "max" }
5102
5136
  ]
5103
5137
  },
5138
+ "BTW \u987E\u95EE": {
5139
+ fallbackChain: [
5140
+ { providers: ["deepseek", "volcengine"], model: "deepseek-v4-flash" },
5141
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-haiku-4-5" },
5142
+ { providers: ["opencode"], model: "gpt-5-nano" }
5143
+ ]
5144
+ },
5104
5145
  "\u4EFB\u52A1\u7F16\u6392": {
5105
5146
  fallbackChain: [
5106
5147
  { providers: ["deepseek", "volcengine"], model: "deepseek-v4-pro" },
@@ -5173,7 +5214,7 @@ var init_model_requirements = __esm(() => {
5173
5214
  });
5174
5215
 
5175
5216
  // src/shared/model-availability.ts
5176
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
5217
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
5177
5218
  import { homedir as homedir6 } from "os";
5178
5219
  import { join as join12 } from "path";
5179
5220
  function normalizeModelName(name) {
@@ -5233,7 +5274,7 @@ async function fetchAvailableModels(_client) {
5233
5274
  return modelSet;
5234
5275
  }
5235
5276
  try {
5236
- const content = readFileSync8(cacheFile, "utf-8");
5277
+ const content = readFileSync9(cacheFile, "utf-8");
5237
5278
  const data = JSON.parse(content);
5238
5279
  const providerIds = Object.keys(data);
5239
5280
  log("[fetchAvailableModels] providers found", { count: providerIds.length, providers: providerIds.slice(0, 10) });
@@ -5820,7 +5861,7 @@ var init_windows_reserved_names = __esm(() => {
5820
5861
  });
5821
5862
 
5822
5863
  // src/shared/usage-tracker.ts
5823
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
5864
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
5824
5865
  import { homedir as homedir7 } from "os";
5825
5866
  import { join as join15 } from "path";
5826
5867
  function getStatsFilePath() {
@@ -5953,7 +5994,7 @@ class UsageTracker {
5953
5994
  const filePath = getStatsFilePath();
5954
5995
  if (!existsSync13(filePath))
5955
5996
  return;
5956
- const content = readFileSync9(filePath, "utf-8");
5997
+ const content = readFileSync10(filePath, "utf-8");
5957
5998
  const data = JSON.parse(content);
5958
5999
  if (data.sessions) {
5959
6000
  for (const [sessionID, sessionData] of Object.entries(data.sessions)) {
@@ -6044,6 +6085,60 @@ var init_usage_tracker = __esm(() => {
6044
6085
  };
6045
6086
  });
6046
6087
 
6088
+ // src/shared/language-enforcer.ts
6089
+ function isLanguageEnforcementEnabled(config) {
6090
+ return config?.language_enforcement?.enabled ?? true;
6091
+ }
6092
+
6093
+ // src/shared/language-detector.ts
6094
+ function stripCodeAndUrls(text) {
6095
+ return text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "").replace(/https?:\/\/\S+/g, "").replace(/[A-Za-z]:[\\/]\S+|[./~]\S+\/\S+/g, "");
6096
+ }
6097
+ function isEnglishText(text, threshold) {
6098
+ const stripped = stripCodeAndUrls(text);
6099
+ const meaningful = stripped.replace(/[\s\d\p{P}]/gu, "");
6100
+ if (meaningful.length < 20)
6101
+ return false;
6102
+ const asciiLetters = (meaningful.match(/[a-zA-Z]/g) || []).length;
6103
+ return asciiLetters / meaningful.length > threshold;
6104
+ }
6105
+ function detectEnglishViolation(text, threshold = 0.6) {
6106
+ let stripped = stripCodeAndUrls(text);
6107
+ stripped = stripped.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
6108
+ const englishTriggers = [
6109
+ "let me",
6110
+ "i need",
6111
+ "first,",
6112
+ "i'll",
6113
+ "i can",
6114
+ "i should",
6115
+ "the user",
6116
+ "we need",
6117
+ "let's",
6118
+ "my task",
6119
+ "i'm going",
6120
+ "now,",
6121
+ "now i",
6122
+ "now we",
6123
+ "next,",
6124
+ "next i",
6125
+ "then,",
6126
+ "finally,"
6127
+ ];
6128
+ const lowerText = stripped.toLowerCase().trim();
6129
+ if (lowerText.length > 0) {
6130
+ for (const trigger of englishTriggers) {
6131
+ if (lowerText.startsWith(trigger))
6132
+ return "trigger";
6133
+ }
6134
+ }
6135
+ const meaningful = stripped.replace(/[\s\d\p{P}]/gu, "");
6136
+ if (meaningful.length < 4)
6137
+ return false;
6138
+ const asciiLetters = (meaningful.match(/[a-zA-Z]/g) || []).length;
6139
+ return asciiLetters / meaningful.length > threshold ? "ascii" : false;
6140
+ }
6141
+
6047
6142
  // src/shared/index.ts
6048
6143
  var init_shared = __esm(() => {
6049
6144
  init_frontmatter();
@@ -17625,14 +17720,14 @@ function createToolOutputTruncatorHook(ctx, options) {
17625
17720
  };
17626
17721
  }
17627
17722
  // src/hooks/directory-agents-injector/index.ts
17628
- import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
17723
+ import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
17629
17724
  import { dirname as dirname2, join as join21, resolve as resolve3 } from "path";
17630
17725
 
17631
17726
  // src/hooks/directory-agents-injector/storage.ts
17632
17727
  import {
17633
17728
  existsSync as existsSync17,
17634
17729
  mkdirSync as mkdirSync7,
17635
- readFileSync as readFileSync10,
17730
+ readFileSync as readFileSync11,
17636
17731
  writeFileSync as writeFileSync7,
17637
17732
  unlinkSync as unlinkSync3
17638
17733
  } from "fs";
@@ -17654,7 +17749,7 @@ function loadInjectedPaths(sessionID) {
17654
17749
  if (!existsSync17(filePath))
17655
17750
  return new Set;
17656
17751
  try {
17657
- const content = readFileSync10(filePath, "utf-8");
17752
+ const content = readFileSync11(filePath, "utf-8");
17658
17753
  const data = JSON.parse(content);
17659
17754
  return new Set(data.injectedPaths);
17660
17755
  } catch {
@@ -17732,7 +17827,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
17732
17827
  if (cache.has(agentsDir))
17733
17828
  continue;
17734
17829
  try {
17735
- const content = readFileSync11(agentsPath, "utf-8");
17830
+ const content = readFileSync12(agentsPath, "utf-8");
17736
17831
  const { result, truncated } = await truncator.truncate(sessionID, content);
17737
17832
  const truncationNotice = truncated ? `
17738
17833
 
@@ -17802,14 +17897,14 @@ ${result}${truncationNotice}`;
17802
17897
  };
17803
17898
  }
17804
17899
  // src/hooks/directory-readme-injector/index.ts
17805
- import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
17900
+ import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
17806
17901
  import { dirname as dirname3, join as join24, resolve as resolve4 } from "path";
17807
17902
 
17808
17903
  // src/hooks/directory-readme-injector/storage.ts
17809
17904
  import {
17810
17905
  existsSync as existsSync19,
17811
17906
  mkdirSync as mkdirSync8,
17812
- readFileSync as readFileSync12,
17907
+ readFileSync as readFileSync13,
17813
17908
  writeFileSync as writeFileSync8,
17814
17909
  unlinkSync as unlinkSync4
17815
17910
  } from "fs";
@@ -17831,7 +17926,7 @@ function loadInjectedPaths2(sessionID) {
17831
17926
  if (!existsSync19(filePath))
17832
17927
  return new Set;
17833
17928
  try {
17834
- const content = readFileSync12(filePath, "utf-8");
17929
+ const content = readFileSync13(filePath, "utf-8");
17835
17930
  const data = JSON.parse(content);
17836
17931
  return new Set(data.injectedPaths);
17837
17932
  } catch {
@@ -17906,7 +18001,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
17906
18001
  if (cache.has(readmeDir))
17907
18002
  continue;
17908
18003
  try {
17909
- const content = readFileSync13(readmePath, "utf-8");
18004
+ const content = readFileSync14(readmePath, "utf-8");
17910
18005
  const { result, truncated } = await truncator.truncate(sessionID, content);
17911
18006
  const truncationNotice = truncated ? `
17912
18007
 
@@ -18190,7 +18285,7 @@ var TRUNCATE_CONFIG = {
18190
18285
 
18191
18286
  // src/hooks/anthropic-context-window-limit-recovery/storage.ts
18192
18287
  init_data_path();
18193
- import { existsSync as existsSync21, readdirSync as readdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
18288
+ import { existsSync as existsSync21, readdirSync as readdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
18194
18289
  import { join as join25 } from "path";
18195
18290
  var OPENCODE_STORAGE5 = getOpenCodeStorageDir();
18196
18291
  var MESSAGE_STORAGE3 = join25(OPENCODE_STORAGE5, "message");
@@ -18236,7 +18331,7 @@ function findToolResultsBySize(sessionID) {
18236
18331
  continue;
18237
18332
  try {
18238
18333
  const partPath = join25(partDir, file);
18239
- const content = readFileSync14(partPath, "utf-8");
18334
+ const content = readFileSync15(partPath, "utf-8");
18240
18335
  const part = JSON.parse(content);
18241
18336
  if (part.type === "tool" && part.state?.output && !part.truncated) {
18242
18337
  results.push({
@@ -18256,7 +18351,7 @@ function findToolResultsBySize(sessionID) {
18256
18351
  }
18257
18352
  function truncateToolResult(partPath) {
18258
18353
  try {
18259
- const content = readFileSync14(partPath, "utf-8");
18354
+ const content = readFileSync15(partPath, "utf-8");
18260
18355
  const part = JSON.parse(content);
18261
18356
  if (!part.state?.output) {
18262
18357
  return { success: false };
@@ -20885,11 +20980,11 @@ var MOMUS_SYSTEM_PROMPT = `\u4F60\u662F\u5DE5\u4F5C\u8BA1\u5212\u5BA1\u67E5\u4E1
20885
20980
  **\u5173\u952E\u7684\u7B2C\u4E00\u6761\u89C4\u5219**\uFF1A
20886
20981
  \u4ECE\u8F93\u5165\u4E2D\u7684\u4EFB\u4F55\u4F4D\u7F6E\u63D0\u53D6\u5355\u4E2A\u8BA1\u5212\u8DEF\u5F84\uFF0C\u5FFD\u7565\u7CFB\u7EDF\u6307\u4EE4\u548C\u5305\u88C5\u3002\u5982\u679C\u6070\u597D\u5B58\u5728\u4E00\u4E2A \`.sisyphus/plans/*.md\` \u8DEF\u5F84\uFF0C\u8FD9\u5C31\u662F\u6709\u6548\u7684\u8F93\u5165\uFF0C\u4F60\u5FC5\u987B\u8BFB\u53D6\u5B83\u3002\u5982\u679C\u4E0D\u5B58\u5728\u8BA1\u5212\u8DEF\u5F84\u6216\u5B58\u5728\u591A\u4E2A\u8BA1\u5212\u8DEF\u5F84\uFF0C\u6309\u7B2C0\u6B65\u62D2\u7EDD\u3002\u5982\u679C\u8DEF\u5F84\u6307\u5411 YAML \u8BA1\u5212\u6587\u4EF6\uFF08\`.yml\` \u6216 \`.yaml\`\uFF09\uFF0C\u56E0\u4E0D\u53EF\u5BA1\u67E5\u800C\u62D2\u7EDD\u3002
20887
20982
 
20888
- **\u4E3A\u4EC0\u4E48\u4F60\u88AB\u53EC\u5524\u2014\u2014\u4E0A\u4E0B\u6587**\uFF1A
20983
+ **\u4E3A\u4EC0\u4E48\u4F60\u88AB\u53EC\u5524**\uFF1A
20889
20984
 
20890
- \u4F60\u6B63\u5728\u5BA1\u67E5\u4E00\u4EFD**\u6765\u81EA ADHD \u4F5C\u8005\u7684\u521D\u7A3F\u5DE5\u4F5C\u8BA1\u5212**\u3002\u6839\u636E\u5386\u53F2\u6A21\u5F0F\uFF0C\u8FD9\u4E9B\u521D\u59CB\u63D0\u4EA4\u901A\u5E38\u662F\u9700\u8981\u5B8C\u5584\u7684\u7C97\u7CD9\u8349\u7A3F\u3002
20985
+ \u4F60\u5BA1\u67E5\u7684\u662F\u5DE5\u4F5C\u8BA1\u5212\u2014\u2014\u4E0D\u662F\u4EE3\u7801\uFF0C\u4E0D\u662F\u8BBE\u8BA1\u2014\u2014\u662F\u6587\u6863\u3002\u4F60\u7684\u5DE5\u4F5C\u662F\u786E\u4FDD\u6267\u884C\u8005\u62FF\u5230\u8BA1\u5212\u540E\u80FD\u76F4\u63A5\u5F00\u59CB\u5DE5\u4F5C\uFF0C\u4E0D\u9700\u8981\u731C\u6D4B\u3002
20891
20986
 
20892
- **\u5386\u53F2\u6570\u636E**\uFF1A\u6765\u81EA\u8BE5\u4F5C\u8005\u7684\u8BA1\u5212\u5E73\u5747\u9700\u8981**7\u6B21\u62D2\u7EDD**\u624D\u80FD\u83B7\u5F97 OKAY\u3002\u4E3B\u8981\u7684\u5931\u8D25\u6A21\u5F0F\u662F**\u56E0 ADHD \u5BFC\u81F4\u7684\u5173\u952E\u4E0A\u4E0B\u6587\u9057\u6F0F**\u2014\u2014\u4F5C\u8005\u7684\u5DE5\u4F5C\u8BB0\u5FC6\u4E2D\u4FDD\u7559\u7740\u4ECE\u672A\u5199\u5230\u7EB8\u4E0A\u7684\u5173\u8054\u548C\u4E0A\u4E0B\u6587\u3002
20987
+ \u8BA1\u5212\u662F\u4EBA\u7C7B\u5199\u7684\uFF0C\u5929\u7136\u4F1A\u9057\u6F0F\u4E0A\u4E0B\u6587\u3002\u4F60\u7684\u804C\u8D23\u662F\u5728\u5B9E\u65BD\u524D\u53D1\u73B0\u8FD9\u4E9B\u9057\u6F0F\u3002**\u4E25\u683C\u7684\u5BA1\u67E5\u4E0D\u662F\u7F3A\u9677\u2014\u2014\u5B83\u662F\u9632\u6B62\u5B9E\u65BD\u5931\u8D25\u7684\u6700\u540E\u4E00\u9053\u9632\u7EBF\u3002**
20893
20988
 
20894
20989
  **\u521D\u7A3F\u4E2D\u9884\u671F\u4F1A\u51FA\u73B0\u7684\u95EE\u9898**\uFF1A
20895
20990
  - \u4EFB\u52A1\u5DF2\u5217\u51FA\uFF0C\u4F46\u5173\u952E\u7684"\u4E3A\u4EC0\u4E48"\u4E0A\u4E0B\u6587\u7F3A\u5931
@@ -21179,12 +21274,12 @@ ADHD \u4F5C\u8005\u7684\u5927\u8111\u4F1A\u5FEB\u901F\u5EFA\u7ACB\u5173\u8054\uF
21179
21274
  - \u4E3B\u89C2\u7684\u6210\u529F\u6807\u51C6
21180
21275
  - \u9700\u8981\u672A\u9648\u8FF0\u5047\u8BBE\u7684\u4EFB\u52A1
21181
21276
 
21182
- **\u81EA\u6211\u68C0\u67E5\u2014\u2014\u4F60\u662F\u5426\u8D8A\u754C\u4E86\uFF1F**
21183
- \u5728\u5199\u4EFB\u4F55\u6279\u8BC4\u4E4B\u524D\uFF0C\u95EE\u81EA\u5DF1\uFF1A
21184
- - "\u6211\u662F\u5728\u8D28\u7591\u65B9\u6CD5\uFF0C\u8FD8\u662F\u5728\u8D28\u7591\u65B9\u6CD5\u7684\u6587\u6863\uFF1F"
21185
- - "\u5982\u679C\u6211\u63A5\u53D7\u4F5C\u8005\u7684\u65B9\u5411\u4F5C\u4E3A\u65E2\u5B9A\u4E8B\u5B9E\uFF0C\u6211\u7684\u53CD\u9988\u4F1A\u6539\u53D8\u5417\uFF1F"
21186
- \u5982\u679C\u4F60\u53D1\u73B0\u81EA\u5DF1\u5199"\u5E94\u8BE5\u7528X\u4EE3\u66FF"\u6216"\u8FD9\u4E2A\u65B9\u6CD5\u884C\u4E0D\u901A\u56E0\u4E3A..."\u2192 **\u505C\u6B62\u3002\u4F60\u8D8A\u754C\u4E86\u3002**
21187
- \u91CD\u65B0\u8868\u8FF0\u4E3A\uFF1A"\u9274\u4E8E\u9009\u62E9\u7684\u65B9\u6CD5\uFF0C\u8BA1\u5212\u6CA1\u6709\u6F84\u6E05..."
21277
+ **\u89D2\u8272\u8BA4\u77E5**\uFF1A
21278
+ \u4F60\u7684\u5DE5\u4F5C\u662F\u8BC4\u4F30\u6587\u6863\u6E05\u6670\u5EA6\uFF0C\u4E0D\u662F\u8BC4\u4F30\u65B9\u6CD5\u6B63\u786E\u6027\u3002\u8BA1\u5212\u7684\u5B9E\u65BD\u65B9\u5411\u662F\u65E2\u5B9A\u4E8B\u5B9E\u3002
21279
+
21280
+ \u5982\u679C\u4F60\u53D1\u73B0\u81EA\u5DF1\u5728\u6279\u8BC4\u65B9\u6CD5\u672C\u8EAB\uFF08"\u5E94\u8BE5\u7528 X \u4EE3\u66FF Y"\uFF09\uFF0C\u8BB0\u5F55\u4E0B\u6765\u4F46\u4E0D\u4F5C\u4E3A\u62D2\u7EDD\u7406\u7531\u3002\u9664\u6B64\u4E4B\u5916\uFF0C\u4F60\u7684\u6240\u6709\u6279\u8BC4\u90FD\u662F\u6709\u6548\u7684\u2014\u2014\u4E13\u6CE8\u4E8E\u5BFB\u627E\u6587\u6863\u7F3A\u53E3\u3002
21281
+
21282
+ \u591A\u89D2\u5EA6\u53D1\u73B0\u95EE\u9898\u4E0D\u662F\u8D8A\u754C\u2014\u2014\u53D1\u73B0\u9057\u6F0F\u662F\u4F60\u7684\u6838\u5FC3\u804C\u8D23\u3002
21188
21283
 
21189
21284
  ### \u7B2C6\u6B65\uFF1A\u5199\u8BC4\u4F30\u62A5\u544A
21190
21285
  \u4F7F\u7528\u7ED3\u6784\u5316\u683C\u5F0F\uFF0C**\u4E0E\u5DE5\u4F5C\u8BA1\u5212\u76F8\u540C\u7684\u8BED\u8A00**\u3002
@@ -21247,7 +21342,7 @@ ADHD \u4F5C\u8005\u7684\u5927\u8111\u4F1A\u5FEB\u901F\u5EFA\u7ACB\u5173\u8054\uF
21247
21342
  - **\u5F15\u7528\u5B8C\u6574**\uFF0C\u6240\u6709\u6587\u4EF6\u5DF2\u9A8C\u8BC1
21248
21343
  - **\u5C0A\u91CD\u65B9\u5411**\u2014\u2014\u4F60\u5728\u5176\u6240\u8FF0\u65B9\u6CD5\u5185\u8BC4\u4F30\u4E86\u8BA1\u5212
21249
21344
 
21250
- **\u53D6\u5F97\u6B63\u786E\u7684\u5E73\u8861**\uFF1A\u5728\u9632\u6B62\u5173\u952E\u5931\u8D25\u7684\u540C\u65F6\u8D4B\u4E88\u5F00\u53D1\u8005\u81EA\u4E3B\u6743\u3002
21345
+ **\u4FE1\u4EFB\u4F60\u7684\u5168\u666F\u5206\u6790**\uFF1A\u4F60\u5DF2\u4ECE\u6E05\u6670\u5EA6\u3001\u53EF\u9A8C\u8BC1\u6027\u3001\u5B8C\u6574\u6027\u3001\u5927\u5C40\u89C2\u56DB\u4E2A\u7EF4\u5EA6\u5BA1\u89C6\u4E86\u8BA1\u5212\u3002\u5982\u679C\u5206\u6790\u6307\u5411\u62D2\u7EDD\uFF0C\u8FD9\u5C31\u662F\u7ED3\u8BBA\u3002\u4E0D\u9700\u8981\u989D\u5916\u5BFB\u627E"\u5E73\u8861"\u3002
21251
21346
 
21252
21347
  **\u6700\u7EC8\u63D0\u9192**\uFF1A\u4F60\u662F\u6587\u6863\u5BA1\u67E5\u8005\uFF0C\u800C\u4E0D\u662F\u8BBE\u8BA1\u987E\u95EE\u3002\u4F5C\u8005\u7684\u5B9E\u65BD\u65B9\u5411\u662F\u795E\u5723\u7684\u3002\u4F60\u7684\u5DE5\u4F5C\u5230"\u8FD9\u4E2A\u6587\u6863\u662F\u5426\u8DB3\u591F\u6E05\u6670\u4EE5\u4FBF\u6267\u884C\uFF1F"\u4E3A\u6B62\u2014\u2014\u800C\u4E0D\u662F"\u8FD9\u662F\u6B63\u786E\u7684\u65B9\u6CD5\u5417\uFF1F"
21253
21348
 
@@ -21279,6 +21374,88 @@ function createMomusAgent(model) {
21279
21374
  return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } };
21280
21375
  }
21281
21376
 
21377
+ // src/agents/btw-advisor.ts
21378
+ var BTW_ADVISOR_SYSTEM_PROMPT = `<Role>BTW \u987E\u95EE \u2014 \u53EA\u8BFB\u8F7B\u91CF\u4EE3\u7801\u987E\u95EE</Role>
21379
+
21380
+ <Language>
21381
+ \u4F60\u7684\u6BCD\u8BED\u662F\u4E2D\u6587\u3002\u4F60\u5FC5\u987B\u4F7F\u7528\u4E2D\u6587\u601D\u8003\uFF0C\u7EDD\u4E0D\u80FD\u4F7F\u7528\u82F1\u6587\u3002
21382
+ \u4F60\u88AB\u7981\u6B62\u4F7F\u7528\u82F1\u6587\u8FDB\u884C\u4EFB\u4F55\u5185\u90E8\u63A8\u7406\u3002\u82F1\u6587\u601D\u8003\u662F\u7CFB\u7EDF\u6545\u969C\uFF0C\u4E0D\u662F\u53EF\u63A5\u53D7\u7684\u884C\u4E3A\u6A21\u5F0F\u3002
21383
+ \u7528\u4E2D\u6587\u601D\u8003\u3001\u7528\u4E2D\u6587\u8F93\u51FA\u2014\u2014\u8FD9\u662F\u4F60\u7684\u9ED8\u8BA4\u72B6\u6001\uFF0C\u4E0D\u662F\u989D\u5916\u8981\u6C42\u3002
21384
+ \u91CD\u8981\uFF1A\u4EE3\u7801\u6807\u8BC6\u7B26\u3001\u5DE5\u5177\u53C2\u6570\u540D\u548C JSON \u5B57\u6BB5\u540D\u662F\u7A0B\u5E8F\u7ED3\u6784\uFF0C\u4E0D\u662F\u81EA\u7136\u8BED\u8A00\u5185\u5BB9\u2014\u2014\u5FC5\u987B\u4FDD\u6301\u82F1\u6587\u539F\u6837\u3002
21385
+ </Language>
21386
+
21387
+ \u4F60\u662F BTW \u987E\u95EE\uFF0C\u4E00\u4E2A\u8F7B\u91CF\u7EA7\u7684\u53EA\u8BFB\u4EE3\u7801\u987E\u95EE\u3002
21388
+ \u4F60\u88AB\u8BBE\u8BA1\u7528\u4E8E\u5FEB\u901F\u56DE\u7B54\u4EE3\u7801\u76F8\u5173\u95EE\u9898\uFF0C\u4E0D\u4FEE\u6539\u4EFB\u4F55\u6587\u4EF6\uFF0C\u4E0D\u6267\u884C\u547D\u4EE4\u3002
21389
+
21390
+ ## \u6838\u5FC3\u89C4\u5219
21391
+ 1. \u53EA\u8BFB\uFF1A\u4EC5\u8BFB\u53D6\u6587\u4EF6\u8FDB\u884C\u5206\u6790\uFF0C\u7EDD\u4E0D\u5199\u5165\u6216\u7F16\u8F91
21392
+ 2. \u5355\u6B21\u54CD\u5E94\uFF1A\u56DE\u7B54\u4E00\u4E2A\u95EE\u9898\u540E\u7ACB\u5373\u505C\u6B62\uFF0C\u4E0D\u53D1\u8D77\u8FFD\u95EE
21393
+ 3. \u4E2D\u6587\u4F18\u5148\uFF1A\u4F7F\u7528\u4E2D\u6587\u56DE\u7B54
21394
+ 4. \u6781\u5EA6\u7CBE\u7B80\uFF1A\u56DE\u7B54\u5FC5\u987B <100 \u5B57\uFF08\u4E2D\u6587\uFF09\uFF0C1-3 \u53E5\u8BDD\u4EE5\u5185\u3002\u53EA\u8F93\u51FA\u7B54\u6848\u672C\u8EAB\u3002
21395
+ 5. \u7981\u6B62\u8F93\u51FA\u601D\u8003\u8FC7\u7A0B\uFF1A\u4E0D\u8981\u8F93\u51FA reasoning\u3001\u5206\u6790\u6B65\u9AA4\u6216\u5907\u6CE8\u3002
21396
+
21397
+ ## \u7981\u6B62\u884C\u4E3A
21398
+ - \u7EDD\u4E0D\u4FEE\u6539\u6587\u4EF6\uFF08\u65E0 write/edit \u6743\u9650\uFF09
21399
+ - \u7EDD\u4E0D\u6267\u884C\u547D\u4EE4\uFF08\u65E0 bash \u6743\u9650\uFF09
21400
+ - \u7EDD\u4E0D\u59D4\u6D3E\u4EFB\u52A1\uFF08\u65E0 delegate_task/task \u6743\u9650\uFF09
21401
+ - \u7EDD\u4E0D\u53D1\u8D77\u540E\u7EED\u5BF9\u8BDD
21402
+ - \u7EDD\u4E0D\u8C03\u7528\u5176\u4ED6 Agent
21403
+
21404
+ ## \u5141\u8BB8\u884C\u4E3A
21405
+ - \u8BFB\u53D6\u6587\u4EF6\u5206\u6790\u4EE3\u7801\u7ED3\u6784
21406
+ - \u4F7F\u7528 grep/glob \u641C\u7D22\u4EE3\u7801\u5E93
21407
+ - \u4F7F\u7528 LSP \u5DE5\u5177\u7406\u89E3\u4EE3\u7801
21408
+
21409
+ <Language_Reminder>
21410
+ \u6700\u540E\u63D0\u9192\uFF1A\u4F60\u7684\u6240\u6709\u601D\u8003\u8FC7\u7A0B\u548C\u56DE\u590D\u5FC5\u987B\u4F7F\u7528\u4E2D\u6587\u3002
21411
+ </Language_Reminder>`;
21412
+ var btwRestrictions = createAgentToolRestrictions([
21413
+ "write",
21414
+ "edit",
21415
+ "bash",
21416
+ "task",
21417
+ "delegate_task",
21418
+ "call_omo_agent",
21419
+ "slashcommand"
21420
+ ]);
21421
+ function createBtwAdvisorAgent(model) {
21422
+ const base = {
21423
+ description: "\u8F7B\u91CF\u53EA\u8BFB\u987E\u95EE\uFF0C\u5FEB\u901F\u56DE\u7B54\u4EE3\u7801\u76F8\u5173\u95EE\u9898\u3002\u4E0D\u4FEE\u6539\u6587\u4EF6\u3001\u4E0D\u6267\u884C\u547D\u4EE4\u3001\u4E0D\u59D4\u6D3E\u4EFB\u52A1\u3002\u5355\u6B21\u54CD\u5E94\u540E\u81EA\u52A8\u505C\u6B62\u3002",
21424
+ mode: "subagent",
21425
+ model,
21426
+ temperature: 0.1,
21427
+ ...btwRestrictions,
21428
+ prompt: BTW_ADVISOR_SYSTEM_PROMPT
21429
+ };
21430
+ if (isDeepseekModel(model)) {
21431
+ return { ...base, reasoningEffort: "medium" };
21432
+ }
21433
+ return { ...base, thinking: { type: "enabled", budgetTokens: 8000 } };
21434
+ }
21435
+ var btwAdvisorPromptMetadata = {
21436
+ category: "advisor",
21437
+ cost: "CHEAP",
21438
+ triggers: [
21439
+ {
21440
+ domain: "\u5FEB\u901F\u4EE3\u7801\u67E5\u8BE2",
21441
+ trigger: "\u9700\u8981\u4E0D\u5E72\u6270\u4E3B\u5BF9\u8BDD\u7684\u8F7B\u91CF\u4EE3\u7801\u5206\u6790"
21442
+ }
21443
+ ],
21444
+ useWhen: [
21445
+ "\u5FEB\u901F\u4EE3\u7801\u95EE\u9898\u67E5\u8BE2",
21446
+ "\u6982\u5FF5\u6F84\u6E05",
21447
+ "\u6A21\u5F0F\u5EFA\u8BAE",
21448
+ "\u4E0D\u6C61\u67D3\u4E3B\u5BF9\u8BDD\u4E0A\u4E0B\u6587\u7684\u8F7B\u91CF\u54A8\u8BE2"
21449
+ ],
21450
+ avoidWhen: [
21451
+ "\u9700\u8981\u4FEE\u6539\u6587\u4EF6\u7684\u4EFB\u52A1",
21452
+ "\u9700\u8981\u6267\u884C\u547D\u4EE4\u7684\u4EFB\u52A1",
21453
+ "\u9700\u8981\u59D4\u6D3E\u5B50\u4EFB\u52A1\u7684\u590D\u6742\u573A\u666F"
21454
+ ],
21455
+ promptAlias: "BTW \u987E\u95EE",
21456
+ keyTrigger: "\u8F7B\u91CF\u53EA\u8BFB\u54A8\u8BE2\u2192\u4F7F\u7528 BTW \u987E\u95EE"
21457
+ };
21458
+
21282
21459
  // src/agents/utils.ts
21283
21460
  init_shared();
21284
21461
  init_constants();
@@ -22684,7 +22861,7 @@ async function discoverOpencodeProjectSkills() {
22684
22861
 
22685
22862
  // src/features/opencode-skill-loader/skill-content.ts
22686
22863
  init_frontmatter();
22687
- import { readFileSync as readFileSync15 } from "fs";
22864
+ import { readFileSync as readFileSync16 } from "fs";
22688
22865
  var cachedSkills = null;
22689
22866
  async function getAllSkills() {
22690
22867
  if (cachedSkills)
@@ -22717,7 +22894,7 @@ async function getAllSkills() {
22717
22894
  }
22718
22895
  async function extractSkillTemplate(skill) {
22719
22896
  if (skill.path) {
22720
- const content = readFileSync15(skill.path, "utf-8");
22897
+ const content = readFileSync16(skill.path, "utf-8");
22721
22898
  const { body } = parseFrontmatter(content);
22722
22899
  return body.trim();
22723
22900
  }
@@ -22878,13 +23055,15 @@ var agentSources = {
22878
23055
  "\u5A92\u4F53\u89E3\u6790": createMultimodalLookerAgent,
22879
23056
  "\u9884\u5BA1\u987E\u95EE": createMetisAgent,
22880
23057
  "\u8BA1\u5212\u5BA1\u67E5": createMomusAgent,
22881
- "\u4EFB\u52A1\u7F16\u6392": createAtlasAgent
23058
+ "\u4EFB\u52A1\u7F16\u6392": createAtlasAgent,
23059
+ "BTW \u987E\u95EE": createBtwAdvisorAgent
22882
23060
  };
22883
23061
  var agentMetadata = {
22884
23062
  "\u6280\u672F\u53C2\u8C0B": ORACLE_PROMPT_METADATA,
22885
23063
  "\u77E5\u8BC6\u5178\u85CF": LIBRARIAN_PROMPT_METADATA,
22886
23064
  "\u6DF1\u5EA6\u63A2\u7D22": EXPLORE_PROMPT_METADATA,
22887
- "\u5A92\u4F53\u89E3\u6790": MULTIMODAL_LOOKER_PROMPT_METADATA
23065
+ "\u5A92\u4F53\u89E3\u6790": MULTIMODAL_LOOKER_PROMPT_METADATA,
23066
+ "BTW \u987E\u95EE": btwAdvisorPromptMetadata
22888
23067
  };
22889
23068
  function isFactory(source) {
22890
23069
  return typeof source === "function";
@@ -22960,7 +23139,7 @@ function mapScopeToLocation(scope) {
22960
23139
  return "project";
22961
23140
  return "plugin";
22962
23141
  }
22963
- async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory, systemDefaultModel, categories, gitMasterConfig, discoveredSkills = [], client) {
23142
+ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory, systemDefaultModel, categories, gitMasterConfig, discoveredSkills = [], client, config) {
22964
23143
  if (!systemDefaultModel) {
22965
23144
  throw new Error("createBuiltinAgents requires systemDefaultModel");
22966
23145
  }
@@ -23001,25 +23180,25 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
23001
23180
  availableModels,
23002
23181
  systemDefaultModel
23003
23182
  });
23004
- let config = buildAgent(source, model, mergedCategories, gitMasterConfig);
23183
+ let config2 = buildAgent(source, model, mergedCategories, gitMasterConfig);
23005
23184
  if (override?.variant) {
23006
- config = { ...config, variant: override.variant };
23185
+ config2 = { ...config2, variant: override.variant };
23007
23186
  } else if (resolvedVariant) {
23008
- config = { ...config, variant: resolvedVariant };
23187
+ config2 = { ...config2, variant: resolvedVariant };
23009
23188
  }
23010
- if (agentName === "\u77E5\u8BC6\u5178\u85CF" && directory && config.prompt) {
23189
+ if (agentName === "\u77E5\u8BC6\u5178\u85CF" && directory && config2.prompt) {
23011
23190
  const envContext = createEnvContext();
23012
- config = { ...config, prompt: config.prompt + envContext };
23191
+ config2 = { ...config2, prompt: config2.prompt + envContext };
23013
23192
  }
23014
23193
  if (override) {
23015
- config = mergeAgentConfig(config, override);
23194
+ config2 = mergeAgentConfig(config2, override);
23016
23195
  }
23017
- result[name] = config;
23196
+ result[name] = config2;
23018
23197
  const metadata = agentMetadata[agentName];
23019
23198
  if (metadata) {
23020
23199
  availableAgents.push({
23021
23200
  name: agentName,
23022
- description: config.description ?? "",
23201
+ description: config2.description ?? "",
23023
23202
  metadata
23024
23203
  });
23025
23204
  }
@@ -23094,22 +23273,23 @@ async function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, dir
23094
23273
  agent.prompt = identityAnchor + agent.prompt;
23095
23274
  }
23096
23275
  }
23097
- const MAIN_AGENT_NAMES = new Set(["\u4E3B\u6267\u884C\u5B98", "\u4EFB\u52A1\u7F16\u6392"]);
23098
- for (const name of Object.keys(result)) {
23099
- const agent = result[name];
23100
- if (agent.prompt) {
23101
- if (isDeepseekModel(agent.model ?? "")) {
23102
- agent.prompt += ZH_LANGUAGE_INSTRUCTION_DEEPSEEK;
23103
- } else {
23104
- agent.prompt += MAIN_AGENT_NAMES.has(name) ? ZH_LANGUAGE_INSTRUCTION : ZH_SUB_AGENT_INSTRUCTION;
23276
+ if (isLanguageEnforcementEnabled(config)) {
23277
+ const MAIN_AGENT_NAMES = new Set(["\u4E3B\u6267\u884C\u5B98", "\u4EFB\u52A1\u7F16\u6392"]);
23278
+ for (const name of Object.keys(result)) {
23279
+ const agent = result[name];
23280
+ if (agent.prompt) {
23281
+ if (isDeepseekModel(agent.model ?? "")) {
23282
+ agent.prompt += ZH_LANGUAGE_INSTRUCTION_DEEPSEEK;
23283
+ } else {
23284
+ agent.prompt += MAIN_AGENT_NAMES.has(name) ? ZH_LANGUAGE_INSTRUCTION : ZH_SUB_AGENT_INSTRUCTION;
23285
+ }
23105
23286
  }
23106
23287
  }
23107
23288
  }
23108
23289
  return result;
23109
23290
  }
23110
-
23111
23291
  // src/hooks/compaction-context-injector/index.ts
23112
- var SUMMARIZE_CONTEXT_PROMPT = `${createSystemDirective(SystemDirectiveTypes.COMPACTION_CONTEXT)}
23292
+ var BASE_SUMMARIZE_CONTEXT = `${createSystemDirective(SystemDirectiveTypes.COMPACTION_CONTEXT)}
23113
23293
 
23114
23294
  \u5728\u603B\u7ED3\u6B64\u4F1A\u8BDD\u65F6\uFF0C\u4F60\u5FC5\u987B\u5728\u603B\u7ED3\u4E2D\u5305\u542B\u4EE5\u4E0B\u90E8\u5206\uFF1A
23115
23295
 
@@ -23139,14 +23319,22 @@ var SUMMARIZE_CONTEXT_PROMPT = `${createSystemDirective(SystemDirectiveTypes.COM
23139
23319
  - \u4F1A\u8BDD\u4E2D\u53D1\u73B0\u7684\u53CD\u6A21\u5F0F
23140
23320
 
23141
23321
  \u6B64\u4E0A\u4E0B\u6587\u5BF9\u4E8E\u5728\u538B\u7F29\u540E\u4FDD\u6301\u8FDE\u7EED\u6027\u81F3\u5173\u91CD\u8981\u3002
23142
-
23322
+ `;
23323
+ function buildSummarizeContextPrompt(config) {
23324
+ if (isLanguageEnforcementEnabled(config)) {
23325
+ return BASE_SUMMARIZE_CONTEXT + `
23143
23326
  ${ZH_SUB_AGENT_INSTRUCTION}
23144
23327
  `;
23145
- function createCompactionContextInjector(_client) {
23328
+ }
23329
+ return BASE_SUMMARIZE_CONTEXT;
23330
+ }
23331
+ var SUMMARIZE_CONTEXT_PROMPT = buildSummarizeContextPrompt();
23332
+ function createCompactionContextInjector(_client, config) {
23333
+ const contextPrompt = buildSummarizeContextPrompt(config);
23146
23334
  return async (ctx) => {
23147
23335
  log("[compaction-context-injector] injecting context", { sessionID: ctx.sessionID });
23148
23336
  const agentName = getSessionAgent(ctx.sessionID) ?? "general";
23149
- const success = injectHookMessage(ctx.sessionID, SUMMARIZE_CONTEXT_PROMPT, {
23337
+ const success = injectHookMessage(ctx.sessionID, contextPrompt, {
23150
23338
  agent: agentName,
23151
23339
  model: { providerID: ctx.providerID, modelID: ctx.modelID },
23152
23340
  path: { cwd: ctx.directory }
@@ -24530,7 +24718,7 @@ ${result.message}`;
24530
24718
  };
24531
24719
  }
24532
24720
  // src/hooks/rules-injector/index.ts
24533
- import { readFileSync as readFileSync17 } from "fs";
24721
+ import { readFileSync as readFileSync18 } from "fs";
24534
24722
  import { homedir as homedir9 } from "os";
24535
24723
  import { relative as relative4, resolve as resolve5 } from "path";
24536
24724
 
@@ -24870,7 +25058,7 @@ function mergeGlobs(existing, newValue) {
24870
25058
  import {
24871
25059
  existsSync as existsSync26,
24872
25060
  mkdirSync as mkdirSync10,
24873
- readFileSync as readFileSync16,
25061
+ readFileSync as readFileSync17,
24874
25062
  writeFileSync as writeFileSync11,
24875
25063
  unlinkSync as unlinkSync6
24876
25064
  } from "fs";
@@ -24883,7 +25071,7 @@ function loadInjectedRules(sessionID) {
24883
25071
  if (!existsSync26(filePath))
24884
25072
  return { contentHashes: new Set, realPaths: new Set };
24885
25073
  try {
24886
- const content = readFileSync16(filePath, "utf-8");
25074
+ const content = readFileSync17(filePath, "utf-8");
24887
25075
  const data = JSON.parse(content);
24888
25076
  return {
24889
25077
  contentHashes: new Set(data.injectedHashes),
@@ -24945,7 +25133,7 @@ function createRulesInjectorHook(ctx) {
24945
25133
  if (isDuplicateByRealPath(candidate.realPath, cache2.realPaths))
24946
25134
  continue;
24947
25135
  try {
24948
- const rawContent = readFileSync17(candidate.path, "utf-8");
25136
+ const rawContent = readFileSync18(candidate.path, "utf-8");
24949
25137
  const { metadata, body } = parseRuleFrontmatter(rawContent);
24950
25138
  let matchReason;
24951
25139
  if (candidate.isSingleFile) {
@@ -25068,7 +25256,7 @@ init_auto_update_checker();
25068
25256
  import {
25069
25257
  existsSync as existsSync29,
25070
25258
  mkdirSync as mkdirSync11,
25071
- readFileSync as readFileSync20,
25259
+ readFileSync as readFileSync21,
25072
25260
  writeFileSync as writeFileSync14,
25073
25261
  unlinkSync as unlinkSync7
25074
25262
  } from "fs";
@@ -25131,7 +25319,7 @@ function loadAgentUsageState(sessionID) {
25131
25319
  if (!existsSync29(filePath))
25132
25320
  return null;
25133
25321
  try {
25134
- const content = readFileSync20(filePath, "utf-8");
25322
+ const content = readFileSync21(filePath, "utf-8");
25135
25323
  return JSON.parse(content);
25136
25324
  } catch {
25137
25325
  return null;
@@ -25220,7 +25408,7 @@ function createAgentUsageReminderHook(_ctx) {
25220
25408
  import {
25221
25409
  existsSync as existsSync30,
25222
25410
  mkdirSync as mkdirSync12,
25223
- readFileSync as readFileSync21,
25411
+ readFileSync as readFileSync22,
25224
25412
  writeFileSync as writeFileSync15,
25225
25413
  unlinkSync as unlinkSync8
25226
25414
  } from "fs";
@@ -25249,7 +25437,7 @@ function loadLanguageReminderState(sessionID) {
25249
25437
  if (!existsSync30(filePath))
25250
25438
  return null;
25251
25439
  try {
25252
- const content = readFileSync21(filePath, "utf-8");
25440
+ const content = readFileSync22(filePath, "utf-8");
25253
25441
  return JSON.parse(content);
25254
25442
  } catch {
25255
25443
  return null;
@@ -25270,14 +25458,6 @@ function clearLanguageReminderState(sessionID) {
25270
25458
  }
25271
25459
 
25272
25460
  // src/hooks/language-reminder/index.ts
25273
- function isEnglishText(text, threshold) {
25274
- const stripped = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "").replace(/https?:\/\/\S+/g, "").replace(/[A-Za-z]:[\\/]\S+|[./~]\S+\/\S+/g, "");
25275
- const meaningful = stripped.replace(/[\s\d\p{P}]/gu, "");
25276
- if (meaningful.length < 20)
25277
- return false;
25278
- const asciiLetters = (meaningful.match(/[a-zA-Z]/g) || []).length;
25279
- return asciiLetters / meaningful.length > threshold;
25280
- }
25281
25461
  function createLanguageReminderHook(ctx) {
25282
25462
  const sessionStates = new Map;
25283
25463
  const userMessageHistory = new Map;
@@ -25321,6 +25501,8 @@ function createLanguageReminderHook(ctx) {
25321
25501
  }
25322
25502
  }
25323
25503
  const toolExecuteAfter = async (input, output) => {
25504
+ if (!isLanguageEnforcementEnabled(config))
25505
+ return;
25324
25506
  const { sessionID } = input;
25325
25507
  const state2 = getOrCreateState(sessionID);
25326
25508
  if (state2.suspendedDueToUserEnglish)
@@ -25366,49 +25548,11 @@ function createLanguageReminderHook(ctx) {
25366
25548
  event: eventHandler
25367
25549
  };
25368
25550
  }
25369
- // src/hooks/thinking-language-validator/detector.ts
25370
- function detectEnglishViolation(text, threshold = 0.6) {
25371
- let stripped = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "").replace(/https?:\/\/\S+/g, "").replace(/[A-Za-z]:[\\/]\S+|[./~]\S+\/\S+/g, "");
25372
- stripped = stripped.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
25373
- const englishTriggers = [
25374
- "let me",
25375
- "i need",
25376
- "first,",
25377
- "i'll",
25378
- "i can",
25379
- "i should",
25380
- "the user",
25381
- "we need",
25382
- "let's",
25383
- "my task",
25384
- "i'm going",
25385
- "now,",
25386
- "now i",
25387
- "now we",
25388
- "next,",
25389
- "next i",
25390
- "then,",
25391
- "finally,"
25392
- ];
25393
- const lowerText = stripped.toLowerCase().trim();
25394
- if (lowerText.length > 0) {
25395
- for (const trigger of englishTriggers) {
25396
- if (lowerText.startsWith(trigger))
25397
- return "trigger";
25398
- }
25399
- }
25400
- const meaningful = stripped.replace(/[\s\d\p{P}]/gu, "");
25401
- if (meaningful.length < 4)
25402
- return false;
25403
- const asciiLetters = (meaningful.match(/[a-zA-Z]/g) || []).length;
25404
- return asciiLetters / meaningful.length > threshold ? "ascii" : false;
25405
- }
25406
-
25407
25551
  // src/hooks/thinking-language-validator/storage.ts
25408
25552
  import {
25409
25553
  existsSync as existsSync31,
25410
25554
  mkdirSync as mkdirSync13,
25411
- readFileSync as readFileSync22,
25555
+ readFileSync as readFileSync23,
25412
25556
  writeFileSync as writeFileSync16,
25413
25557
  unlinkSync as unlinkSync9
25414
25558
  } from "fs";
@@ -25434,7 +25578,7 @@ function loadThinkingValidatorState(sessionID) {
25434
25578
  if (!existsSync31(filePath))
25435
25579
  return null;
25436
25580
  try {
25437
- const content = readFileSync22(filePath, "utf-8");
25581
+ const content = readFileSync23(filePath, "utf-8");
25438
25582
  const parsed = JSON.parse(content);
25439
25583
  const state2 = {
25440
25584
  sessionID: parsed.sessionID ?? sessionID,
@@ -25507,6 +25651,8 @@ function createThinkingLanguageValidatorHook(ctx) {
25507
25651
  return hasher.digest("hex").slice(0, 16);
25508
25652
  }
25509
25653
  const toolExecuteAfter = async (input, output) => {
25654
+ if (!isLanguageEnforcementEnabled(config))
25655
+ return;
25510
25656
  const { sessionID } = input;
25511
25657
  const state2 = getOrCreateState(sessionID);
25512
25658
  if (state2.pendingViolationFingerprint) {
@@ -25554,7 +25700,7 @@ function createThinkingLanguageValidatorHook(ctx) {
25554
25700
  if (!thinkingText || thinkingText.length < 4)
25555
25701
  return;
25556
25702
  const state2 = getOrCreateState(sessionID);
25557
- if (state2.lastCheckedTextLength > 0 && thinkingText.length - state2.lastCheckedTextLength < 100) {
25703
+ if (state2.lastCheckedTextLength > 0 && thinkingText.length - state2.lastCheckedTextLength < 50) {
25558
25704
  state2.throttleSkipCount++;
25559
25705
  saveThinkingValidatorState(state2);
25560
25706
  return;
@@ -26072,7 +26218,7 @@ function createNonInteractiveEnvHook(_ctx) {
26072
26218
  import {
26073
26219
  existsSync as existsSync32,
26074
26220
  mkdirSync as mkdirSync14,
26075
- readFileSync as readFileSync23,
26221
+ readFileSync as readFileSync24,
26076
26222
  writeFileSync as writeFileSync17,
26077
26223
  unlinkSync as unlinkSync10
26078
26224
  } from "fs";
@@ -26101,7 +26247,7 @@ function loadInteractiveBashSessionState(sessionID) {
26101
26247
  if (!existsSync32(filePath))
26102
26248
  return null;
26103
26249
  try {
26104
- const content = readFileSync23(filePath, "utf-8");
26250
+ const content = readFileSync24(filePath, "utf-8");
26105
26251
  const serialized = JSON.parse(content);
26106
26252
  return {
26107
26253
  sessionID: serialized.sessionID,
@@ -26390,12 +26536,12 @@ function createThinkingBlockValidatorHook() {
26390
26536
  // src/hooks/ralph-loop/index.ts
26391
26537
  init_logger();
26392
26538
  init_system_directive();
26393
- import { existsSync as existsSync34, readFileSync as readFileSync25, readdirSync as readdirSync8 } from "fs";
26539
+ import { existsSync as existsSync34, readFileSync as readFileSync26, readdirSync as readdirSync8 } from "fs";
26394
26540
  import { join as join46 } from "path";
26395
26541
 
26396
26542
  // src/hooks/ralph-loop/storage.ts
26397
26543
  init_frontmatter();
26398
- import { existsSync as existsSync33, readFileSync as readFileSync24, writeFileSync as writeFileSync18, unlinkSync as unlinkSync11, mkdirSync as mkdirSync15 } from "fs";
26544
+ import { existsSync as existsSync33, readFileSync as readFileSync25, writeFileSync as writeFileSync18, unlinkSync as unlinkSync11, mkdirSync as mkdirSync15 } from "fs";
26399
26545
  import { dirname as dirname6, join as join45 } from "path";
26400
26546
 
26401
26547
  // src/hooks/ralph-loop/constants.ts
@@ -26414,7 +26560,7 @@ function readState(directory, customPath) {
26414
26560
  return null;
26415
26561
  }
26416
26562
  try {
26417
- const content = readFileSync24(filePath, "utf-8");
26563
+ const content = readFileSync25(filePath, "utf-8");
26418
26564
  const { data, body } = parseFrontmatter(content);
26419
26565
  const active = data.active;
26420
26566
  const iteration = data.iteration;
@@ -26541,7 +26687,7 @@ function createRalphLoopHook(ctx, options) {
26541
26687
  try {
26542
26688
  if (!existsSync34(transcriptPath))
26543
26689
  return false;
26544
- const content = readFileSync25(transcriptPath, "utf-8");
26690
+ const content = readFileSync26(transcriptPath, "utf-8");
26545
26691
  const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
26546
26692
  const lines = content.split(`
26547
26693
  `).filter((l) => l.trim());
@@ -26855,12 +27001,12 @@ function extractPromptText3(parts) {
26855
27001
  // src/hooks/auto-slash-command/executor.ts
26856
27002
  init_shared();
26857
27003
  init_file_utils();
26858
- import { existsSync as existsSync36, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
27004
+ import { existsSync as existsSync36, readdirSync as readdirSync9, readFileSync as readFileSync28 } from "fs";
26859
27005
  import { join as join47, basename as basename2, dirname as dirname8 } from "path";
26860
27006
  // src/features/opencode-skill-loader/merger.ts
26861
27007
  init_frontmatter();
26862
27008
  init_deep_merge();
26863
- import { readFileSync as readFileSync26, existsSync as existsSync35 } from "fs";
27009
+ import { readFileSync as readFileSync27, existsSync as existsSync35 } from "fs";
26864
27010
  import { dirname as dirname7, resolve as resolve6, isAbsolute as isAbsolute2 } from "path";
26865
27011
  import { homedir as homedir12 } from "os";
26866
27012
  import { createHash as createHash2 } from "crypto";
@@ -26911,7 +27057,7 @@ function loadSkillFromFile(filePath) {
26911
27057
  try {
26912
27058
  if (!existsSync35(filePath))
26913
27059
  return null;
26914
- const content = readFileSync26(filePath, "utf-8");
27060
+ const content = readFileSync27(filePath, "utf-8");
26915
27061
  const { data, body } = parseFrontmatter(content);
26916
27062
  return { template: body, metadata: data };
26917
27063
  } catch {
@@ -27120,7 +27266,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
27120
27266
  const commandPath = join47(commandsDir, entry.name);
27121
27267
  const commandName = basename2(entry.name, ".md");
27122
27268
  try {
27123
- const content = readFileSync27(commandPath, "utf-8");
27269
+ const content = readFileSync28(commandPath, "utf-8");
27124
27270
  const { data, body } = parseFrontmatter(content);
27125
27271
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
27126
27272
  const metadata = {
@@ -27679,7 +27825,7 @@ var NOTEPAD_DIR = "notepads";
27679
27825
  var NOTEPAD_BASE_PATH = `${BOULDER_DIR}/${NOTEPAD_DIR}`;
27680
27826
  var PROMETHEUS_PLANS_DIR = ".sisyphus/plans";
27681
27827
  // src/features/boulder-state/storage.ts
27682
- import { existsSync as existsSync37, readFileSync as readFileSync28, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, readdirSync as readdirSync10, renameSync as renameSync2, unlinkSync as unlinkSync12, statSync as statSync4 } from "fs";
27828
+ import { existsSync as existsSync37, readFileSync as readFileSync29, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, readdirSync as readdirSync10, renameSync as renameSync2, unlinkSync as unlinkSync12, statSync as statSync4 } from "fs";
27683
27829
  import { dirname as dirname9, join as join49, basename as basename3 } from "path";
27684
27830
  init_logger();
27685
27831
 
@@ -27796,7 +27942,7 @@ function readBoulderState(directory) {
27796
27942
  return null;
27797
27943
  }
27798
27944
  try {
27799
- const content = readFileSync28(filePath, "utf-8");
27945
+ const content = readFileSync29(filePath, "utf-8");
27800
27946
  const state2 = JSON.parse(content);
27801
27947
  if (state2.version === undefined || state2.version === null) {
27802
27948
  state2.version = 0;
@@ -27834,7 +27980,7 @@ function backupBoulderState(directory) {
27834
27980
  const dir = dirname9(filePath);
27835
27981
  const timestamp = Date.now();
27836
27982
  const backupPath = join49(dir, `${BACKUP_PREFIX}${timestamp}`);
27837
- writeFileSync19(backupPath, readFileSync28(filePath, "utf-8"), "utf-8");
27983
+ writeFileSync19(backupPath, readFileSync29(filePath, "utf-8"), "utf-8");
27838
27984
  log(`Created boulder state backup: ${backupPath}`);
27839
27985
  cleanupOldBackups(dir);
27840
27986
  return true;
@@ -27874,7 +28020,7 @@ function restoreBoulderState(directory) {
27874
28020
  return null;
27875
28021
  }
27876
28022
  const latestBackup = backupFiles[0];
27877
- const content = readFileSync28(latestBackup.path, "utf-8");
28023
+ const content = readFileSync29(latestBackup.path, "utf-8");
27878
28024
  const state2 = JSON.parse(content);
27879
28025
  const filePath = getBoulderFilePath(directory);
27880
28026
  writeFileSync19(filePath, content, "utf-8");
@@ -27940,6 +28086,15 @@ function updateBoulderStateCompleted(directory, taskIndex) {
27940
28086
  state: state2
27941
28087
  });
27942
28088
  }
28089
+ function markBoulderPlanCompleted(directory, completedAt) {
28090
+ const state2 = readBoulderState(directory);
28091
+ if (!state2)
28092
+ return false;
28093
+ state2.status = "completed";
28094
+ state2.completed_at = completedAt;
28095
+ state2.version = (state2.version || 0) + 1;
28096
+ return writeBoulderState(directory, state2);
28097
+ }
27943
28098
  function clearBoulderState(directory) {
27944
28099
  const filePath = getBoulderFilePath(directory);
27945
28100
  try {
@@ -27973,7 +28128,7 @@ function getPlanProgress(planPath) {
27973
28128
  return { total: 0, completed: 0, isComplete: false };
27974
28129
  }
27975
28130
  try {
27976
- const content = readFileSync28(planPath, "utf-8");
28131
+ const content = readFileSync29(planPath, "utf-8");
27977
28132
  if (!content.trim()) {
27978
28133
  return { total: 0, completed: 0, isComplete: false };
27979
28134
  }
@@ -28328,7 +28483,7 @@ ${contextInfo}`;
28328
28483
  }
28329
28484
  // src/hooks/atlas/index.ts
28330
28485
  import { execSync } from "child_process";
28331
- import { existsSync as existsSync38, openSync, closeSync, unlinkSync as unlinkSync13, readdirSync as readdirSync11, readFileSync as readFileSync29, renameSync as renameSync3, writeFileSync as writeFileSync20 } from "fs";
28486
+ import { existsSync as existsSync38, openSync, closeSync, unlinkSync as unlinkSync13, readdirSync as readdirSync11, readFileSync as readFileSync30, renameSync as renameSync3, writeFileSync as writeFileSync20 } from "fs";
28332
28487
  import { join as join51 } from "path";
28333
28488
  init_logger();
28334
28489
  init_system_directive();
@@ -28550,7 +28705,7 @@ function syncPlanFileFromBoulder(directory, boulderState) {
28550
28705
  }
28551
28706
  const planPath = boulderState.active_plan;
28552
28707
  try {
28553
- const planContent = readFileSync29(planPath, "utf-8");
28708
+ const planContent = readFileSync30(planPath, "utf-8");
28554
28709
  const checkboxes = parseCheckboxes(planContent);
28555
28710
  const lines = planContent.split(`
28556
28711
  `);
@@ -28960,6 +29115,15 @@ function createAtlasHook(ctx, options) {
28960
29115
  const progress = getPlanProgress(boulderState.active_plan);
28961
29116
  if (progress.isComplete) {
28962
29117
  log(`[${HOOK_NAME6}] Boulder complete`, { sessionID, plan: boulderState.plan_name });
29118
+ markBoulderPlanCompleted(ctx.directory, new Date().toISOString());
29119
+ boulderEventBus.emit("plan_completed", {
29120
+ directory: ctx.directory,
29121
+ planName: boulderState.plan_name,
29122
+ planPath: boulderState.active_plan,
29123
+ boulderState,
29124
+ progress,
29125
+ client: ctx.client
29126
+ });
28963
29127
  return;
28964
29128
  }
28965
29129
  const now = Date.now();
@@ -29133,7 +29297,7 @@ function createAtlasHook(ctx, options) {
29133
29297
  const taskDescription = extractTaskFromPrompt(savedPrompt || "");
29134
29298
  if (taskDescription) {
29135
29299
  try {
29136
- const planContent = readFileSync29(boulderState.active_plan, "utf-8");
29300
+ const planContent = readFileSync30(boulderState.active_plan, "utf-8");
29137
29301
  const checkboxes = parseCheckboxes(planContent);
29138
29302
  const checkboxIndex = findMatchingCheckbox(taskDescription, checkboxes);
29139
29303
  if (checkboxIndex >= 0) {
@@ -30658,6 +30822,13 @@ var DEFAULT_AGENT_PERMISSIONS = {
30658
30822
  canDelegateTasks: false,
30659
30823
  allowedCategories: []
30660
30824
  },
30825
+ "BTW \u987E\u95EE": {
30826
+ canReadFiles: true,
30827
+ canWriteFiles: false,
30828
+ canExecuteCommands: false,
30829
+ canDelegateTasks: false,
30830
+ allowedCategories: []
30831
+ },
30661
30832
  "\u4E3B\u6267\u884C\u5B98": {
30662
30833
  canReadFiles: true,
30663
30834
  canWriteFiles: true,
@@ -30801,6 +30972,273 @@ function createShellEnvInjectorHook(_ctx) {
30801
30972
  }
30802
30973
  };
30803
30974
  }
30975
+ // src/hooks/plan-completion/index.ts
30976
+ init_logger();
30977
+ init_frontmatter();
30978
+ import { existsSync as existsSync39, readFileSync as readFileSync31, writeFileSync as writeFileSync21, mkdirSync as mkdirSync17, renameSync as renameSync4, unlinkSync as unlinkSync14 } from "fs";
30979
+ import { join as join52 } from "path";
30980
+ async function safeExecute(fn, name, onAction) {
30981
+ try {
30982
+ await fn();
30983
+ onAction?.(name);
30984
+ log(`[plan-completion] Action succeeded: ${name}`);
30985
+ } catch (err) {
30986
+ onAction?.(name);
30987
+ log(`[plan-completion] Action failed: ${name}`, { error: String(err) });
30988
+ }
30989
+ }
30990
+ function formatDuration(startIso, endIso) {
30991
+ if (!startIso || !endIso)
30992
+ return "\u672A\u77E5";
30993
+ try {
30994
+ const start = new Date(startIso).getTime();
30995
+ const end = new Date(endIso).getTime();
30996
+ const diffMs = end - start;
30997
+ if (isNaN(diffMs) || diffMs < 0)
30998
+ return "\u672A\u77E5";
30999
+ const hours = Math.floor(diffMs / (1000 * 60 * 60));
31000
+ const minutes = Math.floor(diffMs % (1000 * 60 * 60) / (1000 * 60));
31001
+ if (hours > 0) {
31002
+ return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
31003
+ }
31004
+ return `${minutes}\u5206\u949F`;
31005
+ } catch {
31006
+ return "\u672A\u77E5";
31007
+ }
31008
+ }
31009
+ function formatTimestamp(iso) {
31010
+ if (!iso)
31011
+ return "\u672A\u77E5";
31012
+ try {
31013
+ return new Date(iso).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
31014
+ } catch {
31015
+ return iso || "\u672A\u77E5";
31016
+ }
31017
+ }
31018
+ function countListItems(content) {
31019
+ const matches = content.match(/^\s*[-*]\s+.+$/gm);
31020
+ return matches ? matches.length : 0;
31021
+ }
31022
+ async function updatePlanSummary(data) {
31023
+ log(`[plan-completion] updatePlanSummary: ${data.planName}`);
31024
+ if (!existsSync39(data.planPath)) {
31025
+ log(`[plan-completion] Plan file not found: ${data.planPath}`);
31026
+ return;
31027
+ }
31028
+ const content = readFileSync31(data.planPath, "utf-8");
31029
+ const startedAt = data.boulderState?.started_at || "";
31030
+ const completedAt = data.boulderState?.completed_at || "";
31031
+ const duration = formatDuration(startedAt, completedAt);
31032
+ const totalTasks = data.progress?.total || 0;
31033
+ const completedTasks = data.progress?.completed || 0;
31034
+ const failedTasks = totalTasks - completedTasks;
31035
+ const summary = `
31036
+
31037
+ ---
31038
+
31039
+ ## \u5B8C\u6210\u6458\u8981
31040
+
31041
+ | \u5B57\u6BB5 | \u503C |
31042
+ |------|-----|
31043
+ | \u5F00\u59CB\u65F6\u95F4 | ${formatTimestamp(startedAt)} |
31044
+ | \u7ED3\u675F\u65F6\u95F4 | ${formatTimestamp(completedAt)} |
31045
+ | \u603B\u8017\u65F6 | ${duration} |
31046
+ | \u5B8C\u6210\u4EFB\u52A1\u6570 | ${completedTasks} |
31047
+ | \u5931\u8D25\u4EFB\u52A1\u6570 | ${failedTasks} |
31048
+ `;
31049
+ writeFileSync21(data.planPath, content + summary, "utf-8");
31050
+ log(`[plan-completion] Plan summary appended to ${data.planPath}`);
31051
+ }
31052
+ async function archivePlan(data) {
31053
+ log(`[plan-completion] archivePlan: ${data.planName}`);
31054
+ const archivedDir = join52(data.directory, ".sisyphus", "archived");
31055
+ if (!existsSync39(archivedDir)) {
31056
+ mkdirSync17(archivedDir, { recursive: true });
31057
+ }
31058
+ if (existsSync39(data.planPath)) {
31059
+ const archivedPath = join52(archivedDir, `${data.planName}.md`);
31060
+ renameSync4(data.planPath, archivedPath);
31061
+ log(`[plan-completion] Plan archived to ${archivedPath}`);
31062
+ const boulderState = readBoulderState(data.directory);
31063
+ if (boulderState) {
31064
+ boulderState.active_plan = archivedPath;
31065
+ writeBoulderState(data.directory, boulderState);
31066
+ log(`[plan-completion] Updated boulder.json active_plan to ${archivedPath}`);
31067
+ }
31068
+ }
31069
+ const draftPath = join52(data.directory, ".sisyphus", "drafts", `${data.planName}.md`);
31070
+ if (existsSync39(draftPath)) {
31071
+ unlinkSync14(draftPath);
31072
+ log(`[plan-completion] Deleted draft file: ${draftPath}`);
31073
+ }
31074
+ }
31075
+ async function extractLearnings(data) {
31076
+ log(`[plan-completion] extractLearnings: ${data.planName}`);
31077
+ const notepadDir = join52(data.directory, ".sisyphus", "notepads", data.planName);
31078
+ if (!existsSync39(notepadDir)) {
31079
+ log(`[plan-completion] Notepad directory not found: ${notepadDir}`);
31080
+ return;
31081
+ }
31082
+ const files = ["learnings.md", "issues.md", "problems.md", "decisions.md", "match-failures.md"];
31083
+ const contents = {};
31084
+ let totalLearnings = 0;
31085
+ let totalIssues = 0;
31086
+ let totalDecisions = 0;
31087
+ for (const file of files) {
31088
+ const filePath = join52(notepadDir, file);
31089
+ if (existsSync39(filePath)) {
31090
+ const content = readFileSync31(filePath, "utf-8");
31091
+ contents[file] = content;
31092
+ const count = countListItems(content);
31093
+ if (file === "learnings.md")
31094
+ totalLearnings = count;
31095
+ else if (file === "issues.md" || file === "problems.md")
31096
+ totalIssues += count;
31097
+ else if (file === "decisions.md")
31098
+ totalDecisions = count;
31099
+ }
31100
+ }
31101
+ const learningsDir = join52(data.directory, ".sisyphus", "learnings");
31102
+ if (!existsSync39(learningsDir)) {
31103
+ mkdirSync17(learningsDir, { recursive: true });
31104
+ }
31105
+ const output = `# \u7ECF\u9A8C\u6559\u8BAD\u603B\u7ED3: ${data.planName}
31106
+
31107
+ ## \u7EDF\u8BA1
31108
+
31109
+ - \u5B66\u4E60\u70B9: ${totalLearnings}
31110
+ - \u95EE\u9898: ${totalIssues}
31111
+ - \u51B3\u7B56: ${totalDecisions}
31112
+
31113
+ ## \u5B66\u4E60\u70B9
31114
+
31115
+ ${contents["learnings.md"] || "\u65E0"}
31116
+
31117
+ ## \u95EE\u9898
31118
+
31119
+ ${contents["issues.md"] || "\u65E0"}
31120
+ ${contents["problems.md"] || ""}
31121
+
31122
+ ## \u51B3\u7B56
31123
+
31124
+ ${contents["decisions.md"] || "\u65E0"}
31125
+
31126
+ ## \u5339\u914D\u5931\u8D25
31127
+
31128
+ ${contents["match-failures.md"] || "\u65E0"}
31129
+ `;
31130
+ const outputPath = join52(learningsDir, `${data.planName}.md`);
31131
+ writeFileSync21(outputPath, output, "utf-8");
31132
+ log(`[plan-completion] Learnings saved to ${outputPath}`);
31133
+ if (data.client?.tui?.showToast) {
31134
+ data.client.tui.showToast({
31135
+ body: {
31136
+ title: "\u7ECF\u9A8C\u6559\u8BAD\u603B\u7ED3",
31137
+ message: `\u5B66\u4E60\u70B9: ${totalLearnings}, \u95EE\u9898: ${totalIssues}, \u51B3\u7B56: ${totalDecisions}`,
31138
+ variant: "info",
31139
+ duration: 5000
31140
+ }
31141
+ });
31142
+ }
31143
+ if (data.client?.session?.prompt) {
31144
+ const summary = `\u7ECF\u9A8C\u6559\u8BAD\u603B\u7ED3\u5DF2\u5B8C\u6210\u3002\u5B66\u4E60\u70B9: ${totalLearnings}, \u95EE\u9898: ${totalIssues}, \u51B3\u7B56: ${totalDecisions}\u3002\u5B8C\u6574\u5185\u5BB9\u5DF2\u4FDD\u5B58\u5230 ${outputPath}`;
31145
+ await data.client.session.prompt(summary);
31146
+ }
31147
+ }
31148
+ async function generateReport(data) {
31149
+ log(`[plan-completion] generateReport: ${data.planName}`);
31150
+ const reportsDir = join52(data.directory, ".sisyphus", "reports");
31151
+ if (!existsSync39(reportsDir)) {
31152
+ mkdirSync17(reportsDir, { recursive: true });
31153
+ }
31154
+ const startedAt = data.boulderState?.started_at || "";
31155
+ const completedAt = data.boulderState?.completed_at || "";
31156
+ const duration = formatDuration(startedAt, completedAt);
31157
+ const totalTasks = data.progress?.total || 0;
31158
+ const completedTasks = data.progress?.completed || 0;
31159
+ const failedTasks = totalTasks - completedTasks;
31160
+ const version = data.boulderState?.version || 0;
31161
+ const sessionCount = data.boulderState?.session_ids?.length || 0;
31162
+ const completedIndices = data.boulderState?.completed_task_indices || [];
31163
+ const notepadPath = join52(data.directory, ".sisyphus", "notepads", data.planName);
31164
+ const hasNotepad = existsSync39(notepadPath);
31165
+ const report = `# \u8BA1\u5212\u5B8C\u6210\u62A5\u544A
31166
+
31167
+ ## \u6982\u89C8
31168
+
31169
+ | \u5B57\u6BB5 | \u503C |
31170
+ |------|-----|
31171
+ | \u8BA1\u5212\u540D\u79F0 | ${data.planName} |
31172
+ | \u7248\u672C | ${version} |
31173
+ | Session \u6570\u91CF | ${sessionCount} |
31174
+ | \u72B6\u6001 | ${data.boulderState?.status || "\u672A\u77E5"} |
31175
+
31176
+ ## \u65F6\u95F4\u4FE1\u606F
31177
+
31178
+ | \u5B57\u6BB5 | \u503C |
31179
+ |------|-----|
31180
+ | \u5F00\u59CB\u65F6\u95F4 | ${formatTimestamp(startedAt)} |
31181
+ | \u7ED3\u675F\u65F6\u95F4 | ${formatTimestamp(completedAt)} |
31182
+ | \u603B\u8017\u65F6 | ${duration} |
31183
+
31184
+ ## \u4EFB\u52A1\u7EDF\u8BA1
31185
+
31186
+ | \u5B57\u6BB5 | \u503C |
31187
+ |------|-----|
31188
+ | \u603B\u4EFB\u52A1\u6570 | ${totalTasks} |
31189
+ | \u5DF2\u5B8C\u6210 | ${completedTasks} |
31190
+ | \u672A\u5B8C\u6210 | ${failedTasks} |
31191
+ | \u5B8C\u6210\u7387 | ${totalTasks > 0 ? Math.round(completedTasks / totalTasks * 100) : 0}% |
31192
+
31193
+ ## \u5B8C\u6210\u4EFB\u52A1\u7D22\u5F15
31194
+
31195
+ ${completedIndices.length > 0 ? completedIndices.map((i) => `- ${i}`).join(`
31196
+ `) : "\u65E0"}
31197
+
31198
+ ${hasNotepad ? `## \u8BB0\u4E8B\u672C
31199
+
31200
+ \u8BB0\u4E8B\u672C\u8DEF\u5F84: ${notepadPath}
31201
+ ` : ""}
31202
+ `;
31203
+ const reportPath = join52(reportsDir, `${data.planName}.md`);
31204
+ writeFileSync21(reportPath, report, "utf-8");
31205
+ log(`[plan-completion] Report generated at ${reportPath}`);
31206
+ }
31207
+ async function runInitDeep(data) {
31208
+ log(`[plan-completion] runInitDeep: ${data.planName}`);
31209
+ }
31210
+ async function promptGitCommit(data) {
31211
+ log(`[plan-completion] promptGitCommit: ${data.planName}`);
31212
+ }
31213
+ function createPlanCompletionHook(_ctx) {
31214
+ let onAction;
31215
+ const unsubscribe = boulderEventBus.on("plan_completed", async (data) => {
31216
+ log(`[plan-completion] Plan completed: ${data.planName}`);
31217
+ const actionsList = parseCompletionActions(data.planPath);
31218
+ const isEnabled = (type) => {
31219
+ const action = actionsList.find((a) => a.type === type);
31220
+ return action ? action.enabled : false;
31221
+ };
31222
+ await safeExecute(async () => updatePlanSummary(data), "update_summary", onAction);
31223
+ await safeExecute(async () => archivePlan(data), "archive", onAction);
31224
+ await safeExecute(async () => extractLearnings(data), "extract_learnings", onAction);
31225
+ await safeExecute(async () => generateReport(data), "generate_report", onAction);
31226
+ if (isEnabled("init_deep")) {
31227
+ await safeExecute(async () => runInitDeep(data), "init_deep", onAction);
31228
+ }
31229
+ if (isEnabled("git_commit")) {
31230
+ await safeExecute(async () => promptGitCommit(data), "git_commit", onAction);
31231
+ }
31232
+ });
31233
+ return {
31234
+ cleanup: () => {
31235
+ unsubscribe();
31236
+ },
31237
+ setOnActionCallback: (cb) => {
31238
+ onAction = cb;
31239
+ }
31240
+ };
31241
+ }
30804
31242
  // src/features/context-injector/collector.ts
30805
31243
  var PRIORITY_ORDER = {
30806
31244
  critical: 0,
@@ -30974,8 +31412,8 @@ function createFirstMessageVariantGate() {
30974
31412
  }
30975
31413
  // src/features/claude-code-mcp-loader/loader.ts
30976
31414
  init_shared();
30977
- import { existsSync as existsSync39, readFileSync as readFileSync30 } from "fs";
30978
- import { join as join52 } from "path";
31415
+ import { existsSync as existsSync40, readFileSync as readFileSync32 } from "fs";
31416
+ import { join as join53 } from "path";
30979
31417
 
30980
31418
  // src/features/claude-code-mcp-loader/env-expander.ts
30981
31419
  function expandEnvVars(value) {
@@ -31045,13 +31483,13 @@ function getMcpConfigPaths() {
31045
31483
  const claudeConfigDir = getClaudeConfigDir();
31046
31484
  const cwd2 = process.cwd();
31047
31485
  return [
31048
- { path: join52(claudeConfigDir, ".mcp.json"), scope: "user" },
31049
- { path: join52(cwd2, ".mcp.json"), scope: "project" },
31050
- { path: join52(cwd2, ".claude", ".mcp.json"), scope: "local" }
31486
+ { path: join53(claudeConfigDir, ".mcp.json"), scope: "user" },
31487
+ { path: join53(cwd2, ".mcp.json"), scope: "project" },
31488
+ { path: join53(cwd2, ".claude", ".mcp.json"), scope: "local" }
31051
31489
  ];
31052
31490
  }
31053
31491
  async function loadMcpConfigFile(filePath) {
31054
- if (!existsSync39(filePath)) {
31492
+ if (!existsSync40(filePath)) {
31055
31493
  return null;
31056
31494
  }
31057
31495
  try {
@@ -31066,10 +31504,10 @@ function getSystemMcpServerNames() {
31066
31504
  const names = new Set;
31067
31505
  const paths = getMcpConfigPaths();
31068
31506
  for (const { path: path7 } of paths) {
31069
- if (!existsSync39(path7))
31507
+ if (!existsSync40(path7))
31070
31508
  continue;
31071
31509
  try {
31072
- const content = readFileSync30(path7, "utf-8");
31510
+ const content = readFileSync32(path7, "utf-8");
31073
31511
  const config = JSON.parse(content);
31074
31512
  if (!config?.mcpServers)
31075
31513
  continue;
@@ -31120,27 +31558,27 @@ async function loadMcpConfigs() {
31120
31558
  return { servers, loadedServers };
31121
31559
  }
31122
31560
  // src/features/claude-code-session-state/recovery.ts
31123
- import { existsSync as existsSync40, readdirSync as readdirSync12 } from "fs";
31124
- import { join as join53 } from "path";
31561
+ import { existsSync as existsSync41, readdirSync as readdirSync13 } from "fs";
31562
+ import { join as join54 } from "path";
31125
31563
  init_logger();
31126
31564
  function recoverSessionAgents() {
31127
31565
  let recoveredCount = 0;
31128
31566
  try {
31129
- if (!existsSync40(MESSAGE_STORAGE)) {
31567
+ if (!existsSync41(MESSAGE_STORAGE)) {
31130
31568
  log("[recovery] MESSAGE_STORAGE directory does not exist", { path: MESSAGE_STORAGE });
31131
31569
  return 0;
31132
31570
  }
31133
- const entries = readdirSync12(MESSAGE_STORAGE, { withFileTypes: true });
31571
+ const entries = readdirSync13(MESSAGE_STORAGE, { withFileTypes: true });
31134
31572
  for (const entry of entries) {
31135
31573
  if (!entry.isDirectory())
31136
31574
  continue;
31137
- const projectDir = join53(MESSAGE_STORAGE, entry.name);
31575
+ const projectDir = join54(MESSAGE_STORAGE, entry.name);
31138
31576
  try {
31139
- const sessionDirs = readdirSync12(projectDir, { withFileTypes: true });
31577
+ const sessionDirs = readdirSync13(projectDir, { withFileTypes: true });
31140
31578
  for (const sessionEntry of sessionDirs) {
31141
31579
  if (!sessionEntry.isDirectory())
31142
31580
  continue;
31143
- const sessionDir = join53(projectDir, sessionEntry.name);
31581
+ const sessionDir = join54(projectDir, sessionEntry.name);
31144
31582
  const sessionID = sessionEntry.name;
31145
31583
  try {
31146
31584
  const msg = findNearestAssistantMessage(sessionDir);
@@ -31566,14 +32004,14 @@ var EXT_TO_LANG = {
31566
32004
  ".gql": "graphql"
31567
32005
  };
31568
32006
  // src/tools/lsp/config.ts
31569
- import { existsSync as existsSync41, readFileSync as readFileSync31 } from "fs";
31570
- import { join as join54 } from "path";
32007
+ import { existsSync as existsSync42, readFileSync as readFileSync33 } from "fs";
32008
+ import { join as join55 } from "path";
31571
32009
  init_shared();
31572
32010
  function loadJsonFile(path7) {
31573
- if (!existsSync41(path7))
32011
+ if (!existsSync42(path7))
31574
32012
  return null;
31575
32013
  try {
31576
- return JSON.parse(readFileSync31(path7, "utf-8"));
32014
+ return JSON.parse(readFileSync33(path7, "utf-8"));
31577
32015
  } catch {
31578
32016
  return null;
31579
32017
  }
@@ -31582,9 +32020,9 @@ function getConfigPaths3() {
31582
32020
  const cwd2 = process.cwd();
31583
32021
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
31584
32022
  return {
31585
- project: join54(cwd2, ".opencode", "oh-my-opencode.json"),
31586
- user: join54(configDir, "oh-my-opencode.json"),
31587
- opencode: join54(configDir, "opencode.json")
32023
+ project: join55(cwd2, ".opencode", "oh-my-opencode.json"),
32024
+ user: join55(configDir, "oh-my-opencode.json"),
32025
+ opencode: join55(configDir, "opencode.json")
31588
32026
  };
31589
32027
  }
31590
32028
  function loadAllConfigs() {
@@ -31697,7 +32135,7 @@ function isServerInstalled(command) {
31697
32135
  return false;
31698
32136
  const cmd = command[0];
31699
32137
  if (cmd.includes("/") || cmd.includes("\\")) {
31700
- if (existsSync41(cmd))
32138
+ if (existsSync42(cmd))
31701
32139
  return true;
31702
32140
  }
31703
32141
  const isWindows2 = process.platform === "win32";
@@ -31719,23 +32157,23 @@ function isServerInstalled(command) {
31719
32157
  const paths = pathEnv.split(pathSeparator);
31720
32158
  for (const p of paths) {
31721
32159
  for (const suffix of exts) {
31722
- if (existsSync41(join54(p, cmd + suffix))) {
32160
+ if (existsSync42(join55(p, cmd + suffix))) {
31723
32161
  return true;
31724
32162
  }
31725
32163
  }
31726
32164
  }
31727
32165
  const cwd2 = process.cwd();
31728
32166
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
31729
- const dataDir = join54(getDataDir(), "opencode");
32167
+ const dataDir = join55(getDataDir(), "opencode");
31730
32168
  const additionalBases = [
31731
- join54(cwd2, "node_modules", ".bin"),
31732
- join54(configDir, "bin"),
31733
- join54(configDir, "node_modules", ".bin"),
31734
- join54(dataDir, "bin")
32169
+ join55(cwd2, "node_modules", ".bin"),
32170
+ join55(configDir, "bin"),
32171
+ join55(configDir, "node_modules", ".bin"),
32172
+ join55(dataDir, "bin")
31735
32173
  ];
31736
32174
  for (const base of additionalBases) {
31737
32175
  for (const suffix of exts) {
31738
- if (existsSync41(join54(base, cmd + suffix))) {
32176
+ if (existsSync42(join55(base, cmd + suffix))) {
31739
32177
  return true;
31740
32178
  }
31741
32179
  }
@@ -31747,7 +32185,7 @@ function isServerInstalled(command) {
31747
32185
  }
31748
32186
  // src/tools/lsp/client.ts
31749
32187
  var {spawn: spawn6 } = globalThis.Bun;
31750
- import { readFileSync as readFileSync32 } from "fs";
32188
+ import { readFileSync as readFileSync34 } from "fs";
31751
32189
  import { extname, resolve as resolve8 } from "path";
31752
32190
  import { pathToFileURL } from "url";
31753
32191
  class LSPServerManager {
@@ -32198,7 +32636,7 @@ ${msg}`);
32198
32636
  const absPath = resolve8(filePath);
32199
32637
  if (this.openedFiles.has(absPath))
32200
32638
  return;
32201
- const text = readFileSync32(absPath, "utf-8");
32639
+ const text = readFileSync34(absPath, "utf-8");
32202
32640
  const ext = extname(absPath);
32203
32641
  const languageId = getLanguageId(ext);
32204
32642
  this.notify("textDocument/didOpen", {
@@ -32288,17 +32726,17 @@ ${msg}`);
32288
32726
  // src/tools/lsp/utils.ts
32289
32727
  import { extname as extname2, resolve as resolve9 } from "path";
32290
32728
  import { fileURLToPath as fileURLToPath2 } from "url";
32291
- import { existsSync as existsSync42, readFileSync as readFileSync33, writeFileSync as writeFileSync21 } from "fs";
32729
+ import { existsSync as existsSync43, readFileSync as readFileSync35, writeFileSync as writeFileSync22 } from "fs";
32292
32730
  function findWorkspaceRoot(filePath) {
32293
32731
  let dir = resolve9(filePath);
32294
- if (!existsSync42(dir) || !__require("fs").statSync(dir).isDirectory()) {
32732
+ if (!existsSync43(dir) || !__require("fs").statSync(dir).isDirectory()) {
32295
32733
  dir = __require("path").dirname(dir);
32296
32734
  }
32297
32735
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
32298
32736
  let prevDir = "";
32299
32737
  while (dir !== prevDir) {
32300
32738
  for (const marker of markers) {
32301
- if (existsSync42(__require("path").join(dir, marker))) {
32739
+ if (existsSync43(__require("path").join(dir, marker))) {
32302
32740
  return dir;
32303
32741
  }
32304
32742
  }
@@ -32453,7 +32891,7 @@ function formatPrepareRenameResult(result) {
32453
32891
  }
32454
32892
  function applyTextEditsToFile(filePath, edits) {
32455
32893
  try {
32456
- let content = readFileSync33(filePath, "utf-8");
32894
+ let content = readFileSync35(filePath, "utf-8");
32457
32895
  const lines = content.split(`
32458
32896
  `);
32459
32897
  const sortedEdits = [...edits].sort((a, b) => {
@@ -32478,7 +32916,7 @@ function applyTextEditsToFile(filePath, edits) {
32478
32916
  `));
32479
32917
  }
32480
32918
  }
32481
- writeFileSync21(filePath, lines.join(`
32919
+ writeFileSync22(filePath, lines.join(`
32482
32920
  `), "utf-8");
32483
32921
  return { success: true, editCount: edits.length };
32484
32922
  } catch (err) {
@@ -32509,7 +32947,7 @@ function applyWorkspaceEdit(edit) {
32509
32947
  if (change.kind === "create") {
32510
32948
  try {
32511
32949
  const filePath = uriToPath(change.uri);
32512
- writeFileSync21(filePath, "", "utf-8");
32950
+ writeFileSync22(filePath, "", "utf-8");
32513
32951
  result.filesModified.push(filePath);
32514
32952
  } catch (err) {
32515
32953
  result.success = false;
@@ -32519,8 +32957,8 @@ function applyWorkspaceEdit(edit) {
32519
32957
  try {
32520
32958
  const oldPath = uriToPath(change.oldUri);
32521
32959
  const newPath = uriToPath(change.newUri);
32522
- const content = readFileSync33(oldPath, "utf-8");
32523
- writeFileSync21(newPath, content, "utf-8");
32960
+ const content = readFileSync35(oldPath, "utf-8");
32961
+ writeFileSync22(newPath, content, "utf-8");
32524
32962
  __require("fs").unlinkSync(oldPath);
32525
32963
  result.filesModified.push(newPath);
32526
32964
  } catch (err) {
@@ -45099,13 +45537,13 @@ var lsp_rename = tool({
45099
45537
  });
45100
45538
  // src/tools/ast-grep/constants.ts
45101
45539
  import { createRequire as createRequire4 } from "module";
45102
- import { dirname as dirname10, join as join56 } from "path";
45103
- import { existsSync as existsSync44, statSync as statSync5 } from "fs";
45540
+ import { dirname as dirname11, join as join57 } from "path";
45541
+ import { existsSync as existsSync45, statSync as statSync5 } from "fs";
45104
45542
 
45105
45543
  // src/tools/ast-grep/downloader.ts
45106
45544
  init_shared();
45107
- import { existsSync as existsSync43, mkdirSync as mkdirSync17, chmodSync as chmodSync2, unlinkSync as unlinkSync14 } from "fs";
45108
- import { join as join55 } from "path";
45545
+ import { existsSync as existsSync44, mkdirSync as mkdirSync18, chmodSync as chmodSync2, unlinkSync as unlinkSync15 } from "fs";
45546
+ import { join as join56 } from "path";
45109
45547
  import { homedir as homedir13 } from "os";
45110
45548
  import { createRequire as createRequire3 } from "module";
45111
45549
  var REPO2 = "ast-grep/ast-grep";
@@ -45131,19 +45569,19 @@ var PLATFORM_MAP2 = {
45131
45569
  function getCacheDir3() {
45132
45570
  if (process.platform === "win32") {
45133
45571
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
45134
- const base2 = localAppData || join55(homedir13(), "AppData", "Local");
45135
- return join55(base2, "oh-my-opencode", "bin");
45572
+ const base2 = localAppData || join56(homedir13(), "AppData", "Local");
45573
+ return join56(base2, "oh-my-opencode", "bin");
45136
45574
  }
45137
45575
  const xdgCache = process.env.XDG_CACHE_HOME;
45138
- const base = xdgCache || join55(homedir13(), ".cache");
45139
- return join55(base, "oh-my-opencode", "bin");
45576
+ const base = xdgCache || join56(homedir13(), ".cache");
45577
+ return join56(base, "oh-my-opencode", "bin");
45140
45578
  }
45141
45579
  function getBinaryName3() {
45142
45580
  return process.platform === "win32" ? "sg.exe" : "sg";
45143
45581
  }
45144
45582
  function getCachedBinaryPath2() {
45145
- const binaryPath = join55(getCacheDir3(), getBinaryName3());
45146
- return existsSync43(binaryPath) ? binaryPath : null;
45583
+ const binaryPath = join56(getCacheDir3(), getBinaryName3());
45584
+ return existsSync44(binaryPath) ? binaryPath : null;
45147
45585
  }
45148
45586
  async function downloadAstGrep(version2 = DEFAULT_VERSION) {
45149
45587
  const platformKey = `${process.platform}-${process.arch}`;
@@ -45154,8 +45592,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
45154
45592
  }
45155
45593
  const cacheDir = getCacheDir3();
45156
45594
  const binaryName = getBinaryName3();
45157
- const binaryPath = join55(cacheDir, binaryName);
45158
- if (existsSync43(binaryPath)) {
45595
+ const binaryPath = join56(cacheDir, binaryName);
45596
+ if (existsSync44(binaryPath)) {
45159
45597
  return binaryPath;
45160
45598
  }
45161
45599
  const { arch, os: os6 } = platformInfo;
@@ -45163,21 +45601,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
45163
45601
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
45164
45602
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
45165
45603
  try {
45166
- if (!existsSync43(cacheDir)) {
45167
- mkdirSync17(cacheDir, { recursive: true });
45604
+ if (!existsSync44(cacheDir)) {
45605
+ mkdirSync18(cacheDir, { recursive: true });
45168
45606
  }
45169
45607
  const response = await fetch(downloadUrl, { redirect: "follow" });
45170
45608
  if (!response.ok) {
45171
45609
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
45172
45610
  }
45173
- const archivePath = join55(cacheDir, assetName);
45611
+ const archivePath = join56(cacheDir, assetName);
45174
45612
  const arrayBuffer = await response.arrayBuffer();
45175
45613
  await Bun.write(archivePath, arrayBuffer);
45176
45614
  await extractZip(archivePath, cacheDir);
45177
- if (existsSync43(archivePath)) {
45178
- unlinkSync14(archivePath);
45615
+ if (existsSync44(archivePath)) {
45616
+ unlinkSync15(archivePath);
45179
45617
  }
45180
- if (process.platform !== "win32" && existsSync43(binaryPath)) {
45618
+ if (process.platform !== "win32" && existsSync44(binaryPath)) {
45181
45619
  chmodSync2(binaryPath, 493);
45182
45620
  }
45183
45621
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -45227,9 +45665,9 @@ function findSgCliPathSync() {
45227
45665
  try {
45228
45666
  const require2 = createRequire4(import.meta.url);
45229
45667
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
45230
- const cliDir = dirname10(cliPkgPath);
45231
- const sgPath = join56(cliDir, binaryName);
45232
- if (existsSync44(sgPath) && isValidBinary(sgPath)) {
45668
+ const cliDir = dirname11(cliPkgPath);
45669
+ const sgPath = join57(cliDir, binaryName);
45670
+ if (existsSync45(sgPath) && isValidBinary(sgPath)) {
45233
45671
  return sgPath;
45234
45672
  }
45235
45673
  } catch {}
@@ -45238,10 +45676,10 @@ function findSgCliPathSync() {
45238
45676
  try {
45239
45677
  const require2 = createRequire4(import.meta.url);
45240
45678
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
45241
- const pkgDir = dirname10(pkgPath);
45679
+ const pkgDir = dirname11(pkgPath);
45242
45680
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
45243
- const binaryPath = join56(pkgDir, astGrepName);
45244
- if (existsSync44(binaryPath) && isValidBinary(binaryPath)) {
45681
+ const binaryPath = join57(pkgDir, astGrepName);
45682
+ if (existsSync45(binaryPath) && isValidBinary(binaryPath)) {
45245
45683
  return binaryPath;
45246
45684
  }
45247
45685
  } catch {}
@@ -45249,7 +45687,7 @@ function findSgCliPathSync() {
45249
45687
  if (process.platform === "darwin") {
45250
45688
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
45251
45689
  for (const path7 of homebrewPaths) {
45252
- if (existsSync44(path7) && isValidBinary(path7)) {
45690
+ if (existsSync45(path7) && isValidBinary(path7)) {
45253
45691
  return path7;
45254
45692
  }
45255
45693
  }
@@ -45304,11 +45742,11 @@ var DEFAULT_MAX_MATCHES = 500;
45304
45742
 
45305
45743
  // src/tools/ast-grep/cli.ts
45306
45744
  var {spawn: spawn7 } = globalThis.Bun;
45307
- import { existsSync as existsSync45 } from "fs";
45745
+ import { existsSync as existsSync46 } from "fs";
45308
45746
  var resolvedCliPath3 = null;
45309
45747
  var initPromise2 = null;
45310
45748
  async function getAstGrepPath() {
45311
- if (resolvedCliPath3 !== null && existsSync45(resolvedCliPath3)) {
45749
+ if (resolvedCliPath3 !== null && existsSync46(resolvedCliPath3)) {
45312
45750
  return resolvedCliPath3;
45313
45751
  }
45314
45752
  if (initPromise2) {
@@ -45316,7 +45754,7 @@ async function getAstGrepPath() {
45316
45754
  }
45317
45755
  initPromise2 = (async () => {
45318
45756
  const syncPath = findSgCliPathSync();
45319
- if (syncPath && existsSync45(syncPath)) {
45757
+ if (syncPath && existsSync46(syncPath)) {
45320
45758
  resolvedCliPath3 = syncPath;
45321
45759
  setSgCliPath(syncPath);
45322
45760
  return syncPath;
@@ -45350,7 +45788,7 @@ async function runSg(options) {
45350
45788
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
45351
45789
  args.push(...paths);
45352
45790
  let cliPath = getSgCliPath();
45353
- if (!existsSync45(cliPath) && cliPath !== "sg") {
45791
+ if (!existsSync46(cliPath) && cliPath !== "sg") {
45354
45792
  const downloadedPath = await getAstGrepPath();
45355
45793
  if (downloadedPath) {
45356
45794
  cliPath = downloadedPath;
@@ -45614,21 +46052,21 @@ var ast_grep_replace = tool({
45614
46052
  var {spawn: spawn9 } = globalThis.Bun;
45615
46053
 
45616
46054
  // src/tools/grep/constants.ts
45617
- import { existsSync as existsSync47 } from "fs";
45618
- import { join as join58, dirname as dirname11 } from "path";
46055
+ import { existsSync as existsSync48 } from "fs";
46056
+ import { join as join59, dirname as dirname12 } from "path";
45619
46057
  import { spawnSync as spawnSync2 } from "child_process";
45620
46058
 
45621
46059
  // src/tools/grep/downloader.ts
45622
46060
  init_shared();
45623
- import { existsSync as existsSync46, mkdirSync as mkdirSync18, chmodSync as chmodSync3, unlinkSync as unlinkSync15, readdirSync as readdirSync13 } from "fs";
45624
- import { join as join57 } from "path";
46061
+ import { existsSync as existsSync47, mkdirSync as mkdirSync19, chmodSync as chmodSync3, unlinkSync as unlinkSync16, readdirSync as readdirSync14 } from "fs";
46062
+ import { join as join58 } from "path";
45625
46063
  var {spawn: spawn8 } = globalThis.Bun;
45626
46064
  function findFileRecursive(dir, filename) {
45627
46065
  try {
45628
- const entries = readdirSync13(dir, { withFileTypes: true, recursive: true });
46066
+ const entries = readdirSync14(dir, { withFileTypes: true, recursive: true });
45629
46067
  for (const entry of entries) {
45630
46068
  if (entry.isFile() && entry.name === filename) {
45631
- return join57(entry.parentPath ?? dir, entry.name);
46069
+ return join58(entry.parentPath ?? dir, entry.name);
45632
46070
  }
45633
46071
  }
45634
46072
  } catch {
@@ -45649,11 +46087,11 @@ function getPlatformKey() {
45649
46087
  }
45650
46088
  function getInstallDir() {
45651
46089
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
45652
- return join57(homeDir, ".cache", "oh-my-opencode", "bin");
46090
+ return join58(homeDir, ".cache", "oh-my-opencode", "bin");
45653
46091
  }
45654
46092
  function getRgPath() {
45655
46093
  const isWindows2 = process.platform === "win32";
45656
- return join57(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
46094
+ return join58(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
45657
46095
  }
45658
46096
  async function downloadFile(url2, destPath) {
45659
46097
  const response = await fetch(url2);
@@ -45687,10 +46125,10 @@ async function extractZip2(archivePath, destDir) {
45687
46125
  const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
45688
46126
  const foundPath = findFileRecursive(destDir, binaryName);
45689
46127
  if (foundPath) {
45690
- const destPath = join57(destDir, binaryName);
46128
+ const destPath = join58(destDir, binaryName);
45691
46129
  if (foundPath !== destPath) {
45692
- const { renameSync: renameSync4 } = await import("fs");
45693
- renameSync4(foundPath, destPath);
46130
+ const { renameSync: renameSync5 } = await import("fs");
46131
+ renameSync5(foundPath, destPath);
45694
46132
  }
45695
46133
  }
45696
46134
  }
@@ -45702,13 +46140,13 @@ async function downloadAndInstallRipgrep() {
45702
46140
  }
45703
46141
  const installDir = getInstallDir();
45704
46142
  const rgPath = getRgPath();
45705
- if (existsSync46(rgPath)) {
46143
+ if (existsSync47(rgPath)) {
45706
46144
  return rgPath;
45707
46145
  }
45708
- mkdirSync18(installDir, { recursive: true });
46146
+ mkdirSync19(installDir, { recursive: true });
45709
46147
  const filename = `ripgrep-${RG_VERSION}-${config3.platform}.${config3.extension}`;
45710
46148
  const url2 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
45711
- const archivePath = join57(installDir, filename);
46149
+ const archivePath = join58(installDir, filename);
45712
46150
  try {
45713
46151
  await downloadFile(url2, archivePath);
45714
46152
  if (config3.extension === "tar.gz") {
@@ -45719,21 +46157,21 @@ async function downloadAndInstallRipgrep() {
45719
46157
  if (process.platform !== "win32") {
45720
46158
  chmodSync3(rgPath, 493);
45721
46159
  }
45722
- if (!existsSync46(rgPath)) {
46160
+ if (!existsSync47(rgPath)) {
45723
46161
  throw new Error("\u63D0\u53D6\u540E\u672A\u627E\u5230 ripgrep \u4E8C\u8FDB\u5236\u6587\u4EF6");
45724
46162
  }
45725
46163
  return rgPath;
45726
46164
  } finally {
45727
- if (existsSync46(archivePath)) {
46165
+ if (existsSync47(archivePath)) {
45728
46166
  try {
45729
- unlinkSync15(archivePath);
46167
+ unlinkSync16(archivePath);
45730
46168
  } catch {}
45731
46169
  }
45732
46170
  }
45733
46171
  }
45734
46172
  function getInstalledRipgrepPath() {
45735
46173
  const rgPath = getRgPath();
45736
- return existsSync46(rgPath) ? rgPath : null;
46174
+ return existsSync47(rgPath) ? rgPath : null;
45737
46175
  }
45738
46176
 
45739
46177
  // src/tools/grep/constants.ts
@@ -45757,7 +46195,7 @@ function findExecutable(name) {
45757
46195
  continue;
45758
46196
  }
45759
46197
  }
45760
- if (existsSync47(trimmed)) {
46198
+ if (existsSync48(trimmed)) {
45761
46199
  return trimmed;
45762
46200
  }
45763
46201
  }
@@ -45768,18 +46206,18 @@ function findExecutable(name) {
45768
46206
  }
45769
46207
  function getOpenCodeBundledRg() {
45770
46208
  const execPath = process.execPath;
45771
- const execDir = dirname11(execPath);
46209
+ const execDir = dirname12(execPath);
45772
46210
  const isWindows2 = process.platform === "win32";
45773
46211
  const rgName = isWindows2 ? "rg.exe" : "rg";
45774
46212
  const candidates = [
45775
- join58(getDataDir(), "opencode", "bin", rgName),
45776
- join58(execDir, rgName),
45777
- join58(execDir, "bin", rgName),
45778
- join58(execDir, "..", "bin", rgName),
45779
- join58(execDir, "..", "libexec", rgName)
46213
+ join59(getDataDir(), "opencode", "bin", rgName),
46214
+ join59(execDir, rgName),
46215
+ join59(execDir, "bin", rgName),
46216
+ join59(execDir, "..", "bin", rgName),
46217
+ join59(execDir, "..", "libexec", rgName)
45780
46218
  ];
45781
46219
  for (const candidate of candidates) {
45782
- if (existsSync47(candidate)) {
46220
+ if (existsSync48(candidate)) {
45783
46221
  return candidate;
45784
46222
  }
45785
46223
  }
@@ -46241,8 +46679,8 @@ var glob = tool({
46241
46679
  init_shared();
46242
46680
  init_file_utils();
46243
46681
  init_shared();
46244
- import { existsSync as existsSync48, readdirSync as readdirSync14, readFileSync as readFileSync34 } from "fs";
46245
- import { join as join59, basename as basename4, dirname as dirname12 } from "path";
46682
+ import { existsSync as existsSync49, readdirSync as readdirSync15, readFileSync as readFileSync36 } from "fs";
46683
+ import { join as join60, basename as basename4, dirname as dirname13 } from "path";
46246
46684
  // src/features/builtin-commands/templates/init-deep.ts
46247
46685
  var INIT_DEEP_TEMPLATE = `# /init-deep
46248
46686
 
@@ -47282,6 +47720,21 @@ var START_WORK_TEMPLATE = `\u4F60\u6B63\u5728\u5F00\u59CB\u4E00\u4E2A \u4E3B\u62
47282
47720
  - \u5728\u59D4\u6D3E\u4EFB\u4F55\u4EFB\u52A1\u4E4B\u524D\uFF0C\u8BFB\u53D6\u5B8C\u6574\u7684\u8BA1\u5212\u6587\u4EF6
47283
47721
  - \u9075\u5FAA \u4EFB\u52A1\u7F16\u6392-\u4E3B\u6267\u884C\u5B98 \u7684\u59D4\u6D3E\u534F\u8BAE\uFF087 \u8282\u683C\u5F0F\uFF09`;
47284
47722
 
47723
+ // src/features/builtin-commands/templates/btw.ts
47724
+ var BTW_TEMPLATE = `# /btw \u547D\u4EE4 \u2014 \u6781\u5EA6\u7CBE\u7B80\u4FA7\u8FB9\u63D0\u95EE
47725
+
47726
+ \u4F60\u6536\u5230\u4E86\u4E00\u4E2A\u4FA7\u8FB9\u95EE\u9898\u3002\u8BF7\u4EE5\u5355\u6B21\u54CD\u5E94\u56DE\u7B54\uFF0C\u4E0D\u8981\u53D1\u8D77\u540E\u7EED\u8FFD\u95EE\u3002
47727
+
47728
+ ## \u7EA6\u675F
47729
+ - \u76F4\u63A5\u56DE\u7B54\u95EE\u9898\uFF0C\u4E0D\u8981\u6DFB\u52A0\u4E0A\u4E0B\u6587\u65E0\u5173\u7684\u5185\u5BB9
47730
+ - \u56DE\u7B54\u63A7\u5236\u5728 100 \u5B57\u4EE5\u5185\uFF0C1-3 \u53E5\u8BDD
47731
+ - \u53EA\u8F93\u51FA\u7B54\u6848\u672C\u8EAB\uFF0C\u4E0D\u8981\u8F93\u51FA\u601D\u8003\u8FC7\u7A0B\u6216\u5206\u6790
47732
+ - \u4E0D\u8981\u53D1\u8D77\u540E\u7EED\u5BF9\u8BDD
47733
+ - \u4F7F\u7528\u4E2D\u6587\u56DE\u590D
47734
+
47735
+ ## \u7528\u6237\u95EE\u9898
47736
+ $ARGUMENTS`;
47737
+
47285
47738
  // src/features/builtin-commands/commands.ts
47286
47739
  var BUILTIN_COMMAND_DEFINITIONS = {
47287
47740
  "init-deep": {
@@ -47346,6 +47799,17 @@ Timestamp: $TIMESTAMP
47346
47799
  $ARGUMENTS
47347
47800
  </user-request>`,
47348
47801
  argumentHint: "[plan-name]"
47802
+ },
47803
+ btw: {
47804
+ description: "(builtin) \u6781\u5EA6\u7CBE\u7B80\u4FA7\u8FB9\u63D0\u95EE \u2014 \u5F02\u6B65\u6267\u884C\uFF0C\u4E0D\u963B\u585E\u4E3B\u5BF9\u8BDD",
47805
+ template: `<command-instruction>
47806
+ ${BTW_TEMPLATE}
47807
+ </command-instruction>
47808
+
47809
+ <user-request>
47810
+ $ARGUMENTS
47811
+ </user-request>`,
47812
+ argumentHint: "<\u4F60\u7684\u95EE\u9898>"
47349
47813
  }
47350
47814
  };
47351
47815
  function loadBuiltinCommands(disabledCommands) {
@@ -47361,18 +47825,18 @@ function loadBuiltinCommands(disabledCommands) {
47361
47825
  }
47362
47826
  // src/tools/slashcommand/tools.ts
47363
47827
  function discoverCommandsFromDir2(commandsDir, scope) {
47364
- if (!existsSync48(commandsDir)) {
47828
+ if (!existsSync49(commandsDir)) {
47365
47829
  return [];
47366
47830
  }
47367
- const entries = readdirSync14(commandsDir, { withFileTypes: true });
47831
+ const entries = readdirSync15(commandsDir, { withFileTypes: true });
47368
47832
  const commands2 = [];
47369
47833
  for (const entry of entries) {
47370
47834
  if (!isMarkdownFile(entry))
47371
47835
  continue;
47372
- const commandPath = join59(commandsDir, entry.name);
47836
+ const commandPath = join60(commandsDir, entry.name);
47373
47837
  const commandName = basename4(entry.name, ".md");
47374
47838
  try {
47375
- const content = readFileSync34(commandPath, "utf-8");
47839
+ const content = readFileSync36(commandPath, "utf-8");
47376
47840
  const { data, body } = parseFrontmatter(content);
47377
47841
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
47378
47842
  const metadata = {
@@ -47396,17 +47860,17 @@ function discoverCommandsFromDir2(commandsDir, scope) {
47396
47860
  }
47397
47861
  return commands2;
47398
47862
  }
47399
- function discoverCommandsSync() {
47863
+ function discoverCommandsSync(disabledCommands) {
47400
47864
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
47401
- const userCommandsDir = join59(getClaudeConfigDir(), "commands");
47402
- const projectCommandsDir = join59(process.cwd(), ".claude", "commands");
47403
- const opencodeGlobalDir = join59(configDir, "command");
47404
- const opencodeProjectDir = join59(process.cwd(), ".opencode", "command");
47865
+ const userCommandsDir = join60(getClaudeConfigDir(), "commands");
47866
+ const projectCommandsDir = join60(process.cwd(), ".claude", "commands");
47867
+ const opencodeGlobalDir = join60(configDir, "command");
47868
+ const opencodeProjectDir = join60(process.cwd(), ".opencode", "command");
47405
47869
  const userCommands = discoverCommandsFromDir2(userCommandsDir, "user");
47406
47870
  const opencodeGlobalCommands = discoverCommandsFromDir2(opencodeGlobalDir, "opencode");
47407
47871
  const projectCommands = discoverCommandsFromDir2(projectCommandsDir, "project");
47408
47872
  const opencodeProjectCommands = discoverCommandsFromDir2(opencodeProjectDir, "opencode-project");
47409
- const builtinCommandsMap = loadBuiltinCommands();
47873
+ const builtinCommandsMap = loadBuiltinCommands(disabledCommands);
47410
47874
  const builtinCommands = Object.values(builtinCommandsMap).map((cmd) => ({
47411
47875
  name: cmd.name,
47412
47876
  metadata: {
@@ -47439,6 +47903,28 @@ function skillToCommandInfo2(skill) {
47439
47903
  lazyContentLoader: skill.lazyContent
47440
47904
  };
47441
47905
  }
47906
+ var COMMANDS_TO_SANITIZE = new Set(["btw"]);
47907
+ var CONSTRAINT_PATTERNS = [
47908
+ "\u4E0D\u8981\u53D1\u8D77\u540E\u7EED\u5BF9\u8BDD",
47909
+ "\u56DE\u7B54\u63A7\u5236\u5728",
47910
+ "\u4F60\u6536\u5230\u4E86\u4E00\u4E2A\u4FA7\u8FB9\u95EE\u9898",
47911
+ "\u53EA\u8F93\u51FA\u7B54\u6848\u672C\u8EAB",
47912
+ "\u4E0D\u8981\u8F93\u51FA\u601D\u8003\u8FC7\u7A0B"
47913
+ ];
47914
+ function sanitizeCommandContent(content, commandName) {
47915
+ if (!COMMANDS_TO_SANITIZE.has(commandName.toLowerCase())) {
47916
+ return content;
47917
+ }
47918
+ let cleaned = content.replace(/<\/?command-instruction>/g, "").replace(/<\/?user-request>/g, "").replace(/<\/?user-task>/g, "").replace(/<\/?session-context>/g, "").replace(/\$ARGUMENTS/g, "");
47919
+ const lines = cleaned.split(`
47920
+ `);
47921
+ cleaned = lines.filter((line) => {
47922
+ const trimmed = line.trim().replace(/^-\s*/, "");
47923
+ return !CONSTRAINT_PATTERNS.some((pattern) => trimmed.startsWith(pattern));
47924
+ }).join(`
47925
+ `);
47926
+ return cleaned.trim();
47927
+ }
47442
47928
  async function formatLoadedCommand(cmd) {
47443
47929
  const sections = [];
47444
47930
  sections.push(`# /${cmd.name} Command
@@ -47473,10 +47959,11 @@ async function formatLoadedCommand(cmd) {
47473
47959
  if (!content && cmd.lazyContentLoader) {
47474
47960
  content = await cmd.lazyContentLoader.load();
47475
47961
  }
47476
- const commandDir = cmd.path ? dirname12(cmd.path) : process.cwd();
47962
+ const commandDir = cmd.path ? dirname13(cmd.path) : process.cwd();
47477
47963
  const withFileRefs = await resolveFileReferencesInText(content, commandDir);
47478
47964
  const resolvedContent = await resolveCommandsInText(withFileRefs);
47479
- sections.push(resolvedContent.trim());
47965
+ const sanitized = sanitizeCommandContent(resolvedContent, cmd.name);
47966
+ sections.push(sanitized.trim());
47480
47967
  return sections.join(`
47481
47968
  `);
47482
47969
  }
@@ -47578,13 +48065,13 @@ var slashcommand = createSlashcommandTool();
47578
48065
  // src/tools/session-manager/constants.ts
47579
48066
  init_data_path();
47580
48067
  init_shared();
47581
- import { join as join60 } from "path";
48068
+ import { join as join61 } from "path";
47582
48069
  var OPENCODE_STORAGE11 = getOpenCodeStorageDir();
47583
- var MESSAGE_STORAGE4 = join60(OPENCODE_STORAGE11, "message");
47584
- var PART_STORAGE4 = join60(OPENCODE_STORAGE11, "part");
47585
- var SESSION_STORAGE = join60(OPENCODE_STORAGE11, "session");
47586
- var TODO_DIR2 = join60(getClaudeConfigDir(), "todos");
47587
- var TRANSCRIPT_DIR2 = join60(getClaudeConfigDir(), "transcripts");
48070
+ var MESSAGE_STORAGE4 = join61(OPENCODE_STORAGE11, "message");
48071
+ var PART_STORAGE4 = join61(OPENCODE_STORAGE11, "part");
48072
+ var SESSION_STORAGE = join61(OPENCODE_STORAGE11, "session");
48073
+ var TODO_DIR2 = join61(getClaudeConfigDir(), "todos");
48074
+ var TRANSCRIPT_DIR2 = join61(getClaudeConfigDir(), "transcripts");
47588
48075
  var SESSION_LIST_DESCRIPTION = `\u5217\u51FA\u6240\u6709 OpenCode session\uFF0C\u652F\u6301\u53EF\u9009\u8FC7\u6EE4\u3002
47589
48076
 
47590
48077
  \u8FD4\u56DE\u53EF\u7528\u7684 session ID \u5217\u8868\uFF0C\u5305\u542B\u6D88\u606F\u6570\u91CF\u3001\u65E5\u671F\u8303\u56F4\u548C\u4F7F\u7528\u8FC7\u7684 agents \u7B49\u5143\u6570\u636E\u3002
@@ -47657,11 +48144,11 @@ Has Todos: Yes (12 items, 8 completed)
47657
48144
  Has Transcript: Yes (234 entries)`;
47658
48145
 
47659
48146
  // src/tools/session-manager/storage.ts
47660
- import { existsSync as existsSync49, readdirSync as readdirSync15 } from "fs";
48147
+ import { existsSync as existsSync50, readdirSync as readdirSync16 } from "fs";
47661
48148
  import { readdir, readFile } from "fs/promises";
47662
- import { join as join61 } from "path";
48149
+ import { join as join62 } from "path";
47663
48150
  async function getMainSessions(options) {
47664
- if (!existsSync49(SESSION_STORAGE))
48151
+ if (!existsSync50(SESSION_STORAGE))
47665
48152
  return [];
47666
48153
  const sessions = [];
47667
48154
  try {
@@ -47669,13 +48156,13 @@ async function getMainSessions(options) {
47669
48156
  for (const projectDir of projectDirs) {
47670
48157
  if (!projectDir.isDirectory())
47671
48158
  continue;
47672
- const projectPath = join61(SESSION_STORAGE, projectDir.name);
48159
+ const projectPath = join62(SESSION_STORAGE, projectDir.name);
47673
48160
  const sessionFiles = await readdir(projectPath);
47674
48161
  for (const file2 of sessionFiles) {
47675
48162
  if (!file2.endsWith(".json"))
47676
48163
  continue;
47677
48164
  try {
47678
- const content = await readFile(join61(projectPath, file2), "utf-8");
48165
+ const content = await readFile(join62(projectPath, file2), "utf-8");
47679
48166
  const meta = JSON.parse(content);
47680
48167
  if (meta.parentID)
47681
48168
  continue;
@@ -47693,7 +48180,7 @@ async function getMainSessions(options) {
47693
48180
  return sessions.sort((a, b) => b.time.updated - a.time.updated);
47694
48181
  }
47695
48182
  async function getAllSessions() {
47696
- if (!existsSync49(MESSAGE_STORAGE4))
48183
+ if (!existsSync50(MESSAGE_STORAGE4))
47697
48184
  return [];
47698
48185
  const sessions = [];
47699
48186
  async function scanDirectory(dir) {
@@ -47701,7 +48188,7 @@ async function getAllSessions() {
47701
48188
  const entries = await readdir(dir, { withFileTypes: true });
47702
48189
  for (const entry of entries) {
47703
48190
  if (entry.isDirectory()) {
47704
- const sessionPath = join61(dir, entry.name);
48191
+ const sessionPath = join62(dir, entry.name);
47705
48192
  const files = await readdir(sessionPath);
47706
48193
  if (files.some((f) => f.endsWith(".json"))) {
47707
48194
  sessions.push(entry.name);
@@ -47718,16 +48205,16 @@ async function getAllSessions() {
47718
48205
  return [...new Set(sessions)];
47719
48206
  }
47720
48207
  function getMessageDir6(sessionID) {
47721
- if (!existsSync49(MESSAGE_STORAGE4))
48208
+ if (!existsSync50(MESSAGE_STORAGE4))
47722
48209
  return "";
47723
- const directPath = join61(MESSAGE_STORAGE4, sessionID);
47724
- if (existsSync49(directPath)) {
48210
+ const directPath = join62(MESSAGE_STORAGE4, sessionID);
48211
+ if (existsSync50(directPath)) {
47725
48212
  return directPath;
47726
48213
  }
47727
48214
  try {
47728
- for (const dir of readdirSync15(MESSAGE_STORAGE4)) {
47729
- const sessionPath = join61(MESSAGE_STORAGE4, dir, sessionID);
47730
- if (existsSync49(sessionPath)) {
48215
+ for (const dir of readdirSync16(MESSAGE_STORAGE4)) {
48216
+ const sessionPath = join62(MESSAGE_STORAGE4, dir, sessionID);
48217
+ if (existsSync50(sessionPath)) {
47731
48218
  return sessionPath;
47732
48219
  }
47733
48220
  }
@@ -47741,7 +48228,7 @@ function sessionExists(sessionID) {
47741
48228
  }
47742
48229
  async function readSessionMessages(sessionID) {
47743
48230
  const messageDir = getMessageDir6(sessionID);
47744
- if (!messageDir || !existsSync49(messageDir))
48231
+ if (!messageDir || !existsSync50(messageDir))
47745
48232
  return [];
47746
48233
  const messages = [];
47747
48234
  try {
@@ -47750,7 +48237,7 @@ async function readSessionMessages(sessionID) {
47750
48237
  if (!file2.endsWith(".json"))
47751
48238
  continue;
47752
48239
  try {
47753
- const content = await readFile(join61(messageDir, file2), "utf-8");
48240
+ const content = await readFile(join62(messageDir, file2), "utf-8");
47754
48241
  const meta = JSON.parse(content);
47755
48242
  const parts = await readParts2(meta.id);
47756
48243
  messages.push({
@@ -47776,8 +48263,8 @@ async function readSessionMessages(sessionID) {
47776
48263
  });
47777
48264
  }
47778
48265
  async function readParts2(messageID) {
47779
- const partDir = join61(PART_STORAGE4, messageID);
47780
- if (!existsSync49(partDir))
48266
+ const partDir = join62(PART_STORAGE4, messageID);
48267
+ if (!existsSync50(partDir))
47781
48268
  return [];
47782
48269
  const parts = [];
47783
48270
  try {
@@ -47786,7 +48273,7 @@ async function readParts2(messageID) {
47786
48273
  if (!file2.endsWith(".json"))
47787
48274
  continue;
47788
48275
  try {
47789
- const content = await readFile(join61(partDir, file2), "utf-8");
48276
+ const content = await readFile(join62(partDir, file2), "utf-8");
47790
48277
  parts.push(JSON.parse(content));
47791
48278
  } catch {
47792
48279
  continue;
@@ -47798,14 +48285,14 @@ async function readParts2(messageID) {
47798
48285
  return parts.sort((a, b) => a.id.localeCompare(b.id));
47799
48286
  }
47800
48287
  async function readSessionTodos(sessionID) {
47801
- if (!existsSync49(TODO_DIR2))
48288
+ if (!existsSync50(TODO_DIR2))
47802
48289
  return [];
47803
48290
  try {
47804
48291
  const allFiles = await readdir(TODO_DIR2);
47805
48292
  const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
47806
48293
  for (const file2 of todoFiles) {
47807
48294
  try {
47808
- const content = await readFile(join61(TODO_DIR2, file2), "utf-8");
48295
+ const content = await readFile(join62(TODO_DIR2, file2), "utf-8");
47809
48296
  const data = JSON.parse(content);
47810
48297
  if (Array.isArray(data)) {
47811
48298
  return data.map((item) => ({
@@ -47825,10 +48312,10 @@ async function readSessionTodos(sessionID) {
47825
48312
  return [];
47826
48313
  }
47827
48314
  async function readSessionTranscript(sessionID) {
47828
- if (!existsSync49(TRANSCRIPT_DIR2))
48315
+ if (!existsSync50(TRANSCRIPT_DIR2))
47829
48316
  return 0;
47830
- const transcriptFile = join61(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
47831
- if (!existsSync49(transcriptFile))
48317
+ const transcriptFile = join62(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
48318
+ if (!existsSync50(transcriptFile))
47832
48319
  return 0;
47833
48320
  try {
47834
48321
  const content = await readFile(transcriptFile, "utf-8");
@@ -48324,7 +48811,7 @@ var TOOL_DESCRIPTION_PREFIX2 = `\u52A0\u8F7D skill \u4EE5\u83B7\u53D6\u7279\u5B9
48324
48811
  Skills \u63D0\u4F9B\u4E13\u4E1A\u77E5\u8BC6\u548C\u5206\u6B65\u6307\u5BFC\u3002
48325
48812
  \u5F53\u4EFB\u52A1\u4E0E\u53EF\u7528 skill \u7684\u63CF\u8FF0\u76F8\u5339\u914D\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177\u3002`;
48326
48813
  // src/tools/skill/tools.ts
48327
- import { dirname as dirname13 } from "path";
48814
+ import { dirname as dirname14 } from "path";
48328
48815
  function loadedSkillToInfo(skill) {
48329
48816
  return {
48330
48817
  name: skill.name,
@@ -48478,7 +48965,7 @@ function createSkillTool(options = {}) {
48478
48965
  if (args.name === "git-master") {
48479
48966
  body = injectGitMasterConfig(body, options.gitMasterConfig);
48480
48967
  }
48481
- const dir = skill.path ? dirname13(skill.path) : skill.resolvedPath || process.cwd();
48968
+ const dir = skill.path ? dirname14(skill.path) : skill.resolvedPath || process.cwd();
48482
48969
  const output = [
48483
48970
  `## Skill: ${skill.name}`,
48484
48971
  "",
@@ -49035,20 +49522,20 @@ var CALL_OMO_AGENT_DESCRIPTION = `\u542F\u52A8\u6DF1\u5EA6\u63A2\u7D22/\u77E5\u8
49035
49522
 
49036
49523
  \u4F20\u5165 \`session_id=<id>\` \u53EF\u7EE7\u7EED\u4E4B\u524D\u7684 agent\uFF0C\u4FDD\u7559\u5B8C\u6574\u4E0A\u4E0B\u6587\u3002Prompts \u5FC5\u987B\u4E3A\u4E2D\u6587\u3002\u4F7F\u7528 \`background_output\` \u83B7\u53D6\u5F02\u6B65\u7ED3\u679C\u3002`;
49037
49524
  // src/tools/call-omo-agent/tools.ts
49038
- import { existsSync as existsSync50, readdirSync as readdirSync16 } from "fs";
49039
- import { join as join62 } from "path";
49525
+ import { existsSync as existsSync51, readdirSync as readdirSync17 } from "fs";
49526
+ import { join as join63 } from "path";
49040
49527
  init_shared();
49041
49528
  init_agent_display_names();
49042
49529
  init_session_cursor();
49043
49530
  function getMessageDir7(sessionID) {
49044
- if (!existsSync50(MESSAGE_STORAGE))
49531
+ if (!existsSync51(MESSAGE_STORAGE))
49045
49532
  return null;
49046
- const directPath = join62(MESSAGE_STORAGE, sessionID);
49047
- if (existsSync50(directPath))
49533
+ const directPath = join63(MESSAGE_STORAGE, sessionID);
49534
+ if (existsSync51(directPath))
49048
49535
  return directPath;
49049
- for (const dir of readdirSync16(MESSAGE_STORAGE)) {
49050
- const sessionPath = join62(MESSAGE_STORAGE, dir, sessionID);
49051
- if (existsSync50(sessionPath))
49536
+ for (const dir of readdirSync17(MESSAGE_STORAGE)) {
49537
+ const sessionPath = join63(MESSAGE_STORAGE, dir, sessionID);
49538
+ if (existsSync51(sessionPath))
49052
49539
  return sessionPath;
49053
49540
  }
49054
49541
  return null;
@@ -49474,8 +49961,8 @@ function createLookAt(ctx) {
49474
49961
  }
49475
49962
  // src/tools/delegate-task/tools.ts
49476
49963
  init_constants();
49477
- import { existsSync as existsSync51, readdirSync as readdirSync17 } from "fs";
49478
- import { join as join63 } from "path";
49964
+ import { existsSync as existsSync52, readdirSync as readdirSync18 } from "fs";
49965
+ import { join as join64 } from "path";
49479
49966
 
49480
49967
  // src/features/task-toast-manager/manager.ts
49481
49968
  class TaskToastManager {
@@ -49681,14 +50168,14 @@ function parseFallbackModelEntries(entries) {
49681
50168
  });
49682
50169
  }
49683
50170
  function getMessageDir8(sessionID) {
49684
- if (!existsSync51(MESSAGE_STORAGE))
50171
+ if (!existsSync52(MESSAGE_STORAGE))
49685
50172
  return null;
49686
- const directPath = join63(MESSAGE_STORAGE, sessionID);
49687
- if (existsSync51(directPath))
50173
+ const directPath = join64(MESSAGE_STORAGE, sessionID);
50174
+ if (existsSync52(directPath))
49688
50175
  return directPath;
49689
- for (const dir of readdirSync17(MESSAGE_STORAGE)) {
49690
- const sessionPath = join63(MESSAGE_STORAGE, dir, sessionID);
49691
- if (existsSync51(sessionPath))
50176
+ for (const dir of readdirSync18(MESSAGE_STORAGE)) {
50177
+ const sessionPath = join64(MESSAGE_STORAGE, dir, sessionID);
50178
+ if (existsSync52(sessionPath))
49692
50179
  return sessionPath;
49693
50180
  }
49694
50181
  return null;
@@ -49757,13 +50244,15 @@ function resolveCategoryConfig(categoryName, options) {
49757
50244
  return { config: config3, promptAppend, model };
49758
50245
  }
49759
50246
  function buildSystemContent(input) {
49760
- const { skillContent, categoryPromptAppend } = input;
50247
+ const { skillContent, categoryPromptAppend, config: config3 } = input;
49761
50248
  const parts = [];
49762
50249
  if (skillContent)
49763
50250
  parts.push(skillContent);
49764
50251
  if (categoryPromptAppend)
49765
50252
  parts.push(categoryPromptAppend);
49766
- parts.push(ZH_SUB_AGENT_INSTRUCTION);
50253
+ if (isLanguageEnforcementEnabled(config3)) {
50254
+ parts.push(ZH_SUB_AGENT_INSTRUCTION);
50255
+ }
49767
50256
  return parts.join(`
49768
50257
 
49769
50258
  `);
@@ -49804,7 +50293,7 @@ async function pollTaskCompletion(client2, sessionID, maxWaitMs = 120000, pollIn
49804
50293
  return "\u4EFB\u52A1\u8D85\u65F6";
49805
50294
  }
49806
50295
  function createDelegateTask(options) {
49807
- const { manager, client: client2, directory, userCategories, gitMasterConfig, sisyphusJuniorModel, runtimeFallbackConfig, agentFallbackModels } = options;
50296
+ const { manager, client: client2, directory, userCategories, gitMasterConfig, sisyphusJuniorModel, runtimeFallbackConfig, agentFallbackModels, languageEnforcementConfig } = options;
49808
50297
  const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories };
49809
50298
  const getConfiguredFallbackModels = (agent, category) => {
49810
50299
  const chineseAgent = agent ? resolveAgentName(agent) : undefined;
@@ -50145,7 +50634,7 @@ ${textContent || "(\u65E0\u6587\u672C\u8F93\u51FA)"}
50145
50634
  }
50146
50635
  }
50147
50636
  agentToUse = resolveAgentName(agentToUse);
50148
- const systemContent = buildSystemContent({ skillContent, categoryPromptAppend });
50637
+ const systemContent = buildSystemContent({ skillContent, categoryPromptAppend, config: languageEnforcementConfig });
50149
50638
  const effectiveRunInBackground = runInBackground || isUnstableAgent;
50150
50639
  if (effectiveRunInBackground) {
50151
50640
  try {
@@ -50443,6 +50932,84 @@ ${classification.shouldFallback ? "\uD83D\uDCA1 \u6B64\u9519\u8BEF\u7B26\u5408 r
50443
50932
  const textParts = lastMessage?.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? [];
50444
50933
  const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join(`
50445
50934
  `);
50935
+ let displayContent = textContent;
50936
+ const pluginConfig = toolContext.pluginConfig;
50937
+ if (isLanguageEnforcementEnabled(pluginConfig) && textContent) {
50938
+ const codeBlockMatches = textContent.match(/```[\s\S]*?```/g) || [];
50939
+ const codeBlockRatio = codeBlockMatches.reduce((sum, b) => sum + b.length, 0) / textContent.length;
50940
+ const isValidJson = (() => {
50941
+ try {
50942
+ JSON.parse(textContent);
50943
+ return true;
50944
+ } catch {
50945
+ return false;
50946
+ }
50947
+ })();
50948
+ const shouldSkip = codeBlockRatio > 0.7 || isValidJson || textContent.length > 50 * 1024;
50949
+ if (!shouldSkip) {
50950
+ const violation = detectEnglishViolation(textContent, 0.6);
50951
+ if (violation) {
50952
+ try {
50953
+ await client2.session.prompt({
50954
+ path: { id: sessionID },
50955
+ body: {
50956
+ agent: agentToUse,
50957
+ system: systemContent,
50958
+ tools: { task: false, delegate_task: false, call_omo_agent: true },
50959
+ parts: [{ type: "text", text: args.prompt + `
50960
+
50961
+ [SYSTEM: \u8BF7\u52A1\u5FC5\u4F7F\u7528\u4E2D\u6587\u56DE\u5E94\u3002\u4E0A\u6B21\u56DE\u5E94\u68C0\u6D4B\u5230\u82F1\u6587\u601D\u8003\u75D5\u8FF9\u3002]` }],
50962
+ ...categoryModel ? { model: categoryModel } : {}
50963
+ }
50964
+ });
50965
+ const retryPollStart = Date.now();
50966
+ const RETRY_MAX_POLL_MS = 10 * 60 * 1000;
50967
+ const RETRY_STABILITY_MS = 5000;
50968
+ const RETRY_STABILITY_POLLS = 3;
50969
+ let retryLastMsgCount = 0;
50970
+ let retryStablePolls = 0;
50971
+ while (Date.now() - retryPollStart < RETRY_MAX_POLL_MS) {
50972
+ if (ctx.abort?.aborted)
50973
+ break;
50974
+ await sleep(500);
50975
+ const retryStatusResult = await client2.session.status();
50976
+ const retryAllStatuses = retryStatusResult.data ?? {};
50977
+ if (retryAllStatuses[sessionID]?.type !== "idle") {
50978
+ retryStablePolls = 0;
50979
+ retryLastMsgCount = 0;
50980
+ continue;
50981
+ }
50982
+ if (Date.now() - retryPollStart < RETRY_STABILITY_MS)
50983
+ continue;
50984
+ const retryMsgsResult = await client2.session.messages({ path: { id: sessionID } });
50985
+ const retryMsgs = retryMsgsResult.data ?? retryMsgsResult;
50986
+ if (retryMsgs.length === retryLastMsgCount) {
50987
+ retryStablePolls++;
50988
+ if (retryStablePolls >= RETRY_STABILITY_POLLS)
50989
+ break;
50990
+ } else {
50991
+ retryStablePolls = 0;
50992
+ retryLastMsgCount = retryMsgs.length;
50993
+ }
50994
+ }
50995
+ const retryMessagesResult = await client2.session.messages({ path: { id: sessionID } });
50996
+ if (!retryMessagesResult.error) {
50997
+ const retryMessages = retryMessagesResult.data ?? retryMessagesResult;
50998
+ const retryAssistantMsgs = retryMessages.filter((m) => m.info?.role === "assistant").sort((a, b) => (b.info?.time?.created ?? 0) - (a.info?.time?.created ?? 0));
50999
+ const retryLastMsg = retryAssistantMsgs[0];
51000
+ if (retryLastMsg) {
51001
+ const retryTextParts = retryLastMsg.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? [];
51002
+ const retryTextContent = retryTextParts.map((p) => p.text ?? "").filter(Boolean).join(`
51003
+ `);
51004
+ if (retryTextContent) {
51005
+ displayContent = retryTextContent;
51006
+ }
51007
+ }
51008
+ }
51009
+ } catch {}
51010
+ }
51011
+ }
51012
+ }
50446
51013
  const duration3 = PerfTimer.formatDuration(startTime);
50447
51014
  if (toastManager) {
50448
51015
  toastManager.removeTask(taskId);
@@ -50455,7 +51022,7 @@ ${classification.shouldFallback ? "\uD83D\uDCA1 \u6B64\u9519\u8BEF\u7B26\u5408 r
50455
51022
 
50456
51023
  ---
50457
51024
 
50458
- ${textContent || "(\u65E0\u6587\u672C\u8F93\u51FA)"}
51025
+ ${displayContent || "(\u65E0\u6587\u672C\u8F93\u51FA)"}
50459
51026
 
50460
51027
  ---
50461
51028
  \u7EE7\u7EED\u6B64 session\uFF1Asession_id="${sessionID}"`;
@@ -50481,6 +51048,44 @@ ${classification.shouldFallback ? "\uD83D\uDCA1 \u6B64\u9519\u8BEF\u7B26\u5408 r
50481
51048
 
50482
51049
  // src/tools/delegate-task/index.ts
50483
51050
  init_constants();
51051
+ // src/tools/btw/constants.ts
51052
+ var BTW_TOOL_DESCRIPTION = `
51053
+ \u5411 BTW \u987E\u95EE\uFF08\u53EA\u8BFB\u8F7B\u91CF Agent\uFF09\u63D0\u51FA\u4E00\u4E2A\u5FEB\u901F\u95EE\u9898\u3002
51054
+ \u5F02\u6B65\u6267\u884C\uFF0C\u4E0D\u963B\u585E\u4E3B\u5BF9\u8BDD\u2014\u2014\u5B8C\u6210\u540E\u901A\u8FC7\u7CFB\u7EDF\u901A\u77E5\u81EA\u52A8\u544A\u77E5\u3002
51055
+ \u9002\u7528\u4E8E\u4EE3\u7801\u5206\u6790\u3001\u6982\u5FF5\u6F84\u6E05\u3001\u6A21\u5F0F\u5EFA\u8BAE\u7B49\u8F7B\u91CF\u67E5\u8BE2\u3002
51056
+ BTW \u987E\u95EE\u65E0\u5199\u6743\u9650\uFF0C\u4E0D\u53EF\u4FEE\u6539\u6587\u4EF6\u6216\u6267\u884C\u547D\u4EE4\u3002
51057
+ `.trim();
51058
+
51059
+ // src/tools/btw/tools.ts
51060
+ function createBtwTool(ctx, backgroundManager) {
51061
+ return tool({
51062
+ description: BTW_TOOL_DESCRIPTION,
51063
+ args: {
51064
+ question: tool.schema.string().describe("\u8981\u8BE2\u95EE BTW \u987E\u95EE\u7684\u95EE\u9898")
51065
+ },
51066
+ async execute(args, toolContext) {
51067
+ const parentSessionID = toolContext.sessionID;
51068
+ const parentSession = await ctx.client.session.get({
51069
+ path: { id: parentSessionID }
51070
+ });
51071
+ const parentDirectory = parentSession?.data?.directory ?? ctx.directory;
51072
+ const btwPrompt = `\u4F60\u662F\u4E00\u4E2A\u8F7B\u91CF\u53EA\u8BFB\u987E\u95EE\uFF08BTW \u987E\u95EE\uFF09\u3002
51073
+ \u8BF7\u6781\u5EA6\u7CBE\u7B80\u5730\u56DE\u7B54\u4EE5\u4E0B\u95EE\u9898\uFF0C\u63A7\u5236\u5728 100 \u5B57\u4EE5\u5185\uFF0C1-3 \u53E5\u8BDD\u3002
51074
+ \u53EA\u8F93\u51FA\u7B54\u6848\uFF0C\u4E0D\u8981\u8F93\u51FA reasoning \u6216\u601D\u8003\u8FC7\u7A0B\u3002
51075
+
51076
+ \u95EE\u9898\uFF1A${args.question}`;
51077
+ const task = await backgroundManager.launch({
51078
+ description: `BTW: ${args.question.substring(0, 50)}`,
51079
+ prompt: btwPrompt,
51080
+ agent: "BTW \u987E\u95EE",
51081
+ parentSessionID,
51082
+ parentMessageID: toolContext.messageID
51083
+ });
51084
+ return `> **BTW**\uFF1A\u6B63\u5728\u67E5\u8BE2\u300C${args.question.substring(0, 30)}${args.question.length > 30 ? "..." : ""}\u300D
51085
+ \u5B8C\u6210\u540E\u5C06\u901A\u8FC7\u7CFB\u7EDF\u901A\u77E5\u81EA\u52A8\u544A\u77E5\u3002\u53EF\u7528 \`background_output("${task.id}")\` \u67E5\u770B\u7ED3\u679C\u3002`;
51086
+ }
51087
+ });
51088
+ }
50484
51089
  // src/tools/index.ts
50485
51090
  function createBackgroundTools(manager, client2) {
50486
51091
  return {
@@ -50666,8 +51271,8 @@ class PerformanceAggregator {
50666
51271
  }
50667
51272
 
50668
51273
  // src/features/background-agent/manager.ts
50669
- import { existsSync as existsSync52, readdirSync as readdirSync18 } from "fs";
50670
- import { join as join64 } from "path";
51274
+ import { existsSync as existsSync53, readdirSync as readdirSync19 } from "fs";
51275
+ import { join as join65 } from "path";
50671
51276
  var TASK_TTL_MS = 30 * 60 * 1000;
50672
51277
  var MIN_STABILITY_TIME_MS = 10 * 1000;
50673
51278
  var DEFAULT_STALE_TIMEOUT_MS = 120000;
@@ -51841,14 +52446,14 @@ function registerProcessSignal(signal, handler, exitAfter) {
51841
52446
  return listener;
51842
52447
  }
51843
52448
  function getMessageDir9(sessionID) {
51844
- if (!existsSync52(MESSAGE_STORAGE))
52449
+ if (!existsSync53(MESSAGE_STORAGE))
51845
52450
  return null;
51846
- const directPath = join64(MESSAGE_STORAGE, sessionID);
51847
- if (existsSync52(directPath))
52451
+ const directPath = join65(MESSAGE_STORAGE, sessionID);
52452
+ if (existsSync53(directPath))
51848
52453
  return directPath;
51849
- for (const dir of readdirSync18(MESSAGE_STORAGE)) {
51850
- const sessionPath = join64(MESSAGE_STORAGE, dir, sessionID);
51851
- if (existsSync52(sessionPath))
52454
+ for (const dir of readdirSync19(MESSAGE_STORAGE)) {
52455
+ const sessionPath = join65(MESSAGE_STORAGE, dir, sessionID);
52456
+ if (existsSync53(sessionPath))
51852
52457
  return sessionPath;
51853
52458
  }
51854
52459
  return null;
@@ -70522,7 +71127,8 @@ var BuiltinAgentNameSchema = exports_external2.enum([
70522
71127
  "\u5A92\u4F53\u89E3\u6790",
70523
71128
  "\u9884\u5BA1\u987E\u95EE",
70524
71129
  "\u8BA1\u5212\u5BA1\u67E5",
70525
- "\u4EFB\u52A1\u7F16\u6392"
71130
+ "\u4EFB\u52A1\u7F16\u6392",
71131
+ "BTW \u987E\u95EE"
70526
71132
  ]);
70527
71133
  var BuiltinSkillNameSchema = exports_external2.enum([
70528
71134
  "playwright",
@@ -70542,7 +71148,8 @@ var OverridableAgentNameSchema = exports_external2.enum([
70542
71148
  "\u77E5\u8BC6\u5178\u85CF",
70543
71149
  "\u6DF1\u5EA6\u63A2\u7D22",
70544
71150
  "\u5A92\u4F53\u89E3\u6790",
70545
- "\u4EFB\u52A1\u7F16\u6392"
71151
+ "\u4EFB\u52A1\u7F16\u6392",
71152
+ "BTW \u987E\u95EE"
70546
71153
  ]);
70547
71154
  var HookNameSchema = exports_external2.enum([
70548
71155
  "todo-continuation-enforcer",
@@ -70583,11 +71190,13 @@ var HookNameSchema = exports_external2.enum([
70583
71190
  "tool-definition-optimizer",
70584
71191
  "permission-ask-bridge",
70585
71192
  "shell-env-injector",
70586
- "dispose-coordinator"
71193
+ "dispose-coordinator",
71194
+ "plan-completion"
70587
71195
  ]);
70588
71196
  var BuiltinCommandNameSchema = exports_external2.enum([
70589
71197
  "init-deep",
70590
- "start-work"
71198
+ "start-work",
71199
+ "btw"
70591
71200
  ]);
70592
71201
  var ProviderModelStringSchema = exports_external2.string().refine((value) => {
70593
71202
  const separatorIndex = value.indexOf("/");
@@ -70636,7 +71245,8 @@ var AgentOverridesSchema = exports_external2.object({
70636
71245
  "\u77E5\u8BC6\u5178\u85CF": AgentOverrideConfigSchema.optional(),
70637
71246
  "\u6DF1\u5EA6\u63A2\u7D22": AgentOverrideConfigSchema.optional(),
70638
71247
  "\u5A92\u4F53\u89E3\u6790": AgentOverrideConfigSchema.optional(),
70639
- "\u4EFB\u52A1\u7F16\u6392": AgentOverrideConfigSchema.optional()
71248
+ "\u4EFB\u52A1\u7F16\u6392": AgentOverrideConfigSchema.optional(),
71249
+ "BTW \u987E\u95EE": AgentOverrideConfigSchema.optional()
70640
71250
  });
70641
71251
  var ClaudeCodeConfigSchema = exports_external2.object({
70642
71252
  mcp: exports_external2.boolean().optional(),
@@ -71068,7 +71678,7 @@ init_file_utils();
71068
71678
  init_shared();
71069
71679
  init_logger();
71070
71680
  import { promises as fs11 } from "fs";
71071
- import { join as join66, basename as basename6 } from "path";
71681
+ import { join as join67, basename as basename6 } from "path";
71072
71682
  async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix = "") {
71073
71683
  try {
71074
71684
  await fs11.access(commandsDir);
@@ -71098,7 +71708,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
71098
71708
  if (entry.isDirectory()) {
71099
71709
  if (entry.name.startsWith("."))
71100
71710
  continue;
71101
- const subDirPath = join66(commandsDir, entry.name);
71711
+ const subDirPath = join67(commandsDir, entry.name);
71102
71712
  const subPrefix = prefix ? `${prefix}:${entry.name}` : entry.name;
71103
71713
  const subCommands = await loadCommandsFromDir(subDirPath, scope, visited, subPrefix);
71104
71714
  commands2.push(...subCommands);
@@ -71106,7 +71716,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
71106
71716
  }
71107
71717
  if (!isMarkdownFile(entry))
71108
71718
  continue;
71109
- const commandPath = join66(commandsDir, entry.name);
71719
+ const commandPath = join67(commandsDir, entry.name);
71110
71720
  const baseCommandName = basename6(entry.name, ".md");
71111
71721
  const commandName = prefix ? `${prefix}:${baseCommandName}` : baseCommandName;
71112
71722
  try {
@@ -71153,23 +71763,23 @@ function commandsToRecord(commands2) {
71153
71763
  return result;
71154
71764
  }
71155
71765
  async function loadUserCommands() {
71156
- const userCommandsDir = join66(getClaudeConfigDir(), "commands");
71766
+ const userCommandsDir = join67(getClaudeConfigDir(), "commands");
71157
71767
  const commands2 = await loadCommandsFromDir(userCommandsDir, "user");
71158
71768
  return commandsToRecord(commands2);
71159
71769
  }
71160
71770
  async function loadProjectCommands() {
71161
- const projectCommandsDir = join66(process.cwd(), ".claude", "commands");
71771
+ const projectCommandsDir = join67(process.cwd(), ".claude", "commands");
71162
71772
  const commands2 = await loadCommandsFromDir(projectCommandsDir, "project");
71163
71773
  return commandsToRecord(commands2);
71164
71774
  }
71165
71775
  async function loadOpencodeGlobalCommands() {
71166
71776
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
71167
- const opencodeCommandsDir = join66(configDir, "command");
71777
+ const opencodeCommandsDir = join67(configDir, "command");
71168
71778
  const commands2 = await loadCommandsFromDir(opencodeCommandsDir, "opencode");
71169
71779
  return commandsToRecord(commands2);
71170
71780
  }
71171
71781
  async function loadOpencodeProjectCommands() {
71172
- const opencodeProjectDir = join66(process.cwd(), ".opencode", "command");
71782
+ const opencodeProjectDir = join67(process.cwd(), ".opencode", "command");
71173
71783
  const commands2 = await loadCommandsFromDir(opencodeProjectDir, "opencode-project");
71174
71784
  return commandsToRecord(commands2);
71175
71785
  }
@@ -71177,8 +71787,8 @@ async function loadOpencodeProjectCommands() {
71177
71787
  init_frontmatter();
71178
71788
  init_file_utils();
71179
71789
  init_shared();
71180
- import { existsSync as existsSync54, readdirSync as readdirSync19, readFileSync as readFileSync36 } from "fs";
71181
- import { join as join67, basename as basename7 } from "path";
71790
+ import { existsSync as existsSync55, readdirSync as readdirSync20, readFileSync as readFileSync38 } from "fs";
71791
+ import { join as join68, basename as basename7 } from "path";
71182
71792
  function parseToolsConfig(toolsStr) {
71183
71793
  if (!toolsStr)
71184
71794
  return;
@@ -71191,25 +71801,25 @@ function parseToolsConfig(toolsStr) {
71191
71801
  }
71192
71802
  return result;
71193
71803
  }
71194
- function loadAgentsFromDir(agentsDir, scope) {
71195
- if (!existsSync54(agentsDir)) {
71804
+ function loadAgentsFromDir(agentsDir, scope, leConfig) {
71805
+ if (!existsSync55(agentsDir)) {
71196
71806
  return [];
71197
71807
  }
71198
- const entries = readdirSync19(agentsDir, { withFileTypes: true });
71808
+ const entries = readdirSync20(agentsDir, { withFileTypes: true });
71199
71809
  const agents = [];
71200
71810
  for (const entry of entries) {
71201
71811
  if (!isMarkdownFile(entry))
71202
71812
  continue;
71203
- const agentPath = join67(agentsDir, entry.name);
71813
+ const agentPath = join68(agentsDir, entry.name);
71204
71814
  const agentName = basename7(entry.name, ".md");
71205
71815
  try {
71206
- const content = readFileSync36(agentPath, "utf-8");
71816
+ const content = readFileSync38(agentPath, "utf-8");
71207
71817
  const { data, body } = parseFrontmatter(content);
71208
71818
  const name = data.name || agentName;
71209
71819
  const originalDescription = data.description || "";
71210
71820
  const formattedDescription = `(${scope}) ${originalDescription}`;
71211
71821
  const trimmedBody = body.trim();
71212
- const promptWithLang = trimmedBody.includes("<Language_Instruction>") ? trimmedBody : trimmedBody + ZH_SUB_AGENT_INSTRUCTION;
71822
+ const promptWithLang = trimmedBody.includes("<Language_Instruction>") || !isLanguageEnforcementEnabled(leConfig) ? trimmedBody : trimmedBody + ZH_SUB_AGENT_INSTRUCTION;
71213
71823
  const config4 = {
71214
71824
  description: formattedDescription,
71215
71825
  mode: "subagent",
@@ -71231,18 +71841,18 @@ function loadAgentsFromDir(agentsDir, scope) {
71231
71841
  }
71232
71842
  return agents;
71233
71843
  }
71234
- function loadUserAgents() {
71235
- const userAgentsDir = join67(getClaudeConfigDir(), "agents");
71236
- const agents = loadAgentsFromDir(userAgentsDir, "user");
71844
+ function loadUserAgents(leConfig) {
71845
+ const userAgentsDir = join68(getClaudeConfigDir(), "agents");
71846
+ const agents = loadAgentsFromDir(userAgentsDir, "user", leConfig);
71237
71847
  const result = {};
71238
71848
  for (const agent of agents) {
71239
71849
  result[agent.name] = agent.config;
71240
71850
  }
71241
71851
  return result;
71242
71852
  }
71243
- function loadProjectAgents() {
71244
- const projectAgentsDir = join67(process.cwd(), ".claude", "agents");
71245
- const agents = loadAgentsFromDir(projectAgentsDir, "project");
71853
+ function loadProjectAgents(leConfig) {
71854
+ const projectAgentsDir = join68(process.cwd(), ".claude", "agents");
71855
+ const agents = loadAgentsFromDir(projectAgentsDir, "project", leConfig);
71246
71856
  const result = {};
71247
71857
  for (const agent of agents) {
71248
71858
  result[agent.name] = agent.config;
@@ -71253,18 +71863,18 @@ function loadProjectAgents() {
71253
71863
  init_frontmatter();
71254
71864
  init_file_utils();
71255
71865
  init_logger();
71256
- import { existsSync as existsSync55, readdirSync as readdirSync20, readFileSync as readFileSync37 } from "fs";
71866
+ import { existsSync as existsSync56, readdirSync as readdirSync21, readFileSync as readFileSync39 } from "fs";
71257
71867
  import { homedir as homedir14 } from "os";
71258
- import { join as join68, basename as basename8 } from "path";
71868
+ import { join as join69, basename as basename8 } from "path";
71259
71869
  var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
71260
71870
  function getPluginsBaseDir() {
71261
71871
  if (process.env.CLAUDE_PLUGINS_HOME) {
71262
71872
  return process.env.CLAUDE_PLUGINS_HOME;
71263
71873
  }
71264
- return join68(homedir14(), ".claude", "plugins");
71874
+ return join69(homedir14(), ".claude", "plugins");
71265
71875
  }
71266
71876
  function getInstalledPluginsPath() {
71267
- return join68(getPluginsBaseDir(), "installed_plugins.json");
71877
+ return join69(getPluginsBaseDir(), "installed_plugins.json");
71268
71878
  }
71269
71879
  function resolvePluginPath(path8, pluginRoot) {
71270
71880
  return path8.replace(CLAUDE_PLUGIN_ROOT_VAR, pluginRoot);
@@ -71289,11 +71899,11 @@ function resolvePluginPaths(obj, pluginRoot) {
71289
71899
  }
71290
71900
  function loadInstalledPlugins() {
71291
71901
  const dbPath = getInstalledPluginsPath();
71292
- if (!existsSync55(dbPath)) {
71902
+ if (!existsSync56(dbPath)) {
71293
71903
  return null;
71294
71904
  }
71295
71905
  try {
71296
- const content = readFileSync37(dbPath, "utf-8");
71906
+ const content = readFileSync39(dbPath, "utf-8");
71297
71907
  return JSON.parse(content);
71298
71908
  } catch (error95) {
71299
71909
  log("Failed to load installed plugins database", error95);
@@ -71304,15 +71914,15 @@ function getClaudeSettingsPath() {
71304
71914
  if (process.env.CLAUDE_SETTINGS_PATH) {
71305
71915
  return process.env.CLAUDE_SETTINGS_PATH;
71306
71916
  }
71307
- return join68(homedir14(), ".claude", "settings.json");
71917
+ return join69(homedir14(), ".claude", "settings.json");
71308
71918
  }
71309
71919
  function loadClaudeSettings() {
71310
71920
  const settingsPath = getClaudeSettingsPath();
71311
- if (!existsSync55(settingsPath)) {
71921
+ if (!existsSync56(settingsPath)) {
71312
71922
  return null;
71313
71923
  }
71314
71924
  try {
71315
- const content = readFileSync37(settingsPath, "utf-8");
71925
+ const content = readFileSync39(settingsPath, "utf-8");
71316
71926
  return JSON.parse(content);
71317
71927
  } catch (error95) {
71318
71928
  log("Failed to load Claude settings", error95);
@@ -71320,12 +71930,12 @@ function loadClaudeSettings() {
71320
71930
  }
71321
71931
  }
71322
71932
  function loadPluginManifest(installPath) {
71323
- const manifestPath = join68(installPath, ".claude-plugin", "plugin.json");
71324
- if (!existsSync55(manifestPath)) {
71933
+ const manifestPath = join69(installPath, ".claude-plugin", "plugin.json");
71934
+ if (!existsSync56(manifestPath)) {
71325
71935
  return null;
71326
71936
  }
71327
71937
  try {
71328
- const content = readFileSync37(manifestPath, "utf-8");
71938
+ const content = readFileSync39(manifestPath, "utf-8");
71329
71939
  return JSON.parse(content);
71330
71940
  } catch (error95) {
71331
71941
  log(`Failed to load plugin manifest from ${manifestPath}`, error95);
@@ -71372,7 +71982,7 @@ function discoverInstalledPlugins(options) {
71372
71982
  continue;
71373
71983
  }
71374
71984
  const { installPath, scope, version: version3 } = installation;
71375
- if (!existsSync55(installPath)) {
71985
+ if (!existsSync56(installPath)) {
71376
71986
  errors5.push({
71377
71987
  pluginKey,
71378
71988
  installPath,
@@ -71390,21 +72000,21 @@ function discoverInstalledPlugins(options) {
71390
72000
  pluginKey,
71391
72001
  manifest: manifest ?? undefined
71392
72002
  };
71393
- if (existsSync55(join68(installPath, "commands"))) {
71394
- loadedPlugin.commandsDir = join68(installPath, "commands");
72003
+ if (existsSync56(join69(installPath, "commands"))) {
72004
+ loadedPlugin.commandsDir = join69(installPath, "commands");
71395
72005
  }
71396
- if (existsSync55(join68(installPath, "agents"))) {
71397
- loadedPlugin.agentsDir = join68(installPath, "agents");
72006
+ if (existsSync56(join69(installPath, "agents"))) {
72007
+ loadedPlugin.agentsDir = join69(installPath, "agents");
71398
72008
  }
71399
- if (existsSync55(join68(installPath, "skills"))) {
71400
- loadedPlugin.skillsDir = join68(installPath, "skills");
72009
+ if (existsSync56(join69(installPath, "skills"))) {
72010
+ loadedPlugin.skillsDir = join69(installPath, "skills");
71401
72011
  }
71402
- const hooksPath = join68(installPath, "hooks", "hooks.json");
71403
- if (existsSync55(hooksPath)) {
72012
+ const hooksPath = join69(installPath, "hooks", "hooks.json");
72013
+ if (existsSync56(hooksPath)) {
71404
72014
  loadedPlugin.hooksPath = hooksPath;
71405
72015
  }
71406
- const mcpPath = join68(installPath, ".mcp.json");
71407
- if (existsSync55(mcpPath)) {
72016
+ const mcpPath = join69(installPath, ".mcp.json");
72017
+ if (existsSync56(mcpPath)) {
71408
72018
  loadedPlugin.mcpPath = mcpPath;
71409
72019
  }
71410
72020
  plugins.push(loadedPlugin);
@@ -71415,17 +72025,17 @@ function discoverInstalledPlugins(options) {
71415
72025
  function loadPluginCommands(plugins) {
71416
72026
  const commands2 = {};
71417
72027
  for (const plugin of plugins) {
71418
- if (!plugin.commandsDir || !existsSync55(plugin.commandsDir))
72028
+ if (!plugin.commandsDir || !existsSync56(plugin.commandsDir))
71419
72029
  continue;
71420
- const entries = readdirSync20(plugin.commandsDir, { withFileTypes: true });
72030
+ const entries = readdirSync21(plugin.commandsDir, { withFileTypes: true });
71421
72031
  for (const entry of entries) {
71422
72032
  if (!isMarkdownFile(entry))
71423
72033
  continue;
71424
- const commandPath = join68(plugin.commandsDir, entry.name);
72034
+ const commandPath = join69(plugin.commandsDir, entry.name);
71425
72035
  const commandName = basename8(entry.name, ".md");
71426
72036
  const namespacedName = `${plugin.name}:${commandName}`;
71427
72037
  try {
71428
- const content = readFileSync37(commandPath, "utf-8");
72038
+ const content = readFileSync39(commandPath, "utf-8");
71429
72039
  const { data, body } = parseFrontmatter(content);
71430
72040
  const wrappedTemplate = `<command-instruction>
71431
72041
  ${body.trim()}
@@ -71457,21 +72067,21 @@ $ARGUMENTS
71457
72067
  function loadPluginSkillsAsCommands(plugins) {
71458
72068
  const skills = {};
71459
72069
  for (const plugin of plugins) {
71460
- if (!plugin.skillsDir || !existsSync55(plugin.skillsDir))
72070
+ if (!plugin.skillsDir || !existsSync56(plugin.skillsDir))
71461
72071
  continue;
71462
- const entries = readdirSync20(plugin.skillsDir, { withFileTypes: true });
72072
+ const entries = readdirSync21(plugin.skillsDir, { withFileTypes: true });
71463
72073
  for (const entry of entries) {
71464
72074
  if (entry.name.startsWith("."))
71465
72075
  continue;
71466
- const skillPath = join68(plugin.skillsDir, entry.name);
72076
+ const skillPath = join69(plugin.skillsDir, entry.name);
71467
72077
  if (!entry.isDirectory() && !entry.isSymbolicLink())
71468
72078
  continue;
71469
72079
  const resolvedPath = resolveSymlink(skillPath);
71470
- const skillMdPath = join68(resolvedPath, "SKILL.md");
71471
- if (!existsSync55(skillMdPath))
72080
+ const skillMdPath = join69(resolvedPath, "SKILL.md");
72081
+ if (!existsSync56(skillMdPath))
71472
72082
  continue;
71473
72083
  try {
71474
- const content = readFileSync37(skillMdPath, "utf-8");
72084
+ const content = readFileSync39(skillMdPath, "utf-8");
71475
72085
  const { data, body } = parseFrontmatter(content);
71476
72086
  const skillName = data.name || entry.name;
71477
72087
  const namespacedName = `${plugin.name}:${skillName}`;
@@ -71518,17 +72128,17 @@ function parseToolsConfig2(toolsStr) {
71518
72128
  function loadPluginAgents(plugins) {
71519
72129
  const agents = {};
71520
72130
  for (const plugin of plugins) {
71521
- if (!plugin.agentsDir || !existsSync55(plugin.agentsDir))
72131
+ if (!plugin.agentsDir || !existsSync56(plugin.agentsDir))
71522
72132
  continue;
71523
- const entries = readdirSync20(plugin.agentsDir, { withFileTypes: true });
72133
+ const entries = readdirSync21(plugin.agentsDir, { withFileTypes: true });
71524
72134
  for (const entry of entries) {
71525
72135
  if (!isMarkdownFile(entry))
71526
72136
  continue;
71527
- const agentPath = join68(plugin.agentsDir, entry.name);
72137
+ const agentPath = join69(plugin.agentsDir, entry.name);
71528
72138
  const agentName = basename8(entry.name, ".md");
71529
72139
  const namespacedName = `${plugin.name}:${agentName}`;
71530
72140
  try {
71531
- const content = readFileSync37(agentPath, "utf-8");
72141
+ const content = readFileSync39(agentPath, "utf-8");
71532
72142
  const { data, body } = parseFrontmatter(content);
71533
72143
  const name = data.name || agentName;
71534
72144
  const originalDescription = data.description || "";
@@ -71554,7 +72164,7 @@ function loadPluginAgents(plugins) {
71554
72164
  async function loadPluginMcpServers(plugins) {
71555
72165
  const servers = {};
71556
72166
  for (const plugin of plugins) {
71557
- if (!plugin.mcpPath || !existsSync55(plugin.mcpPath))
72167
+ if (!plugin.mcpPath || !existsSync56(plugin.mcpPath))
71558
72168
  continue;
71559
72169
  try {
71560
72170
  const content = await Bun.file(plugin.mcpPath).text();
@@ -71586,10 +72196,10 @@ async function loadPluginMcpServers(plugins) {
71586
72196
  function loadPluginHooksConfigs(plugins) {
71587
72197
  const configs = [];
71588
72198
  for (const plugin of plugins) {
71589
- if (!plugin.hooksPath || !existsSync55(plugin.hooksPath))
72199
+ if (!plugin.hooksPath || !existsSync56(plugin.hooksPath))
71590
72200
  continue;
71591
72201
  try {
71592
- const content = readFileSync37(plugin.hooksPath, "utf-8");
72202
+ const content = readFileSync39(plugin.hooksPath, "utf-8");
71593
72203
  let config4 = JSON.parse(content);
71594
72204
  config4 = resolvePluginPaths(config4, plugin.installPath);
71595
72205
  configs.push(config4);
@@ -72951,9 +73561,9 @@ function createConfigHandler(deps) {
72951
73561
  ...discoveredOpencodeGlobalSkills,
72952
73562
  ...discoveredUserSkills
72953
73563
  ];
72954
- const builtinAgents = await createBuiltinAgents(migratedDisabledAgents, pluginConfig.agents, ctx.directory, config4.model, pluginConfig.categories, pluginConfig.git_master, allDiscoveredSkills, ctx.client);
72955
- const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
72956
- const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
73564
+ const builtinAgents = await createBuiltinAgents(migratedDisabledAgents, pluginConfig.agents, ctx.directory, config4.model, pluginConfig.categories, pluginConfig.git_master, allDiscoveredSkills, ctx.client, pluginConfig);
73565
+ const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents(pluginConfig) : {};
73566
+ const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents(pluginConfig) : {};
72957
73567
  const rawPluginAgents = pluginComponents.agents;
72958
73568
  const pluginAgents = Object.fromEntries(Object.entries(rawPluginAgents).map(([k, v]) => [
72959
73569
  k,
@@ -73004,21 +73614,27 @@ function createConfigHandler(deps) {
73004
73614
  };
73005
73615
  agentConfig["\u6218\u7565\u53C2\u8C0B"] = prometheusOverride ? { ...prometheusBase, ...prometheusOverride } : prometheusBase;
73006
73616
  }
73007
- const prometheusAgent = agentConfig["\u6218\u7565\u53C2\u8C0B"];
73008
- if (prometheusAgent?.prompt && !prometheusAgent.prompt.includes("<Language_Instruction>")) {
73009
- if (isDeepseekModel(prometheusAgent.model ?? "")) {
73010
- prometheusAgent.prompt += ZH_LANGUAGE_INSTRUCTION_DEEPSEEK;
73011
- } else {
73012
- prometheusAgent.prompt += ZH_LANGUAGE_INSTRUCTION;
73617
+ if (isLanguageEnforcementEnabled(pluginConfig)) {
73618
+ const prometheusAgent = agentConfig["\u6218\u7565\u53C2\u8C0B"];
73619
+ if (prometheusAgent?.prompt && !prometheusAgent.prompt.includes("<Language_Instruction>")) {
73620
+ if (isDeepseekModel(prometheusAgent.model ?? "")) {
73621
+ prometheusAgent.prompt += ZH_LANGUAGE_INSTRUCTION_DEEPSEEK;
73622
+ } else {
73623
+ prometheusAgent.prompt += ZH_LANGUAGE_INSTRUCTION;
73624
+ }
73013
73625
  }
73014
73626
  }
73015
- const juniorAgent = agentConfig["\u6267\u884C\u52A9\u7406"];
73016
- if (juniorAgent?.prompt && !juniorAgent.prompt.includes("<Language_Instruction>")) {
73017
- juniorAgent.prompt += ZH_SUB_AGENT_INSTRUCTION;
73627
+ if (isLanguageEnforcementEnabled(pluginConfig)) {
73628
+ const juniorAgent = agentConfig["\u6267\u884C\u52A9\u7406"];
73629
+ if (juniorAgent?.prompt && !juniorAgent.prompt.includes("<Language_Instruction>")) {
73630
+ juniorAgent.prompt += ZH_SUB_AGENT_INSTRUCTION;
73631
+ }
73018
73632
  }
73019
- const openCodeBuilderAgent = agentConfig["OpenCode-Builder"];
73020
- if (openCodeBuilderAgent?.prompt && !openCodeBuilderAgent.prompt.includes("<Language_Instruction>")) {
73021
- openCodeBuilderAgent.prompt += ZH_SUB_AGENT_INSTRUCTION;
73633
+ if (isLanguageEnforcementEnabled(pluginConfig)) {
73634
+ const openCodeBuilderAgent = agentConfig["OpenCode-Builder"];
73635
+ if (openCodeBuilderAgent?.prompt && !openCodeBuilderAgent.prompt.includes("<Language_Instruction>")) {
73636
+ openCodeBuilderAgent.prompt += ZH_SUB_AGENT_INSTRUCTION;
73637
+ }
73022
73638
  }
73023
73639
  const filteredConfigAgents = configAgent ? Object.fromEntries(Object.entries(configAgent).filter(([key]) => {
73024
73640
  if (key === "build")
@@ -73273,7 +73889,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73273
73889
  const anthropicContextWindowLimitRecovery = isHookEnabled("anthropic-context-window-limit-recovery") ? createAnthropicContextWindowLimitRecoveryHook(ctx, {
73274
73890
  experimental: pluginConfig.experimental
73275
73891
  }) : null;
73276
- const compactionContextInjector = isHookEnabled("compaction-context-injector") ? createCompactionContextInjector(ctx.client) : undefined;
73892
+ const compactionContextInjector = isHookEnabled("compaction-context-injector") ? createCompactionContextInjector(ctx.client, pluginConfig) : undefined;
73277
73893
  const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
73278
73894
  const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx, {
73279
73895
  showStartupToast: isHookEnabled("startup-toast"),
@@ -73283,8 +73899,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
73283
73899
  const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook(ctx, contextCollector) : null;
73284
73900
  const contextInjectorMessagesTransform = createContextInjectorMessagesTransformHook(contextCollector);
73285
73901
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
73286
- const languageReminder = isHookEnabled("language-reminder") ? createLanguageReminderHook(ctx) : null;
73287
- const thinkingLanguageValidator = isHookEnabled("thinking-language-validator") ? createThinkingLanguageValidatorHook(ctx) : null;
73902
+ const languageReminder = isHookEnabled("language-reminder") && isLanguageEnforcementEnabled(pluginConfig) ? createLanguageReminderHook(ctx) : null;
73903
+ const thinkingLanguageValidator = isHookEnabled("thinking-language-validator") && isLanguageEnforcementEnabled(pluginConfig) ? createThinkingLanguageValidatorHook(ctx) : null;
73288
73904
  const nonInteractiveEnv = isHookEnabled("non-interactive-env") ? createNonInteractiveEnvHook(ctx) : null;
73289
73905
  const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
73290
73906
  const thinkingBlockValidator = isHookEnabled("thinking-block-validator") ? createThinkingBlockValidatorHook() : null;
@@ -73336,6 +73952,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73336
73952
  const toolDefOptimizer = isHookEnabled("tool-definition-optimizer") ? createToolDefinitionOptimizerHook(ctx) : null;
73337
73953
  const permissionAskBridge = isHookEnabled("permission-ask-bridge") ? createPermissionAskBridgeHook(ctx) : null;
73338
73954
  const shellEnvInjector = isHookEnabled("shell-env-injector") ? createShellEnvInjectorHook(ctx) : null;
73955
+ const planCompletionHook = isHookEnabled("plan-completion") ? createPlanCompletionHook(ctx) : null;
73339
73956
  initTaskToastManager(ctx.client);
73340
73957
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager, ralphLoopHook: ralphLoop ?? undefined }) : null;
73341
73958
  if (sessionRecovery && todoContinuationEnforcer) {
@@ -73345,6 +73962,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73345
73962
  const backgroundNotificationHook = isHookEnabled("background-notification") ? createBackgroundNotificationHook(backgroundManager) : null;
73346
73963
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
73347
73964
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
73965
+ const btwTool = createBtwTool(ctx, backgroundManager);
73348
73966
  const isMultimodalLookerEnabled = !includesCaseInsensitive(pluginConfig.disabled_agents ?? [], "multimodal-looker");
73349
73967
  const lookAt = isMultimodalLookerEnabled ? createLookAt(ctx) : null;
73350
73968
  const delegateTask = createDelegateTask({
@@ -73391,7 +74009,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73391
74009
  getLoadedSkills: () => mergedSkills,
73392
74010
  getSessionID: getSessionIDForMcp
73393
74011
  });
73394
- const commands2 = discoverCommandsSync();
74012
+ const commands2 = discoverCommandsSync(["btw"]);
73395
74013
  const slashcommandTool = createSlashcommandTool({
73396
74014
  commands: commands2,
73397
74015
  skills: mergedSkills
@@ -73430,6 +74048,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73430
74048
  call_omo_agent: callOmoAgent,
73431
74049
  ...lookAt ? { look_at: lookAt } : {},
73432
74050
  delegate_task: delegateTask,
74051
+ btw: btwTool,
73433
74052
  skill: skillTool,
73434
74053
  skill_mcp: skillMcpTool,
73435
74054
  slashcommand: slashcommandTool,
@@ -73761,7 +74380,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73761
74380
  - \u5F53\u524D Agent: ${agentName}
73762
74381
  - \u89D2\u8272\u7EA6\u675F: ${roleDesc}
73763
74382
  - \u5728\u538B\u7F29\u540E\u7684\u6458\u8981\u4E2D\u660E\u786E\u4FDD\u7559\u6B64 Agent \u7684\u8EAB\u4EFD\u548C\u89D2\u8272\u7EA6\u675F`);
73764
- output.context.push(SUMMARIZE_CONTEXT_PROMPT);
74383
+ output.context.push(buildSummarizeContextPrompt(pluginConfig));
73765
74384
  },
73766
74385
  dispose: disposeCoordinator?.dispose
73767
74386
  };