oh-my-opencode 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -602,18 +602,13 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}) {
602
602
  return result;
603
603
  }
604
604
  // src/hooks/todo-continuation-enforcer.ts
605
- var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO ENFORCEMENT]
605
+ var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
606
606
 
607
- Your todo list is NOT complete. There are still incomplete tasks remaining.
607
+ Incomplete tasks remain in your todo list. Continue working on the next pending task.
608
608
 
609
- CRITICAL INSTRUCTION:
610
- - You MUST NOT stop working until ALL todos are marked as completed
611
- - Continue working on the next pending task immediately
612
- - Work honestly and diligently to finish every task
613
- - Do NOT ask for permission to continue - just proceed with the work
614
- - Mark each task as completed as soon as you finish it
615
-
616
- Resume your work NOW.`;
609
+ - Proceed without asking for permission
610
+ - Mark each task complete when finished
611
+ - Do not stop until all tasks are done`;
617
612
  function detectInterrupt(error) {
618
613
  if (!error)
619
614
  return false;
@@ -694,7 +689,7 @@ function createTodoContinuationEnforcer(ctx) {
694
689
  type: "text",
695
690
  text: `${CONTINUATION_PROMPT}
696
691
 
697
- [Status: ${incomplete.length}/${todos.length} tasks remaining]`
692
+ [Status: ${todos.length - incomplete.length}/${todos.length} completed, ${incomplete.length} remaining]`
698
693
  }
699
694
  ]
700
695
  },
@@ -790,6 +785,32 @@ ${CONTEXT_REMINDER}
790
785
  };
791
786
  }
792
787
  // src/hooks/session-recovery.ts
788
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
789
+ import { join } from "path";
790
+
791
+ // node_modules/xdg-basedir/index.js
792
+ import os from "os";
793
+ import path from "path";
794
+ var homeDirectory = os.homedir();
795
+ var { env } = process;
796
+ var xdgData = env.XDG_DATA_HOME || (homeDirectory ? path.join(homeDirectory, ".local", "share") : undefined);
797
+ var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path.join(homeDirectory, ".config") : undefined);
798
+ var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path.join(homeDirectory, ".local", "state") : undefined);
799
+ var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path.join(homeDirectory, ".cache") : undefined);
800
+ var xdgRuntime = env.XDG_RUNTIME_DIR || undefined;
801
+ var xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
802
+ if (xdgData) {
803
+ xdgDataDirectories.unshift(xdgData);
804
+ }
805
+ var xdgConfigDirectories = (env.XDG_CONFIG_DIRS || "/etc/xdg").split(":");
806
+ if (xdgConfig) {
807
+ xdgConfigDirectories.unshift(xdgConfig);
808
+ }
809
+
810
+ // src/hooks/session-recovery.ts
811
+ var OPENCODE_STORAGE = join(xdgData ?? "", "opencode", "storage");
812
+ var MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message");
813
+ var PART_STORAGE = join(OPENCODE_STORAGE, "part");
793
814
  function getErrorMessage(error) {
794
815
  if (!error)
795
816
  return "";
@@ -894,80 +915,121 @@ async function recoverThinkingDisabledViolation(client, sessionID, failedAssista
894
915
  return false;
895
916
  }
896
917
  var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
897
- function hasNonEmptyOutput(msg) {
898
- const parts = msg.parts;
899
- if (!parts || parts.length === 0)
900
- return false;
901
- return parts.some((p) => {
902
- if (THINKING_TYPES.has(p.type))
903
- return false;
904
- if (p.type === "step-start" || p.type === "step-finish")
918
+ var META_TYPES = new Set(["step-start", "step-finish"]);
919
+ function generatePartId() {
920
+ const timestamp = Date.now().toString(16);
921
+ const random = Math.random().toString(36).substring(2, 10);
922
+ return `prt_${timestamp}${random}`;
923
+ }
924
+ function getMessageDir(sessionID) {
925
+ const projectHash = readdirSync(MESSAGE_STORAGE).find((dir) => {
926
+ const sessionDir = join(MESSAGE_STORAGE, dir);
927
+ try {
928
+ return readdirSync(sessionDir).some((f) => f.includes(sessionID.replace("ses_", "")));
929
+ } catch {
905
930
  return false;
906
- if (p.type === "text" && p.text && p.text.trim())
907
- return true;
908
- if (p.type === "tool_use" && p.id)
909
- return true;
910
- if (p.type === "tool_result")
911
- return true;
912
- return false;
931
+ }
913
932
  });
933
+ if (projectHash) {
934
+ return join(MESSAGE_STORAGE, projectHash, sessionID);
935
+ }
936
+ for (const dir of readdirSync(MESSAGE_STORAGE)) {
937
+ const sessionPath = join(MESSAGE_STORAGE, dir, sessionID);
938
+ if (existsSync(sessionPath)) {
939
+ return sessionPath;
940
+ }
941
+ }
942
+ return "";
914
943
  }
915
- function findEmptyContentMessage(msgs) {
916
- for (let i = 0;i < msgs.length; i++) {
917
- const msg = msgs[i];
918
- const isLastMessage = i === msgs.length - 1;
919
- const isAssistant = msg.info?.role === "assistant";
920
- if (isLastMessage && isAssistant)
944
+ function readMessagesFromStorage(sessionID) {
945
+ const messageDir = getMessageDir(sessionID);
946
+ if (!messageDir || !existsSync(messageDir))
947
+ return [];
948
+ const messages = [];
949
+ for (const file of readdirSync(messageDir)) {
950
+ if (!file.endsWith(".json"))
951
+ continue;
952
+ try {
953
+ const content = readFileSync(join(messageDir, file), "utf-8");
954
+ messages.push(JSON.parse(content));
955
+ } catch {
921
956
  continue;
922
- if (!hasNonEmptyOutput(msg)) {
923
- return msg;
924
957
  }
925
958
  }
926
- return null;
959
+ return messages.sort((a, b) => a.id.localeCompare(b.id));
927
960
  }
928
- async function recoverEmptyContentMessage(client, sessionID, failedAssistantMsg, directory) {
929
- try {
930
- const messagesResp = await client.session.messages({
931
- path: { id: sessionID },
932
- query: { directory }
933
- });
934
- const msgs = messagesResp.data;
935
- if (!msgs || msgs.length === 0)
936
- return false;
937
- const emptyMsg = findEmptyContentMessage(msgs) || failedAssistantMsg;
938
- const messageID = emptyMsg.info?.id;
939
- if (!messageID)
940
- return false;
941
- const existingParts = emptyMsg.parts || [];
942
- const hasOnlyThinkingOrMeta = existingParts.length > 0 && existingParts.every((p) => THINKING_TYPES.has(p.type) || p.type === "step-start" || p.type === "step-finish");
943
- if (hasOnlyThinkingOrMeta) {
944
- const strippedParts = [{ type: "text", text: "(interrupted)" }];
945
- try {
946
- await client.message?.update?.({
947
- path: { id: messageID },
948
- body: { parts: strippedParts }
949
- });
950
- return true;
951
- } catch {}
952
- try {
953
- await client.session.patch?.({
954
- path: { id: sessionID },
955
- body: { messageID, parts: strippedParts }
956
- });
957
- return true;
958
- } catch {}
961
+ function readPartsFromStorage(messageID) {
962
+ const partDir = join(PART_STORAGE, messageID);
963
+ if (!existsSync(partDir))
964
+ return [];
965
+ const parts = [];
966
+ for (const file of readdirSync(partDir)) {
967
+ if (!file.endsWith(".json"))
968
+ continue;
969
+ try {
970
+ const content = readFileSync(join(partDir, file), "utf-8");
971
+ parts.push(JSON.parse(content));
972
+ } catch {
973
+ continue;
959
974
  }
960
- const revertTargetID = emptyMsg.info?.parentID || messageID;
961
- await client.session.revert({
962
- path: { id: sessionID },
963
- body: { messageID: revertTargetID },
964
- query: { directory }
965
- });
975
+ }
976
+ return parts;
977
+ }
978
+ function injectTextPartToStorage(sessionID, messageID, text) {
979
+ const partDir = join(PART_STORAGE, messageID);
980
+ if (!existsSync(partDir)) {
981
+ mkdirSync(partDir, { recursive: true });
982
+ }
983
+ const partId = generatePartId();
984
+ const part = {
985
+ id: partId,
986
+ sessionID,
987
+ messageID,
988
+ type: "text",
989
+ text
990
+ };
991
+ try {
992
+ writeFileSync(join(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
966
993
  return true;
967
994
  } catch {
968
995
  return false;
969
996
  }
970
997
  }
998
+ function findEmptyContentMessageFromStorage(sessionID) {
999
+ const messages = readMessagesFromStorage(sessionID);
1000
+ for (let i = 0;i < messages.length; i++) {
1001
+ const msg = messages[i];
1002
+ if (msg.role !== "assistant")
1003
+ continue;
1004
+ const isLastMessage = i === messages.length - 1;
1005
+ if (isLastMessage)
1006
+ continue;
1007
+ const parts = readPartsFromStorage(msg.id);
1008
+ const hasContent = parts.some((p) => {
1009
+ if (THINKING_TYPES.has(p.type))
1010
+ return false;
1011
+ if (META_TYPES.has(p.type))
1012
+ return false;
1013
+ if (p.type === "text" && p.text?.trim())
1014
+ return true;
1015
+ if (p.type === "tool_use")
1016
+ return true;
1017
+ if (p.type === "tool_result")
1018
+ return true;
1019
+ return false;
1020
+ });
1021
+ if (!hasContent && parts.length > 0) {
1022
+ return msg.id;
1023
+ }
1024
+ }
1025
+ return null;
1026
+ }
1027
+ async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory) {
1028
+ const emptyMessageID = findEmptyContentMessageFromStorage(sessionID) || failedAssistantMsg.info?.id;
1029
+ if (!emptyMessageID)
1030
+ return false;
1031
+ return injectTextPartToStorage(sessionID, emptyMessageID, "(interrupted)");
1032
+ }
971
1033
  async function fallbackRevertStrategy(client, sessionID, failedAssistantMsg, directory) {
972
1034
  const parentMsgID = failedAssistantMsg.info?.parentID;
973
1035
  const messagesResp = await client.session.messages({
@@ -1093,14 +1155,14 @@ function createSessionRecoveryHook(ctx) {
1093
1155
  // src/hooks/comment-checker/cli.ts
1094
1156
  var {spawn: spawn2 } = globalThis.Bun;
1095
1157
  import { createRequire as createRequire2 } from "module";
1096
- import { dirname, join as join2 } from "path";
1097
- import { existsSync as existsSync2 } from "fs";
1158
+ import { dirname, join as join3 } from "path";
1159
+ import { existsSync as existsSync3 } from "fs";
1098
1160
  import * as fs from "fs";
1099
1161
 
1100
1162
  // src/hooks/comment-checker/downloader.ts
1101
1163
  var {spawn } = globalThis.Bun;
1102
- import { existsSync, mkdirSync, chmodSync, unlinkSync, appendFileSync } from "fs";
1103
- import { join } from "path";
1164
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync, appendFileSync } from "fs";
1165
+ import { join as join2 } from "path";
1104
1166
  import { homedir } from "os";
1105
1167
  import { createRequire } from "module";
1106
1168
  var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
@@ -1121,16 +1183,16 @@ var PLATFORM_MAP = {
1121
1183
  "win32-x64": { os: "windows", arch: "amd64", ext: "zip" }
1122
1184
  };
1123
1185
  function getCacheDir() {
1124
- const xdgCache = process.env.XDG_CACHE_HOME;
1125
- const base = xdgCache || join(homedir(), ".cache");
1126
- return join(base, "oh-my-opencode", "bin");
1186
+ const xdgCache2 = process.env.XDG_CACHE_HOME;
1187
+ const base = xdgCache2 || join2(homedir(), ".cache");
1188
+ return join2(base, "oh-my-opencode", "bin");
1127
1189
  }
1128
1190
  function getBinaryName() {
1129
1191
  return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
1130
1192
  }
1131
1193
  function getCachedBinaryPath() {
1132
- const binaryPath = join(getCacheDir(), getBinaryName());
1133
- return existsSync(binaryPath) ? binaryPath : null;
1194
+ const binaryPath = join2(getCacheDir(), getBinaryName());
1195
+ return existsSync2(binaryPath) ? binaryPath : null;
1134
1196
  }
1135
1197
  function getPackageVersion() {
1136
1198
  try {
@@ -1177,26 +1239,26 @@ async function downloadCommentChecker() {
1177
1239
  }
1178
1240
  const cacheDir = getCacheDir();
1179
1241
  const binaryName = getBinaryName();
1180
- const binaryPath = join(cacheDir, binaryName);
1181
- if (existsSync(binaryPath)) {
1242
+ const binaryPath = join2(cacheDir, binaryName);
1243
+ if (existsSync2(binaryPath)) {
1182
1244
  debugLog("Binary already cached at:", binaryPath);
1183
1245
  return binaryPath;
1184
1246
  }
1185
1247
  const version = getPackageVersion();
1186
- const { os, arch, ext } = platformInfo;
1187
- const assetName = `comment-checker_v${version}_${os}_${arch}.${ext}`;
1248
+ const { os: os2, arch, ext } = platformInfo;
1249
+ const assetName = `comment-checker_v${version}_${os2}_${arch}.${ext}`;
1188
1250
  const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
1189
1251
  debugLog(`Downloading from: ${downloadUrl}`);
1190
1252
  console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
1191
1253
  try {
1192
- if (!existsSync(cacheDir)) {
1193
- mkdirSync(cacheDir, { recursive: true });
1254
+ if (!existsSync2(cacheDir)) {
1255
+ mkdirSync2(cacheDir, { recursive: true });
1194
1256
  }
1195
1257
  const response = await fetch(downloadUrl, { redirect: "follow" });
1196
1258
  if (!response.ok) {
1197
1259
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1198
1260
  }
1199
- const archivePath = join(cacheDir, assetName);
1261
+ const archivePath = join2(cacheDir, assetName);
1200
1262
  const arrayBuffer = await response.arrayBuffer();
1201
1263
  await Bun.write(archivePath, arrayBuffer);
1202
1264
  debugLog(`Downloaded archive to: ${archivePath}`);
@@ -1205,10 +1267,10 @@ async function downloadCommentChecker() {
1205
1267
  } else {
1206
1268
  await extractZip(archivePath, cacheDir);
1207
1269
  }
1208
- if (existsSync(archivePath)) {
1270
+ if (existsSync2(archivePath)) {
1209
1271
  unlinkSync(archivePath);
1210
1272
  }
1211
- if (process.platform !== "win32" && existsSync(binaryPath)) {
1273
+ if (process.platform !== "win32" && existsSync2(binaryPath)) {
1212
1274
  chmodSync(binaryPath, 493);
1213
1275
  }
1214
1276
  debugLog(`Successfully downloaded binary to: ${binaryPath}`);
@@ -1261,8 +1323,8 @@ function findCommentCheckerPathSync() {
1261
1323
  const require2 = createRequire2(import.meta.url);
1262
1324
  const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
1263
1325
  const cliDir = dirname(cliPkgPath);
1264
- const binaryPath = join2(cliDir, "bin", binaryName);
1265
- if (existsSync2(binaryPath)) {
1326
+ const binaryPath = join3(cliDir, "bin", binaryName);
1327
+ if (existsSync3(binaryPath)) {
1266
1328
  debugLog2("found binary in main package:", binaryPath);
1267
1329
  return binaryPath;
1268
1330
  }
@@ -1275,8 +1337,8 @@ function findCommentCheckerPathSync() {
1275
1337
  const require2 = createRequire2(import.meta.url);
1276
1338
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
1277
1339
  const pkgDir = dirname(pkgPath);
1278
- const binaryPath = join2(pkgDir, "bin", binaryName);
1279
- if (existsSync2(binaryPath)) {
1340
+ const binaryPath = join3(pkgDir, "bin", binaryName);
1341
+ if (existsSync3(binaryPath)) {
1280
1342
  debugLog2("found binary in platform package:", binaryPath);
1281
1343
  return binaryPath;
1282
1344
  }
@@ -1289,10 +1351,10 @@ function findCommentCheckerPathSync() {
1289
1351
  "/opt/homebrew/bin/comment-checker",
1290
1352
  "/usr/local/bin/comment-checker"
1291
1353
  ];
1292
- for (const path of homebrewPaths) {
1293
- if (existsSync2(path)) {
1294
- debugLog2("found binary via homebrew:", path);
1295
- return path;
1354
+ for (const path2 of homebrewPaths) {
1355
+ if (existsSync3(path2)) {
1356
+ debugLog2("found binary via homebrew:", path2);
1357
+ return path2;
1296
1358
  }
1297
1359
  }
1298
1360
  }
@@ -1315,7 +1377,7 @@ async function getCommentCheckerPath() {
1315
1377
  }
1316
1378
  initPromise = (async () => {
1317
1379
  const syncPath = findCommentCheckerPathSync();
1318
- if (syncPath && existsSync2(syncPath)) {
1380
+ if (syncPath && existsSync3(syncPath)) {
1319
1381
  resolvedCliPath = syncPath;
1320
1382
  debugLog2("using sync-resolved path:", syncPath);
1321
1383
  return syncPath;
@@ -1335,8 +1397,8 @@ async function getCommentCheckerPath() {
1335
1397
  function startBackgroundInit() {
1336
1398
  if (!initPromise) {
1337
1399
  initPromise = getCommentCheckerPath();
1338
- initPromise.then((path) => {
1339
- debugLog2("background init complete:", path || "no binary");
1400
+ initPromise.then((path2) => {
1401
+ debugLog2("background init complete:", path2 || "no binary");
1340
1402
  }).catch((err) => {
1341
1403
  debugLog2("background init error:", err);
1342
1404
  });
@@ -1349,7 +1411,7 @@ async function runCommentChecker(input, cliPath) {
1349
1411
  debugLog2("comment-checker binary not found");
1350
1412
  return { hasComments: false, message: "" };
1351
1413
  }
1352
- if (!existsSync2(binaryPath)) {
1414
+ if (!existsSync3(binaryPath)) {
1353
1415
  debugLog2("comment-checker binary does not exist:", binaryPath);
1354
1416
  return { hasComments: false, message: "" };
1355
1417
  }
@@ -1383,7 +1445,7 @@ async function runCommentChecker(input, cliPath) {
1383
1445
 
1384
1446
  // src/hooks/comment-checker/index.ts
1385
1447
  import * as fs2 from "fs";
1386
- import { existsSync as existsSync3 } from "fs";
1448
+ import { existsSync as existsSync4 } from "fs";
1387
1449
  var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
1388
1450
  var DEBUG_FILE3 = "/tmp/comment-checker-debug.log";
1389
1451
  function debugLog3(...args) {
@@ -1409,8 +1471,8 @@ function createCommentCheckerHooks() {
1409
1471
  debugLog3("createCommentCheckerHooks called");
1410
1472
  startBackgroundInit();
1411
1473
  cliPathPromise = getCommentCheckerPath();
1412
- cliPathPromise.then((path) => {
1413
- debugLog3("CLI path resolved:", path || "disabled (no binary)");
1474
+ cliPathPromise.then((path2) => {
1475
+ debugLog3("CLI path resolved:", path2 || "disabled (no binary)");
1414
1476
  }).catch((err) => {
1415
1477
  debugLog3("CLI path resolution error:", err);
1416
1478
  });
@@ -1461,7 +1523,7 @@ function createCommentCheckerHooks() {
1461
1523
  }
1462
1524
  try {
1463
1525
  const cliPath = await cliPathPromise;
1464
- if (!cliPath || !existsSync3(cliPath)) {
1526
+ if (!cliPath || !existsSync4(cliPath)) {
1465
1527
  debugLog3("CLI not available, skipping comment check");
1466
1528
  return;
1467
1529
  }
@@ -1499,6 +1561,208 @@ ${result.message}`;
1499
1561
  debugLog3("CLI: no comments detected");
1500
1562
  }
1501
1563
  }
