Compare commits

...

6 Commits

Author SHA1 Message Date
github-actions[bot]
456d9cea65 release: v3.1.8 2026-01-30 02:58:12 +00:00
justsisyphus
30f893b766 fix(cli/run): fix [undefine] tag and add text preview to verbose log
- Fix sessionTag showing '[undefine]' when sessionID is undefined
  - System events now display as '[system]' instead
- Fix message.updated expecting non-existent 'content' field
  - SDK's EventMessageUpdated only contains info metadata, not content
  - Content is streamed via message.part.updated events
- Add text preview to message.part.updated verbose logging
- Update MessageUpdatedProps type to match SDK structure
- Update tests to reflect actual SDK behavior
2026-01-30 11:45:58 +09:00
justsisyphus
c905e1cb7a fix(delegate-task): restore resolved.model to category userModel chain (#1227)
PR #1227 incorrectly removed resolved.model from the userModel chain,
assuming it was bypassing the fallback chain. However, resolved.model
contained the category's DEFAULT_CATEGORIES model (e.g., quick ->
claude-haiku-4-5), not the main session model.

Without resolved.model, when connectedProvidersCache is null and
availableModels is empty, category model resolution falls through to
systemDefaultModel (opus) instead of using the category's default.

This fix restores the original priority:
1. User category model override
2. Category default model (from resolved.model)
3. sisyphusJuniorModel
4. Fallback chain
5. System default
2026-01-30 11:45:19 +09:00
YeonGyu-Kim
d3e2b36e3d refactor(tmux-subagent): introduce dependency injection for testability (#1267)
Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
2026-01-30 10:59:54 +09:00
YeonGyu-Kim
5f0b6d49f5 fix(run): prevent premature exit on idle before meaningful work (#1263)
The run command's completion check had a race condition: when a session
transitions busy->idle before the LLM generates any output (empty
response or API delay), checkCompletionConditions() returns true because
0 incomplete todos + 0 busy children = complete. This caused the runner
to exit with 'All tasks completed' before any work was done.

Fix:
- Add hasReceivedMeaningfulWork flag to EventState
- Set flag on: assistant text content, tool execution, or message update
  with actual content (all scoped to main session only)
- Guard completion check in runner poll loop: skip if no meaningful work
  has been observed yet

This ensures the runner waits until the session has produced at least one
observable output before considering completion conditions.

Adds 6 new test cases covering the race condition scenarios.
2026-01-30 09:10:24 +09:00
github-actions[bot]
b45408dd9c @LeekJay has signed the CLA in code-yeongyu/oh-my-opencode#1254 2026-01-29 17:03:39 +00:00
17 changed files with 288 additions and 63 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode",
"version": "3.1.7",
"version": "3.1.8",
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -74,13 +74,13 @@
"typescript": "^5.7.3"
},
"optionalDependencies": {
"oh-my-opencode-darwin-arm64": "3.1.7",
"oh-my-opencode-darwin-x64": "3.1.7",
"oh-my-opencode-linux-arm64": "3.1.7",
"oh-my-opencode-linux-arm64-musl": "3.1.7",
"oh-my-opencode-linux-x64": "3.1.7",
"oh-my-opencode-linux-x64-musl": "3.1.7",
"oh-my-opencode-windows-x64": "3.1.7"
"oh-my-opencode-darwin-arm64": "3.1.8",
"oh-my-opencode-darwin-x64": "3.1.8",
"oh-my-opencode-linux-arm64": "3.1.8",
"oh-my-opencode-linux-arm64-musl": "3.1.8",
"oh-my-opencode-linux-x64": "3.1.8",
"oh-my-opencode-linux-x64-musl": "3.1.8",
"oh-my-opencode-windows-x64": "3.1.8"
},
"trustedDependencies": [
"@ast-grep/cli",

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-darwin-arm64",
"version": "3.1.7",
"version": "3.1.8",
"description": "Platform-specific binary for oh-my-opencode (darwin-arm64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-darwin-x64",
"version": "3.1.7",
"version": "3.1.8",
"description": "Platform-specific binary for oh-my-opencode (darwin-x64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-arm64-musl",
"version": "3.1.7",
"version": "3.1.8",
"description": "Platform-specific binary for oh-my-opencode (linux-arm64-musl)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-arm64",
"version": "3.1.7",
"version": "3.1.8",
"description": "Platform-specific binary for oh-my-opencode (linux-arm64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64-musl",
"version": "3.1.7",
"version": "3.1.8",
"description": "Platform-specific binary for oh-my-opencode (linux-x64-musl)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64",
"version": "3.1.7",
"version": "3.1.8",
"description": "Platform-specific binary for oh-my-opencode (linux-x64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-windows-x64",
"version": "3.1.7",
"version": "3.1.8",
"description": "Platform-specific binary for oh-my-opencode (windows-x64)",
"license": "MIT",
"repository": {

View File

@@ -967,6 +967,14 @@
"created_at": "2026-01-29T09:00:28Z",
"repoId": 1108837393,
"pullRequestNo": 1241
},
{
"name": "LeekJay",
"id": 39609783,
"comment_id": 3819009761,
"created_at": "2026-01-29T17:03:24Z",
"repoId": 1108837393,
"pullRequestNo": 1254
}
]
}

