@caupulican/pi-adaptative 0.81.26 → 0.81.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli/args.d.ts.map +1 -1
  3. package/dist/cli/args.js +3 -2
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/core/agent-session-services.d.ts +4 -0
  6. package/dist/core/agent-session-services.d.ts.map +1 -1
  7. package/dist/core/agent-session-services.js +19 -2
  8. package/dist/core/agent-session-services.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +21 -10
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +71 -33
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  14. package/dist/core/autonomy/bounded-completion.js +59 -19
  15. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  16. package/dist/core/autonomy/lane-tool-surface.d.ts +31 -0
  17. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -0
  18. package/dist/core/autonomy/lane-tool-surface.js +96 -0
  19. package/dist/core/autonomy/lane-tool-surface.js.map +1 -0
  20. package/dist/core/background-lane-controller.d.ts +6 -3
  21. package/dist/core/background-lane-controller.d.ts.map +1 -1
  22. package/dist/core/background-lane-controller.js +134 -25
  23. package/dist/core/background-lane-controller.js.map +1 -1
  24. package/dist/core/billing-failover-controller.d.ts.map +1 -1
  25. package/dist/core/billing-failover-controller.js +1 -1
  26. package/dist/core/billing-failover-controller.js.map +1 -1
  27. package/dist/core/context/current-work-memory.d.ts +9 -0
  28. package/dist/core/context/current-work-memory.d.ts.map +1 -0
  29. package/dist/core/context/current-work-memory.js +61 -0
  30. package/dist/core/context/current-work-memory.js.map +1 -0
  31. package/dist/core/context/file-store-memory-provider.d.ts +9 -0
  32. package/dist/core/context/file-store-memory-provider.d.ts.map +1 -0
  33. package/dist/core/context/file-store-memory-provider.js +106 -0
  34. package/dist/core/context/file-store-memory-provider.js.map +1 -0
  35. package/dist/core/context/long-term-memory-trigger.d.ts +15 -0
  36. package/dist/core/context/long-term-memory-trigger.d.ts.map +1 -0
  37. package/dist/core/context/long-term-memory-trigger.js +39 -0
  38. package/dist/core/context/long-term-memory-trigger.js.map +1 -0
  39. package/dist/core/context/memory-prompt-block.d.ts +3 -1
  40. package/dist/core/context/memory-prompt-block.d.ts.map +1 -1
  41. package/dist/core/context/memory-prompt-block.js +20 -3
  42. package/dist/core/context/memory-prompt-block.js.map +1 -1
  43. package/dist/core/context/memory-prompt-budget.d.ts +17 -0
  44. package/dist/core/context/memory-prompt-budget.d.ts.map +1 -0
  45. package/dist/core/context/memory-prompt-budget.js +64 -0
  46. package/dist/core/context/memory-prompt-budget.js.map +1 -0
  47. package/dist/core/context/memory-provider-contract.d.ts.map +1 -1
  48. package/dist/core/context/memory-provider-contract.js +2 -5
  49. package/dist/core/context/memory-provider-contract.js.map +1 -1
  50. package/dist/core/context/memory-tier-composer.d.ts +27 -0
  51. package/dist/core/context/memory-tier-composer.d.ts.map +1 -0
  52. package/dist/core/context/memory-tier-composer.js +79 -0
  53. package/dist/core/context/memory-tier-composer.js.map +1 -0
  54. package/dist/core/context-gc.d.ts.map +1 -1
  55. package/dist/core/context-gc.js +1 -0
  56. package/dist/core/context-gc.js.map +1 -1
  57. package/dist/core/cost/cost-summary.d.ts +3 -1
  58. package/dist/core/cost/cost-summary.d.ts.map +1 -1
  59. package/dist/core/cost/cost-summary.js +3 -3
  60. package/dist/core/cost/cost-summary.js.map +1 -1
  61. package/dist/core/cost-guard.d.ts +17 -7
  62. package/dist/core/cost-guard.d.ts.map +1 -1
  63. package/dist/core/cost-guard.js +33 -10
  64. package/dist/core/cost-guard.js.map +1 -1
  65. package/dist/core/default-tool-surface.d.ts +9 -0
  66. package/dist/core/default-tool-surface.d.ts.map +1 -0
  67. package/dist/core/default-tool-surface.js +18 -0
  68. package/dist/core/default-tool-surface.js.map +1 -0
  69. package/dist/core/delegation/worker-runner.d.ts +11 -5
  70. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  71. package/dist/core/delegation/worker-runner.js +24 -12
  72. package/dist/core/delegation/worker-runner.js.map +1 -1
  73. package/dist/core/extensions/loader.d.ts.map +1 -1
  74. package/dist/core/extensions/loader.js +15 -0
  75. package/dist/core/extensions/loader.js.map +1 -1
  76. package/dist/core/extensions/runner.d.ts +2 -0
  77. package/dist/core/extensions/runner.d.ts.map +1 -1
  78. package/dist/core/extensions/runner.js +7 -0
  79. package/dist/core/extensions/runner.js.map +1 -1
  80. package/dist/core/extensions/types.d.ts +9 -0
  81. package/dist/core/extensions/types.d.ts.map +1 -1
  82. package/dist/core/extensions/types.js.map +1 -1
  83. package/dist/core/failure-corpus.d.ts.map +1 -1
  84. package/dist/core/failure-corpus.js +2 -4
  85. package/dist/core/failure-corpus.js.map +1 -1
  86. package/dist/core/footer-data-provider.d.ts.map +1 -1
  87. package/dist/core/footer-data-provider.js +9 -1
  88. package/dist/core/footer-data-provider.js.map +1 -1
  89. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  90. package/dist/core/goals/goal-continuation-prompt.js +4 -3
  91. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  92. package/dist/core/http-dispatcher.d.ts +12 -0
  93. package/dist/core/http-dispatcher.d.ts.map +1 -1
  94. package/dist/core/http-dispatcher.js +42 -4
  95. package/dist/core/http-dispatcher.js.map +1 -1
  96. package/dist/core/memory/memory-manager.d.ts +8 -3
  97. package/dist/core/memory/memory-manager.d.ts.map +1 -1
  98. package/dist/core/memory/memory-manager.js +27 -11
  99. package/dist/core/memory/memory-manager.js.map +1 -1
  100. package/dist/core/memory/memory-provider.d.ts +5 -1
  101. package/dist/core/memory/memory-provider.d.ts.map +1 -1
  102. package/dist/core/memory/memory-provider.js.map +1 -1
  103. package/dist/core/memory/providers/file-store.d.ts +3 -1
  104. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  105. package/dist/core/memory/providers/file-store.js +7 -2
  106. package/dist/core/memory/providers/file-store.js.map +1 -1
  107. package/dist/core/memory/providers/transcript-recall.d.ts +1 -0
  108. package/dist/core/memory/providers/transcript-recall.d.ts.map +1 -1
  109. package/dist/core/memory/providers/transcript-recall.js +4 -7
  110. package/dist/core/memory/providers/transcript-recall.js.map +1 -1
  111. package/dist/core/memory-controller.d.ts +36 -10
  112. package/dist/core/memory-controller.d.ts.map +1 -1
  113. package/dist/core/memory-controller.js +137 -21
  114. package/dist/core/memory-controller.js.map +1 -1
  115. package/dist/core/model-registry.d.ts +23 -0
  116. package/dist/core/model-registry.d.ts.map +1 -1
  117. package/dist/core/model-registry.js +87 -8
  118. package/dist/core/model-registry.js.map +1 -1
  119. package/dist/core/model-resolver.d.ts.map +1 -1
  120. package/dist/core/model-resolver.js +30 -11
  121. package/dist/core/model-resolver.js.map +1 -1
  122. package/dist/core/model-router-controller.d.ts.map +1 -1
  123. package/dist/core/model-router-controller.js +33 -29
  124. package/dist/core/model-router-controller.js.map +1 -1
  125. package/dist/core/model-selection-controller.d.ts +3 -0
  126. package/dist/core/model-selection-controller.d.ts.map +1 -1
  127. package/dist/core/model-selection-controller.js +8 -1
  128. package/dist/core/model-selection-controller.js.map +1 -1
  129. package/dist/core/models/perf-profile.d.ts.map +1 -1
  130. package/dist/core/models/perf-profile.js +95 -33
  131. package/dist/core/models/perf-profile.js.map +1 -1
  132. package/dist/core/profile-filter-controller.d.ts +11 -0
  133. package/dist/core/profile-filter-controller.d.ts.map +1 -1
  134. package/dist/core/profile-filter-controller.js +56 -11
  135. package/dist/core/profile-filter-controller.js.map +1 -1
  136. package/dist/core/profile-registry.d.ts +2 -2
  137. package/dist/core/profile-registry.d.ts.map +1 -1
  138. package/dist/core/profile-registry.js +58 -7
  139. package/dist/core/profile-registry.js.map +1 -1
  140. package/dist/core/reflection-controller.d.ts +7 -7
  141. package/dist/core/reflection-controller.d.ts.map +1 -1
  142. package/dist/core/reflection-controller.js +78 -12
  143. package/dist/core/reflection-controller.js.map +1 -1
  144. package/dist/core/research/research-runner.d.ts +2 -2
  145. package/dist/core/research/research-runner.d.ts.map +1 -1
  146. package/dist/core/research/research-runner.js +8 -6
  147. package/dist/core/research/research-runner.js.map +1 -1
  148. package/dist/core/resource-loader.d.ts +4 -2
  149. package/dist/core/resource-loader.d.ts.map +1 -1
  150. package/dist/core/resource-loader.js +11 -1
  151. package/dist/core/resource-loader.js.map +1 -1
  152. package/dist/core/runtime-builder.d.ts +13 -1
  153. package/dist/core/runtime-builder.d.ts.map +1 -1
  154. package/dist/core/runtime-builder.js +127 -35
  155. package/dist/core/runtime-builder.js.map +1 -1
  156. package/dist/core/sdk.d.ts +8 -7
  157. package/dist/core/sdk.d.ts.map +1 -1
  158. package/dist/core/sdk.js +35 -5
  159. package/dist/core/sdk.js.map +1 -1
  160. package/dist/core/security/secret-text.d.ts +3 -0
  161. package/dist/core/security/secret-text.d.ts.map +1 -0
  162. package/dist/core/security/secret-text.js +26 -0
  163. package/dist/core/security/secret-text.js.map +1 -0
  164. package/dist/core/settings-manager.d.ts +59 -17
  165. package/dist/core/settings-manager.d.ts.map +1 -1
  166. package/dist/core/settings-manager.js +300 -51
  167. package/dist/core/settings-manager.js.map +1 -1
  168. package/dist/core/skills.d.ts.map +1 -1
  169. package/dist/core/skills.js +1 -1
  170. package/dist/core/skills.js.map +1 -1
  171. package/dist/core/system-prompt-builder.d.ts +7 -0
  172. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  173. package/dist/core/system-prompt-builder.js +23 -1
  174. package/dist/core/system-prompt-builder.js.map +1 -1
  175. package/dist/core/tools/delegate.d.ts.map +1 -1
  176. package/dist/core/tools/delegate.js +15 -11
  177. package/dist/core/tools/delegate.js.map +1 -1
  178. package/dist/main.d.ts.map +1 -1
  179. package/dist/main.js +2 -0
  180. package/dist/main.js.map +1 -1
  181. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  182. package/dist/modes/interactive/components/footer.js +8 -1
  183. package/dist/modes/interactive/components/footer.js.map +1 -1
  184. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  185. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  186. package/dist/modes/interactive/components/settings-selector.js +69 -29
  187. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  188. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  189. package/dist/modes/interactive/components/thinking-selector.js +2 -0
  190. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  191. package/dist/modes/interactive/config-backup.d.ts +7 -1
  192. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  193. package/dist/modes/interactive/config-backup.js +196 -32
  194. package/dist/modes/interactive/config-backup.js.map +1 -1
  195. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  196. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  197. package/dist/modes/interactive/interactive-mode.js +10 -28
  198. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  199. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  200. package/dist/modes/interactive/local-model-commands.js +2 -0
  201. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  202. package/dist/modes/interactive/profile-menu-controller.d.ts +6 -4
  203. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  204. package/dist/modes/interactive/profile-menu-controller.js +248 -65
  205. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  206. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  207. package/dist/modes/interactive/report-commands.js +8 -2
  208. package/dist/modes/interactive/report-commands.js.map +1 -1
  209. package/dist/modes/interactive/settings-selector-flow.d.ts.map +1 -1
  210. package/dist/modes/interactive/settings-selector-flow.js +8 -0
  211. package/dist/modes/interactive/settings-selector-flow.js.map +1 -1
  212. package/dist/modes/interactive/theme/theme-schema.json +1 -1
  213. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  214. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  215. package/dist/modes/interactive/theme/theme.js +3 -1
  216. package/dist/modes/interactive/theme/theme.js.map +1 -1
  217. package/docs/extensions.md +32 -0
  218. package/docs/resources.md +15 -7
  219. package/docs/settings.md +54 -5
  220. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  221. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  222. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  223. package/examples/extensions/preset.ts +2 -2
  224. package/examples/extensions/sandbox/package-lock.json +2 -2
  225. package/examples/extensions/sandbox/package.json +1 -1
  226. package/examples/extensions/with-deps/package-lock.json +2 -2
  227. package/examples/extensions/with-deps/package.json +1 -1
  228. package/npm-shrinkwrap.json +44 -35
  229. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"thinking-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/thinking-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAmB,UAAU,EAAgC,MAAM,oBAAoB,CAAC;AAkB1G;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,UAAU,CAAa;IAE/B,YACC,YAAY,EAAE,aAAa,EAC3B,eAAe,EAAE,aAAa,EAAE,EAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACxC,QAAQ,EAAE,MAAM,IAAI,EAuCpB;IAED,aAAa,IAAI,UAAU,CAE1B;CACD","sourcesContent":["import type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { Container, type SelectItem, SelectList, type SelectListLayoutOptions } from \"@caupulican/pi-tui\";\nimport { getSelectListTheme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\n\nconst THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: ThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: ThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingLevels: SelectItem[] = availableLevels.map((level) => ({\n\t\t\tvalue: level,\n\t\t\tlabel: level,\n\t\t\tdescription: LEVEL_DESCRIPTIONS[level],\n\t\t}));\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingLevels,\n\t\t\tthinkingLevels.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tTHINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Preselect current level\n\t\tconst currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as ThinkingLevel);\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"thinking-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/thinking-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAmB,UAAU,EAAgC,MAAM,oBAAoB,CAAC;AAoB1G;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,UAAU,CAAa;IAE/B,YACC,YAAY,EAAE,aAAa,EAC3B,eAAe,EAAE,aAAa,EAAE,EAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EACxC,QAAQ,EAAE,MAAM,IAAI,EAuCpB;IAED,aAAa,IAAI,UAAU,CAE1B;CACD","sourcesContent":["import type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { Container, type SelectItem, SelectList, type SelectListLayoutOptions } from \"@caupulican/pi-tui\";\nimport { getSelectListTheme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\n\nconst THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n\tmax: \"Maximum reasoning depth for the hardest problems\",\n\tultra: \"Maximum reasoning with reinforced proactive delegation\",\n};\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: ThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: ThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingLevels: SelectItem[] = availableLevels.map((level) => ({\n\t\t\tvalue: level,\n\t\t\tlabel: level,\n\t\t\tdescription: LEVEL_DESCRIPTIONS[level],\n\t\t}));\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingLevels,\n\t\t\tthinkingLevels.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tTHINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Preselect current level\n\t\tconst currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as ThinkingLevel);\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
@@ -12,6 +12,8 @@ const LEVEL_DESCRIPTIONS = {
12
12
  medium: "Moderate reasoning (~8k tokens)",
13
13
  high: "Deep reasoning (~16k tokens)",
14
14
  xhigh: "Maximum reasoning (~32k tokens)",
15
+ max: "Maximum reasoning depth for the hardest problems",
16
+ ultra: "Maximum reasoning with reinforced proactive delegation",
15
17
  };
16
18
  /**
17
19
  * Component that renders a thinking level selector with borders
@@ -1 +1 @@
1
- {"version":3,"file":"thinking-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/thinking-selector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAmB,UAAU,EAAgC,MAAM,oBAAoB,CAAC;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,2BAA2B,GAA4B;IAC5D,qBAAqB,EAAE,EAAE;IACzB,qBAAqB,EAAE,EAAE;CACzB,CAAC;AAEF,MAAM,kBAAkB,GAAkC;IACzD,GAAG,EAAE,cAAc;IACnB,OAAO,EAAE,mCAAmC;IAC5C,GAAG,EAAE,8BAA8B;IACnC,MAAM,EAAE,iCAAiC;IACzC,IAAI,EAAE,8BAA8B;IACpC,KAAK,EAAE,iCAAiC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,OAAO,yBAA0B,SAAQ,SAAS;IAC/C,UAAU,CAAa;IAE/B,YACC,YAA2B,EAC3B,eAAgC,EAChC,QAAwC,EACxC,QAAoB,EACnB;QACD,KAAK,EAAE,CAAC;QAER,MAAM,cAAc,GAAiB,eAAe,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACpE,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,KAAK;YACZ,WAAW,EAAE,kBAAkB,CAAC,KAAK,CAAC;SACtC,CAAC,CAAC,CAAC;QAEJ,iBAAiB;QACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QAEnC,kBAAkB;QAClB,IAAI,CAAC,UAAU,GAAG,IAAI,UAAU,CAC/B,cAAc,EACd,cAAc,CAAC,MAAM,EACrB,kBAAkB,EAAE,EACpB,2BAA2B,CAC3B,CAAC;QAEF,0BAA0B;QAC1B,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC;QACrF,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CAAC,KAAsB,CAAC,CAAC;QAAA,CACtC,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YAChC,QAAQ,EAAE,CAAC;QAAA,CACX,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/B,oBAAoB;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;IAAA,CACnC;IAED,aAAa,GAAe;QAC3B,OAAO,IAAI,CAAC,UAAU,CAAC;IAAA,CACvB;CACD","sourcesContent":["import type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { Container, type SelectItem, SelectList, type SelectListLayoutOptions } from \"@caupulican/pi-tui\";\nimport { getSelectListTheme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\n\nconst THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: ThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: ThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingLevels: SelectItem[] = availableLevels.map((level) => ({\n\t\t\tvalue: level,\n\t\t\tlabel: level,\n\t\t\tdescription: LEVEL_DESCRIPTIONS[level],\n\t\t}));\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingLevels,\n\t\t\tthinkingLevels.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tTHINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Preselect current level\n\t\tconst currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as ThinkingLevel);\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"thinking-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/thinking-selector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAmB,UAAU,EAAgC,MAAM,oBAAoB,CAAC;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,2BAA2B,GAA4B;IAC5D,qBAAqB,EAAE,EAAE;IACzB,qBAAqB,EAAE,EAAE;CACzB,CAAC;AAEF,MAAM,kBAAkB,GAAkC;IACzD,GAAG,EAAE,cAAc;IACnB,OAAO,EAAE,mCAAmC;IAC5C,GAAG,EAAE,8BAA8B;IACnC,MAAM,EAAE,iCAAiC;IACzC,IAAI,EAAE,8BAA8B;IACpC,KAAK,EAAE,iCAAiC;IACxC,GAAG,EAAE,kDAAkD;IACvD,KAAK,EAAE,wDAAwD;CAC/D,CAAC;AAEF;;GAEG;AACH,MAAM,OAAO,yBAA0B,SAAQ,SAAS;IAC/C,UAAU,CAAa;IAE/B,YACC,YAA2B,EAC3B,eAAgC,EAChC,QAAwC,EACxC,QAAoB,EACnB;QACD,KAAK,EAAE,CAAC;QAER,MAAM,cAAc,GAAiB,eAAe,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACpE,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,KAAK;YACZ,WAAW,EAAE,kBAAkB,CAAC,KAAK,CAAC;SACtC,CAAC,CAAC,CAAC;QAEJ,iBAAiB;QACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QAEnC,kBAAkB;QAClB,IAAI,CAAC,UAAU,GAAG,IAAI,UAAU,CAC/B,cAAc,EACd,cAAc,CAAC,MAAM,EACrB,kBAAkB,EAAE,EACpB,2BAA2B,CAC3B,CAAC;QAEF,0BAA0B;QAC1B,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC;QACrF,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CAAC,KAAsB,CAAC,CAAC;QAAA,CACtC,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YAChC,QAAQ,EAAE,CAAC;QAAA,CACX,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/B,oBAAoB;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;IAAA,CACnC;IAED,aAAa,GAAe;QAC3B,OAAO,IAAI,CAAC,UAAU,CAAC;IAAA,CACvB;CACD","sourcesContent":["import type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport { Container, type SelectItem, SelectList, type SelectListLayoutOptions } from \"@caupulican/pi-tui\";\nimport { getSelectListTheme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\n\nconst THINKING_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst LEVEL_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n\tmax: \"Maximum reasoning depth for the hardest problems\",\n\tultra: \"Maximum reasoning with reinforced proactive delegation\",\n};\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\tcurrentLevel: ThinkingLevel,\n\t\tavailableLevels: ThinkingLevel[],\n\t\tonSelect: (level: ThinkingLevel) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst thinkingLevels: SelectItem[] = availableLevels.map((level) => ({\n\t\t\tvalue: level,\n\t\t\tlabel: level,\n\t\t\tdescription: LEVEL_DESCRIPTIONS[level],\n\t\t}));\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(\n\t\t\tthinkingLevels,\n\t\t\tthinkingLevels.length,\n\t\t\tgetSelectListTheme(),\n\t\t\tTHINKING_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Preselect current level\n\t\tconst currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as ThinkingLevel);\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
@@ -20,8 +20,14 @@ export interface ConfigRestoreHost extends ConfigBackupHost {
20
20
  component: Component;
21
21
  focus: Component;
22
22
  }): void;
23
- handleReloadCommand(): Promise<void>;
23
+ handleReloadCommand(): Promise<boolean>;
24
24
  }
25
+ export interface ProfileFilesSnapshot {
26
+ directoryExisted: boolean;
27
+ files: Map<string, string>;
28
+ }
29
+ export declare function captureProfileFiles(profilesDir: string): ProfileFilesSnapshot;
30
+ export declare function restoreProfileFiles(profilesDir: string, snapshot: ProfileFilesSnapshot): void;
25
31
  export declare function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void>;
26
32
  export declare function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void>;
27
33
  //# sourceMappingURL=config-backup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-backup.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGtE,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IAC1D,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDvG;AAED,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkGxG","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\thandleReloadCommand(): Promise<void>;\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profiles: Record<string, any> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst backupData = {\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: host.settingsManager.settings.resourceProfiles,\n\t\t\t\tactiveResourceProfile: host.settingsManager.settings.activeResourceProfile,\n\t\t\t\texternalResourceRoots: host.settingsManager.settings.externalResourceRoots,\n\t\t\t\ttrustedResourceRoots: host.settingsManager.settings.trustedResourceRoots,\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = path.join(getAgentDir(), \"backups\");\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: any;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!bundle || typeof bundle !== \"object\") {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn { component: submenu, focus: submenu.getSelectList() };\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\t// 1. Restore profile files (reusable-file scope)\n\t\tif (bundle.profiles && typeof bundle.profiles === \"object\") {\n\t\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\t\tfs.mkdirSync(profilesDir, { recursive: true });\n\t\t\tfor (const [filename, content] of Object.entries(bundle.profiles)) {\n\t\t\t\tconst targetPath = path.join(profilesDir, filename);\n\t\t\t\tfs.writeFileSync(targetPath, JSON.stringify(content, null, 2), \"utf-8\");\n\t\t\t}\n\t\t}\n\n\t\t// 2. Restore settings\n\t\tif (bundle.settings && typeof bundle.settings === \"object\") {\n\t\t\tconst bs = bundle.settings;\n\n\t\t\t// Global profiles definitions\n\t\t\tif (bs.resourceProfiles && typeof bs.resourceProfiles === \"object\") {\n\t\t\t\tfor (const [name, definition] of Object.entries(bs.resourceProfiles)) {\n\t\t\t\t\thost.settingsManager.setProfileDefinition(name, definition as any, \"global\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Active profile selection\n\t\t\tif (bs.activeResourceProfile) {\n\t\t\t\thost.settingsManager.setActiveProfile(bs.activeResourceProfile, \"global\");\n\t\t\t}\n\n\t\t\t// External roots (trustedRoots are NOT restored, as per SECURITY requirement)\n\t\t\tif (Array.isArray(bs.externalResourceRoots)) {\n\t\t\t\thost.settingsManager.setExternalResourceRoots(bs.externalResourceRoots, \"global\");\n\n\t\t\t\tconst currentTrusted = host.settingsManager.getTrustedResourceRoots();\n\t\t\t\tconst newTrusted = currentTrusted.filter((r) => !bs.externalResourceRoots.includes(r));\n\t\t\t\thost.settingsManager.setTrustedResourceRoots(newTrusted, \"global\");\n\t\t\t}\n\t\t}\n\n\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\tawait host.handleReloadCommand();\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
1
+ {"version":3,"file":"config-backup.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAMX,eAAe,EAEf,MAAM,gCAAgC,CAAC;AAGxC,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IAC1D,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACxC;AAqCD,MAAM,WAAW,oBAAoB;IACpC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3B;AAkDD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAU7E;AAsBD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAS7F;AAYD,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDvG;AAED,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuHxG","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type {\n\tGlobalResourceProfileConfiguration,\n\tModelRouterSettings,\n\tProfileDefinitionInput,\n\tResourceProfileSettings,\n\tSettings,\n\tSettingsManager,\n\tThinkingLevel,\n} from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\thandleReloadCommand(): Promise<boolean>;\n}\n\nconst PROFILE_THINKING_LEVELS = new Set<ThinkingLevel>([\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n\t\"ultra\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction decodeBackupProfile(name: string, value: unknown): ProfileDefinitionInput | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst isWrapper = Object.hasOwn(value, \"resources\");\n\tconst resources = isWrapper ? value.resources : value;\n\tif (!isRecord(resources)) return undefined;\n\tconst thinking =\n\t\ttypeof value.thinking === \"string\" && PROFILE_THINKING_LEVELS.has(value.thinking as ThinkingLevel)\n\t\t\t? (value.thinking as ThinkingLevel)\n\t\t\t: undefined;\n\treturn {\n\t\tname,\n\t\tdescription: typeof value.description === \"string\" ? value.description : undefined,\n\t\tmodel: typeof value.model === \"string\" ? value.model : undefined,\n\t\tthinking,\n\t\tmodelRouter: isRecord(value.modelRouter) ? (value.modelRouter as ModelRouterSettings) : undefined,\n\t\tsoul: typeof value.soul === \"string\" ? value.soul : undefined,\n\t\tresources: resources as ResourceProfileSettings,\n\t};\n}\n\nexport interface ProfileFilesSnapshot {\n\tdirectoryExisted: boolean;\n\tfiles: Map<string, string>;\n}\n\nfunction canonicalActiveResourceProfiles(settings: Settings): string[] {\n\tconst values = settings.activeResourceProfiles ?? settings.activeResourceProfile ?? [];\n\tconst entries = Array.isArray(values) ? values : [values];\n\treturn [...new Set(entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeStringArray(label: string, value: unknown): string[] {\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\")) {\n\t\tthrow new Error(`Invalid backup file: ${label} must be an array of strings`);\n\t}\n\treturn [...new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeActiveResourceProfiles(settings: Record<string, unknown>): string[] {\n\tif (Object.hasOwn(settings, \"activeResourceProfiles\")) {\n\t\treturn decodeStringArray(\"settings.activeResourceProfiles\", settings.activeResourceProfiles);\n\t}\n\tconst legacy = settings.activeResourceProfile;\n\tif (legacy === undefined) return [];\n\tif (typeof legacy === \"string\") return legacy.trim() ? [legacy.trim()] : [];\n\treturn decodeStringArray(\"settings.activeResourceProfile\", legacy);\n}\n\nfunction decodeResourceProfiles(value: unknown): Record<string, ProfileDefinitionInput> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) {\n\t\tthrow new Error(\"Invalid backup file: settings.resourceProfiles must be an object\");\n\t}\n\tconst profiles: Record<string, ProfileDefinitionInput> = {};\n\tfor (const [name, definition] of Object.entries(value)) {\n\t\tconst decoded = decodeBackupProfile(name, definition);\n\t\tif (!decoded) throw new Error(`Invalid backup file: malformed resource profile \"${name}\"`);\n\t\tprofiles[name] = decoded;\n\t}\n\treturn profiles;\n}\n\nfunction decodeProfileFiles(value: unknown): Record<string, unknown> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) throw new Error(\"Invalid backup file: profiles must be an object\");\n\tfor (const filename of Object.keys(value)) {\n\t\tif (path.basename(filename) !== filename || !filename.endsWith(\".json\")) {\n\t\t\tthrow new Error(`Invalid backup file: unsafe profile filename \"${filename}\"`);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport function captureProfileFiles(profilesDir: string): ProfileFilesSnapshot {\n\tconst files = new Map<string, string>();\n\tif (!fs.existsSync(profilesDir)) return { directoryExisted: false, files };\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tif (!fs.lstatSync(profilePath).isFile()) continue;\n\t\tfiles.set(filename, fs.readFileSync(profilePath, \"utf-8\"));\n\t}\n\treturn { directoryExisted: true, files };\n}\n\nfunction clearProfileFiles(profilesDir: string): void {\n\tif (!fs.existsSync(profilesDir)) return;\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tconst stat = fs.lstatSync(profilePath);\n\t\tif (stat.isFile() || stat.isSymbolicLink()) fs.rmSync(profilePath, { force: true });\n\t}\n}\n\nfunction replaceProfileFiles(profilesDir: string, profiles: Record<string, unknown>): void {\n\tfs.mkdirSync(profilesDir, { recursive: true });\n\tclearProfileFiles(profilesDir);\n\tfor (const [filename, content] of Object.entries(profiles)) {\n\t\tconst serialized = JSON.stringify(content, null, 2);\n\t\tif (serialized === undefined) throw new Error(`Invalid backup file: profile \"${filename}\" is not serializable`);\n\t\tfs.writeFileSync(path.join(profilesDir, filename), serialized, \"utf-8\");\n\t}\n}\n\nexport function restoreProfileFiles(profilesDir: string, snapshot: ProfileFilesSnapshot): void {\n\tclearProfileFiles(profilesDir);\n\tif (snapshot.files.size > 0) fs.mkdirSync(profilesDir, { recursive: true });\n\tfor (const [filename, content] of snapshot.files) {\n\t\tfs.writeFileSync(path.join(profilesDir, filename), content, \"utf-8\");\n\t}\n\tif (!snapshot.directoryExisted && fs.existsSync(profilesDir) && fs.readdirSync(profilesDir).length === 0) {\n\t\tfs.rmdirSync(profilesDir);\n\t}\n}\n\nfunction profileConfigurationFromSettings(settings: Settings): GlobalResourceProfileConfiguration {\n\treturn {\n\t\tresourceProfiles: settings.resourceProfiles,\n\t\tactiveResourceProfile: settings.activeResourceProfile,\n\t\tactiveResourceProfiles: settings.activeResourceProfiles,\n\t\texternalResourceRoots: settings.externalResourceRoots,\n\t\ttrustedResourceRoots: settings.trustedResourceRoots,\n\t};\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profiles: Record<string, unknown> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!fs.lstatSync(pPath).isFile()) continue;\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst globalSettings = host.settingsManager.getGlobalSettings();\n\t\tconst backupData = {\n\t\t\tversion: 2,\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: globalSettings.resourceProfiles ?? {},\n\t\t\t\tactiveResourceProfiles: canonicalActiveResourceProfiles(globalSettings),\n\t\t\t\texternalResourceRoots: globalSettings.externalResourceRoots ?? [],\n\t\t\t\ttrustedResourceRoots: globalSettings.trustedResourceRoots ?? [],\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = path.join(getAgentDir(), \"backups\");\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: unknown;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isRecord(bundle)) {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet profileFiles: Record<string, unknown>;\n\t\tlet restoredResourceProfiles: Record<string, ProfileDefinitionInput>;\n\t\tlet restoredActiveProfiles: string[];\n\t\tlet restoredExternalRoots: string[];\n\t\ttry {\n\t\t\tprofileFiles = decodeProfileFiles(bundle.profiles);\n\t\t\tconst backupSettings = isRecord(bundle.settings) ? bundle.settings : {};\n\t\t\trestoredResourceProfiles = decodeResourceProfiles(backupSettings.resourceProfiles);\n\t\t\trestoredActiveProfiles = decodeActiveResourceProfiles(backupSettings);\n\t\t\trestoredExternalRoots = decodeStringArray(\n\t\t\t\t\"settings.externalResourceRoots\",\n\t\t\t\tbackupSettings.externalResourceRoots ?? [],\n\t\t\t);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn { component: submenu, focus: submenu.getSelectList() };\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profileFilesSnapshot = captureProfileFiles(profilesDir);\n\t\tconst settingsSnapshot = host.settingsManager.createReloadSnapshot();\n\t\tlet restoreStarted = false;\n\t\ttry {\n\t\t\trestoreStarted = true;\n\t\t\treplaceProfileFiles(profilesDir, profileFiles);\n\t\t\tconst currentTrusted = settingsSnapshot.globalSettings.trustedResourceRoots ?? [];\n\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration({\n\t\t\t\tresourceProfiles: Object.keys(restoredResourceProfiles).length > 0 ? restoredResourceProfiles : undefined,\n\t\t\t\tactiveResourceProfiles: restoredActiveProfiles,\n\t\t\t\texternalResourceRoots: restoredExternalRoots,\n\t\t\t\t// Security boundary: roots named by a backup never become trusted through restore.\n\t\t\t\ttrustedResourceRoots: currentTrusted.filter((root) => !restoredExternalRoots.includes(root)),\n\t\t\t});\n\t\t\tawait host.settingsManager.flush();\n\t\t\tif (!(await host.handleReloadCommand())) {\n\t\t\t\tthrow new Error(\"restored configuration failed runtime validation\");\n\t\t\t}\n\t\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\t} catch (error) {\n\t\t\tlet rollbackError: unknown;\n\t\t\tif (restoreStarted) {\n\t\t\t\ttry {\n\t\t\t\t\trestoreProfileFiles(profilesDir, profileFilesSnapshot);\n\t\t\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration(\n\t\t\t\t\t\tprofileConfigurationFromSettings(settingsSnapshot.globalSettings),\n\t\t\t\t\t);\n\t\t\t\t\tawait host.settingsManager.flush();\n\t\t\t\t\thost.settingsManager.restoreReloadSnapshot(settingsSnapshot);\n\t\t\t\t} catch (restoreError) {\n\t\t\t\t\trollbackError = restoreError;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\thost.showError(\n\t\t\t\trollbackError\n\t\t\t\t\t? `Configuration restore failed: ${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n\t\t\t\t\t: `Configuration restore failed: ${message}; previous configuration restored`,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
@@ -12,6 +12,144 @@ import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
13
  import { getAgentDir } from "../../config.js";
14
14
  import { SelectSubmenu } from "./components/settings-selector.js";
15
+ const PROFILE_THINKING_LEVELS = new Set([
16
+ "off",
17
+ "minimal",
18
+ "low",
19
+ "medium",
20
+ "high",
21
+ "xhigh",
22
+ "max",
23
+ "ultra",
24
+ ]);
25
+ function isRecord(value) {
26
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
27
+ }
28
+ function decodeBackupProfile(name, value) {
29
+ if (!isRecord(value))
30
+ return undefined;
31
+ const isWrapper = Object.hasOwn(value, "resources");
32
+ const resources = isWrapper ? value.resources : value;
33
+ if (!isRecord(resources))
34
+ return undefined;
35
+ const thinking = typeof value.thinking === "string" && PROFILE_THINKING_LEVELS.has(value.thinking)
36
+ ? value.thinking
37
+ : undefined;
38
+ return {
39
+ name,
40
+ description: typeof value.description === "string" ? value.description : undefined,
41
+ model: typeof value.model === "string" ? value.model : undefined,
42
+ thinking,
43
+ modelRouter: isRecord(value.modelRouter) ? value.modelRouter : undefined,
44
+ soul: typeof value.soul === "string" ? value.soul : undefined,
45
+ resources: resources,
46
+ };
47
+ }
48
+ function canonicalActiveResourceProfiles(settings) {
49
+ const values = settings.activeResourceProfiles ?? settings.activeResourceProfile ?? [];
50
+ const entries = Array.isArray(values) ? values : [values];
51
+ return [...new Set(entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];
52
+ }
53
+ function decodeStringArray(label, value) {
54
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
55
+ throw new Error(`Invalid backup file: ${label} must be an array of strings`);
56
+ }
57
+ return [...new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];
58
+ }
59
+ function decodeActiveResourceProfiles(settings) {
60
+ if (Object.hasOwn(settings, "activeResourceProfiles")) {
61
+ return decodeStringArray("settings.activeResourceProfiles", settings.activeResourceProfiles);
62
+ }
63
+ const legacy = settings.activeResourceProfile;
64
+ if (legacy === undefined)
65
+ return [];
66
+ if (typeof legacy === "string")
67
+ return legacy.trim() ? [legacy.trim()] : [];
68
+ return decodeStringArray("settings.activeResourceProfile", legacy);
69
+ }
70
+ function decodeResourceProfiles(value) {
71
+ if (value === undefined)
72
+ return {};
73
+ if (!isRecord(value)) {
74
+ throw new Error("Invalid backup file: settings.resourceProfiles must be an object");
75
+ }
76
+ const profiles = {};
77
+ for (const [name, definition] of Object.entries(value)) {
78
+ const decoded = decodeBackupProfile(name, definition);
79
+ if (!decoded)
80
+ throw new Error(`Invalid backup file: malformed resource profile "${name}"`);
81
+ profiles[name] = decoded;
82
+ }
83
+ return profiles;
84
+ }
85
+ function decodeProfileFiles(value) {
86
+ if (value === undefined)
87
+ return {};
88
+ if (!isRecord(value))
89
+ throw new Error("Invalid backup file: profiles must be an object");
90
+ for (const filename of Object.keys(value)) {
91
+ if (path.basename(filename) !== filename || !filename.endsWith(".json")) {
92
+ throw new Error(`Invalid backup file: unsafe profile filename "${filename}"`);
93
+ }
94
+ }
95
+ return value;
96
+ }
97
+ export function captureProfileFiles(profilesDir) {
98
+ const files = new Map();
99
+ if (!fs.existsSync(profilesDir))
100
+ return { directoryExisted: false, files };
101
+ for (const filename of fs.readdirSync(profilesDir)) {
102
+ if (!filename.endsWith(".json"))
103
+ continue;
104
+ const profilePath = path.join(profilesDir, filename);
105
+ if (!fs.lstatSync(profilePath).isFile())
106
+ continue;
107
+ files.set(filename, fs.readFileSync(profilePath, "utf-8"));
108
+ }
109
+ return { directoryExisted: true, files };
110
+ }
111
+ function clearProfileFiles(profilesDir) {
112
+ if (!fs.existsSync(profilesDir))
113
+ return;
114
+ for (const filename of fs.readdirSync(profilesDir)) {
115
+ if (!filename.endsWith(".json"))
116
+ continue;
117
+ const profilePath = path.join(profilesDir, filename);
118
+ const stat = fs.lstatSync(profilePath);
119
+ if (stat.isFile() || stat.isSymbolicLink())
120
+ fs.rmSync(profilePath, { force: true });
121
+ }
122
+ }
123
+ function replaceProfileFiles(profilesDir, profiles) {
124
+ fs.mkdirSync(profilesDir, { recursive: true });
125
+ clearProfileFiles(profilesDir);
126
+ for (const [filename, content] of Object.entries(profiles)) {
127
+ const serialized = JSON.stringify(content, null, 2);
128
+ if (serialized === undefined)
129
+ throw new Error(`Invalid backup file: profile "${filename}" is not serializable`);
130
+ fs.writeFileSync(path.join(profilesDir, filename), serialized, "utf-8");
131
+ }
132
+ }
133
+ export function restoreProfileFiles(profilesDir, snapshot) {
134
+ clearProfileFiles(profilesDir);
135
+ if (snapshot.files.size > 0)
136
+ fs.mkdirSync(profilesDir, { recursive: true });
137
+ for (const [filename, content] of snapshot.files) {
138
+ fs.writeFileSync(path.join(profilesDir, filename), content, "utf-8");
139
+ }
140
+ if (!snapshot.directoryExisted && fs.existsSync(profilesDir) && fs.readdirSync(profilesDir).length === 0) {
141
+ fs.rmdirSync(profilesDir);
142
+ }
143
+ }
144
+ function profileConfigurationFromSettings(settings) {
145
+ return {
146
+ resourceProfiles: settings.resourceProfiles,
147
+ activeResourceProfile: settings.activeResourceProfile,
148
+ activeResourceProfiles: settings.activeResourceProfiles,
149
+ externalResourceRoots: settings.externalResourceRoots,
150
+ trustedResourceRoots: settings.trustedResourceRoots,
151
+ };
152
+ }
15
153
  export async function handleConfigBackupCommand(host, fileArg) {
16
154
  try {
17
155
  const profilesDir = path.join(getAgentDir(), "profiles");
@@ -22,6 +160,8 @@ export async function handleConfigBackupCommand(host, fileArg) {
22
160
  if (entry.endsWith(".json")) {
23
161
  const pPath = path.join(profilesDir, entry);
24
162
  try {
163
+ if (!fs.lstatSync(pPath).isFile())
164
+ continue;
25
165
  const content = fs.readFileSync(pPath, "utf-8");
26
166
  profiles[entry] = JSON.parse(content);
27
167
  }
@@ -31,13 +171,15 @@ export async function handleConfigBackupCommand(host, fileArg) {
31
171
  }
32
172
  }
33
173
  }
174
+ const globalSettings = host.settingsManager.getGlobalSettings();
34
175
  const backupData = {
176
+ version: 2,
35
177
  profiles,
36
178
  settings: {
37
- resourceProfiles: host.settingsManager.settings.resourceProfiles,
38
- activeResourceProfile: host.settingsManager.settings.activeResourceProfile,
39
- externalResourceRoots: host.settingsManager.settings.externalResourceRoots,
40
- trustedResourceRoots: host.settingsManager.settings.trustedResourceRoots,
179
+ resourceProfiles: globalSettings.resourceProfiles ?? {},
180
+ activeResourceProfiles: canonicalActiveResourceProfiles(globalSettings),
181
+ externalResourceRoots: globalSettings.externalResourceRoots ?? [],
182
+ trustedResourceRoots: globalSettings.trustedResourceRoots ?? [],
41
183
  },
42
184
  };
43
185
  let targetFile = fileArg;
@@ -82,10 +224,25 @@ export async function handleConfigRestoreCommand(host, fileArg) {
82
224
  host.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);
83
225
  return;
84
226
  }
85
- if (!bundle || typeof bundle !== "object") {
227
+ if (!isRecord(bundle)) {
86
228
  host.showError("Invalid backup file: must be a JSON object");
87
229
  return;
88
230
  }
231
+ let profileFiles;
232
+ let restoredResourceProfiles;
233
+ let restoredActiveProfiles;
234
+ let restoredExternalRoots;
235
+ try {
236
+ profileFiles = decodeProfileFiles(bundle.profiles);
237
+ const backupSettings = isRecord(bundle.settings) ? bundle.settings : {};
238
+ restoredResourceProfiles = decodeResourceProfiles(backupSettings.resourceProfiles);
239
+ restoredActiveProfiles = decodeActiveResourceProfiles(backupSettings);
240
+ restoredExternalRoots = decodeStringArray("settings.externalResourceRoots", backupSettings.externalResourceRoots ?? []);
241
+ }
242
+ catch (error) {
243
+ host.showError(error instanceof Error ? error.message : String(error));
244
+ return;
245
+ }
89
246
  // Confirm before clobbering
90
247
  const confirm = await new Promise((resolve) => {
91
248
  host.showSelector((done) => {
@@ -106,38 +263,45 @@ export async function handleConfigRestoreCommand(host, fileArg) {
106
263
  host.showStatus("Restore aborted.");
107
264
  return;
108
265
  }
109
- // 1. Restore profile files (reusable-file scope)
110
- if (bundle.profiles && typeof bundle.profiles === "object") {
111
- const profilesDir = path.join(getAgentDir(), "profiles");
112
- fs.mkdirSync(profilesDir, { recursive: true });
113
- for (const [filename, content] of Object.entries(bundle.profiles)) {
114
- const targetPath = path.join(profilesDir, filename);
115
- fs.writeFileSync(targetPath, JSON.stringify(content, null, 2), "utf-8");
266
+ const profilesDir = path.join(getAgentDir(), "profiles");
267
+ const profileFilesSnapshot = captureProfileFiles(profilesDir);
268
+ const settingsSnapshot = host.settingsManager.createReloadSnapshot();
269
+ let restoreStarted = false;
270
+ try {
271
+ restoreStarted = true;
272
+ replaceProfileFiles(profilesDir, profileFiles);
273
+ const currentTrusted = settingsSnapshot.globalSettings.trustedResourceRoots ?? [];
274
+ host.settingsManager.replaceGlobalResourceProfileConfiguration({
275
+ resourceProfiles: Object.keys(restoredResourceProfiles).length > 0 ? restoredResourceProfiles : undefined,
276
+ activeResourceProfiles: restoredActiveProfiles,
277
+ externalResourceRoots: restoredExternalRoots,
278
+ // Security boundary: roots named by a backup never become trusted through restore.
279
+ trustedResourceRoots: currentTrusted.filter((root) => !restoredExternalRoots.includes(root)),
280
+ });
281
+ await host.settingsManager.flush();
282
+ if (!(await host.handleReloadCommand())) {
283
+ throw new Error("restored configuration failed runtime validation");
116
284
  }
285
+ host.showStatus("Configuration restored successfully.");
117
286
  }
118
- // 2. Restore settings
119
- if (bundle.settings && typeof bundle.settings === "object") {
120
- const bs = bundle.settings;
121
- // Global profiles definitions
122
- if (bs.resourceProfiles && typeof bs.resourceProfiles === "object") {
123
- for (const [name, definition] of Object.entries(bs.resourceProfiles)) {
124
- host.settingsManager.setProfileDefinition(name, definition, "global");
287
+ catch (error) {
288
+ let rollbackError;
289
+ if (restoreStarted) {
290
+ try {
291
+ restoreProfileFiles(profilesDir, profileFilesSnapshot);
292
+ host.settingsManager.replaceGlobalResourceProfileConfiguration(profileConfigurationFromSettings(settingsSnapshot.globalSettings));
293
+ await host.settingsManager.flush();
294
+ host.settingsManager.restoreReloadSnapshot(settingsSnapshot);
295
+ }
296
+ catch (restoreError) {
297
+ rollbackError = restoreError;
125
298
  }
126
299
  }
127
- // Active profile selection
128
- if (bs.activeResourceProfile) {
129
- host.settingsManager.setActiveProfile(bs.activeResourceProfile, "global");
130
- }
131
- // External roots (trustedRoots are NOT restored, as per SECURITY requirement)
132
- if (Array.isArray(bs.externalResourceRoots)) {
133
- host.settingsManager.setExternalResourceRoots(bs.externalResourceRoots, "global");
134
- const currentTrusted = host.settingsManager.getTrustedResourceRoots();
135
- const newTrusted = currentTrusted.filter((r) => !bs.externalResourceRoots.includes(r));
136
- host.settingsManager.setTrustedResourceRoots(newTrusted, "global");
137
- }
300
+ const message = error instanceof Error ? error.message : String(error);
301
+ host.showError(rollbackError
302
+ ? `Configuration restore failed: ${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
303
+ : `Configuration restore failed: ${message}; previous configuration restored`);
138
304
  }
139
- host.showStatus("Configuration restored successfully.");
140
- await host.handleReloadCommand();
141
305
  }
142
306
  catch (error) {
143
307
  host.showError(error instanceof Error ? error.message : String(error));
@@ -1 +1 @@
1
- {"version":3,"file":"config-backup.js","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { SettingsManager } from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\thandleReloadCommand(): Promise<void>;\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profiles: Record<string, any> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst backupData = {\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: host.settingsManager.settings.resourceProfiles,\n\t\t\t\tactiveResourceProfile: host.settingsManager.settings.activeResourceProfile,\n\t\t\t\texternalResourceRoots: host.settingsManager.settings.externalResourceRoots,\n\t\t\t\ttrustedResourceRoots: host.settingsManager.settings.trustedResourceRoots,\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = path.join(getAgentDir(), \"backups\");\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: any;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!bundle || typeof bundle !== \"object\") {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn { component: submenu, focus: submenu.getSelectList() };\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\t// 1. Restore profile files (reusable-file scope)\n\t\tif (bundle.profiles && typeof bundle.profiles === \"object\") {\n\t\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\t\tfs.mkdirSync(profilesDir, { recursive: true });\n\t\t\tfor (const [filename, content] of Object.entries(bundle.profiles)) {\n\t\t\t\tconst targetPath = path.join(profilesDir, filename);\n\t\t\t\tfs.writeFileSync(targetPath, JSON.stringify(content, null, 2), \"utf-8\");\n\t\t\t}\n\t\t}\n\n\t\t// 2. Restore settings\n\t\tif (bundle.settings && typeof bundle.settings === \"object\") {\n\t\t\tconst bs = bundle.settings;\n\n\t\t\t// Global profiles definitions\n\t\t\tif (bs.resourceProfiles && typeof bs.resourceProfiles === \"object\") {\n\t\t\t\tfor (const [name, definition] of Object.entries(bs.resourceProfiles)) {\n\t\t\t\t\thost.settingsManager.setProfileDefinition(name, definition as any, \"global\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Active profile selection\n\t\t\tif (bs.activeResourceProfile) {\n\t\t\t\thost.settingsManager.setActiveProfile(bs.activeResourceProfile, \"global\");\n\t\t\t}\n\n\t\t\t// External roots (trustedRoots are NOT restored, as per SECURITY requirement)\n\t\t\tif (Array.isArray(bs.externalResourceRoots)) {\n\t\t\t\thost.settingsManager.setExternalResourceRoots(bs.externalResourceRoots, \"global\");\n\n\t\t\t\tconst currentTrusted = host.settingsManager.getTrustedResourceRoots();\n\t\t\t\tconst newTrusted = currentTrusted.filter((r) => !bs.externalResourceRoots.includes(r));\n\t\t\t\thost.settingsManager.setTrustedResourceRoots(newTrusted, \"global\");\n\t\t\t}\n\t\t}\n\n\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\tawait host.handleReloadCommand();\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
1
+ {"version":3,"file":"config-backup.js","sourceRoot":"","sources":["../../../src/modes/interactive/config-backup.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Config backup/restore commands extracted from interactive-mode.\n *\n * `handleConfigBackupCommand` snapshots the reusable-file profiles and the\n * resource-relevant settings to a JSON bundle; `handleConfigRestoreCommand`\n * reads such a bundle back, confirming with the user before clobbering local\n * state. Both operate through a narrow `host` seam (settings manager + the\n * status/error/selector callbacks) so the file IO and validation live outside\n * the god file while interactive-mode keeps only thin delegating wrappers.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Component } from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type {\n\tGlobalResourceProfileConfiguration,\n\tModelRouterSettings,\n\tProfileDefinitionInput,\n\tResourceProfileSettings,\n\tSettings,\n\tSettingsManager,\n\tThinkingLevel,\n} from \"../../core/settings-manager.ts\";\nimport { SelectSubmenu } from \"./components/settings-selector.ts\";\n\nexport interface ConfigBackupHost {\n\treadonly settingsManager: SettingsManager;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport interface ConfigRestoreHost extends ConfigBackupHost {\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\thandleReloadCommand(): Promise<boolean>;\n}\n\nconst PROFILE_THINKING_LEVELS = new Set<ThinkingLevel>([\n\t\"off\",\n\t\"minimal\",\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"xhigh\",\n\t\"max\",\n\t\"ultra\",\n]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction decodeBackupProfile(name: string, value: unknown): ProfileDefinitionInput | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst isWrapper = Object.hasOwn(value, \"resources\");\n\tconst resources = isWrapper ? value.resources : value;\n\tif (!isRecord(resources)) return undefined;\n\tconst thinking =\n\t\ttypeof value.thinking === \"string\" && PROFILE_THINKING_LEVELS.has(value.thinking as ThinkingLevel)\n\t\t\t? (value.thinking as ThinkingLevel)\n\t\t\t: undefined;\n\treturn {\n\t\tname,\n\t\tdescription: typeof value.description === \"string\" ? value.description : undefined,\n\t\tmodel: typeof value.model === \"string\" ? value.model : undefined,\n\t\tthinking,\n\t\tmodelRouter: isRecord(value.modelRouter) ? (value.modelRouter as ModelRouterSettings) : undefined,\n\t\tsoul: typeof value.soul === \"string\" ? value.soul : undefined,\n\t\tresources: resources as ResourceProfileSettings,\n\t};\n}\n\nexport interface ProfileFilesSnapshot {\n\tdirectoryExisted: boolean;\n\tfiles: Map<string, string>;\n}\n\nfunction canonicalActiveResourceProfiles(settings: Settings): string[] {\n\tconst values = settings.activeResourceProfiles ?? settings.activeResourceProfile ?? [];\n\tconst entries = Array.isArray(values) ? values : [values];\n\treturn [...new Set(entries.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeStringArray(label: string, value: unknown): string[] {\n\tif (!Array.isArray(value) || value.some((entry) => typeof entry !== \"string\")) {\n\t\tthrow new Error(`Invalid backup file: ${label} must be an array of strings`);\n\t}\n\treturn [...new Set(value.map((entry) => entry.trim()).filter((entry) => entry.length > 0))];\n}\n\nfunction decodeActiveResourceProfiles(settings: Record<string, unknown>): string[] {\n\tif (Object.hasOwn(settings, \"activeResourceProfiles\")) {\n\t\treturn decodeStringArray(\"settings.activeResourceProfiles\", settings.activeResourceProfiles);\n\t}\n\tconst legacy = settings.activeResourceProfile;\n\tif (legacy === undefined) return [];\n\tif (typeof legacy === \"string\") return legacy.trim() ? [legacy.trim()] : [];\n\treturn decodeStringArray(\"settings.activeResourceProfile\", legacy);\n}\n\nfunction decodeResourceProfiles(value: unknown): Record<string, ProfileDefinitionInput> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) {\n\t\tthrow new Error(\"Invalid backup file: settings.resourceProfiles must be an object\");\n\t}\n\tconst profiles: Record<string, ProfileDefinitionInput> = {};\n\tfor (const [name, definition] of Object.entries(value)) {\n\t\tconst decoded = decodeBackupProfile(name, definition);\n\t\tif (!decoded) throw new Error(`Invalid backup file: malformed resource profile \"${name}\"`);\n\t\tprofiles[name] = decoded;\n\t}\n\treturn profiles;\n}\n\nfunction decodeProfileFiles(value: unknown): Record<string, unknown> {\n\tif (value === undefined) return {};\n\tif (!isRecord(value)) throw new Error(\"Invalid backup file: profiles must be an object\");\n\tfor (const filename of Object.keys(value)) {\n\t\tif (path.basename(filename) !== filename || !filename.endsWith(\".json\")) {\n\t\t\tthrow new Error(`Invalid backup file: unsafe profile filename \"${filename}\"`);\n\t\t}\n\t}\n\treturn value;\n}\n\nexport function captureProfileFiles(profilesDir: string): ProfileFilesSnapshot {\n\tconst files = new Map<string, string>();\n\tif (!fs.existsSync(profilesDir)) return { directoryExisted: false, files };\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tif (!fs.lstatSync(profilePath).isFile()) continue;\n\t\tfiles.set(filename, fs.readFileSync(profilePath, \"utf-8\"));\n\t}\n\treturn { directoryExisted: true, files };\n}\n\nfunction clearProfileFiles(profilesDir: string): void {\n\tif (!fs.existsSync(profilesDir)) return;\n\tfor (const filename of fs.readdirSync(profilesDir)) {\n\t\tif (!filename.endsWith(\".json\")) continue;\n\t\tconst profilePath = path.join(profilesDir, filename);\n\t\tconst stat = fs.lstatSync(profilePath);\n\t\tif (stat.isFile() || stat.isSymbolicLink()) fs.rmSync(profilePath, { force: true });\n\t}\n}\n\nfunction replaceProfileFiles(profilesDir: string, profiles: Record<string, unknown>): void {\n\tfs.mkdirSync(profilesDir, { recursive: true });\n\tclearProfileFiles(profilesDir);\n\tfor (const [filename, content] of Object.entries(profiles)) {\n\t\tconst serialized = JSON.stringify(content, null, 2);\n\t\tif (serialized === undefined) throw new Error(`Invalid backup file: profile \"${filename}\" is not serializable`);\n\t\tfs.writeFileSync(path.join(profilesDir, filename), serialized, \"utf-8\");\n\t}\n}\n\nexport function restoreProfileFiles(profilesDir: string, snapshot: ProfileFilesSnapshot): void {\n\tclearProfileFiles(profilesDir);\n\tif (snapshot.files.size > 0) fs.mkdirSync(profilesDir, { recursive: true });\n\tfor (const [filename, content] of snapshot.files) {\n\t\tfs.writeFileSync(path.join(profilesDir, filename), content, \"utf-8\");\n\t}\n\tif (!snapshot.directoryExisted && fs.existsSync(profilesDir) && fs.readdirSync(profilesDir).length === 0) {\n\t\tfs.rmdirSync(profilesDir);\n\t}\n}\n\nfunction profileConfigurationFromSettings(settings: Settings): GlobalResourceProfileConfiguration {\n\treturn {\n\t\tresourceProfiles: settings.resourceProfiles,\n\t\tactiveResourceProfile: settings.activeResourceProfile,\n\t\tactiveResourceProfiles: settings.activeResourceProfiles,\n\t\texternalResourceRoots: settings.externalResourceRoots,\n\t\ttrustedResourceRoots: settings.trustedResourceRoots,\n\t};\n}\n\nexport async function handleConfigBackupCommand(host: ConfigBackupHost, fileArg?: string): Promise<void> {\n\ttry {\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profiles: Record<string, unknown> = {};\n\t\tif (fs.existsSync(profilesDir)) {\n\t\t\tconst entries = fs.readdirSync(profilesDir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.endsWith(\".json\")) {\n\t\t\t\t\tconst pPath = path.join(profilesDir, entry);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (!fs.lstatSync(pPath).isFile()) continue;\n\t\t\t\t\t\tconst content = fs.readFileSync(pPath, \"utf-8\");\n\t\t\t\t\t\tprofiles[entry] = JSON.parse(content);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// skip\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst globalSettings = host.settingsManager.getGlobalSettings();\n\t\tconst backupData = {\n\t\t\tversion: 2,\n\t\t\tprofiles,\n\t\t\tsettings: {\n\t\t\t\tresourceProfiles: globalSettings.resourceProfiles ?? {},\n\t\t\t\tactiveResourceProfiles: canonicalActiveResourceProfiles(globalSettings),\n\t\t\t\texternalResourceRoots: globalSettings.externalResourceRoots ?? [],\n\t\t\t\ttrustedResourceRoots: globalSettings.trustedResourceRoots ?? [],\n\t\t\t},\n\t\t};\n\n\t\tlet targetFile = fileArg;\n\t\tif (!targetFile) {\n\t\t\tconst backupsDir = path.join(getAgentDir(), \"backups\");\n\t\t\tfs.mkdirSync(backupsDir, { recursive: true });\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n\t\t\ttargetFile = path.join(backupsDir, `config-${timestamp}.json`);\n\t\t} else {\n\t\t\tconst resolved = host.settingsManager.canonicalizePath(targetFile);\n\t\t\tif (resolved) {\n\t\t\t\ttargetFile = resolved;\n\t\t\t}\n\t\t}\n\n\t\tfs.mkdirSync(path.dirname(targetFile), { recursive: true });\n\t\tfs.writeFileSync(targetFile, JSON.stringify(backupData, null, 2), \"utf-8\");\n\t\thost.showStatus(`Configuration backup saved to ${targetFile}`);\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nexport async function handleConfigRestoreCommand(host: ConfigRestoreHost, fileArg: string): Promise<void> {\n\ttry {\n\t\tconst trimmed = fileArg.trim();\n\t\tif (!trimmed) {\n\t\t\thost.showError(\"Usage: /config-restore <file>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = host.settingsManager.canonicalizePath(trimmed);\n\t\tif (!resolved || !fs.existsSync(resolved)) {\n\t\t\thost.showError(`Backup file does not exist: ${trimmed}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet bundle: unknown;\n\t\ttry {\n\t\t\tconst content = fs.readFileSync(resolved, \"utf-8\");\n\t\t\tbundle = JSON.parse(content);\n\t\t} catch (error) {\n\t\t\thost.showError(`Failed to parse backup file: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isRecord(bundle)) {\n\t\t\thost.showError(\"Invalid backup file: must be a JSON object\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet profileFiles: Record<string, unknown>;\n\t\tlet restoredResourceProfiles: Record<string, ProfileDefinitionInput>;\n\t\tlet restoredActiveProfiles: string[];\n\t\tlet restoredExternalRoots: string[];\n\t\ttry {\n\t\t\tprofileFiles = decodeProfileFiles(bundle.profiles);\n\t\t\tconst backupSettings = isRecord(bundle.settings) ? bundle.settings : {};\n\t\t\trestoredResourceProfiles = decodeResourceProfiles(backupSettings.resourceProfiles);\n\t\t\trestoredActiveProfiles = decodeActiveResourceProfiles(backupSettings);\n\t\t\trestoredExternalRoots = decodeStringArray(\n\t\t\t\t\"settings.externalResourceRoots\",\n\t\t\t\tbackupSettings.externalResourceRoots ?? [],\n\t\t\t);\n\t\t} catch (error) {\n\t\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t\t\treturn;\n\t\t}\n\n\t\t// Confirm before clobbering\n\t\tconst confirm = await new Promise<boolean>((resolve) => {\n\t\t\thost.showSelector((done) => {\n\t\t\t\tconst submenu = new SelectSubmenu(\n\t\t\t\t\t\"Restore configuration?\",\n\t\t\t\t\t\"This will overwrite existing local profiles and settings with the backup values. Do you want to continue?\",\n\t\t\t\t\t[\n\t\t\t\t\t\t{ value: \"yes\", label: \"Yes\", description: \"Proceed with restoration.\" },\n\t\t\t\t\t\t{ value: \"no\", label: \"No\", description: \"Cancel and abort.\" },\n\t\t\t\t\t],\n\t\t\t\t\t\"no\",\n\t\t\t\t\t(value) => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(value === \"yes\");\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tdone();\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\treturn { component: submenu, focus: submenu.getSelectList() };\n\t\t\t});\n\t\t});\n\n\t\tif (!confirm) {\n\t\t\thost.showStatus(\"Restore aborted.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst profilesDir = path.join(getAgentDir(), \"profiles\");\n\t\tconst profileFilesSnapshot = captureProfileFiles(profilesDir);\n\t\tconst settingsSnapshot = host.settingsManager.createReloadSnapshot();\n\t\tlet restoreStarted = false;\n\t\ttry {\n\t\t\trestoreStarted = true;\n\t\t\treplaceProfileFiles(profilesDir, profileFiles);\n\t\t\tconst currentTrusted = settingsSnapshot.globalSettings.trustedResourceRoots ?? [];\n\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration({\n\t\t\t\tresourceProfiles: Object.keys(restoredResourceProfiles).length > 0 ? restoredResourceProfiles : undefined,\n\t\t\t\tactiveResourceProfiles: restoredActiveProfiles,\n\t\t\t\texternalResourceRoots: restoredExternalRoots,\n\t\t\t\t// Security boundary: roots named by a backup never become trusted through restore.\n\t\t\t\ttrustedResourceRoots: currentTrusted.filter((root) => !restoredExternalRoots.includes(root)),\n\t\t\t});\n\t\t\tawait host.settingsManager.flush();\n\t\t\tif (!(await host.handleReloadCommand())) {\n\t\t\t\tthrow new Error(\"restored configuration failed runtime validation\");\n\t\t\t}\n\t\t\thost.showStatus(\"Configuration restored successfully.\");\n\t\t} catch (error) {\n\t\t\tlet rollbackError: unknown;\n\t\t\tif (restoreStarted) {\n\t\t\t\ttry {\n\t\t\t\t\trestoreProfileFiles(profilesDir, profileFilesSnapshot);\n\t\t\t\t\thost.settingsManager.replaceGlobalResourceProfileConfiguration(\n\t\t\t\t\t\tprofileConfigurationFromSettings(settingsSnapshot.globalSettings),\n\t\t\t\t\t);\n\t\t\t\t\tawait host.settingsManager.flush();\n\t\t\t\t\thost.settingsManager.restoreReloadSnapshot(settingsSnapshot);\n\t\t\t\t} catch (restoreError) {\n\t\t\t\t\trollbackError = restoreError;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\thost.showError(\n\t\t\t\trollbackError\n\t\t\t\t\t? `Configuration restore failed: ${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n\t\t\t\t\t: `Configuration restore failed: ${message}; previous configuration restored`,\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\thost.showError(error instanceof Error ? error.message : String(error));\n\t}\n}\n"]}
@@ -334,8 +334,8 @@ export declare class InteractiveMode {
334
334
  private showSessionSelector;
335
335
  private handleResumeSession;
336
336
  private handleReloadCommand;
337
+ private handleReloadCommandWithResult;
337
338
  private refreshUIAfterExtensionsChanged;
338
- private reconcileExtensionsAndRefreshUI;
339
339
  private handleExportCommand;
340
340
  private getPathCommandArgument;
341
341
  private handleImportCommand;