2026-04-10 15:53:35 +09:00
declare const require : NodeJS . Require
2026-02-08 13:52:55 +09:00
const { describe , test , expect , beforeEach , afterEach , spyOn , mock } = require ( "bun:test" )
2026-02-08 16:48:52 +09:00
import { DEFAULT_CATEGORIES , CATEGORY_PROMPT_APPENDS , CATEGORY_DESCRIPTIONS , isPlanAgent , PLAN_AGENT_NAMES , isPlanFamily , PLAN_FAMILY_NAMES } from "./constants"
2026-04-10 15:53:35 +09:00
import { getAgentDisplayName , getAgentListDisplayName } from "../../shared/agent-display-names"
2026-01-09 02:24:43 +09:00
import type { CategoryConfig } from "../../config/schema"
2026-02-14 14:07:57 +09:00
import type { DelegateTaskArgs } from "./types"
2026-01-23 02:42:51 +09:00
import { __resetModelCache } from "../../shared/model-availability"
2026-01-25 15:02:41 +09:00
import { clearSkillCache } from "../../features/opencode-skill-loader/skill-content"
2026-01-28 14:17:56 +09:00
import { __setTimingConfig , __resetTimingConfig } from "./timing"
2026-01-28 14:27:34 +09:00
import * as connectedProvidersCache from "../../shared/connected-providers-cache"
2026-02-14 14:07:57 +09:00
import * as executor from "./executor"
2026-01-09 02:24:43 +09:00
2026-04-10 15:53:35 +09:00
const runtimeRequire = require as NodeJS . Require & { cache? : Record < string , unknown > }
function clearRequireCache ( modulePath : string ) : void {
const resolvedPath = runtimeRequire . resolve ( modulePath )
if ( runtimeRequire . cache ? . [ resolvedPath ] ) {
delete runtimeRequire . cache [ resolvedPath ]
}
}
function resolveCategoryConfig ( . . . args : Parameters < typeof import ( "./tools" ) .resolveCategoryConfig > ) : ReturnType < typeof import ( "./tools" ) .resolveCategoryConfig > {
clearRequireCache ( "./tools" )
return require ( "./tools" ) . resolveCategoryConfig ( . . . args )
}
2026-02-18 15:51:24 +09:00
const SYSTEM_DEFAULT_MODEL = "anthropic/claude-sonnet-4-6"
2026-01-09 02:24:43 +09:00
2026-02-08 18:41:39 +09:00
const TEST_CONNECTED_PROVIDERS = [ "anthropic" , "google" , "openai" ]
const TEST_AVAILABLE_MODELS = new Set ( [
2026-04-17 14:51:52 +09:00
"anthropic/claude-opus-4-7" ,
2026-02-18 15:51:24 +09:00
"anthropic/claude-sonnet-4-6" ,
2026-02-08 18:41:39 +09:00
"anthropic/claude-haiku-4-5" ,
2026-02-26 21:01:05 +09:00
"google/gemini-3.1-pro" ,
2026-02-08 18:41:39 +09:00
"google/gemini-3-flash" ,
2026-04-25 00:41:16 +09:00
"openai/gpt-5.4-mini" ,
2026-04-24 16:17:44 +09:00
"openai/gpt-5.5" ,
2026-02-08 18:41:39 +09:00
"openai/gpt-5.3-codex" ,
] )
2026-02-14 14:07:57 +09:00
type DelegateTaskArgsWithSerializedSkills = Omit < DelegateTaskArgs , "load_skills" > & {
load_skills : string
}
2026-02-08 18:41:39 +09:00
function createTestAvailableModels ( ) : Set < string > {
return new Set ( TEST_AVAILABLE_MODELS )
}
2026-01-09 02:24:43 +09:00
describe ( "sisyphus-task" , ( ) = > {
2026-01-28 14:27:34 +09:00
let cacheSpy : ReturnType < typeof spyOn >
2026-02-01 19:26:57 +09:00
let providerModelsSpy : ReturnType < typeof spyOn >
2026-01-28 14:27:34 +09:00
2026-01-23 02:42:51 +09:00
beforeEach ( ( ) = > {
2026-02-08 18:50:16 +09:00
mock . restore ( )
2026-04-10 15:53:35 +09:00
clearRequireCache ( "./tools" )
2026-01-23 02:42:51 +09:00
__resetModelCache ( )
2026-01-25 15:02:41 +09:00
clearSkillCache ( )
2026-01-28 14:17:56 +09:00
__setTimingConfig ( {
POLL_INTERVAL_MS : 10 ,
MIN_STABILITY_TIME_MS : 50 ,
STABILITY_POLLS_REQUIRED : 1 ,
WAIT_FOR_SESSION_INTERVAL_MS : 10 ,
WAIT_FOR_SESSION_TIMEOUT_MS : 1000 ,
2026-02-15 19:48:59 +09:00
MAX_POLL_TIME_MS : 2000 ,
2026-01-28 14:17:56 +09:00
SESSION_CONTINUATION_STABILITY_MS : 50 ,
} )
2026-01-28 14:27:34 +09:00
cacheSpy = spyOn ( connectedProvidersCache , "readConnectedProvidersCache" ) . mockReturnValue ( [ "anthropic" , "google" , "openai" ] )
2026-02-01 19:26:57 +09:00
providerModelsSpy = spyOn ( connectedProvidersCache , "readProviderModelsCache" ) . mockReturnValue ( {
models : {
2026-04-17 14:51:52 +09:00
anthropic : [ "claude-opus-4-7" , "claude-sonnet-4-6" , "claude-haiku-4-5" ] ,
2026-02-26 21:01:05 +09:00
google : [ "gemini-3.1-pro" , "gemini-3-flash" ] ,
2026-04-25 00:41:16 +09:00
openai : [ "gpt-5.5" , "gpt-5.4-mini" , "gpt-5.3-codex" ] ,
2026-02-01 19:26:57 +09:00
} ,
connected : [ "anthropic" , "google" , "openai" ] ,
updatedAt : "2026-01-01T00:00:00.000Z" ,
} )
2026-01-28 14:17:56 +09:00
} )
afterEach ( ( ) = > {
__resetTimingConfig ( )
2026-01-28 14:27:34 +09:00
cacheSpy ? . mockRestore ( )
2026-02-01 19:26:57 +09:00
providerModelsSpy ? . mockRestore ( )
2026-01-23 02:42:51 +09:00
} )
2026-01-09 02:24:43 +09:00
describe ( "DEFAULT_CATEGORIES" , ( ) = > {
2026-02-15 13:59:00 +09:00
test ( "visual-engineering category has model and variant config" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const category = DEFAULT_CATEGORIES [ "visual-engineering" ]
2026-02-01 16:47:50 +09:00
// when / #then
2026-01-09 02:24:43 +09:00
expect ( category ) . toBeDefined ( )
2026-02-26 21:01:05 +09:00
expect ( category . model ) . toBe ( "google/gemini-3.1-pro" )
2026-02-15 13:59:00 +09:00
expect ( category . variant ) . toBe ( "high" )
2026-01-09 02:24:43 +09:00
} )
2026-01-20 16:22:53 +09:00
test ( "ultrabrain category has model and variant config" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const category = DEFAULT_CATEGORIES [ "ultrabrain" ]
2026-02-01 16:47:50 +09:00
// when / #then
2026-01-09 02:24:43 +09:00
expect ( category ) . toBeDefined ( )
2026-04-25 00:41:16 +09:00
expect ( category . model ) . toBe ( "openai/gpt-5.5" )
2026-01-20 16:22:53 +09:00
expect ( category . variant ) . toBe ( "xhigh" )
2026-01-09 02:24:43 +09:00
} )
2026-01-30 14:26:18 +09:00
test ( "deep category has model and variant config" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-30 14:26:18 +09:00
const category = DEFAULT_CATEGORIES [ "deep" ]
2026-02-01 16:47:50 +09:00
// when / #then
2026-01-30 14:26:18 +09:00
expect ( category ) . toBeDefined ( )
2026-04-24 16:17:44 +09:00
expect ( category . model ) . toBe ( "openai/gpt-5.5" )
2026-01-30 14:26:18 +09:00
expect ( category . variant ) . toBe ( "medium" )
} )
2026-03-07 16:35:39 +09:00
2026-04-17 14:51:52 +09:00
test ( "unspecified-high category uses claude-opus-4-7 max as primary" , ( ) = > {
2026-03-07 16:35:39 +09:00
// given
const category = DEFAULT_CATEGORIES [ "unspecified-high" ]
// when / #then
expect ( category ) . toBeDefined ( )
2026-04-17 14:51:52 +09:00
expect ( category . model ) . toBe ( "anthropic/claude-opus-4-7" )
2026-03-12 01:00:41 +09:00
expect ( category . variant ) . toBe ( "max" )
2026-03-07 16:35:39 +09:00
} )
2026-01-09 02:24:43 +09:00
} )
describe ( "CATEGORY_PROMPT_APPENDS" , ( ) = > {
test ( "visual-engineering category has design-focused prompt" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const promptAppend = CATEGORY_PROMPT_APPENDS [ "visual-engineering" ]
2026-02-01 16:47:50 +09:00
// when / #then
2026-01-09 02:24:43 +09:00
expect ( promptAppend ) . toContain ( "VISUAL/UI" )
expect ( promptAppend ) . toContain ( "Design-first" )
} )
2026-01-30 14:05:47 +09:00
test ( "ultrabrain category has deep logical reasoning prompt" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const promptAppend = CATEGORY_PROMPT_APPENDS [ "ultrabrain" ]
2026-02-01 16:47:50 +09:00
// when / #then
2026-01-30 14:05:47 +09:00
expect ( promptAppend ) . toContain ( "DEEP LOGICAL REASONING" )
2026-01-09 02:24:43 +09:00
expect ( promptAppend ) . toContain ( "Strategic advisor" )
} )
2026-01-30 14:26:18 +09:00
test ( "deep category has goal-oriented autonomous prompt" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-30 14:26:18 +09:00
const promptAppend = CATEGORY_PROMPT_APPENDS [ "deep" ]
2026-02-01 16:47:50 +09:00
// when / #then
2026-01-30 14:26:18 +09:00
expect ( promptAppend ) . toContain ( "GOAL-ORIENTED" )
expect ( promptAppend ) . toContain ( "autonomous" )
} )
2026-01-09 02:24:43 +09:00
} )
describe ( "CATEGORY_DESCRIPTIONS" , ( ) = > {
test ( "has description for all default categories" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const defaultCategoryNames = Object . keys ( DEFAULT_CATEGORIES )
2026-02-01 16:47:50 +09:00
// when / #then
2026-01-09 02:24:43 +09:00
for ( const name of defaultCategoryNames ) {
expect ( CATEGORY_DESCRIPTIONS [ name ] ) . toBeDefined ( )
expect ( CATEGORY_DESCRIPTIONS [ name ] . length ) . toBeGreaterThan ( 0 )
}
} )
2026-01-20 16:22:53 +09:00
test ( "unspecified-high category exists and has description" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given / #when
2026-01-20 16:22:53 +09:00
const description = CATEGORY_DESCRIPTIONS [ "unspecified-high" ]
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( description ) . toBeDefined ( )
2026-01-20 16:22:53 +09:00
expect ( description ) . toContain ( "high effort" )
2026-01-09 02:24:43 +09:00
} )
} )
2026-01-26 17:00:06 +09:00
describe ( "isPlanAgent" , ( ) = > {
test ( "returns true for 'plan'" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "plan" )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( true )
} )
2026-02-08 13:42:15 +09:00
test ( "returns false for 'prometheus' (decoupled from plan)" , ( ) = > {
//#given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "prometheus" )
2026-02-08 13:42:15 +09:00
//#then - prometheus is NOT a plan agent
expect ( result ) . toBe ( false )
2026-01-26 17:00:06 +09:00
} )
2026-04-11 13:40:02 +09:00
test ( "returns false for 'planner' (no longer matches via substring)" , ( ) = > {
2026-02-08 16:48:52 +09:00
//#given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "planner" )
2026-04-08 13:33:18 +09:00
//#then - "planner" is NOT an exact match for "plan" (T37 exact match fix)
expect ( result ) . toBe ( false )
2026-01-26 17:00:06 +09:00
} )
test ( "returns true for case-insensitive match 'PLAN'" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "PLAN" )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( true )
} )
2026-02-08 13:42:15 +09:00
test ( "returns false for case-insensitive match 'Prometheus' (decoupled from plan)" , ( ) = > {
//#given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "Prometheus" )
2026-02-08 13:42:15 +09:00
//#then - Prometheus is NOT a plan agent
expect ( result ) . toBe ( false )
2026-01-26 17:00:06 +09:00
} )
test ( "returns false for 'oracle'" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "oracle" )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( false )
} )
test ( "returns false for 'explore'" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "explore" )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( false )
} )
test ( "returns false for undefined" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( undefined )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( false )
} )
test ( "returns false for empty string" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given / #when
2026-01-26 17:00:06 +09:00
const result = isPlanAgent ( "" )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( false )
} )
2026-02-08 16:48:52 +09:00
test ( "PLAN_AGENT_NAMES contains only plan" , ( ) = > {
2026-02-08 13:42:15 +09:00
//#given / #when / #then
2026-02-08 16:48:52 +09:00
expect ( PLAN_AGENT_NAMES ) . toEqual ( [ "plan" ] )
} )
2026-04-11 13:40:02 +09:00
test ( "returns false for non-plan agent display names (regression: isPlanAgent display-name false-positive)" , ( ) = > {
//#given / #when / #then
expect ( isPlanAgent ( getAgentDisplayName ( "metis" ) ) ) . toBe ( false )
expect ( isPlanAgent ( getAgentDisplayName ( "momus" ) ) ) . toBe ( false )
expect ( isPlanAgent ( getAgentDisplayName ( "atlas" ) ) ) . toBe ( false )
} )
2026-02-08 16:48:52 +09:00
} )
describe ( "isPlanFamily" , ( ) = > {
test ( "returns true for 'plan'" , ( ) = > {
//#given / #when
const result = isPlanFamily ( "plan" )
//#then
expect ( result ) . toBe ( true )
} )
test ( "returns true for 'prometheus'" , ( ) = > {
//#given / #when
const result = isPlanFamily ( "prometheus" )
//#then
expect ( result ) . toBe ( true )
} )
2026-04-10 15:53:35 +09:00
test ( "returns true for prometheus display name" , ( ) = > {
//#given / #when
const result = isPlanFamily ( getAgentDisplayName ( "prometheus" ) )
//#then
expect ( result ) . toBe ( true )
} )
test ( "returns true for prometheus list display name with zwsp prefix" , ( ) = > {
//#given / #when
const result = isPlanFamily ( getAgentListDisplayName ( "prometheus" ) )
//#then
expect ( result ) . toBe ( true )
} )
2026-02-08 16:48:52 +09:00
test ( "returns false for 'oracle'" , ( ) = > {
//#given / #when
const result = isPlanFamily ( "oracle" )
//#then
expect ( result ) . toBe ( false )
} )
2026-04-15 11:27:07 +09:00
test ( "returns false for 'Momus (Plan Critic)' - no substring false-positive" , ( ) = > {
//#given / #when
const result = isPlanFamily ( "Momus (Plan Critic)" )
//#then
expect ( result ) . toBe ( false )
} )
test ( "returns false for 'Momus - Plan Critic' - no substring false-positive" , ( ) = > {
//#given / #when
const result = isPlanFamily ( "Momus - Plan Critic" )
//#then
expect ( result ) . toBe ( false )
} )
test ( "returns false for 'Metis - Plan Consultant' - no substring false-positive" , ( ) = > {
//#given / #when
const result = isPlanFamily ( "Metis - Plan Consultant" )
//#then
expect ( result ) . toBe ( false )
} )
2026-02-08 16:48:52 +09:00
test ( "returns false for undefined" , ( ) = > {
//#given / #when
const result = isPlanFamily ( undefined )
//#then
expect ( result ) . toBe ( false )
} )
2026-04-11 13:40:02 +09:00
test ( "returns false for non-plan-family agent display names (regression: isPlanFamily includes() false-positive)" , ( ) = > {
//#given / #when / #then
expect ( isPlanFamily ( getAgentDisplayName ( "metis" ) ) ) . toBe ( false )
expect ( isPlanFamily ( getAgentDisplayName ( "momus" ) ) ) . toBe ( false )
expect ( isPlanFamily ( getAgentDisplayName ( "atlas" ) ) ) . toBe ( false )
} )
2026-02-08 16:48:52 +09:00
test ( "PLAN_FAMILY_NAMES contains plan and prometheus" , ( ) = > {
//#given / #when / #then
expect ( PLAN_FAMILY_NAMES ) . toEqual ( [ "plan" , "prometheus" ] )
2026-01-26 17:00:06 +09:00
} )
} )
2026-02-14 14:07:57 +09:00
describe ( "load_skills parsing" , ( ) = > {
test ( "parses valid JSON string into array before validation" , async ( ) = > {
//#given
const { createDelegateTask } = require ( "./tools" )
const mockManager = {
launch : async ( ) = > ( {
id : "task-123" ,
status : "pending" ,
description : "Parse test" ,
agent : "sisyphus-junior" ,
sessionID : "test-session" ,
} ) ,
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { } ) } ,
provider : { list : async ( ) = > ( { data : { connected : [ "openai" ] } } ) } ,
model : { list : async ( ) = > ( { data : [ { provider : "openai" , id : "gpt-5.3-codex" } ] } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
const resolveSkillContentSpy = spyOn ( executor , "resolveSkillContent" ) . mockResolvedValue ( {
content : "resolved skill content" ,
error : null ,
} )
const args : DelegateTaskArgsWithSerializedSkills = {
description : "Parse valid string" ,
prompt : "Load skill parsing test" ,
category : "quick" ,
run_in_background : true ,
load_skills : '["playwright", "git-master"]' ,
}
//#when
2026-05-01 17:57:49 +09:00
await tool . execute ( args , toolContext )
2026-02-14 14:07:57 +09:00
//#then
expect ( args . load_skills ) . toEqual ( [ "playwright" , "git-master" ] )
expect ( resolveSkillContentSpy ) . toHaveBeenCalledWith ( [ "playwright" , "git-master" ] , expect . any ( Object ) )
} , { timeout : 10000 } )
test ( "defaults to [] when load_skills is malformed JSON" , async ( ) = > {
//#given
const { createDelegateTask } = require ( "./tools" )
const mockManager = {
launch : async ( ) = > ( {
id : "task-456" ,
status : "pending" ,
description : "Parse test" ,
agent : "sisyphus-junior" ,
sessionID : "test-session" ,
} ) ,
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { } ) } ,
provider : { list : async ( ) = > ( { data : { connected : [ "openai" ] } } ) } ,
model : { list : async ( ) = > ( { data : [ { provider : "openai" , id : "gpt-5.3-codex" } ] } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
const resolveSkillContentSpy = spyOn ( executor , "resolveSkillContent" ) . mockResolvedValue ( {
content : "resolved skill content" ,
error : null ,
} )
const args : DelegateTaskArgsWithSerializedSkills = {
description : "Parse malformed string" ,
prompt : "Load skill parsing test" ,
category : "quick" ,
run_in_background : true ,
load_skills : '["playwright", "git-master"' ,
}
//#when
2026-05-01 17:57:49 +09:00
await tool . execute ( args , toolContext )
2026-02-14 14:07:57 +09:00
//#then
expect ( args . load_skills ) . toEqual ( [ ] )
expect ( resolveSkillContentSpy ) . toHaveBeenCalledWith ( [ ] , expect . any ( Object ) )
} , { timeout : 10000 } )
} )
2026-01-17 12:52:07 -05:00
describe ( "category delegation config validation" , ( ) = > {
2026-02-06 16:01:54 +09:00
test ( "fills subagent_type as sisyphus-junior when category is provided without subagent_type" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
const mockManager = {
launch : async ( ) = > ( {
id : "task-123" ,
status : "pending" ,
description : "Test task" ,
agent : "sisyphus-junior" ,
sessionID : "test-session" ,
} ) ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { } ) } ,
provider : { list : async ( ) = > ( { data : { connected : [ "openai" ] } } ) } ,
model : { list : async ( ) = > ( { data : [ { provider : "openai" , id : "gpt-5.3-codex" } ] } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-07 14:32:47 +01:00
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
const args : {
description : string
prompt : string
category : string
run_in_background : boolean
load_skills : string [ ]
subagent_type? : string
} = {
description : "Quick category test" ,
prompt : "Do something" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
}
// when
await tool . execute ( args , toolContext )
2026-02-06 16:01:54 +09:00
2026-02-09 16:14:30 +09:00
// then
2026-02-16 21:32:33 +09:00
expect ( args . subagent_type ) . toBe ( "Sisyphus-Junior" )
2026-02-09 16:14:30 +09:00
} , { timeout : 10000 } )
2026-03-29 14:58:56 +00:00
test ( "prefers category over subagent_type when both are provided" , async ( ) = > {
2026-02-09 16:14:30 +09:00
//#given
const { createDelegateTask } = require ( "./tools" )
const mockManager = {
launch : async ( ) = > ( {
id : "task-override" ,
status : "pending" ,
description : "Override test" ,
agent : "sisyphus-junior" ,
sessionID : "test-session" ,
} ) ,
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { } ) } ,
provider : { list : async ( ) = > ( { data : { connected : [ "openai" ] } } ) } ,
model : { list : async ( ) = > ( { data : [ { provider : "openai" , id : "gpt-5.3-codex" } ] } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-03-27 11:53:54 +09:00
const args = {
2026-02-09 16:14:30 +09:00
description : "Override test" ,
prompt : "Do something" ,
category : "quick" ,
subagent_type : "oracle" ,
run_in_background : true ,
load_skills : [ ] ,
}
2026-03-29 14:58:56 +00:00
//#when
await tool . execute ( args , toolContext )
//#then - category takes precedence, subagent_type is overridden to sisyphus-junior
expect ( args . subagent_type ) . toBe ( "Sisyphus-Junior" )
2026-02-06 16:01:54 +09:00
} , { timeout : 10000 } )
2026-01-26 17:01:08 +09:00
test ( "proceeds without error when systemDefaultModel is undefined" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given a mock client with no model in config
2026-01-17 12:52:07 -05:00
const { createDelegateTask } = require ( "./tools" )
2026-05-02 03:01:03 +09:00
const mockManager = { launch : async ( ) = > ( { id : "task-123" , status : "pending" , description : "Test task" , agent : "sisyphus-junior" , sessionId : "test-session" } ) }
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { } ) } , // No model configured
provider : { list : async ( ) = > ( { data : { connected : [ "openai" ] } } ) } ,
model : { list : async ( ) = > ( { data : [ { provider : "openai" , id : "gpt-5.3-codex" } ] } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-07 14:32:47 +01:00
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when delegating with a category
const result = await tool . execute (
{
description : "Test task" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
// then proceeds without error - uses fallback chain
expect ( result ) . not . toContain ( "oh-my-opencode requires a default model" )
2026-02-01 19:01:40 +09:00
} , { timeout : 10000 } )
2026-01-27 09:28:40 +09:00
test ( "returns clear error when no model can be resolved" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - custom category with no model, no systemDefaultModel, no available models
2026-01-27 09:28:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-02-07 14:32:47 +01:00
const mockManager = { launch : async ( ) = > ( { id : "task-123" } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { } ) } , // No model configured
model : { list : async ( ) = > [ ] } , // No available models
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
// Custom category with no model defined
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
userCategories : {
"custom-no-model" : { temperature : 0.5 } , // No model field
} ,
} )
2026-01-27 09:28:40 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when delegating with a custom category that has no model
2026-01-27 09:28:40 +09:00
const result = await tool . execute (
{
description : "Test task" ,
prompt : "Do something" ,
category : "custom-no-model" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then returns clear error message with configuration guidance
2026-01-27 09:28:40 +09:00
expect ( result ) . toContain ( "Model not configured" )
expect ( result ) . toContain ( "custom-no-model" )
expect ( result ) . toContain ( "Configure in one of" )
} )
2026-01-17 12:52:07 -05:00
} )
2026-02-06 16:01:54 +09:00
describe ( "background metadata sessionId" , ( ) = > {
test ( "should wait for background sessionId and set metadata for TUI toolcall counting" , async ( ) = > {
//#given - manager.launch returns before sessionID is available
const { createDelegateTask } = require ( "./tools" )
const tasks = new Map < string , { id : string ; sessionID ? : string ; status : string ; description : string ; agent : string } > ( )
const mockManager = {
getTask : ( id : string ) = > tasks . get ( id ) ,
launch : async ( ) = > {
const task = { id : "bg_1" , status : "pending" , description : "Test task" , agent : "explore" }
tasks . set ( task . id , task )
setTimeout ( ( ) = > {
2026-05-02 03:01:03 +09:00
tasks . set ( task . id , { . . . task , status : "running" , sessionId : "ses_child" } )
2026-02-06 16:01:54 +09:00
} , 20 )
return task
} ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ { name : "explore" , mode : "subagent" } ] } ) } ,
config : { get : async ( ) = > ( { } ) } ,
provider : { list : async ( ) = > ( { data : { connected : [ "openai" ] } } ) } ,
model : { list : async ( ) = > ( { data : [ { provider : "openai" , id : "gpt-5.3-codex" } ] } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-07 14:32:47 +01:00
} )
const metadataCalls : Array < { title? : string ; metadata? : Record < string , unknown > } > = [ ]
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-02-06 16:01:54 +09:00
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
metadata : ( input : { title? : string ; metadata? : Record < string , unknown > } ) = > {
metadataCalls . push ( input )
} ,
}
const args = {
description : "Explore task" ,
prompt : "Explore features directory deeply" ,
subagent_type : "explore" ,
run_in_background : true ,
load_skills : [ ] ,
}
//#when
const result = await tool . execute ( args , toolContext )
//#then - metadata should include sessionId (camelCase) once it's available
expect ( String ( result ) ) . toContain ( "Background task launched" )
const sessionIdCall = metadataCalls . find ( ( c ) = > c . metadata ? . sessionId === "ses_child" )
expect ( sessionIdCall ) . toBeDefined ( )
} )
} )
2026-01-09 02:24:43 +09:00
describe ( "resolveCategoryConfig" , ( ) = > {
test ( "returns null for unknown category without user config" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const categoryName = "unknown-category"
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . toBeNull ( )
} )
2026-05-01 17:57:49 +09:00
test ( "allows artistry to use its fallback chain when gemini is missing" , ( ) = > {
// given - artistry can fall back from gemini to another capable model
2026-03-31 17:26:30 -07:00
const categoryName = "artistry"
2026-04-17 14:51:52 +09:00
const availableModels = new Set < string > ( [ "anthropic/claude-opus-4-7" ] )
2026-01-30 16:57:13 +09:00
2026-02-01 16:47:50 +09:00
// when
2026-01-30 16:57:13 +09:00
const result = resolveCategoryConfig ( categoryName , {
systemDefaultModel : SYSTEM_DEFAULT_MODEL ,
availableModels ,
} )
2026-02-01 16:47:50 +09:00
// then
2026-05-01 17:57:49 +09:00
expect ( result ) . not . toBeNull ( )
expect ( result ? . model ) . toBe ( "google/gemini-3.1-pro" )
2026-01-30 16:57:13 +09:00
} )
2026-05-01 17:57:49 +09:00
test ( "allows artistry when availability is empty" , ( ) = > {
// given - empty availability should not disable fallback-capable categories
2026-03-31 17:26:30 -07:00
const categoryName = "artistry"
2026-01-30 16:57:13 +09:00
const availableModels = new Set < string > ( )
2026-02-01 16:47:50 +09:00
// when
2026-01-30 16:57:13 +09:00
const result = resolveCategoryConfig ( categoryName , {
systemDefaultModel : SYSTEM_DEFAULT_MODEL ,
availableModels ,
} )
2026-02-01 16:47:50 +09:00
// then
2026-05-01 17:57:49 +09:00
expect ( result ) . not . toBeNull ( )
expect ( result ? . model ) . toBe ( "google/gemini-3.1-pro" )
2026-01-30 16:57:13 +09:00
} )
2026-02-01 19:26:57 +09:00
test ( "bypasses requiresModel when explicit user config provided" , ( ) = > {
// #given
const categoryName = "deep"
2026-04-17 14:51:52 +09:00
const availableModels = new Set < string > ( [ "anthropic/claude-opus-4-7" ] )
2026-02-01 19:26:57 +09:00
const userCategories = {
2026-04-17 14:51:52 +09:00
deep : { model : "anthropic/claude-opus-4-7" } ,
2026-02-01 19:26:57 +09:00
}
// #when
const result = resolveCategoryConfig ( categoryName , {
systemDefaultModel : SYSTEM_DEFAULT_MODEL ,
availableModels ,
userCategories ,
} )
// #then
expect ( result ) . not . toBeNull ( )
2026-04-17 14:51:52 +09:00
expect ( result ! . config . model ) . toBe ( "anthropic/claude-opus-4-7" )
2026-02-01 19:26:57 +09:00
} )
test ( "bypasses requiresModel when explicit user config provided even with empty availability" , ( ) = > {
// #given
const categoryName = "deep"
const availableModels = new Set < string > ( )
const userCategories = {
2026-04-17 14:51:52 +09:00
deep : { model : "anthropic/claude-opus-4-7" } ,
2026-02-01 19:26:57 +09:00
}
// #when
const result = resolveCategoryConfig ( categoryName , {
systemDefaultModel : SYSTEM_DEFAULT_MODEL ,
availableModels ,
userCategories ,
} )
// #then
expect ( result ) . not . toBeNull ( )
2026-04-17 14:51:52 +09:00
expect ( result ! . config . model ) . toBe ( "anthropic/claude-opus-4-7" )
2026-02-01 19:26:57 +09:00
} )
2026-01-20 16:22:53 +09:00
test ( "returns default model from DEFAULT_CATEGORIES for builtin category" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const categoryName = "visual-engineering"
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . not . toBeNull ( )
2026-02-26 21:01:05 +09:00
expect ( result ! . config . model ) . toBe ( "google/gemini-3.1-pro" )
2026-01-09 02:24:43 +09:00
expect ( result ! . promptAppend ) . toContain ( "VISUAL/UI" )
} )
2026-01-17 12:51:03 -05:00
test ( "user config overrides systemDefaultModel" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const categoryName = "visual-engineering"
const userCategories = {
2026-04-17 14:51:52 +09:00
"visual-engineering" : { model : "anthropic/claude-opus-4-7" } ,
2026-01-09 02:24:43 +09:00
}
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { userCategories , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . not . toBeNull ( )
2026-04-17 14:51:52 +09:00
expect ( result ! . config . model ) . toBe ( "anthropic/claude-opus-4-7" )
2026-01-09 02:24:43 +09:00
} )
test ( "user prompt_append is appended to default" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const categoryName = "visual-engineering"
const userCategories = {
"visual-engineering" : {
2026-02-26 21:01:05 +09:00
model : "google/gemini-3.1-pro" ,
2026-01-09 02:24:43 +09:00
prompt_append : "Custom instructions here" ,
} ,
}
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { userCategories , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . not . toBeNull ( )
expect ( result ! . promptAppend ) . toContain ( "VISUAL/UI" )
expect ( result ! . promptAppend ) . toContain ( "Custom instructions here" )
} )
test ( "user can define custom category" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const categoryName = "my-custom"
const userCategories = {
"my-custom" : {
2026-04-25 00:41:16 +09:00
model : "openai/gpt-5.5" ,
2026-01-09 02:24:43 +09:00
temperature : 0.5 ,
prompt_append : "You are a custom agent" ,
} ,
}
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { userCategories , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . not . toBeNull ( )
2026-04-25 00:41:16 +09:00
expect ( result ! . config . model ) . toBe ( "openai/gpt-5.5" )
2026-01-09 02:24:43 +09:00
expect ( result ! . config . temperature ) . toBe ( 0.5 )
expect ( result ! . promptAppend ) . toBe ( "You are a custom agent" )
} )
test ( "user category overrides temperature" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const categoryName = "visual-engineering"
const userCategories = {
"visual-engineering" : {
2026-02-26 21:01:05 +09:00
model : "google/gemini-3.1-pro" ,
2026-01-09 02:24:43 +09:00
temperature : 0.3 ,
} ,
}
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { userCategories , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . not . toBeNull ( )
expect ( result ! . config . temperature ) . toBe ( 0.3 )
} )
2026-01-14 22:15:05 +07:00
2026-01-20 17:05:17 +09:00
test ( "category built-in model takes precedence over inheritedModel" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given - builtin category with its own model, parent model also provided
2026-01-14 22:15:05 +07:00
const categoryName = "visual-engineering"
2026-04-17 14:51:52 +09:00
const inheritedModel = "cliproxy/claude-opus-4-7"
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// then - category's built-in model wins over inheritedModel
2026-01-16 18:45:19 +09:00
expect ( result ) . not . toBeNull ( )
2026-02-26 21:01:05 +09:00
expect ( result ! . config . model ) . toBe ( "google/gemini-3.1-pro" )
2026-01-16 18:45:19 +09:00
} )
2026-01-20 17:05:17 +09:00
test ( "systemDefaultModel is used as fallback when custom category has no model" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given - custom category with no model defined
2026-01-16 18:45:19 +09:00
const categoryName = "my-custom-no-model"
2026-04-25 00:41:16 +09:00
const userCategories : Record < string , CategoryConfig > = { "my-custom-no-model" : { temperature : 0.5 } }
2026-04-17 14:51:52 +09:00
const inheritedModel = "cliproxy/claude-opus-4-7"
2026-01-16 18:45:19 +09:00
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { userCategories , inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-16 18:45:19 +09:00
2026-02-01 16:47:50 +09:00
// then - systemDefaultModel is used since custom category has no built-in model
2026-01-14 22:15:05 +07:00
expect ( result ) . not . toBeNull ( )
2026-01-20 17:05:17 +09:00
expect ( result ! . config . model ) . toBe ( SYSTEM_DEFAULT_MODEL )
2026-01-14 22:15:05 +07:00
} )
2026-01-17 12:51:03 -05:00
test ( "user model takes precedence over inheritedModel" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-14 22:15:05 +07:00
const categoryName = "visual-engineering"
const userCategories = {
"visual-engineering" : { model : "my-provider/my-model" } ,
}
2026-04-17 14:51:52 +09:00
const inheritedModel = "cliproxy/claude-opus-4-7"
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { userCategories , inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// then
2026-01-14 22:15:05 +07:00
expect ( result ) . not . toBeNull ( )
expect ( result ! . config . model ) . toBe ( "my-provider/my-model" )
} )
2026-01-20 16:22:53 +09:00
test ( "default model from category config is used when no user model and no inheritedModel" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-14 22:15:05 +07:00
const categoryName = "visual-engineering"
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const result = resolveCategoryConfig ( categoryName , { systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// then
2026-01-14 22:15:05 +07:00
expect ( result ) . not . toBeNull ( )
2026-02-26 21:01:05 +09:00
expect ( result ! . config . model ) . toBe ( "google/gemini-3.1-pro" )
2026-01-14 22:15:05 +07:00
} )
2026-01-09 02:24:43 +09:00
} )
2026-01-10 21:44:20 +00:00
describe ( "category variant" , ( ) = > {
test ( "passes variant to background model payload" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-16 17:34:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-01-10 21:44:20 +00:00
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-variant" ,
sessionID : "session-variant" ,
description : "Variant task" ,
2026-01-24 03:00:01 +09:00
agent : "sisyphus-junior" ,
2026-01-10 21:44:20 +00:00
status : "running" ,
}
} ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
2026-01-10 21:44:20 +00:00
2026-02-07 14:32:47 +01:00
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
userCategories : {
2026-04-25 00:41:16 +09:00
ultrabrain : { model : "openai/gpt-5.5" , variant : "xhigh" } ,
2026-02-07 14:32:47 +01:00
} ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-07 14:32:47 +01:00
} )
2026-01-10 21:44:20 +00:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-10 21:44:20 +00:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when
2026-01-10 21:44:20 +00:00
await tool . execute (
{
description : "Variant task" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : true ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-10 21:44:20 +00:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then
2026-01-10 21:44:20 +00:00
expect ( launchInput . model ) . toEqual ( {
providerID : "openai" ,
2026-04-25 00:41:16 +09:00
modelID : "gpt-5.5" ,
2026-01-10 21:44:20 +00:00
variant : "xhigh" ,
} )
} )
2026-01-20 18:55:08 +09:00
2026-03-07 16:35:39 +09:00
test ( "DEFAULT_CATEGORIES explicit high model passes to background WITHOUT userCategories" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - NO userCategories, testing DEFAULT_CATEGORIES only
2026-01-20 18:55:08 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-default-variant" ,
sessionID : "session-default-variant" ,
description : "Default variant task" ,
2026-01-24 03:00:01 +09:00
agent : "sisyphus-junior" ,
2026-01-20 18:55:08 +09:00
status : "running" ,
}
} ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
2026-04-17 14:51:52 +09:00
model : { list : async ( ) = > [ { provider : "anthropic" , id : "claude-opus-4-7" } ] } ,
2026-02-07 14:32:47 +01:00
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
2026-01-20 18:55:08 +09:00
2026-02-07 14:32:47 +01:00
// NO userCategories - must use DEFAULT_CATEGORIES
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-07 14:32:47 +01:00
} )
2026-01-20 18:55:08 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-20 18:55:08 +09:00
abort : new AbortController ( ) . signal ,
}
2026-04-17 14:51:52 +09:00
// when - unspecified-high uses claude-opus-4-7 max in DEFAULT_CATEGORIES
2026-01-20 18:55:08 +09:00
await tool . execute (
{
description : "Test unspecified-high default variant" ,
prompt : "Do something" ,
category : "unspecified-high" ,
run_in_background : true ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-20 18:55:08 +09:00
} ,
toolContext
)
2026-04-17 14:51:52 +09:00
// then - claude-opus-4-7 should be passed with max variant
2026-01-20 18:55:08 +09:00
expect ( launchInput . model ) . toEqual ( {
2026-03-12 01:00:41 +09:00
providerID : "anthropic" ,
2026-04-17 14:51:52 +09:00
modelID : "claude-opus-4-7" ,
2026-03-12 01:00:41 +09:00
variant : "max" ,
2026-01-20 18:55:08 +09:00
} )
2026-02-04 15:25:50 +09:00
} , { timeout : 20000 } )
2026-01-20 18:55:08 +09:00
2026-03-07 16:35:39 +09:00
test ( "DEFAULT_CATEGORIES explicit high model passes to sync session.prompt WITHOUT userCategories" , async ( ) = > {
2026-02-07 14:32:47 +01:00
// given - NO userCategories, testing DEFAULT_CATEGORIES for sync mode
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
2026-04-17 14:51:52 +09:00
model : { list : async ( ) = > [ { provider : "anthropic" , id : "claude-opus-4-7" } ] } ,
2026-02-07 14:32:47 +01:00
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_sync_default_variant" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "done" } ] } ]
} ) ,
status : async ( ) = > ( { data : { "ses_sync_default_variant" : { type : "idle" } } } ) ,
} ,
}
2026-01-20 18:55:08 +09:00
// NO userCategories - must use DEFAULT_CATEGORIES
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-20 18:55:08 +09:00
abort : new AbortController ( ) . signal ,
}
2026-04-17 14:51:52 +09:00
// when - unspecified-high uses claude-opus-4-7 max in DEFAULT_CATEGORIES
2026-01-20 18:55:08 +09:00
await tool . execute (
{
description : "Test unspecified-high sync variant" ,
prompt : "Do something" ,
category : "unspecified-high" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-20 18:55:08 +09:00
} ,
toolContext
)
2026-04-17 14:51:52 +09:00
// then - claude-opus-4-7 should be passed with max variant
2026-01-20 18:55:08 +09:00
expect ( promptBody . model ) . toEqual ( {
2026-03-12 01:00:41 +09:00
providerID : "anthropic" ,
2026-04-17 14:51:52 +09:00
modelID : "claude-opus-4-7" ,
2026-01-20 18:55:08 +09:00
} )
2026-03-12 01:00:41 +09:00
expect ( promptBody . variant ) . toBe ( "max" )
2026-01-20 18:55:08 +09:00
} , { timeout : 20000 } )
2026-01-10 21:44:20 +00:00
} )
2026-01-09 02:24:43 +09:00
describe ( "skills parameter" , ( ) = > {
2026-02-08 16:31:02 +09:00
test ( "skills parameter is required - throws error when not provided" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-16 17:34:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-02-08 13:09:58 +09:00
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
2026-02-08 16:31:02 +09:00
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
2026-02-08 13:09:58 +09:00
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-02-08 16:31:02 +09:00
// when - skills not provided (undefined)
// then - should throw error about missing skills
await expect ( tool . execute (
2026-02-08 13:09:58 +09:00
{
description : "Test task" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
} ,
toolContext
2026-02-16 22:59:52 +09:00
) ) . rejects . toThrow ( "Invalid arguments: 'load_skills' parameter is REQUIRED" )
2026-02-08 16:31:02 +09:00
} )
2026-02-07 14:32:47 +01:00
test ( "null skills throws error" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - null passed
// then - should throw error about null
await expect ( tool . execute (
{
description : "Test task" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
load_skills : null ,
} ,
toolContext
2026-02-16 22:59:52 +09:00
) ) . rejects . toThrow ( "Invalid arguments: load_skills=null is not allowed" )
2026-02-07 14:32:47 +01:00
} )
test ( "empty array [] is allowed and proceeds without skill content" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ]
} ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
2026-01-16 13:12:48 +09:00
2026-01-16 17:34:40 +09:00
const tool = createDelegateTask ( {
2026-01-16 13:12:48 +09:00
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-16 13:12:48 +09:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - empty array passed
2026-01-16 13:12:48 +09:00
await tool . execute (
{
description : "Test task" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ ] ,
2026-01-16 13:12:48 +09:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should proceed without system content from skills
2026-01-16 13:12:48 +09:00
expect ( promptBody ) . toBeDefined ( )
} , { timeout : 20000 } )
2026-01-09 02:24:43 +09:00
} )
2026-03-17 15:44:18 +09:00
describe ( "run_in_background parameter" , ( ) = > {
test ( "#given category without run_in_background #when executing #then throws required parameter error" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
// then
await expect ( tool . execute (
{
description : "Category without run flag" ,
prompt : "Do something" ,
category : "quick" ,
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
) ) . rejects . toThrow ( "Invalid arguments: 'run_in_background' parameter is REQUIRED" )
} )
test ( "#given subagent_type without run_in_background #when executing #then throws required parameter error" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ { name : "explore" , mode : "subagent" } ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
// then
await expect ( tool . execute (
{
description : "Subagent without run flag" ,
prompt : "Find patterns" ,
subagent_type : "explore" ,
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
) ) . rejects . toThrow ( "Invalid arguments: 'run_in_background' parameter is REQUIRED" )
} )
2026-04-16 23:18:38 +09:00
test ( "#given task_id without run_in_background #when executing #then throws required parameter error" , async ( ) = > {
2026-03-17 15:44:18 +09:00
// given
const { createDelegateTask } = require ( "./tools" )
2026-05-02 03:01:03 +09:00
const mockManager = { resume : async ( ) = > ( { id : "task-1" , sessionId : "ses_1" , status : "running" } ) }
2026-03-17 15:44:18 +09:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
// then
await expect ( tool . execute (
{
description : "Continue without run flag" ,
prompt : "Continue" ,
2026-04-16 23:18:38 +09:00
task_id : "ses_existing" ,
2026-03-17 15:44:18 +09:00
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
) ) . rejects . toThrow ( "Invalid arguments: 'run_in_background' parameter is REQUIRED" )
} )
2026-04-16 23:18:38 +09:00
test ( "#given no category no subagent_type no task_id and no run_in_background #when executing #then throws required parameter error" , async ( ) = > {
2026-03-17 15:44:18 +09:00
// given
const { createDelegateTask } = require ( "./tools" )
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
// then
await expect ( tool . execute (
{
description : "Missing required args" ,
prompt : "Do something" ,
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
) ) . rejects . toThrow ( "Invalid arguments: 'run_in_background' parameter is REQUIRED" )
} )
2026-04-07 09:30:22 +09:00
test ( "#given category without description #when executing #then auto-generates description from prompt" , async ( ) = > {
2026-04-05 15:46:24 +09:00
// given
const { createDelegateTask } = require ( "./tools" )
2026-04-07 09:30:22 +09:00
let capturedTitle : string | undefined
2026-04-05 15:46:24 +09:00
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
2026-04-07 09:30:22 +09:00
try {
await tool . execute (
{
prompt : "Fix the broken unit tests in parser module" ,
category : "quick" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
{
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
metadata : async ( meta : { title? : string } ) = > { capturedTitle = meta . title } ,
}
)
} catch {
// execution may fail due to incomplete mocks — we only care about the title
}
// then — description auto-generated from first 4 words of prompt
expect ( capturedTitle ) . toBe ( "Fix the broken unit" )
} )
test ( "#given empty description #when executing #then auto-generates description from prompt" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
let capturedTitle : string | undefined
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
try {
await tool . execute (
{
description : " " ,
prompt : "Refactor authentication module completely" ,
category : "quick" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
{
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
metadata : async ( meta : { title? : string } ) = > { capturedTitle = meta . title } ,
}
)
} catch {
// execution may fail due to incomplete mocks
}
2026-04-05 15:46:24 +09:00
// then
2026-04-07 09:30:22 +09:00
expect ( capturedTitle ) . toBe ( "Refactor authentication module completely" )
} )
test ( "#given explicit description #when executing #then preserves provided description" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
let capturedTitle : string | undefined
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
2026-04-05 15:46:24 +09:00
} ,
2026-04-07 09:30:22 +09:00
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
try {
await tool . execute (
{
description : "My custom task name" ,
prompt : "Do something else entirely" ,
category : "quick" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
{
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
metadata : async ( meta : { title? : string } ) = > { capturedTitle = meta . title } ,
}
)
} catch {
// execution may fail due to incomplete mocks
}
// then — explicit description preserved
expect ( capturedTitle ) . toBe ( "My custom task name" )
2026-04-05 15:46:24 +09:00
} )
2026-03-17 15:44:18 +09:00
test ( "#given explicit run_in_background=false #when executing #then sync execution succeeds" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
let promptCalled = false
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
2026-04-17 14:51:52 +09:00
app : { agents : async ( ) = > ( { data : [ { name : "oracle" , mode : "subagent" , model : { providerID : "anthropic" , modelID : "claude-opus-4-7" } } ] } ) } ,
2026-03-17 15:44:18 +09:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_explicit_false" } } ) ,
prompt : async ( ) = > {
promptCalled = true
return { data : { } }
} ,
promptAsync : async ( ) = > {
promptCalled = true
return { data : { } }
} ,
messages : async ( ) = > ( { data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ] } ) ,
status : async ( ) = > ( { data : { ses_explicit_false : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
const result = await tool . execute (
{
description : "Explicit false" ,
prompt : "Run sync" ,
subagent_type : "oracle" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
)
// then
expect ( promptCalled ) . toBe ( true )
expect ( result ) . toContain ( "Done" )
} , { timeout : 10000 } )
test ( "#given explicit run_in_background=true #when executing #then background execution succeeds" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
const mockManager = {
launch : async ( ) = > {
launchCalled = true
return {
id : "bg_explicit_true" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_bg_explicit_true" ,
2026-03-17 15:44:18 +09:00
description : "Explicit true" ,
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 : "ses_bg_explicit_true" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
const result = await tool . execute (
{
description : "Explicit true" ,
prompt : "Run background" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
)
// then
expect ( launchCalled ) . toBe ( true )
expect ( result ) . toContain ( "Background task launched" )
} , { timeout : 10000 } )
2026-03-31 15:11:12 -07:00
test ( "#given concurrent background launches from the same parent #when one parent call aborts during session wait #then sibling launch is not interrupted" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
const firstAbortController = new AbortController ( )
const secondAbortController = new AbortController ( )
const taskStates = new Map ( [
2026-05-02 03:01:03 +09:00
[ "bg_tool_first" , { reads : 0 , abortOnFirstRead : true , sessionId : "ses_tool_first" } ] ,
[ "bg_tool_second" , { reads : 0 , abortOnFirstRead : false , sessionId : "ses_tool_second" } ] ,
2026-03-31 15:11:12 -07:00
] )
let launchCount = 0
const mockManager = {
launch : async ( ) = > {
launchCount += 1
return launchCount === 1
? {
id : "bg_tool_first" ,
2026-05-02 03:01:03 +09:00
sessionId : undefined ,
2026-03-31 15:11:12 -07:00
description : "Tool first" ,
agent : "Sisyphus-Junior" ,
status : "running" ,
}
: {
id : "bg_tool_second" ,
2026-05-02 03:01:03 +09:00
sessionId : undefined ,
2026-03-31 15:11:12 -07:00
description : "Tool second" ,
agent : "Sisyphus-Junior" ,
status : "running" ,
}
} ,
getTask : ( taskID : string ) = > {
const state = taskStates . get ( taskID )
if ( ! state ) return undefined
state . reads += 1
if ( state . abortOnFirstRead && state . reads === 1 ) {
firstAbortController . abort ( )
}
return state . reads >= 2
2026-05-02 03:01:03 +09:00
? { sessionId : state.sessionId , status : "running" }
: { sessionId : undefined , status : "pending" }
2026-03-31 15:11:12 -07:00
} ,
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
model : { list : async ( ) = > [ ] } ,
session : {
create : async ( ) = > ( { data : { id : "ses_bg_explicit_true" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( { manager : mockManager , client : mockClient } )
// when
const [ firstResult , secondResult ] = await Promise . all ( [
tool . execute (
{
description : "Tool first" ,
prompt : "Run background" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message-1" , agent : "sisyphus" , abort : firstAbortController.signal }
) ,
tool . execute (
{
description : "Tool second" ,
prompt : "Run background" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
{ sessionID : "parent-session" , messageID : "parent-message-2" , agent : "sisyphus" , abort : secondAbortController.signal }
) ,
] )
// then
expect ( firstResult ) . toContain ( "Background task launched" )
expect ( firstResult ) . not . toContain ( "Task failed to start" )
expect ( secondResult ) . toContain ( "Background task launched" )
expect ( secondResult ) . toContain ( "session_id: ses_tool_second" )
expect ( secondResult ) . not . toContain ( "interrupt" )
} , { timeout : 10000 } )
2026-03-17 15:44:18 +09:00
} )
2026-04-16 23:18:38 +09:00
describe ( "task_id with background parameter" , ( ) = > {
test ( "task_id with background=false should wait for result and return content" , async ( ) = > {
2026-01-11 11:20:00 +09:00
// Note: This test needs extended timeout because the implementation has MIN_STABILITY_TIME_MS = 5000
2026-02-01 16:47:50 +09:00
// given
2026-01-16 17:34:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-01-09 02:24:43 +09:00
const mockTask = {
id : "task-123" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_continue_test" ,
2026-01-25 13:28:44 +09:00
description : "Continued task" ,
2026-01-09 02:24:43 +09:00
agent : "explore" ,
status : "running" ,
}
const mockManager = {
resume : async ( ) = > mockTask ,
launch : async ( ) = > mockTask ,
}
2026-02-15 19:48:59 +09:00
let messagesCallCount = 0
2026-02-10 17:19:06 +09:00
2026-02-15 19:48:59 +09:00
const mockClient = {
session : {
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( args ? : { path ? : { id? : string } } ) = > {
const sessionID = args ? . path ? . id
// Only track calls for the target session (ses_continue_test),
// not for parent-session calls from resolveParentContext
if ( sessionID !== "ses_continue_test" ) {
return { data : [ ] }
}
messagesCallCount ++
const now = Date . now ( )
const beforeContinuation = [
{
info : { id : "msg_001" , role : "user" , time : { created : now } } ,
parts : [ { type : "text" , text : "Previous context" } ] ,
} ,
{
info : { id : "msg_002" , role : "assistant" , time : { created : now + 1 } , finish : "end_turn" } ,
parts : [ { type : "text" , text : "Previous result" } ] ,
} ,
]
if ( messagesCallCount === 1 ) {
return { data : beforeContinuation }
}
return {
data : [
. . . beforeContinuation ,
{
info : { id : "msg_003" , role : "user" , time : { created : now + 2 } } ,
parts : [ { type : "text" , text : "Continue the task" } ] ,
} ,
{
info : { id : "msg_004" , role : "assistant" , time : { created : now + 3 } , finish : "end_turn" } ,
parts : [ { type : "text" , text : "This is the continued task result" } ] ,
} ,
] ,
}
} ,
status : async ( ) = > ( { data : { "ses_continue_test" : { type : "idle" } } } ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
app : {
agents : async ( ) = > ( { data : [ ] } ) ,
2026-02-10 17:19:06 +09:00
} ,
2026-02-15 19:48:59 +09:00
}
2026-02-07 14:32:47 +01:00
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when
const result = await tool . execute (
{
description : "Continue test" ,
prompt : "Continue the task" ,
2026-04-16 23:18:38 +09:00
task_id : "ses_continue_test" ,
2026-02-07 14:32:47 +01:00
run_in_background : false ,
load_skills : [ "git-master" ] ,
} ,
toolContext
)
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then - should contain actual result, not just "Background task continued"
2026-01-25 13:28:44 +09:00
expect ( result ) . toContain ( "This is the continued task result" )
expect ( result ) . not . toContain ( "Background task continued" )
2026-01-11 11:20:00 +09:00
} , { timeout : 10000 } )
2026-01-09 02:24:43 +09:00
2026-02-08 13:52:55 +09:00
test ( "sync continuation preserves variant from previous session message" , async ( ) = > {
//#given a session with a previous message that has variant "max"
const { createDelegateTask } = require ( "./tools" )
2026-05-01 17:57:49 +09:00
const promptMock = mock ( async ( ) = > {
2026-02-08 13:52:55 +09:00
return { data : { } }
} )
2026-02-27 13:54:50 +08:00
const baseTime = Date . now ( )
const initialMessages = [
{
info : {
id : "msg_001" ,
role : "user" ,
agent : "sisyphus-junior" ,
2026-04-17 14:51:52 +09:00
model : { providerID : "anthropic" , modelID : "claude-opus-4-7" } ,
2026-02-27 13:54:50 +08:00
variant : "max" ,
time : { created : baseTime } ,
} ,
parts : [ { type : "text" , text : "previous message" } ] ,
} ,
{
info : { id : "msg_002" , role : "assistant" , time : { created : baseTime + 1 } , finish : "end_turn" } ,
parts : [ { type : "text" , text : "Completed." } ] ,
} ,
]
const messagesCallCounts : Record < string , number > = { }
2026-02-08 13:52:55 +09:00
const mockClient = {
session : {
2026-02-08 13:57:13 +09:00
prompt : promptMock ,
2026-02-09 15:37:19 +09:00
promptAsync : promptMock ,
2026-02-27 13:54:50 +08:00
messages : async ( input : any ) = > {
const sessionID = input ? . path ? . id
if ( typeof sessionID !== "string" ) {
return { data : [ ] }
}
const callCount = ( messagesCallCounts [ sessionID ] ? ? 0 ) + 1
messagesCallCounts [ sessionID ] = callCount
if ( sessionID !== "ses_var_test" ) {
return { data : [ ] }
}
if ( callCount === 1 ) {
return { data : initialMessages }
}
return {
data : [
. . . initialMessages ,
{
info : { id : "msg_003" , role : "assistant" , time : { created : baseTime + 2 } , finish : "end_turn" } ,
parts : [ { type : "text" , text : "Continued." } ] ,
2026-02-08 13:52:55 +09:00
} ,
2026-02-27 13:54:50 +08:00
] ,
}
} ,
2026-02-09 15:37:19 +09:00
status : async ( ) = > ( { data : { "ses_var_test" : { type : "idle" } } } ) ,
2026-02-08 13:52:55 +09:00
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
app : {
agents : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
2026-05-02 03:01:03 +09:00
manager : { resume : async ( ) = > ( { id : "task-var" , sessionId : "ses_var_test" , description : "Variant test" , agent : "sisyphus-junior" , status : "running" } ) } ,
2026-02-08 13:52:55 +09:00
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
//#when continuing the session
await tool . execute (
{
description : "Continue with variant" ,
prompt : "Continue the task" ,
2026-04-16 23:18:38 +09:00
task_id : "ses_var_test" ,
2026-02-08 13:52:55 +09:00
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
2026-02-08 13:57:13 +09:00
//#then prompt should include variant from previous message
2026-02-08 13:52:55 +09:00
expect ( promptMock ) . toHaveBeenCalled ( )
const callArgs = promptMock . mock . calls [ 0 ] [ 0 ]
expect ( callArgs . body . variant ) . toBe ( "max" )
expect ( callArgs . body . agent ) . toBe ( "sisyphus-junior" )
2026-04-17 14:51:52 +09:00
expect ( callArgs . body . model ) . toEqual ( { providerID : "anthropic" , modelID : "claude-opus-4-7" } )
2026-02-08 13:52:55 +09:00
} , { timeout : 10000 } )
2026-04-16 23:18:38 +09:00
test ( "task_id with background=true should return immediately without waiting" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-16 17:34:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-01-09 02:24:43 +09:00
const mockTask = {
id : "task-456" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_bg_continue" ,
2026-01-25 13:28:44 +09:00
description : "Background continued task" ,
2026-01-09 02:24:43 +09:00
agent : "explore" ,
status : "running" ,
}
const mockManager = {
resume : async ( ) = > mockTask ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
session : {
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( {
data : [ ] ,
} ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when
const result = await tool . execute (
{
description : "Continue bg test" ,
prompt : "Continue in background" ,
2026-04-16 23:18:38 +09:00
task_id : "ses_bg_continue" ,
2026-02-07 14:32:47 +01:00
run_in_background : true ,
load_skills : [ "git-master" ] ,
} ,
toolContext
)
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then - should return background message
2026-01-25 13:28:44 +09:00
expect ( result ) . toContain ( "Background task continued" )
2026-01-09 02:24:43 +09:00
expect ( result ) . toContain ( "task-456" )
} )
} )
2026-01-12 19:26:32 +11:00
describe ( "sync mode new task (run_in_background=false)" , ( ) = > {
test ( "sync mode prompt error returns error message immediately" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-16 17:34:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-01-12 19:26:32 +11:00
const mockManager = {
launch : async ( ) = > ( { } ) ,
}
2026-02-07 14:32:47 +01:00
const promptMock = async ( ) = > {
throw new Error ( "JSON Parse error: Unexpected EOF" )
}
const mockClient = {
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_sync_error_test" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
app : {
agents : async ( ) = > ( { data : [ { name : "ultrabrain" , mode : "subagent" } ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-12 19:26:32 +11:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-12 19:26:32 +11:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when
2026-01-12 19:26:32 +11:00
const result = await tool . execute (
{
description : "Sync error test" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-12 19:26:32 +11:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should return detailed error message with args and stack trace
2026-01-15 18:54:04 +09:00
expect ( result ) . toContain ( "Send prompt failed" )
2026-01-12 19:26:32 +11:00
expect ( result ) . toContain ( "JSON Parse error" )
2026-01-15 18:54:04 +09:00
expect ( result ) . toContain ( "**Arguments**:" )
expect ( result ) . toContain ( "**Stack Trace**:" )
2026-01-12 19:26:32 +11:00
} )
test ( "sync mode success returns task result with content" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-16 17:34:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-01-12 19:26:32 +11:00
const mockManager = {
launch : async ( ) = > ( { } ) ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_sync_success" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( {
data : [
{
2026-02-09 15:37:19 +09:00
info : { id : "msg_001" , role : "user" , time : { created : Date.now ( ) } } ,
parts : [ { type : "text" , text : "Do something" } ] ,
} ,
{
info : { id : "msg_002" , role : "assistant" , time : { created : Date.now ( ) + 1 } , finish : "end_turn" } ,
2026-02-07 14:32:47 +01:00
parts : [ { type : "text" , text : "Sync task completed successfully" } ] ,
} ,
] ,
} ) ,
status : async ( ) = > ( { data : { "ses_sync_success" : { type : "idle" } } } ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
app : {
agents : async ( ) = > ( { data : [ { name : "ultrabrain" , mode : "subagent" } ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-12 19:26:32 +11:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-12 19:26:32 +11:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when
2026-01-12 19:26:32 +11:00
const result = await tool . execute (
{
description : "Sync success test" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-12 19:26:32 +11:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should return the task result content
2026-01-12 19:26:32 +11:00
expect ( result ) . toContain ( "Sync task completed successfully" )
expect ( result ) . toContain ( "Task completed" )
} , { timeout : 20000 } )
test ( "sync mode agent not found returns helpful error" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-16 17:34:40 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-01-12 19:26:32 +11:00
const mockManager = {
launch : async ( ) = > ( { } ) ,
}
2026-02-07 14:32:47 +01:00
const promptMock = async ( ) = > {
throw new Error ( "Cannot read property 'name' of undefined agent.name" )
}
const mockClient = {
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_agent_notfound" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( { data : [ ] } ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
app : {
agents : async ( ) = > ( { data : [ { name : "ultrabrain" , mode : "subagent" } ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-12 19:26:32 +11:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-12 19:26:32 +11:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when
2026-01-12 19:26:32 +11:00
const result = await tool . execute (
{
description : "Agent not found test" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-12 19:26:32 +11:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should return agent not found error
2026-01-12 19:26:32 +11:00
expect ( result ) . toContain ( "not found" )
expect ( result ) . toContain ( "registered" )
} )
2026-02-07 14:32:47 +01:00
test ( "sync mode passes category model to prompt" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_sync_model" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ]
} ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
}
2026-01-12 19:26:32 +11:00
2026-01-16 17:34:40 +09:00
const tool = createDelegateTask ( {
2026-01-12 19:26:32 +11:00
manager : mockManager ,
client : mockClient ,
userCategories : {
"custom-cat" : { model : "provider/custom-model" }
}
} )
const toolContext = {
sessionID : "parent" ,
messageID : "msg" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-12 19:26:32 +11:00
abort : new AbortController ( ) . signal
}
2026-02-01 16:47:50 +09:00
// when
2026-01-12 19:26:32 +11:00
await tool . execute ( {
description : "Sync model test" ,
prompt : "test" ,
category : "custom-cat" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ]
2026-01-12 19:26:32 +11:00
} , toolContext )
2026-02-01 16:47:50 +09:00
// then
2026-01-12 19:26:32 +11:00
expect ( promptBody . model ) . toEqual ( {
providerID : "provider" ,
modelID : "custom-model"
} )
} , { timeout : 20000 } )
} )
2026-01-21 00:06:05 +09:00
describe ( "unstable agent forced background mode" , ( ) = > {
test ( "gemini model with run_in_background=false should force background but wait for result" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - category using gemini model with run_in_background=false
2026-01-21 00:06:05 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
2026-02-16 15:56:52 +09:00
const launchedTask = {
id : "task-unstable" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_unstable_gemini" ,
2026-02-16 15:56:52 +09:00
description : "Unstable gemini task" ,
agent : "sisyphus-junior" ,
status : "running" ,
}
2026-01-21 00:06:05 +09:00
const mockManager = {
launch : async ( ) = > {
launchCalled = true
2026-02-16 15:56:52 +09:00
return launchedTask
2026-01-21 00:06:05 +09:00
} ,
2026-02-16 15:56:52 +09:00
getTask : ( ) = > launchedTask ,
2026-01-21 00:06:05 +09:00
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
2026-02-26 21:01:05 +09:00
model : { list : async ( ) = > [ { provider : "google" , id : "gemini-3.1-pro" } ] } ,
2026-02-07 14:32:47 +01:00
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_unstable_gemini" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( {
data : [
{ info : { role : "assistant" , time : { created : Date.now ( ) } } , parts : [ { type : "text" , text : "Gemini task completed successfully" } ] }
]
} ) ,
status : async ( ) = > ( { data : { "ses_unstable_gemini" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-21 00:06:05 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-21 00:06:05 +09:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - using visual-engineering (gemini model) with run_in_background=false
2026-01-21 00:06:05 +09:00
const result = await tool . execute (
{
description : "Test gemini forced background" ,
prompt : "Do something visual" ,
category : "visual-engineering" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-21 00:06:05 +09:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should launch as background BUT wait for and return actual result
2026-01-21 00:06:05 +09:00
expect ( launchCalled ) . toBe ( true )
2026-01-22 22:46:23 +09:00
expect ( result ) . toContain ( "SUPERVISED TASK COMPLETED" )
2026-01-21 00:06:05 +09:00
expect ( result ) . toContain ( "Gemini task completed successfully" )
} , { timeout : 20000 } )
test ( "gemini model with run_in_background=true should not show unstable message (normal background)" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - category using gemini model with run_in_background=true (normal background flow)
2026-01-21 00:06:05 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
const mockManager = {
launch : async ( ) = > {
launchCalled = true
return {
id : "task-normal-bg" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_normal_bg" ,
2026-01-21 00:06:05 +09:00
description : "Normal background task" ,
2026-01-24 03:00:01 +09:00
agent : "sisyphus-junior" ,
2026-01-21 00:06:05 +09:00
status : "running" ,
}
} ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "test-session" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - using visual-engineering with run_in_background=true (normal background)
const result = await tool . execute (
{
description : "Test normal background" ,
prompt : "Do something visual" ,
category : "visual-engineering" ,
run_in_background : true , // User explicitly says true - normal background
load_skills : [ "git-master" ] ,
} ,
toolContext
)
2026-01-21 00:06:05 +09:00
2026-02-01 16:47:50 +09:00
// then - should NOT show unstable message (it's normal background flow)
2026-01-21 00:06:05 +09:00
expect ( launchCalled ) . toBe ( true )
expect ( result ) . not . toContain ( "UNSTABLE AGENT MODE" )
expect ( result ) . toContain ( "task-normal-bg" )
} )
2026-02-01 17:20:01 +09:00
test ( "minimax model with run_in_background=false should force background but wait for result" , async ( ) = > {
// given - custom category using minimax model with run_in_background=false
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
2026-02-16 15:56:52 +09:00
const launchedTask = {
id : "task-unstable-minimax" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_unstable_minimax" ,
2026-02-16 15:56:52 +09:00
description : "Unstable minimax task" ,
agent : "sisyphus-junior" ,
status : "running" ,
}
2026-02-01 17:20:01 +09:00
const mockManager = {
launch : async ( ) = > {
launchCalled = true
2026-02-16 15:56:52 +09:00
return launchedTask
2026-02-01 17:20:01 +09:00
} ,
2026-02-16 15:56:52 +09:00
getTask : ( ) = > launchedTask ,
2026-02-01 17:20:01 +09:00
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_unstable_minimax" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( {
data : [
{ info : { role : "assistant" , time : { created : Date.now ( ) } } , parts : [ { type : "text" , text : "Minimax task completed successfully" } ] }
]
} ) ,
status : async ( ) = > ( { data : { "ses_unstable_minimax" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
userCategories : {
"minimax-cat" : {
model : "minimax/abab-5" ,
} ,
} ,
} )
2026-02-01 17:20:01 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - using minimax category with run_in_background=false
const result = await tool . execute (
{
description : "Test minimax forced background" ,
prompt : "Do something with minimax" ,
category : "minimax-cat" ,
run_in_background : false ,
load_skills : [ "git-master" ] ,
} ,
toolContext
)
// then - should launch as background BUT wait for and return actual result
expect ( launchCalled ) . toBe ( true )
expect ( result ) . toContain ( "SUPERVISED TASK COMPLETED" )
expect ( result ) . toContain ( "Minimax task completed successfully" )
} , { timeout : 20000 } )
2026-01-21 00:06:05 +09:00
test ( "non-gemini model with run_in_background=false should run sync (not forced to background)" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - category using non-gemini model with run_in_background=false
2026-01-21 00:06:05 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
let promptCalled = false
const mockManager = {
launch : async ( ) = > {
launchCalled = true
2026-05-02 03:01:03 +09:00
return { id : "should-not-be-called" , sessionId : "x" , description : "x" , agent : "x" , status : "running" }
2026-01-21 00:06:05 +09:00
} ,
}
2026-02-07 14:32:47 +01:00
const promptMock = async ( ) = > {
promptCalled = true
return { data : { } }
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_sync_non_gemini" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done sync" } ] } ]
} ) ,
status : async ( ) = > ( { data : { "ses_sync_non_gemini" : { type : "idle" } } } ) ,
} ,
}
2026-04-25 00:41:16 +09:00
// Use ultrabrain which uses gpt-5.5 (non-gemini)
2026-02-07 14:32:47 +01:00
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-21 00:06:05 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-21 00:06:05 +09:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - using ultrabrain (gpt model) with run_in_background=false
2026-01-21 00:06:05 +09:00
const result = await tool . execute (
{
description : "Test non-gemini sync" ,
prompt : "Do something smart" ,
category : "ultrabrain" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-21 00:06:05 +09:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should run sync, NOT forced to background
2026-01-21 00:06:05 +09:00
expect ( launchCalled ) . toBe ( false ) // manager.launch should NOT be called
expect ( promptCalled ) . toBe ( true ) // sync mode uses session.prompt
expect ( result ) . not . toContain ( "UNSTABLE AGENT MODE" )
} , { timeout : 20000 } )
test ( "artistry category (gemini) with run_in_background=false should force background but wait for result" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - artistry also uses gemini model
2026-01-21 00:06:05 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
2026-02-16 15:56:52 +09:00
const launchedTask = {
id : "task-artistry" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_artistry_gemini" ,
2026-02-16 15:56:52 +09:00
description : "Artistry gemini task" ,
agent : "sisyphus-junior" ,
status : "running" ,
}
2026-01-21 00:06:05 +09:00
const mockManager = {
launch : async ( ) = > {
launchCalled = true
2026-02-16 15:56:52 +09:00
return launchedTask
2026-01-21 00:06:05 +09:00
} ,
2026-02-16 15:56:52 +09:00
getTask : ( ) = > launchedTask ,
2026-01-21 00:06:05 +09:00
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
2026-02-26 21:01:05 +09:00
model : { list : async ( ) = > [ { provider : "google" , id : "gemini-3.1-pro" } ] } ,
2026-02-07 14:32:47 +01:00
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_artistry_gemini" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( {
data : [
{ info : { role : "assistant" , time : { created : Date.now ( ) } } , parts : [ { type : "text" , text : "Artistry result here" } ] }
]
} ) ,
status : async ( ) = > ( { data : { "ses_artistry_gemini" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-21 00:06:05 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-21 00:06:05 +09:00
abort : new AbortController ( ) . signal ,
}
2026-02-26 21:01:05 +09:00
// when - artistry category (gemini-3.1-pro with high variant)
2026-01-21 00:06:05 +09:00
const result = await tool . execute (
{
description : "Test artistry forced background" ,
prompt : "Do something artistic" ,
category : "artistry" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-21 00:06:05 +09:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should launch as background BUT wait for and return actual result
2026-01-21 00:06:05 +09:00
expect ( launchCalled ) . toBe ( true )
2026-01-22 22:46:23 +09:00
expect ( result ) . toContain ( "SUPERVISED TASK COMPLETED" )
2026-01-21 00:06:05 +09:00
expect ( result ) . toContain ( "Artistry result here" )
} , { timeout : 20000 } )
2026-04-05 15:30:36 +09:00
test ( "writing category (kimi) with run_in_background=false should run sync when kimi provider is available" , async ( ) = > {
// given - writing uses kimi model which is no longer considered unstable
// Override provider cache to include kimi-for-coding provider
providerModelsSpy . mockReturnValue ( {
models : {
2026-04-17 14:51:52 +09:00
anthropic : [ "claude-opus-4-7" , "claude-sonnet-4-6" , "claude-haiku-4-5" ] ,
2026-04-05 15:30:36 +09:00
google : [ "gemini-3.1-pro" , "gemini-3-flash" ] ,
2026-04-25 00:41:16 +09:00
openai : [ "gpt-5.5" , "gpt-5.5" , "gpt-5.3-codex" ] ,
2026-04-05 15:30:36 +09:00
"kimi-for-coding" : [ "k2p5" ] ,
} ,
connected : [ "anthropic" , "google" , "openai" , "kimi-for-coding" ] ,
updatedAt : "2026-01-01T00:00:00.000Z" ,
} )
cacheSpy . mockReturnValue ( [ "anthropic" , "google" , "openai" , "kimi-for-coding" ] )
2026-01-21 00:06:05 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
2026-04-05 15:30:36 +09:00
let promptCalled = false
2026-01-21 00:06:05 +09:00
const mockManager = {
launch : async ( ) = > {
launchCalled = true
2026-05-02 03:01:03 +09:00
return { id : "should-not-be-called" , sessionId : "x" , description : "x" , agent : "x" , status : "running" }
2026-01-21 00:06:05 +09:00
} ,
}
2026-04-05 15:30:36 +09:00
const promptMock = async ( ) = > {
promptCalled = true
return { data : { } }
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
2026-04-05 15:30:36 +09:00
create : async ( ) = > ( { data : { id : "ses_writing_kimi" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
2026-02-07 14:32:47 +01:00
messages : async ( ) = > ( {
2026-04-05 15:30:36 +09:00
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Writing result here" } ] } ]
2026-02-07 14:32:47 +01:00
} ) ,
2026-04-05 15:30:36 +09:00
status : async ( ) = > ( { data : { "ses_writing_kimi" : { type : "idle" } } } ) ,
2026-02-07 14:32:47 +01:00
} ,
}
2026-04-05 15:30:36 +09:00
2026-02-07 14:32:47 +01:00
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-04-05 15:30:36 +09:00
2026-01-21 00:06:05 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-21 00:06:05 +09:00
abort : new AbortController ( ) . signal ,
}
2026-04-05 15:30:36 +09:00
// when - writing category (kimi) with run_in_background=false
2026-01-21 00:06:05 +09:00
const result = await tool . execute (
{
2026-04-05 15:30:36 +09:00
description : "Test writing sync" ,
2026-01-21 00:06:05 +09:00
prompt : "Write something" ,
category : "writing" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-21 00:06:05 +09:00
} ,
toolContext
)
2026-04-05 15:30:36 +09:00
// then - should run sync, NOT forced to background (kimi is not unstable)
expect ( launchCalled ) . toBe ( false )
expect ( promptCalled ) . toBe ( true )
expect ( result ) . not . toContain ( "SUPERVISED TASK COMPLETED" )
2026-01-21 00:06:05 +09:00
} , { timeout : 20000 } )
test ( "is_unstable_agent=true should force background but wait for result" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - custom category with is_unstable_agent=true but non-gemini model
2026-01-21 00:06:05 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchCalled = false
2026-02-16 15:56:52 +09:00
const launchedTask = {
id : "task-custom-unstable" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_custom_unstable" ,
2026-02-16 15:56:52 +09:00
description : "Custom unstable task" ,
agent : "sisyphus-junior" ,
status : "running" ,
}
2026-01-21 00:06:05 +09:00
const mockManager = {
launch : async ( ) = > {
launchCalled = true
2026-02-16 15:56:52 +09:00
return launchedTask
2026-01-21 00:06:05 +09:00
} ,
2026-02-16 15:56:52 +09:00
getTask : ( ) = > launchedTask ,
2026-01-21 00:06:05 +09:00
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_custom_unstable" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
2026-02-07 14:32:47 +01:00
promptAsync : async ( ) = > ( { data : { } } ) ,
2026-01-21 00:06:05 +09:00
messages : async ( ) = > ( {
data : [
{ info : { role : "assistant" , time : { created : Date.now ( ) } } , parts : [ { type : "text" , text : "Custom unstable result" } ] }
]
} ) ,
status : async ( ) = > ( { data : { "ses_custom_unstable" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
userCategories : {
"my-unstable-cat" : {
2026-04-25 00:41:16 +09:00
model : "openai/gpt-5.5" ,
2026-01-21 00:06:05 +09:00
is_unstable_agent : true ,
} ,
} ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-21 00:06:05 +09:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - using custom unstable category with run_in_background=false
2026-01-21 00:06:05 +09:00
const result = await tool . execute (
{
description : "Test custom unstable" ,
prompt : "Do something" ,
category : "my-unstable-cat" ,
run_in_background : false ,
2026-01-22 22:46:23 +09:00
load_skills : [ "git-master" ] ,
2026-01-21 00:06:05 +09:00
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - should launch as background BUT wait for and return actual result
2026-01-21 00:06:05 +09:00
expect ( launchCalled ) . toBe ( true )
2026-01-22 22:46:23 +09:00
expect ( result ) . toContain ( "SUPERVISED TASK COMPLETED" )
2026-01-21 00:06:05 +09:00
expect ( result ) . toContain ( "Custom unstable result" )
} , { timeout : 20000 } )
2026-01-25 15:02:41 +09:00
} )
2026-01-30 11:45:19 +09:00
describe ( "category model resolution fallback" , ( ) = > {
test ( "category uses resolved.model when connectedProvidersCache is null and availableModels is empty" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - connectedProvidersCache returns null (simulates missing cache file)
2026-01-30 11:45:19 +09:00
// 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" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_fallback_test" ,
2026-01-30 11:45:19 +09:00
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
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : null ,
availableModelsOverride : new Set ( ) ,
2026-01-30 11:45:19 +09:00
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-04-25 00:41:16 +09:00
// when - using "quick" category which should use the catalog model
2026-01-30 11:45:19 +09:00
await tool . execute (
{
description : "Test category fallback" ,
prompt : "Do something quick" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
2026-04-25 00:41:16 +09:00
// then - model should be openai/gpt-5.4-mini from DEFAULT_CATEGORIES
2026-02-18 15:51:24 +09:00
// NOT anthropic/claude-sonnet-4-6 (system default)
2026-04-25 00:41:16 +09:00
expect ( launchInput . model . providerID ) . toBe ( "openai" )
expect ( launchInput . model . modelID ) . toBe ( "gpt-5.4-mini" )
2026-01-30 11:45:19 +09:00
} )
2026-02-03 10:41:26 +09:00
2026-02-06 17:44:47 +09:00
test ( "category delegation ignores UI-selected (Kimi) system default model" , async ( ) = > {
// given - OpenCode system default model is Kimi (selected from UI)
const { createDelegateTask } = require ( "./tools" )
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-ui-model" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_ui_model_test" ,
2026-02-06 17:44:47 +09:00
description : "UI model inheritance test" ,
agent : "sisyphus-junior" ,
status : "running" ,
}
} ,
}
2026-02-07 14:32:47 +01:00
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 : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
2026-02-06 17:44:47 +09:00
2026-02-07 14:32:47 +01:00
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
userCategories : {
2026-04-17 14:51:52 +09:00
"fallback-test" : { model : "anthropic/claude-opus-4-7" } ,
2026-02-07 14:32:47 +01:00
} ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-07 14:32:47 +01:00
} )
2026-02-06 17:44:47 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-04-25 00:41:16 +09:00
// when - using "quick" category which should use the catalog model
2026-02-06 17:44:47 +09:00
await tool . execute (
{
description : "UI model inheritance test" ,
prompt : "Do something quick" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
// then - category model must win (not Kimi)
2026-04-25 00:41:16 +09:00
expect ( launchInput . model . providerID ) . toBe ( "openai" )
expect ( launchInput . model . modelID ) . toBe ( "gpt-5.4-mini" )
2026-02-06 17:44:47 +09:00
} )
2026-02-03 10:41:26 +09:00
test ( "sisyphus-junior model override takes precedence over category model" , async ( ) = > {
// given - sisyphus-junior override model differs from category default
const { createDelegateTask } = require ( "./tools" )
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-override" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_override_test" ,
2026-02-03 10:41:26 +09:00
description : "Override precedence test" ,
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 : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-02-18 15:51:24 +09:00
sisyphusJuniorModel : "anthropic/claude-sonnet-4-6" ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-03 10:41:26 +09:00
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-04-25 00:41:16 +09:00
// when - using ultrabrain category (default model is openai/gpt-5.5)
2026-02-03 10:41:26 +09:00
await tool . execute (
{
description : "Override precedence test" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
// then - override model should be used instead of category model
expect ( launchInput . model . providerID ) . toBe ( "anthropic" )
2026-02-18 15:51:24 +09:00
expect ( launchInput . model . modelID ) . toBe ( "claude-sonnet-4-6" )
2026-02-03 10:41:26 +09:00
} )
2026-02-04 11:51:20 +08:00
test ( "explicit category model takes precedence over sisyphus-junior model" , async ( ) = > {
// given - explicit category model differs from sisyphus-junior override
const { createDelegateTask } = require ( "./tools" )
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-category-precedence" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_category_precedence_test" ,
2026-02-04 11:51:20 +08:00
description : "Category precedence test" ,
agent : "sisyphus-junior" ,
status : "running" ,
}
} ,
}
2026-02-07 14:32:47 +01:00
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 : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-02-18 15:51:24 +09:00
sisyphusJuniorModel : "anthropic/claude-sonnet-4-6" ,
2026-02-07 14:32:47 +01:00
userCategories : {
2026-04-25 00:41:16 +09:00
ultrabrain : { model : "openai/gpt-5.5" } ,
2026-02-07 14:32:47 +01:00
} ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-07 14:32:47 +01:00
} )
2026-02-04 11:51:20 +08:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - using ultrabrain category with explicit model override
await tool . execute (
{
description : "Category precedence test" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
// then - explicit category model should win
expect ( launchInput . model . providerID ) . toBe ( "openai" )
2026-04-25 00:41:16 +09:00
expect ( launchInput . model . modelID ) . toBe ( "gpt-5.5" )
2026-02-04 11:51:20 +08:00
} )
2026-02-08 13:13:47 +09:00
test ( "sisyphus-junior model override works with quick category (#1295)" , async ( ) = > {
// given - user configures agents.sisyphus-junior.model but uses quick category
const { createDelegateTask } = require ( "./tools" )
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-1295-quick" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_1295_quick" ,
2026-02-08 13:13:47 +09:00
description : "Issue 1295 regression" ,
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 : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-02-18 15:51:24 +09:00
sisyphusJuniorModel : "anthropic/claude-sonnet-4-6" ,
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-08 13:13:47 +09:00
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - using quick category (default: anthropic/claude-haiku-4-5)
await tool . execute (
{
description : "Issue 1295 quick category test" ,
prompt : "Quick task" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
// then - sisyphus-junior override model should be used, not category default
expect ( launchInput . model . providerID ) . toBe ( "anthropic" )
2026-02-18 15:51:24 +09:00
expect ( launchInput . model . modelID ) . toBe ( "claude-sonnet-4-6" )
2026-04-07 11:20:42 +09:00
expect ( launchInput . fallbackChain ) . toBeUndefined ( )
2026-02-08 13:13:47 +09:00
} )
test ( "sisyphus-junior model override works with user-defined category (#1295)" , async ( ) = > {
// given - user has a custom category with no model requirement
const { createDelegateTask } = require ( "./tools" )
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-1295-custom" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_1295_custom" ,
2026-02-08 13:13:47 +09:00
description : "Issue 1295 custom category" ,
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 : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
2026-04-25 00:41:16 +09:00
sisyphusJuniorModel : "openai/gpt-5.5" ,
2026-02-08 13:13:47 +09:00
userCategories : {
"my-custom" : { temperature : 0.5 } ,
} ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - using custom category with no explicit model
await tool . execute (
{
description : "Custom category with agent model" ,
prompt : "Do something custom" ,
category : "my-custom" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
// then - sisyphus-junior override model should be used as fallback
expect ( launchInput . model . providerID ) . toBe ( "openai" )
2026-04-25 00:41:16 +09:00
expect ( launchInput . model . modelID ) . toBe ( "gpt-5.5" )
2026-02-08 13:13:47 +09:00
} )
2026-01-30 11:45:19 +09:00
} )
2026-01-25 15:02:41 +09:00
describe ( "browserProvider propagation" , ( ) = > {
test ( "should resolve agent-browser skill when browserProvider is passed" , async ( ) = > {
2026-02-06 16:01:54 +09:00
// given - task configured with browserProvider: "agent-browser"
2026-01-25 15:02:41 +09:00
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
2026-02-07 14:32:47 +01:00
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_browser_provider" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ]
} ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
// Pass browserProvider to createDelegateTask
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
browserProvider : "agent-browser" ,
} )
2026-01-25 15:02:41 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-25 15:02:41 +09:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - request agent-browser skill
2026-01-25 15:02:41 +09:00
await tool . execute (
{
description : "Test browserProvider propagation" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
load_skills : [ "agent-browser" ] ,
} ,
toolContext
)
2026-02-04 15:25:50 +09:00
// then - agent-browser skill should be resolved
2026-01-25 15:02:41 +09:00
expect ( promptBody ) . toBeDefined ( )
expect ( promptBody . system ) . toBeDefined ( )
2026-02-04 15:25:50 +09:00
expect ( promptBody . system ) . toContain ( "<Category_Context>" )
expect ( String ( promptBody . system ) . startsWith ( "<Category_Context>" ) ) . toBe ( false )
2026-01-25 15:02:41 +09:00
} , { timeout : 20000 } )
2026-02-04 15:25:50 +09:00
test ( "should resolve agent-browser skill even when browserProvider is not set" , async ( ) = > {
// given - delegate_task without browserProvider
2026-01-25 15:02:41 +09:00
const { createDelegateTask } = require ( "./tools" )
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
2026-02-04 15:25:50 +09:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_no_browser_provider" } } ) ,
2026-05-01 17:57:49 +09:00
prompt : async ( ) = > {
2026-02-04 15:25:50 +09:00
return { data : { } }
} ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ]
} ) ,
status : async ( ) = > ( { data : { } } ) ,
} ,
}
2026-02-07 14:32:47 +01:00
// No browserProvider passed
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-25 15:02:41 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
2026-01-26 11:53:41 +09:00
agent : "sisyphus" ,
2026-01-25 15:02:41 +09:00
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - request agent-browser skill without browserProvider
2026-01-25 15:02:41 +09:00
const result = await tool . execute (
{
description : "Test missing browserProvider" ,
prompt : "Do something" ,
category : "ultrabrain" ,
run_in_background : false ,
load_skills : [ "agent-browser" ] ,
} ,
toolContext
)
2026-02-19 12:55:04 +09:00
// then - agent-browser skill should NOT resolve without browserProvider
expect ( result ) . toContain ( "Skills not found" )
expect ( result ) . toContain ( "agent-browser" )
2026-01-25 15:02:41 +09:00
} )
2026-01-21 00:06:05 +09:00
} )
2026-01-12 19:26:32 +11:00
describe ( "buildSystemContent" , ( ) = > {
2026-01-09 02:24:43 +09:00
test ( "returns undefined when no skills and no category promptAppend" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const { buildSystemContent } = require ( "./tools" )
2026-02-01 16:47:50 +09:00
// when
2026-01-14 14:45:01 -05:00
const result = buildSystemContent ( { skillContent : undefined , categoryPromptAppend : undefined } )
2026-01-09 02:24:43 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . toBeUndefined ( )
} )
test ( "returns skill content only when skills provided without category" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const { buildSystemContent } = require ( "./tools" )
const skillContent = "You are a playwright expert"
2026-02-01 16:47:50 +09:00
// when
2026-01-09 02:24:43 +09:00
const result = buildSystemContent ( { skillContent , categoryPromptAppend : undefined } )
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . toBe ( skillContent )
} )
test ( "returns category promptAppend only when no skills" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const { buildSystemContent } = require ( "./tools" )
const categoryPromptAppend = "Focus on visual design"
2026-02-01 16:47:50 +09:00
// when
2026-01-09 02:24:43 +09:00
const result = buildSystemContent ( { skillContent : undefined , categoryPromptAppend } )
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . toBe ( categoryPromptAppend )
} )
test ( "combines skill content and category promptAppend with separator" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-09 02:24:43 +09:00
const { buildSystemContent } = require ( "./tools" )
const skillContent = "You are a playwright expert"
const categoryPromptAppend = "Focus on visual design"
2026-02-01 16:47:50 +09:00
// when
2026-01-09 02:24:43 +09:00
const result = buildSystemContent ( { skillContent , categoryPromptAppend } )
2026-02-01 16:47:50 +09:00
// then
2026-01-09 02:24:43 +09:00
expect ( result ) . toContain ( skillContent )
expect ( result ) . toContain ( categoryPromptAppend )
expect ( result ) . toContain ( "\n\n" )
} )
2026-01-26 17:00:06 +09:00
test ( "prepends plan agent system prompt when agentName is 'plan'" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-26 17:00:06 +09:00
const { buildSystemContent } = require ( "./tools" )
2026-02-06 17:31:13 +09:00
const { buildPlanAgentSystemPrepend } = require ( "./constants" )
const availableCategories = [
{
name : "deep" ,
description : "Goal-oriented autonomous problem-solving" ,
model : "openai/gpt-5.3-codex" ,
} ,
]
const availableSkills = [
{
name : "typescript-programmer" ,
description : "Production TypeScript code." ,
location : "plugin" ,
} ,
]
2026-01-26 17:00:06 +09:00
2026-02-01 16:47:50 +09:00
// when
2026-02-06 17:31:13 +09:00
const result = buildSystemContent ( {
agentName : "plan" ,
availableCategories ,
availableSkills ,
} )
2026-01-26 17:00:06 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toContain ( "<system>" )
expect ( result ) . toContain ( "MANDATORY CONTEXT GATHERING PROTOCOL" )
2026-02-06 17:31:13 +09:00
expect ( result ) . toContain ( "### AVAILABLE CATEGORIES" )
expect ( result ) . toContain ( "`deep`" )
expect ( result ) . not . toContain ( "prompt-engineer" )
expect ( result ) . toBe ( buildPlanAgentSystemPrepend ( availableCategories , availableSkills ) )
2026-01-26 17:00:06 +09:00
} )
2026-02-08 13:42:15 +09:00
test ( "does not prepend plan agent prompt for prometheus agent" , ( ) = > {
//#given - prometheus is NOT a plan agent (decoupled)
2026-01-26 17:00:06 +09:00
const { buildSystemContent } = require ( "./tools" )
2026-02-08 13:42:15 +09:00
const skillContent = "You are a strategic planner"
2026-01-26 17:00:06 +09:00
2026-02-08 13:42:15 +09:00
//#when
2026-02-06 17:31:13 +09:00
const result = buildSystemContent ( {
2026-02-08 13:42:15 +09:00
skillContent ,
2026-02-06 17:31:13 +09:00
agentName : "prometheus" ,
} )
2026-01-26 17:00:06 +09:00
2026-02-08 13:42:15 +09:00
//#then - prometheus should NOT get plan agent system prepend
expect ( result ) . toBe ( skillContent )
expect ( result ) . not . toContain ( "MANDATORY CONTEXT GATHERING PROTOCOL" )
2026-01-26 17:00:06 +09:00
} )
2026-02-08 13:42:15 +09:00
test ( "does not prepend plan agent prompt for Prometheus (case insensitive)" , ( ) = > {
//#given - Prometheus (capitalized) is NOT a plan agent
2026-01-26 17:00:06 +09:00
const { buildSystemContent } = require ( "./tools" )
2026-02-08 13:42:15 +09:00
const skillContent = "You are a strategic planner"
2026-01-26 17:00:06 +09:00
2026-02-08 13:42:15 +09:00
//#when
2026-02-06 17:31:13 +09:00
const result = buildSystemContent ( {
2026-02-08 13:42:15 +09:00
skillContent ,
2026-02-06 17:31:13 +09:00
agentName : "Prometheus" ,
} )
2026-01-26 17:00:06 +09:00
2026-02-08 13:42:15 +09:00
//#then
expect ( result ) . toBe ( skillContent )
expect ( result ) . not . toContain ( "MANDATORY CONTEXT GATHERING PROTOCOL" )
2026-01-26 17:00:06 +09:00
} )
test ( "combines plan agent prepend with skill content" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-26 17:00:06 +09:00
const { buildSystemContent } = require ( "./tools" )
2026-02-06 17:31:13 +09:00
const { buildPlanAgentSystemPrepend } = require ( "./constants" )
2026-01-26 17:00:06 +09:00
const skillContent = "You are a planning expert"
2026-02-06 17:31:13 +09:00
const availableCategories = [
{
name : "writing" ,
description : "Documentation, prose, technical writing" ,
2026-02-15 14:00:03 +09:00
model : "kimi-for-coding/k2p5" ,
2026-02-06 17:31:13 +09:00
} ,
]
const availableSkills = [
{
name : "python-programmer" ,
description : "Production Python code." ,
location : "plugin" ,
} ,
]
const planPrepend = buildPlanAgentSystemPrepend ( availableCategories , availableSkills )
2026-02-01 16:47:50 +09:00
// when
2026-02-06 17:31:13 +09:00
const result = buildSystemContent ( {
skillContent ,
agentName : "plan" ,
availableCategories ,
availableSkills ,
} )
2026-01-26 17:00:06 +09:00
2026-02-01 16:47:50 +09:00
// then
2026-02-06 17:31:13 +09:00
expect ( result ) . toContain ( planPrepend )
2026-01-26 17:00:06 +09:00
expect ( result ) . toContain ( skillContent )
2026-02-06 17:31:13 +09:00
expect ( result ! . indexOf ( planPrepend ) ) . toBeLessThan ( result ! . indexOf ( skillContent ) )
2026-01-26 17:00:06 +09:00
} )
test ( "does not prepend plan agent prompt for non-plan agents" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-26 17:00:06 +09:00
const { buildSystemContent } = require ( "./tools" )
const skillContent = "You are an expert"
2026-02-01 16:47:50 +09:00
// when
2026-01-26 17:00:06 +09:00
const result = buildSystemContent ( { skillContent , agentName : "oracle" } )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( skillContent )
expect ( result ) . not . toContain ( "<system>" )
} )
test ( "does not prepend plan agent prompt when agentName is undefined" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-26 17:00:06 +09:00
const { buildSystemContent } = require ( "./tools" )
const skillContent = "You are an expert"
2026-02-01 16:47:50 +09:00
// when
2026-01-26 17:00:06 +09:00
const result = buildSystemContent ( { skillContent , agentName : undefined } )
2026-02-01 16:47:50 +09:00
// then
2026-01-26 17:00:06 +09:00
expect ( result ) . toBe ( skillContent )
expect ( result ) . not . toContain ( "<system>" )
} )
2026-01-09 02:24:43 +09:00
} )
2026-01-14 22:15:05 +07:00
2026-03-06 22:56:51 +09:00
describe ( "buildTaskPrompt" , ( ) = > {
test ( "appends English ULW TDD and commit guidance for plan agent" , ( ) = > {
// given
const { buildTaskPrompt } = require ( "./tools" )
const prompt = "Create a work plan for this feature"
// when
const result = buildTaskPrompt ( prompt , "plan" )
// then
expect ( result ) . toContain ( prompt )
expect ( result ) . toContain ( "Answer in English." )
expect ( result ) . toContain ( "Write the plan in English." )
expect ( result ) . toContain ( "Plan well for ultrawork execution." )
expect ( result ) . toContain ( "Use TDD-oriented planning." )
expect ( result ) . toContain ( "Include a clear atomic commit strategy." )
} )
test ( "does not append plan guidance for non-plan agents" , ( ) = > {
// given
const { buildTaskPrompt } = require ( "./tools" )
const prompt = "Investigate this module"
// when
const result = buildTaskPrompt ( prompt , "explore" )
// then
expect ( result ) . toBe ( prompt )
} )
2026-03-28 09:34:58 -07:00
test ( "excludes TDD line when tddEnabled is false" , ( ) = > {
// given
const { buildTaskPrompt } = require ( "./tools" )
const prompt = "Create a work plan for this feature"
// when
const result = buildTaskPrompt ( prompt , "plan" , false )
// then
expect ( result ) . toContain ( prompt )
expect ( result ) . toContain ( "Answer in English." )
expect ( result ) . toContain ( "Write the plan in English." )
expect ( result ) . toContain ( "Plan well for ultrawork execution." )
expect ( result ) . toContain ( "Include a clear atomic commit strategy." )
expect ( result ) . not . toContain ( "Use TDD-oriented planning." )
} )
test ( "includes TDD line when tddEnabled is true" , ( ) = > {
// given
const { buildTaskPrompt } = require ( "./tools" )
const prompt = "Create a work plan for this feature"
// when
const result = buildTaskPrompt ( prompt , "plan" , true )
// then
expect ( result ) . toContain ( "Use TDD-oriented planning." )
} )
2026-03-06 22:56:51 +09:00
} )
2026-01-14 22:15:05 +07:00
describe ( "modelInfo detection via resolveCategoryConfig" , ( ) = > {
2026-01-20 15:55:09 +09:00
test ( "catalog model is used for category with catalog entry" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given - ultrabrain has catalog entry
2026-01-14 22:15:05 +07:00
const categoryName = "ultrabrain"
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// then - catalog model is used
2026-01-14 22:15:05 +07:00
expect ( resolved ) . not . toBeNull ( )
2026-04-25 00:41:16 +09:00
expect ( resolved ! . config . model ) . toBe ( "openai/gpt-5.5" )
2026-01-20 15:55:09 +09:00
expect ( resolved ! . config . variant ) . toBe ( "xhigh" )
} )
2026-01-20 16:22:53 +09:00
test ( "default model is used for category with default entry" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given - unspecified-low has default model
2026-01-20 16:22:53 +09:00
const categoryName = "unspecified-low"
2026-01-20 15:55:09 +09:00
2026-02-01 16:47:50 +09:00
// when
2026-01-20 15:55:09 +09:00
const resolved = resolveCategoryConfig ( categoryName , { systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-02-01 16:47:50 +09:00
// then - default model from DEFAULT_CATEGORIES is used
2026-01-20 15:55:09 +09:00
expect ( resolved ) . not . toBeNull ( )
2026-02-18 15:51:24 +09:00
expect ( resolved ! . config . model ) . toBe ( "anthropic/claude-sonnet-4-6" )
2026-01-14 22:15:05 +07:00
} )
2026-01-20 17:05:17 +09:00
test ( "category built-in model takes precedence over inheritedModel for builtin category" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given - builtin ultrabrain category with its own model, inherited model also provided
2026-01-14 22:15:05 +07:00
const categoryName = "ultrabrain"
2026-04-17 14:51:52 +09:00
const inheritedModel = "cliproxy/claude-opus-4-7"
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-14 22:15:05 +07:00
2026-04-25 00:41:16 +09:00
// then - category's built-in model wins (ultrabrain uses gpt-5.5)
2026-01-14 22:15:05 +07:00
expect ( resolved ) . not . toBeNull ( )
const actualModel = resolved ! . config . model
2026-04-25 00:41:16 +09:00
expect ( actualModel ) . toBe ( "openai/gpt-5.5" )
2026-01-14 22:15:05 +07:00
} )
2026-01-17 12:51:03 -05:00
test ( "when user defines model - modelInfo should report user-defined regardless of inheritedModel" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given
2026-01-14 22:15:05 +07:00
const categoryName = "ultrabrain"
const userCategories = { "ultrabrain" : { model : "my-provider/custom-model" } }
2026-04-17 14:51:52 +09:00
const inheritedModel = "cliproxy/claude-opus-4-7"
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { userCategories , inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-14 22:15:05 +07:00
2026-02-01 16:47:50 +09:00
// then - actualModel should be userModel, type should be "user-defined"
2026-01-14 22:15:05 +07:00
expect ( resolved ) . not . toBeNull ( )
const actualModel = resolved ! . config . model
const userDefinedModel = userCategories [ categoryName ] ? . model
expect ( actualModel ) . toBe ( userDefinedModel )
expect ( actualModel ) . toBe ( "my-provider/custom-model" )
} )
test ( "detection logic: actualModel comparison correctly identifies source" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given - This test verifies the fix for PR #770 bug
2026-01-17 12:51:03 -05:00
// The bug was: checking `if (inheritedModel)` instead of `if (actualModel === inheritedModel)`
2026-01-14 22:15:05 +07:00
const categoryName = "ultrabrain"
2026-04-17 14:51:52 +09:00
const inheritedModel = "cliproxy/claude-opus-4-7"
2026-01-14 22:15:05 +07:00
const userCategories = { "ultrabrain" : { model : "user/model" } }
2026-02-01 16:47:50 +09:00
// when - user model wins
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { userCategories , inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-01-14 22:15:05 +07:00
const actualModel = resolved ! . config . model
const userDefinedModel = userCategories [ categoryName ] ? . model
2026-02-01 16:47:50 +09:00
// then - detection should compare against actual resolved model
2026-01-14 22:15:05 +07:00
const detectedType = actualModel === userDefinedModel
? "user-defined"
2026-01-17 12:51:03 -05:00
: actualModel === inheritedModel
2026-01-14 22:15:05 +07:00
? "inherited"
2026-01-17 12:51:03 -05:00
: actualModel === SYSTEM_DEFAULT_MODEL
? "system-default"
2026-01-14 22:15:05 +07:00
: undefined
expect ( detectedType ) . toBe ( "user-defined" )
2026-01-17 12:51:03 -05:00
expect ( actualModel ) . not . toBe ( inheritedModel )
2026-01-14 22:15:05 +07:00
} )
2026-01-14 14:45:01 -05:00
2026-01-17 12:51:03 -05:00
// These tests verify the NEW behavior where categories do NOT have default models
2026-01-20 17:05:17 +09:00
test ( "FIXED: category built-in model takes precedence over inheritedModel" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given a builtin category with its own model, and an inherited model from parent
2026-01-20 17:05:17 +09:00
// The CORRECT chain: userConfig?.model ?? categoryBuiltIn ?? systemDefaultModel
2026-01-17 12:51:03 -05:00
const categoryName = "ultrabrain"
2026-04-17 14:51:52 +09:00
const inheritedModel = "anthropic/claude-opus-4-7"
2026-01-17 12:51:03 -05:00
2026-04-25 00:41:16 +09:00
// when category has a built-in model (gpt-5.5 for ultrabrain)
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-02-01 16:47:50 +09:00
// then category's built-in model should be used, NOT inheritedModel
2026-01-17 12:51:03 -05:00
expect ( resolved ) . not . toBeNull ( )
2026-04-25 00:41:16 +09:00
expect ( resolved ! . model ) . toBe ( "openai/gpt-5.5" )
2026-01-17 12:51:03 -05:00
} )
test ( "FIXED: systemDefaultModel is used when no userConfig.model and no inheritedModel" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given a custom category with no default model
2026-01-17 12:51:03 -05:00
const categoryName = "custom-no-default"
2026-04-25 00:41:16 +09:00
const userCategories : Record < string , CategoryConfig > = { "custom-no-default" : { temperature : 0.5 } }
2026-02-18 15:51:24 +09:00
const systemDefaultModel = "anthropic/claude-sonnet-4-6"
2026-01-14 14:45:01 -05:00
2026-02-01 16:47:50 +09:00
// when no inheritedModel is provided, only systemDefaultModel
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , {
userCategories ,
systemDefaultModel
} )
2026-01-14 14:45:01 -05:00
2026-02-01 16:47:50 +09:00
// then systemDefaultModel should be returned
2026-01-14 14:45:01 -05:00
expect ( resolved ) . not . toBeNull ( )
2026-02-18 15:51:24 +09:00
expect ( resolved ! . model ) . toBe ( "anthropic/claude-sonnet-4-6" )
2026-01-14 14:45:01 -05:00
} )
2026-01-17 12:51:03 -05:00
test ( "FIXED: userConfig.model always takes priority over everything" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given userConfig.model is explicitly set
2026-01-17 12:51:03 -05:00
const categoryName = "ultrabrain"
const userCategories = { "ultrabrain" : { model : "custom/user-model" } }
2026-04-17 14:51:52 +09:00
const inheritedModel = "anthropic/claude-opus-4-7"
2026-02-18 15:51:24 +09:00
const systemDefaultModel = "anthropic/claude-sonnet-4-6"
2026-01-17 12:51:03 -05:00
2026-02-01 16:47:50 +09:00
// when resolveCategoryConfig is called with all sources
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , {
userCategories ,
inheritedModel ,
systemDefaultModel
} )
2026-02-01 16:47:50 +09:00
// then userConfig.model should win
2026-01-17 12:51:03 -05:00
expect ( resolved ) . not . toBeNull ( )
expect ( resolved ! . model ) . toBe ( "custom/user-model" )
} )
2026-01-20 17:05:17 +09:00
test ( "FIXED: empty string in userConfig.model is treated as unset and falls back to systemDefault" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given userConfig.model is empty string "" for a custom category (no built-in model)
2026-01-17 12:51:03 -05:00
const categoryName = "custom-empty-model"
const userCategories = { "custom-empty-model" : { model : "" , temperature : 0.3 } }
2026-04-17 14:51:52 +09:00
const inheritedModel = "anthropic/claude-opus-4-7"
2026-01-17 12:51:03 -05:00
2026-02-01 16:47:50 +09:00
// when resolveCategoryConfig is called
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { userCategories , inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-02-01 16:47:50 +09:00
// then should fall back to systemDefaultModel since custom category has no built-in model
2026-01-17 12:51:03 -05:00
expect ( resolved ) . not . toBeNull ( )
2026-01-20 17:05:17 +09:00
expect ( resolved ! . model ) . toBe ( SYSTEM_DEFAULT_MODEL )
2026-01-17 12:51:03 -05:00
} )
2026-01-20 17:05:17 +09:00
test ( "FIXED: undefined userConfig.model falls back to category built-in model" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given user sets a builtin category but leaves model undefined
2026-01-17 12:51:03 -05:00
const categoryName = "visual-engineering"
2026-04-25 00:41:16 +09:00
const userCategories : Record < string , CategoryConfig > = { "visual-engineering" : { temperature : 0.2 } }
2026-04-17 14:51:52 +09:00
const inheritedModel = "anthropic/claude-opus-4-7"
2026-01-17 12:51:03 -05:00
2026-02-01 16:47:50 +09:00
// when resolveCategoryConfig is called
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { userCategories , inheritedModel , systemDefaultModel : SYSTEM_DEFAULT_MODEL } )
2026-02-26 21:01:05 +09:00
// then should use category's built-in model (gemini-3.1-pro for visual-engineering)
2026-01-17 12:51:03 -05:00
expect ( resolved ) . not . toBeNull ( )
2026-02-26 21:01:05 +09:00
expect ( resolved ! . model ) . toBe ( "google/gemini-3.1-pro" )
2026-01-17 12:51:03 -05:00
} )
test ( "systemDefaultModel is used when no other model is available" , ( ) = > {
2026-02-01 16:47:50 +09:00
// given - custom category with no model, but systemDefaultModel is set
2026-01-14 14:45:01 -05:00
const categoryName = "my-custom"
2026-04-25 00:41:16 +09:00
const userCategories : Record < string , CategoryConfig > = { "my-custom" : { temperature : 0.5 } }
2026-02-18 15:51:24 +09:00
const systemDefaultModel = "anthropic/claude-sonnet-4-6"
2026-01-14 14:45:01 -05:00
2026-02-01 16:47:50 +09:00
// when
2026-01-17 12:51:03 -05:00
const resolved = resolveCategoryConfig ( categoryName , { userCategories , systemDefaultModel } )
2026-01-14 14:45:01 -05:00
2026-02-01 16:47:50 +09:00
// then - actualModel should be systemDefaultModel
2026-01-14 14:45:01 -05:00
expect ( resolved ) . not . toBeNull ( )
2026-01-17 12:51:03 -05:00
expect ( resolved ! . model ) . toBe ( systemDefaultModel )
2026-01-14 14:45:01 -05:00
} )
2026-01-14 22:15:05 +07:00
} )
2026-01-28 17:52:19 +09:00
2026-02-08 16:48:52 +09:00
describe ( "plan family mutual delegation block" , ( ) = > {
test ( "plan cannot delegate to plan (self-delegation)" , async ( ) = > {
//#given
2026-01-28 17:52:19 +09:00
const { createDelegateTask } = require ( "./tools" )
const mockClient = {
2026-02-08 13:42:15 +09:00
app : { agents : async ( ) = > ( { data : [ { name : "plan" , mode : "subagent" } ] } ) } ,
2026-02-07 14:32:47 +01:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
2026-02-08 16:48:52 +09:00
session : { get : async ( ) = > ( { data : { directory : "/project" } } ) , create : async ( ) = > ( { data : { id : "s" } } ) , prompt : async ( ) = > ( { data : { } } ) , promptAsync : async ( ) = > ( { data : { } } ) , messages : async ( ) = > ( { data : [ ] } ) , status : async ( ) = > ( { data : { } } ) } ,
2026-02-07 14:32:47 +01:00
}
2026-02-08 16:48:52 +09:00
const tool = createDelegateTask ( { manager : { launch : async ( ) = > ( { } ) } , client : mockClient } )
2026-01-28 17:52:19 +09:00
2026-02-08 16:48:52 +09:00
//#when
2026-01-28 17:52:19 +09:00
const result = await tool . execute (
2026-02-08 16:48:52 +09:00
{ description : "test" , prompt : "Create a plan" , subagent_type : "plan" , run_in_background : false , load_skills : [ ] } ,
{ sessionID : "p" , messageID : "m" , agent : "plan" , abort : new AbortController ( ) . signal }
2026-01-28 17:52:19 +09:00
)
2026-02-08 16:48:52 +09:00
//#then
expect ( result ) . toContain ( "plan-family" )
2026-01-28 17:52:19 +09:00
expect ( result ) . toContain ( "directly" )
} )
2026-02-08 16:48:52 +09:00
test ( "prometheus cannot delegate to plan (cross-blocking)" , async ( ) = > {
//#given
2026-01-28 17:52:19 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-02-08 16:48:52 +09:00
const mockClient = {
2026-02-08 13:42:15 +09:00
app : { agents : async ( ) = > ( { data : [ { name : "plan" , mode : "subagent" } ] } ) } ,
2026-02-07 14:32:47 +01:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
2026-02-08 16:48:52 +09:00
session : { get : async ( ) = > ( { data : { directory : "/project" } } ) , create : async ( ) = > ( { data : { id : "s" } } ) , prompt : async ( ) = > ( { data : { } } ) , promptAsync : async ( ) = > ( { data : { } } ) , messages : async ( ) = > ( { data : [ ] } ) , status : async ( ) = > ( { data : { } } ) } ,
2026-02-07 14:32:47 +01:00
}
2026-02-08 16:48:52 +09:00
const tool = createDelegateTask ( { manager : { launch : async ( ) = > ( { } ) } , client : mockClient } )
2026-01-28 17:52:19 +09:00
2026-02-08 16:48:52 +09:00
//#when
2026-01-28 17:52:19 +09:00
const result = await tool . execute (
2026-02-08 16:48:52 +09:00
{ description : "test" , prompt : "Create a plan" , subagent_type : "plan" , run_in_background : false , load_skills : [ ] } ,
{ sessionID : "p" , messageID : "m" , agent : "prometheus" , abort : new AbortController ( ) . signal }
2026-01-28 17:52:19 +09:00
)
2026-02-08 16:48:52 +09:00
//#then
expect ( result ) . toContain ( "plan-family" )
} )
2026-01-28 17:52:19 +09:00
2026-04-10 15:53:35 +09:00
test ( "prometheus display name cannot delegate to plan (cross-blocking)" , async ( ) = > {
//#given
const { createDelegateTask } = require ( "./tools" )
const mockClient = {
app : { agents : async ( ) = > ( { data : [ { name : "plan" , mode : "subagent" } ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : { get : async ( ) = > ( { data : { directory : "/project" } } ) , create : async ( ) = > ( { data : { id : "s" } } ) , prompt : async ( ) = > ( { data : { } } ) , promptAsync : async ( ) = > ( { data : { } } ) , messages : async ( ) = > ( { data : [ ] } ) , status : async ( ) = > ( { data : { } } ) } ,
}
const tool = createDelegateTask ( { manager : { launch : async ( ) = > ( { } ) } , client : mockClient } )
//#when
const result = await tool . execute (
{ description : "test" , prompt : "Create a plan" , subagent_type : "plan" , run_in_background : false , load_skills : [ ] } ,
{ sessionID : "p" , messageID : "m" , agent : getAgentDisplayName ( "prometheus" ) , abort : new AbortController ( ) . signal }
)
//#then
expect ( result ) . toContain ( "plan-family" )
} )
2026-04-13 10:57:01 +09:00
test ( "plan cannot delegate to prometheus even when it is exposed as a primary agent" , async ( ) = > {
2026-02-08 16:48:52 +09:00
//#given
2026-01-28 17:52:19 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-02-08 16:48:52 +09:00
const mockClient = {
2026-04-13 10:57:01 +09:00
app : { agents : async ( ) = > ( { data : [ { name : "prometheus" , mode : "primary" } ] } ) } ,
2026-02-08 16:48:52 +09:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : { get : async ( ) = > ( { data : { directory : "/project" } } ) , create : async ( ) = > ( { data : { id : "s" } } ) , prompt : async ( ) = > ( { data : { } } ) , promptAsync : async ( ) = > ( { data : { } } ) , messages : async ( ) = > ( { data : [ ] } ) , status : async ( ) = > ( { data : { } } ) } ,
}
const tool = createDelegateTask ( { manager : { launch : async ( ) = > ( { } ) } , client : mockClient } )
2026-01-28 17:52:19 +09:00
2026-02-08 16:48:52 +09:00
//#when
const result = await tool . execute (
{ description : "test" , prompt : "Execute" , subagent_type : "prometheus" , run_in_background : false , load_skills : [ ] } ,
{ sessionID : "p" , messageID : "m" , agent : "plan" , abort : new AbortController ( ) . signal }
)
//#then
expect ( result ) . toContain ( "plan-family" )
} )
test ( "sisyphus CAN delegate to plan (not in plan family)" , async ( ) = > {
//#given
const { createDelegateTask } = require ( "./tools" )
const mockClient = {
app : { agents : async ( ) = > ( { data : [ { name : "plan" , mode : "subagent" } ] } ) } ,
2026-02-07 14:32:47 +01:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
2026-02-08 16:48:52 +09:00
create : async ( ) = > ( { data : { id : "ses_ok" } } ) ,
2026-02-07 14:32:47 +01:00
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
2026-02-08 16:48:52 +09:00
messages : async ( ) = > ( { data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Plan created" } ] } ] } ) ,
status : async ( ) = > ( { data : { "ses_ok" : { type : "idle" } } } ) ,
2026-02-07 14:32:47 +01:00
} ,
}
2026-02-08 16:48:52 +09:00
const tool = createDelegateTask ( { manager : { launch : async ( ) = > ( { } ) } , client : mockClient } )
2026-01-28 17:52:19 +09:00
2026-02-08 16:48:52 +09:00
//#when
const result = await tool . execute (
{ description : "test" , prompt : "Create a plan" , subagent_type : "plan" , run_in_background : false , load_skills : [ ] } ,
{ sessionID : "p" , messageID : "m" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
)
//#then
expect ( result ) . not . toContain ( "plan-family" )
expect ( result ) . toContain ( "Plan created" )
} , { timeout : 20000 } )
2026-01-28 17:52:19 +09:00
} )
2026-01-29 11:27:07 +09:00
describe ( "subagent_type model extraction (issue #1225)" , ( ) = > {
test ( "background mode passes matched agent model to manager.launch" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - agent with model registered, using subagent_type with run_in_background=true
2026-01-29 11:27:07 +09:00
const { createDelegateTask } = require ( "./tools" )
let launchInput : any
const mockManager = {
launch : async ( input : any ) = > {
launchInput = input
return {
id : "task-explore" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_explore_model" ,
2026-01-29 11:27:07 +09:00
description : "Explore task" ,
agent : "explore" ,
status : "running" ,
}
} ,
}
2026-02-07 14:32:47 +01:00
const mockClient = {
app : {
agents : async ( ) = > ( {
data : [
{ name : "explore" , mode : "subagent" , model : { providerID : "anthropic" , modelID : "claude-haiku-4-5" } } ,
] ,
} ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
create : async ( ) = > ( { data : { id : "ses_explore_model" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-29 11:27:07 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - delegating to explore agent via subagent_type
2026-01-29 11:27:07 +09:00
await tool . execute (
{
description : "Explore codebase" ,
prompt : "Find auth patterns" ,
subagent_type : "explore" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - matched agent's model should be passed to manager.launch
2026-01-29 11:27:07 +09:00
expect ( launchInput . model ) . toEqual ( {
providerID : "anthropic" ,
modelID : "claude-haiku-4-5" ,
} )
} )
test ( "sync mode passes matched agent model to session.prompt" , async ( ) = > {
2026-02-01 16:47:50 +09:00
// given - agent with model registered, using subagent_type with run_in_background=false
2026-01-29 11:27:07 +09:00
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
2026-02-07 14:32:47 +01:00
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : {
agents : async ( ) = > ( {
data : [
2026-04-17 14:51:52 +09:00
{ name : "oracle" , mode : "subagent" , model : { providerID : "anthropic" , modelID : "claude-opus-4-7" } } ,
2026-02-07 14:32:47 +01:00
] ,
} ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_oracle_model" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Consultation done" } ] } ] ,
} ) ,
status : async ( ) = > ( { data : { "ses_oracle_model" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-29 11:27:07 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - delegating to oracle agent via subagent_type in sync mode
2026-01-29 11:27:07 +09:00
await tool . execute (
{
description : "Consult oracle" ,
prompt : "Review architecture" ,
subagent_type : "oracle" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
2026-02-01 16:47:50 +09:00
// then - matched agent's model should be passed to session.prompt
2026-01-29 11:27:07 +09:00
expect ( promptBody . model ) . toEqual ( {
providerID : "anthropic" ,
2026-04-17 14:51:52 +09:00
modelID : "claude-opus-4-7" ,
2026-01-29 11:27:07 +09:00
} )
} , { timeout : 20000 } )
2026-02-08 13:14:11 +09:00
test ( "agent without model resolves via fallback chain" , async ( ) = > {
// given - agent registered without model field, fallback chain should resolve
2026-01-29 11:27:07 +09:00
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
2026-02-07 14:32:47 +01:00
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : {
agents : async ( ) = > ( {
data : [
2026-02-08 13:14:11 +09:00
{ name : "explore" , mode : "subagent" } ,
2026-02-07 14:32:47 +01:00
] ,
} ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_no_model_agent" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ] ,
} ) ,
status : async ( ) = > ( { data : { "ses_no_model_agent" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-01-29 11:27:07 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - delegating to agent without model
2026-01-29 11:27:07 +09:00
await tool . execute (
{
description : "Explore without model" ,
prompt : "Find something" ,
subagent_type : "explore" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
2026-02-08 13:14:11 +09:00
// then - model should be resolved via AGENT_MODEL_REQUIREMENTS fallback chain
expect ( promptBody . model ) . toBeDefined ( )
} , { timeout : 20000 } )
test ( "agentOverrides model takes priority over matchedAgent.model (#1357)" , async ( ) = > {
// given - user configured oracle to use a specific model in oh-my-opencode.json
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : {
agents : async ( ) = > ( {
data : [
2026-04-25 00:41:16 +09:00
{ name : "oracle" , mode : "subagent" , model : { providerID : "openai" , modelID : "gpt-5.5" } } ,
2026-02-08 13:14:11 +09:00
] ,
} ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_override_model" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ] ,
} ) ,
status : async ( ) = > ( { data : { "ses_override_model" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
agentOverrides : {
2026-04-17 14:51:52 +09:00
oracle : { model : "anthropic/claude-opus-4-7" } ,
2026-02-08 13:14:11 +09:00
} ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - delegating to oracle via subagent_type with user override
await tool . execute (
{
description : "Consult oracle with override" ,
prompt : "Review architecture" ,
subagent_type : "oracle" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
// then - user-configured model should take priority over matchedAgent.model
expect ( promptBody . model ) . toEqual ( {
providerID : "anthropic" ,
2026-04-17 14:51:52 +09:00
modelID : "claude-opus-4-7" ,
2026-02-08 13:14:11 +09:00
} )
} , { timeout : 20000 } )
test ( "agentOverrides variant is applied when model is overridden (#1357)" , async ( ) = > {
// given - user configured oracle with model and variant
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : {
agents : async ( ) = > ( {
data : [
2026-04-25 00:41:16 +09:00
{ name : "oracle" , mode : "subagent" , model : { providerID : "openai" , modelID : "gpt-5.5" } } ,
2026-02-08 13:14:11 +09:00
] ,
} ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_variant_test" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ] ,
} ) ,
status : async ( ) = > ( { data : { "ses_variant_test" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
agentOverrides : {
2026-04-17 14:51:52 +09:00
oracle : { model : "anthropic/claude-opus-4-7" , variant : "max" } ,
2026-02-08 13:14:11 +09:00
} ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - delegating to oracle via subagent_type with variant override
await tool . execute (
{
description : "Consult oracle with variant" ,
prompt : "Review architecture" ,
subagent_type : "oracle" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
// then - user-configured variant should be applied
expect ( promptBody . variant ) . toBe ( "max" )
} , { timeout : 20000 } )
test ( "fallback chain resolves model when no override and no matchedAgent.model (#1357)" , async ( ) = > {
// given - agent registered without model, no override, but AGENT_MODEL_REQUIREMENTS has fallback
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : {
agents : async ( ) = > ( {
data : [
{ name : "oracle" , mode : "subagent" } , // no model field
] ,
} ) ,
} ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_fallback_test" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Done" } ] } ] ,
} ) ,
status : async ( ) = > ( { data : { "ses_fallback_test" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
// no agentOverrides
2026-02-08 18:41:39 +09:00
connectedProvidersOverride : TEST_CONNECTED_PROVIDERS ,
availableModelsOverride : createTestAvailableModels ( ) ,
2026-02-08 13:14:11 +09:00
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - delegating to oracle with no override and no matchedAgent model
await tool . execute (
{
description : "Consult oracle with fallback" ,
prompt : "Review architecture" ,
subagent_type : "oracle" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
// then - should resolve via AGENT_MODEL_REQUIREMENTS fallback chain for oracle
2026-04-24 16:17:44 +09:00
// oracle fallback chain: gpt-5.5 (openai) > gemini-3.1-pro (google) > claude-opus-4-7 (anthropic)
// Since openai is in connectedProviders, should resolve to openai/gpt-5.5
2026-02-08 13:14:11 +09:00
expect ( promptBody . model ) . toBeDefined ( )
expect ( promptBody . model . providerID ) . toBe ( "openai" )
2026-04-24 16:17:44 +09:00
expect ( promptBody . model . modelID ) . toContain ( "gpt-5.5" )
2026-01-29 11:27:07 +09:00
} , { timeout : 20000 } )
} )
2026-02-08 13:42:15 +09:00
describe ( "subagent task permission" , ( ) = > {
test ( "plan subagent should have task permission enabled" , async ( ) = > {
//#given - sisyphus delegates to plan agent
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const promptMock = async ( input : any ) = > {
promptBody = input . body
return { data : { } }
}
const mockClient = {
app : { agents : async ( ) = > ( { data : [ { name : "plan" , mode : "subagent" } ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_plan_delegate" } } ) ,
prompt : promptMock ,
promptAsync : promptMock ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Plan created" } ] } ]
} ) ,
status : async ( ) = > ( { data : { "ses_plan_delegate" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
//#when - sisyphus delegates to plan
await tool . execute (
{
description : "Test plan task permission" ,
prompt : "Create a plan" ,
subagent_type : "plan" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
//#then - plan agent should have task permission
expect ( promptBody . tools . task ) . toBe ( true )
} , { timeout : 20000 } )
2026-04-13 10:57:01 +09:00
test ( "prometheus primary agent should not be callable via task" , async ( ) = > {
2026-02-08 16:48:52 +09:00
//#given
2026-01-28 17:52:19 +09:00
const { createDelegateTask } = require ( "./tools" )
2026-02-07 14:32:47 +01:00
const mockClient = {
2026-04-13 10:57:01 +09:00
app : { agents : async ( ) = > ( { data : [ { name : "prometheus" , mode : "primary" } ] } ) } ,
2026-02-07 14:32:47 +01:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
2026-02-08 16:48:52 +09:00
create : async ( ) = > ( { data : { id : "ses_prometheus_task" } } ) ,
2026-04-13 10:57:01 +09:00
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
2026-02-08 16:48:52 +09:00
messages : async ( ) = > ( { data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Plan created" } ] } ] } ) ,
status : async ( ) = > ( { data : { "ses_prometheus_task" : { type : "idle" } } } ) ,
2026-02-07 14:32:47 +01:00
} ,
}
2026-02-08 16:48:52 +09:00
const tool = createDelegateTask ( { manager : { launch : async ( ) = > ( { } ) } , client : mockClient } )
2026-01-28 17:52:19 +09:00
2026-02-08 16:48:52 +09:00
//#when
2026-04-13 10:57:01 +09:00
const result = await tool . execute (
2026-02-08 16:48:52 +09:00
{ description : "Test prometheus task permission" , prompt : "Create a plan" , subagent_type : "prometheus" , run_in_background : false , load_skills : [ ] } ,
{ sessionID : "p" , messageID : "m" , agent : "sisyphus" , abort : new AbortController ( ) . signal }
2026-01-28 17:52:19 +09:00
)
2026-04-13 10:57:01 +09:00
//#then
2026-04-13 15:52:44 +09:00
expect ( result ) . toContain ( 'Cannot delegate to primary agent "prometheus" via task. Select that agent directly instead.' )
2026-01-28 17:52:19 +09:00
} , { timeout : 20000 } )
2026-02-08 13:42:15 +09:00
test ( "non-plan subagent should NOT have task permission" , async ( ) = > {
//#given - sisyphus delegates to oracle (non-plan)
2026-01-28 17:52:19 +09:00
const { createDelegateTask } = require ( "./tools" )
let promptBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ { name : "oracle" , mode : "subagent" } ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_oracle_no_delegate" } } ) ,
prompt : async ( input : any ) = > {
promptBody = input . body
return { data : { } }
} ,
2026-02-07 14:32:47 +01:00
promptAsync : async ( input : any ) = > {
promptBody = input . body
return { data : { } }
} ,
2026-01-28 17:52:19 +09:00
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Consultation done" } ] } ]
} ) ,
status : async ( ) = > ( { data : { "ses_oracle_no_delegate" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
2026-02-01 16:47:50 +09:00
// when - sisyphus delegates to oracle
2026-01-28 17:52:19 +09:00
await tool . execute (
{
2026-02-06 16:01:54 +09:00
description : "Test oracle no task permission" ,
2026-01-28 17:52:19 +09:00
prompt : "Consult on architecture" ,
subagent_type : "oracle" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
2026-02-06 16:01:54 +09:00
// then - oracle should NOT have task permission
expect ( promptBody . tools . task ) . toBe ( false )
2026-01-28 17:52:19 +09:00
} , { timeout : 20000 } )
} )
2026-02-01 19:44:22 +09:00
describe ( "session title and metadata format (OpenCode compatibility)" , ( ) = > {
test ( "sync session title follows OpenCode format: '{description} (@{agent} subagent)'" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
let createBody : any
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
2026-02-07 14:32:47 +01:00
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
model : { list : async ( ) = > [ { id : SYSTEM_DEFAULT_MODEL } ] } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( input : any ) = > {
createBody = input . body
return { data : { id : "ses_title_test" } }
} ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "done" } ] } ]
} ) ,
status : async ( ) = > ( { data : { "ses_title_test" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-02-01 19:44:22 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when - sync task with category
await tool . execute (
{
description : "Implement feature X" ,
prompt : "Build the feature" ,
category : "quick" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
// then - title should follow OpenCode format
2026-02-16 21:32:33 +09:00
expect ( createBody . title ) . toBe ( "Implement feature X (@Sisyphus-Junior subagent)" )
2026-02-01 19:44:22 +09:00
} , { timeout : 10000 } )
test ( "sync task output includes <task_metadata> block with session_id" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
2026-02-07 14:32:47 +01:00
const mockManager = { launch : async ( ) = > ( { } ) }
const mockClient = {
app : { agents : async ( ) = > ( { data : [ ] } ) } ,
config : { get : async ( ) = > ( { data : { model : SYSTEM_DEFAULT_MODEL } } ) } ,
model : { list : async ( ) = > [ { id : SYSTEM_DEFAULT_MODEL } ] } ,
session : {
get : async ( ) = > ( { data : { directory : "/project" } } ) ,
create : async ( ) = > ( { data : { id : "ses_metadata_test" } } ) ,
prompt : async ( ) = > ( { data : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( {
data : [ { info : { role : "assistant" } , parts : [ { type : "text" , text : "Task completed" } ] } ]
} ) ,
status : async ( ) = > ( { data : { "ses_metadata_test" : { type : "idle" } } } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
} )
2026-02-01 19:44:22 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when
const result = await tool . execute (
{
description : "Test metadata format" ,
prompt : "Do something" ,
category : "quick" ,
run_in_background : false ,
load_skills : [ ] ,
} ,
toolContext
)
// then - output should contain <task_metadata> block
expect ( result ) . toContain ( "<task_metadata>" )
expect ( result ) . toContain ( "session_id: ses_metadata_test" )
expect ( result ) . toContain ( "</task_metadata>" )
} , { timeout : 10000 } )
test ( "background task output includes <task_metadata> block with session_id" , async ( ) = > {
// given
const { createDelegateTask } = require ( "./tools" )
const mockManager = {
launch : async ( ) = > ( {
id : "bg_meta_test" ,
2026-05-02 03:01:03 +09:00
sessionId : "ses_bg_metadata" ,
2026-02-01 19:44:22 +09:00
description : "Background metadata test" ,
agent : "sisyphus-junior" ,
status : "running" ,
} ) ,
}
2026-02-07 14:32:47 +01:00
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 : { } } ) ,
promptAsync : async ( ) = > ( { data : { } } ) ,
messages : async ( ) = > ( { data : [ ] } ) ,
} ,
}
const tool = createDelegateTask ( {
manager : mockManager ,
client : mockClient ,
userCategories : {
2026-02-18 15:51:24 +09:00
"sisyphus-junior" : { model : "anthropic/claude-sonnet-4-6" } ,
2026-02-07 14:32:47 +01:00
} ,
} )
2026-02-01 19:44:22 +09:00
const toolContext = {
sessionID : "parent-session" ,
messageID : "parent-message" ,
agent : "sisyphus" ,
abort : new AbortController ( ) . signal ,
}
// when
const result = await tool . execute (
{
description : "Background metadata test" ,
prompt : "Do something" ,
category : "quick" ,
run_in_background : true ,
load_skills : [ ] ,
} ,
toolContext
)
// then - output should contain <task_metadata> block
expect ( result ) . toContain ( "<task_metadata>" )
expect ( result ) . toContain ( "session_id: ses_bg_metadata" )
expect ( result ) . toContain ( "</task_metadata>" )
} , { timeout : 10000 } )
} )
2026-01-09 02:24:43 +09:00
} )