1564
+ // src/hooks/grep-output-truncator.ts
1565
+ var ANTHROPIC_ACTUAL_LIMIT2 = 200000;
1566
+ var CHARS_PER_TOKEN_ESTIMATE = 4;
1567
+ var TARGET_MAX_TOKENS = 50000;
1568
+ function estimateTokens(text) {
1569
+ return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
1570
+ }
1571
+ function truncateToTokenLimit(output, maxTokens) {
1572
+ const currentTokens = estimateTokens(output);
1573
+ if (currentTokens <= maxTokens) {
1574
+ return { result: output, truncated: false };
1575
+ }
1576
+ const lines = output.split(`
1577
+ `);
1578
+ if (lines.length <= 3) {
1579
+ const maxChars = maxTokens * CHARS_PER_TOKEN_ESTIMATE;
1580
+ return {
1581
+ result: output.slice(0, maxChars) + `
1582
+
1583
+ [Output truncated due to context window limit]`,
1584
+ truncated: true
1585
+ };
1586
+ }
1587
+ const headerLines = lines.slice(0, 3);
1588
+ const contentLines = lines.slice(3);
1589
+ const headerText = headerLines.join(`
1590
+ `);
1591
+ const headerTokens = estimateTokens(headerText);
1592
+ const availableTokens = maxTokens - headerTokens - 50;
1593
+ if (availableTokens <= 0) {
1594
+ return {
1595
+ result: headerText + `
1596
+
1597
+ [Content truncated due to context window limit]`,
1598
+ truncated: true
1599
+ };
1600
+ }
1601
+ let resultLines = [];
1602
+ let currentTokenCount = 0;
1603
+ for (const line of contentLines) {
1604
+ const lineTokens = estimateTokens(line + `
1605
+ `);
1606
+ if (currentTokenCount + lineTokens > availableTokens) {
1607
+ break;
1608
+ }
1609
+ resultLines.push(line);
1610
+ currentTokenCount += lineTokens;
1611
+ }
1612
+ const truncatedContent = [...headerLines, ...resultLines].join(`
1613
+ `);
1614
+ const removedCount = contentLines.length - resultLines.length;
1615
+ return {
1616
+ result: truncatedContent + `
1617
+
1618
+ [${removedCount} more lines truncated due to context window limit]`,
1619
+ truncated: true
1620
+ };
1621
+ }
1622
+ function createGrepOutputTruncatorHook(ctx) {
1623
+ const GREP_TOOLS = ["safe_grep", "Grep"];
1624
+ const toolExecuteAfter = async (input, output) => {
1625
+ if (!GREP_TOOLS.includes(input.tool))
1626
+ return;
1627
+ const { sessionID } = input;
1628
+ try {
1629
+ const response = await ctx.client.session.messages({
1630
+ path: { id: sessionID }
1631
+ });
1632
+ const messages = response.data ?? response;
1633
+ const assistantMessages = messages.filter((m) => m.info.role === "assistant").map((m) => m.info);
1634
+ if (assistantMessages.length === 0)
1635
+ return;
1636
+ const totalInputTokens = assistantMessages.reduce((sum, m) => {
1637
+ const inputTokens = m.tokens?.input ?? 0;
1638
+ const cacheReadTokens = m.tokens?.cache?.read ?? 0;
1639
+ return sum + inputTokens + cacheReadTokens;
1640
+ }, 0);
1641
+ const remainingTokens = ANTHROPIC_ACTUAL_LIMIT2 - totalInputTokens;
1642
+ const maxOutputTokens = Math.min(remainingTokens * 0.5, TARGET_MAX_TOKENS);
1643
+ if (maxOutputTokens <= 0) {
1644
+ output.output = "[Output suppressed - context window exhausted]";
1645
+ return;
1646
+ }
1647
+ const { result, truncated } = truncateToTokenLimit(output.output, maxOutputTokens);
1648
+ if (truncated) {
1649
+ output.output = result;
1650
+ }
1651
+ } catch {}
1652
+ };
1653
+ return {
1654
+ "tool.execute.after": toolExecuteAfter
1655
+ };
1656
+ }
1657
+ // src/hooks/pulse-monitor.ts
1658
+ function createPulseMonitorHook(ctx) {
1659
+ const STANDARD_TIMEOUT = 5 * 60 * 1000;
1660
+ const THINKING_TIMEOUT = 5 * 60 * 1000;
1661
+ const CHECK_INTERVAL = 5 * 1000;
1662
+ let lastHeartbeat = Date.now();
1663
+ let isMonitoring = false;
1664
+ let currentSessionID = null;
1665
+ let monitorTimer = null;
1666
+ let isThinking = false;
1667
+ const startMonitoring = (sessionID) => {
1668
+ if (currentSessionID !== sessionID) {
1669
+ currentSessionID = sessionID;
1670
+ isThinking = false;
1671
+ }
1672
+ lastHeartbeat = Date.now();
1673
+ if (!isMonitoring) {
1674
+ isMonitoring = true;
1675
+ if (monitorTimer)
1676
+ clearInterval(monitorTimer);
1677
+ monitorTimer = setInterval(async () => {
1678
+ if (!isMonitoring || !currentSessionID)
1679
+ return;
1680
+ const timeSinceLastHeartbeat = Date.now() - lastHeartbeat;
1681
+ const currentTimeout = isThinking ? THINKING_TIMEOUT : STANDARD_TIMEOUT;
1682
+ if (timeSinceLastHeartbeat > currentTimeout) {
1683
+ await recoverStalledSession(currentSessionID, timeSinceLastHeartbeat, isThinking);
1684
+ }
1685
+ }, CHECK_INTERVAL);
1686
+ }
1687
+ };
1688
+ const stopMonitoring = () => {
1689
+ isMonitoring = false;
1690
+ if (monitorTimer) {
1691
+ clearInterval(monitorTimer);
1692
+ monitorTimer = null;
1693
+ }
1694
+ };
1695
+ const updateHeartbeat = (isThinkingUpdate) => {
1696
+ if (isMonitoring) {
1697
+ lastHeartbeat = Date.now();
1698
+ if (isThinkingUpdate !== undefined) {
1699
+ isThinking = isThinkingUpdate;
1700
+ }
1701
+ }
1702
+ };
1703
+ const recoverStalledSession = async (sessionID, stalledDuration, wasThinking) => {
1704
+ stopMonitoring();
1705
+ try {
1706
+ const durationSec = Math.round(stalledDuration / 1000);
1707
+ const typeStr = wasThinking ? "Thinking" : "Standard";
1708
+ await ctx.client.tui.showToast({
1709
+ body: {
1710
+ title: "Pulse Monitor: Cardiac Arrest",
1711
+ message: `Session stalled (${typeStr}) for ${durationSec}s. Defibrillating...`,
1712
+ variant: "error",
1713
+ duration: 5000
1714
+ }
1715
+ }).catch(() => {});
1716
+ await ctx.client.session.abort({ path: { id: sessionID } }).catch(() => {});
1717
+ await new Promise((resolve) => setTimeout(resolve, 1500));
1718
+ await ctx.client.session.prompt({
1719
+ path: { id: sessionID },
1720
+ body: { parts: [{ type: "text", text: "The connection was unstable and stalled. Please continue from where you left off." }] },
1721
+ query: { directory: ctx.directory }
1722
+ });
1723
+ startMonitoring(sessionID);
1724
+ } catch (err) {
1725
+ console.error("[PulseMonitor] Recovery failed:", err);
1726
+ stopMonitoring();
1727
+ }
1728
+ };
1729
+ return {
1730
+ event: async (input) => {
1731
+ const { event } = input;
1732
+ const props = event.properties;
1733
+ if (event.type === "session.updated" || event.type === "message.part.updated") {
1734
+ const sessionID = props?.info?.id || props?.sessionID;
1735
+ if (sessionID) {
1736
+ if (!isMonitoring)
1737
+ startMonitoring(sessionID);
1738
+ let thinkingUpdate = undefined;
1739
+ if (event.type === "message.part.updated") {
1740
+ const part = props?.part;
1741
+ if (part) {
1742
+ const THINKING_TYPES2 = ["thinking", "redacted_thinking", "reasoning"];
1743
+ if (THINKING_TYPES2.includes(part.type)) {
1744
+ thinkingUpdate = true;
1745
+ } else if (part.type === "text" || part.type === "tool_use") {
1746
+ thinkingUpdate = false;
1747
+ }
1748
+ }
1749
+ }
1750
+ updateHeartbeat(thinkingUpdate);
1751
+ }
1752
+ } else if (event.type === "session.idle" || event.type === "session.error" || event.type === "session.stopped") {
1753
+ stopMonitoring();
1754
+ }
1755
+ },
1756
+ "tool.execute.before": async () => {
1757
+ stopMonitoring();
1758
+ },
1759
+ "tool.execute.after": async (input) => {
1760
+ if (input.sessionID) {
1761
+ startMonitoring(input.sessionID);
1762
+ }
1763
+ }
1764
+ };
1765
+ }
1502
1766
  // src/features/terminal/title.ts
