@caupulican/pi-adaptative 0.80.42 → 0.80.44

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 (101) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/bundled-resources/prompts/extensionify.md +20 -0
  3. package/dist/bundled-resources/prompts/learn.md +27 -0
  4. package/dist/bundled-resources/prompts/skillify.md +21 -0
  5. package/dist/bundled-resources/skills/pi-harness-learning/SKILL.md +217 -0
  6. package/dist/bundled-resources/skills/skill-architect/SKILL.md +162 -0
  7. package/dist/config.d.ts +17 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +30 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +37 -2
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +215 -9
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/context-gc.d.ts.map +1 -1
  16. package/dist/core/context-gc.js +27 -5
  17. package/dist/core/context-gc.js.map +1 -1
  18. package/dist/core/extensions/loader.d.ts +10 -3
  19. package/dist/core/extensions/loader.d.ts.map +1 -1
  20. package/dist/core/extensions/loader.js +38 -7
  21. package/dist/core/extensions/loader.js.map +1 -1
  22. package/dist/core/extensions/runner.d.ts +6 -0
  23. package/dist/core/extensions/runner.d.ts.map +1 -1
  24. package/dist/core/extensions/runner.js +53 -6
  25. package/dist/core/extensions/runner.js.map +1 -1
  26. package/dist/core/extensions/types.d.ts +16 -2
  27. package/dist/core/extensions/types.d.ts.map +1 -1
  28. package/dist/core/extensions/types.js.map +1 -1
  29. package/dist/core/profile-registry.d.ts +39 -0
  30. package/dist/core/profile-registry.d.ts.map +1 -0
  31. package/dist/core/profile-registry.js +230 -0
  32. package/dist/core/profile-registry.js.map +1 -0
  33. package/dist/core/profile-resource-selection.d.ts +19 -0
  34. package/dist/core/profile-resource-selection.d.ts.map +1 -0
  35. package/dist/core/profile-resource-selection.js +84 -0
  36. package/dist/core/profile-resource-selection.js.map +1 -0
  37. package/dist/core/resource-loader.d.ts +33 -3
  38. package/dist/core/resource-loader.d.ts.map +1 -1
  39. package/dist/core/resource-loader.js +68 -11
  40. package/dist/core/resource-loader.js.map +1 -1
  41. package/dist/core/settings-manager.d.ts +44 -2
  42. package/dist/core/settings-manager.d.ts.map +1 -1
  43. package/dist/core/settings-manager.js +557 -27
  44. package/dist/core/settings-manager.js.map +1 -1
  45. package/dist/core/skills.d.ts +5 -0
  46. package/dist/core/skills.d.ts.map +1 -1
  47. package/dist/core/skills.js +2 -2
  48. package/dist/core/skills.js.map +1 -1
  49. package/dist/core/slash-commands.d.ts.map +1 -1
  50. package/dist/core/slash-commands.js +1 -0
  51. package/dist/core/slash-commands.js.map +1 -1
  52. package/dist/core/system-prompt.d.ts.map +1 -1
  53. package/dist/core/system-prompt.js +5 -17
  54. package/dist/core/system-prompt.js.map +1 -1
  55. package/dist/core/tools/extensionify.d.ts +33 -0
  56. package/dist/core/tools/extensionify.d.ts.map +1 -0
  57. package/dist/core/tools/extensionify.js +146 -0
  58. package/dist/core/tools/extensionify.js.map +1 -0
  59. package/dist/core/tools/index.d.ts +10 -1
  60. package/dist/core/tools/index.d.ts.map +1 -1
  61. package/dist/core/tools/index.js +36 -1
  62. package/dist/core/tools/index.js.map +1 -1
  63. package/dist/core/tools/skill-audit.d.ts +63 -0
  64. package/dist/core/tools/skill-audit.d.ts.map +1 -0
  65. package/dist/core/tools/skill-audit.js +175 -0
  66. package/dist/core/tools/skill-audit.js.map +1 -0
  67. package/dist/core/tools/skillify.d.ts +30 -0
  68. package/dist/core/tools/skillify.d.ts.map +1 -0
  69. package/dist/core/tools/skillify.js +91 -0
  70. package/dist/core/tools/skillify.js.map +1 -0
  71. package/dist/index.d.ts +1 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +4 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/modes/interactive/components/profile-resource-editor.d.ts +50 -0
  76. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -0
  77. package/dist/modes/interactive/components/profile-resource-editor.js +232 -0
  78. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -0
  79. package/dist/modes/interactive/components/profile-selector.d.ts +8 -0
  80. package/dist/modes/interactive/components/profile-selector.d.ts.map +1 -0
  81. package/dist/modes/interactive/components/profile-selector.js +77 -0
  82. package/dist/modes/interactive/components/profile-selector.js.map +1 -0
  83. package/dist/modes/interactive/components/settings-selector.d.ts +7 -0
  84. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/settings-selector.js +62 -0
  86. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.d.ts +12 -0
  88. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  89. package/dist/modes/interactive/interactive-mode.js +362 -24
  90. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  91. package/docs/settings.md +20 -1
  92. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  93. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  94. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  95. package/examples/extensions/sandbox/package-lock.json +2 -2
  96. package/examples/extensions/sandbox/package.json +1 -1
  97. package/examples/extensions/with-deps/package-lock.json +2 -2
  98. package/examples/extensions/with-deps/package.json +1 -1
  99. package/examples/sdk/12-full-control.ts +4 -0
  100. package/npm-shrinkwrap.json +12 -12
  101. package/package.json +6 -6
