@caupulican/pi-adaptative 0.80.47 → 0.80.49

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 (26) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/core/profile-resource-selection.d.ts +9 -0
  3. package/dist/core/profile-resource-selection.d.ts.map +1 -1
  4. package/dist/core/profile-resource-selection.js +49 -0
  5. package/dist/core/profile-resource-selection.js.map +1 -1
  6. package/dist/modes/interactive/components/profile-resource-editor.d.ts +23 -6
  7. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  8. package/dist/modes/interactive/components/profile-resource-editor.js +253 -23
  9. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  10. package/dist/modes/interactive/components/settings-selector.d.ts +1 -7
  11. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  12. package/dist/modes/interactive/components/settings-selector.js +41 -108
  13. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  14. package/dist/modes/interactive/interactive-mode.d.ts +13 -2
  15. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  16. package/dist/modes/interactive/interactive-mode.js +533 -95
  17. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  18. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  19. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  20. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  21. package/examples/extensions/sandbox/package-lock.json +2 -2
  22. package/examples/extensions/sandbox/package.json +1 -1
  23. package/examples/extensions/with-deps/package-lock.json +2 -2
  24. package/examples/extensions/with-deps/package.json +1 -1
  25. package/npm-shrinkwrap.json +12 -12
  26. package/package.json +4 -4
@@ -32,6 +32,7 @@ import { hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manag
32
32
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
33
33
  import { copyToClipboard } from "../../utils/clipboard.js";
34
34
  import { readClipboardImage } from "../../utils/clipboard-image.js";
35
+ import { parseFrontmatter } from "../../utils/frontmatter.js";
35
36
  import { parseGitUrl } from "../../utils/git.js";
36
37
  import { getCwdRelativePath, resolvePath } from "../../utils/paths.js";
37
38
  import { getPiUserAgent } from "../../utils/pi-user-agent.js";
@@ -58,7 +59,7 @@ import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./c
58
59
  import { LoginDialogComponent } from "./components/login-dialog.js";
59
60
  import { ModelSelectorComponent } from "./components/model-selector.js";
60
61
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
61
- import { ProfileResourceEditorComponent, } from "./components/profile-resource-editor.js";
62
+ import { ProfileResourceEditorComponent, resolveResourceEditPath, } from "./components/profile-resource-editor.js";
62
63
  import { ProfileSelectorComponent } from "./components/profile-selector.js";
63
64
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
64
65
  import { SessionSelectorComponent } from "./components/session-selector.js";
@@ -3717,6 +3718,50 @@ export class InteractiveMode {
3717
3718
  this.ui.requestRender(true);
3718
3719
  }
3719
3720
  }
3721
+ async openEditorForPath(filePath) {
3722
+ let editorCmd = process.env.EDITOR || process.env.VISUAL;
3723
+ let isFallback = false;
3724
+ if (!editorCmd) {
3725
+ editorCmd = "vi";
3726
+ isFallback = true;
3727
+ }
3728
+ try {
3729
+ // Stop TUI to release terminal
3730
+ this.ui.stop();
3731
+ // Split by space to support editor arguments (e.g., "code --wait")
3732
+ const [editor, ...editorArgs] = editorCmd.split(" ");
3733
+ process.stdout.write(`Launching external editor: ${editorCmd} ${filePath}\nPi will resume when the editor exits.\n`);
3734
+ const status = await new Promise((resolve) => {
3735
+ const child = spawn(editor, [...editorArgs, filePath], {
3736
+ stdio: "inherit",
3737
+ shell: process.platform === "win32",
3738
+ });
3739
+ child.on("error", () => resolve(null));
3740
+ child.on("close", (code) => resolve(code));
3741
+ });
3742
+ if (status === null) {
3743
+ if (isFallback) {
3744
+ process.stdout.write(`\nError: Failed to launch fallback editor "vi".\n`);
3745
+ }
3746
+ else {
3747
+ process.stdout.write(`\nError: Failed to launch editor "${editorCmd}".\n`);
3748
+ }
3749
+ process.stdout.write(`Please set the $EDITOR or $VISUAL environment variable to edit inline.\n`);
3750
+ process.stdout.write(`Absolute file path: ${filePath}\n\nPress Enter to return to Pi...`);
3751
+ // Wait for enter key
3752
+ await new Promise((resolve) => {
3753
+ process.stdin.once("data", () => resolve());
3754
+ });
3755
+ }
3756
+ return status === 0;
3757
+ }
3758
+ finally {
3759
+ // Restart TUI
3760
+ this.ui.start();
3761
+ // Force full re-render since external editor uses alternate screen
3762
+ this.ui.requestRender(true);
3763
+ }
3764
+ }
3720
3765
  // =========================================================================
