Compare commits

..

8 Commits

Author SHA1 Message Date
github-actions[bot]
fdb39ba404 release: v0.1.7 2025-12-05 00:24:20 +00:00
YeonGyu-Kim
36ef885141 fix: trust @ast-grep/napi in CI to enable native module install scripts 2025-12-05 04:29:40 +09:00
YeonGyu-Kim
909ce37826 fix: remove --ignore-scripts from bun install, add build verification step 2025-12-05 04:19:13 +09:00
YeonGyu-Kim
132bb3c373 fix: add --ignore-scripts to npm publish to prevent CI build failure 2025-12-05 04:16:53 +09:00
YeonGyu-Kim
180d16b977 fix: prevent plugin crash by removing non-function exports from barrel files
BREAKING: OpenCode plugin loader calls all exports as functions.
Exporting non-function values (schemas, constants, types) causes TypeError.

Changes:
- Remove OhMyOpenCodeConfigSchema export from root index.ts
- Replace 'export *' with explicit function exports in hooks/index.ts
- Remove 'export *' from comment-checker/index.ts
2025-12-05 04:08:59 +09:00
YeonGyu-Kim
eba89a6626 hotfix: move McpNameSchema to src/mcp/types.ts for proper module organization 2025-12-05 03:58:21 +09:00
YeonGyu-Kim
0a82787614 hotfix: use McpName from config schema instead of duplicate type definition 2025-12-05 03:56:14 +09:00
YeonGyu-Kim
a1a2d2fdb3 hotfix: add empty content message recovery to session recovery 2025-12-05 03:54:51 +09:00
10 changed files with 93 additions and 23 deletions

View File

@@ -48,10 +48,37 @@ jobs:
run: npm config set registry https://registry.npmjs.org
- name: Install dependencies
run: bun install --ignore-scripts
run: bun install
env:
BUN_INSTALL_ALLOW_SCRIPTS: "@ast-grep/napi"
- name: Debug environment
run: |
echo "=== Bun version ==="
bun --version
echo "=== Node version ==="
node --version
echo "=== Current directory ==="
pwd
echo "=== List src/ ==="
ls -la src/
echo "=== package.json scripts ==="
cat package.json | jq '.scripts'
- name: Build
run: bun run build
run: |
echo "=== Running bun build ==="
bun build src/index.ts --outdir dist --target bun --format esm --external @ast-grep/napi
echo "=== bun build exit code: $? ==="
echo "=== Running tsc ==="
tsc --emitDeclarationOnly
echo "=== Running build:schema ==="
bun run build:schema
- name: Verify build output
run: |
ls -la dist/
test -f dist/index.js || (echo "ERROR: dist/index.js not found!" && exit 1)
- name: Publish
run: bun run script/publish.ts

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode",
"version": "0.1.2",
"version": "0.1.7",
"description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -57,6 +57,7 @@
"bun": ">=1.0.0"
},
"trustedDependencies": [
"@ast-grep/cli"
"@ast-grep/cli",
"@ast-grep/napi"
]
}

View File

