@skj1724/oh-my-opencode 3.22.0 → 3.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) 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/types.d.ts +1 -1
  5. package/dist/cli/index.js +20 -7
  6. package/dist/config/schema.d.ts +137 -3
  7. package/dist/features/boulder-state/event-bus.d.ts +9 -1
  8. package/dist/features/boulder-state/storage.d.ts +1 -0
  9. package/dist/features/boulder-state/types.d.ts +5 -0
  10. package/dist/features/boulder-state/types.test.d.ts +1 -0
  11. package/dist/features/builtin-commands/templates/btw.d.ts +1 -0
  12. package/dist/features/builtin-commands/types.d.ts +1 -1
  13. package/dist/hooks/atlas/plan-completion.test.d.ts +1 -0
  14. package/dist/hooks/index.d.ts +1 -0
  15. package/dist/hooks/plan-completion/archive.test.d.ts +1 -0
  16. package/dist/hooks/plan-completion/extract-learnings.test.d.ts +1 -0
  17. package/dist/hooks/plan-completion/generate-report.test.d.ts +1 -0
  18. package/dist/hooks/plan-completion/index.d.ts +13 -0
  19. package/dist/hooks/plan-completion/index.test.d.ts +1 -0
  20. package/dist/hooks/plan-completion/integration.test.d.ts +1 -0
  21. package/dist/hooks/plan-completion/update-summary.test.d.ts +1 -0
  22. package/dist/index.js +761 -272
  23. package/dist/shared/frontmatter.d.ts +7 -0
  24. package/dist/tools/btw/constants.d.ts +1 -0
  25. package/dist/tools/btw/index.d.ts +3 -0
  26. package/dist/tools/btw/post-processor.d.ts +1 -0
  27. package/dist/tools/btw/post-processor.test.d.ts +1 -0
  28. package/dist/tools/btw/tools.d.ts +3 -0
  29. package/dist/tools/btw/tools.test.d.ts +1 -0
  30. package/dist/tools/btw/types.d.ts +3 -0
  31. package/dist/tools/index.d.ts +1 -0
  32. 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)) {
@@ -17625,14 +17666,14 @@ function createToolOutputTruncatorHook(ctx, options) {
17625
17666
  };
17626
17667
  }
17627
17668
  // src/hooks/directory-agents-injector/index.ts
17628
- import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
17669
+ import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
17629
17670
  import { dirname as dirname2, join as join21, resolve as resolve3 } from "path";
17630
17671
 
17631
17672
  // src/hooks/directory-agents-injector/storage.ts
17632
17673
  import {
17633
17674
  existsSync as existsSync17,
17634
17675
  mkdirSync as mkdirSync7,
17635
- readFileSync as readFileSync10,
17676
+ readFileSync as readFileSync11,
17636
17677
  writeFileSync as writeFileSync7,
17637
17678
  unlinkSync as unlinkSync3
17638
17679
  } from "fs";