1503
1767
  var STATUS_ICONS = {
1504
1768
  ready: "",
@@ -1707,17 +1971,31 @@ var EXT_TO_LANG = {
1707
1971
  ".svelte": "svelte",
1708
1972
  ".astro": "astro",
1709
1973
  ".yaml": "yaml",
1710
- ".yml": "yaml"
1974
+ ".yml": "yaml",
1975
+ ".json": "json",
1976
+ ".jsonc": "jsonc",
1977
+ ".html": "html",
1978
+ ".htm": "html",
1979
+ ".css": "css",
1980
+ ".scss": "scss",
1981
+ ".less": "less",
1982
+ ".sh": "shellscript",
1983
+ ".bash": "shellscript",
1984
+ ".zsh": "shellscript",
1985
+ ".fish": "fish",
1986
+ ".md": "markdown",
1987
+ ".tf": "terraform",
1988
+ ".tfvars": "terraform"
1711
1989
  };
1712
1990
  // src/tools/lsp/config.ts
1713
- import { existsSync as existsSync4, readFileSync } from "fs";
1714
- import { join as join3 } from "path";
1991
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
1992
+ import { join as join4 } from "path";
1715
1993
  import { homedir as homedir2 } from "os";
1716
- function loadJsonFile(path) {
1717
- if (!existsSync4(path))
1994
+ function loadJsonFile(path2) {
1995
+ if (!existsSync5(path2))
1718
1996
  return null;
1719
1997
  try {
1720
- return JSON.parse(readFileSync(path, "utf-8"));
1998
+ return JSON.parse(readFileSync2(path2, "utf-8"));
1721
1999
  } catch {
1722
2000
  return null;
1723
2001
  }
@@ -1725,9 +2003,9 @@ function loadJsonFile(path) {
1725
2003
  function getConfigPaths() {
1726
2004
  const cwd = process.cwd();
1727
2005
  return {
1728
- project: join3(cwd, ".opencode", "oh-my-opencode.json"),
1729
- user: join3(homedir2(), ".config", "opencode", "oh-my-opencode.json"),
1730
- opencode: join3(homedir2(), ".config", "opencode", "opencode.json")
2006
+ project: join4(cwd, ".opencode", "oh-my-opencode.json"),
2007
+ user: join4(homedir2(), ".config", "opencode", "oh-my-opencode.json"),
2008
+ opencode: join4(homedir2(), ".config", "opencode", "opencode.json")
1731
2009
  };
1732
2010
  }
1733
2011
  function loadAllConfigs() {
@@ -1820,7 +2098,7 @@ function isServerInstalled(command) {
1820
2098
  const pathEnv = process.env.PATH || "";
1821
2099
  const paths = pathEnv.split(":");
1822
2100
  for (const p of paths) {
1823
- if (existsSync4(join3(p, cmd))) {
2101
+ if (existsSync5(join4(p, cmd))) {
1824
2102
  return true;
1825
2103
  }
1826
2104
  }
@@ -1870,7 +2148,7 @@ function getAllServers() {
1870
2148
  }
1871
2149
  // src/tools/lsp/client.ts
1872
2150
  var {spawn: spawn3 } = globalThis.Bun;
1873
- import { readFileSync as readFileSync2 } from "fs";
2151
+ import { readFileSync as readFileSync3 } from "fs";
1874
2152
  import { extname, resolve } from "path";
1875
2153
  class LSPServerManager {
1876
2154
  static instance;
@@ -2000,6 +2278,7 @@ class LSPClient {
2000
2278
  openedFiles = new Set;
2001
2279
  stderrBuffer = [];
2002
2280
  processExited = false;
2281
+ diagnosticsStore = new Map;
2003
2282
  constructor(root, server) {
2004
2283
  this.root = root;
2005
2284
  this.server = server;
@@ -2124,7 +2403,11 @@ stderr: ${stderr}` : ""));
2124
2403
  this.buffer = this.buffer.slice(end);
2125
2404
  try {
2126
2405
  const msg = JSON.parse(content);
2127
- if ("id" in msg && "method" in msg) {
2406
+ if ("method" in msg && !("id" in msg)) {
2407
+ if (msg.method === "textDocument/publishDiagnostics" && msg.params?.uri) {
2408
+ this.diagnosticsStore.set(msg.params.uri, msg.params.diagnostics ?? []);
2409
+ }
2410
+ } else if ("id" in msg && "method" in msg) {
2128
2411
  this.handleServerRequest(msg.id, msg.method, msg.params);
2129
2412
  } else if ("id" in msg && this.pending.has(msg.id)) {
2130
2413
  const handler = this.pending.get(msg.id);
@@ -2186,9 +2469,15 @@ ${msg}`);
2186
2469
  \r
2187
2470
  ${msg}`);
2188
2471
  }
2189
- handleServerRequest(id, method, _params) {
2472
+ handleServerRequest(id, method, params) {
2190
2473
  if (method === "workspace/configuration") {
2191
- this.respond(id, [{}]);
2474
+ const items = params?.items ?? [];
2475
+ const result = items.map((item) => {
2476
+ if (item.section === "json")
2477
+ return { validate: { enable: true } };
2478
+ return {};
2479
+ });
2480
+ this.respond(id, result);
2192
2481
  } else if (method === "client/registerCapability") {
2193
2482
  this.respond(id, null);
2194
2483
  } else if (method === "window/workDoneProgress/create") {
@@ -2250,14 +2539,16 @@ ${msg}`);
2250
2539
  ...this.server.initialization
2251
2540
  });
2252
2541
  this.notify("initialized");
2253
- this.notify("workspace/didChangeConfiguration", { settings: {} });
2542
+ this.notify("workspace/didChangeConfiguration", {
2543
+ settings: { json: { validate: { enable: true } } }
2544
+ });
2254
2545
  await new Promise((r) => setTimeout(r, 300));
2255
2546
  }
2256
2547
  async openFile(filePath) {
2257
2548
  const absPath = resolve(filePath);
2258
2549
  if (this.openedFiles.has(absPath))
2259
2550
  return;
2260
- const text = readFileSync2(absPath, "utf-8");
2551
+ const text = readFileSync3(absPath, "utf-8");
2261
2552
  const ext = extname(absPath);
2262
2553
  const languageId = getLanguageId(ext);
2263
2554
  this.notify("textDocument/didOpen", {
@@ -2308,11 +2599,18 @@ ${msg}`);
2308
2599
  }
2309
2600
  async diagnostics(filePath) {
2310
2601
  const absPath = resolve(filePath);
2602
+ const uri = `file://${absPath}`;
2311
2603
  await this.openFile(absPath);
2312
2604
  await new Promise((r) => setTimeout(r, 500));
2313
- return this.send("textDocument/diagnostic", {
2314
- textDocument: { uri: `file://${absPath}` }
2315
- });
2605
+ try {
2606
+ const result = await this.send("textDocument/diagnostic", {
2607
+ textDocument: { uri }
2608
+ });
2609
+ if (result && typeof result === "object" && "items" in result) {
2610
+ return result;
2611
+ }
2612
+ } catch {}
2613
+ return { items: this.diagnosticsStore.get(uri) ?? [] };
2316
2614
  }
2317
2615
  async prepareRename(filePath, line, character) {
2318
2616
  const absPath = resolve(filePath);
@@ -2360,20 +2658,21 @@ ${msg}`);
2360
2658
  this.proc?.kill();
2361
2659
  this.proc = null;
2362
2660
  this.processExited = true;
2661
+ this.diagnosticsStore.clear();
2363
2662
  }
2364
2663
  }
2365
2664
  // src/tools/lsp/utils.ts
2366
2665
  import { extname as extname2, resolve as resolve2 } from "path";
2367
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync } from "fs";
2666
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2368
2667
  function findWorkspaceRoot(filePath) {
2369
2668
  let dir = resolve2(filePath);
2370
- if (!existsSync5(dir) || !__require("fs").statSync(dir).isDirectory()) {
2669
+ if (!existsSync6(dir) || !__require("fs").statSync(dir).isDirectory()) {
2371
2670
  dir = __require("path").dirname(dir);
2372
2671
  }
2373
2672
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
2374
2673
  while (dir !== "/") {
2375
2674
  for (const marker of markers) {
2376
- if (existsSync5(__require("path").join(dir, marker))) {
2675
+ if (existsSync6(__require("path").join(dir, marker))) {
2377
2676
  return dir;
2378
2677
  }
2379
2678
  }
@@ -2487,12 +2786,22 @@ function formatPrepareRenameResult(result) {
2487
2786
  if ("defaultBehavior" in result) {
2488
2787
  return result.defaultBehavior ? "Rename supported (using default behavior)" : "Cannot rename at this position";
2489
2788
  }
2490
- const startLine = result.range.start.line + 1;
2491
- const startChar = result.range.start.character;
2492
- const endLine = result.range.end.line + 1;
2493
- const endChar = result.range.end.character;
2494
- const placeholder = result.placeholder ? ` (current: "${result.placeholder}")` : "";
2495
- return `Rename available at ${startLine}:${startChar}-${endLine}:${endChar}${placeholder}`;
2789
+ if ("range" in result && result.range) {
2790
+ const startLine = result.range.start.line + 1;
2791
+ const startChar = result.range.start.character;
2792
+ const endLine = result.range.end.line + 1;
2793
+ const endChar = result.range.end.character;
2794
+ const placeholder = result.placeholder ? ` (current: "${result.placeholder}")` : "";
2795
+ return `Rename available at ${startLine}:${startChar}-${endLine}:${endChar}${placeholder}`;
2796
+ }
2797
+ if ("start" in result && "end" in result) {
2798
+ const startLine = result.start.line + 1;
2799
+ const startChar = result.start.character;
2800
+ const endLine = result.end.line + 1;
2801
+ const endChar = result.end.character;
2802
+ return `Rename available at ${startLine}:${startChar}-${endLine}:${endChar}`;
2803
+ }
2804
+ return "Cannot rename at this position";
2496
2805
  }
2497
2806
  function formatCodeAction(action) {
2498
2807
  let result = `[${action.kind || "action"}] ${action.title}`;
@@ -2521,7 +2830,7 @@ function formatCodeActions(actions) {
2521
2830
  }
2522
2831
  function applyTextEditsToFile(filePath, edits) {
2523
2832
  try {
2524
- let content = readFileSync3(filePath, "utf-8");
2833
+ let content = readFileSync4(filePath, "utf-8");
2525
2834
  const lines = content.split(`
2526
2835
  `);
2527
2836
  const sortedEdits = [...edits].sort((a, b) => {
@@ -2546,7 +2855,7 @@ function applyTextEditsToFile(filePath, edits) {
2546
2855
  `));
2547
2856
  }
2548
2857
  }
2549
- writeFileSync(filePath, lines.join(`
2858
+ writeFileSync2(filePath, lines.join(`
2550
2859
  `), "utf-8");
2551
2860
  return { success: true, editCount: edits.length };
2552
2861
  } catch (err) {
@@ -2577,7 +2886,7 @@ function applyWorkspaceEdit(edit) {
2577
2886
  if (change.kind === "create") {
2578
2887
  try {
2579
2888
  const filePath = change.uri.replace("file://", "");
2580
- writeFileSync(filePath, "", "utf-8");
2889
+ writeFileSync2(filePath, "", "utf-8");
2581
2890
  result.filesModified.push(filePath);
2582
2891
  } catch (err) {
2583
2892
  result.success = false;
@@ -2587,8 +2896,8 @@ function applyWorkspaceEdit(edit) {
2587
2896
  try {
2588
2897
  const oldPath = change.oldUri.replace("file://", "");
2589
2898
  const newPath = change.newUri.replace("file://", "");
2590
- const content = readFileSync3(oldPath, "utf-8");
2591
- writeFileSync(newPath, content, "utf-8");
2899
+ const content = readFileSync4(oldPath, "utf-8");
2900
+ writeFileSync2(newPath, content, "utf-8");
2592
2901
  __require("fs").unlinkSync(oldPath);
2593
2902
  result.filesModified.push(newPath);
2594
2903
  } catch (err) {
@@ -3368,10 +3677,10 @@ function mergeDefs(...defs) {
3368
3677
  function cloneDef(schema) {
3369
3678
  return mergeDefs(schema._zod.def);
3370
3679
  }
3371
- function getElementAtPath(obj, path) {
3372
- if (!path)
3680
+ function getElementAtPath(obj, path2) {
3681
+ if (!path2)
3373
3682
  return obj;
3374
- return path.reduce((acc, key) => acc?.[key], obj);
3683
+ return path2.reduce((acc, key) => acc?.[key], obj);
3375
3684
  }
3376
3685
  function promiseAllObject(promisesObj) {
3377
3686
  const keys = Object.keys(promisesObj);
@@ -3730,11 +4039,11 @@ function aborted(x, startIndex = 0) {
3730
4039
  }
3731
4040
  return false;
3732
4041
  }
3733
- function prefixIssues(path, issues) {
4042
+ function prefixIssues(path2, issues) {
3734
4043
  return issues.map((iss) => {
3735
4044
  var _a;
3736
4045
  (_a = iss).path ?? (_a.path = []);
3737
- iss.path.unshift(path);
4046
+ iss.path.unshift(path2);
3738
4047
  return iss;
3739
4048
  });
3740
4049
  }
@@ -3902,7 +4211,7 @@ function treeifyError(error, _mapper) {
3902
4211
  return issue2.message;
3903
4212
  };
3904
4213
  const result = { errors: [] };
3905
- const processError = (error2, path = []) => {
4214
+ const processError = (error2, path2 = []) => {
3906
4215
  var _a, _b;
3907
4216
  for (const issue2 of error2.issues) {
3908
4217
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -3912,7 +4221,7 @@ function treeifyError(error, _mapper) {
3912
4221
  } else if (issue2.code === "invalid_element") {
3913
4222
  processError({ issues: issue2.issues }, issue2.path);
3914
4223
  } else {
3915
- const fullpath = [...path, ...issue2.path];
4224
+ const fullpath = [...path2, ...issue2.path];
3916
4225
  if (fullpath.length === 0) {
3917
4226
  result.errors.push(mapper(issue2));
3918
4227
  continue;
@@ -3944,8 +4253,8 @@ function treeifyError(error, _mapper) {
3944
4253
  }
3945
4254
  function toDotPath(_path) {
3946
4255
  const segs = [];
3947
- const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3948
- for (const seg of path) {
4256
+ const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4257
+ for (const seg of path2) {
3949
4258
  if (typeof seg === "number")
3950
4259
  segs.push(`[${seg}]`);
3951
4260
  else if (typeof seg === "symbol")
@@ -15288,13 +15597,13 @@ var lsp_code_action_resolve = tool({
15288
15597
  });
15289
15598
  // src/tools/ast-grep/constants.ts
15290
15599
  import { createRequire as createRequire4 } from "module";
15291
- import { dirname as dirname2, join as join5 } from "path";
15292
- import { existsSync as existsSync7, statSync } from "fs";
15600
+ import { dirname as dirname2, join as join6 } from "path";
15601
+ import { existsSync as existsSync8, statSync } from "fs";
15293
15602
 
15294
15603
  // src/tools/ast-grep/downloader.ts
15295
15604
  var {spawn: spawn4 } = globalThis.Bun;
15296
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, chmodSync as chmodSync2, unlinkSync as unlinkSync2 } from "fs";
15297
- import { join as join4 } from "path";
15605
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, chmodSync as chmodSync2, unlinkSync as unlinkSync2 } from "fs";
15606
+ import { join as join5 } from "path";
15298
15607
  import { homedir as homedir3 } from "os";
15299
15608
  import { createRequire as createRequire3 } from "module";
15300
15609
  var REPO2 = "ast-grep/ast-grep";
@@ -15320,19 +15629,19 @@ var PLATFORM_MAP2 = {
15320
15629
  function getCacheDir2() {
15321
15630
  if (process.platform === "win32") {
15322
15631
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
15323
- const base2 = localAppData || join4(homedir3(), "AppData", "Local");
15324
- return join4(base2, "oh-my-opencode", "bin");
15632
+ const base2 = localAppData || join5(homedir3(), "AppData", "Local");
15633
+ return join5(base2, "oh-my-opencode", "bin");
15325
15634
  }
15326
- const xdgCache = process.env.XDG_CACHE_HOME;
15327
- const base = xdgCache || join4(homedir3(), ".cache");
15328
- return join4(base, "oh-my-opencode", "bin");
15635
+ const xdgCache2 = process.env.XDG_CACHE_HOME;
15636
+ const base = xdgCache2 || join5(homedir3(), ".cache");
15637
+ return join5(base, "oh-my-opencode", "bin");
15329
15638
  }
15330
15639
  function getBinaryName3() {
15331
15640
  return process.platform === "win32" ? "sg.exe" : "sg";
15332
15641
  }
15333
15642
  function getCachedBinaryPath2() {
15334
- const binaryPath = join4(getCacheDir2(), getBinaryName3());
15335
- return existsSync6(binaryPath) ? binaryPath : null;
15643
+ const binaryPath = join5(getCacheDir2(), getBinaryName3());
15644
+ return existsSync7(binaryPath) ? binaryPath : null;
15336
15645
  }
15337
15646
  async function extractZip2(archivePath, destDir) {
15338
15647
  const proc = process.platform === "win32" ? spawn4([
@@ -15358,30 +15667,30 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
15358
15667
  }
15359
15668
  const cacheDir = getCacheDir2();
15360
15669
  const binaryName = getBinaryName3();
15361
- const binaryPath = join4(cacheDir, binaryName);
15362
- if (existsSync6(binaryPath)) {
15670
+ const binaryPath = join5(cacheDir, binaryName);
15671
+ if (existsSync7(binaryPath)) {
15363
15672
  return binaryPath;
15364
15673
  }
15365
- const { arch, os } = platformInfo;
15366
- const assetName = `app-${arch}-${os}.zip`;
15674
+ const { arch, os: os2 } = platformInfo;
15675
+ const assetName = `app-${arch}-${os2}.zip`;
15367
15676
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
15368
15677
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
15369
15678
  try {
15370
- if (!existsSync6(cacheDir)) {
15371
- mkdirSync2(cacheDir, { recursive: true });
15679
+ if (!existsSync7(cacheDir)) {
15680
+ mkdirSync3(cacheDir, { recursive: true });
15372
15681
  }
15373
15682
  const response = await fetch(downloadUrl, { redirect: "follow" });
15374
15683
  if (!response.ok) {
15375
15684
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
15376
15685
  }
15377
- const archivePath = join4(cacheDir, assetName);
15686
+ const archivePath = join5(cacheDir, assetName);
15378
15687
  const arrayBuffer = await response.arrayBuffer();
15379
15688
  await Bun.write(archivePath, arrayBuffer);
15380
15689
  await extractZip2(archivePath, cacheDir);
15381
- if (existsSync6(archivePath)) {
15690
+ if (existsSync7(archivePath)) {
15382
15691
  unlinkSync2(archivePath);
15383
15692
  }
15384
- if (process.platform !== "win32" && existsSync6(binaryPath)) {
15693
+ if (process.platform !== "win32" && existsSync7(binaryPath)) {
15385
15694
  chmodSync2(binaryPath, 493);
15386
15695
  }
15387
15696
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -15432,8 +15741,8 @@ function findSgCliPathSync() {
15432
15741
  const require2 = createRequire4(import.meta.url);
15433
15742
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
15434
15743
  const cliDir = dirname2(cliPkgPath);
15435
- const sgPath = join5(cliDir, binaryName);
15436
- if (existsSync7(sgPath) && isValidBinary(sgPath)) {
15744
+ const sgPath = join6(cliDir, binaryName);
15745
+ if (existsSync8(sgPath) && isValidBinary(sgPath)) {
15437
15746
  return sgPath;
15438
15747
  }
15439
15748
  } catch {}
@@ -15444,17 +15753,17 @@ function findSgCliPathSync() {
15444
15753
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
15445
15754
  const pkgDir = dirname2(pkgPath);
15446
15755
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
15447
- const binaryPath = join5(pkgDir, astGrepName);
15448
- if (existsSync7(binaryPath) && isValidBinary(binaryPath)) {
15756
+ const binaryPath = join6(pkgDir, astGrepName);
15757
+ if (existsSync8(binaryPath) && isValidBinary(binaryPath)) {
15449
15758
  return binaryPath;
15450
15759
  }
15451
15760
  } catch {}
15452
15761
  }
15453
15762
  if (process.platform === "darwin") {
15454
15763
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
15455
- for (const path of homebrewPaths) {
15456
- if (existsSync7(path) && isValidBinary(path)) {
15457
- return path;
15764
+ for (const path2 of homebrewPaths) {
15765
+ if (existsSync8(path2) && isValidBinary(path2)) {
15766
+ return path2;
15458
15767
  }
15459
15768
  }
15460
15769
  }
@@ -15472,8 +15781,8 @@ function getSgCliPath() {
15472
15781
  }
15473
15782
  return "sg";
15474
15783
  }
15475
- function setSgCliPath(path) {
15476
- resolvedCliPath2 = path;
15784
+ function setSgCliPath(path2) {
15785
+ resolvedCliPath2 = path2;
15477
15786
  }
15478
15787
  var SG_CLI_PATH = getSgCliPath();
15479
15788
  var CLI_LANGUAGES = [
@@ -15537,11 +15846,11 @@ var LANG_EXTENSIONS = {
15537
15846
 
15538
15847
  // src/tools/ast-grep/cli.ts
15539
15848
  var {spawn: spawn5 } = globalThis.Bun;
15540
- import { existsSync as existsSync8 } from "fs";
15849
+ import { existsSync as existsSync9 } from "fs";
15541
15850
  var resolvedCliPath3 = null;
15542
15851
  var initPromise2 = null;
15543
15852
  async function getAstGrepPath() {
15544
- if (resolvedCliPath3 !== null && existsSync8(resolvedCliPath3)) {
15853
+ if (resolvedCliPath3 !== null && existsSync9(resolvedCliPath3)) {
15545
15854
  return resolvedCliPath3;
15546
15855
  }
15547
15856
  if (initPromise2) {
@@ -15549,7 +15858,7 @@ async function getAstGrepPath() {
15549
15858
  }
15550
15859
  initPromise2 = (async () => {
15551
15860
  const syncPath = findSgCliPathSync();
15552
- if (syncPath && existsSync8(syncPath)) {
15861
+ if (syncPath && existsSync9(syncPath)) {
15553
15862
  resolvedCliPath3 = syncPath;
15554
15863
  setSgCliPath(syncPath);
15555
15864
  return syncPath;
@@ -15583,7 +15892,7 @@ async function runSg(options) {
15583
15892
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
15584
15893
  args.push(...paths);
15585
15894
  let cliPath = getSgCliPath();
15586
- if (!existsSync8(cliPath) && cliPath !== "sg") {
15895
+ if (!existsSync9(cliPath) && cliPath !== "sg") {
15587
15896
  const downloadedPath = await getAstGrepPath();
15588
15897
  if (downloadedPath) {
15589
15898
  cliPath = downloadedPath;
@@ -16028,8 +16337,8 @@ var ast_grep_transform = tool({
16028
16337
  var {spawn: spawn6 } = globalThis.Bun;
16029
16338
 
16030
16339
  // src/tools/safe-grep/constants.ts
16031
- import { existsSync as existsSync9 } from "fs";
16032
- import { join as join6, dirname as dirname3 } from "path";
16340
+ import { existsSync as existsSync10 } from "fs";
16341
+ import { join as join7, dirname as dirname3 } from "path";
16033
16342
  import { spawnSync } from "child_process";
16034
16343
  var cachedCli = null;
16035
16344
  function findExecutable(name) {
@@ -16050,13 +16359,13 @@ function getOpenCodeBundledRg() {
16050
16359
  const isWindows = process.platform === "win32";
16051
16360
  const rgName = isWindows ? "rg.exe" : "rg";
16052
16361
  const candidates = [
16053
- join6(execDir, rgName),
16054
- join6(execDir, "bin", rgName),
16055
- join6(execDir, "..", "bin", rgName),
16056
- join6(execDir, "..", "libexec", rgName)
16362
+ join7(execDir, rgName),
16363
+ join7(execDir, "bin", rgName),
16364
+ join7(execDir, "..", "bin", rgName),
16365
+ join7(execDir, "..", "libexec", rgName)
16057
16366
  ];
16058
16367
  for (const candidate of candidates) {
16059
- if (existsSync9(candidate)) {
16368
+ if (existsSync10(candidate)) {
16060
16369
  return candidate;
16061
16370
  }
16062
16371
  }
@@ -16389,11 +16698,11 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
16389
16698
  });
16390
16699
  // src/index.ts
16391
16700
  import * as fs3 from "fs";
16392
- import * as path from "path";
16701
+ import * as path2 from "path";
16393
16702
  function loadPluginConfig(directory) {
16394
16703
  const configPaths = [
16395
- path.join(directory, "oh-my-opencode.json"),
16396
- path.join(directory, ".oh-my-opencode.json")
16704
+ path2.join(directory, "oh-my-opencode.json"),
16705
+ path2.join(directory, ".oh-my-opencode.json")
16397
16706
  ];
16398
16707
  for (const configPath of configPaths) {
16399
16708
  try {
@@ -16418,7 +16727,9 @@ var OhMyOpenCodePlugin = async (ctx) => {
16418
16727
  const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx);
16419
16728
  const contextWindowMonitor = createContextWindowMonitorHook(ctx);
16420
16729
  const sessionRecovery = createSessionRecoveryHook(ctx);
16730
+ const pulseMonitor = createPulseMonitorHook(ctx);
16421
16731
  const commentChecker = createCommentCheckerHooks();
16732
+ const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
16422
16733
  updateTerminalTitle({ sessionId: "main" });
16423
16734
  const pluginConfig = loadPluginConfig(ctx.directory);
16424
16735
  let mainSessionID;
@@ -16444,6 +16755,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
16444
16755
  event: async (input) => {
16445
16756
  await todoContinuationEnforcer(input);
16446
16757
  await contextWindowMonitor.event(input);
16758
+ await pulseMonitor.event(input);
16447
16759
  const { event } = input;
16448
16760
  const props = event.properties;
16449
16761
  if (event.type === "session.created") {
@@ -16526,6 +16838,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
16526
16838
  }
16527
16839
  },
16528
16840
  "tool.execute.before": async (input, output) => {
16841
+ await pulseMonitor["tool.execute.before"]();
16529
16842
  await commentChecker["tool.execute.before"](input, output);
16530
16843
  if (input.sessionID === mainSessionID) {
16531
16844
  updateTerminalTitle({
@@ -16538,6 +16851,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
16538
16851
  }
16539
16852
  },
16540
16853
  "tool.execute.after": async (input, output) => {
16854
+ await pulseMonitor["tool.execute.after"](input);
16855
+ await grepOutputTruncator["tool.execute.after"](input, output);
16541
16856
  await contextWindowMonitor["tool.execute.after"](input, output);
16542
16857
  await commentChecker["tool.execute.after"](input, output);
16543
16858
  if (input.sessionID === mainSessionID) {