fix: resolve 25 pre-publish blockers

- postinstall.mjs: fix alias package detection
- migrate-legacy-plugin-entry: dedupe + regression tests
- task_system: default consistency across runtime paths
- task() contract: consistent tool behavior
- runtime model selection, tool cap, stale-task cancellation
- recovery sanitization, context-limit gating
- Ralph semantic DONE hardening, Atlas fallback persistence
- native-skill description/content, skill path traversal guard
- publish workflow: platform awaited via reusable workflow job
- release: version edits reapplied before commit/tag
- JSONC plugin migration: top-level plugin key safety
- cold-cache: user fallback models skip disconnected providers
- docs/version/release framing updates

Verified: bun test (4599 pass), tsc --noEmit clean, bun run build clean
This commit is contained in:
YeonGyu-Kim
2026-03-28 15:24:18 +09:00
parent 44b039bef6
commit d2c576c510
62 changed files with 1264 additions and 292 deletions
+39
View File
@@ -1,6 +1,7 @@
const { describe, expect, test } = require("bun:test")
const { createToolExecuteBeforeHandler } = require("./tool-execute-before")
const { createToolRegistry } = require("./tool-registry")
const { builtinTools } = require("../tools")
describe("createToolExecuteBeforeHandler", () => {
test("does not execute subagent question blocker hook for question tool", async () => {
@@ -268,6 +269,44 @@ describe("createToolRegistry", () => {
})
})
})
describe("#given max_tools is lower than or equal to builtin tool count", () => {
describe("#when creating the tool registry", () => {
test("#then it trims to the exact configured cap", () => {
const result = createToolRegistry(
createRegistryInput({
experimental: { max_tools: Object.keys(builtinTools).length },
}),
)
expect(Object.keys(result.filteredTools)).toHaveLength(Object.keys(builtinTools).length)
})
})
})
describe("#given max_tools is set below the full plugin tool count", () => {
describe("#when creating the tool registry", () => {
test("#then it enforces the exact cap deterministically", () => {
const result = createToolRegistry(
createRegistryInput({
experimental: { max_tools: 10 },
}),
)
expect(Object.keys(result.filteredTools)).toHaveLength(10)
})
test("#then it keeps the task tool when lower-priority tools can satisfy the cap", () => {
const result = createToolRegistry(
createRegistryInput({
experimental: { max_tools: 10 },
}),
)
expect(result.filteredTools.task).toBeDefined()
})
})
})
})
export {}
+58 -23
View File
@@ -40,6 +40,63 @@ export type ToolRegistryResult = {
taskSystemEnabled: boolean
}
const LOW_PRIORITY_TOOL_ORDER = [
"session_list",
"session_read",
"session_search",
"session_info",
"interactive_bash",
"look_at",
"call_omo_agent",
"task_create",
"task_get",
"task_list",
"task_update",
"background_output",
"background_cancel",
"hashline_edit",
"ast_grep_replace",
"ast_grep_search",
"glob",
"grep",
"skill_mcp",
"skill",
"task",
"lsp_rename",
"lsp_prepare_rename",
"lsp_find_references",
"lsp_goto_definition",
"lsp_symbols",
"lsp_diagnostics",
] as const
function trimToolsToCap(filteredTools: ToolsRecord, maxTools: number): void {
const toolNames = Object.keys(filteredTools)
if (toolNames.length <= maxTools) return
const removableToolNames = [
...LOW_PRIORITY_TOOL_ORDER.filter((toolName) => toolNames.includes(toolName)),
...toolNames
.filter((toolName) => !LOW_PRIORITY_TOOL_ORDER.includes(toolName as (typeof LOW_PRIORITY_TOOL_ORDER)[number]))
.sort(),
]
let currentCount = toolNames.length
let removed = 0
for (const toolName of removableToolNames) {
if (currentCount <= maxTools) break
if (!filteredTools[toolName]) continue
delete filteredTools[toolName]
currentCount -= 1
removed += 1
}
log(
`[tool-registry] Trimmed ${removed} tools to satisfy max_tools=${maxTools}. Final plugin tool count=${currentCount}.`,
)
}
export function createToolRegistry(args: {
ctx: PluginContext
pluginConfig: OhMyOpenCodeConfig
@@ -158,29 +215,7 @@ export function createToolRegistry(args: {
const maxTools = pluginConfig.experimental?.max_tools
if (maxTools) {
const estimatedBuiltinTools = 20
const pluginToolBudget = maxTools - estimatedBuiltinTools
const toolEntries = Object.entries(filteredTools)
if (pluginToolBudget > 0 && toolEntries.length > pluginToolBudget) {
const excess = toolEntries.length - pluginToolBudget
log(`[tool-registry] Tool count (${toolEntries.length} plugin + ~${estimatedBuiltinTools} builtin = ~${toolEntries.length + estimatedBuiltinTools}) exceeds max_tools=${maxTools}. Trimming ${excess} lower-priority tools.`)
const lowPriorityTools = [
"session_list", "session_read", "session_search", "session_info",
"call_omo_agent", "interactive_bash", "look_at",
"task_create", "task_get", "task_list", "task_update",
]
let removed = 0
for (const toolName of lowPriorityTools) {
if (removed >= excess) break
if (filteredTools[toolName]) {
delete filteredTools[toolName]
removed += 1
}
}
if (removed < excess) {
log(`[tool-registry] WARNING: Could not trim enough tools. ${toolEntries.length - removed} plugin tools remain.`)
}
}
trimToolsToCap(filteredTools, maxTools)
}
return {