@@ -63,10 +63,11 @@ async function generateChangelog(previous: string): Promise<string> {
async function buildAndPublish(): Promise<void> {
console.log("\nPublishing to npm...")
// --ignore-scripts: workflow에서 이미 빌드 완료, prepublishOnly 재실행 방지
if (process.env.CI) {
await $`npm publish --access public --provenance`
await $`npm publish --access public --provenance --ignore-scripts`
} else {
await $`npm publish --access public`
await $`npm publish --access public --ignore-scripts`
}
}

View File

@@ -1,4 +1,5 @@
import { z } from "zod"
import { McpNameSchema } from "../mcp/types"
const PermissionValue = z.enum(["ask", "allow", "deny"])
@@ -23,8 +24,6 @@ export const AgentNameSchema = z.enum([
"document-writer",
])
export const McpNameSchema = z.enum(["websearch_exa", "context7"])
export const AgentOverrideConfigSchema = z.object({
model: z.string().optional(),
temperature: z.number().min(0).max(2).optional(),
@@ -53,5 +52,6 @@ export const OhMyOpenCodeConfigSchema = z.object({
export type OhMyOpenCodeConfig = z.infer<typeof OhMyOpenCodeConfigSchema>
export type AgentOverrideConfig = z.infer<typeof AgentOverrideConfigSchema>
export type AgentOverrides = z.infer<typeof AgentOverridesSchema>
export type McpName = z.infer<typeof McpNameSchema>
export type AgentName = z.infer<typeof AgentNameSchema>
export { McpNameSchema, type McpName } from "../mcp/types"

View File

@@ -94,8 +94,4 @@ export function createCommentCheckerHooks() {
}
}
export * from "./types"
export * from "./constants"
export * from "./detector"
export * from "./filters"
export * from "./output"

View File

@@ -1,5 +1,5 @@
export * from "./todo-continuation-enforcer"
export * from "./context-window-monitor"
export * from "./session-notification"
export * from "./session-recovery"
export * from "./comment-checker"
export { createTodoContinuationEnforcer } from "./todo-continuation-enforcer"
export { createContextWindowMonitorHook } from "./context-window-monitor"
export { createSessionNotification } from "./session-notification"
export { createSessionRecoveryHook } from "./session-recovery"
export { createCommentCheckerHooks } from "./comment-checker"

View File

@@ -1,7 +1,7 @@
/**
* Session Recovery - Message State Error Recovery
*
* Handles THREE specific scenarios:
* Handles FOUR specific scenarios:
* 1. tool_use block exists without tool_result
* - Recovery: inject tool_result with "cancelled" content
*
@@ -10,6 +10,9 @@
*
* 3. Thinking disabled but message contains thinking blocks
* - Recovery: strip thinking/redacted_thinking blocks
*
* 4. Empty content message (non-empty content required)
* - Recovery: delete the empty message via revert
*/
import type { PluginInput } from "@opencode-ai/plugin"
@@ -17,7 +20,7 @@ import type { createOpencodeClient } from "@opencode-ai/sdk"
type Client = ReturnType<typeof createOpencodeClient>
type RecoveryErrorType = "tool_result_missing" | "thinking_block_order" | "thinking_disabled_violation" | null
type RecoveryErrorType = "tool_result_missing" | "thinking_block_order" | "thinking_disabled_violation" | "empty_content_message" | null
interface MessageInfo {
id?: string
@@ -75,6 +78,10 @@ function detectErrorType(error: unknown): RecoveryErrorType {
return "thinking_disabled_violation"
}
if (message.includes("non-empty content") || message.includes("must have non-empty content")) {
return "empty_content_message"
}
return null
}
@@ -204,6 +211,35 @@ async function recoverThinkingDisabledViolation(
return false
}
async function recoverEmptyContentMessage(
client: Client,
sessionID: string,
failedAssistantMsg: MessageData,
directory: string
): Promise<boolean> {
const messageID = failedAssistantMsg.info?.id
const parentMsgID = failedAssistantMsg.info?.parentID
if (!messageID) {
return false
}
// Revert to parent message (delete the empty message)
const revertTargetID = parentMsgID || messageID
try {
await client.session.revert({
path: { id: sessionID },
body: { messageID: revertTargetID },
query: { directory },
})
return true
} catch {
return false
}
}
async function fallbackRevertStrategy(
client: Client,
sessionID: string,
@@ -308,11 +344,13 @@ export function createSessionRecoveryHook(ctx: PluginInput) {
tool_result_missing: "Tool Crash Recovery",
thinking_block_order: "Thinking Block Recovery",
thinking_disabled_violation: "Thinking Strip Recovery",
empty_content_message: "Empty Message Recovery",
}
const toastMessages: Record<RecoveryErrorType & string, string> = {
tool_result_missing: "Injecting cancelled tool results...",
thinking_block_order: "Fixing message structure...",
thinking_disabled_violation: "Stripping thinking blocks...",
empty_content_message: "Deleting empty message...",
}
const toastTitle = toastTitles[errorType]
const toastMessage = toastMessages[errorType]
@@ -336,6 +374,8 @@ export function createSessionRecoveryHook(ctx: PluginInput) {
success = await recoverThinkingBlockOrder(ctx.client, sessionID, failedMsg, ctx.directory)
} else if (errorType === "thinking_disabled_violation") {
success = await recoverThinkingDisabledViolation(ctx.client, sessionID, failedMsg)
} else if (errorType === "empty_content_message") {
success = await recoverEmptyContentMessage(ctx.client, sessionID, failedMsg, ctx.directory)
}
return success

View File

@@ -190,7 +190,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
export default OhMyOpenCodePlugin
export { OhMyOpenCodeConfigSchema } from "./config"
export type {
OhMyOpenCodeConfig,
AgentName,

View File

@@ -1,7 +1,8 @@
import { websearch_exa } from "./websearch-exa"
import { context7 } from "./context7"
import type { McpName } from "./types"
export type McpName = "websearch_exa" | "context7"
export { McpNameSchema, type McpName } from "./types"
const allBuiltinMcps: Record<McpName, { type: "remote"; url: string; enabled: boolean }> = {
websearch_exa,

5
src/mcp/types.ts Normal file
View File

@@ -0,0 +1,5 @@
import { z } from "zod"
export const McpNameSchema = z.enum(["websearch_exa", "context7"])
export type McpName = z.infer<typeof McpNameSchema>