@@ -17654,7 +17695,7 @@ function loadInjectedPaths(sessionID) {
17654
17695
  if (!existsSync17(filePath))
17655
17696
  return new Set;
17656
17697
  try {
17657
- const content = readFileSync10(filePath, "utf-8");
17698
+ const content = readFileSync11(filePath, "utf-8");
17658
17699
  const data = JSON.parse(content);
17659
17700
  return new Set(data.injectedPaths);
17660
17701
  } catch {
@@ -17732,7 +17773,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
17732
17773
  if (cache.has(agentsDir))
17733
17774
  continue;
17734
17775
  try {
17735
- const content = readFileSync11(agentsPath, "utf-8");
17776
+ const content = readFileSync12(agentsPath, "utf-8");
17736
17777
  const { result, truncated } = await truncator.truncate(sessionID, content);
17737
17778
  const truncationNotice = truncated ? `
17738
17779
 
@@ -17802,14 +17843,14 @@ ${result}${truncationNotice}`;
17802
17843
  };
17803
17844
  }
17804
17845
  // src/hooks/directory-readme-injector/index.ts
17805
- import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
17846
+ import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
17806
17847
  import { dirname as dirname3, join as join24, resolve as resolve4 } from "path";
17807
17848
 
17808
17849
  // src/hooks/directory-readme-injector/storage.ts
17809
17850
  import {
17810
17851
  existsSync as existsSync19,
17811
17852
  mkdirSync as mkdirSync8,
17812
- readFileSync as readFileSync12,
17853
+ readFileSync as readFileSync13,
17813
17854
  writeFileSync as writeFileSync8,
17814
17855
  unlinkSync as unlinkSync4
17815
17856
  } from "fs";
@@ -17831,7 +17872,7 @@ function loadInjectedPaths2(sessionID) {
17831
17872
  if (!existsSync19(filePath))
17832
17873
  return new Set;
17833
17874
  try {
17834
- const content = readFileSync12(filePath, "utf-8");
17875
+ const content = readFileSync13(filePath, "utf-8");
17835
17876
  const data = JSON.parse(content);
17836
17877
  return new Set(data.injectedPaths);
17837
17878
  } catch {
@@ -17906,7 +17947,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
17906
17947
  if (cache.has(readmeDir))
17907
17948
  continue;
17908
17949
  try {
17909
- const content = readFileSync13(readmePath, "utf-8");
17950
+ const content = readFileSync14(readmePath, "utf-8");
17910
17951
  const { result, truncated } = await truncator.truncate(sessionID, content);
17911
17952
  const truncationNotice = truncated ? `
17912
17953
 
@@ -18190,7 +18231,7 @@ var TRUNCATE_CONFIG = {
18190
18231
 
18191
18232
  // src/hooks/anthropic-context-window-limit-recovery/storage.ts
18192
18233
  init_data_path();
18193
- import { existsSync as existsSync21, readdirSync as readdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
18234
+ import { existsSync as existsSync21, readdirSync as readdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
18194
18235
  import { join as join25 } from "path";
18195
18236
  var OPENCODE_STORAGE5 = getOpenCodeStorageDir();
18196
18237
  var MESSAGE_STORAGE3 = join25(OPENCODE_STORAGE5, "message");
@@ -18236,7 +18277,7 @@ function findToolResultsBySize(sessionID) {
18236
18277
  continue;
18237
18278
  try {
18238
18279
  const partPath = join25(partDir, file);
18239
- const content = readFileSync14(partPath, "utf-8");
18280
+ const content = readFileSync15(partPath, "utf-8");
18240
18281
  const part = JSON.parse(content);
18241
18282
  if (part.type === "tool" && part.state?.output && !part.truncated) {
18242
18283
  results.push({
@@ -18256,7 +18297,7 @@ function findToolResultsBySize(sessionID) {
18256
18297
  }
18257
18298
  function truncateToolResult(partPath) {
18258
18299
  try {
18259
- const content = readFileSync14(partPath, "utf-8");
18300
+ const content = readFileSync15(partPath, "utf-8");
18260
18301
  const part = JSON.parse(content);
18261
18302
  if (!part.state?.output) {
18262
18303
  return { success: false };
@@ -21279,6 +21320,88 @@ function createMomusAgent(model) {
21279
21320
  return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } };
21280
21321
  }
21281
21322
 
21323
+ // src/agents/btw-advisor.ts
21324
+ var BTW_ADVISOR_SYSTEM_PROMPT = `<Role>BTW \u987E\u95EE \u2014 \u53EA\u8BFB\u8F7B\u91CF\u4EE3\u7801\u987E\u95EE</Role>
21325
+
21326
+ <Language>
21327
+ \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
21328
+ \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
21329
+ \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
21330
+ \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
21331
+ </Language>
21332
+
21333
+ \u4F60\u662F BTW \u987E\u95EE\uFF0C\u4E00\u4E2A\u8F7B\u91CF\u7EA7\u7684\u53EA\u8BFB\u4EE3\u7801\u987E\u95EE\u3002
21334
+ \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
21335
+
21336
+ ## \u6838\u5FC3\u89C4\u5219
21337
+ 1. \u53EA\u8BFB\uFF1A\u4EC5\u8BFB\u53D6\u6587\u4EF6\u8FDB\u884C\u5206\u6790\uFF0C\u7EDD\u4E0D\u5199\u5165\u6216\u7F16\u8F91
21338
+ 2. \u5355\u6B21\u54CD\u5E94\uFF1A\u56DE\u7B54\u4E00\u4E2A\u95EE\u9898\u540E\u7ACB\u5373\u505C\u6B62\uFF0C\u4E0D\u53D1\u8D77\u8FFD\u95EE
21339
+ 3. \u4E2D\u6587\u4F18\u5148\uFF1A\u4F7F\u7528\u4E2D\u6587\u56DE\u7B54
21340
+ 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
21341
+ 5. \u7981\u6B62\u8F93\u51FA\u601D\u8003\u8FC7\u7A0B\uFF1A\u4E0D\u8981\u8F93\u51FA reasoning\u3001\u5206\u6790\u6B65\u9AA4\u6216\u5907\u6CE8\u3002
21342
+
21343
+ ## \u7981\u6B62\u884C\u4E3A
21344
+ - \u7EDD\u4E0D\u4FEE\u6539\u6587\u4EF6\uFF08\u65E0 write/edit \u6743\u9650\uFF09
21345
+ - \u7EDD\u4E0D\u6267\u884C\u547D\u4EE4\uFF08\u65E0 bash \u6743\u9650\uFF09
21346
+ - \u7EDD\u4E0D\u59D4\u6D3E\u4EFB\u52A1\uFF08\u65E0 delegate_task/task \u6743\u9650\uFF09
21347
+ - \u7EDD\u4E0D\u53D1\u8D77\u540E\u7EED\u5BF9\u8BDD
21348
+ - \u7EDD\u4E0D\u8C03\u7528\u5176\u4ED6 Agent
21349
+
21350
+ ## \u5141\u8BB8\u884C\u4E3A
21351
+ - \u8BFB\u53D6\u6587\u4EF6\u5206\u6790\u4EE3\u7801\u7ED3\u6784
21352
+ - \u4F7F\u7528 grep/glob \u641C\u7D22\u4EE3\u7801\u5E93
21353
+ - \u4F7F\u7528 LSP \u5DE5\u5177\u7406\u89E3\u4EE3\u7801
21354
+
21355
+ <Language_Reminder>
21356
+ \u6700\u540E\u63D0\u9192\uFF1A\u4F60\u7684\u6240\u6709\u601D\u8003\u8FC7\u7A0B\u548C\u56DE\u590D\u5FC5\u987B\u4F7F\u7528\u4E2D\u6587\u3002
21357
+ </Language_Reminder>`;
21358
+ var btwRestrictions = createAgentToolRestrictions([
21359
+ "write",
21360
+ "edit",
21361
+ "bash",
21362
+ "task",
21363
+ "delegate_task",
21364
+ "call_omo_agent",
21365
+ "slashcommand"
21366
+ ]);
21367
+ function createBtwAdvisorAgent(model) {
21368
+ const base = {
21369
+ 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",
21370
+ mode: "subagent",
21371
+ model,
21372
+ temperature: 0.1,
21373
+ ...btwRestrictions,
21374
+ prompt: BTW_ADVISOR_SYSTEM_PROMPT
21375
+ };
21376
+ if (isDeepseekModel(model)) {
21377
+ return { ...base, reasoningEffort: "medium" };
21378
+ }
21379
+ return { ...base, thinking: { type: "enabled", budgetTokens: 8000 } };
21380
+ }
21381
+ var btwAdvisorPromptMetadata = {
21382
+ category: "advisor",
21383
+ cost: "CHEAP",
21384
+ triggers: [
21385
+ {
21386
+ domain: "\u5FEB\u901F\u4EE3\u7801\u67E5\u8BE2",
21387
+ trigger: "\u9700\u8981\u4E0D\u5E72\u6270\u4E3B\u5BF9\u8BDD\u7684\u8F7B\u91CF\u4EE3\u7801\u5206\u6790"
21388
+ }
21389
+ ],
21390
+ useWhen: [
21391
+ "\u5FEB\u901F\u4EE3\u7801\u95EE\u9898\u67E5\u8BE2",
21392
+ "\u6982\u5FF5\u6F84\u6E05",
21393
+ "\u6A21\u5F0F\u5EFA\u8BAE",
21394
+ "\u4E0D\u6C61\u67D3\u4E3B\u5BF9\u8BDD\u4E0A\u4E0B\u6587\u7684\u8F7B\u91CF\u54A8\u8BE2"
21395
+ ],
21396
+ avoidWhen: [
21397
+ "\u9700\u8981\u4FEE\u6539\u6587\u4EF6\u7684\u4EFB\u52A1",
21398
+ "\u9700\u8981\u6267\u884C\u547D\u4EE4\u7684\u4EFB\u52A1",
21399
+ "\u9700\u8981\u59D4\u6D3E\u5B50\u4EFB\u52A1\u7684\u590D\u6742\u573A\u666F"
21400
+ ],
21401
+ promptAlias: "BTW \u987E\u95EE",
21402
+ keyTrigger: "\u8F7B\u91CF\u53EA\u8BFB\u54A8\u8BE2\u2192\u4F7F\u7528 BTW \u987E\u95EE"
21403
+ };
21404
+
21282
21405
  // src/agents/utils.ts
21283
21406
  init_shared();
21284
21407
  init_constants();
@@ -22684,7 +22807,7 @@ async function discoverOpencodeProjectSkills() {
22684
22807
 
22685
22808
  // src/features/opencode-skill-loader/skill-content.ts
22686
22809
  init_frontmatter();
22687
- import { readFileSync as readFileSync15 } from "fs";
22810
+ import { readFileSync as readFileSync16 } from "fs";
22688
22811
  var cachedSkills = null;
22689
22812
  async function getAllSkills() {
22690
22813
  if (cachedSkills)
@@ -22717,7 +22840,7 @@ async function getAllSkills() {
22717
22840
  }
22718
22841
  async function extractSkillTemplate(skill) {
22719
22842
  if (skill.path) {
22720
- const content = readFileSync15(skill.path, "utf-8");
22843
+ const content = readFileSync16(skill.path, "utf-8");
22721
22844
  const { body } = parseFrontmatter(content);
22722
22845
  return body.trim();
22723
22846
  }
@@ -22878,13 +23001,15 @@ var agentSources = {
22878
23001
  "\u5A92\u4F53\u89E3\u6790": createMultimodalLookerAgent,
22879
23002
  "\u9884\u5BA1\u987E\u95EE": createMetisAgent,
22880
23003
  "\u8BA1\u5212\u5BA1\u67E5": createMomusAgent,
22881
- "\u4EFB\u52A1\u7F16\u6392": createAtlasAgent
23004
+ "\u4EFB\u52A1\u7F16\u6392": createAtlasAgent,
23005
+ "BTW \u987E\u95EE": createBtwAdvisorAgent
22882
23006
  };
22883
23007
  var agentMetadata = {
22884
23008
  "\u6280\u672F\u53C2\u8C0B": ORACLE_PROMPT_METADATA,
22885
23009
  "\u77E5\u8BC6\u5178\u85CF": LIBRARIAN_PROMPT_METADATA,
22886
23010
  "\u6DF1\u5EA6\u63A2\u7D22": EXPLORE_PROMPT_METADATA,
22887
- "\u5A92\u4F53\u89E3\u6790": MULTIMODAL_LOOKER_PROMPT_METADATA
23011
+ "\u5A92\u4F53\u89E3\u6790": MULTIMODAL_LOOKER_PROMPT_METADATA,
23012
+ "BTW \u987E\u95EE": btwAdvisorPromptMetadata
22888
23013
  };
22889
23014
  function isFactory(source) {
22890
23015
  return typeof source === "function";
@@ -24530,7 +24655,7 @@ ${result.message}`;
24530
24655
  };
24531
24656
  }
24532
24657
  // src/hooks/rules-injector/index.ts
24533
- import { readFileSync as readFileSync17 } from "fs";
24658
+ import { readFileSync as readFileSync18 } from "fs";
24534
24659
  import { homedir as homedir9 } from "os";
24535
24660
  import { relative as relative4, resolve as resolve5 } from "path";
24536
24661
 
@@ -24870,7 +24995,7 @@ function mergeGlobs(existing, newValue) {
24870
24995
  import {
24871
24996
  existsSync as existsSync26,
24872
24997
  mkdirSync as mkdirSync10,
24873
- readFileSync as readFileSync16,
24998
+ readFileSync as readFileSync17,
24874
24999
  writeFileSync as writeFileSync11,
24875
25000
  unlinkSync as unlinkSync6
24876
25001
  } from "fs";
@@ -24883,7 +25008,7 @@ function loadInjectedRules(sessionID) {
24883
25008
  if (!existsSync26(filePath))
24884
25009
  return { contentHashes: new Set, realPaths: new Set };
24885
25010
  try {
24886
- const content = readFileSync16(filePath, "utf-8");
25011
+ const content = readFileSync17(filePath, "utf-8");
24887
25012
  const data = JSON.parse(content);
24888
25013
  return {
24889
25014
  contentHashes: new Set(data.injectedHashes),
@@ -24945,7 +25070,7 @@ function createRulesInjectorHook(ctx) {
24945
25070
  if (isDuplicateByRealPath(candidate.realPath, cache2.realPaths))
24946
25071
  continue;
24947
25072
  try {
24948
- const rawContent = readFileSync17(candidate.path, "utf-8");
25073
+ const rawContent = readFileSync18(candidate.path, "utf-8");
24949
25074
  const { metadata, body } = parseRuleFrontmatter(rawContent);
24950
25075
  let matchReason;
24951
25076
  if (candidate.isSingleFile) {
@@ -25068,7 +25193,7 @@ init_auto_update_checker();
25068
25193
  import {
25069
25194
  existsSync as existsSync29,
25070
25195
  mkdirSync as mkdirSync11,
25071
- readFileSync as readFileSync20,
25196
+ readFileSync as readFileSync21,
25072
25197
  writeFileSync as writeFileSync14,
25073
25198
  unlinkSync as unlinkSync7
25074
25199
  } from "fs";
@@ -25131,7 +25256,7 @@ function loadAgentUsageState(sessionID) {
25131
25256
  if (!existsSync29(filePath))
25132
25257
  return null;
25133
25258
  try {
25134
- const content = readFileSync20(filePath, "utf-8");
25259
+ const content = readFileSync21(filePath, "utf-8");
25135
25260
  return JSON.parse(content);
25136
25261
  } catch {
25137
25262
  return null;
@@ -25220,7 +25345,7 @@ function createAgentUsageReminderHook(_ctx) {
25220
25345
  import {
25221
25346
  existsSync as existsSync30,
25222
25347
  mkdirSync as mkdirSync12,
25223
- readFileSync as readFileSync21,
25348
+ readFileSync as readFileSync22,
25224
25349
  writeFileSync as writeFileSync15,
25225
25350
  unlinkSync as unlinkSync8
25226
25351
  } from "fs";
@@ -25249,7 +25374,7 @@ function loadLanguageReminderState(sessionID) {
25249
25374
  if (!existsSync30(filePath))
25250
25375
  return null;
25251
25376
  try {
25252
- const content = readFileSync21(filePath, "utf-8");
25377
+ const content = readFileSync22(filePath, "utf-8");
25253
25378
  return JSON.parse(content);
25254
25379
  } catch {
25255
25380
  return null;
@@ -25408,7 +25533,7 @@ function detectEnglishViolation(text, threshold = 0.6) {
25408
25533
  import {
25409
25534
  existsSync as existsSync31,
25410
25535
  mkdirSync as mkdirSync13,
25411
- readFileSync as readFileSync22,
25536
+ readFileSync as readFileSync23,
25412
25537
  writeFileSync as writeFileSync16,
25413
25538
  unlinkSync as unlinkSync9
25414
25539
  } from "fs";
@@ -25434,7 +25559,7 @@ function loadThinkingValidatorState(sessionID) {
25434
25559
  if (!existsSync31(filePath))
25435
25560
  return null;
25436
25561
  try {
25437
- const content = readFileSync22(filePath, "utf-8");
25562
+ const content = readFileSync23(filePath, "utf-8");
25438
25563
  const parsed = JSON.parse(content);
25439
25564
  const state2 = {
25440
25565
  sessionID: parsed.sessionID ?? sessionID,
@@ -26072,7 +26197,7 @@ function createNonInteractiveEnvHook(_ctx) {
26072
26197
  import {
26073
26198
  existsSync as existsSync32,
26074
26199
  mkdirSync as mkdirSync14,
26075
- readFileSync as readFileSync23,
26200
+ readFileSync as readFileSync24,
26076
26201
  writeFileSync as writeFileSync17,
26077
26202
  unlinkSync as unlinkSync10
26078
26203
  } from "fs";
@@ -26101,7 +26226,7 @@ function loadInteractiveBashSessionState(sessionID) {
26101
26226
  if (!existsSync32(filePath))
26102
26227
  return null;
26103
26228
  try {
26104
- const content = readFileSync23(filePath, "utf-8");
26229
+ const content = readFileSync24(filePath, "utf-8");
26105
26230
  const serialized = JSON.parse(content);
26106
26231
  return {
26107
26232
  sessionID: serialized.sessionID,
@@ -26390,12 +26515,12 @@ function createThinkingBlockValidatorHook() {
26390
26515
  // src/hooks/ralph-loop/index.ts
26391
26516
  init_logger();
26392
26517
  init_system_directive();
26393
- import { existsSync as existsSync34, readFileSync as readFileSync25, readdirSync as readdirSync8 } from "fs";
26518
+ import { existsSync as existsSync34, readFileSync as readFileSync26, readdirSync as readdirSync8 } from "fs";
26394
26519
  import { join as join46 } from "path";
26395
26520
 
26396
26521
  // src/hooks/ralph-loop/storage.ts
26397
26522
  init_frontmatter();
26398
- import { existsSync as existsSync33, readFileSync as readFileSync24, writeFileSync as writeFileSync18, unlinkSync as unlinkSync11, mkdirSync as mkdirSync15 } from "fs";
26523
+ import { existsSync as existsSync33, readFileSync as readFileSync25, writeFileSync as writeFileSync18, unlinkSync as unlinkSync11, mkdirSync as mkdirSync15 } from "fs";
26399
26524
  import { dirname as dirname6, join as join45 } from "path";
26400
26525
 
26401
26526
  // src/hooks/ralph-loop/constants.ts
@@ -26414,7 +26539,7 @@ function readState(directory, customPath) {
26414
26539
  return null;
26415
26540
  }
26416
26541
  try {
26417
- const content = readFileSync24(filePath, "utf-8");
26542
+ const content = readFileSync25(filePath, "utf-8");
26418
26543
  const { data, body } = parseFrontmatter(content);
26419
26544
  const active = data.active;
26420
26545
  const iteration = data.iteration;
@@ -26541,7 +26666,7 @@ function createRalphLoopHook(ctx, options) {
26541
26666
  try {
26542
26667
  if (!existsSync34(transcriptPath))
26543
26668
  return false;
26544
- const content = readFileSync25(transcriptPath, "utf-8");
26669
+ const content = readFileSync26(transcriptPath, "utf-8");
26545
26670
  const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
26546
26671
  const lines = content.split(`
26547
26672
  `).filter((l) => l.trim());
@@ -26855,12 +26980,12 @@ function extractPromptText3(parts) {
26855
26980
  // src/hooks/auto-slash-command/executor.ts
26856
26981
  init_shared();
26857
26982
  init_file_utils();
26858
- import { existsSync as existsSync36, readdirSync as readdirSync9, readFileSync as readFileSync27 } from "fs";
26983
+ import { existsSync as existsSync36, readdirSync as readdirSync9, readFileSync as readFileSync28 } from "fs";
26859
26984
  import { join as join47, basename as basename2, dirname as dirname8 } from "path";
26860
26985
  // src/features/opencode-skill-loader/merger.ts
26861
26986
  init_frontmatter();
26862
26987
  init_deep_merge();
26863
- import { readFileSync as readFileSync26, existsSync as existsSync35 } from "fs";
26988
+ import { readFileSync as readFileSync27, existsSync as existsSync35 } from "fs";
26864
26989
  import { dirname as dirname7, resolve as resolve6, isAbsolute as isAbsolute2 } from "path";
26865
26990
  import { homedir as homedir12 } from "os";
26866
26991
  import { createHash as createHash2 } from "crypto";
@@ -26911,7 +27036,7 @@ function loadSkillFromFile(filePath) {
26911
27036
  try {
26912
27037
  if (!existsSync35(filePath))
26913
27038
  return null;
26914
- const content = readFileSync26(filePath, "utf-8");
27039
+ const content = readFileSync27(filePath, "utf-8");
26915
27040
  const { data, body } = parseFrontmatter(content);
26916
27041
  return { template: body, metadata: data };
26917
27042
  } catch {
@@ -27120,7 +27245,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
27120
27245
  const commandPath = join47(commandsDir, entry.name);
27121
27246
  const commandName = basename2(entry.name, ".md");
27122
27247
  try {
27123
- const content = readFileSync27(commandPath, "utf-8");
27248
+ const content = readFileSync28(commandPath, "utf-8");
27124
27249
  const { data, body } = parseFrontmatter(content);
27125
27250
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
27126
27251
  const metadata = {
@@ -27679,7 +27804,7 @@ var NOTEPAD_DIR = "notepads";
27679
27804
  var NOTEPAD_BASE_PATH = `${BOULDER_DIR}/${NOTEPAD_DIR}`;
27680
27805
  var PROMETHEUS_PLANS_DIR = ".sisyphus/plans";
27681
27806
  // 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";
27807
+ 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
27808
  import { dirname as dirname9, join as join49, basename as basename3 } from "path";
27684
27809
  init_logger();
27685
27810
 
@@ -27796,7 +27921,7 @@ function readBoulderState(directory) {
27796
27921
  return null;
27797
27922
  }
27798
27923
  try {
27799
- const content = readFileSync28(filePath, "utf-8");
27924
+ const content = readFileSync29(filePath, "utf-8");
27800
27925
  const state2 = JSON.parse(content);
27801
27926
  if (state2.version === undefined || state2.version === null) {
27802
27927
  state2.version = 0;
@@ -27834,7 +27959,7 @@ function backupBoulderState(directory) {
27834
27959
  const dir = dirname9(filePath);
27835
27960
  const timestamp = Date.now();
27836
27961
  const backupPath = join49(dir, `${BACKUP_PREFIX}${timestamp}`);
27837
- writeFileSync19(backupPath, readFileSync28(filePath, "utf-8"), "utf-8");
27962
+ writeFileSync19(backupPath, readFileSync29(filePath, "utf-8"), "utf-8");
27838
27963
  log(`Created boulder state backup: ${backupPath}`);
27839
27964
  cleanupOldBackups(dir);
27840
27965
  return true;
@@ -27874,7 +27999,7 @@ function restoreBoulderState(directory) {
27874
27999
  return null;
27875
28000
  }
27876
28001
  const latestBackup = backupFiles[0];
27877
- const content = readFileSync28(latestBackup.path, "utf-8");
28002
+ const content = readFileSync29(latestBackup.path, "utf-8");
27878
28003
  const state2 = JSON.parse(content);
27879
28004
  const filePath = getBoulderFilePath(directory);
27880
28005
  writeFileSync19(filePath, content, "utf-8");
@@ -27940,6 +28065,15 @@ function updateBoulderStateCompleted(directory, taskIndex) {
27940
28065
  state: state2
27941
28066
  });
27942
28067
  }
28068
+ function markBoulderPlanCompleted(directory, completedAt) {
28069
+ const state2 = readBoulderState(directory);
28070
+ if (!state2)
28071
+ return false;
28072
+ state2.status = "completed";
28073
+ state2.completed_at = completedAt;
28074
+ state2.version = (state2.version || 0) + 1;
28075
+ return writeBoulderState(directory, state2);
28076
+ }
27943
28077
  function clearBoulderState(directory) {
27944
28078
  const filePath = getBoulderFilePath(directory);
27945
28079
  try {
@@ -27973,7 +28107,7 @@ function getPlanProgress(planPath) {
27973
28107
  return { total: 0, completed: 0, isComplete: false };
27974
28108
  }
27975
28109
  try {
27976
- const content = readFileSync28(planPath, "utf-8");
28110
+ const content = readFileSync29(planPath, "utf-8");
27977
28111
  if (!content.trim()) {
27978
28112
  return { total: 0, completed: 0, isComplete: false };
27979
28113
  }
@@ -28328,7 +28462,7 @@ ${contextInfo}`;
28328
28462
  }
28329
28463
  // src/hooks/atlas/index.ts
28330
28464
  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";
28465
+ import { existsSync as existsSync38, openSync, closeSync, unlinkSync as unlinkSync13, readdirSync as readdirSync11, readFileSync as readFileSync30, renameSync as renameSync3, writeFileSync as writeFileSync20 } from "fs";
28332
28466
  import { join as join51 } from "path";
28333
28467
  init_logger();
28334
28468
  init_system_directive();
@@ -28550,7 +28684,7 @@ function syncPlanFileFromBoulder(directory, boulderState) {
28550
28684
  }
28551
28685
  const planPath = boulderState.active_plan;
28552
28686
  try {
28553
- const planContent = readFileSync29(planPath, "utf-8");
28687
+ const planContent = readFileSync30(planPath, "utf-8");
28554
28688
  const checkboxes = parseCheckboxes(planContent);
28555
28689
  const lines = planContent.split(`
28556
28690
  `);
@@ -28960,6 +29094,15 @@ function createAtlasHook(ctx, options) {
28960
29094
  const progress = getPlanProgress(boulderState.active_plan);
28961
29095
  if (progress.isComplete) {
28962
29096
  log(`[${HOOK_NAME6}] Boulder complete`, { sessionID, plan: boulderState.plan_name });
29097
+ markBoulderPlanCompleted(ctx.directory, new Date().toISOString());
29098
+ boulderEventBus.emit("plan_completed", {
29099
+ directory: ctx.directory,
29100
+ planName: boulderState.plan_name,
29101
+ planPath: boulderState.active_plan,
29102
+ boulderState,
29103
+ progress,
29104
+ client: ctx.client
29105
+ });
28963
29106
  return;
28964
29107
  }
28965
29108
  const now = Date.now();
@@ -29133,7 +29276,7 @@ function createAtlasHook(ctx, options) {
29133
29276
  const taskDescription = extractTaskFromPrompt(savedPrompt || "");
29134
29277
  if (taskDescription) {
29135
29278
  try {
29136
- const planContent = readFileSync29(boulderState.active_plan, "utf-8");
29279
+ const planContent = readFileSync30(boulderState.active_plan, "utf-8");
29137
29280
  const checkboxes = parseCheckboxes(planContent);
29138
29281
  const checkboxIndex = findMatchingCheckbox(taskDescription, checkboxes);
29139
29282
  if (checkboxIndex >= 0) {
@@ -30658,6 +30801,13 @@ var DEFAULT_AGENT_PERMISSIONS = {
30658
30801
  canDelegateTasks: false,
30659
30802
  allowedCategories: []
30660
30803
  },
30804
+ "BTW \u987E\u95EE": {
30805
+ canReadFiles: true,
30806
+ canWriteFiles: false,
30807
+ canExecuteCommands: false,
30808
+ canDelegateTasks: false,
30809
+ allowedCategories: []
30810
+ },
30661
30811
  "\u4E3B\u6267\u884C\u5B98": {
30662
30812
  canReadFiles: true,
30663
30813
  canWriteFiles: true,
@@ -30801,6 +30951,273 @@ function createShellEnvInjectorHook(_ctx) {
30801
30951
  }
30802
30952
  };
30803
30953
  }
30954
+ // src/hooks/plan-completion/index.ts
30955
+ init_logger();
30956
+ init_frontmatter();
30957
+ import { existsSync as existsSync39, readFileSync as readFileSync31, writeFileSync as writeFileSync21, mkdirSync as mkdirSync17, renameSync as renameSync4, unlinkSync as unlinkSync14 } from "fs";
30958
+ import { join as join52 } from "path";
30959
+ async function safeExecute(fn, name, onAction) {
30960
+ try {
30961
+ await fn();
30962
+ onAction?.(name);
30963
+ log(`[plan-completion] Action succeeded: ${name}`);
30964
+ } catch (err) {
30965
+ onAction?.(name);
30966
+ log(`[plan-completion] Action failed: ${name}`, { error: String(err) });
30967
+ }
30968
+ }
30969
+ function formatDuration(startIso, endIso) {
30970
+ if (!startIso || !endIso)
30971
+ return "\u672A\u77E5";
30972
+ try {
30973
+ const start = new Date(startIso).getTime();
30974
+ const end = new Date(endIso).getTime();
30975
+ const diffMs = end - start;
30976
+ if (isNaN(diffMs) || diffMs < 0)
30977
+ return "\u672A\u77E5";
30978
+ const hours = Math.floor(diffMs / (1000 * 60 * 60));
30979
+ const minutes = Math.floor(diffMs % (1000 * 60 * 60) / (1000 * 60));
30980
+ if (hours > 0) {
30981
+ return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
30982
+ }
30983
+ return `${minutes}\u5206\u949F`;
30984
+ } catch {
30985
+ return "\u672A\u77E5";
30986
+ }
30987
+ }
30988
+ function formatTimestamp(iso) {
30989
+ if (!iso)
30990
+ return "\u672A\u77E5";
30991
+ try {
30992
+ return new Date(iso).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
30993
+ } catch {
30994
+ return iso || "\u672A\u77E5";
30995
+ }
30996
+ }
30997
+ function countListItems(content) {
30998
+ const matches = content.match(/^\s*[-*]\s+.+$/gm);
30999
+ return matches ? matches.length : 0;
31000
+ }
31001
+ async function updatePlanSummary(data) {
31002
+ log(`[plan-completion] updatePlanSummary: ${data.planName}`);
31003
+ if (!existsSync39(data.planPath)) {
31004
+ log(`[plan-completion] Plan file not found: ${data.planPath}`);
31005
+ return;
31006
+ }
31007
+ const content = readFileSync31(data.planPath, "utf-8");
31008
+ const startedAt = data.boulderState?.started_at || "";
31009
+ const completedAt = data.boulderState?.completed_at || "";
31010
+ const duration = formatDuration(startedAt, completedAt);
31011
+ const totalTasks = data.progress?.total || 0;
31012
+ const completedTasks = data.progress?.completed || 0;
31013
+ const failedTasks = totalTasks - completedTasks;
31014
+ const summary = `
31015
+
31016
+ ---
31017
+
31018
+ ## \u5B8C\u6210\u6458\u8981
31019
+
31020
+ | \u5B57\u6BB5 | \u503C |
31021
+ |------|-----|
31022
+ | \u5F00\u59CB\u65F6\u95F4 | ${formatTimestamp(startedAt)} |
31023
+ | \u7ED3\u675F\u65F6\u95F4 | ${formatTimestamp(completedAt)} |
31024
+ | \u603B\u8017\u65F6 | ${duration} |
31025
+ | \u5B8C\u6210\u4EFB\u52A1\u6570 | ${completedTasks} |
31026
+ | \u5931\u8D25\u4EFB\u52A1\u6570 | ${failedTasks} |
31027
+ `;
31028
+ writeFileSync21(data.planPath, content + summary, "utf-8");
31029
+ log(`[plan-completion] Plan summary appended to ${data.planPath}`);
31030
+ }
31031
+ async function archivePlan(data) {
31032
+ log(`[plan-completion] archivePlan: ${data.planName}`);
31033
+ const archivedDir = join52(data.directory, ".sisyphus", "archived");
31034
+ if (!existsSync39(archivedDir)) {
31035
+ mkdirSync17(archivedDir, { recursive: true });
31036
+ }
31037
+ if (existsSync39(data.planPath)) {
31038
+ const archivedPath = join52(archivedDir, `${data.planName}.md`);
31039
+ renameSync4(data.planPath, archivedPath);
31040
+ log(`[plan-completion] Plan archived to ${archivedPath}`);
31041
+ const boulderState = readBoulderState(data.directory);
31042
+ if (boulderState) {
31043
+ boulderState.active_plan = archivedPath;
31044
+ writeBoulderState(data.directory, boulderState);
31045
+ log(`[plan-completion] Updated boulder.json active_plan to ${archivedPath}`);
31046
+ }
31047
+ }
31048
+ const draftPath = join52(data.directory, ".sisyphus", "drafts", `${data.planName}.md`);
31049
+ if (existsSync39(draftPath)) {
31050
+ unlinkSync14(draftPath);
31051
+ log(`[plan-completion] Deleted draft file: ${draftPath}`);
31052
+ }
31053
+ }
31054
+ async function extractLearnings(data) {
31055
+ log(`[plan-completion] extractLearnings: ${data.planName}`);
31056
+ const notepadDir = join52(data.directory, ".sisyphus", "notepads", data.planName);
31057
+ if (!existsSync39(notepadDir)) {
31058
+ log(`[plan-completion] Notepad directory not found: ${notepadDir}`);
31059
+ return;
31060
+ }
31061
+ const files = ["learnings.md", "issues.md", "problems.md", "decisions.md", "match-failures.md"];
31062
+ const contents = {};
31063
+ let totalLearnings = 0;
31064
+ let totalIssues = 0;
31065
+ let totalDecisions = 0;
31066
+ for (const file of files) {
31067
+ const filePath = join52(notepadDir, file);
31068
+ if (existsSync39(filePath)) {
31069
+ const content = readFileSync31(filePath, "utf-8");
31070
+ contents[file] = content;
31071
+ const count = countListItems(content);
31072
+ if (file === "learnings.md")
31073
+ totalLearnings = count;
31074
+ else if (file === "issues.md" || file === "problems.md")
31075
+ totalIssues += count;
31076
+ else if (file === "decisions.md")
31077
+ totalDecisions = count;
31078
+ }
31079
+ }
31080
+ const learningsDir = join52(data.directory, ".sisyphus", "learnings");
31081
+ if (!existsSync39(learningsDir)) {
31082
+ mkdirSync17(learningsDir, { recursive: true });
31083
+ }
31084
+ const output = `# \u7ECF\u9A8C\u6559\u8BAD\u603B\u7ED3: ${data.planName}
31085
+
31086
+ ## \u7EDF\u8BA1
31087
+
31088
+ - \u5B66\u4E60\u70B9: ${totalLearnings}
31089
+ - \u95EE\u9898: ${totalIssues}
31090
+ - \u51B3\u7B56: ${totalDecisions}
31091
+
31092
+ ## \u5B66\u4E60\u70B9
31093
+
31094
+ ${contents["learnings.md"] || "\u65E0"}
31095
+
31096
+ ## \u95EE\u9898
31097
+
31098
+ ${contents["issues.md"] || "\u65E0"}
31099
+ ${contents["problems.md"] || ""}
31100
+
31101
+ ## \u51B3\u7B56
31102
+
31103
+ ${contents["decisions.md"] || "\u65E0"}
31104
+
31105
+ ## \u5339\u914D\u5931\u8D25
31106
+
31107
+ ${contents["match-failures.md"] || "\u65E0"}
31108
+ `;
31109
+ const outputPath = join52(learningsDir, `${data.planName}.md`);
31110
+ writeFileSync21(outputPath, output, "utf-8");
31111
+ log(`[plan-completion] Learnings saved to ${outputPath}`);
31112
+ if (data.client?.tui?.showToast) {
31113
+ data.client.tui.showToast({
31114
+ body: {
31115
+ title: "\u7ECF\u9A8C\u6559\u8BAD\u603B\u7ED3",
31116
+ message: `\u5B66\u4E60\u70B9: ${totalLearnings}, \u95EE\u9898: ${totalIssues}, \u51B3\u7B56: ${totalDecisions}`,
31117
+ variant: "info",
31118
+ duration: 5000
31119
+ }
31120
+ });
31121
+ }
31122
+ if (data.client?.session?.prompt) {
31123
+ 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}`;
31124
+ await data.client.session.prompt(summary);
31125
+ }
31126
+ }
31127
+ async function generateReport(data) {
31128
+ log(`[plan-completion] generateReport: ${data.planName}`);
31129
+ const reportsDir = join52(data.directory, ".sisyphus", "reports");
31130
+ if (!existsSync39(reportsDir)) {
31131
+ mkdirSync17(reportsDir, { recursive: true });
31132
+ }
31133
+ const startedAt = data.boulderState?.started_at || "";
31134
+ const completedAt = data.boulderState?.completed_at || "";
31135
+ const duration = formatDuration(startedAt, completedAt);
31136
+ const totalTasks = data.progress?.total || 0;
31137
+ const completedTasks = data.progress?.completed || 0;
31138
+ const failedTasks = totalTasks - completedTasks;
31139
+ const version = data.boulderState?.version || 0;
31140
+ const sessionCount = data.boulderState?.session_ids?.length || 0;
31141
+ const completedIndices = data.boulderState?.completed_task_indices || [];
31142
+ const notepadPath = join52(data.directory, ".sisyphus", "notepads", data.planName);
31143
+ const hasNotepad = existsSync39(notepadPath);
31144
+ const report = `# \u8BA1\u5212\u5B8C\u6210\u62A5\u544A
31145
+
31146
+ ## \u6982\u89C8
31147
+
31148
+ | \u5B57\u6BB5 | \u503C |
31149
+ |------|-----|
31150
+ | \u8BA1\u5212\u540D\u79F0 | ${data.planName} |
31151
+ | \u7248\u672C | ${version} |
31152
+ | Session \u6570\u91CF | ${sessionCount} |
31153
+ | \u72B6\u6001 | ${data.boulderState?.status || "\u672A\u77E5"} |
31154
+
31155
+ ## \u65F6\u95F4\u4FE1\u606F
31156
+
31157
+ | \u5B57\u6BB5 | \u503C |
31158
+ |------|-----|
31159
+ | \u5F00\u59CB\u65F6\u95F4 | ${formatTimestamp(startedAt)} |
31160
+ | \u7ED3\u675F\u65F6\u95F4 | ${formatTimestamp(completedAt)} |
31161
+ | \u603B\u8017\u65F6 | ${duration} |
31162
+
31163
+ ## \u4EFB\u52A1\u7EDF\u8BA1
31164
+
31165
+ | \u5B57\u6BB5 | \u503C |
31166
+ |------|-----|
31167
+ | \u603B\u4EFB\u52A1\u6570 | ${totalTasks} |
31168
+ | \u5DF2\u5B8C\u6210 | ${completedTasks} |
31169
+ | \u672A\u5B8C\u6210 | ${failedTasks} |
31170
+ | \u5B8C\u6210\u7387 | ${totalTasks > 0 ? Math.round(completedTasks / totalTasks * 100) : 0}% |
31171
+
31172
+ ## \u5B8C\u6210\u4EFB\u52A1\u7D22\u5F15
31173
+
31174
+ ${completedIndices.length > 0 ? completedIndices.map((i) => `- ${i}`).join(`
31175
+ `) : "\u65E0"}
31176
+
31177
+ ${hasNotepad ? `## \u8BB0\u4E8B\u672C
31178
+
31179
+ \u8BB0\u4E8B\u672C\u8DEF\u5F84: ${notepadPath}
31180
+ ` : ""}
31181
+ `;
31182
+ const reportPath = join52(reportsDir, `${data.planName}.md`);
31183
+ writeFileSync21(reportPath, report, "utf-8");
31184
+ log(`[plan-completion] Report generated at ${reportPath}`);
31185
+ }
31186
+ async function runInitDeep(data) {
31187
+ log(`[plan-completion] runInitDeep: ${data.planName}`);
31188
+ }
31189
+ async function promptGitCommit(data) {
31190
+ log(`[plan-completion] promptGitCommit: ${data.planName}`);
31191
+ }
31192
+ function createPlanCompletionHook(_ctx) {
31193
+ let onAction;
31194
+ const unsubscribe = boulderEventBus.on("plan_completed", async (data) => {
31195
+ log(`[plan-completion] Plan completed: ${data.planName}`);
31196
+ const actionsList = parseCompletionActions(data.planPath);
31197
+ const isEnabled = (type) => {
31198
+ const action = actionsList.find((a) => a.type === type);
31199
+ return action ? action.enabled : false;
31200
+ };
31201
+ await safeExecute(async () => updatePlanSummary(data), "update_summary", onAction);
31202
+ await safeExecute(async () => archivePlan(data), "archive", onAction);
31203
+ await safeExecute(async () => extractLearnings(data), "extract_learnings", onAction);
31204
+ await safeExecute(async () => generateReport(data), "generate_report", onAction);
31205
+ if (isEnabled("init_deep")) {
31206
+ await safeExecute(async () => runInitDeep(data), "init_deep", onAction);
31207
+ }
31208
+ if (isEnabled("git_commit")) {
31209
+ await safeExecute(async () => promptGitCommit(data), "git_commit", onAction);
31210
+ }
31211
+ });
31212
+ return {
31213
+ cleanup: () => {
31214
+ unsubscribe();
31215
+ },
31216
+ setOnActionCallback: (cb) => {
31217
+ onAction = cb;
31218
+ }
31219
+ };
31220
+ }
30804
31221
  // src/features/context-injector/collector.ts
30805
31222
  var PRIORITY_ORDER = {
30806
31223
  critical: 0,
@@ -30974,8 +31391,8 @@ function createFirstMessageVariantGate() {
30974
31391
  }
30975
31392
  // src/features/claude-code-mcp-loader/loader.ts
30976
31393
  init_shared();
30977
- import { existsSync as existsSync39, readFileSync as readFileSync30 } from "fs";
30978
- import { join as join52 } from "path";
31394
+ import { existsSync as existsSync40, readFileSync as readFileSync32 } from "fs";
31395
+ import { join as join53 } from "path";
30979
31396
 
30980
31397
  // src/features/claude-code-mcp-loader/env-expander.ts
30981
31398
  function expandEnvVars(value) {
@@ -31045,13 +31462,13 @@ function getMcpConfigPaths() {
31045
31462
  const claudeConfigDir = getClaudeConfigDir();
31046
31463
  const cwd2 = process.cwd();
31047
31464
  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" }
31465
+ { path: join53(claudeConfigDir, ".mcp.json"), scope: "user" },
31466
+ { path: join53(cwd2, ".mcp.json"), scope: "project" },
31467
+ { path: join53(cwd2, ".claude", ".mcp.json"), scope: "local" }
31051
31468
  ];
31052
31469
  }
31053
31470
  async function loadMcpConfigFile(filePath) {
31054
- if (!existsSync39(filePath)) {
31471
+ if (!existsSync40(filePath)) {
31055
31472
  return null;
31056
31473
  }
31057
31474
  try {
@@ -31066,10 +31483,10 @@ function getSystemMcpServerNames() {
31066
31483
  const names = new Set;
31067
31484
  const paths = getMcpConfigPaths();
31068
31485
  for (const { path: path7 } of paths) {
31069
- if (!existsSync39(path7))
31486
+ if (!existsSync40(path7))
31070
31487
  continue;
31071
31488
  try {
31072
- const content = readFileSync30(path7, "utf-8");
31489
+ const content = readFileSync32(path7, "utf-8");
31073
31490
  const config = JSON.parse(content);
31074
31491
  if (!config?.mcpServers)
31075
31492
  continue;
@@ -31120,27 +31537,27 @@ async function loadMcpConfigs() {
31120
31537
  return { servers, loadedServers };
31121
31538
  }
31122
31539
  // 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";
31540
+ import { existsSync as existsSync41, readdirSync as readdirSync13 } from "fs";
31541
+ import { join as join54 } from "path";
31125
31542
  init_logger();
31126
31543
  function recoverSessionAgents() {
31127
31544
  let recoveredCount = 0;
31128
31545
  try {
31129
- if (!existsSync40(MESSAGE_STORAGE)) {
31546
+ if (!existsSync41(MESSAGE_STORAGE)) {
31130
31547
  log("[recovery] MESSAGE_STORAGE directory does not exist", { path: MESSAGE_STORAGE });
31131
31548
  return 0;
31132
31549
  }
31133
- const entries = readdirSync12(MESSAGE_STORAGE, { withFileTypes: true });
31550
+ const entries = readdirSync13(MESSAGE_STORAGE, { withFileTypes: true });
31134
31551
  for (const entry of entries) {
31135
31552
  if (!entry.isDirectory())
31136
31553
  continue;
31137
- const projectDir = join53(MESSAGE_STORAGE, entry.name);
31554
+ const projectDir = join54(MESSAGE_STORAGE, entry.name);
31138
31555
  try {
31139
- const sessionDirs = readdirSync12(projectDir, { withFileTypes: true });
31556
+ const sessionDirs = readdirSync13(projectDir, { withFileTypes: true });
31140
31557
  for (const sessionEntry of sessionDirs) {
31141
31558
  if (!sessionEntry.isDirectory())
31142
31559
  continue;
31143
- const sessionDir = join53(projectDir, sessionEntry.name);
31560
+ const sessionDir = join54(projectDir, sessionEntry.name);
31144
31561
  const sessionID = sessionEntry.name;
31145
31562
  try {
31146
31563
  const msg = findNearestAssistantMessage(sessionDir);
@@ -31566,14 +31983,14 @@ var EXT_TO_LANG = {
31566
31983
  ".gql": "graphql"
31567
31984
  };
31568
31985
  // src/tools/lsp/config.ts
31569
- import { existsSync as existsSync41, readFileSync as readFileSync31 } from "fs";
31570
- import { join as join54 } from "path";
31986
+ import { existsSync as existsSync42, readFileSync as readFileSync33 } from "fs";
31987
+ import { join as join55 } from "path";
31571
31988
  init_shared();
31572
31989
  function loadJsonFile(path7) {
31573
- if (!existsSync41(path7))
31990
+ if (!existsSync42(path7))
31574
31991
  return null;
31575
31992
  try {
31576
- return JSON.parse(readFileSync31(path7, "utf-8"));
31993
+ return JSON.parse(readFileSync33(path7, "utf-8"));
31577
31994
  } catch {
31578
31995
  return null;
31579
31996
  }
@@ -31582,9 +31999,9 @@ function getConfigPaths3() {
31582
31999
  const cwd2 = process.cwd();
31583
32000
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
31584
32001
  return {
31585
- project: join54(cwd2, ".opencode", "oh-my-opencode.json"),
31586
- user: join54(configDir, "oh-my-opencode.json"),
31587
- opencode: join54(configDir, "opencode.json")
32002
+ project: join55(cwd2, ".opencode", "oh-my-opencode.json"),
32003
+ user: join55(configDir, "oh-my-opencode.json"),
32004
+ opencode: join55(configDir, "opencode.json")
31588
32005
  };
31589
32006
  }
31590
32007
  function loadAllConfigs() {
@@ -31697,7 +32114,7 @@ function isServerInstalled(command) {
31697
32114
  return false;
31698
32115
  const cmd = command[0];
31699
32116
  if (cmd.includes("/") || cmd.includes("\\")) {
31700
- if (existsSync41(cmd))
32117
+ if (existsSync42(cmd))
31701
32118
  return true;
31702
32119
  }
31703
32120
  const isWindows2 = process.platform === "win32";
@@ -31719,23 +32136,23 @@ function isServerInstalled(command) {
31719
32136
  const paths = pathEnv.split(pathSeparator);
31720
32137
  for (const p of paths) {
31721
32138
  for (const suffix of exts) {
31722
- if (existsSync41(join54(p, cmd + suffix))) {
32139
+ if (existsSync42(join55(p, cmd + suffix))) {
31723
32140
  return true;
31724
32141
  }
31725
32142
  }
31726
32143
  }
31727
32144
  const cwd2 = process.cwd();
31728
32145
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
31729
- const dataDir = join54(getDataDir(), "opencode");
32146
+ const dataDir = join55(getDataDir(), "opencode");
31730
32147
  const additionalBases = [
31731
- join54(cwd2, "node_modules", ".bin"),
31732
- join54(configDir, "bin"),
31733
- join54(configDir, "node_modules", ".bin"),
31734
- join54(dataDir, "bin")
32148
+ join55(cwd2, "node_modules", ".bin"),
32149
+ join55(configDir, "bin"),
32150
+ join55(configDir, "node_modules", ".bin"),
32151
+ join55(dataDir, "bin")
31735
32152
  ];
31736
32153
  for (const base of additionalBases) {
31737
32154
  for (const suffix of exts) {
31738
- if (existsSync41(join54(base, cmd + suffix))) {
32155
+ if (existsSync42(join55(base, cmd + suffix))) {
31739
32156
  return true;
31740
32157
  }
31741
32158
  }
@@ -31747,7 +32164,7 @@ function isServerInstalled(command) {
31747
32164
  }
31748
32165
  // src/tools/lsp/client.ts
31749
32166
  var {spawn: spawn6 } = globalThis.Bun;
31750
- import { readFileSync as readFileSync32 } from "fs";
32167
+ import { readFileSync as readFileSync34 } from "fs";
31751
32168
  import { extname, resolve as resolve8 } from "path";
31752
32169
  import { pathToFileURL } from "url";
31753
32170
  class LSPServerManager {
@@ -32198,7 +32615,7 @@ ${msg}`);
32198
32615
  const absPath = resolve8(filePath);
32199
32616
  if (this.openedFiles.has(absPath))
32200
32617
  return;
32201
- const text = readFileSync32(absPath, "utf-8");
32618
+ const text = readFileSync34(absPath, "utf-8");
32202
32619
  const ext = extname(absPath);
32203
32620
  const languageId = getLanguageId(ext);
32204
32621
  this.notify("textDocument/didOpen", {
@@ -32288,17 +32705,17 @@ ${msg}`);
32288
32705
  // src/tools/lsp/utils.ts
32289
32706
  import { extname as extname2, resolve as resolve9 } from "path";
32290
32707
  import { fileURLToPath as fileURLToPath2 } from "url";
32291
- import { existsSync as existsSync42, readFileSync as readFileSync33, writeFileSync as writeFileSync21 } from "fs";
32708
+ import { existsSync as existsSync43, readFileSync as readFileSync35, writeFileSync as writeFileSync22 } from "fs";
32292
32709
  function findWorkspaceRoot(filePath) {
32293
32710
  let dir = resolve9(filePath);
32294
- if (!existsSync42(dir) || !__require("fs").statSync(dir).isDirectory()) {
32711
+ if (!existsSync43(dir) || !__require("fs").statSync(dir).isDirectory()) {
32295
32712
  dir = __require("path").dirname(dir);
32296
32713
  }
32297
32714
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
32298
32715
  let prevDir = "";
32299
32716
  while (dir !== prevDir) {
32300
32717
  for (const marker of markers) {
32301
- if (existsSync42(__require("path").join(dir, marker))) {
32718
+ if (existsSync43(__require("path").join(dir, marker))) {
32302
32719
  return dir;
32303
32720
  }
32304
32721
  }
@@ -32453,7 +32870,7 @@ function formatPrepareRenameResult(result) {
32453
32870
  }
32454
32871
  function applyTextEditsToFile(filePath, edits) {
32455
32872
  try {
32456
- let content = readFileSync33(filePath, "utf-8");
32873
+ let content = readFileSync35(filePath, "utf-8");
32457
32874
  const lines = content.split(`
32458
32875
  `);
32459
32876
  const sortedEdits = [...edits].sort((a, b) => {
@@ -32478,7 +32895,7 @@ function applyTextEditsToFile(filePath, edits) {
32478
32895
  `));
32479
32896
  }
32480
32897
  }
32481
- writeFileSync21(filePath, lines.join(`
32898
+ writeFileSync22(filePath, lines.join(`
32482
32899
  `), "utf-8");
32483
32900
  return { success: true, editCount: edits.length };
32484
32901
  } catch (err) {
@@ -32509,7 +32926,7 @@ function applyWorkspaceEdit(edit) {
32509
32926
  if (change.kind === "create") {
32510
32927
  try {
32511
32928
  const filePath = uriToPath(change.uri);
32512
- writeFileSync21(filePath, "", "utf-8");
32929
+ writeFileSync22(filePath, "", "utf-8");
32513
32930
  result.filesModified.push(filePath);
32514
32931
  } catch (err) {
32515
32932
  result.success = false;
@@ -32519,8 +32936,8 @@ function applyWorkspaceEdit(edit) {
32519
32936
  try {
32520
32937
  const oldPath = uriToPath(change.oldUri);
32521
32938
  const newPath = uriToPath(change.newUri);
32522
- const content = readFileSync33(oldPath, "utf-8");
32523
- writeFileSync21(newPath, content, "utf-8");
32939
+ const content = readFileSync35(oldPath, "utf-8");
32940
+ writeFileSync22(newPath, content, "utf-8");
32524
32941
  __require("fs").unlinkSync(oldPath);
32525
32942
  result.filesModified.push(newPath);
32526
32943
  } catch (err) {
@@ -45099,13 +45516,13 @@ var lsp_rename = tool({
45099
45516
  });
45100
45517
  // src/tools/ast-grep/constants.ts
45101
45518
  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";
45519
+ import { dirname as dirname11, join as join57 } from "path";
45520
+ import { existsSync as existsSync45, statSync as statSync5 } from "fs";
45104
45521
 
45105
45522
  // src/tools/ast-grep/downloader.ts
45106
45523
  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";
45524
+ import { existsSync as existsSync44, mkdirSync as mkdirSync18, chmodSync as chmodSync2, unlinkSync as unlinkSync15 } from "fs";
45525
+ import { join as join56 } from "path";
45109
45526
  import { homedir as homedir13 } from "os";
45110
45527
  import { createRequire as createRequire3 } from "module";
45111
45528
  var REPO2 = "ast-grep/ast-grep";
@@ -45131,19 +45548,19 @@ var PLATFORM_MAP2 = {
45131
45548
  function getCacheDir3() {
45132
45549
  if (process.platform === "win32") {
45133
45550
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
45134
- const base2 = localAppData || join55(homedir13(), "AppData", "Local");
45135
- return join55(base2, "oh-my-opencode", "bin");
45551
+ const base2 = localAppData || join56(homedir13(), "AppData", "Local");
45552
+ return join56(base2, "oh-my-opencode", "bin");
45136
45553
  }
45137
45554
  const xdgCache = process.env.XDG_CACHE_HOME;
45138
- const base = xdgCache || join55(homedir13(), ".cache");
45139
- return join55(base, "oh-my-opencode", "bin");
45555
+ const base = xdgCache || join56(homedir13(), ".cache");
45556
+ return join56(base, "oh-my-opencode", "bin");
45140
45557
  }
45141
45558
  function getBinaryName3() {
45142
45559
  return process.platform === "win32" ? "sg.exe" : "sg";
45143
45560
  }
45144
45561
  function getCachedBinaryPath2() {
45145
- const binaryPath = join55(getCacheDir3(), getBinaryName3());
45146
- return existsSync43(binaryPath) ? binaryPath : null;
45562
+ const binaryPath = join56(getCacheDir3(), getBinaryName3());
45563
+ return existsSync44(binaryPath) ? binaryPath : null;
45147
45564
  }
45148
45565
  async function downloadAstGrep(version2 = DEFAULT_VERSION) {
45149
45566
  const platformKey = `${process.platform}-${process.arch}`;
@@ -45154,8 +45571,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
45154
45571
  }
45155
45572
  const cacheDir = getCacheDir3();
45156
45573
  const binaryName = getBinaryName3();
45157
- const binaryPath = join55(cacheDir, binaryName);
45158
- if (existsSync43(binaryPath)) {
45574
+ const binaryPath = join56(cacheDir, binaryName);
45575
+ if (existsSync44(binaryPath)) {
45159
45576
  return binaryPath;
45160
45577
  }
45161
45578
  const { arch, os: os6 } = platformInfo;
@@ -45163,21 +45580,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
45163
45580
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
45164
45581
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
45165
45582
  try {
45166
- if (!existsSync43(cacheDir)) {
45167
- mkdirSync17(cacheDir, { recursive: true });
45583
+ if (!existsSync44(cacheDir)) {
45584
+ mkdirSync18(cacheDir, { recursive: true });
45168
45585
  }
45169
45586
  const response = await fetch(downloadUrl, { redirect: "follow" });
45170
45587
  if (!response.ok) {
45171
45588
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
45172
45589
  }
45173
- const archivePath = join55(cacheDir, assetName);
45590
+ const archivePath = join56(cacheDir, assetName);
45174
45591
  const arrayBuffer = await response.arrayBuffer();
45175
45592
  await Bun.write(archivePath, arrayBuffer);
45176
45593
  await extractZip(archivePath, cacheDir);
45177
- if (existsSync43(archivePath)) {
45178
- unlinkSync14(archivePath);
45594
+ if (existsSync44(archivePath)) {
45595
+ unlinkSync15(archivePath);
45179
45596
  }
45180
- if (process.platform !== "win32" && existsSync43(binaryPath)) {
45597
+ if (process.platform !== "win32" && existsSync44(binaryPath)) {
45181
45598
  chmodSync2(binaryPath, 493);
45182
45599
  }
45183
45600
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -45227,9 +45644,9 @@ function findSgCliPathSync() {
45227
45644
  try {
45228
45645
  const require2 = createRequire4(import.meta.url);
45229
45646
  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)) {
45647
+ const cliDir = dirname11(cliPkgPath);
45648
+ const sgPath = join57(cliDir, binaryName);
45649
+ if (existsSync45(sgPath) && isValidBinary(sgPath)) {
45233
45650
  return sgPath;
45234
45651
  }
45235
45652
  } catch {}
@@ -45238,10 +45655,10 @@ function findSgCliPathSync() {
45238
45655
  try {
45239
45656
  const require2 = createRequire4(import.meta.url);
45240
45657
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
45241
- const pkgDir = dirname10(pkgPath);
45658
+ const pkgDir = dirname11(pkgPath);
45242
45659
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
45243
- const binaryPath = join56(pkgDir, astGrepName);
45244
- if (existsSync44(binaryPath) && isValidBinary(binaryPath)) {
45660
+ const binaryPath = join57(pkgDir, astGrepName);
45661
+ if (existsSync45(binaryPath) && isValidBinary(binaryPath)) {
45245
45662
  return binaryPath;
45246
45663
  }
45247
45664
  } catch {}
@@ -45249,7 +45666,7 @@ function findSgCliPathSync() {
45249
45666
  if (process.platform === "darwin") {
45250
45667
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
45251
45668
  for (const path7 of homebrewPaths) {
45252
- if (existsSync44(path7) && isValidBinary(path7)) {
45669
+ if (existsSync45(path7) && isValidBinary(path7)) {
45253
45670
  return path7;
45254
45671
  }
45255
45672
  }
@@ -45304,11 +45721,11 @@ var DEFAULT_MAX_MATCHES = 500;
45304
45721
 
45305
45722
  // src/tools/ast-grep/cli.ts
45306
45723
  var {spawn: spawn7 } = globalThis.Bun;
45307
- import { existsSync as existsSync45 } from "fs";
45724
+ import { existsSync as existsSync46 } from "fs";
45308
45725
  var resolvedCliPath3 = null;
45309
45726
  var initPromise2 = null;
45310
45727
  async function getAstGrepPath() {
45311
- if (resolvedCliPath3 !== null && existsSync45(resolvedCliPath3)) {
45728
+ if (resolvedCliPath3 !== null && existsSync46(resolvedCliPath3)) {
45312
45729
  return resolvedCliPath3;
45313
45730
  }
45314
45731
  if (initPromise2) {
@@ -45316,7 +45733,7 @@ async function getAstGrepPath() {
45316
45733
  }
45317
45734
  initPromise2 = (async () => {
45318
45735
  const syncPath = findSgCliPathSync();
45319
- if (syncPath && existsSync45(syncPath)) {
45736
+ if (syncPath && existsSync46(syncPath)) {
45320
45737
  resolvedCliPath3 = syncPath;
45321
45738
  setSgCliPath(syncPath);
45322
45739
  return syncPath;
@@ -45350,7 +45767,7 @@ async function runSg(options) {
45350
45767
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
45351
45768
  args.push(...paths);
45352
45769
  let cliPath = getSgCliPath();
45353
- if (!existsSync45(cliPath) && cliPath !== "sg") {
45770
+ if (!existsSync46(cliPath) && cliPath !== "sg") {
45354
45771
  const downloadedPath = await getAstGrepPath();
45355
45772
  if (downloadedPath) {
45356
45773
  cliPath = downloadedPath;
@@ -45614,21 +46031,21 @@ var ast_grep_replace = tool({
45614
46031
  var {spawn: spawn9 } = globalThis.Bun;
45615
46032
 
45616
46033
  // src/tools/grep/constants.ts
45617
- import { existsSync as existsSync47 } from "fs";
45618
- import { join as join58, dirname as dirname11 } from "path";
46034
+ import { existsSync as existsSync48 } from "fs";
46035
+ import { join as join59, dirname as dirname12 } from "path";
45619
46036
  import { spawnSync as spawnSync2 } from "child_process";
45620
46037
 
45621
46038
  // src/tools/grep/downloader.ts
45622
46039
  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";
46040
+ import { existsSync as existsSync47, mkdirSync as mkdirSync19, chmodSync as chmodSync3, unlinkSync as unlinkSync16, readdirSync as readdirSync14 } from "fs";
46041
+ import { join as join58 } from "path";
45625
46042
  var {spawn: spawn8 } = globalThis.Bun;
45626
46043
  function findFileRecursive(dir, filename) {
45627
46044
  try {
45628
- const entries = readdirSync13(dir, { withFileTypes: true, recursive: true });
46045
+ const entries = readdirSync14(dir, { withFileTypes: true, recursive: true });
45629
46046
  for (const entry of entries) {
45630
46047
  if (entry.isFile() && entry.name === filename) {
45631
- return join57(entry.parentPath ?? dir, entry.name);
46048
+ return join58(entry.parentPath ?? dir, entry.name);
45632
46049
  }
45633
46050
  }
45634
46051
  } catch {
@@ -45649,11 +46066,11 @@ function getPlatformKey() {
45649
46066
  }
45650
46067
  function getInstallDir() {
45651
46068
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
45652
- return join57(homeDir, ".cache", "oh-my-opencode", "bin");
46069
+ return join58(homeDir, ".cache", "oh-my-opencode", "bin");
45653
46070
  }
45654
46071
  function getRgPath() {
45655
46072
  const isWindows2 = process.platform === "win32";
45656
- return join57(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
46073
+ return join58(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
45657
46074
  }
45658
46075
  async function downloadFile(url2, destPath) {
45659
46076
  const response = await fetch(url2);
@@ -45687,10 +46104,10 @@ async function extractZip2(archivePath, destDir) {
45687
46104
  const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
45688
46105
  const foundPath = findFileRecursive(destDir, binaryName);
45689
46106
  if (foundPath) {
45690
- const destPath = join57(destDir, binaryName);
46107
+ const destPath = join58(destDir, binaryName);
45691
46108
  if (foundPath !== destPath) {
45692
- const { renameSync: renameSync4 } = await import("fs");
45693
- renameSync4(foundPath, destPath);
46109
+ const { renameSync: renameSync5 } = await import("fs");
46110
+ renameSync5(foundPath, destPath);
45694
46111
  }
45695
46112
  }
45696
46113
  }
@@ -45702,13 +46119,13 @@ async function downloadAndInstallRipgrep() {
45702
46119
  }
45703
46120
  const installDir = getInstallDir();
45704
46121
  const rgPath = getRgPath();
45705
- if (existsSync46(rgPath)) {
46122
+ if (existsSync47(rgPath)) {
45706
46123
  return rgPath;
45707
46124
  }
45708
- mkdirSync18(installDir, { recursive: true });
46125
+ mkdirSync19(installDir, { recursive: true });
45709
46126
  const filename = `ripgrep-${RG_VERSION}-${config3.platform}.${config3.extension}`;
45710
46127
  const url2 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
45711
- const archivePath = join57(installDir, filename);
46128
+ const archivePath = join58(installDir, filename);
45712
46129
  try {
45713
46130
  await downloadFile(url2, archivePath);
45714
46131
  if (config3.extension === "tar.gz") {
@@ -45719,21 +46136,21 @@ async function downloadAndInstallRipgrep() {
45719
46136
  if (process.platform !== "win32") {
45720
46137
  chmodSync3(rgPath, 493);
45721
46138
  }
45722
- if (!existsSync46(rgPath)) {
46139
+ if (!existsSync47(rgPath)) {
45723
46140
  throw new Error("\u63D0\u53D6\u540E\u672A\u627E\u5230 ripgrep \u4E8C\u8FDB\u5236\u6587\u4EF6");
45724
46141
  }
45725
46142
  return rgPath;
45726
46143
  } finally {
45727
- if (existsSync46(archivePath)) {
46144
+ if (existsSync47(archivePath)) {
45728
46145
  try {
45729
- unlinkSync15(archivePath);
46146
+ unlinkSync16(archivePath);
45730
46147
  } catch {}
45731
46148
  }
45732
46149
  }
45733
46150
  }
45734
46151
  function getInstalledRipgrepPath() {
45735
46152
  const rgPath = getRgPath();
45736
- return existsSync46(rgPath) ? rgPath : null;
46153
+ return existsSync47(rgPath) ? rgPath : null;
45737
46154
  }
45738
46155
 
45739
46156
  // src/tools/grep/constants.ts
@@ -45757,7 +46174,7 @@ function findExecutable(name) {
45757
46174
  continue;
45758
46175
  }
45759
46176
  }
45760
- if (existsSync47(trimmed)) {
46177
+ if (existsSync48(trimmed)) {
45761
46178
  return trimmed;
45762
46179
  }
45763
46180
  }
@@ -45768,18 +46185,18 @@ function findExecutable(name) {
45768
46185
  }
45769
46186
  function getOpenCodeBundledRg() {
45770
46187
  const execPath = process.execPath;
45771
- const execDir = dirname11(execPath);
46188
+ const execDir = dirname12(execPath);
45772
46189
  const isWindows2 = process.platform === "win32";
45773
46190
  const rgName = isWindows2 ? "rg.exe" : "rg";
45774
46191
  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)
46192
+ join59(getDataDir(), "opencode", "bin", rgName),
46193
+ join59(execDir, rgName),
46194
+ join59(execDir, "bin", rgName),
46195
+ join59(execDir, "..", "bin", rgName),
46196
+ join59(execDir, "..", "libexec", rgName)
45780
46197
  ];
45781
46198
  for (const candidate of candidates) {
45782
- if (existsSync47(candidate)) {
46199
+ if (existsSync48(candidate)) {
45783
46200
  return candidate;
45784
46201
  }
45785
46202
  }
@@ -46241,8 +46658,8 @@ var glob = tool({
46241
46658
  init_shared();
46242
46659
  init_file_utils();
46243
46660
  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";
46661
+ import { existsSync as existsSync49, readdirSync as readdirSync15, readFileSync as readFileSync36 } from "fs";
46662
+ import { join as join60, basename as basename4, dirname as dirname13 } from "path";
46246
46663
  // src/features/builtin-commands/templates/init-deep.ts
46247
46664
  var INIT_DEEP_TEMPLATE = `# /init-deep
46248
46665
 
@@ -47282,6 +47699,21 @@ var START_WORK_TEMPLATE = `\u4F60\u6B63\u5728\u5F00\u59CB\u4E00\u4E2A \u4E3B\u62
47282
47699
  - \u5728\u59D4\u6D3E\u4EFB\u4F55\u4EFB\u52A1\u4E4B\u524D\uFF0C\u8BFB\u53D6\u5B8C\u6574\u7684\u8BA1\u5212\u6587\u4EF6
47283
47700
  - \u9075\u5FAA \u4EFB\u52A1\u7F16\u6392-\u4E3B\u6267\u884C\u5B98 \u7684\u59D4\u6D3E\u534F\u8BAE\uFF087 \u8282\u683C\u5F0F\uFF09`;
47284
47701
 
47702
+ // src/features/builtin-commands/templates/btw.ts
47703
+ var BTW_TEMPLATE = `# /btw \u547D\u4EE4 \u2014 \u6781\u5EA6\u7CBE\u7B80\u4FA7\u8FB9\u63D0\u95EE
47704
+
47705
+ \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
47706
+
47707
+ ## \u7EA6\u675F
47708
+ - \u76F4\u63A5\u56DE\u7B54\u95EE\u9898\uFF0C\u4E0D\u8981\u6DFB\u52A0\u4E0A\u4E0B\u6587\u65E0\u5173\u7684\u5185\u5BB9
47709
+ - \u56DE\u7B54\u63A7\u5236\u5728 100 \u5B57\u4EE5\u5185\uFF0C1-3 \u53E5\u8BDD
47710
+ - \u53EA\u8F93\u51FA\u7B54\u6848\u672C\u8EAB\uFF0C\u4E0D\u8981\u8F93\u51FA\u601D\u8003\u8FC7\u7A0B\u6216\u5206\u6790
47711
+ - \u4E0D\u8981\u53D1\u8D77\u540E\u7EED\u5BF9\u8BDD
47712
+ - \u4F7F\u7528\u4E2D\u6587\u56DE\u590D
47713
+
47714
+ ## \u7528\u6237\u95EE\u9898
47715
+ $ARGUMENTS`;
47716
+
47285
47717
  // src/features/builtin-commands/commands.ts
47286
47718
  var BUILTIN_COMMAND_DEFINITIONS = {
47287
47719
  "init-deep": {
@@ -47346,6 +47778,17 @@ Timestamp: $TIMESTAMP
47346
47778
  $ARGUMENTS
47347
47779
  </user-request>`,
47348
47780
  argumentHint: "[plan-name]"
47781
+ },
47782
+ btw: {
47783
+ description: "(builtin) \u6781\u5EA6\u7CBE\u7B80\u4FA7\u8FB9\u63D0\u95EE \u2014 \u5F02\u6B65\u6267\u884C\uFF0C\u4E0D\u963B\u585E\u4E3B\u5BF9\u8BDD",
47784
+ template: `<command-instruction>
47785
+ ${BTW_TEMPLATE}
47786
+ </command-instruction>
47787
+
47788
+ <user-request>
47789
+ $ARGUMENTS
47790
+ </user-request>`,
47791
+ argumentHint: "<\u4F60\u7684\u95EE\u9898>"
47349
47792
  }
47350
47793
  };
47351
47794
  function loadBuiltinCommands(disabledCommands) {
@@ -47361,18 +47804,18 @@ function loadBuiltinCommands(disabledCommands) {
47361
47804
  }
47362
47805
  // src/tools/slashcommand/tools.ts
47363
47806
  function discoverCommandsFromDir2(commandsDir, scope) {
47364
- if (!existsSync48(commandsDir)) {
47807
+ if (!existsSync49(commandsDir)) {
47365
47808
  return [];
47366
47809
  }
47367
- const entries = readdirSync14(commandsDir, { withFileTypes: true });
47810
+ const entries = readdirSync15(commandsDir, { withFileTypes: true });
47368
47811
  const commands2 = [];
47369
47812
  for (const entry of entries) {
47370
47813
  if (!isMarkdownFile(entry))
47371
47814
  continue;
47372
- const commandPath = join59(commandsDir, entry.name);
47815
+ const commandPath = join60(commandsDir, entry.name);
47373
47816
  const commandName = basename4(entry.name, ".md");
47374
47817
  try {
47375
- const content = readFileSync34(commandPath, "utf-8");
47818
+ const content = readFileSync36(commandPath, "utf-8");
47376
47819
  const { data, body } = parseFrontmatter(content);
47377
47820
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
47378
47821
  const metadata = {
@@ -47398,10 +47841,10 @@ function discoverCommandsFromDir2(commandsDir, scope) {
47398
47841
  }
47399
47842
  function discoverCommandsSync() {
47400
47843
  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");
47844
+ const userCommandsDir = join60(getClaudeConfigDir(), "commands");
47845
+ const projectCommandsDir = join60(process.cwd(), ".claude", "commands");
47846
+ const opencodeGlobalDir = join60(configDir, "command");
47847
+ const opencodeProjectDir = join60(process.cwd(), ".opencode", "command");
47405
47848
  const userCommands = discoverCommandsFromDir2(userCommandsDir, "user");
47406
47849
  const opencodeGlobalCommands = discoverCommandsFromDir2(opencodeGlobalDir, "opencode");
47407
47850
  const projectCommands = discoverCommandsFromDir2(projectCommandsDir, "project");
@@ -47473,7 +47916,7 @@ async function formatLoadedCommand(cmd) {
47473
47916
  if (!content && cmd.lazyContentLoader) {
47474
47917
  content = await cmd.lazyContentLoader.load();
47475
47918
  }
47476
- const commandDir = cmd.path ? dirname12(cmd.path) : process.cwd();
47919
+ const commandDir = cmd.path ? dirname13(cmd.path) : process.cwd();
47477
47920
  const withFileRefs = await resolveFileReferencesInText(content, commandDir);
47478
47921
  const resolvedContent = await resolveCommandsInText(withFileRefs);
47479
47922
  sections.push(resolvedContent.trim());
@@ -47578,13 +48021,13 @@ var slashcommand = createSlashcommandTool();
47578
48021
  // src/tools/session-manager/constants.ts
47579
48022
  init_data_path();
47580
48023
  init_shared();
47581
- import { join as join60 } from "path";
48024
+ import { join as join61 } from "path";
47582
48025
  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");
48026
+ var MESSAGE_STORAGE4 = join61(OPENCODE_STORAGE11, "message");
48027
+ var PART_STORAGE4 = join61(OPENCODE_STORAGE11, "part");
48028
+ var SESSION_STORAGE = join61(OPENCODE_STORAGE11, "session");
48029
+ var TODO_DIR2 = join61(getClaudeConfigDir(), "todos");
48030
+ var TRANSCRIPT_DIR2 = join61(getClaudeConfigDir(), "transcripts");
47588
48031
  var SESSION_LIST_DESCRIPTION = `\u5217\u51FA\u6240\u6709 OpenCode session\uFF0C\u652F\u6301\u53EF\u9009\u8FC7\u6EE4\u3002
47589
48032
 
47590
48033
  \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 +48100,11 @@ Has Todos: Yes (12 items, 8 completed)
47657
48100
  Has Transcript: Yes (234 entries)`;
47658
48101
 
47659
48102
  // src/tools/session-manager/storage.ts
47660
- import { existsSync as existsSync49, readdirSync as readdirSync15 } from "fs";
48103
+ import { existsSync as existsSync50, readdirSync as readdirSync16 } from "fs";
47661
48104
  import { readdir, readFile } from "fs/promises";
47662
- import { join as join61 } from "path";
48105
+ import { join as join62 } from "path";
47663
48106
  async function getMainSessions(options) {
47664
- if (!existsSync49(SESSION_STORAGE))
48107
+ if (!existsSync50(SESSION_STORAGE))
47665
48108
  return [];
47666
48109
  const sessions = [];
47667
48110
  try {
@@ -47669,13 +48112,13 @@ async function getMainSessions(options) {
47669
48112
  for (const projectDir of projectDirs) {
47670
48113
  if (!projectDir.isDirectory())
47671
48114
  continue;
47672
- const projectPath = join61(SESSION_STORAGE, projectDir.name);
48115
+ const projectPath = join62(SESSION_STORAGE, projectDir.name);
47673
48116
  const sessionFiles = await readdir(projectPath);
47674
48117
  for (const file2 of sessionFiles) {
47675
48118
  if (!file2.endsWith(".json"))
47676
48119
  continue;
47677
48120
  try {
47678
- const content = await readFile(join61(projectPath, file2), "utf-8");
48121
+ const content = await readFile(join62(projectPath, file2), "utf-8");
47679
48122
  const meta = JSON.parse(content);
47680
48123
  if (meta.parentID)
47681
48124
  continue;
@@ -47693,7 +48136,7 @@ async function getMainSessions(options) {
47693
48136
  return sessions.sort((a, b) => b.time.updated - a.time.updated);
47694
48137
  }
47695
48138
  async function getAllSessions() {
47696
- if (!existsSync49(MESSAGE_STORAGE4))
48139
+ if (!existsSync50(MESSAGE_STORAGE4))
47697
48140
  return [];
47698
48141
  const sessions = [];
47699
48142
  async function scanDirectory(dir) {
@@ -47701,7 +48144,7 @@ async function getAllSessions() {
47701
48144
  const entries = await readdir(dir, { withFileTypes: true });
47702
48145
  for (const entry of entries) {
47703
48146
  if (entry.isDirectory()) {
47704
- const sessionPath = join61(dir, entry.name);
48147
+ const sessionPath = join62(dir, entry.name);
47705
48148
  const files = await readdir(sessionPath);
47706
48149
  if (files.some((f) => f.endsWith(".json"))) {
47707
48150
  sessions.push(entry.name);
@@ -47718,16 +48161,16 @@ async function getAllSessions() {
47718
48161
  return [...new Set(sessions)];
47719
48162
  }
47720
48163
  function getMessageDir6(sessionID) {
47721
- if (!existsSync49(MESSAGE_STORAGE4))
48164
+ if (!existsSync50(MESSAGE_STORAGE4))
47722
48165
  return "";
47723
- const directPath = join61(MESSAGE_STORAGE4, sessionID);
47724
- if (existsSync49(directPath)) {
48166
+ const directPath = join62(MESSAGE_STORAGE4, sessionID);
48167
+ if (existsSync50(directPath)) {
47725
48168
  return directPath;
47726
48169
  }
47727
48170
  try {
47728
- for (const dir of readdirSync15(MESSAGE_STORAGE4)) {
47729
- const sessionPath = join61(MESSAGE_STORAGE4, dir, sessionID);
47730
- if (existsSync49(sessionPath)) {
48171
+ for (const dir of readdirSync16(MESSAGE_STORAGE4)) {
48172
+ const sessionPath = join62(MESSAGE_STORAGE4, dir, sessionID);
48173
+ if (existsSync50(sessionPath)) {
47731
48174
  return sessionPath;
47732
48175
  }
47733
48176
  }
@@ -47741,7 +48184,7 @@ function sessionExists(sessionID) {
47741
48184
  }
47742
48185
  async function readSessionMessages(sessionID) {
47743
48186
  const messageDir = getMessageDir6(sessionID);
47744
- if (!messageDir || !existsSync49(messageDir))
48187
+ if (!messageDir || !existsSync50(messageDir))
47745
48188
  return [];
47746
48189
  const messages = [];
47747
48190
  try {
@@ -47750,7 +48193,7 @@ async function readSessionMessages(sessionID) {
47750
48193
  if (!file2.endsWith(".json"))
47751
48194
  continue;
47752
48195
  try {
47753
- const content = await readFile(join61(messageDir, file2), "utf-8");
48196
+ const content = await readFile(join62(messageDir, file2), "utf-8");
47754
48197
  const meta = JSON.parse(content);
47755
48198
  const parts = await readParts2(meta.id);
47756
48199
  messages.push({
@@ -47776,8 +48219,8 @@ async function readSessionMessages(sessionID) {
47776
48219
  });
47777
48220
  }
47778
48221
  async function readParts2(messageID) {
47779
- const partDir = join61(PART_STORAGE4, messageID);
47780
- if (!existsSync49(partDir))
48222
+ const partDir = join62(PART_STORAGE4, messageID);
48223
+ if (!existsSync50(partDir))
47781
48224
  return [];
47782
48225
  const parts = [];
47783
48226
  try {
@@ -47786,7 +48229,7 @@ async function readParts2(messageID) {
47786
48229
  if (!file2.endsWith(".json"))
47787
48230
  continue;
47788
48231
  try {
47789
- const content = await readFile(join61(partDir, file2), "utf-8");
48232
+ const content = await readFile(join62(partDir, file2), "utf-8");
47790
48233
  parts.push(JSON.parse(content));
47791
48234
  } catch {
47792
48235
  continue;
@@ -47798,14 +48241,14 @@ async function readParts2(messageID) {
47798
48241
  return parts.sort((a, b) => a.id.localeCompare(b.id));
47799
48242
  }
47800
48243
  async function readSessionTodos(sessionID) {
47801
- if (!existsSync49(TODO_DIR2))
48244
+ if (!existsSync50(TODO_DIR2))
47802
48245
  return [];
47803
48246
  try {
47804
48247
  const allFiles = await readdir(TODO_DIR2);
47805
48248
  const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
47806
48249
  for (const file2 of todoFiles) {
47807
48250
  try {
47808
- const content = await readFile(join61(TODO_DIR2, file2), "utf-8");
48251
+ const content = await readFile(join62(TODO_DIR2, file2), "utf-8");
47809
48252
  const data = JSON.parse(content);
47810
48253
  if (Array.isArray(data)) {
47811
48254
  return data.map((item) => ({
@@ -47825,10 +48268,10 @@ async function readSessionTodos(sessionID) {
47825
48268
  return [];
47826
48269
  }
47827
48270
  async function readSessionTranscript(sessionID) {
47828
- if (!existsSync49(TRANSCRIPT_DIR2))
48271
+ if (!existsSync50(TRANSCRIPT_DIR2))
47829
48272
  return 0;
47830
- const transcriptFile = join61(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
47831
- if (!existsSync49(transcriptFile))
48273
+ const transcriptFile = join62(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
48274
+ if (!existsSync50(transcriptFile))
47832
48275
  return 0;
47833
48276
  try {
47834
48277
  const content = await readFile(transcriptFile, "utf-8");
@@ -48324,7 +48767,7 @@ var TOOL_DESCRIPTION_PREFIX2 = `\u52A0\u8F7D skill \u4EE5\u83B7\u53D6\u7279\u5B9
48324
48767
  Skills \u63D0\u4F9B\u4E13\u4E1A\u77E5\u8BC6\u548C\u5206\u6B65\u6307\u5BFC\u3002
48325
48768
  \u5F53\u4EFB\u52A1\u4E0E\u53EF\u7528 skill \u7684\u63CF\u8FF0\u76F8\u5339\u914D\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177\u3002`;
48326
48769
  // src/tools/skill/tools.ts
48327
- import { dirname as dirname13 } from "path";
48770
+ import { dirname as dirname14 } from "path";
48328
48771
  function loadedSkillToInfo(skill) {
48329
48772
  return {
48330
48773
  name: skill.name,
@@ -48478,7 +48921,7 @@ function createSkillTool(options = {}) {
48478
48921
  if (args.name === "git-master") {
48479
48922
  body = injectGitMasterConfig(body, options.gitMasterConfig);
48480
48923
  }
48481
- const dir = skill.path ? dirname13(skill.path) : skill.resolvedPath || process.cwd();
48924
+ const dir = skill.path ? dirname14(skill.path) : skill.resolvedPath || process.cwd();
48482
48925
  const output = [
48483
48926
  `## Skill: ${skill.name}`,
48484
48927
  "",
@@ -49035,20 +49478,20 @@ var CALL_OMO_AGENT_DESCRIPTION = `\u542F\u52A8\u6DF1\u5EA6\u63A2\u7D22/\u77E5\u8
49035
49478
 
49036
49479
  \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
49480
  // src/tools/call-omo-agent/tools.ts
49038
- import { existsSync as existsSync50, readdirSync as readdirSync16 } from "fs";
49039
- import { join as join62 } from "path";
49481
+ import { existsSync as existsSync51, readdirSync as readdirSync17 } from "fs";
49482
+ import { join as join63 } from "path";
49040
49483
  init_shared();
49041
49484
  init_agent_display_names();
49042
49485
  init_session_cursor();
49043
49486
  function getMessageDir7(sessionID) {
49044
- if (!existsSync50(MESSAGE_STORAGE))
49487
+ if (!existsSync51(MESSAGE_STORAGE))
49045
49488
  return null;
49046
- const directPath = join62(MESSAGE_STORAGE, sessionID);
49047
- if (existsSync50(directPath))
49489
+ const directPath = join63(MESSAGE_STORAGE, sessionID);
49490
+ if (existsSync51(directPath))
49048
49491
  return directPath;
49049
- for (const dir of readdirSync16(MESSAGE_STORAGE)) {
49050
- const sessionPath = join62(MESSAGE_STORAGE, dir, sessionID);
49051
- if (existsSync50(sessionPath))
49492
+ for (const dir of readdirSync17(MESSAGE_STORAGE)) {
49493
+ const sessionPath = join63(MESSAGE_STORAGE, dir, sessionID);
49494
+ if (existsSync51(sessionPath))
49052
49495
  return sessionPath;
49053
49496
  }
49054
49497
  return null;
@@ -49474,8 +49917,8 @@ function createLookAt(ctx) {
49474
49917
  }
49475
49918
  // src/tools/delegate-task/tools.ts
49476
49919
  init_constants();
49477
- import { existsSync as existsSync51, readdirSync as readdirSync17 } from "fs";
49478
- import { join as join63 } from "path";
49920
+ import { existsSync as existsSync52, readdirSync as readdirSync18 } from "fs";
49921
+ import { join as join64 } from "path";
49479
49922
 
49480
49923
  // src/features/task-toast-manager/manager.ts
49481
49924
  class TaskToastManager {
@@ -49681,14 +50124,14 @@ function parseFallbackModelEntries(entries) {
49681
50124
  });
49682
50125
  }
49683
50126
  function getMessageDir8(sessionID) {
49684
- if (!existsSync51(MESSAGE_STORAGE))
50127
+ if (!existsSync52(MESSAGE_STORAGE))
49685
50128
  return null;
49686
- const directPath = join63(MESSAGE_STORAGE, sessionID);
49687
- if (existsSync51(directPath))
50129
+ const directPath = join64(MESSAGE_STORAGE, sessionID);
50130
+ if (existsSync52(directPath))
49688
50131
  return directPath;
49689
- for (const dir of readdirSync17(MESSAGE_STORAGE)) {
49690
- const sessionPath = join63(MESSAGE_STORAGE, dir, sessionID);
49691
- if (existsSync51(sessionPath))
50132
+ for (const dir of readdirSync18(MESSAGE_STORAGE)) {
50133
+ const sessionPath = join64(MESSAGE_STORAGE, dir, sessionID);
50134
+ if (existsSync52(sessionPath))
49692
50135
  return sessionPath;
49693
50136
  }
49694
50137
  return null;
@@ -50481,6 +50924,44 @@ ${classification.shouldFallback ? "\uD83D\uDCA1 \u6B64\u9519\u8BEF\u7B26\u5408 r
50481
50924
 
50482
50925
  // src/tools/delegate-task/index.ts
50483
50926
  init_constants();
50927
+ // src/tools/btw/constants.ts
50928
+ var BTW_TOOL_DESCRIPTION = `
50929
+ \u5411 BTW \u987E\u95EE\uFF08\u53EA\u8BFB\u8F7B\u91CF Agent\uFF09\u63D0\u51FA\u4E00\u4E2A\u5FEB\u901F\u95EE\u9898\u3002
50930
+ \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
50931
+ \u9002\u7528\u4E8E\u4EE3\u7801\u5206\u6790\u3001\u6982\u5FF5\u6F84\u6E05\u3001\u6A21\u5F0F\u5EFA\u8BAE\u7B49\u8F7B\u91CF\u67E5\u8BE2\u3002
50932
+ BTW \u987E\u95EE\u65E0\u5199\u6743\u9650\uFF0C\u4E0D\u53EF\u4FEE\u6539\u6587\u4EF6\u6216\u6267\u884C\u547D\u4EE4\u3002
50933
+ `.trim();
50934
+
50935
+ // src/tools/btw/tools.ts
50936
+ function createBtwTool(ctx, backgroundManager) {
50937
+ return tool({
50938
+ description: BTW_TOOL_DESCRIPTION,
50939
+ args: {
50940
+ question: tool.schema.string().describe("\u8981\u8BE2\u95EE BTW \u987E\u95EE\u7684\u95EE\u9898")
50941
+ },
50942
+ async execute(args, toolContext) {
50943
+ const parentSessionID = toolContext.sessionID;
50944
+ const parentSession = await ctx.client.session.get({
50945
+ path: { id: parentSessionID }
50946
+ });
50947
+ const parentDirectory = parentSession?.data?.directory ?? ctx.directory;
50948
+ const btwPrompt = `\u4F60\u662F\u4E00\u4E2A\u8F7B\u91CF\u53EA\u8BFB\u987E\u95EE\uFF08BTW \u987E\u95EE\uFF09\u3002
50949
+ \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
50950
+ \u53EA\u8F93\u51FA\u7B54\u6848\uFF0C\u4E0D\u8981\u8F93\u51FA reasoning \u6216\u601D\u8003\u8FC7\u7A0B\u3002
50951
+
50952
+ \u95EE\u9898\uFF1A${args.question}`;
50953
+ const task = await backgroundManager.launch({
50954
+ description: `BTW: ${args.question.substring(0, 50)}`,
50955
+ prompt: btwPrompt,
50956
+ agent: "BTW \u987E\u95EE",
50957
+ parentSessionID,
50958
+ parentMessageID: toolContext.messageID
50959
+ });
50960
+ return `> **BTW**\uFF1A\u6B63\u5728\u67E5\u8BE2\u300C${args.question.substring(0, 30)}${args.question.length > 30 ? "..." : ""}\u300D
50961
+ \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`;
50962
+ }
50963
+ });
50964
+ }
50484
50965
  // src/tools/index.ts
50485
50966
  function createBackgroundTools(manager, client2) {
50486
50967
  return {
@@ -50666,8 +51147,8 @@ class PerformanceAggregator {
50666
51147
  }
50667
51148
 
50668
51149
  // src/features/background-agent/manager.ts
50669
- import { existsSync as existsSync52, readdirSync as readdirSync18 } from "fs";
50670
- import { join as join64 } from "path";
51150
+ import { existsSync as existsSync53, readdirSync as readdirSync19 } from "fs";
51151
+ import { join as join65 } from "path";
50671
51152
  var TASK_TTL_MS = 30 * 60 * 1000;
50672
51153
  var MIN_STABILITY_TIME_MS = 10 * 1000;
50673
51154
  var DEFAULT_STALE_TIMEOUT_MS = 120000;
@@ -51841,14 +52322,14 @@ function registerProcessSignal(signal, handler, exitAfter) {
51841
52322
  return listener;
51842
52323
  }
51843
52324
  function getMessageDir9(sessionID) {
51844
- if (!existsSync52(MESSAGE_STORAGE))
52325
+ if (!existsSync53(MESSAGE_STORAGE))
51845
52326
  return null;
51846
- const directPath = join64(MESSAGE_STORAGE, sessionID);
51847
- if (existsSync52(directPath))
52327
+ const directPath = join65(MESSAGE_STORAGE, sessionID);
52328
+ if (existsSync53(directPath))
51848
52329
  return directPath;
51849
- for (const dir of readdirSync18(MESSAGE_STORAGE)) {
51850
- const sessionPath = join64(MESSAGE_STORAGE, dir, sessionID);
51851
- if (existsSync52(sessionPath))
52330
+ for (const dir of readdirSync19(MESSAGE_STORAGE)) {
52331
+ const sessionPath = join65(MESSAGE_STORAGE, dir, sessionID);
52332
+ if (existsSync53(sessionPath))
51852
52333
  return sessionPath;
51853
52334
  }
51854
52335
  return null;
@@ -70522,7 +71003,8 @@ var BuiltinAgentNameSchema = exports_external2.enum([
70522
71003
  "\u5A92\u4F53\u89E3\u6790",
70523
71004
  "\u9884\u5BA1\u987E\u95EE",
70524
71005
  "\u8BA1\u5212\u5BA1\u67E5",
70525
- "\u4EFB\u52A1\u7F16\u6392"
71006
+ "\u4EFB\u52A1\u7F16\u6392",
71007
+ "BTW \u987E\u95EE"
70526
71008
  ]);
70527
71009
  var BuiltinSkillNameSchema = exports_external2.enum([
70528
71010
  "playwright",
@@ -70542,7 +71024,8 @@ var OverridableAgentNameSchema = exports_external2.enum([
70542
71024
  "\u77E5\u8BC6\u5178\u85CF",
70543
71025
  "\u6DF1\u5EA6\u63A2\u7D22",
70544
71026
  "\u5A92\u4F53\u89E3\u6790",
70545
- "\u4EFB\u52A1\u7F16\u6392"
71027
+ "\u4EFB\u52A1\u7F16\u6392",
71028
+ "BTW \u987E\u95EE"
70546
71029
  ]);
70547
71030
  var HookNameSchema = exports_external2.enum([
70548
71031
  "todo-continuation-enforcer",
@@ -70583,11 +71066,13 @@ var HookNameSchema = exports_external2.enum([
70583
71066
  "tool-definition-optimizer",
70584
71067
  "permission-ask-bridge",
70585
71068
  "shell-env-injector",
70586
- "dispose-coordinator"
71069
+ "dispose-coordinator",
71070
+ "plan-completion"
70587
71071
  ]);
70588
71072
  var BuiltinCommandNameSchema = exports_external2.enum([
70589
71073
  "init-deep",
70590
- "start-work"
71074
+ "start-work",
71075
+ "btw"
70591
71076
  ]);
70592
71077
  var ProviderModelStringSchema = exports_external2.string().refine((value) => {
70593
71078
  const separatorIndex = value.indexOf("/");
@@ -70636,7 +71121,8 @@ var AgentOverridesSchema = exports_external2.object({
70636
71121
  "\u77E5\u8BC6\u5178\u85CF": AgentOverrideConfigSchema.optional(),
70637
71122
  "\u6DF1\u5EA6\u63A2\u7D22": AgentOverrideConfigSchema.optional(),
70638
71123
  "\u5A92\u4F53\u89E3\u6790": AgentOverrideConfigSchema.optional(),
70639
- "\u4EFB\u52A1\u7F16\u6392": AgentOverrideConfigSchema.optional()
71124
+ "\u4EFB\u52A1\u7F16\u6392": AgentOverrideConfigSchema.optional(),
71125
+ "BTW \u987E\u95EE": AgentOverrideConfigSchema.optional()
70640
71126
  });
70641
71127
  var ClaudeCodeConfigSchema = exports_external2.object({
70642
71128
  mcp: exports_external2.boolean().optional(),
@@ -71068,7 +71554,7 @@ init_file_utils();
71068
71554
  init_shared();
71069
71555
  init_logger();
71070
71556
  import { promises as fs11 } from "fs";
71071
- import { join as join66, basename as basename6 } from "path";
71557
+ import { join as join67, basename as basename6 } from "path";
71072
71558
  async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix = "") {
71073
71559
  try {
71074
71560
  await fs11.access(commandsDir);
@@ -71098,7 +71584,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
71098
71584
  if (entry.isDirectory()) {
71099
71585
  if (entry.name.startsWith("."))
71100
71586
  continue;
71101
- const subDirPath = join66(commandsDir, entry.name);
71587
+ const subDirPath = join67(commandsDir, entry.name);
71102
71588
  const subPrefix = prefix ? `${prefix}:${entry.name}` : entry.name;
71103
71589
  const subCommands = await loadCommandsFromDir(subDirPath, scope, visited, subPrefix);
71104
71590
  commands2.push(...subCommands);
@@ -71106,7 +71592,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
71106
71592
  }
71107
71593
  if (!isMarkdownFile(entry))
71108
71594
  continue;
71109
- const commandPath = join66(commandsDir, entry.name);
71595
+ const commandPath = join67(commandsDir, entry.name);
71110
71596
  const baseCommandName = basename6(entry.name, ".md");
71111
71597
  const commandName = prefix ? `${prefix}:${baseCommandName}` : baseCommandName;
71112
71598
  try {
@@ -71153,23 +71639,23 @@ function commandsToRecord(commands2) {
71153
71639
  return result;
71154
71640
  }
71155
71641
  async function loadUserCommands() {
71156
- const userCommandsDir = join66(getClaudeConfigDir(), "commands");
71642
+ const userCommandsDir = join67(getClaudeConfigDir(), "commands");
71157
71643
  const commands2 = await loadCommandsFromDir(userCommandsDir, "user");
71158
71644
  return commandsToRecord(commands2);
71159
71645
  }
71160
71646
  async function loadProjectCommands() {
71161
- const projectCommandsDir = join66(process.cwd(), ".claude", "commands");
71647
+ const projectCommandsDir = join67(process.cwd(), ".claude", "commands");
71162
71648
  const commands2 = await loadCommandsFromDir(projectCommandsDir, "project");
71163
71649
  return commandsToRecord(commands2);
71164
71650
  }
71165
71651
  async function loadOpencodeGlobalCommands() {
71166
71652
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
71167
- const opencodeCommandsDir = join66(configDir, "command");
71653
+ const opencodeCommandsDir = join67(configDir, "command");
71168
71654
  const commands2 = await loadCommandsFromDir(opencodeCommandsDir, "opencode");
71169
71655
  return commandsToRecord(commands2);
71170
71656
  }
71171
71657
  async function loadOpencodeProjectCommands() {
71172
- const opencodeProjectDir = join66(process.cwd(), ".opencode", "command");
71658
+ const opencodeProjectDir = join67(process.cwd(), ".opencode", "command");
71173
71659
  const commands2 = await loadCommandsFromDir(opencodeProjectDir, "opencode-project");
71174
71660
  return commandsToRecord(commands2);
71175
71661
  }
@@ -71177,8 +71663,8 @@ async function loadOpencodeProjectCommands() {
71177
71663
  init_frontmatter();
71178
71664
  init_file_utils();
71179
71665
  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";
71666
+ import { existsSync as existsSync55, readdirSync as readdirSync20, readFileSync as readFileSync38 } from "fs";
71667
+ import { join as join68, basename as basename7 } from "path";
71182
71668
  function parseToolsConfig(toolsStr) {
71183
71669
  if (!toolsStr)
71184
71670
  return;
@@ -71192,18 +71678,18 @@ function parseToolsConfig(toolsStr) {
71192
71678
  return result;
71193
71679
  }
71194
71680
  function loadAgentsFromDir(agentsDir, scope) {
71195
- if (!existsSync54(agentsDir)) {
71681
+ if (!existsSync55(agentsDir)) {
71196
71682
  return [];
71197
71683
  }
71198
- const entries = readdirSync19(agentsDir, { withFileTypes: true });
71684
+ const entries = readdirSync20(agentsDir, { withFileTypes: true });
71199
71685
  const agents = [];
71200
71686
  for (const entry of entries) {
71201
71687
  if (!isMarkdownFile(entry))
71202
71688
  continue;
71203
- const agentPath = join67(agentsDir, entry.name);
71689
+ const agentPath = join68(agentsDir, entry.name);
71204
71690
  const agentName = basename7(entry.name, ".md");
71205
71691
  try {
71206
- const content = readFileSync36(agentPath, "utf-8");
71692
+ const content = readFileSync38(agentPath, "utf-8");
71207
71693
  const { data, body } = parseFrontmatter(content);
71208
71694
  const name = data.name || agentName;
71209
71695
  const originalDescription = data.description || "";
@@ -71232,7 +71718,7 @@ function loadAgentsFromDir(agentsDir, scope) {
71232
71718
  return agents;
71233
71719
  }
71234
71720
  function loadUserAgents() {
71235
- const userAgentsDir = join67(getClaudeConfigDir(), "agents");
71721
+ const userAgentsDir = join68(getClaudeConfigDir(), "agents");
71236
71722
  const agents = loadAgentsFromDir(userAgentsDir, "user");
71237
71723
  const result = {};
71238
71724
  for (const agent of agents) {
@@ -71241,7 +71727,7 @@ function loadUserAgents() {
71241
71727
  return result;
71242
71728
  }
71243
71729
  function loadProjectAgents() {
71244
- const projectAgentsDir = join67(process.cwd(), ".claude", "agents");
71730
+ const projectAgentsDir = join68(process.cwd(), ".claude", "agents");
71245
71731
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
71246
71732
  const result = {};
71247
71733
  for (const agent of agents) {
@@ -71253,18 +71739,18 @@ function loadProjectAgents() {
71253
71739
  init_frontmatter();
71254
71740
  init_file_utils();
71255
71741
  init_logger();
71256
- import { existsSync as existsSync55, readdirSync as readdirSync20, readFileSync as readFileSync37 } from "fs";
71742
+ import { existsSync as existsSync56, readdirSync as readdirSync21, readFileSync as readFileSync39 } from "fs";
71257
71743
  import { homedir as homedir14 } from "os";
71258
- import { join as join68, basename as basename8 } from "path";
71744
+ import { join as join69, basename as basename8 } from "path";
71259
71745
  var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
71260
71746
  function getPluginsBaseDir() {
71261
71747
  if (process.env.CLAUDE_PLUGINS_HOME) {
71262
71748
  return process.env.CLAUDE_PLUGINS_HOME;
71263
71749
  }
71264
- return join68(homedir14(), ".claude", "plugins");
71750
+ return join69(homedir14(), ".claude", "plugins");
71265
71751
  }
71266
71752
  function getInstalledPluginsPath() {
71267
- return join68(getPluginsBaseDir(), "installed_plugins.json");
71753
+ return join69(getPluginsBaseDir(), "installed_plugins.json");
71268
71754
  }
71269
71755
  function resolvePluginPath(path8, pluginRoot) {
71270
71756
  return path8.replace(CLAUDE_PLUGIN_ROOT_VAR, pluginRoot);
@@ -71289,11 +71775,11 @@ function resolvePluginPaths(obj, pluginRoot) {
71289
71775
  }
71290
71776
  function loadInstalledPlugins() {
71291
71777
  const dbPath = getInstalledPluginsPath();
71292
- if (!existsSync55(dbPath)) {
71778
+ if (!existsSync56(dbPath)) {
71293
71779
  return null;
71294
71780
  }
71295
71781
  try {
71296
- const content = readFileSync37(dbPath, "utf-8");
71782
+ const content = readFileSync39(dbPath, "utf-8");
71297
71783
  return JSON.parse(content);
71298
71784
  } catch (error95) {
71299
71785
  log("Failed to load installed plugins database", error95);
@@ -71304,15 +71790,15 @@ function getClaudeSettingsPath() {
71304
71790
  if (process.env.CLAUDE_SETTINGS_PATH) {
71305
71791
  return process.env.CLAUDE_SETTINGS_PATH;
71306
71792
  }
71307
- return join68(homedir14(), ".claude", "settings.json");
71793
+ return join69(homedir14(), ".claude", "settings.json");
71308
71794
  }
71309
71795
  function loadClaudeSettings() {
71310
71796
  const settingsPath = getClaudeSettingsPath();
71311
- if (!existsSync55(settingsPath)) {
71797
+ if (!existsSync56(settingsPath)) {
71312
71798
  return null;
71313
71799
  }
71314
71800
  try {
71315
- const content = readFileSync37(settingsPath, "utf-8");
71801
+ const content = readFileSync39(settingsPath, "utf-8");
71316
71802
  return JSON.parse(content);
71317
71803
  } catch (error95) {
71318
71804
  log("Failed to load Claude settings", error95);
@@ -71320,12 +71806,12 @@ function loadClaudeSettings() {
71320
71806
  }
71321
71807
  }
71322
71808
  function loadPluginManifest(installPath) {
71323
- const manifestPath = join68(installPath, ".claude-plugin", "plugin.json");
71324
- if (!existsSync55(manifestPath)) {
71809
+ const manifestPath = join69(installPath, ".claude-plugin", "plugin.json");
71810
+ if (!existsSync56(manifestPath)) {
71325
71811
  return null;
71326
71812
  }
71327
71813
  try {
71328
- const content = readFileSync37(manifestPath, "utf-8");
71814
+ const content = readFileSync39(manifestPath, "utf-8");
71329
71815
  return JSON.parse(content);
71330
71816
  } catch (error95) {
71331
71817
  log(`Failed to load plugin manifest from ${manifestPath}`, error95);
@@ -71372,7 +71858,7 @@ function discoverInstalledPlugins(options) {
71372
71858
  continue;
71373
71859
  }
71374
71860
  const { installPath, scope, version: version3 } = installation;
71375
- if (!existsSync55(installPath)) {
71861
+ if (!existsSync56(installPath)) {
71376
71862
  errors5.push({
71377
71863
  pluginKey,
71378
71864
  installPath,
@@ -71390,21 +71876,21 @@ function discoverInstalledPlugins(options) {
71390
71876
  pluginKey,
71391
71877
  manifest: manifest ?? undefined
71392
71878
  };
71393
- if (existsSync55(join68(installPath, "commands"))) {
71394
- loadedPlugin.commandsDir = join68(installPath, "commands");
71879
+ if (existsSync56(join69(installPath, "commands"))) {
71880
+ loadedPlugin.commandsDir = join69(installPath, "commands");
71395
71881
  }
71396
- if (existsSync55(join68(installPath, "agents"))) {
71397
- loadedPlugin.agentsDir = join68(installPath, "agents");
71882
+ if (existsSync56(join69(installPath, "agents"))) {
71883
+ loadedPlugin.agentsDir = join69(installPath, "agents");
71398
71884
  }
71399
- if (existsSync55(join68(installPath, "skills"))) {
71400
- loadedPlugin.skillsDir = join68(installPath, "skills");
71885
+ if (existsSync56(join69(installPath, "skills"))) {
71886
+ loadedPlugin.skillsDir = join69(installPath, "skills");
71401
71887
  }
71402
- const hooksPath = join68(installPath, "hooks", "hooks.json");
71403
- if (existsSync55(hooksPath)) {
71888
+ const hooksPath = join69(installPath, "hooks", "hooks.json");
71889
+ if (existsSync56(hooksPath)) {
71404
71890
  loadedPlugin.hooksPath = hooksPath;
71405
71891
  }
71406
- const mcpPath = join68(installPath, ".mcp.json");
71407
- if (existsSync55(mcpPath)) {
71892
+ const mcpPath = join69(installPath, ".mcp.json");
71893
+ if (existsSync56(mcpPath)) {
71408
71894
  loadedPlugin.mcpPath = mcpPath;
71409
71895
  }
71410
71896
  plugins.push(loadedPlugin);
@@ -71415,17 +71901,17 @@ function discoverInstalledPlugins(options) {
71415
71901
  function loadPluginCommands(plugins) {
71416
71902
  const commands2 = {};
71417
71903
  for (const plugin of plugins) {
71418
- if (!plugin.commandsDir || !existsSync55(plugin.commandsDir))
71904
+ if (!plugin.commandsDir || !existsSync56(plugin.commandsDir))
71419
71905
  continue;
71420
- const entries = readdirSync20(plugin.commandsDir, { withFileTypes: true });
71906
+ const entries = readdirSync21(plugin.commandsDir, { withFileTypes: true });
71421
71907
  for (const entry of entries) {
71422
71908
  if (!isMarkdownFile(entry))
71423
71909
  continue;
71424
- const commandPath = join68(plugin.commandsDir, entry.name);
71910
+ const commandPath = join69(plugin.commandsDir, entry.name);
71425
71911
  const commandName = basename8(entry.name, ".md");
71426
71912
  const namespacedName = `${plugin.name}:${commandName}`;
71427
71913
  try {
71428
- const content = readFileSync37(commandPath, "utf-8");
71914
+ const content = readFileSync39(commandPath, "utf-8");
71429
71915
  const { data, body } = parseFrontmatter(content);
71430
71916
  const wrappedTemplate = `<command-instruction>
71431
71917
  ${body.trim()}
@@ -71457,21 +71943,21 @@ $ARGUMENTS
71457
71943
  function loadPluginSkillsAsCommands(plugins) {
71458
71944
  const skills = {};
71459
71945
  for (const plugin of plugins) {
71460
- if (!plugin.skillsDir || !existsSync55(plugin.skillsDir))
71946
+ if (!plugin.skillsDir || !existsSync56(plugin.skillsDir))
71461
71947
  continue;
71462
- const entries = readdirSync20(plugin.skillsDir, { withFileTypes: true });
71948
+ const entries = readdirSync21(plugin.skillsDir, { withFileTypes: true });
71463
71949
  for (const entry of entries) {
71464
71950
  if (entry.name.startsWith("."))
71465
71951
  continue;
71466
- const skillPath = join68(plugin.skillsDir, entry.name);
71952
+ const skillPath = join69(plugin.skillsDir, entry.name);
71467
71953
  if (!entry.isDirectory() && !entry.isSymbolicLink())
71468
71954
  continue;
71469
71955
  const resolvedPath = resolveSymlink(skillPath);
71470
- const skillMdPath = join68(resolvedPath, "SKILL.md");
71471
- if (!existsSync55(skillMdPath))
71956
+ const skillMdPath = join69(resolvedPath, "SKILL.md");
71957
+ if (!existsSync56(skillMdPath))
71472
71958
  continue;
71473
71959
  try {
71474
- const content = readFileSync37(skillMdPath, "utf-8");
71960
+ const content = readFileSync39(skillMdPath, "utf-8");
71475
71961
  const { data, body } = parseFrontmatter(content);
71476
71962
  const skillName = data.name || entry.name;
71477
71963
  const namespacedName = `${plugin.name}:${skillName}`;
@@ -71518,17 +72004,17 @@ function parseToolsConfig2(toolsStr) {
71518
72004
  function loadPluginAgents(plugins) {
71519
72005
  const agents = {};
71520
72006
  for (const plugin of plugins) {
71521
- if (!plugin.agentsDir || !existsSync55(plugin.agentsDir))
72007
+ if (!plugin.agentsDir || !existsSync56(plugin.agentsDir))
71522
72008
  continue;
71523
- const entries = readdirSync20(plugin.agentsDir, { withFileTypes: true });
72009
+ const entries = readdirSync21(plugin.agentsDir, { withFileTypes: true });
71524
72010
  for (const entry of entries) {
71525
72011
  if (!isMarkdownFile(entry))
71526
72012
  continue;
71527
- const agentPath = join68(plugin.agentsDir, entry.name);
72013
+ const agentPath = join69(plugin.agentsDir, entry.name);
71528
72014
  const agentName = basename8(entry.name, ".md");
71529
72015
  const namespacedName = `${plugin.name}:${agentName}`;
71530
72016
  try {
71531
- const content = readFileSync37(agentPath, "utf-8");
72017
+ const content = readFileSync39(agentPath, "utf-8");
71532
72018
  const { data, body } = parseFrontmatter(content);
71533
72019
  const name = data.name || agentName;
71534
72020
  const originalDescription = data.description || "";
@@ -71554,7 +72040,7 @@ function loadPluginAgents(plugins) {
71554
72040
  async function loadPluginMcpServers(plugins) {
71555
72041
  const servers = {};
71556
72042
  for (const plugin of plugins) {
71557
- if (!plugin.mcpPath || !existsSync55(plugin.mcpPath))
72043
+ if (!plugin.mcpPath || !existsSync56(plugin.mcpPath))
71558
72044
  continue;
71559
72045
  try {
71560
72046
  const content = await Bun.file(plugin.mcpPath).text();
@@ -71586,10 +72072,10 @@ async function loadPluginMcpServers(plugins) {
71586
72072
  function loadPluginHooksConfigs(plugins) {
71587
72073
  const configs = [];
71588
72074
  for (const plugin of plugins) {
71589
- if (!plugin.hooksPath || !existsSync55(plugin.hooksPath))
72075
+ if (!plugin.hooksPath || !existsSync56(plugin.hooksPath))
71590
72076
  continue;
71591
72077
  try {
71592
- const content = readFileSync37(plugin.hooksPath, "utf-8");
72078
+ const content = readFileSync39(plugin.hooksPath, "utf-8");
71593
72079
  let config4 = JSON.parse(content);
71594
72080
  config4 = resolvePluginPaths(config4, plugin.installPath);
71595
72081
  configs.push(config4);
@@ -73336,6 +73822,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73336
73822
  const toolDefOptimizer = isHookEnabled("tool-definition-optimizer") ? createToolDefinitionOptimizerHook(ctx) : null;
73337
73823
  const permissionAskBridge = isHookEnabled("permission-ask-bridge") ? createPermissionAskBridgeHook(ctx) : null;
73338
73824
  const shellEnvInjector = isHookEnabled("shell-env-injector") ? createShellEnvInjectorHook(ctx) : null;
73825
+ const planCompletionHook = isHookEnabled("plan-completion") ? createPlanCompletionHook(ctx) : null;
73339
73826
  initTaskToastManager(ctx.client);
73340
73827
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager, ralphLoopHook: ralphLoop ?? undefined }) : null;
73341
73828
  if (sessionRecovery && todoContinuationEnforcer) {
@@ -73345,6 +73832,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73345
73832
  const backgroundNotificationHook = isHookEnabled("background-notification") ? createBackgroundNotificationHook(backgroundManager) : null;
73346
73833
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
73347
73834
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
73835
+ const btwTool = createBtwTool(ctx, backgroundManager);
73348
73836
  const isMultimodalLookerEnabled = !includesCaseInsensitive(pluginConfig.disabled_agents ?? [], "multimodal-looker");
73349
73837
  const lookAt = isMultimodalLookerEnabled ? createLookAt(ctx) : null;
73350
73838
  const delegateTask = createDelegateTask({
@@ -73430,6 +73918,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
73430
73918
  call_omo_agent: callOmoAgent,
73431
73919
  ...lookAt ? { look_at: lookAt } : {},
73432
73920
  delegate_task: delegateTask,
73921
+ btw: btwTool,
73433
73922
  skill: skillTool,
73434
73923
  skill_mcp: skillMcpTool,
73435
73924
  slashcommand: slashcommandTool,