@@ -317,6 +317,36 @@ const HEARTBEAT_RULE: LegacyConfigRule = {
317317 "top-level heartbeat is not a valid config path; use agents.defaults.heartbeat (cadence/target/model settings) or channels.defaults.heartbeat (showOk/showAlerts/useIndicator)." ,
318318} ;
319319
320+ const LEGACY_ACP_STREAM_RULES : LegacyConfigRule [ ] = [
321+ {
322+ path : [ "acp" , "stream" , "maxTurnChars" ] ,
323+ message :
324+ "acp.stream.maxTurnChars was renamed; use acp.stream.maxOutputChars instead (auto-migrated on load)." ,
325+ } ,
326+ {
327+ path : [ "acp" , "stream" , "maxToolSummaryChars" ] ,
328+ message :
329+ "acp.stream.maxToolSummaryChars was renamed; use acp.stream.maxSessionUpdateChars instead (auto-migrated on load)." ,
330+ } ,
331+ {
332+ path : [ "acp" , "stream" , "maxStatusChars" ] ,
333+ message : "acp.stream.maxStatusChars was removed with no replacement (auto-removed on load)." ,
334+ } ,
335+ {
336+ path : [ "acp" , "stream" , "maxMetaEventsPerTurn" ] ,
337+ message :
338+ "acp.stream.maxMetaEventsPerTurn was removed with no replacement (auto-removed on load)." ,
339+ } ,
340+ {
341+ path : [ "acp" , "stream" , "metaMode" ] ,
342+ message : "acp.stream.metaMode was removed with no replacement (auto-removed on load)." ,
343+ } ,
344+ {
345+ path : [ "acp" , "stream" , "showUsage" ] ,
346+ message : "acp.stream.showUsage was removed with no replacement (auto-removed on load)." ,
347+ } ,
348+ ] ;
349+
320350const X_SEARCH_RULE : LegacyConfigRule = {
321351 path : [ "tools" , "web" , "x_search" , "apiKey" ] ,
322352 message :
@@ -386,6 +416,68 @@ function migrateLegacySandboxPerSession(
386416 }
387417}
388418
419+ function migrateLegacyAcpStream ( stream : Record < string , unknown > , changes : string [ ] ) : void {
420+ if (
421+ Object . prototype . hasOwnProperty . call ( stream , "maxTurnChars" ) &&
422+ typeof stream . maxTurnChars === "number"
423+ ) {
424+ if ( stream . maxOutputChars === undefined ) {
425+ stream . maxOutputChars = stream . maxTurnChars ;
426+ changes . push ( "Moved acp.stream.maxTurnChars → acp.stream.maxOutputChars." ) ;
427+ } else {
428+ changes . push ( "Removed acp.stream.maxTurnChars (acp.stream.maxOutputChars already set)." ) ;
429+ }
430+ delete stream . maxTurnChars ;
431+ }
432+
433+ if (
434+ Object . prototype . hasOwnProperty . call ( stream , "maxToolSummaryChars" ) &&
435+ typeof stream . maxToolSummaryChars === "number"
436+ ) {
437+ if ( stream . maxSessionUpdateChars === undefined ) {
438+ stream . maxSessionUpdateChars = stream . maxToolSummaryChars ;
439+ changes . push ( "Moved acp.stream.maxToolSummaryChars → acp.stream.maxSessionUpdateChars." ) ;
440+ } else {
441+ changes . push (
442+ "Removed acp.stream.maxToolSummaryChars (acp.stream.maxSessionUpdateChars already set)." ,
443+ ) ;
444+ }
445+ delete stream . maxToolSummaryChars ;
446+ }
447+
448+ if (
449+ Object . prototype . hasOwnProperty . call ( stream , "maxStatusChars" ) &&
450+ typeof stream . maxStatusChars === "number"
451+ ) {
452+ delete stream . maxStatusChars ;
453+ changes . push ( "Removed acp.stream.maxStatusChars (no replacement)." ) ;
454+ }
455+
456+ if (
457+ Object . prototype . hasOwnProperty . call ( stream , "maxMetaEventsPerTurn" ) &&
458+ typeof stream . maxMetaEventsPerTurn === "number"
459+ ) {
460+ delete stream . maxMetaEventsPerTurn ;
461+ changes . push ( "Removed acp.stream.maxMetaEventsPerTurn (no replacement)." ) ;
462+ }
463+
464+ if (
465+ Object . prototype . hasOwnProperty . call ( stream , "metaMode" ) &&
466+ typeof stream . metaMode === "string"
467+ ) {
468+ delete stream . metaMode ;
469+ changes . push ( "Removed acp.stream.metaMode (no replacement)." ) ;
470+ }
471+
472+ if (
473+ Object . prototype . hasOwnProperty . call ( stream , "showUsage" ) &&
474+ typeof stream . showUsage === "boolean"
475+ ) {
476+ delete stream . showUsage ;
477+ changes . push ( "Removed acp.stream.showUsage (no replacement)." ) ;
478+ }
479+ }
480+
389481export const LEGACY_CONFIG_MIGRATIONS_RUNTIME : LegacyConfigMigrationSpec [ ] = [
390482 defineLegacyConfigMigration ( {
391483 id : "agents.sandbox.perSession->scope" ,
@@ -436,6 +528,19 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME: LegacyConfigMigrationSpec[] = [
436528 changes . push ( ...migrated . changes ) ;
437529 } ,
438530 } ) ,
531+ defineLegacyConfigMigration ( {
532+ id : "acp.stream-v2026.3.2-keys" ,
533+ describe : "Migrate removed ACP stream keys from v2026.3.2 to supported config" ,
534+ legacyRules : LEGACY_ACP_STREAM_RULES ,
535+ apply : ( raw , changes ) => {
536+ const acp = getRecord ( raw . acp ) ;
537+ const stream = getRecord ( acp ?. stream ) ;
538+ if ( ! stream ) {
539+ return ;
540+ }
541+ migrateLegacyAcpStream ( stream , changes ) ;
542+ } ,
543+ } ) ,
439544 defineLegacyConfigMigration ( {
440545 // v2026.2.26 added a startup guard requiring gateway.controlUi.allowedOrigins (or the
441546 // host-header fallback flag) for any non-loopback bind. The setup wizard was updated
0 commit comments