Compare commits

...

5 Commits

Author SHA1 Message Date
github-actions[bot]
e3ff34c76e release: v2.3.1 2025-12-19 03:10:03 +00:00
YeonGyu-Kim
8440dce902 fix(hooks): restore grep truncation by removing unused grep-output-truncator (#120)
The grep-output-truncator hook was never registered in index.ts, so grep
output was not being truncated since commit 03a4501 which removed grep/Grep
from tool-output-truncator's TRUNCATABLE_TOOLS list.

- Remove unused grep-output-truncator.ts
- Add "grep" and "Grep" back to tool-output-truncator's TRUNCATABLE_TOOLS

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 12:08:38 +09:00
YeonGyu-Kim
5dba5992b4 fix(schema): update schema to reflect Sisyphus agent (#119)
- Rename OmO → Sisyphus in disabled_agents enum
- Rename OmO, OmO-Plan → Sisyphus, Planner-Sisyphus in agents properties
- Replace omo_agent with sisyphus_agent config option
- Add experimental config options (aggressive_truncation, empty_message_recovery, auto_resume)

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-19 12:02:36 +09:00
Matthew DeGarmo
662bae2454 feat(lsp): add bash-language-server to builtin servers (#112) 2025-12-19 11:21:13 +09:00
YeonGyu-Kim
c37d41edb2 fix(auto-update-checker): add bun.lock handling to invalidatePackage()
- Removes package from node_modules, package.json dependencies, AND bun.lock (workspaces.dependencies + packages)
- Fixes issue where 'update available' notification appeared but actual update didn't happen on restart due to bun.lock pinning old version
- Added BunLockfile interface and stripTrailingCommas helper for JSON parsing

🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-19 04:44:01 +09:00
7 changed files with 72 additions and 139 deletions

View File

@@ -24,7 +24,7 @@
"items": {
"type": "string",
"enum": [
"OmO",
"Sisyphus",
"oracle",
"librarian",
"explore",
@@ -288,7 +288,7 @@
}
}
},
"OmO": {
"Sisyphus": {
"type": "object",
"properties": {
"model": {
@@ -399,7 +399,7 @@
}
}
},
"OmO-Plan": {
"Planner-Sisyphus": {
"type": "object",
"properties": {
"model": {
@@ -1201,13 +1201,27 @@
"google_auth": {
"type": "boolean"
},
"omo_agent": {
"sisyphus_agent": {
"type": "object",
"properties": {
"disabled": {
"type": "boolean"
}
}
},
"experimental": {
"type": "object",
"properties": {
"aggressive_truncation": {
"type": "boolean"
},
"empty_message_recovery": {
"type": "boolean"
},
"auto_resume": {
"type": "boolean"
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode",
"version": "2.3.0",
"version": "2.3.1",
"description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -3,6 +3,49 @@ import * as path from "node:path"
import { CACHE_DIR, PACKAGE_NAME } from "./constants"
import { log } from "../../shared/logger"
interface BunLockfile {
workspaces?: {
""?: {
dependencies?: Record<string, string>
}
}
packages?: Record<string, unknown>
}
function stripTrailingCommas(json: string): string {
return json.replace(/,(\s*[}\]])/g, "$1")
}
function removeFromBunLock(packageName: string): boolean {
const lockPath = path.join(CACHE_DIR, "bun.lock")
if (!fs.existsSync(lockPath)) return false
try {
const content = fs.readFileSync(lockPath, "utf-8")
const lock = JSON.parse(stripTrailingCommas(content)) as BunLockfile
let modified = false
if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
delete lock.workspaces[""].dependencies[packageName]
modified = true
}
if (lock.packages?.[packageName]) {
delete lock.packages[packageName]
modified = true
}
if (modified) {
fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2))
log(`[auto-update-checker] Removed from bun.lock: ${packageName}`)
}
return modified
} catch {
return false
}
}
export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
try {
const pkgDir = path.join(CACHE_DIR, "node_modules", packageName)
@@ -10,6 +53,7 @@ export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
let packageRemoved = false
let dependencyRemoved = false
let lockRemoved = false
if (fs.existsSync(pkgDir)) {
fs.rmSync(pkgDir, { recursive: true, force: true })
@@ -28,7 +72,9 @@ export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
}
}
if (!packageRemoved && !dependencyRemoved) {
lockRemoved = removeFromBunLock(packageName)
if (!packageRemoved && !dependencyRemoved && !lockRemoved) {
log(`[auto-update-checker] Package not found, nothing to invalidate: ${packageName}`)
return false
}

View File

@@ -1,131 +0,0 @@
import type { PluginInput } from "@opencode-ai/plugin"
const ANTHROPIC_ACTUAL_LIMIT = 200_000
const CHARS_PER_TOKEN_ESTIMATE = 4
const TARGET_MAX_TOKENS = 50_000
interface AssistantMessageInfo {
role: "assistant"
tokens: {
input: number
output: number
reasoning: number
cache: { read: number; write: number }
}
}
interface MessageWrapper {
info: { role: string } & Partial<AssistantMessageInfo>
}
function estimateTokens(text: string): number {
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE)
}
function truncateToTokenLimit(output: string, maxTokens: number): { result: string; truncated: boolean } {
const currentTokens = estimateTokens(output)
if (currentTokens <= maxTokens) {
return { result: output, truncated: false }
}
const lines = output.split("\n")
if (lines.length <= 3) {
const maxChars = maxTokens * CHARS_PER_TOKEN_ESTIMATE
return {
result: output.slice(0, maxChars) + "\n\n[Output truncated due to context window limit]",
truncated: true,
}
}
const headerLines = lines.slice(0, 3)
const contentLines = lines.slice(3)
const headerText = headerLines.join("\n")
const headerTokens = estimateTokens(headerText)
const availableTokens = maxTokens - headerTokens - 50
if (availableTokens <= 0) {
return {
result: headerText + "\n\n[Content truncated due to context window limit]",
truncated: true,
}
}
let resultLines: string[] = []
let currentTokenCount = 0
for (const line of contentLines) {
const lineTokens = estimateTokens(line + "\n")
if (currentTokenCount + lineTokens > availableTokens) {
break
}
resultLines.push(line)
currentTokenCount += lineTokens
}
const truncatedContent = [...headerLines, ...resultLines].join("\n")
const removedCount = contentLines.length - resultLines.length
return {
result: truncatedContent + `\n\n[${removedCount} more lines truncated due to context window limit]`,
truncated: true,
}
}
export function createGrepOutputTruncatorHook(ctx: PluginInput) {
const GREP_TOOLS = ["grep", "Grep", "safe_grep"]
const toolExecuteAfter = async (
input: { tool: string; sessionID: string; callID: string },
output: { title: string; output: string; metadata: unknown }
) => {
if (!GREP_TOOLS.includes(input.tool)) return
const { sessionID } = input
try {
const response = await ctx.client.session.messages({
path: { id: sessionID },
})
const messages = (response.data ?? response) as MessageWrapper[]
const assistantMessages = messages
.filter((m) => m.info.role === "assistant")
.map((m) => m.info as AssistantMessageInfo)
if (assistantMessages.length === 0) return
// Use only the last assistant message's input tokens
// This reflects the ACTUAL current context window usage (post-compaction)
const lastAssistant = assistantMessages[assistantMessages.length - 1]
const lastTokens = lastAssistant.tokens
const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0)
const remainingTokens = ANTHROPIC_ACTUAL_LIMIT - totalInputTokens
const maxOutputTokens = Math.min(
remainingTokens * 0.5,
TARGET_MAX_TOKENS
)
if (maxOutputTokens <= 0) {
output.output = "[Output suppressed - context window exhausted]"
return
}
const { result, truncated } = truncateToTokenLimit(output.output, maxOutputTokens)
if (truncated) {
output.output = result
}
} catch {
// Graceful degradation
}
}
return {
"tool.execute.after": toolExecuteAfter,
}
}

View File

@@ -3,7 +3,6 @@ export { createContextWindowMonitorHook } from "./context-window-monitor";
export { createSessionNotification } from "./session-notification";
export { createSessionRecoveryHook, type SessionRecoveryHook, type SessionRecoveryOptions } from "./session-recovery";
export { createCommentCheckerHooks } from "./comment-checker";
export { createGrepOutputTruncatorHook } from "./grep-output-truncator";
export { createToolOutputTruncatorHook } from "./tool-output-truncator";
export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector";
export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector";

View File

@@ -1,8 +1,9 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { createDynamicTruncator } from "../shared/dynamic-truncator"
// Note: "grep" and "Grep" are handled by dedicated grep-output-truncator.ts
const TRUNCATABLE_TOOLS = [
"grep",
"Grep",
"safe_grep",
"glob",
"Glob",

View File

@@ -109,6 +109,10 @@ export const BUILTIN_SERVERS: Record<string, Omit<LSPServerConfig, "id">> = {
command: ["astro-ls", "--stdio"],
extensions: [".astro"],
},
"bash-ls": {
command: ["bash-language-server", "start"],
extensions: [".sh", ".bash", ".zsh", ".ksh"],
},
jdtls: {
command: ["jdtls"],
extensions: [".java"],