3721
3766
  // UI helpers
3722
3767
  // =========================================================================
@@ -5088,40 +5133,487 @@ export class InteractiveMode {
5088
5133
  this.updateAutoLearnFooter();
5089
5134
  this.showStatus(`Auto Learn settings saved to ${scope}. Use /auto-learn status or /auto-learn run.`);
5090
5135
  },
5091
- onProfileChange: (profile) => {
5136
+ onResourcesHubAction: (action) => {
5092
5137
  done();
5093
- void this.applyProfile(profile);
5138
+ void this.handleResourcesHubAction(action);
5094
5139
  },
5095
- onProfileCreate: () => {
5140
+ onCancel: () => {
5096
5141
  done();
5097
- void this.createProfileFlow();
5142
+ this.ui.requestRender();
5098
5143
  },
5099
- onProfileEdit: (profileName) => {
5144
+ });
5145
+ return { component: selector, focus: selector.getSettingsList() };
5146
+ });
5147
+ }
5148
+ async handleResourcesHubAction(action) {
5149
+ switch (action) {
5150
+ case "nudge-add-source":
5151
+ void this.addExternalResourceRootFlow().then(() => {
5152
+ void this.showSettingsSelector();
5153
+ });
5154
+ break;
5155
+ case "active-profile":
5156
+ void this.openActiveProfileSelector();
5157
+ break;
5158
+ case "manage-library":
5159
+ void this.openLibraryManagerFlow();
5160
+ break;
5161
+ case "manage-profiles":
5162
+ void this.openManageProfilesFlow();
5163
+ break;
5164
+ case "sources":
5165
+ void this.openSourcesManagerFlow();
5166
+ break;
5167
+ }
5168
+ }
5169
+ async openActiveProfileSelector() {
5170
+ const registry = this.settingsManager.getProfileRegistry();
5171
+ const profiles = registry.listProfiles();
5172
+ const activeNames = this.settingsManager.getActiveResourceProfileNames();
5173
+ const options = [
5174
+ { value: "(none)", label: "(none)", description: "No active profile/situation (all resources enabled)" },
5175
+ ...profiles.map((p) => ({
5176
+ value: p.name,
5177
+ label: p.name,
5178
+ description: p.description || p.source,
5179
+ })),
5180
+ ];
5181
+ this.showSelector((done) => {
5182
+ const selector = new SelectSubmenu("Profile / Situation", "Select the active runtime profile/situation for this session. This is session-only unless saved elsewhere.", options, activeNames[0] || "(none)", (value) => {
5183
+ done();
5184
+ void this.applyProfile(value === "(none)" ? "" : value).then(() => {
5185
+ void this.showSettingsSelector();
5186
+ });
5187
+ }, () => {
5188
+ done();
5189
+ void this.showSettingsSelector();
5190
+ });
5191
+ return { component: selector, focus: selector.getSelectList() };
5192
+ });
5193
+ }
5194
+ async openManageProfilesFlow() {
5195
+ const registry = this.settingsManager.getProfileRegistry();
5196
+ const profiles = registry.listProfiles();
5197
+ const editableProfiles = profiles.map((p) => ({
5198
+ value: p.name,
5199
+ label: p.name,
5200
+ description: p.description || p.source,
5201
+ }));
5202
+ const options = [
5203
+ {
5204
+ value: "create",
5205
+ label: "+ Create profile / situation...",
5206
+ description: "Create a new resource profile/situation definition.",
5207
+ },
5208
+ ];
5209
+ if (this.settingsManager.getActiveResourceProfileNames().length > 0) {
5210
+ options.push({
5211
+ value: "persist",
5212
+ label: "Persist active profile / situation to...",
5213
+ description: "Save the current active profile/situation selection so it survives restart.",
5214
+ });
5215
+ }
5216
+ if (editableProfiles.length > 0) {
5217
+ options.push({
5218
+ value: "delete",
5219
+ label: "Delete profile / situation...",
5220
+ description: "Remove a profile/situation definition from where it is stored.",
5221
+ });
5222
+ }
5223
+ this.showSelector((done) => {
5224
+ const selector = new SelectSubmenu("Manage Profiles / Situations", "Create, delete, or persist profile/situation definitions.", options, "", (value) => {
5225
+ done();
5226
+ if (value === "create") {
5227
+ void this.createProfileFlow().then(() => {
5228
+ void this.showSettingsSelector();
5229
+ });
5230
+ }
5231
+ else if (value === "persist") {
5232
+ void this.openPersistProfileSelector();
5233
+ }
5234
+ else if (value === "delete") {
5235
+ void this.openDeleteProfileSelector();
5236
+ }
5237
+ }, () => {
5238
+ done();
5239
+ void this.showSettingsSelector();
5240
+ });
5241
+ return { component: selector, focus: selector.getSelectList() };
5242
+ });
5243
+ }
5244
+ async openPersistProfileSelector() {
5245
+ const scopeOptions = [
5246
+ { value: "session", label: "session", description: "Runtime only (not written to disk)" },
5247
+ {
5248
+ value: "directory",
5249
+ label: "directory",
5250
+ description: "~/.pi/agent/resource-profiles/<hash>/settings.json",
5251
+ },
5252
+ { value: "project", label: "project", description: ".pi/settings.json" },
5253
+ { value: "global", label: "global", description: "~/.pi/agent/settings.json" },
5254
+ ];
5255
+ this.showSelector((done) => {
5256
+ const selector = new SelectSubmenu("Persist Active Profile / Situation", "Choose where to write the active profile/situation selection.", scopeOptions, "directory", (value) => {
5257
+ done();
5258
+ this.persistActiveProfile(value);
5259
+ void this.showSettingsSelector();
5260
+ }, () => {
5261
+ done();
5262
+ void this.openManageProfilesFlow();
5263
+ });
5264
+ return { component: selector, focus: selector.getSelectList() };
5265
+ });
5266
+ }
5267
+ async openDeleteProfileSelector() {
5268
+ const registry = this.settingsManager.getProfileRegistry();
5269
+ const editableProfiles = registry.listProfiles().map((p) => ({
5270
+ value: p.name,
5271
+ label: p.name,
5272
+ description: p.description || p.source,
5273
+ }));
5274
+ this.showSelector((done) => {
5275
+ const selector = new SelectSubmenu("Delete Profile / Situation", "Pick a profile/situation to delete.", editableProfiles, "", (value) => {
5276
+ done();
5277
+ this.deleteProfileFromSource(value);
5278
+ void this.showSettingsSelector();
5279
+ }, () => {
5280
+ done();
5281
+ void this.openManageProfilesFlow();
5282
+ });
5283
+ return { component: selector, focus: selector.getSelectList() };
5284
+ });
5285
+ }
5286
+ async openSourcesManagerFlow() {
5287
+ const externalRoots = this.settingsManager.getExternalResourceRoots();
5288
+ const trustedRoots = this.settingsManager.getTrustedResourceRoots();
5289
+ const options = [
5290
+ {
5291
+ value: "add",
5292
+ label: "+ Add external root...",
5293
+ description: "Register a new external directory root (requires trust)",
5294
+ },
5295
+ ];
5296
+ for (const r of externalRoots) {
5297
+ const isTrusted = trustedRoots.includes(r);
5298
+ options.push({
5299
+ value: `remove:${r}`,
5300
+ label: `Remove: ${r}`,
5301
+ description: isTrusted ? "Trusted external root" : "Untrusted external root",
5302
+ });
5303
+ }
5304
+ this.showSelector((done) => {
5305
+ const selector = new SelectSubmenu("Sources", "Manage external resource roots. Adding a root requires trust confirmation.", options, "", (value) => {
5306
+ done();
5307
+ if (value === "add") {
5308
+ void this.addExternalResourceRootFlow().then(() => {
5309
+ void this.showSettingsSelector();
5310
+ });
5311
+ }
5312
+ else if (value.startsWith("remove:")) {
5313
+ const root = value.slice("remove:".length);
5314
+ void this.removeExternalResourceRootFlow(root).then(() => {
5315
+ void this.showSettingsSelector();
5316
+ });
5317
+ }
5318
+ }, () => {
5319
+ done();
5320
+ void this.showSettingsSelector();
5321
+ });
5322
+ return { component: selector, focus: selector.getSelectList() };
5323
+ });
5324
+ }
5325
+ async openLibraryManagerFlow() {
5326
+ const activeNames = this.settingsManager.getActiveResourceProfileNames();
5327
+ const activeName = activeNames[0];
5328
+ if (!activeName || activeName === "(none)") {
5329
+ this.showSelector((done) => {
5330
+ const selector = new SelectSubmenu("No Active Profile / Situation", "Select or create a profile/situation to manage the library.", [
5331
+ {
5332
+ value: "select",
5333
+ label: "Select existing profile / situation...",
5334
+ description: "Choose an existing profile/situation to activate.",
5335
+ },
5336
+ {
5337
+ value: "create",
5338
+ label: "Create new profile / situation...",
5339
+ description: "Create a new profile/situation definition.",
5340
+ },
5341
+ ], "select", (value) => {
5100
5342
  done();
5101
- void this.openProfileResourceEditor(profileName);
5102
- },
5103
- onProfilePersistActive: (scope) => {
5343
+ if (value === "create") {
5344
+ void this.createProfileAndOpenLibraryFlow();
5345
+ }
5346
+ else {
5347
+ void this.selectProfileAndOpenLibraryFlow();
5348
+ }
5349
+ }, () => {
5104
5350
  done();
5105
- this.persistActiveProfile(scope);
5106
- },
5107
- onProfileDelete: (profileName) => {
5351
+ void this.showSettingsSelector();
5352
+ });
5353
+ return { component: selector, focus: selector.getSelectList() };
5354
+ });
5355
+ return;
5356
+ }
5357
+ const registry = this.settingsManager.getProfileRegistry();
5358
+ const profile = registry.getProfile(activeName);
5359
+ if (!profile) {
5360
+ this.showError(`Active profile/situation "${activeName}" not found in registry.`);
5361
+ return;
5362
+ }
5363
+ const scope = this.scopeForProfileSource(profile.source);
5364
+ void this.openLibraryEditorForProfile(profile.name, scope);
5365
+ }
5366
+ async createProfileAndOpenLibraryFlow() {
5367
+ const name = await new Promise((resolve) => {
5368
+ this.showSelector((done) => {
5369
+ const input = new ExtensionInputComponent("Create Profile / Situation", "Enter profile/situation name", (value) => {
5370
+ done();
5371
+ resolve(value);
5372
+ }, () => {
5373
+ done();
5374
+ resolve(undefined);
5375
+ }, { tui: this.ui });
5376
+ return { component: input, focus: input };
5377
+ });
5378
+ });
5379
+ if (name === undefined) {
5380
+ void this.openLibraryManagerFlow();
5381
+ return;
5382
+ }
5383
+ const trimmed = name.trim();
5384
+ if (!trimmed) {
5385
+ this.showWarning("Profile/situation name cannot be empty.");
5386
+ void this.openLibraryManagerFlow();
5387
+ return;
5388
+ }
5389
+ try {
5390
+ this.settingsManager.setProfileDefinition(trimmed, {
5391
+ name: trimmed,
5392
+ resources: {},
5393
+ }, "directory");
5394
+ await this.applyProfile(trimmed);
5395
+ void this.openLibraryEditorForProfile(trimmed, "directory");
5396
+ }
5397
+ catch (error) {
5398
+ this.showError(error instanceof Error ? error.message : String(error));
5399
+ void this.openLibraryManagerFlow();
5400
+ }
5401
+ }
5402
+ async selectProfileAndOpenLibraryFlow() {
5403
+ const registry = this.settingsManager.getProfileRegistry();
5404
+ const profiles = registry.listProfiles();
5405
+ const editableProfiles = profiles.map((p) => ({
5406
+ value: p.name,
5407
+ label: p.name,
5408
+ description: p.description || p.source,
5409
+ }));
5410
+ if (editableProfiles.length === 0) {
5411
+ this.showWarning("No existing profiles/situations to select. Please create one.");
5412
+ void this.createProfileAndOpenLibraryFlow();
5413
+ return;
5414
+ }
5415
+ this.showSelector((done) => {
5416
+ const selector = new SelectSubmenu("Select Profile / Situation", "Pick a profile/situation to activate and edit.", editableProfiles, "", (value) => {
5417
+ done();
5418
+ void this.applyProfile(value).then(() => {
5419
+ const profile = registry.getProfile(value);
5420
+ const scope = this.scopeForProfileSource(profile.source);
5421
+ void this.openLibraryEditorForProfile(value, scope);
5422
+ });
5423
+ }, () => {
5424
+ done();
5425
+ void this.openLibraryManagerFlow();
5426
+ });
5427
+ return { component: selector, focus: selector.getSelectList() };
5428
+ });
5429
+ }
5430
+ async getProfileResourceKinds() {
5431
+ const loader = this.session.resourceLoader;
5432
+ const base = (p) => p.split(/[\\/]/).pop() ?? p;
5433
+ const allDiscoverableExtensions = await loader.getDiscoverableExtensionPaths();
5434
+ const skills = loader.getSkills().skills;
5435
+ const prompts = loader.getPrompts().prompts;
5436
+ const themes = getAvailableThemesWithPaths();
5437
+ const agents = loader.getAgentsFiles().agentsFiles;
5438
+ const getAgentDescription = (filePath) => {
5439
+ try {
5440
+ const content = fs.readFileSync(filePath, "utf-8");
5441
+ const { frontmatter } = parseFrontmatter(content);
5442
+ if (typeof frontmatter.description === "string") {
5443
+ return frontmatter.description;
5444
+ }
5445
+ }
5446
+ catch { }
5447
+ return undefined;
5448
+ };
5449
+ const getExtensionDescription = (filePath) => {
5450
+ try {
5451
+ let dir = filePath;
5452
+ if (fs.existsSync(filePath) && !fs.statSync(filePath).isDirectory()) {
5453
+ dir = path.dirname(filePath);
5454
+ }
5455
+ const pkgPath = path.join(dir, "package.json");
5456
+ if (fs.existsSync(pkgPath)) {
5457
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
5458
+ if (typeof pkg.description === "string") {
5459
+ return pkg.description;
5460
+ }
5461
+ }
5462
+ }
5463
+ catch { }
5464
+ return undefined;
5465
+ };
5466
+ return [
5467
+ {
5468
+ kind: "tools",
5469
+ label: "Tools",
5470
+ items: Array.from(allToolNames).map((name) => ({ id: name })),
5471
+ },
5472
+ {
5473
+ kind: "skills",
5474
+ label: "Skills",
5475
+ items: skills.map((s) => ({
5476
+ id: s.name,
5477
+ path: s.filePath,
5478
+ description: s.description,
5479
+ })),
5480
+ },
5481
+ {
5482
+ kind: "extensions",
5483
+ label: "Extensions",
5484
+ items: allDiscoverableExtensions.map((e) => ({
5485
+ id: base(e),
5486
+ path: e,
5487
+ description: getExtensionDescription(e),
5488
+ })),
5489
+ },
5490
+ {
5491
+ kind: "agents",
5492
+ label: "Agents",
5493
+ items: agents.map((f) => ({
5494
+ id: base(f.path),
5495
+ path: f.path,
5496
+ description: getAgentDescription(f.path),
5497
+ })),
5498
+ },
5499
+ {
5500
+ kind: "prompts",
5501
+ label: "Prompts",
5502
+ items: prompts.map((p) => ({
5503
+ id: p.name,
5504
+ path: p.filePath,
5505
+ description: p.description,
5506
+ })),
5507
+ },
5508
+ {
5509
+ kind: "themes",
5510
+ label: "Themes",
5511
+ items: themes.map((t) => ({
5512
+ id: t.name,
5513
+ path: t.path,
5514
+ })),
5515
+ },
5516
+ ];
5517
+ }
5518
+ async openLibraryEditorForProfile(profileName, initialScope) {
5519
+ const currentScope = initialScope;
5520
+ const registry = this.settingsManager.getProfileRegistry();
5521
+ const profile = registry.getProfile(profileName);
5522
+ if (!profile) {
5523
+ this.showError(`Profile not found: ${profileName}`);
5524
+ return;
5525
+ }
5526
+ const kinds = await this.getProfileResourceKinds();
5527
+ const originalResources = profile.resources;
5528
+ const isActiveProfile = this.settingsManager.getActiveResourceProfileNames().includes(profile.name);
5529
+ this.showSelector((done) => {
5530
+ const editor = new ProfileResourceEditorComponent({
5531
+ profileName: profile.name,
5532
+ profileScope: currentScope,
5533
+ initialResources: profile.resources,
5534
+ kinds,
5535
+ cwd: this.sessionManager.getCwd(),
5536
+ agentDir: getAgentDir(),
5537
+ externalResourceRoots: this.settingsManager.getExternalResourceRoots(),
5538
+ onSave: (resources) => {
5108
5539
  done();
5109
- this.deleteProfileFromSource(profileName);
5540
+ try {
5541
+ this.settingsManager.setProfileDefinition(profileName, {
5542
+ name: profileName,
5543
+ description: profile.description,
5544
+ model: profile.model,
5545
+ thinking: profile.thinking,
5546
+ resources,
5547
+ }, currentScope);
5548
+ this.showStatus(`Saved profile "${profileName}" to ${currentScope}.`);
5549
+ if (isActiveProfile) {
5550
+ const extensionsChanged = originalResources.extensions !== resources.extensions;
5551
+ const otherResourcesChanged = originalResources.tools !== resources.tools ||
5552
+ originalResources.skills !== resources.skills ||
5553
+ originalResources.agents !== resources.agents ||
5554
+ originalResources.prompts !== resources.prompts ||
5555
+ originalResources.themes !== resources.themes;
5556
+ if (extensionsChanged && !otherResourcesChanged) {
5557
+ void this.reconcileExtensionsAndRefreshUI(profileName);
5558
+ }
5559
+ else {
5560
+ void this.refreshAfterProfileMutation(profileName);
5561
+ }
5562
+ }
5563
+ }
5564
+ catch (error) {
5565
+ this.showError(error instanceof Error ? error.message : String(error));
5566
+ }
5110
5567
  },
5111
- onAddExternalResourceRoot: () => {
5568
+ onCancel: () => {
5112
5569
  done();
5113
- void this.addExternalResourceRootFlow();
5570
+ void this.openLibraryManagerFlow();
5114
5571
  },
5115
- onRemoveExternalResourceRoot: (root) => {
5572
+ onScopeChange: () => {
5116
5573
  done();
5117
- void this.removeExternalResourceRootFlow(root);
5574
+ void this.promptScopeChangeForProfile(profileName, currentScope);
5118
5575
  },
5119
- onCancel: () => {
5576
+ onEdit: async (id, pathValue, kind) => {
5120
5577
  done();
5121
- this.ui.requestRender();
5578
+ const resolvedEditPath = resolveResourceEditPath(id, pathValue, kind);
5579
+ if (!resolvedEditPath) {
5580
+ this.showWarning(`Resource "${id}" of kind "${kind}" has no editable file path.`);
5581
+ void this.openLibraryEditorForProfile(profileName, currentScope);
5582
+ return;
5583
+ }
5584
+ if (!fs.existsSync(resolvedEditPath)) {
5585
+ this.showError(`Resolved path for "${id}" does not exist: ${resolvedEditPath}`);
5586
+ void this.openLibraryEditorForProfile(profileName, currentScope);
5587
+ return;
5588
+ }
5589
+ await this.openEditorForPath(resolvedEditPath);
5590
+ await this.handleReloadCommand();
5591
+ void this.openLibraryEditorForProfile(profileName, currentScope);
5122
5592
  },
5123
5593
  });
5124
- return { component: selector, focus: selector.getSettingsList() };
5594
+ return { component: editor, focus: editor };
5595
+ });
5596
+ }
5597
+ async promptScopeChangeForProfile(profileName, currentScope) {
5598
+ const scopeOptions = [
5599
+ { value: "session", label: "session", description: "Runtime only (not written to disk)" },
5600
+ {
5601
+ value: "directory",
5602
+ label: "directory",
5603
+ description: "~/.pi/agent/resource-profiles/<hash>/settings.json",
5604
+ },
5605
+ { value: "project", label: "project", description: ".pi/settings.json" },
5606
+ { value: "global", label: "global", description: "~/.pi/agent/settings.json" },
5607
+ ];
5608
+ this.showSelector((done) => {
5609
+ const selector = new SelectSubmenu("Change Profile / Situation Scope", `Select new scope for profile/situation "${profileName}".`, scopeOptions, currentScope, (value) => {
5610
+ done();
5611
+ void this.openLibraryEditorForProfile(profileName, value);
5612
+ }, () => {
5613
+ done();
5614
+ void this.openLibraryEditorForProfile(profileName, currentScope);
5615
+ });
5616
+ return { component: selector, focus: selector.getSelectList() };
5125
5617
  });
5126
5618
  }
5127
5619
  async handleProfilesCommand(profileName) {
@@ -5216,24 +5708,6 @@ export class InteractiveMode {
5216
5708
  this.showError(error instanceof Error ? error.message : String(error));
5217
5709
  }
5218
5710
  }
5219
- async getProfileResourceKinds() {
5220
- const loader = this.session.resourceLoader;
5221
- const base = (p) => p.split(/[\\/]/).pop() ?? p;
5222
- // Get all discoverable extension paths (enabled and disabled) for profile filtering
5223
- const allDiscoverableExtensions = await loader.getDiscoverableExtensionPaths();
5224
- return [
5225
- { kind: "tools", label: "Tools", allIds: [...allToolNames] },
5226
- { kind: "skills", label: "Skills", allIds: loader.getSkills().skills.map((s) => s.name) },
5227
- {
5228
- kind: "extensions",
5229
- label: "Extensions",
5230
- allIds: allDiscoverableExtensions.map((e) => base(e)),
5231
- },
5232
- { kind: "agents", label: "Agents", allIds: loader.getAgentsFiles().agentsFiles.map((f) => base(f.path)) },
5233
- { kind: "prompts", label: "Prompts", allIds: loader.getPrompts().prompts.map((p) => p.name) },
5234
- { kind: "themes", label: "Themes", allIds: getAvailableThemes() },
5235
- ];
5236
- }
5237
5711
  /** Map where a profile currently lives to the scope we should write it back to. */
5238
5712
  scopeForProfileSource(source) {
5239
5713
  switch (source) {
@@ -5260,7 +5734,7 @@ export class InteractiveMode {
5260
5734
  async createProfileFlow() {
5261
5735
  const name = await new Promise((resolve) => {
5262
5736
  this.showSelector((done) => {
5263
- const input = new ExtensionInputComponent("Create Profile", "Enter profile name", (value) => {
5737
+ const input = new ExtensionInputComponent("Create Profile / Situation", "Enter profile/situation name", (value) => {
5264
5738
  done();
5265
5739
  resolve(value);
5266
5740
  }, () => {
@@ -5276,19 +5750,19 @@ export class InteractiveMode {
5276
5750
  }
5277
5751
  const trimmed = name.trim();
5278
5752
  if (!trimmed) {
5279
- this.showError("Profile name cannot be empty");
5753
+ this.showError("Profile/situation name cannot be empty");
5280
5754
  return this.createProfileFlow();
5281
5755
  }
5282
5756
  // Validate name rules using validateSkillName
5283
5757
  const errors = validateSkillName(trimmed);
5284
5758
  if (errors.length > 0) {
5285
- this.showError(`Invalid profile name: ${errors.join(", ")}`);
5759
+ this.showError(`Invalid profile/situation name: ${errors.join(", ")}`);
5286
5760
  return this.createProfileFlow();
5287
5761
  }
5288
5762
  // Collision check
5289
5763
  const existing = this.settingsManager.getProfileRegistry().getProfile(trimmed);
5290
5764
  if (existing) {
5291
- this.showError(`Profile "${trimmed}" already exists`);
5765
+ this.showError(`Profile/situation "${trimmed}" already exists`);
5292
5766
  return this.createProfileFlow();
5293
5767
  }
5294
5768
  // Open the resource editor on the NEW profile
@@ -5300,8 +5774,12 @@ export class InteractiveMode {
5300
5774
  this.showSelector((done) => {
5301
5775
  const editor = new ProfileResourceEditorComponent({
5302
5776
  profileName,
5777
+ profileScope: scope,
5303
5778
  initialResources: {},
5304
5779
  kinds,
5780
+ cwd: this.sessionManager.getCwd(),
5781
+ agentDir: getAgentDir(),
5782
+ externalResourceRoots: this.settingsManager.getExternalResourceRoots(),
5305
5783
  onSave: (resources) => {
5306
5784
  done();
5307
5785
  try {
@@ -5320,62 +5798,22 @@ export class InteractiveMode {
5320
5798
  done();
5321
5799
  this.ui.requestRender();
5322
5800
  },
5323
- });
5324
- return { component: editor, focus: editor };
5325
- });
5326
- }
5327
- async openProfileResourceEditor(profileName) {
5328
- const profile = this.settingsManager.getProfileRegistry().getProfile(profileName);
5329
- if (!profile) {
5330
- this.showError(`Profile not found: ${profileName}`);
5331
- return;
5332
- }
5333
- const scope = this.scopeForProfileSource(profile.source);
5334
- const kinds = await this.getProfileResourceKinds();
5335
- const isActiveProfile = this.settingsManager.getActiveResourceProfileNames().includes(profile.name);
5336
- const originalResources = profile.resources;
5337
- this.showSelector((done) => {
5338
- const editor = new ProfileResourceEditorComponent({
5339
- profileName: profile.name,
5340
- initialResources: profile.resources,
5341
- kinds,
5342
- onSave: (resources) => {
5801
+ onEdit: async (id, pathValue, kind) => {
5343
5802
  done();
5344
- try {
5345
- this.settingsManager.setProfileDefinition(profile.name, {
5346
- name: profile.name,
5347
- description: profile.description,
5348
- model: profile.model,
5349
- thinking: profile.thinking,
5350
- resources,
5351
- }, scope);
5352
- this.showStatus(`Saved profile "${profile.name}" to ${scope}.`);
5353
- // For active profiles, detect if only extensions changed to avoid full reload
5354
- if (isActiveProfile) {
5355
- const extensionsChanged = originalResources.extensions !== resources.extensions;
5356
- const otherResourcesChanged = originalResources.tools !== resources.tools ||
5357
- originalResources.skills !== resources.skills ||
5358
- originalResources.agents !== resources.agents ||
5359
- originalResources.prompts !== resources.prompts ||
5360
- originalResources.themes !== resources.themes;
5361
- if (extensionsChanged && !otherResourcesChanged) {
5362
- // Only extensions changed: use live reconciliation
5363
- void this.reconcileExtensionsAndRefreshUI(profile.name);
5364
- }
5365
- else {
5366
- // Other resources changed or mixed: use full reload
5367
- void this.refreshAfterProfileMutation(profile.name);
5368
- }
5369
- }
5370
- // Non-active profiles don't need refresh
5803
+ const resolvedEditPath = resolveResourceEditPath(id, pathValue, kind);
5804
+ if (!resolvedEditPath) {
5805
+ this.showWarning(`Resource "${id}" of kind "${kind}" has no editable file path.`);
5806
+ void this.openNewProfileEditor(profileName);
5807
+ return;
5371
5808
  }
5372
- catch (error) {
5373
- this.showError(error instanceof Error ? error.message : String(error));
5809
+ if (!fs.existsSync(resolvedEditPath)) {
5810
+ this.showError(`Resolved path for "${id}" does not exist: ${resolvedEditPath}`);
5811
+ void this.openNewProfileEditor(profileName);
5812
+ return;
5374
5813
  }
5375
- },
5376
- onCancel: () => {
5377
- done();
5378
- this.ui.requestRender();
5814
+ await this.openEditorForPath(resolvedEditPath);
5815
+ await this.handleReloadCommand();
5816
+ void this.openNewProfileEditor(profileName);
5379
5817
  },
5380
5818
  });
5381
5819
  return { component: editor, focus: editor };