@@ -18,7 +18,7 @@ import { FooterDataProvider } from "../../core/footer-data-provider.js";
18
18
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
19
19
  import { KeybindingsManager } from "../../core/keybindings.js";
20
20
  import { createCompactionSummaryMessage } from "../../core/messages.js";
21
- import { cliProviderAliases, defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope, } from "../../core/model-resolver.js";
21
+ import { cliProviderAliases, defaultModelPerProvider, findExactModelReferenceMatch, resolveCliModel, resolveModelScope, } from "../../core/model-resolver.js";
22
22
  import { DefaultPackageManager } from "../../core/package-manager.js";
23
23
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
24
24
  import { getPendingReloadBlockers } from "../../core/reload-blockers.js";
@@ -26,6 +26,7 @@ import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../cor
26
26
  import { isAutoLearnSessionId, SessionManager } from "../../core/session-manager.js";
27
27
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
28
28
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
29
+ import { allToolNames } from "../../core/tools/index.js";
29
30
  import { hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manager.js";
30
31
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
31
32
  import { copyToClipboard } from "../../utils/clipboard.js";
@@ -56,6 +57,8 @@ import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./c
56
57
  import { LoginDialogComponent } from "./components/login-dialog.js";
57
58
  import { ModelSelectorComponent } from "./components/model-selector.js";
58
59
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
60
+ import { ProfileResourceEditorComponent, } from "./components/profile-resource-editor.js";
61
+ import { ProfileSelectorComponent } from "./components/profile-selector.js";
59
62
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
60
63
  import { SessionSelectorComponent } from "./components/session-selector.js";
61
64
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
@@ -101,6 +104,7 @@ const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is acti
101
104
  const AUTO_LEARN_DEFAULTS = {
102
105
  enabled: false,
103
106
  model: "active",
107
+ thinkingLevel: "low",
104
108
  longSessionMessages: 32,
105
109
  longSessionContextPercent: 70,
106
110
  cooldownMinutes: 24 * 60,
@@ -108,36 +112,39 @@ const AUTO_LEARN_DEFAULTS = {
108
112
  maxConcurrentLearners: 1,
109
113
  applyHighConfidence: false,
110
114
  reflectionReview: true,
111
- reflectionMinToolCalls: 5,
115
+ reflectionMinToolCalls: 12,
112
116
  reflectionCooldownMinutes: 24 * 60,
117
+ complexTaskToolCalls: 12,
113
118
  };
114
119
  const AUTONOMY_AUTO_LEARN_PRESETS = {
115
120
  off: { ...AUTO_LEARN_DEFAULTS, enabled: false, reflectionReview: false },
116
121
  safe: {
117
122
  ...AUTO_LEARN_DEFAULTS,
118
- enabled: true,
123
+ enabled: false,
119
124
  longSessionMessages: 64,
120
125
  longSessionContextPercent: 85,
121
126
  cooldownMinutes: 24 * 60,
122
127
  leaseMinutes: 60,
123
128
  maxConcurrentLearners: 1,
124
129
  applyHighConfidence: false,
125
- reflectionReview: true,
126
- reflectionMinToolCalls: 5,
130
+ reflectionReview: false,
131
+ reflectionMinToolCalls: 12,
127
132
  reflectionCooldownMinutes: 24 * 60,
133
+ complexTaskToolCalls: 12,
128
134
  },
129
135
  balanced: {
130
136
  ...AUTO_LEARN_DEFAULTS,
131
- enabled: true,
137
+ enabled: false,
132
138
  longSessionMessages: 64,
133
139
  longSessionContextPercent: 85,
134
140
  cooldownMinutes: 24 * 60,
135
141
  leaseMinutes: 90,
136
142
  maxConcurrentLearners: 1,
137
143
  applyHighConfidence: false,
138
- reflectionReview: true,
139
- reflectionMinToolCalls: 5,
144
+ reflectionReview: false,
145
+ reflectionMinToolCalls: 12,
140
146
  reflectionCooldownMinutes: 24 * 60,
147
+ complexTaskToolCalls: 12,
141
148
  },
142
149
  full: {
143
150
  ...AUTO_LEARN_DEFAULTS,
@@ -149,14 +156,13 @@ const AUTONOMY_AUTO_LEARN_PRESETS = {
149
156
  maxConcurrentLearners: 1,
150
157
  applyHighConfidence: true,
151
158
  reflectionReview: true,
152
- reflectionMinToolCalls: 5,
159
+ reflectionMinToolCalls: 12,
153
160
  reflectionCooldownMinutes: 24 * 60,
161
+ complexTaskToolCalls: 12,
154
162
  },
155
163
  };
156
164
  const AUTONOMY_MODES = ["off", "safe", "balanced", "full"];
157
165
  const AUTO_LEARN_RESERVATION_MS = 2 * 60 * 1000;
158
- const AUTO_LEARN_THINKING_LEVEL = "xhigh";
159
- const AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS = 5;
160
166
  export const AUTO_LEARN_HISTORY_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
161
167
  function definedStringSet(values) {
162
168
  const set = new Set();
@@ -434,6 +440,7 @@ export class InteractiveMode {
434
440
  skillCommands = new Map();
435
441
  // Agent subscription unsubscribe function
436
442
  unsubscribe;
443
+ unsubscribeExtensionsChanged;
437
444
  signalCleanupHandlers = [];
438
445
  // Track if editor is in bash mode (text starts with !)
439
446
  isBashMode = false;
@@ -750,6 +757,10 @@ export class InteractiveMode {
750
757
  this.isInitialized = true;
751
758
  // Initialize extensions first so resources are shown before messages
752
759
  await this.rebindCurrentSession();
760
+ // Register extensions-changed listener for live reload UI refresh
761
+ this.unsubscribeExtensionsChanged = this.session.onExtensionsChanged(() => {
762
+ this.refreshUIAfterExtensionsChanged();
763
+ });
753
764
  // Render initial messages AFTER showing loaded resources
754
765
  await this.renderInitialMessages();
755
766
  this.renderProjectTrustWarningIfNeeded();
@@ -2437,6 +2448,12 @@ export class InteractiveMode {
2437
2448
  await this.handleModelCommand(searchTerm);
2438
2449
  return;
2439
2450
  }
2451
+ if (text === "/profiles" || text.startsWith("/profiles ")) {
2452
+ const rawProfileName = text.startsWith("/profiles ") ? text.slice(10).trim() : undefined;
2453
+ this.editor.setText("");
2454
+ await this.handleProfilesCommand(rawProfileName?.length ? rawProfileName : undefined);
2455
+ return;
2456
+ }
2440
2457
  if (text === "/export" || text.startsWith("/export ")) {
2441
2458
  await this.handleExportCommand(text);
2442
2459
  this.editor.setText("");
@@ -4134,6 +4151,8 @@ export class InteractiveMode {
4134
4151
  reflectionReview: settings.reflectionReview ?? preset.reflectionReview,
4135
4152
  reflectionMinToolCalls: settings.reflectionMinToolCalls ?? preset.reflectionMinToolCalls,
4136
4153
  reflectionCooldownMinutes: settings.reflectionCooldownMinutes ?? preset.reflectionCooldownMinutes,
4154
+ complexTaskToolCalls: settings.complexTaskToolCalls ?? preset.complexTaskToolCalls,
4155
+ thinkingLevel: settings.thinkingLevel ?? preset.thinkingLevel,
4137
4156
  };
4138
4157
  }
4139
4158
  getAutoLearnTenantKey() {
@@ -4338,7 +4357,7 @@ export class InteractiveMode {
4338
4357
  const objective = options.kind === "reflection"
4339
4358
  ? "review the latest completed turn for durable memory, skill, validation, tooling, and code-baked self-improvement cues, then run one bounded continuous-learning pass if the learning tools are available"
4340
4359
  : "run one bounded continuous-learning pass for this Pi tenant";
4341
- return `You are Pi Auto Learn running as a background learner.\n\nObjective: ${objective}.\nTrigger: ${reason}.\n\n${authorityBlock}\n\nRequired workflow:\n1. Query existing durable memory/rules first when tools allow it. Memory confrontation is mandatory before accepting, merging, upgrading, or rejecting learning candidates.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}. Process candidate validation in vectorized chunks/batches; avoid scalar per-candidate memory queries except for final selected writes.\n3. Apply the learning validation tree to each candidate chunk: (a) Why is this good for the user? (b) Is it unique, or similar to existing memory/skills/agents so it should merge or upgrade existing knowledge? (c) Will this make Pi a better agent? Candidates that cannot answer all three are noise.\n4. Hermes-style learning cycle: after a complex task (${AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS}+ tool calls), user correction, repeated steering pattern, non-trivial fix/workaround/debugging path, loaded-skill defect, trigger gap, tool gap, or harness workflow defect, actively create or update durable learning artifacts. Memory stores compact facts/preferences/state; skills/prompts/agents/extensions/source store procedural behavior. When a lesson changes how Pi should act on a future class of task, memory alone is not completion.\n5. Skill update preference order: (1) patch the currently loaded or consulted skill that governed the task; (2) patch an existing class-level umbrella skill/agent/prompt; (3) add a support file under references/, templates/, or scripts/ and add a SKILL.md pointer; (4) create a new class-level umbrella skill only when no existing artifact fits. Never create one-off PR/error/codename/session skills.\n6. Behavioral self-improvement is code-baked by default: prefer the lowest durable executable layer that fixes the behavior — patch an existing skill/prompt/agent/extension/tool, tune an approved setting, or edit the authorized Pi source when source authority is available. Use Automata only for concise facts/evidence pointers that support the baked change.\n7. Do not harden transient or environment-dependent failures into durable behavior: missing binaries, fresh-install package gaps, credentials not configured, path mismatches, one-off task narratives, or negative tool-broken claims should become setup/troubleshooting fixes only when the fix itself is reusable.\n8. Treat the latest-turn digest as current-session evidence only; do not auto-commit one-off cues unless deterministic tooling and memory confrontation corroborate them.\n9. In mode=full, apply safe memory/skill/user-extension/authorized-source improvements under the standing grant above; otherwise keep them proposal-gated.\n10. Never cross hard-stop boundaries from the authority policy.\n11. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n12. Finish with PASS, BLOCKED, or FAIL and concise evidence, including chunk counts, merge/upgrade/code-bake decisions, changed paths/settings, validation, and cleanup/purge status.${reflectionBlock}`;
4360
+ return `You are Pi Auto Learn running as a background learner.\n\nObjective: ${objective}.\nTrigger: ${reason}.\n\n${authorityBlock}\n\nRequired workflow:\n1. Query existing durable memory/rules first when tools allow it. Memory confrontation is mandatory before accepting, merging, upgrading, or rejecting learning candidates.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}. Process candidate validation in vectorized chunks/batches; avoid scalar per-candidate memory queries except for final selected writes.\n3. Apply the learning validation tree to each candidate chunk: (a) Why is this good for the user? (b) Is it unique, or similar to existing memory/skills/agents so it should merge or upgrade existing knowledge? (c) Will this make Pi a better agent? Candidates that cannot answer all three are noise.\n4. Hermes-style learning cycle: after a complex task (${settings.complexTaskToolCalls}+ tool calls), user correction, repeated steering pattern, non-trivial fix/workaround/debugging path, loaded-skill defect, trigger gap, tool gap, or harness workflow defect, actively create or update durable learning artifacts. Memory stores compact facts/preferences/state; skills/prompts/agents/extensions/source store procedural behavior. When a lesson changes how Pi should act on a future class of task, memory alone is not completion.\n5. Skill update preference order: (1) patch the currently loaded or consulted skill that governed the task; (2) patch an existing class-level umbrella skill/agent/prompt; (3) add a support file under references/, templates/, or scripts/ and add a SKILL.md pointer; (4) create a new class-level umbrella skill only when no existing artifact fits. Never create one-off PR/error/codename/session skills.\n6. Behavioral self-improvement is code-baked by default: prefer the lowest durable executable layer that fixes the behavior — patch an existing skill/prompt/agent/extension/tool, tune an approved setting, or edit the authorized Pi source when source authority is available. Use Automata only for concise facts/evidence pointers that support the baked change.\n7. Do not harden transient or environment-dependent failures into durable behavior: missing binaries, fresh-install package gaps, credentials not configured, path mismatches, one-off task narratives, or negative tool-broken claims should become setup/troubleshooting fixes only when the fix itself is reusable.\n8. Treat the latest-turn digest as current-session evidence only; do not auto-commit one-off cues unless deterministic tooling and memory confrontation corroborate them.\n9. In mode=full, apply safe memory/skill/user-extension/authorized-source improvements under the standing grant above; otherwise keep them proposal-gated.\n10. Never cross hard-stop boundaries from the authority policy.\n11. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n12. Finish with PASS, BLOCKED, or FAIL and concise evidence, including chunk counts, merge/upgrade/code-bake decisions, changed paths/settings, validation, and cleanup/purge status.${reflectionBlock}`;
4342
4361
  }
4343
4362
  reserveAutoLearnRun(params) {
4344
4363
  return this.withAutoLearnStateLock((current) => {
@@ -4468,7 +4487,7 @@ export class InteractiveMode {
4468
4487
  const args = buildAutoLearnSpawnArgs(spawnTarget, {
4469
4488
  name: `Auto Learn ${runId}`,
4470
4489
  modelPattern,
4471
- thinkingLevel: AUTO_LEARN_THINKING_LEVEL,
4490
+ thinkingLevel: settings.thinkingLevel ?? "low",
4472
4491
  sessionDir,
4473
4492
  sessionId,
4474
4493
  promptPath,
@@ -4630,7 +4649,6 @@ export class InteractiveMode {
4630
4649
  }
4631
4650
  evaluateAutonomyReview(messages) {
4632
4651
  const settings = this.getEffectiveAutoLearnSettings();
4633
- const autonomy = this.settingsManager.getAutonomySettings();
4634
4652
  const state = this.withAutoLearnStateLock((current) => {
4635
4653
  const pruned = this.pruneAutoLearnHistoryFromState(current);
4636
4654
  return { result: pruned, next: pruned };
@@ -4651,7 +4669,8 @@ export class InteractiveMode {
4651
4669
  const correctionSignal = /\b(next time|for future|from now on|remember this|don't|do not|avoid|instead|you should|should have|you forgot|you missed|not what i asked|wrong again)\b/i.test(userText);
4652
4670
  const behavioralSelfImprovementSignal = /\b(harness|pi|agent|autonomy|autonomous|self[- ]?improv(?:e|ement|ing)?|steer(?:ing)?|trigger(?:s)?|skill(?:s)?|code[- ]?bak(?:e|ed)|bake(?:d)? into code|not (?:automata|memory)|reference agent|hermes)\b/i.test(userText) &&
4653
4671
  /\b(improve|automatic(?:ally)?|autonomous|trigger|fire|skill|steer|self[- ]?improv(?:e|ement|ing)?|code[- ]?bak(?:e|ed)|bake(?:d)?|too much|less)\b/i.test(userText);
4654
- const complexTaskSignal = toolCalls >= AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS;
4672
+ const complexTaskThreshold = Math.max(1, settings.complexTaskToolCalls ?? 12);
4673
+ const complexTaskSignal = toolCalls >= complexTaskThreshold;
4655
4674
  const bypassCooldown = correctionSignal || behavioralSelfImprovementSignal || complexTaskSignal;
4656
4675
  const base = { messageCount, contextPercent, cooldownRemainingMs, runningCount, toolCalls };
4657
4676
  if (!settings.enabled)
@@ -4690,19 +4709,12 @@ export class InteractiveMode {
4690
4709
  return {
4691
4710
  ...base,
4692
4711
  shouldRun: true,
4693
- reason: `reflection complex task learning signal (${toolCalls}/${AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS} tool calls)`,
4712
+ reason: `reflection complex task learning signal (${toolCalls}/${complexTaskThreshold} tool calls)`,
4694
4713
  digest: this.buildAutonomyReviewDigest(messages),
4695
4714
  bypassCooldown: true,
4696
4715
  };
4697
4716
  }
4698
- if (autonomy.mode === "full") {
4699
- return {
4700
- ...base,
4701
- shouldRun: true,
4702
- reason: "full autonomy post-turn review",
4703
- digest: this.buildAutonomyReviewDigest(messages),
4704
- };
4705
- }
4717
+ // Full autonomy expands allowed action scope for triggered reviews; it does not make every turn a review trigger.
4706
4718
  if (toolCalls >= settings.reflectionMinToolCalls) {
4707
4719
  return {
4708
4720
  ...base,
@@ -4859,6 +4871,21 @@ export class InteractiveMode {
4859
4871
  showSettingsSelector() {
4860
4872
  this.showSelector((done) => {
4861
4873
  const projectSettings = this.settingsManager.getProjectSettings();
4874
+ const profileOptions = [
4875
+ {
4876
+ value: "(none)",
4877
+ label: "(none)",
4878
+ description: "Use configured profile selection (session default)",
4879
+ },
4880
+ ...this.settingsManager
4881
+ .getProfileRegistry()
4882
+ .listProfiles()
4883
+ .map((profile) => ({
4884
+ value: profile.name,
4885
+ label: profile.name,
4886
+ description: profile.description ?? profile.source,
4887
+ })),
4888
+ ];
4862
4889
  const selector = new SettingsSelectorComponent({
4863
4890
  autoCompact: this.session.autoCompactionEnabled,
4864
4891
  showImages: this.settingsManager.getShowImages(),
@@ -4896,6 +4923,8 @@ export class InteractiveMode {
4896
4923
  currentModelPattern: this.session.model
4897
4924
  ? `${this.session.model.provider}/${this.session.model.id}`
4898
4925
  : undefined,
4926
+ activeProfileName: this.settingsManager.getActiveResourceProfileNames()[0],
4927
+ profileOptions,
4899
4928
  }, {
4900
4929
  onAutoCompactChange: (enabled) => {
4901
4930
  this.session.setAutoCompactionEnabled(enabled);
@@ -5038,6 +5067,22 @@ export class InteractiveMode {
5038
5067
  this.updateAutoLearnFooter();
5039
5068
  this.showStatus(`Auto Learn settings saved to ${scope}. Use /auto-learn status or /auto-learn run.`);
5040
5069
  },
5070
+ onProfileChange: (profile) => {
5071
+ done();
5072
+ void this.applyProfile(profile);
5073
+ },
5074
+ onProfileEdit: (profileName) => {
5075
+ done();
5076
+ void this.openProfileResourceEditor(profileName);
5077
+ },
5078
+ onProfilePersistActive: (scope) => {
5079
+ done();
5080
+ this.persistActiveProfile(scope);
5081
+ },
5082
+ onProfileDelete: (profileName) => {
5083
+ done();
5084
+ this.deleteProfileFromSource(profileName);
5085
+ },
5041
5086
  onCancel: () => {
5042
5087
  done();
5043
5088
  this.ui.requestRender();
@@ -5046,6 +5091,231 @@ export class InteractiveMode {
5046
5091
  return { component: selector, focus: selector.getSettingsList() };
5047
5092
  });
5048
5093
  }
5094
+ async handleProfilesCommand(profileName) {
5095
+ if (profileName) {
5096
+ await this.applyProfile(profileName);
5097
+ return;
5098
+ }
5099
+ const registry = this.settingsManager.getProfileRegistry();
5100
+ const profiles = registry.listProfiles();
5101
+ if (profiles.length === 0) {
5102
+ this.showWarning("No profiles found. Add resourceProfiles to settings or JSON files under ~/.pi/agent/profiles/.");
5103
+ return;
5104
+ }
5105
+ this.showSelector((done) => {
5106
+ const selector = new ProfileSelectorComponent(profiles, this.settingsManager.getActiveResourceProfileNames(), (profile) => {
5107
+ done();
5108
+ void this.applyProfile(profile);
5109
+ }, () => {
5110
+ done();
5111
+ this.ui.requestRender();
5112
+ });
5113
+ return { component: selector, focus: selector.getSelectList() };
5114
+ });
5115
+ }
5116
+ async applyProfile(profileName) {
5117
+ const normalizedName = profileName.trim();
5118
+ const normalizedLower = normalizedName.toLowerCase();
5119
+ if (normalizedName.length === 0 || normalizedLower === "none" || normalizedLower === "(none)") {
5120
+ try {
5121
+ this.settingsManager.setRuntimeResourceProfiles([]);
5122
+ this.session.sessionManager.appendCustomEntry("pi.activeResourceProfiles", {
5123
+ profiles: [],
5124
+ });
5125
+ await this.handleReloadCommand();
5126
+ const activeProfileName = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
5127
+ this.footerDataProvider.setExtensionStatus("profile", activeProfileName);
5128
+ this.footer.invalidate();
5129
+ this.updateEditorBorderColor();
5130
+ this.showStatus(`Profile: ${activeProfileName}`);
5131
+ }
5132
+ catch (error) {
5133
+ this.showError(error instanceof Error ? error.message : String(error));
5134
+ }
5135
+ return;
5136
+ }
5137
+ const registry = this.settingsManager.getProfileRegistry();
5138
+ const profile = normalizedName.startsWith("./") || normalizedName.startsWith("../")
5139
+ ? registry.resolveProfileRef(normalizedName, this.sessionManager.getCwd())
5140
+ : registry.getProfile(normalizedName);
5141
+ if (!profile) {
5142
+ this.showError(`Profile not found: ${profileName}`);
5143
+ return;
5144
+ }
5145
+ try {
5146
+ let appliedModel;
5147
+ if (profile.model) {
5148
+ this.session.modelRegistry.refresh();
5149
+ const resolved = resolveCliModel({ cliModel: profile.model, modelRegistry: this.session.modelRegistry });
5150
+ if (resolved.error) {
5151
+ this.showError(resolved.error);
5152
+ return;
5153
+ }
5154
+ if (resolved.warning) {
5155
+ this.showWarning(resolved.warning);
5156
+ }
5157
+ if (resolved.model) {
5158
+ await this.session.setModel(resolved.model, { persistSettings: false });
5159
+ appliedModel = resolved.model;
5160
+ }
5161
+ if (resolved.thinkingLevel && !profile.thinking) {
5162
+ this.session.setThinkingLevel(resolved.thinkingLevel, { persistSettings: false });
5163
+ }
5164
+ }
5165
+ if (profile.thinking) {
5166
+ this.session.setThinkingLevel(profile.thinking, { persistSettings: false });
5167
+ }
5168
+ this.settingsManager.setRuntimeResourceProfiles([profile.name]);
5169
+ this.session.sessionManager.appendCustomEntry("pi.activeResourceProfiles", {
5170
+ profiles: [profile.name],
5171
+ });
5172
+ await this.handleReloadCommand();
5173
+ this.footerDataProvider.setExtensionStatus("profile", profile.name);
5174
+ this.footer.invalidate();
5175
+ this.updateEditorBorderColor();
5176
+ this.showStatus(`Profile: ${profile.name}`);
5177
+ if (appliedModel) {
5178
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(appliedModel);
5179
+ this.checkDaxnutsEasterEgg(appliedModel);
5180
+ }
5181
+ }
5182
+ catch (error) {
5183
+ this.showError(error instanceof Error ? error.message : String(error));
5184
+ }
5185
+ }
5186
+ async getProfileResourceKinds() {
5187
+ const loader = this.session.resourceLoader;
5188
+ const base = (p) => p.split(/[\\/]/).pop() ?? p;
5189
+ // Get all discoverable extension paths (enabled and disabled) for profile filtering
5190
+ const allDiscoverableExtensions = await loader.getDiscoverableExtensionPaths();
5191
+ return [
5192
+ { kind: "tools", label: "Tools", allIds: [...allToolNames] },
5193
+ { kind: "skills", label: "Skills", allIds: loader.getSkills().skills.map((s) => s.name) },
5194
+ {
5195
+ kind: "extensions",
5196
+ label: "Extensions",
5197
+ allIds: allDiscoverableExtensions.map((e) => base(e)),
5198
+ },
5199
+ { kind: "agents", label: "Agents", allIds: loader.getAgentsFiles().agentsFiles.map((f) => base(f.path)) },
5200
+ { kind: "prompts", label: "Prompts", allIds: loader.getPrompts().prompts.map((p) => p.name) },
5201
+ { kind: "themes", label: "Themes", allIds: getAvailableThemes() },
5202
+ ];
5203
+ }
5204
+ /** Map where a profile currently lives to the scope we should write it back to. */
5205
+ scopeForProfileSource(source) {
5206
+ switch (source) {
5207
+ case "profile-file":
5208
+ return "reusable-file";
5209
+ case "directory-overlay":
5210
+ case "embedded":
5211
+ return "directory";
5212
+ case "inline":
5213
+ return "session";
5214
+ default:
5215
+ return "global"; // "settings"
5216
+ }
5217
+ }
5218
+ async refreshAfterProfileMutation(profileName) {
5219
+ if (this.settingsManager.getActiveResourceProfileNames().includes(profileName)) {
5220
+ await this.handleReloadCommand();
5221
+ const active = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
5222
+ this.footerDataProvider.setExtensionStatus("profile", active);
5223
+ this.footer.invalidate();
5224
+ this.updateEditorBorderColor();
5225
+ }
5226
+ }
5227
+ async openProfileResourceEditor(profileName) {
5228
+ const profile = this.settingsManager.getProfileRegistry().getProfile(profileName);
5229
+ if (!profile) {
5230
+ this.showError(`Profile not found: ${profileName}`);
5231
+ return;
5232
+ }
5233
+ const scope = this.scopeForProfileSource(profile.source);
5234
+ const kinds = await this.getProfileResourceKinds();
5235
+ const isActiveProfile = this.settingsManager.getActiveResourceProfileNames().includes(profile.name);
5236
+ const originalResources = profile.resources;
5237
+ this.showSelector((done) => {
5238
+ const editor = new ProfileResourceEditorComponent({
5239
+ profileName: profile.name,
5240
+ initialResources: profile.resources,
5241
+ kinds,
5242
+ onSave: (resources) => {
5243
+ done();
5244
+ try {
5245
+ this.settingsManager.setProfileDefinition(profile.name, {
5246
+ name: profile.name,
5247
+ description: profile.description,
5248
+ model: profile.model,
5249
+ thinking: profile.thinking,
5250
+ resources,
5251
+ }, scope);
5252
+ this.showStatus(`Saved profile "${profile.name}" to ${scope}.`);
5253
+ // For active profiles, detect if only extensions changed to avoid full reload
5254
+ if (isActiveProfile) {
5255
+ const extensionsChanged = originalResources.extensions !== resources.extensions;
5256
+ const otherResourcesChanged = originalResources.tools !== resources.tools ||
5257
+ originalResources.skills !== resources.skills ||
5258
+ originalResources.agents !== resources.agents ||
5259
+ originalResources.prompts !== resources.prompts ||
5260
+ originalResources.themes !== resources.themes;
5261
+ if (extensionsChanged && !otherResourcesChanged) {
5262
+ // Only extensions changed: use live reconciliation
5263
+ void this.reconcileExtensionsAndRefreshUI(profile.name);
5264
+ }
5265
+ else {
5266
+ // Other resources changed or mixed: use full reload
5267
+ void this.refreshAfterProfileMutation(profile.name);
5268
+ }
5269
+ }
5270
+ // Non-active profiles don't need refresh
5271
+ }
5272
+ catch (error) {
5273
+ this.showError(error instanceof Error ? error.message : String(error));
5274
+ }
5275
+ },
5276
+ onCancel: () => {
5277
+ done();
5278
+ this.ui.requestRender();
5279
+ },
5280
+ });
5281
+ return { component: editor, focus: editor };
5282
+ });
5283
+ }
5284
+ persistActiveProfile(scope) {
5285
+ const active = this.settingsManager.getActiveResourceProfileNames()[0];
5286
+ if (!active) {
5287
+ this.showError("No active profile to persist. Select one with /profiles first.");
5288
+ return;
5289
+ }
5290
+ try {
5291
+ if (scope === "session") {
5292
+ this.settingsManager.setRuntimeResourceProfiles([active]);
5293
+ }
5294
+ else {
5295
+ this.settingsManager.setActiveProfile(active, scope);
5296
+ }
5297
+ this.showStatus(`Active profile "${active}" persisted to ${scope}.`);
5298
+ }
5299
+ catch (error) {
5300
+ this.showError(error instanceof Error ? error.message : String(error));
5301
+ }
5302
+ }
5303
+ deleteProfileFromSource(profileName) {
5304
+ const profile = this.settingsManager.getProfileRegistry().getProfile(profileName);
5305
+ if (!profile) {
5306
+ this.showError(`Profile not found: ${profileName}`);
5307
+ return;
5308
+ }
5309
+ const scope = this.scopeForProfileSource(profile.source);
5310
+ try {
5311
+ this.settingsManager.deleteProfile(profileName, scope);
5312
+ this.showStatus(`Deleted profile "${profileName}" from ${scope}.`);
5313
+ void this.refreshAfterProfileMutation(profileName);
5314
+ }
5315
+ catch (error) {
5316
+ this.showError(error instanceof Error ? error.message : String(error));
5317
+ }
5318
+ }
5049
5319
  async handleModelCommand(searchTerm) {
5050
5320
  if (!searchTerm) {
5051
5321
  await this.showModelSelector();
@@ -5916,6 +6186,71 @@ export class InteractiveMode {
5916
6186
  this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
5917
6187
  }
5918
6188
  }
6189
+ /**
6190
+ * Refresh UI after extensions are loaded/unloaded live.
6191
+ * Performs the same refresh calls as handleReloadCommand but without the full reload.
6192
+ */
6193
+ async refreshUIAfterExtensionsChanged() {
6194
+ try {
6195
+ // Refresh keybindings and autocomplete
6196
+ this.keybindings.reload();
6197
+ this.setupAutocompleteProvider();
6198
+ // Refresh themes
6199
+ const activeHeader = this.customHeader ?? this.builtInHeader;
6200
+ if (isExpandable(activeHeader)) {
6201
+ activeHeader.setExpanded(this.toolOutputExpanded);
6202
+ }
6203
+ setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
6204
+ this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
6205
+ const themeName = this.settingsManager.getTheme();
6206
+ const themeResult = themeName ? setTheme(themeName, true) : { success: true };
6207
+ if (!themeResult.success) {
6208
+ this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
6209
+ }
6210
+ // Refresh editor settings
6211
+ const editorPaddingX = this.settingsManager.getEditorPaddingX();
6212
+ const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
6213
+ this.defaultEditor.setPaddingX(editorPaddingX);
6214
+ this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
6215
+ if (this.editor !== this.defaultEditor) {
6216
+ this.editor.setPaddingX?.(editorPaddingX);
6217
+ this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
6218
+ }
6219
+ // Refresh extension shortcuts
6220
+ const runner = this.session.extensionRunner;
6221
+ this.setupExtensionShortcuts(runner);
6222
+ // Refresh chat and UI
6223
+ await this.rebuildChatFromMessages();
6224
+ this.footer.invalidate();
6225
+ this.ui.requestRender();
6226
+ }
6227
+ catch (error) {
6228
+ this.showError(`Extension refresh failed: ${error instanceof Error ? error.message : String(error)}`);
6229
+ }
6230
+ }
6231
+ /**
6232
+ * Reconcile extensions for the active profile and refresh UI.
6233
+ * Used when only extensions change in the active profile to avoid full reload.
6234
+ */
6235
+ async reconcileExtensionsAndRefreshUI(profileName) {
6236
+ try {
6237
+ await this.session.reconcileLoadedExtensions();
6238
+ const active = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
6239
+ this.footerDataProvider.setExtensionStatus("profile", active);
6240
+ this.footer.invalidate();
6241
+ this.updateEditorBorderColor();
6242
+ }
6243
+ catch (error) {
6244
+ // On error, fall back to full reload
6245
+ try {
6246
+ await this.refreshAfterProfileMutation(profileName);
6247
+ }
6248
+ catch {
6249
+ // If full reload also fails, show error
6250
+ this.showError(`Failed to reconcile extensions: ${error instanceof Error ? error.message : String(error)}`);
6251
+ }
6252
+ }
6253
+ }
5919
6254
  async handleExportCommand(text) {
5920
6255
  const outputPath = this.getPathCommandArgument(text, "/export");
5921
6256
  try {
@@ -6466,6 +6801,9 @@ export class InteractiveMode {
6466
6801
  if (this.unsubscribe) {
6467
6802
  this.unsubscribe();
6468
6803
  }
6804
+ if (this.unsubscribeExtensionsChanged) {
6805
+ this.unsubscribeExtensionsChanged();
6806
+ }
6469
6807
  if (this.isInitialized) {
6470
6808
  this.ui.stop();
6471
6809
  this.isInitialized = false;