@caupulican/pi-adaptative 0.80.42 → 0.80.45

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 +24 -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 +8 -0
  84. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  85. package/dist/modes/interactive/components/settings-selector.js +75 -0
  86. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.d.ts +14 -0
  88. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  89. package/dist/modes/interactive/interactive-mode.js +434 -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,14 +18,16 @@ 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";
25
25
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
26
26
  import { isAutoLearnSessionId, SessionManager } from "../../core/session-manager.js";
27
+ import { validateSkillName } from "../../core/skills.js";
27
28
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
28
29
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
30
+ import { allToolNames } from "../../core/tools/index.js";
29
31
  import { hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manager.js";
30
32
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
31
33
  import { copyToClipboard } from "../../utils/clipboard.js";
@@ -56,6 +58,8 @@ import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./c
56
58
  import { LoginDialogComponent } from "./components/login-dialog.js";
57
59
  import { ModelSelectorComponent } from "./components/model-selector.js";
58
60
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
61
+ import { ProfileResourceEditorComponent, } from "./components/profile-resource-editor.js";
62
+ import { ProfileSelectorComponent } from "./components/profile-selector.js";
59
63
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
60
64
  import { SessionSelectorComponent } from "./components/session-selector.js";
61
65
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
@@ -101,6 +105,7 @@ const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is acti
101
105
  const AUTO_LEARN_DEFAULTS = {
102
106
  enabled: false,
103
107
  model: "active",
108
+ thinkingLevel: "low",
104
109
  longSessionMessages: 32,
105
110
  longSessionContextPercent: 70,
106
111
  cooldownMinutes: 24 * 60,
@@ -108,36 +113,39 @@ const AUTO_LEARN_DEFAULTS = {
108
113
  maxConcurrentLearners: 1,
109
114
  applyHighConfidence: false,
110
115
  reflectionReview: true,
111
- reflectionMinToolCalls: 5,
116
+ reflectionMinToolCalls: 12,
112
117
  reflectionCooldownMinutes: 24 * 60,
118
+ complexTaskToolCalls: 12,
113
119
  };
114
120
  const AUTONOMY_AUTO_LEARN_PRESETS = {
115
121
  off: { ...AUTO_LEARN_DEFAULTS, enabled: false, reflectionReview: false },
116
122
  safe: {
117
123
  ...AUTO_LEARN_DEFAULTS,
118
- enabled: true,
124
+ enabled: false,
119
125
  longSessionMessages: 64,
120
126
  longSessionContextPercent: 85,
121
127
  cooldownMinutes: 24 * 60,
122
128
  leaseMinutes: 60,
123
129
  maxConcurrentLearners: 1,
124
130
  applyHighConfidence: false,
125
- reflectionReview: true,
126
- reflectionMinToolCalls: 5,
131
+ reflectionReview: false,
132
+ reflectionMinToolCalls: 12,
127
133
  reflectionCooldownMinutes: 24 * 60,
134
+ complexTaskToolCalls: 12,
128
135
  },
129
136
  balanced: {
130
137
  ...AUTO_LEARN_DEFAULTS,
131
- enabled: true,
138
+ enabled: false,
132
139
  longSessionMessages: 64,
133
140
  longSessionContextPercent: 85,
134
141
  cooldownMinutes: 24 * 60,
135
142
  leaseMinutes: 90,
136
143
  maxConcurrentLearners: 1,
137
144
  applyHighConfidence: false,
138
- reflectionReview: true,
139
- reflectionMinToolCalls: 5,
145
+ reflectionReview: false,
146
+ reflectionMinToolCalls: 12,
140
147
  reflectionCooldownMinutes: 24 * 60,
148
+ complexTaskToolCalls: 12,
141
149
  },
142
150
  full: {
143
151
  ...AUTO_LEARN_DEFAULTS,
@@ -149,14 +157,13 @@ const AUTONOMY_AUTO_LEARN_PRESETS = {
149
157
  maxConcurrentLearners: 1,
150
158
  applyHighConfidence: true,
151
159
  reflectionReview: true,
152
- reflectionMinToolCalls: 5,
160
+ reflectionMinToolCalls: 12,
153
161
  reflectionCooldownMinutes: 24 * 60,
162
+ complexTaskToolCalls: 12,
154
163
  },
155
164
  };
156
165
  const AUTONOMY_MODES = ["off", "safe", "balanced", "full"];
157
166
  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
167
  export const AUTO_LEARN_HISTORY_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
161
168
  function definedStringSet(values) {
162
169
  const set = new Set();
@@ -434,6 +441,7 @@ export class InteractiveMode {
434
441
  skillCommands = new Map();
435
442
  // Agent subscription unsubscribe function
436
443
  unsubscribe;
444
+ unsubscribeExtensionsChanged;
437
445
  signalCleanupHandlers = [];
438
446
  // Track if editor is in bash mode (text starts with !)
439
447
  isBashMode = false;
@@ -750,6 +758,10 @@ export class InteractiveMode {
750
758
  this.isInitialized = true;
751
759
  // Initialize extensions first so resources are shown before messages
752
760
  await this.rebindCurrentSession();
761
+ // Register extensions-changed listener for live reload UI refresh
762
+ this.unsubscribeExtensionsChanged = this.session.onExtensionsChanged(() => {
763
+ this.refreshUIAfterExtensionsChanged();
764
+ });
753
765
  // Render initial messages AFTER showing loaded resources
754
766
  await this.renderInitialMessages();
755
767
  this.renderProjectTrustWarningIfNeeded();
@@ -2437,6 +2449,12 @@ export class InteractiveMode {
2437
2449
  await this.handleModelCommand(searchTerm);
2438
2450
  return;
2439
2451
  }
2452
+ if (text === "/profiles" || text.startsWith("/profiles ")) {
2453
+ const rawProfileName = text.startsWith("/profiles ") ? text.slice(10).trim() : undefined;
2454
+ this.editor.setText("");
2455
+ await this.handleProfilesCommand(rawProfileName?.length ? rawProfileName : undefined);
2456
+ return;
2457
+ }
2440
2458
  if (text === "/export" || text.startsWith("/export ")) {
2441
2459
  await this.handleExportCommand(text);
2442
2460
  this.editor.setText("");
@@ -4134,6 +4152,8 @@ export class InteractiveMode {
4134
4152
  reflectionReview: settings.reflectionReview ?? preset.reflectionReview,
4135
4153
  reflectionMinToolCalls: settings.reflectionMinToolCalls ?? preset.reflectionMinToolCalls,
4136
4154
  reflectionCooldownMinutes: settings.reflectionCooldownMinutes ?? preset.reflectionCooldownMinutes,
4155
+ complexTaskToolCalls: settings.complexTaskToolCalls ?? preset.complexTaskToolCalls,
4156
+ thinkingLevel: settings.thinkingLevel ?? preset.thinkingLevel,
4137
4157
  };
4138
4158
  }
4139
4159
  getAutoLearnTenantKey() {
@@ -4338,7 +4358,7 @@ export class InteractiveMode {
4338
4358
  const objective = options.kind === "reflection"
4339
4359
  ? "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
4360
  : "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}`;
4361
+ 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
4362
  }
4343
4363
  reserveAutoLearnRun(params) {
4344
4364
  return this.withAutoLearnStateLock((current) => {
@@ -4468,7 +4488,7 @@ export class InteractiveMode {
4468
4488
  const args = buildAutoLearnSpawnArgs(spawnTarget, {
4469
4489
  name: `Auto Learn ${runId}`,
4470
4490
  modelPattern,
4471
- thinkingLevel: AUTO_LEARN_THINKING_LEVEL,
4491
+ thinkingLevel: settings.thinkingLevel ?? "low",
4472
4492
  sessionDir,
4473
4493
  sessionId,
4474
4494
  promptPath,
@@ -4630,7 +4650,6 @@ export class InteractiveMode {
4630
4650
  }
4631
4651
  evaluateAutonomyReview(messages) {
4632
4652
  const settings = this.getEffectiveAutoLearnSettings();
4633
- const autonomy = this.settingsManager.getAutonomySettings();
4634
4653
  const state = this.withAutoLearnStateLock((current) => {
4635
4654
  const pruned = this.pruneAutoLearnHistoryFromState(current);
4636
4655
  return { result: pruned, next: pruned };
@@ -4651,7 +4670,8 @@ export class InteractiveMode {
4651
4670
  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
4671
  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
4672
  /\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;
4673
+ const complexTaskThreshold = Math.max(1, settings.complexTaskToolCalls ?? 12);
4674
+ const complexTaskSignal = toolCalls >= complexTaskThreshold;
4655
4675
  const bypassCooldown = correctionSignal || behavioralSelfImprovementSignal || complexTaskSignal;
4656
4676
  const base = { messageCount, contextPercent, cooldownRemainingMs, runningCount, toolCalls };
4657
4677
  if (!settings.enabled)
@@ -4690,19 +4710,12 @@ export class InteractiveMode {
4690
4710
  return {
4691
4711
  ...base,
4692
4712
  shouldRun: true,
4693
- reason: `reflection complex task learning signal (${toolCalls}/${AUTO_LEARN_COMPLEX_TASK_TOOL_CALLS} tool calls)`,
4713
+ reason: `reflection complex task learning signal (${toolCalls}/${complexTaskThreshold} tool calls)`,
4694
4714
  digest: this.buildAutonomyReviewDigest(messages),
4695
4715
  bypassCooldown: true,
4696
4716
  };
4697
4717
  }
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
- }
4718
+ // Full autonomy expands allowed action scope for triggered reviews; it does not make every turn a review trigger.
4706
4719
  if (toolCalls >= settings.reflectionMinToolCalls) {
4707
4720
  return {
4708
4721
  ...base,
@@ -4859,6 +4872,21 @@ export class InteractiveMode {
4859
4872
  showSettingsSelector() {
4860
4873
  this.showSelector((done) => {
4861
4874
  const projectSettings = this.settingsManager.getProjectSettings();
4875
+ const profileOptions = [
4876
+ {
4877
+ value: "(none)",
4878
+ label: "(none)",
4879
+ description: "Use configured profile selection (session default)",
4880
+ },
4881
+ ...this.settingsManager
4882
+ .getProfileRegistry()
4883
+ .listProfiles()
4884
+ .map((profile) => ({
4885
+ value: profile.name,
4886
+ label: profile.name,
4887
+ description: profile.description ?? profile.source,
4888
+ })),
4889
+ ];
4862
4890
  const selector = new SettingsSelectorComponent({
4863
4891
  autoCompact: this.session.autoCompactionEnabled,
4864
4892
  showImages: this.settingsManager.getShowImages(),
@@ -4896,6 +4924,8 @@ export class InteractiveMode {
4896
4924
  currentModelPattern: this.session.model
4897
4925
  ? `${this.session.model.provider}/${this.session.model.id}`
4898
4926
  : undefined,
4927
+ activeProfileName: this.settingsManager.getActiveResourceProfileNames()[0],
4928
+ profileOptions,
4899
4929
  }, {
4900
4930
  onAutoCompactChange: (enabled) => {
4901
4931
  this.session.setAutoCompactionEnabled(enabled);
@@ -5038,6 +5068,26 @@ export class InteractiveMode {
5038
5068
  this.updateAutoLearnFooter();
5039
5069
  this.showStatus(`Auto Learn settings saved to ${scope}. Use /auto-learn status or /auto-learn run.`);
5040
5070
  },
5071
+ onProfileChange: (profile) => {
5072
+ done();
5073
+ void this.applyProfile(profile);
5074
+ },
5075
+ onProfileCreate: () => {
5076
+ done();
5077
+ void this.createProfileFlow();
5078
+ },
5079
+ onProfileEdit: (profileName) => {
5080
+ done();
5081
+ void this.openProfileResourceEditor(profileName);
5082
+ },
5083
+ onProfilePersistActive: (scope) => {
5084
+ done();
5085
+ this.persistActiveProfile(scope);
5086
+ },
5087
+ onProfileDelete: (profileName) => {
5088
+ done();
5089
+ this.deleteProfileFromSource(profileName);
5090
+ },
5041
5091
  onCancel: () => {
5042
5092
  done();
5043
5093
  this.ui.requestRender();
@@ -5046,6 +5096,298 @@ export class InteractiveMode {
5046
5096
  return { component: selector, focus: selector.getSettingsList() };
5047
5097
  });
5048
5098
  }
5099
+ async handleProfilesCommand(profileName) {
5100
+ if (profileName) {
5101
+ await this.applyProfile(profileName);
5102
+ return;
5103
+ }
5104
+ const registry = this.settingsManager.getProfileRegistry();
5105
+ const profiles = registry.listProfiles();
5106
+ if (profiles.length === 0) {
5107
+ this.showWarning("No profiles found. Add resourceProfiles to settings or JSON files under ~/.pi/agent/profiles/.");
5108
+ return;
5109
+ }
5110
+ this.showSelector((done) => {
5111
+ const selector = new ProfileSelectorComponent(profiles, this.settingsManager.getActiveResourceProfileNames(), (profile) => {
5112
+ done();
5113
+ void this.applyProfile(profile);
5114
+ }, () => {
5115
+ done();
5116
+ this.ui.requestRender();
5117
+ });
5118
+ return { component: selector, focus: selector.getSelectList() };
5119
+ });
5120
+ }
5121
+ async applyProfile(profileName) {
5122
+ const normalizedName = profileName.trim();
5123
+ const normalizedLower = normalizedName.toLowerCase();
5124
+ if (normalizedName.length === 0 || normalizedLower === "none" || normalizedLower === "(none)") {
5125
+ try {
5126
+ this.settingsManager.setRuntimeResourceProfiles([]);
5127
+ this.session.sessionManager.appendCustomEntry("pi.activeResourceProfiles", {
5128
+ profiles: [],
5129
+ });
5130
+ await this.handleReloadCommand();
5131
+ const activeProfileName = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
5132
+ this.footerDataProvider.setExtensionStatus("profile", activeProfileName);
5133
+ this.footer.invalidate();
5134
+ this.updateEditorBorderColor();
5135
+ this.showStatus(`Profile: ${activeProfileName}`);
5136
+ }
5137
+ catch (error) {
5138
+ this.showError(error instanceof Error ? error.message : String(error));
5139
+ }
5140
+ return;
5141
+ }
5142
+ const registry = this.settingsManager.getProfileRegistry();
5143
+ const profile = normalizedName.startsWith("./") || normalizedName.startsWith("../")
5144
+ ? registry.resolveProfileRef(normalizedName, this.sessionManager.getCwd())
5145
+ : registry.getProfile(normalizedName);
5146
+ if (!profile) {
5147
+ this.showError(`Profile not found: ${profileName}`);
5148
+ return;
5149
+ }
5150
+ try {
5151
+ let appliedModel;
5152
+ if (profile.model) {
5153
+ this.session.modelRegistry.refresh();
5154
+ const resolved = resolveCliModel({ cliModel: profile.model, modelRegistry: this.session.modelRegistry });
5155
+ if (resolved.error) {
5156
+ this.showError(resolved.error);
5157
+ return;
5158
+ }
5159
+ if (resolved.warning) {
5160
+ this.showWarning(resolved.warning);
5161
+ }
5162
+ if (resolved.model) {
5163
+ await this.session.setModel(resolved.model, { persistSettings: false });
5164
+ appliedModel = resolved.model;
5165
+ }
5166
+ if (resolved.thinkingLevel && !profile.thinking) {
5167
+ this.session.setThinkingLevel(resolved.thinkingLevel, { persistSettings: false });
5168
+ }
5169
+ }
5170
+ if (profile.thinking) {
5171
+ this.session.setThinkingLevel(profile.thinking, { persistSettings: false });
5172
+ }
5173
+ this.settingsManager.setRuntimeResourceProfiles([profile.name]);
5174
+ this.session.sessionManager.appendCustomEntry("pi.activeResourceProfiles", {
5175
+ profiles: [profile.name],
5176
+ });
5177
+ await this.handleReloadCommand();
5178
+ this.footerDataProvider.setExtensionStatus("profile", profile.name);
5179
+ this.footer.invalidate();
5180
+ this.updateEditorBorderColor();
5181
+ this.showStatus(`Profile: ${profile.name}`);
5182
+ if (appliedModel) {
5183
+ void this.maybeWarnAboutAnthropicSubscriptionAuth(appliedModel);
5184
+ this.checkDaxnutsEasterEgg(appliedModel);
5185
+ }
5186
+ }
5187
+ catch (error) {
5188
+ this.showError(error instanceof Error ? error.message : String(error));
5189
+ }
5190
+ }
5191
+ async getProfileResourceKinds() {
5192
+ const loader = this.session.resourceLoader;
5193
+ const base = (p) => p.split(/[\\/]/).pop() ?? p;
5194
+ // Get all discoverable extension paths (enabled and disabled) for profile filtering
5195
+ const allDiscoverableExtensions = await loader.getDiscoverableExtensionPaths();
5196
+ return [
5197
+ { kind: "tools", label: "Tools", allIds: [...allToolNames] },
5198
+ { kind: "skills", label: "Skills", allIds: loader.getSkills().skills.map((s) => s.name) },
5199
+ {
5200
+ kind: "extensions",
5201
+ label: "Extensions",
5202
+ allIds: allDiscoverableExtensions.map((e) => base(e)),
5203
+ },
5204
+ { kind: "agents", label: "Agents", allIds: loader.getAgentsFiles().agentsFiles.map((f) => base(f.path)) },
5205
+ { kind: "prompts", label: "Prompts", allIds: loader.getPrompts().prompts.map((p) => p.name) },
5206
+ { kind: "themes", label: "Themes", allIds: getAvailableThemes() },
5207
+ ];
5208
+ }
5209
+ /** Map where a profile currently lives to the scope we should write it back to. */
5210
+ scopeForProfileSource(source) {
5211
+ switch (source) {
5212
+ case "profile-file":
5213
+ return "reusable-file";
5214
+ case "directory-overlay":
5215
+ case "embedded":
5216
+ return "directory";
5217
+ case "inline":
5218
+ return "session";
5219
+ default:
5220
+ return "global"; // "settings"
5221
+ }
5222
+ }
5223
+ async refreshAfterProfileMutation(profileName) {
5224
+ if (this.settingsManager.getActiveResourceProfileNames().includes(profileName)) {
5225
+ await this.handleReloadCommand();
5226
+ const active = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
5227
+ this.footerDataProvider.setExtensionStatus("profile", active);
5228
+ this.footer.invalidate();
5229
+ this.updateEditorBorderColor();
5230
+ }
5231
+ }
5232
+ async createProfileFlow() {
5233
+ const name = await new Promise((resolve) => {
5234
+ this.showSelector((done) => {
5235
+ const input = new ExtensionInputComponent("Create Profile", "Enter profile name", (value) => {
5236
+ done();
5237
+ resolve(value);
5238
+ }, () => {
5239
+ done();
5240
+ resolve(undefined);
5241
+ }, { tui: this.ui });
5242
+ return { component: input, focus: input };
5243
+ });
5244
+ });
5245
+ if (name === undefined) {
5246
+ this.ui.requestRender();
5247
+ return;
5248
+ }
5249
+ const trimmed = name.trim();
5250
+ if (!trimmed) {
5251
+ this.showError("Profile name cannot be empty");
5252
+ return this.createProfileFlow();
5253
+ }
5254
+ // Validate name rules using validateSkillName
5255
+ const errors = validateSkillName(trimmed);
5256
+ if (errors.length > 0) {
5257
+ this.showError(`Invalid profile name: ${errors.join(", ")}`);
5258
+ return this.createProfileFlow();
5259
+ }
5260
+ // Collision check
5261
+ const existing = this.settingsManager.getProfileRegistry().getProfile(trimmed);
5262
+ if (existing) {
5263
+ this.showError(`Profile "${trimmed}" already exists`);
5264
+ return this.createProfileFlow();
5265
+ }
5266
+ // Open the resource editor on the NEW profile
5267
+ void this.openNewProfileEditor(trimmed);
5268
+ }
5269
+ async openNewProfileEditor(profileName) {
5270
+ const scope = "reusable-file";
5271
+ const kinds = await this.getProfileResourceKinds();
5272
+ this.showSelector((done) => {
5273
+ const editor = new ProfileResourceEditorComponent({
5274
+ profileName,
5275
+ initialResources: {},
5276
+ kinds,
5277
+ onSave: (resources) => {
5278
+ done();
5279
+ try {
5280
+ this.settingsManager.setProfileDefinition(profileName, {
5281
+ name: profileName,
5282
+ resources,
5283
+ }, scope);
5284
+ this.showStatus(`Saved profile "${profileName}" to ${scope}.`);
5285
+ this.ui.requestRender();
5286
+ }
5287
+ catch (error) {
5288
+ this.showError(error instanceof Error ? error.message : String(error));
5289
+ }
5290
+ },
5291
+ onCancel: () => {
5292
+ done();
5293
+ this.ui.requestRender();
5294
+ },
5295
+ });
5296
+ return { component: editor, focus: editor };
5297
+ });
5298
+ }
5299
+ async openProfileResourceEditor(profileName) {
5300
+ const profile = this.settingsManager.getProfileRegistry().getProfile(profileName);
5301
+ if (!profile) {
5302
+ this.showError(`Profile not found: ${profileName}`);
5303
+ return;
5304
+ }
5305
+ const scope = this.scopeForProfileSource(profile.source);
5306
+ const kinds = await this.getProfileResourceKinds();
5307
+ const isActiveProfile = this.settingsManager.getActiveResourceProfileNames().includes(profile.name);
5308
+ const originalResources = profile.resources;
5309
+ this.showSelector((done) => {
5310
+ const editor = new ProfileResourceEditorComponent({
5311
+ profileName: profile.name,
5312
+ initialResources: profile.resources,
5313
+ kinds,
5314
+ onSave: (resources) => {
5315
+ done();
5316
+ try {
5317
+ this.settingsManager.setProfileDefinition(profile.name, {
5318
+ name: profile.name,
5319
+ description: profile.description,
5320
+ model: profile.model,
5321
+ thinking: profile.thinking,
5322
+ resources,
5323
+ }, scope);
5324
+ this.showStatus(`Saved profile "${profile.name}" to ${scope}.`);
5325
+ // For active profiles, detect if only extensions changed to avoid full reload
5326
+ if (isActiveProfile) {
5327
+ const extensionsChanged = originalResources.extensions !== resources.extensions;
5328
+ const otherResourcesChanged = originalResources.tools !== resources.tools ||
5329
+ originalResources.skills !== resources.skills ||
5330
+ originalResources.agents !== resources.agents ||
5331
+ originalResources.prompts !== resources.prompts ||
5332
+ originalResources.themes !== resources.themes;
5333
+ if (extensionsChanged && !otherResourcesChanged) {
5334
+ // Only extensions changed: use live reconciliation
5335
+ void this.reconcileExtensionsAndRefreshUI(profile.name);
5336
+ }
5337
+ else {
5338
+ // Other resources changed or mixed: use full reload
5339
+ void this.refreshAfterProfileMutation(profile.name);
5340
+ }
5341
+ }
5342
+ // Non-active profiles don't need refresh
5343
+ }
5344
+ catch (error) {
5345
+ this.showError(error instanceof Error ? error.message : String(error));
5346
+ }
5347
+ },
5348
+ onCancel: () => {
5349
+ done();
5350
+ this.ui.requestRender();
5351
+ },
5352
+ });
5353
+ return { component: editor, focus: editor };
5354
+ });
5355
+ }
5356
+ persistActiveProfile(scope) {
5357
+ const active = this.settingsManager.getActiveResourceProfileNames()[0];
5358
+ if (!active) {
5359
+ this.showError("No active profile to persist. Select one with /profiles first.");
5360
+ return;
5361
+ }
5362
+ try {
5363
+ if (scope === "session") {
5364
+ this.settingsManager.setRuntimeResourceProfiles([active]);
5365
+ }
5366
+ else {
5367
+ this.settingsManager.setActiveProfile(active, scope);
5368
+ }
5369
+ this.showStatus(`Active profile "${active}" persisted to ${scope}.`);
5370
+ }
5371
+ catch (error) {
5372
+ this.showError(error instanceof Error ? error.message : String(error));
5373
+ }
5374
+ }
5375
+ deleteProfileFromSource(profileName) {
5376
+ const profile = this.settingsManager.getProfileRegistry().getProfile(profileName);
5377
+ if (!profile) {
5378
+ this.showError(`Profile not found: ${profileName}`);
5379
+ return;
5380
+ }
5381
+ const scope = this.scopeForProfileSource(profile.source);
5382
+ try {
5383
+ this.settingsManager.deleteProfile(profileName, scope);
5384
+ this.showStatus(`Deleted profile "${profileName}" from ${scope}.`);
5385
+ void this.refreshAfterProfileMutation(profileName);
5386
+ }
5387
+ catch (error) {
5388
+ this.showError(error instanceof Error ? error.message : String(error));
5389
+ }
5390
+ }
5049
5391
  async handleModelCommand(searchTerm) {
5050
5392
  if (!searchTerm) {
5051
5393
  await this.showModelSelector();
@@ -5916,6 +6258,71 @@ export class InteractiveMode {
5916
6258
  this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
5917
6259
  }
5918
6260
  }
6261
+ /**
6262
+ * Refresh UI after extensions are loaded/unloaded live.
6263
+ * Performs the same refresh calls as handleReloadCommand but without the full reload.
6264
+ */
6265
+ async refreshUIAfterExtensionsChanged() {
6266
+ try {
6267
+ // Refresh keybindings and autocomplete
6268
+ this.keybindings.reload();
6269
+ this.setupAutocompleteProvider();
6270
+ // Refresh themes
6271
+ const activeHeader = this.customHeader ?? this.builtInHeader;
6272
+ if (isExpandable(activeHeader)) {
6273
+ activeHeader.setExpanded(this.toolOutputExpanded);
6274
+ }
6275
+ setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
6276
+ this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
6277
+ const themeName = this.settingsManager.getTheme();
6278
+ const themeResult = themeName ? setTheme(themeName, true) : { success: true };
6279
+ if (!themeResult.success) {
6280
+ this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
6281
+ }
6282
+ // Refresh editor settings
6283
+ const editorPaddingX = this.settingsManager.getEditorPaddingX();
6284
+ const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
6285
+ this.defaultEditor.setPaddingX(editorPaddingX);
6286
+ this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
6287
+ if (this.editor !== this.defaultEditor) {
6288
+ this.editor.setPaddingX?.(editorPaddingX);
6289
+ this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
6290
+ }
6291
+ // Refresh extension shortcuts
6292
+ const runner = this.session.extensionRunner;
6293
+ this.setupExtensionShortcuts(runner);
6294
+ // Refresh chat and UI
6295
+ await this.rebuildChatFromMessages();
6296
+ this.footer.invalidate();
6297
+ this.ui.requestRender();
6298
+ }
6299
+ catch (error) {
6300
+ this.showError(`Extension refresh failed: ${error instanceof Error ? error.message : String(error)}`);
6301
+ }
6302
+ }
6303
+ /**
6304
+ * Reconcile extensions for the active profile and refresh UI.
6305
+ * Used when only extensions change in the active profile to avoid full reload.
6306
+ */
6307
+ async reconcileExtensionsAndRefreshUI(profileName) {
6308
+ try {
6309
+ await this.session.reconcileLoadedExtensions();
6310
+ const active = this.settingsManager.getActiveResourceProfileNames()[0] ?? "(none)";
6311
+ this.footerDataProvider.setExtensionStatus("profile", active);
6312
+ this.footer.invalidate();
6313
+ this.updateEditorBorderColor();
6314
+ }
6315
+ catch (error) {
6316
+ // On error, fall back to full reload
6317
+ try {
6318
+ await this.refreshAfterProfileMutation(profileName);
6319
+ }
6320
+ catch {
6321
+ // If full reload also fails, show error
6322
+ this.showError(`Failed to reconcile extensions: ${error instanceof Error ? error.message : String(error)}`);
6323
+ }
6324
+ }
6325
+ }
5919
6326
  async handleExportCommand(text) {
5920
6327
  const outputPath = this.getPathCommandArgument(text, "/export");
5921
6328
  try {
@@ -6466,6 +6873,9 @@ export class InteractiveMode {
6466
6873
  if (this.unsubscribe) {
6467
6874
  this.unsubscribe();
6468
6875
  }
6876
+ if (this.unsubscribeExtensionsChanged) {
6877
+ this.unsubscribeExtensionsChanged();
6878
+ }
6469
6879
  if (this.isInitialized) {
6470
6880
  this.ui.stop();
6471
6881
  this.isInitialized = false;