View File

@@ -82,6 +82,7 @@ describe("createEventState", () => {
expect(state.lastOutput).toBe("")
expect(state.lastPartText).toBe("")
expect(state.currentTool).toBe(null)
expect(state.hasReceivedMeaningfulWork).toBe(false)
})
})
@@ -126,6 +127,119 @@ describe("event handling", () => {
expect(state.mainSessionIdle).toBe(false)
})
it("hasReceivedMeaningfulWork is false initially after session.idle", async () => {
// #given - session goes idle without any assistant output (race condition scenario)
const ctx = createMockContext("my-session")
const state = createEventState()
const payload: EventPayload = {
type: "session.idle",
properties: { sessionID: "my-session" },
}
const events = toAsyncIterable([payload])
const { processEvents } = await import("./events")
// #when
await processEvents(ctx, events, state)
// #then - idle but no meaningful work yet
expect(state.mainSessionIdle).toBe(true)
expect(state.hasReceivedMeaningfulWork).toBe(false)
})
it("message.updated with assistant role sets hasReceivedMeaningfulWork", async () => {
// #given
const ctx = createMockContext("my-session")
const state = createEventState()
const payload: EventPayload = {
type: "message.updated",
properties: {
info: { sessionID: "my-session", role: "assistant" },
},
}
const events = toAsyncIterable([payload])
const { processEvents } = await import("./events")
// #when
await processEvents(ctx, events, state)
// #then
expect(state.hasReceivedMeaningfulWork).toBe(true)
})
it("message.updated with user role does not set hasReceivedMeaningfulWork", async () => {
// #given - user message should not count as meaningful work
const ctx = createMockContext("my-session")
const state = createEventState()
const payload: EventPayload = {
type: "message.updated",
properties: {
info: { sessionID: "my-session", role: "user" },
},
}
const events = toAsyncIterable([payload])
const { processEvents } = await import("./events")
// #when
await processEvents(ctx, events, state)
// #then - user role should not count as meaningful work
expect(state.hasReceivedMeaningfulWork).toBe(false)
})
it("tool.execute sets hasReceivedMeaningfulWork", async () => {
// #given
const ctx = createMockContext("my-session")
const state = createEventState()
const payload: EventPayload = {
type: "tool.execute",
properties: {
sessionID: "my-session",
name: "read_file",
input: { filePath: "/src/index.ts" },
},
}
const events = toAsyncIterable([payload])
const { processEvents } = await import("./events")
// #when
await processEvents(ctx, events, state)
// #then
expect(state.hasReceivedMeaningfulWork).toBe(true)
})
it("tool.execute from different session does not set hasReceivedMeaningfulWork", async () => {
// #given
const ctx = createMockContext("my-session")
const state = createEventState()
const payload: EventPayload = {
type: "tool.execute",
properties: {
sessionID: "other-session",
name: "read_file",
input: { filePath: "/src/index.ts" },
},
}
const events = toAsyncIterable([payload])
const { processEvents } = await import("./events")
// #when
await processEvents(ctx, events, state)
// #then - different session's tool call shouldn't count
expect(state.hasReceivedMeaningfulWork).toBe(false)
})
it("session.status with busy type sets mainSessionIdle to false", async () => {
// #given
const ctx = createMockContext("my-session")
@@ -136,6 +250,7 @@ describe("event handling", () => {
lastOutput: "",
lastPartText: "",
currentTool: null,
hasReceivedMeaningfulWork: false,
}
const payload: EventPayload = {

View File

@@ -63,6 +63,8 @@ export interface EventState {
lastOutput: string
lastPartText: string
currentTool: string | null
/** Set to true when the main session has produced meaningful work (text, tool call, or tool result) */
hasReceivedMeaningfulWork: boolean
}
export function createEventState(): EventState {
@@ -73,6 +75,7 @@ export function createEventState(): EventState {
lastOutput: "",
lastPartText: "",
currentTool: null,
hasReceivedMeaningfulWork: false,
}
}
@@ -113,7 +116,9 @@ function logEventVerbose(ctx: RunContext, payload: EventPayload): void {
const isMainSession = sessionID === ctx.sessionID
const sessionTag = isMainSession
? pc.green("[MAIN]")
: pc.yellow(`[${String(sessionID).slice(0, 8)}]`)
: sessionID
? pc.yellow(`[${String(sessionID).slice(0, 8)}]`)
: pc.dim("[system]")
switch (payload.type) {
case "session.idle":
@@ -124,8 +129,6 @@ function logEventVerbose(ctx: RunContext, payload: EventPayload): void {
}
case "message.part.updated": {
// Skip verbose logging for partial message updates
// Only log tool invocation state changes, not text streaming
const partProps = props as MessagePartUpdatedProps | undefined
const part = partProps?.part
if (part?.type === "tool-invocation") {
@@ -133,6 +136,11 @@ function logEventVerbose(ctx: RunContext, payload: EventPayload): void {
console.error(
pc.dim(`${sessionTag} message.part (tool): ${toolPart.toolName} [${toolPart.state}]`)
)
} else if (part?.type === "text" && part.text) {
const preview = part.text.slice(0, 80).replace(/\n/g, "\\n")
console.error(
pc.dim(`${sessionTag} message.part (text): "${preview}${part.text.length > 80 ? "..." : ""}"`)
)
}
break
}
@@ -140,11 +148,10 @@ function logEventVerbose(ctx: RunContext, payload: EventPayload): void {
case "message.updated": {
const msgProps = props as MessageUpdatedProps | undefined
const role = msgProps?.info?.role ?? "unknown"
const content = msgProps?.content ?? ""
const preview = content.slice(0, 100).replace(/\n/g, "\\n")
console.error(
pc.dim(`${sessionTag} message.updated (${role}): "${preview}${content.length > 100 ? "..." : ""}"`)
)
const model = msgProps?.info?.modelID
const agent = msgProps?.info?.agent
const details = [role, agent, model].filter(Boolean).join(", ")
console.error(pc.dim(`${sessionTag} message.updated (${details})`))
break
}
@@ -241,6 +248,7 @@ function handleMessagePartUpdated(
const newText = part.text.slice(state.lastPartText.length)
if (newText) {
process.stdout.write(newText)
state.hasReceivedMeaningfulWork = true
}
state.lastPartText = part.text
}
@@ -257,16 +265,7 @@ function handleMessageUpdated(
if (props?.info?.sessionID !== ctx.sessionID) return
if (props?.info?.role !== "assistant") return
const content = props.content
if (!content || content === state.lastOutput) return
if (state.lastPartText.length === 0) {
const newContent = content.slice(state.lastOutput.length)
if (newContent) {
process.stdout.write(newContent)
}
}
state.lastOutput = content
state.hasReceivedMeaningfulWork = true
}
function handleToolExecute(
@@ -296,6 +295,7 @@ function handleToolExecute(
}
}
state.hasReceivedMeaningfulWork = true
process.stdout.write(`\n${pc.cyan(">")} ${pc.bold(toolName)}${inputPreview}\n`)
}

View File

@@ -143,6 +143,14 @@ export async function run(options: RunOptions): Promise<number> {
process.exit(1)
}
// Guard against premature completion: don't check completion until the
// session has produced meaningful work (text output, tool call, or tool result).
// Without this, a session that goes busy->idle before the LLM responds
// would exit immediately because 0 todos + 0 children = "complete".
if (!eventState.hasReceivedMeaningfulWork) {
continue
}
const shouldExit = await checkCompletionConditions(ctx)
if (shouldExit) {
console.log(pc.green("\n\nAll tasks completed."))

View File

@@ -44,8 +44,13 @@ export interface SessionStatusProps {
}
export interface MessageUpdatedProps {
info?: { sessionID?: string; role?: string }
content?: string
info?: {
sessionID?: string
role?: string
modelID?: string
providerID?: string
agent?: string
}
}
export interface MessagePartUpdatedProps {

View File

@@ -2,6 +2,7 @@ import { describe, test, expect, mock, beforeEach } from 'bun:test'
import type { TmuxConfig } from '../../config/schema'
import type { WindowState, PaneAction } from './types'
import type { ActionResult, ExecuteContext } from './action-executor'
import type { TmuxUtilDeps } from './manager'
type ExecuteActionsResult = {
success: boolean
@@ -33,6 +34,11 @@ const mockExecuteAction = mock<(
const mockIsInsideTmux = mock<() => boolean>(() => true)
const mockGetCurrentPaneId = mock<() => string | undefined>(() => '%0')
const mockTmuxDeps: TmuxUtilDeps = {
isInsideTmux: mockIsInsideTmux,
getCurrentPaneId: mockGetCurrentPaneId,
}
mock.module('./pane-state-querier', () => ({
queryWindowState: mockQueryWindowState,
paneExists: mockPaneExists,
@@ -51,15 +57,19 @@ mock.module('./action-executor', () => ({
executeAction: mockExecuteAction,
}))
mock.module('../../shared/tmux', () => ({
isInsideTmux: mockIsInsideTmux,
getCurrentPaneId: mockGetCurrentPaneId,
POLL_INTERVAL_BACKGROUND_MS: 2000,
SESSION_TIMEOUT_MS: 600000,
SESSION_MISSING_GRACE_MS: 6000,
SESSION_READY_POLL_INTERVAL_MS: 100,
SESSION_READY_TIMEOUT_MS: 500,
}))
mock.module('../../shared/tmux', () => {
const { isInsideTmux, getCurrentPaneId } = require('../../shared/tmux/tmux-utils')
const { POLL_INTERVAL_BACKGROUND_MS, SESSION_TIMEOUT_MS, SESSION_MISSING_GRACE_MS } = require('../../shared/tmux/constants')
return {
isInsideTmux,
getCurrentPaneId,
POLL_INTERVAL_BACKGROUND_MS,
SESSION_TIMEOUT_MS,
SESSION_MISSING_GRACE_MS,
SESSION_READY_POLL_INTERVAL_MS: 100,
SESSION_READY_TIMEOUT_MS: 500,
}
})
const trackedSessions = new Set<string>()
@@ -148,7 +158,7 @@ describe('TmuxSessionManager', () => {
}
//#when
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
//#then
expect(manager).toBeDefined()
@@ -168,7 +178,7 @@ describe('TmuxSessionManager', () => {
}
//#when
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
//#then
expect(manager).toBeDefined()
@@ -188,7 +198,7 @@ describe('TmuxSessionManager', () => {
}
//#when
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
//#then
expect(manager).toBeDefined()
@@ -210,7 +220,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = createSessionCreatedEvent(
'ses_child',
'ses_parent',
@@ -271,7 +281,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
//#when - first agent
await manager.onSessionCreated(
@@ -305,7 +315,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = createSessionCreatedEvent('ses_root', undefined, 'Root Session')
//#when
@@ -327,7 +337,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = createSessionCreatedEvent(
'ses_child',
'ses_parent',
@@ -353,7 +363,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
const event = {
type: 'session.deleted',
properties: {
@@ -398,7 +408,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 120,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
//#when
await manager.onSessionCreated(
@@ -450,7 +460,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
createSessionCreatedEvent(
@@ -487,7 +497,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
//#when
await manager.onSessionDeleted({ sessionID: 'ses_unknown' })
@@ -521,7 +531,7 @@ describe('TmuxSessionManager', () => {
main_pane_min_width: 80,
agent_pane_min_width: 40,
}
const manager = new TmuxSessionManager(ctx, config)
const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps)
await manager.onSessionCreated(
createSessionCreatedEvent('ses_1', 'ses_parent', 'Task 1')

View File

@@ -2,8 +2,8 @@ import type { PluginInput } from "@opencode-ai/plugin"
import type { TmuxConfig } from "../../config/schema"
import type { TrackedSession, CapacityConfig } from "./types"
import {
isInsideTmux,
getCurrentPaneId,
isInsideTmux as defaultIsInsideTmux,
getCurrentPaneId as defaultGetCurrentPaneId,
POLL_INTERVAL_BACKGROUND_MS,
SESSION_MISSING_GRACE_MS,
SESSION_READY_POLL_INTERVAL_MS,
@@ -21,6 +21,16 @@ interface SessionCreatedEvent {
properties?: { info?: { id?: string; parentID?: string; title?: string } }
}
export interface TmuxUtilDeps {
isInsideTmux: () => boolean
getCurrentPaneId: () => string | undefined
}
const defaultTmuxDeps: TmuxUtilDeps = {
isInsideTmux: defaultIsInsideTmux,
getCurrentPaneId: defaultGetCurrentPaneId,
}
const SESSION_TIMEOUT_MS = 10 * 60 * 1000
/**
@@ -43,13 +53,15 @@ export class TmuxSessionManager {
private sessions = new Map<string, TrackedSession>()
private pendingSessions = new Set<string>()
private pollInterval?: ReturnType<typeof setInterval>
private deps: TmuxUtilDeps
constructor(ctx: PluginInput, tmuxConfig: TmuxConfig) {
constructor(ctx: PluginInput, tmuxConfig: TmuxConfig, deps: TmuxUtilDeps = defaultTmuxDeps) {
this.client = ctx.client
this.tmuxConfig = tmuxConfig
this.deps = deps
const defaultPort = process.env.OPENCODE_PORT ?? "4096"
this.serverUrl = ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`
this.sourcePaneId = getCurrentPaneId()
this.sourcePaneId = deps.getCurrentPaneId()
log("[tmux-session-manager] initialized", {
configEnabled: this.tmuxConfig.enabled,
@@ -60,7 +72,7 @@ export class TmuxSessionManager {
}
private isEnabled(): boolean {
return this.tmuxConfig.enabled && isInsideTmux()
return this.tmuxConfig.enabled && this.deps.isInsideTmux()
}
private getCapacityConfig(): CapacityConfig {
@@ -113,7 +125,7 @@ export class TmuxSessionManager {
log("[tmux-session-manager] onSessionCreated called", {
enabled,
tmuxConfigEnabled: this.tmuxConfig.enabled,
isInsideTmux: isInsideTmux(),
isInsideTmux: this.deps.isInsideTmux(),
eventType: event.type,
infoId: event.properties?.info?.id,
infoParentID: event.properties?.info?.parentID,

View File

@@ -1474,6 +1474,73 @@ describe("sisyphus-task", () => {
}, { timeout: 20000 })
})
describe("category model resolution fallback", () => {
test("category uses resolved.model when connectedProvidersCache is null and availableModels is empty", async () => {
// #given - connectedProvidersCache returns null (simulates missing cache file)
// This is a regression test for PR #1227 which removed resolved.model from userModel chain
cacheSpy.mockReturnValue(null)
const { createDelegateTask } = require("./tools")
let launchInput: any
const mockManager = {
launch: async (input: any) => {
launchInput = input
return {
id: "task-fallback",
sessionID: "ses_fallback_test",
description: "Fallback test task",
agent: "sisyphus-junior",
status: "running",
}
},
}
const mockClient = {
app: { agents: async () => ({ data: [] }) },
config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) },
model: { list: async () => [] },
session: {
create: async () => ({ data: { id: "test-session" } }),
prompt: async () => ({ data: {} }),
messages: async () => ({ data: [] }),
},
}
// NO userCategories override, NO sisyphusJuniorModel
const tool = createDelegateTask({
manager: mockManager,
client: mockClient,
// userCategories: undefined - use DEFAULT_CATEGORIES only
// sisyphusJuniorModel: undefined
})
const toolContext = {
sessionID: "parent-session",
messageID: "parent-message",
agent: "sisyphus",
abort: new AbortController().signal,
}
// #when - using "quick" category which should use "anthropic/claude-haiku-4-5"
await tool.execute(
{
description: "Test category fallback",
prompt: "Do something quick",
category: "quick",
run_in_background: true,
load_skills: [],
},
toolContext
)
// #then - model should be anthropic/claude-haiku-4-5 from DEFAULT_CATEGORIES
// NOT anthropic/claude-sonnet-4-5 (system default)
expect(launchInput.model.providerID).toBe("anthropic")
expect(launchInput.model.modelID).toBe("claude-haiku-4-5")
})
})
describe("browserProvider propagation", () => {
test("should resolve agent-browser skill when browserProvider is passed", async () => {
// #given - delegate_task configured with browserProvider: "agent-browser"

View File

@@ -541,7 +541,7 @@ To continue this session: session_id="${args.session_id}"`
}
} else {
const resolution = resolveModelWithFallback({
userModel: userCategories?.[args.category]?.model ?? sisyphusJuniorModel,
userModel: userCategories?.[args.category]?.model ?? resolved.model ?? sisyphusJuniorModel,
fallbackChain: requirement.fallbackChain,
availableModels,
systemDefaultModel,