@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
@@ -21,7 +21,30 @@ import { ExtensionInputComponent } from "./components/extension-input.js";
21
21
  import { ProfileResourceEditorComponent, resolveResourceEditPath, } from "./components/profile-resource-editor.js";
22
22
  import { ProfileSelectorComponent } from "./components/profile-selector.js";
23
23
  import { SelectSubmenu } from "./components/settings-selector.js";
24
+ import { captureProfileFiles, restoreProfileFiles } from "./config-backup.js";
24
25
  import { getAvailableThemesWithPaths } from "./theme/theme.js";
26
+ export const NO_ACTIVE_PROFILE_DESCRIPTION = "Baseline resources; inline SDK extensions load, discovered extensions stay withheld";
27
+ function deletionScopeForProfile(profile) {
28
+ switch (profile.source) {
29
+ case "inline":
30
+ return "session";
31
+ case "directory-overlay":
32
+ return "directory";
33
+ case "project-settings":
34
+ return "project";
35
+ case "global-settings":
36
+ return "global";
37
+ case "profile-file": {
38
+ const localProfilePath = path.resolve(getAgentDir(), "profiles", `${profile.name}.json`);
39
+ return profile.sourcePath && path.resolve(profile.sourcePath) === localProfilePath
40
+ ? "reusable-file"
41
+ : undefined;
42
+ }
43
+ case "embedded":
44
+ case "external-settings":
45
+ return undefined;
46
+ }
47
+ }
25
48
  export class ProfileMenuController {
26
49
  deps;
27
50
  constructor(deps) {
@@ -65,7 +88,7 @@ export class ProfileMenuController {
65
88
  const profiles = registry.listProfiles();
66
89
  const activeNames = this.settingsManager.getActiveResourceProfileNames();
67
90
  const options = [
68
- { value: "(none)", label: "(none)", description: "No active profile/situation (all resources enabled)" },
91
+ { value: "(none)", label: "(none)", description: NO_ACTIVE_PROFILE_DESCRIPTION },
69
92
  ...profiles.map((p) => ({
70
93
  value: p.name,
71
94
  label: p.name,
@@ -168,8 +191,9 @@ export class ProfileMenuController {
168
191
  this.ui.showSelector((done) => {
169
192
  const selector = new SelectSubmenu("Delete Profile / Situation", "Pick a profile/situation to delete.", editableProfiles, "", (value) => {
170
193
  done();
171
- this.deleteProfileFromSource(value);
172
- void this.ui.showSettingsSelector();
194
+ void this.deleteProfileFromSource(value).then(() => {
195
+ void this.ui.showSettingsSelector();
196
+ });
173
197
  }, () => {
174
198
  done();
175
199
  void this.openManageProfilesFlow();
@@ -290,9 +314,9 @@ export class ProfileMenuController {
290
314
  name: trimmed,
291
315
  model: profileModel ?? undefined,
292
316
  resources: {},
293
- }, "directory");
317
+ }, "reusable-file");
294
318
  await this.applyProfile(trimmed);
295
- void this.openLibraryEditorForProfile(trimmed, "directory");
319
+ void this.openLibraryEditorForProfile(trimmed, "reusable-file");
296
320
  }
297
321
  catch (error) {
298
322
  this.ui.showError(error instanceof Error ? error.message : String(error));
@@ -490,31 +514,7 @@ export class ProfileMenuController {
490
514
  externalResourceRoots: this.settingsManager.getExternalResourceRoots(),
491
515
  onSave: (resources) => {
492
516
  done();
493
- try {
494
- this.settingsManager.setProfileDefinition(profileName, {
495
- name: profileName,
496
- description: profile.description,
497
- model: profile.model,
498
- thinking: profile.thinking,
499
- resources,
500
- }, currentScope);
501
- this.ui.showStatus(`Saved profile "${profileName}" to ${currentScope}.`);
502
- if (isActiveProfile) {
503
- const changedKinds = resourceProfileSettingsChangedKinds(originalResources, resources);
504
- if (changedKinds.size === 1 && changedKinds.has("extensions")) {
505
- void this.ui.reconcileExtensionsAndRefreshUI(profileName);
506
- }
507
- else if (changedKinds.size > 0) {
508
- void this.refreshAfterProfileMutation(profileName);
509
- }
510
- else {
511
- this.ui.requestRender();
512
- }
513
- }
514
- }
515
- catch (error) {
516
- this.ui.showError(error instanceof Error ? error.message : String(error));
517
- }
517
+ void this.saveProfileResources(profile, originalResources, resources, currentScope, isActiveProfile);
518
518
  },
519
519
  onCancel: () => {
520
520
  done();
@@ -545,6 +545,104 @@ export class ProfileMenuController {
545
545
  return { component: editor, focus: editor };
546
546
  });
547
547
  }
548
+ async saveProfileResources(profile, originalResources, resources, scope, isActiveProfile) {
549
+ const definition = {
550
+ name: profile.name,
551
+ description: profile.description,
552
+ model: profile.model,
553
+ thinking: profile.thinking,
554
+ modelRouter: profile.modelRouter,
555
+ soul: profile.soul,
556
+ resources,
557
+ };
558
+ const changedKinds = resourceProfileSettingsChangedKinds(originalResources, resources);
559
+ if (!isActiveProfile || changedKinds.size === 0) {
560
+ try {
561
+ this.settingsManager.setProfileDefinition(profile.name, definition, scope);
562
+ this.ui.showStatus(`Saved profile "${profile.name}" to ${scope}.`);
563
+ this.ui.requestRender();
564
+ }
565
+ catch (error) {
566
+ this.ui.showError(error instanceof Error ? error.message : String(error));
567
+ }
568
+ return;
569
+ }
570
+ const settingsSnapshot = this.settingsManager.createReloadSnapshot();
571
+ const profilesDir = path.join(getAgentDir(), "profiles");
572
+ const profileFilesSnapshot = scope === "reusable-file" ? captureProfileFiles(profilesDir) : undefined;
573
+ let stagedRuntimeApplied = false;
574
+ try {
575
+ // Validate the edited authority surface as a session overlay first. Persistent scopes are
576
+ // written only after the complete runtime generation passes its reload doctor.
577
+ this.settingsManager.setProfileDefinition(profile.name, definition, "session");
578
+ if (!(await this.ui.handleReloadCommand())) {
579
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
580
+ return;
581
+ }
582
+ stagedRuntimeApplied = true;
583
+ if (scope !== "session") {
584
+ this.settingsManager.setProfileDefinition(profile.name, definition, scope);
585
+ await this.settingsManager.flush();
586
+ // Drop the validation-only inline winner, then refresh the registry from the now-validated
587
+ // persistent definition. The live runtime already represents these identical resources.
588
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
589
+ await this.settingsManager.reload();
590
+ }
591
+ const active = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
592
+ this.ui.footerDataProvider.setExtensionStatus("profile", active);
593
+ this.ui.invalidateFooter();
594
+ this.ui.updateEditorBorderColor();
595
+ this.ui.showStatus(`Saved profile "${profile.name}" to ${scope}.`);
596
+ }
597
+ catch (error) {
598
+ const rollbackError = stagedRuntimeApplied
599
+ ? await this.rollbackValidatedProfileMutation(settingsSnapshot, {
600
+ profileName: profile.name,
601
+ scope,
602
+ profileFilesSnapshot,
603
+ })
604
+ : undefined;
605
+ if (!stagedRuntimeApplied)
606
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
607
+ const message = error instanceof Error ? error.message : String(error);
608
+ this.ui.showError(rollbackError
609
+ ? `${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
610
+ : message);
611
+ }
612
+ }
613
+ async rollbackValidatedProfileMutation(settingsSnapshot, definition) {
614
+ const errors = [];
615
+ try {
616
+ if (definition?.scope === "reusable-file") {
617
+ restoreProfileFiles(path.join(getAgentDir(), "profiles"), definition.profileFilesSnapshot);
618
+ }
619
+ else if (definition && definition.scope !== "session") {
620
+ this.settingsManager.restoreProfileDefinitionFromReloadSnapshot(definition.profileName, definition.scope, settingsSnapshot);
621
+ }
622
+ const global = settingsSnapshot.globalSettings;
623
+ this.settingsManager.replaceGlobalResourceProfileConfiguration({
624
+ resourceProfiles: global.resourceProfiles,
625
+ activeResourceProfile: global.activeResourceProfile,
626
+ activeResourceProfiles: global.activeResourceProfiles,
627
+ externalResourceRoots: global.externalResourceRoots,
628
+ trustedResourceRoots: global.trustedResourceRoots,
629
+ });
630
+ await this.settingsManager.flush();
631
+ }
632
+ catch (error) {
633
+ errors.push(`persistence: ${error instanceof Error ? error.message : String(error)}`);
634
+ }
635
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
636
+ try {
637
+ if (!(await this.ui.handleReloadCommand())) {
638
+ errors.push("runtime: the previous profile runtime could not be restored");
639
+ }
640
+ }
641
+ catch (error) {
642
+ errors.push(`runtime: ${error instanceof Error ? error.message : String(error)}`);
643
+ }
644
+ return errors.length > 0 ? new Error(errors.join("; ")) : undefined;
645
+ }
548
646
  async promptScopeChangeForProfile(profileName, currentScope) {
549
647
  const scopeOptions = [
550
648
  { value: "session", label: "session", description: "Runtime only (not written to disk)" },
@@ -593,14 +691,21 @@ export class ProfileMenuController {
593
691
  const normalizedName = profileName.trim();
594
692
  const normalizedLower = normalizedName.toLowerCase();
595
693
  if (normalizedName.length === 0 || normalizedLower === "none" || normalizedLower === "(none)") {
694
+ const settingsSnapshot = this.settingsManager.createReloadSnapshot();
695
+ let stagedRuntimeApplied = false;
596
696
  try {
597
697
  this.settingsManager.setRuntimeResourceProfiles([]);
598
- // Clearing must also survive restarts (otherwise the old global selection returns).
698
+ if (!(await this.ui.handleReloadCommand())) {
699
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
700
+ return;
701
+ }
702
+ stagedRuntimeApplied = true;
703
+ // Persist only after the new runtime generation passes its reload doctor.
599
704
  this.settingsManager.setActiveProfile(undefined, "global");
705
+ await this.settingsManager.flush();
600
706
  this.session.sessionManager.appendCustomEntry("pi.activeResourceProfiles", {
601
707
  profiles: [],
602
708
  });
603
- await this.ui.handleReloadCommand();
604
709
  const activeProfileName = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
605
710
  this.ui.footerDataProvider.setExtensionStatus("profile", activeProfileName);
606
711
  this.ui.invalidateFooter();
@@ -608,7 +713,15 @@ export class ProfileMenuController {
608
713
  this.ui.showStatus(`Profile: ${activeProfileName}`);
609
714
  }
610
715
  catch (error) {
611
- this.ui.showError(error instanceof Error ? error.message : String(error));
716
+ const rollbackError = stagedRuntimeApplied
717
+ ? await this.rollbackValidatedProfileMutation(settingsSnapshot)
718
+ : undefined;
719
+ if (!stagedRuntimeApplied)
720
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
721
+ const message = error instanceof Error ? error.message : String(error);
722
+ this.ui.showError(rollbackError
723
+ ? `${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
724
+ : message);
612
725
  }
613
726
  return;
614
727
  }
@@ -620,48 +733,63 @@ export class ProfileMenuController {
620
733
  this.ui.showError(`Profile not found: ${profileName}`);
621
734
  return;
622
735
  }
736
+ const settingsSnapshot = this.settingsManager.createReloadSnapshot();
737
+ const modelRegistrySnapshot = profile.model ? this.session.modelRegistry.createReloadSnapshot() : undefined;
738
+ let stagedRuntimeApplied = false;
623
739
  try {
624
- let appliedModel;
740
+ const activeProfileRef = normalizedName.startsWith("./") || normalizedName.startsWith("../") ? normalizedName : profile.name;
741
+ let requestedModel;
625
742
  if (profile.model) {
626
743
  this.session.modelRegistry.refresh();
627
744
  const resolved = resolveCliModel({ cliModel: profile.model, modelRegistry: this.session.modelRegistry });
628
- if (resolved.error) {
629
- this.ui.showError(resolved.error);
630
- return;
631
- }
632
- if (resolved.warning) {
745
+ // The profile may grant an extension that contributes this model. The current generation
746
+ // cannot validate that case; the atomic reload binds new providers before authoritative
747
+ // profile resolution and rolls back if the model is still unresolved.
748
+ if (!resolved.error && resolved.warning) {
633
749
  this.ui.showWarning(resolved.warning);
634
750
  }
635
- if (resolved.model) {
636
- await this.session.setModel(resolved.model, { persistSettings: false });
637
- appliedModel = resolved.model;
638
- }
639
- if (resolved.thinkingLevel && !profile.thinking) {
640
- this.session.setThinkingLevel(resolved.thinkingLevel, { persistSettings: false });
641
- }
751
+ if (!resolved.error)
752
+ requestedModel = resolved.model;
642
753
  }
643
- if (profile.thinking) {
644
- this.session.setThinkingLevel(profile.thinking, { persistSettings: false });
754
+ // Stage the complete situation in memory. Runtime reload applies model/thinking, resource
755
+ // grants, extensions, skills, prompts, and soul together; explicit launch overrides still win.
756
+ this.settingsManager.setRuntimeResourceProfiles([activeProfileRef]);
757
+ if (!(await this.ui.handleReloadCommand())) {
758
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
759
+ if (modelRegistrySnapshot)
760
+ this.session.modelRegistry.restoreReloadSnapshot(modelRegistrySnapshot);
761
+ return;
645
762
  }
646
- this.settingsManager.setRuntimeResourceProfiles([profile.name]);
647
- // Selection must survive pi restarts: persist globally (like model/theme selections).
648
- // Runtime + session-entry alone made every /profile choice evaporate on exit.
649
- this.settingsManager.setActiveProfile(profile.name, "global");
763
+ stagedRuntimeApplied = true;
764
+ // Selection survives restarts only after the new runtime generation passes its reload doctor.
765
+ this.settingsManager.setActiveProfile(activeProfileRef, "global");
766
+ await this.settingsManager.flush();
650
767
  this.session.sessionManager.appendCustomEntry("pi.activeResourceProfiles", {
651
- profiles: [profile.name],
768
+ profiles: [activeProfileRef],
652
769
  });
653
- await this.ui.handleReloadCommand();
654
770
  this.ui.footerDataProvider.setExtensionStatus("profile", profile.name);
655
771
  this.ui.invalidateFooter();
656
772
  this.ui.updateEditorBorderColor();
657
773
  this.ui.showStatus(`Profile: ${profile.name}`);
658
- if (appliedModel) {
659
- void this.ui.maybeWarnAboutAnthropicSubscriptionAuth(appliedModel);
660
- this.ui.checkDaxnutsEasterEgg(appliedModel);
774
+ if (requestedModel &&
775
+ this.session.model?.provider === requestedModel.provider &&
776
+ this.session.model.id === requestedModel.id) {
777
+ void this.ui.maybeWarnAboutAnthropicSubscriptionAuth(requestedModel);
778
+ this.ui.checkDaxnutsEasterEgg(requestedModel);
661
779
  }
662
780
  }
663
781
  catch (error) {
664
- this.ui.showError(error instanceof Error ? error.message : String(error));
782
+ const rollbackError = stagedRuntimeApplied
783
+ ? await this.rollbackValidatedProfileMutation(settingsSnapshot)
784
+ : undefined;
785
+ if (!stagedRuntimeApplied)
786
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
787
+ if (modelRegistrySnapshot)
788
+ this.session.modelRegistry.restoreReloadSnapshot(modelRegistrySnapshot);
789
+ const message = error instanceof Error ? error.message : String(error);
790
+ this.ui.showError(rollbackError
791
+ ? `${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
792
+ : message);
665
793
  }
666
794
  }
667
795
  /** Map where a profile currently lives to the scope we should write it back to. */
@@ -672,15 +800,18 @@ export class ProfileMenuController {
672
800
  case "directory-overlay":
673
801
  case "embedded":
674
802
  return "directory";
803
+ case "project-settings":
804
+ return "project";
675
805
  case "inline":
676
806
  return "session";
677
807
  default:
678
- return "global"; // "settings"
808
+ return "global";
679
809
  }
680
810
  }
681
811
  async refreshAfterProfileMutation(profileName) {
682
812
  if (this.settingsManager.getActiveResourceProfileNames().includes(profileName)) {
683
- await this.ui.handleReloadCommand();
813
+ if (!(await this.ui.handleReloadCommand()))
814
+ return;
684
815
  const active = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
685
816
  this.ui.footerDataProvider.setExtensionStatus("profile", active);
686
817
  this.ui.invalidateFooter();
@@ -822,20 +953,72 @@ export class ProfileMenuController {
822
953
  this.ui.showError(error instanceof Error ? error.message : String(error));
823
954
  }
824
955
  }
825
- deleteProfileFromSource(profileName) {
826
- const profile = this.settingsManager.getProfileRegistry().getProfile(profileName);
956
+ async deleteProfileFromSource(profileName) {
957
+ const registry = this.settingsManager.getProfileRegistry();
958
+ const profile = registry.getProfile(profileName);
827
959
  if (!profile) {
828
960
  this.ui.showError(`Profile not found: ${profileName}`);
829
961
  return;
830
962
  }
831
- const scope = this.scopeForProfileSource(profile.source);
963
+ const scope = deletionScopeForProfile(profile);
964
+ if (!scope) {
965
+ const location = profile.sourcePath ? ` at ${profile.sourcePath}` : "";
966
+ this.ui.showError(`Profile "${profileName}" comes from read-only source ${profile.source}${location}; edit or remove that source definition directly.`);
967
+ return;
968
+ }
969
+ const wasActive = this.settingsManager.getActiveResourceProfileNames().some((profileRef) => {
970
+ if (profileRef === profileName || profileRef === profile.name)
971
+ return true;
972
+ const activeProfile = profileRef.startsWith("./") || profileRef.startsWith("../")
973
+ ? registry.resolveProfileRef(profileRef, this.sessionManager.getCwd())
974
+ : registry.getProfile(profileRef);
975
+ return Boolean(activeProfile && activeProfile.name === profile.name && activeProfile.sourcePath === profile.sourcePath);
976
+ });
977
+ const settingsSnapshot = this.settingsManager.createReloadSnapshot();
978
+ const profileFilesSnapshot = scope === "reusable-file" ? captureProfileFiles(path.join(getAgentDir(), "profiles")) : undefined;
979
+ let switchedToNone = false;
832
980
  try {
981
+ if (wasActive) {
982
+ // Stage an explicit empty runtime selection while the definition is still intact. The
983
+ // full reload can now remove the profile's extensions, tools, providers, model, and
984
+ // memory generation atomically. Persisting the deletion is deliberately deferred until
985
+ // that generation passes its doctor, so a failed reload has no on-disk deletion to undo.
986
+ this.settingsManager.setRuntimeResourceProfiles([]);
987
+ if (!(await this.ui.handleReloadCommand())) {
988
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
989
+ return;
990
+ }
991
+ switchedToNone = true;
992
+ }
833
993
  this.settingsManager.deleteProfile(profileName, scope);
994
+ const remaining = this.settingsManager.getProfileRegistry().getProfile(profileName);
995
+ if (remaining && remaining.source === profile.source && remaining.sourcePath === profile.sourcePath) {
996
+ throw new Error(`Profile "${profileName}" was not removed from ${profile.source}.`);
997
+ }
998
+ if (wasActive) {
999
+ this.settingsManager.setActiveProfile(undefined, "global");
1000
+ await this.settingsManager.flush();
1001
+ this.session.sessionManager.appendCustomEntry("pi.activeResourceProfiles", { profiles: [] });
1002
+ this.ui.footerDataProvider.setExtensionStatus("profile", "(none)");
1003
+ this.ui.invalidateFooter();
1004
+ this.ui.updateEditorBorderColor();
1005
+ }
834
1006
  this.ui.showStatus(`Deleted profile "${profileName}" from ${scope}.`);
835
- void this.refreshAfterProfileMutation(profileName);
836
1007
  }
837
1008
  catch (error) {
838
- this.ui.showError(error instanceof Error ? error.message : String(error));
1009
+ const rollbackError = switchedToNone
1010
+ ? await this.rollbackValidatedProfileMutation(settingsSnapshot, {
1011
+ profileName,
1012
+ scope,
1013
+ profileFilesSnapshot,
1014
+ })
1015
+ : undefined;
1016
+ if (!switchedToNone)
1017
+ this.settingsManager.restoreReloadSnapshot(settingsSnapshot);
1018
+ const message = error instanceof Error ? error.message : String(error);
1019
+ this.ui.showError(rollbackError
1020
+ ? `${message}; rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
1021
+ : message);
839
1022
  }
840
1023
  }
841
1024
  async addExternalResourceRootFlow() {