@@ -61,11 +61,18 @@ const mocks = vi.hoisted(() => ({
6161 maybeArchiveLegacyClawdBrowserProfileResidue : vi . fn ( ) ,
6262 resolveAgentWorkspaceDir : vi . fn ( ( ) => "/tmp/openclaw-workspace" ) ,
6363 resolveDefaultAgentId : vi . fn ( ( ) => "default" ) ,
64+ resolveAgentContextLimits : vi . fn (
65+ ( cfg : { agents ?: { defaults ?: { contextLimits ?: unknown } } } ) =>
66+ cfg . agents ?. defaults ?. contextLimits ?? { } ,
67+ ) ,
6468 note : vi . fn ( ) ,
6569 loadModelCatalog : vi . fn ( async ( ) => [ ] ) ,
70+ findModelCatalogEntry : vi . fn ( ( ) => ( { contextTokens : 200_000 } ) ) ,
6671 getModelRefStatus : vi . fn ( ( ) => ( { allowed : true , inCatalog : true , key : "openai/gpt-5.5" } ) ) ,
6772 resolveConfiguredModelRef : vi . fn ( ( ) => ( { provider : "openai" , model : "gpt-5.5" } ) ) ,
73+ resolveDefaultModelForAgent : vi . fn ( ( ) => ( { provider : "openai" , model : "gpt-5.5" } ) ) ,
6874 resolveHooksGmailModel : vi . fn ( ( ) => ( { provider : "openai" , model : "gpt-5.5" } ) ) ,
75+ modelKey : vi . fn ( ( provider : string , model : string ) => `${ provider } /${ model } ` ) ,
6976 replaceConfigFile : vi . fn ( ) . mockResolvedValue ( undefined ) ,
7077 readConfigFileSnapshot : vi . fn ( ) . mockResolvedValue ( {
7178 exists : true ,
@@ -243,6 +250,7 @@ vi.mock("../commands/doctor-browser.js", () => ({
243250vi . mock ( "../agents/agent-scope.js" , ( ) => ( {
244251 resolveAgentWorkspaceDir : mocks . resolveAgentWorkspaceDir ,
245252 resolveDefaultAgentId : mocks . resolveDefaultAgentId ,
253+ resolveAgentContextLimits : mocks . resolveAgentContextLimits ,
246254} ) ) ;
247255
248256vi . mock ( "../../packages/terminal-core/src/note.js" , ( ) => ( {
@@ -251,12 +259,15 @@ vi.mock("../../packages/terminal-core/src/note.js", () => ({
251259
252260vi . mock ( "../agents/model-catalog.js" , ( ) => ( {
253261 loadModelCatalog : mocks . loadModelCatalog ,
262+ findModelCatalogEntry : mocks . findModelCatalogEntry ,
254263} ) ) ;
255264
256265vi . mock ( "../agents/model-selection.js" , ( ) => ( {
257266 getModelRefStatus : mocks . getModelRefStatus ,
258267 resolveConfiguredModelRef : mocks . resolveConfiguredModelRef ,
268+ resolveDefaultModelForAgent : mocks . resolveDefaultModelForAgent ,
259269 resolveHooksGmailModel : mocks . resolveHooksGmailModel ,
270+ modelKey : mocks . modelKey ,
260271} ) ) ;
261272
262273vi . mock ( "../version.js" , async ( ) => ( {
@@ -441,9 +452,16 @@ describe("doctor health contributions", () => {
441452 mocks . resolveAgentWorkspaceDir . mockReturnValue ( "/tmp/openclaw-workspace" ) ;
442453 mocks . resolveDefaultAgentId . mockReset ( ) ;
443454 mocks . resolveDefaultAgentId . mockReturnValue ( "default" ) ;
455+ mocks . resolveAgentContextLimits . mockReset ( ) ;
456+ mocks . resolveAgentContextLimits . mockImplementation (
457+ ( cfg : { agents ?: { defaults ?: { contextLimits ?: unknown } } } ) =>
458+ cfg . agents ?. defaults ?. contextLimits ?? { } ,
459+ ) ;
444460 mocks . note . mockReset ( ) ;
445461 mocks . loadModelCatalog . mockReset ( ) ;
446462 mocks . loadModelCatalog . mockResolvedValue ( [ ] ) ;
463+ mocks . findModelCatalogEntry . mockReset ( ) ;
464+ mocks . findModelCatalogEntry . mockReturnValue ( { contextTokens : 200_000 } ) ;
447465 mocks . getModelRefStatus . mockReset ( ) ;
448466 mocks . getModelRefStatus . mockReturnValue ( {
449467 allowed : true ,
@@ -452,8 +470,12 @@ describe("doctor health contributions", () => {
452470 } ) ;
453471 mocks . resolveConfiguredModelRef . mockReset ( ) ;
454472 mocks . resolveConfiguredModelRef . mockReturnValue ( { provider : "openai" , model : "gpt-5.5" } ) ;
473+ mocks . resolveDefaultModelForAgent . mockReset ( ) ;
474+ mocks . resolveDefaultModelForAgent . mockReturnValue ( { provider : "openai" , model : "gpt-5.5" } ) ;
455475 mocks . resolveHooksGmailModel . mockReset ( ) ;
456476 mocks . resolveHooksGmailModel . mockReturnValue ( { provider : "openai" , model : "gpt-5.5" } ) ;
477+ mocks . modelKey . mockReset ( ) ;
478+ mocks . modelKey . mockImplementation ( ( provider : string , model : string ) => `${ provider } /${ model } ` ) ;
457479 mocks . readConfigFileSnapshot . mockReset ( ) ;
458480 mocks . readConfigFileSnapshot . mockResolvedValue ( {
459481 exists : true ,
@@ -1191,9 +1213,103 @@ describe("doctor health contributions", () => {
11911213 expect ( contributionIds ) . toContain ( "core/doctor/configured-plugin-installs" ) ;
11921214 expect ( contributionIds ) . toContain ( "core/doctor/device-pairing" ) ;
11931215 expect ( contributionIds ) . toContain ( "core/doctor/channel-plugin-blockers" ) ;
1216+ expect ( contributionIds ) . toContain ( "core/doctor/tool-result-cap" ) ;
11941217 expect ( contributionChecks . map ( ( check ) => check . id ) ) . toEqual ( contributionIds ) ;
11951218 } ) ;
11961219
1220+ it ( "keeps tool result cap opt-in for default lint selection" , async ( ) => {
1221+ const contributionChecks = await resolveDoctorContributionHealthChecks ( ) ;
1222+ const toolResultCapCheck = contributionChecks . find (
1223+ ( check ) => check . id === "core/doctor/tool-result-cap" ,
1224+ ) ;
1225+ expect ( toolResultCapCheck ) . toMatchObject ( { defaultEnabled : false } ) ;
1226+ expect ( toolResultCapCheck ) . toBeDefined ( ) ;
1227+
1228+ const ctx = {
1229+ cfg : {
1230+ agents : {
1231+ defaults : { contextLimits : { toolResultMaxChars : 16_000 } } ,
1232+ } ,
1233+ } ,
1234+ mode : "lint" ,
1235+ runtime : { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ,
1236+ } as const ;
1237+ const checks = [ toolResultCapCheck ! ] ;
1238+
1239+ await expect ( runDoctorLintChecks ( ctx , { checks } ) ) . resolves . toMatchObject ( {
1240+ checksRun : 0 ,
1241+ checksSkipped : 1 ,
1242+ } ) ;
1243+ await expect (
1244+ runDoctorLintChecks ( ctx , { checks, includeAllChecks : true } ) ,
1245+ ) . resolves . toMatchObject ( {
1246+ checksRun : 1 ,
1247+ checksSkipped : 0 ,
1248+ findings : [
1249+ expect . objectContaining ( {
1250+ checkId : "core/doctor/tool-result-cap" ,
1251+ path : "agents.defaults.contextLimits.toolResultMaxChars" ,
1252+ } ) ,
1253+ ] ,
1254+ } ) ;
1255+ await expect (
1256+ runDoctorLintChecks ( ctx , { checks, onlyIds : [ "core/doctor/tool-result-cap" ] } ) ,
1257+ ) . resolves . toMatchObject ( {
1258+ checksRun : 1 ,
1259+ checksSkipped : 0 ,
1260+ } ) ;
1261+ } ) ;
1262+
1263+ it ( "reports agent findings for inherited default tool result caps" , async ( ) => {
1264+ const contributionChecks = await resolveDoctorContributionHealthChecks ( ) ;
1265+ const toolResultCapCheck = contributionChecks . find (
1266+ ( check ) => check . id === "core/doctor/tool-result-cap" ,
1267+ ) ;
1268+ expect ( toolResultCapCheck ) . toBeDefined ( ) ;
1269+
1270+ mocks . resolveAgentContextLimits . mockImplementation (
1271+ ( cfg : { agents ?: { defaults ?: { contextLimits ?: unknown } } } ) =>
1272+ cfg . agents ?. defaults ?. contextLimits ?? { } ,
1273+ ) ;
1274+ mocks . resolveDefaultModelForAgent . mockImplementation ( ( ...args : unknown [ ] ) => {
1275+ const params = args [ 0 ] as { agentId ?: string } ;
1276+ return params . agentId === "writer"
1277+ ? { provider : "openai" , model : "gpt-5.5" }
1278+ : { provider : "local" , model : "tiny" } ;
1279+ } ) ;
1280+ mocks . findModelCatalogEntry . mockImplementation ( ( ...args : unknown [ ] ) => {
1281+ const params = args [ 1 ] as { modelId ?: string } ;
1282+ return params . modelId === "gpt-5.5" ? { contextTokens : 200_000 } : { contextTokens : 8_000 } ;
1283+ } ) ;
1284+
1285+ const ctx = {
1286+ cfg : {
1287+ agents : {
1288+ defaults : { contextLimits : { toolResultMaxChars : 16_000 } } ,
1289+ list : [ { id : "writer" } ] ,
1290+ } ,
1291+ } ,
1292+ mode : "lint" as const ,
1293+ runtime : { log : vi . fn ( ) , error : vi . fn ( ) , exit : vi . fn ( ) } ,
1294+ } ;
1295+
1296+ await expect (
1297+ runDoctorLintChecks ( ctx , {
1298+ checks : [ toolResultCapCheck ! ] ,
1299+ onlyIds : [ "core/doctor/tool-result-cap" ] ,
1300+ } ) ,
1301+ ) . resolves . toMatchObject ( {
1302+ checksRun : 1 ,
1303+ findings : expect . arrayContaining ( [
1304+ expect . objectContaining ( {
1305+ checkId : "core/doctor/tool-result-cap" ,
1306+ path : "agents.defaults.contextLimits.toolResultMaxChars" ,
1307+ target : "agents.list.writer" ,
1308+ } ) ,
1309+ ] ) ,
1310+ } ) ;
1311+ } ) ;
1312+
11971313 it ( "keeps state integrity opt-in for default lint selection" , async ( ) => {
11981314 const contributionChecks = await resolveDoctorContributionHealthChecks ( ) ;
11991315 const stateIntegrityCheck = contributionChecks . find (
0 commit comments