@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.
- package/CHANGELOG.md +16 -0
- package/dist/core/profile-resource-selection.d.ts +9 -0
- package/dist/core/profile-resource-selection.d.ts.map +1 -1
- package/dist/core/profile-resource-selection.js +49 -0
- package/dist/core/profile-resource-selection.js.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts +23 -6
- package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.js +253 -23
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +1 -7
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +41 -108
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +13 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +533 -95
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- 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
|
-
|
|
5136
|
+
onResourcesHubAction: (action) => {
|
|
5092
5137
|
done();
|
|
5093
|
-
void this.
|
|
5138
|
+
void this.handleResourcesHubAction(action);
|
|
5094
5139
|
},
|
|
5095
|
-
|
|
5140
|
+
onCancel: () => {
|
|
5096
5141
|
done();
|
|
5097
|
-
|
|
5142
|
+
this.ui.requestRender();
|
|
5098
5143
|
},
|
|
5099
|
-
|
|
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
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5343
|
+
if (value === "create") {
|
|
5344
|
+
void this.createProfileAndOpenLibraryFlow();
|
|
5345
|
+
}
|
|
5346
|
+
else {
|
|
5347
|
+
void this.selectProfileAndOpenLibraryFlow();
|
|
5348
|
+
}
|
|
5349
|
+
}, () => {
|
|
5104
5350
|
done();
|
|
5105
|
-
this.
|
|
5106
|
-
}
|
|
5107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5568
|
+
onCancel: () => {
|
|
5112
5569
|
done();
|
|
5113
|
-
void this.
|
|
5570
|
+
void this.openLibraryManagerFlow();
|
|
5114
5571
|
},
|
|
5115
|
-
|
|
5572
|
+
onScopeChange: () => {
|
|
5116
5573
|
done();
|
|
5117
|
-
void this.
|
|
5574
|
+
void this.promptScopeChangeForProfile(profileName, currentScope);
|
|
5118
5575
|
},
|
|
5119
|
-
|
|
5576
|
+
onEdit: async (id, pathValue, kind) => {
|
|
5120
5577
|
done();
|
|
5121
|
-
|
|
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:
|
|
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
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
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
|
-
|
|
5373
|
-
this.showError(
|
|
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
|
-
|
|
5377
|
-
|
|
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 };
|