@oh-my-pi/pi-coding-agent 13.16.4 → 13.17.0
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 +51 -0
- package/package.json +7 -7
- package/src/cli/args.ts +7 -0
- package/src/cli/classify-install-target.ts +50 -0
- package/src/cli/plugin-cli.ts +245 -31
- package/src/commands/plugin.ts +3 -0
- package/src/config/model-registry.ts +37 -0
- package/src/config/model-resolver.ts +18 -3
- package/src/config/settings-schema.ts +24 -13
- package/src/cursor.ts +66 -1
- package/src/discovery/claude-plugins.ts +95 -5
- package/src/discovery/helpers.ts +168 -41
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/substitute-plugin-root.ts +29 -0
- package/src/extensibility/plugins/index.ts +1 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +354 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +528 -0
- package/src/extensibility/plugins/marketplace/registry.ts +181 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +177 -0
- package/src/extensibility/skills.ts +3 -3
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/local-protocol.ts +2 -19
- package/src/internal-urls/parse.ts +72 -0
- package/src/internal-urls/router.ts +2 -18
- package/src/lsp/config.ts +9 -0
- package/src/main.ts +50 -1
- package/src/modes/components/plugin-selector.ts +86 -0
- package/src/modes/components/settings-defs.ts +9 -4
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/mcp-command-controller.ts +14 -0
- package/src/modes/controllers/selector-controller.ts +104 -13
- package/src/modes/interactive-mode.ts +9 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/agents/reviewer.md +3 -4
- package/src/prompts/tools/bash.md +3 -3
- package/src/sdk.ts +0 -7
- package/src/session/agent-session.ts +292 -6
- package/src/slash-commands/builtin-registry.ts +273 -0
- package/src/tools/bash-skill-urls.ts +48 -5
- package/src/tools/bash.ts +2 -0
- package/src/tools/read.ts +15 -9
- package/src/web/search/code-search.ts +2 -179
- package/src/web/search/index.ts +2 -3
- package/src/web/search/types.ts +1 -5
package/src/lsp/config.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { YAML } from "bun";
|
|
6
6
|
import { getConfigDirPaths } from "../config";
|
|
7
|
+
import { getPreloadedPluginRoots } from "../discovery/helpers";
|
|
7
8
|
import { BiomeClient } from "./clients/biome-client";
|
|
8
9
|
import { SwiftLintClient } from "./clients/swiftlint-client";
|
|
9
10
|
import DEFAULTS from "./defaults.json" with { type: "json" };
|
|
@@ -248,6 +249,14 @@ function getConfigPaths(cwd: string): string[] {
|
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
// Plugin LSP configs (from marketplace/--plugin-dir roots)
|
|
253
|
+
const pluginRoots = getPreloadedPluginRoots();
|
|
254
|
+
for (const root of pluginRoots) {
|
|
255
|
+
for (const filename of filenames) {
|
|
256
|
+
paths.push(path.join(root.path, filename));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
// User home root files (lowest priority fallback)
|
|
252
261
|
for (const filename of filenames) {
|
|
253
262
|
paths.push(path.join(os.homedir(), filename));
|
package/src/main.ts
CHANGED
|
@@ -11,8 +11,9 @@ import * as os from "node:os";
|
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { createInterface } from "node:readline/promises";
|
|
13
13
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
14
|
-
import { $env, getProjectDir, logger, postmortem, setProjectDir, VERSION } from "@oh-my-pi/pi-utils";
|
|
14
|
+
import { $env, getConfigDirName, getProjectDir, logger, postmortem, setProjectDir, VERSION } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import chalk from "chalk";
|
|
16
|
+
import { invalidate as invalidateFsCache } from "./capability/fs";
|
|
16
17
|
import type { Args } from "./cli/args";
|
|
17
18
|
import { processFileArguments } from "./cli/file-processor";
|
|
18
19
|
import { buildInitialMessage } from "./cli/initial-message";
|
|
@@ -23,8 +24,16 @@ import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
|
|
|
23
24
|
import { resolveCliModel, resolveModelRoleValue, resolveModelScope, type ScopedModel } from "./config/model-resolver";
|
|
24
25
|
import { Settings, settings } from "./config/settings";
|
|
25
26
|
import { initializeWithSettings } from "./discovery";
|
|
27
|
+
import { clearClaudePluginRootsCache, injectPluginDirRoots, preloadPluginRoots } from "./discovery/helpers";
|
|
26
28
|
import { exportFromFile } from "./export/html";
|
|
27
29
|
import type { ExtensionUIContext } from "./extensibility/extensions/types";
|
|
30
|
+
import {
|
|
31
|
+
getInstalledPluginsRegistryPath,
|
|
32
|
+
getMarketplacesCacheDir,
|
|
33
|
+
getMarketplacesRegistryPath,
|
|
34
|
+
getPluginsCacheDir,
|
|
35
|
+
MarketplaceManager,
|
|
36
|
+
} from "./extensibility/plugins/marketplace";
|
|
28
37
|
import type { MCPManager } from "./mcp";
|
|
29
38
|
import { InteractiveMode, runAcpMode, runPrintMode, runRpcMode } from "./modes";
|
|
30
39
|
import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
|
|
@@ -639,6 +648,46 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
639
648
|
sessionManager = await SessionManager.open(selectedPath);
|
|
640
649
|
}
|
|
641
650
|
|
|
651
|
+
// Wire --plugin-dir and preload plugin roots for sync consumers (LSP config)
|
|
652
|
+
const home = os.homedir();
|
|
653
|
+
if (parsedArgs.pluginDirs && parsedArgs.pluginDirs.length > 0) {
|
|
654
|
+
await logger.timeAsync("injectPluginDirRoots", () => injectPluginDirRoots(home, parsedArgs.pluginDirs!));
|
|
655
|
+
} else {
|
|
656
|
+
await logger.timeAsync("preloadPluginRoots", () => preloadPluginRoots(home));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Background marketplace auto-update — never blocks startup.
|
|
660
|
+
const autoUpdate = settings.get("marketplace.autoUpdate");
|
|
661
|
+
if (autoUpdate !== "off") {
|
|
662
|
+
void (async () => {
|
|
663
|
+
try {
|
|
664
|
+
const mgr = new MarketplaceManager({
|
|
665
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
666
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
667
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
668
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
669
|
+
clearPluginRootsCache: () => {
|
|
670
|
+
const h = os.homedir();
|
|
671
|
+
invalidateFsCache(path.join(h, ".claude", "plugins", "installed_plugins.json"));
|
|
672
|
+
invalidateFsCache(path.join(h, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
673
|
+
clearClaudePluginRootsCache();
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
await mgr.refreshStaleMarketplaces();
|
|
677
|
+
const updates = await mgr.checkForUpdates();
|
|
678
|
+
if (updates.length === 0) return;
|
|
679
|
+
if (autoUpdate === "auto") {
|
|
680
|
+
await mgr.upgradeAllPlugins();
|
|
681
|
+
logger.debug(`Auto-upgraded ${updates.length} marketplace plugin(s)`);
|
|
682
|
+
} else {
|
|
683
|
+
logger.debug(`${updates.length} marketplace plugin update(s) available \u2014 /marketplace upgrade`);
|
|
684
|
+
}
|
|
685
|
+
} catch {
|
|
686
|
+
// Silently ignore — network failure, corrupt data, offline.
|
|
687
|
+
}
|
|
688
|
+
})();
|
|
689
|
+
}
|
|
690
|
+
|
|
642
691
|
const { options: sessionOptions } = await logger.timeAsync("buildSessionOptions", () =>
|
|
643
692
|
buildSessionOptions(parsedArgs, scopedModels, sessionManager, modelRegistry),
|
|
644
693
|
);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive marketplace plugin selector.
|
|
3
|
+
*
|
|
4
|
+
* Shows available plugins from all configured marketplaces in a SelectList.
|
|
5
|
+
* Selecting a plugin triggers installation. Esc cancels.
|
|
6
|
+
*/
|
|
7
|
+
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { getSelectListTheme } from "../theme/theme";
|
|
9
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
10
|
+
|
|
11
|
+
export interface PluginSelectorCallbacks {
|
|
12
|
+
onSelect: (pluginName: string, marketplace: string) => void;
|
|
13
|
+
onCancel: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PluginItem {
|
|
17
|
+
plugin: { name: string; version?: string; description?: string };
|
|
18
|
+
marketplace: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class PluginSelectorComponent extends Container {
|
|
22
|
+
#selectList: SelectList;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
marketplaceCount: number,
|
|
26
|
+
plugins: PluginItem[],
|
|
27
|
+
installedIds: Set<string>,
|
|
28
|
+
callbacks: PluginSelectorCallbacks,
|
|
29
|
+
) {
|
|
30
|
+
super();
|
|
31
|
+
|
|
32
|
+
const items: SelectItem[] = plugins.map(({ plugin, marketplace }) => {
|
|
33
|
+
const id = `${plugin.name}@${marketplace}`;
|
|
34
|
+
const installed = installedIds.has(id);
|
|
35
|
+
const version = plugin.version ? `@${plugin.version}` : "";
|
|
36
|
+
const status = installed ? " [installed]" : "";
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
value: id,
|
|
40
|
+
label: `${plugin.name}${version}${status}`,
|
|
41
|
+
description: plugin.description,
|
|
42
|
+
hint: marketplace,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (items.length === 0) {
|
|
47
|
+
items.push({
|
|
48
|
+
value: "__empty__",
|
|
49
|
+
label: "No plugins available",
|
|
50
|
+
description:
|
|
51
|
+
marketplaceCount === 0
|
|
52
|
+
? "Add a marketplace first: /marketplace add <source>"
|
|
53
|
+
: "Configured marketplaces have no plugins",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.addChild(new DynamicBorder());
|
|
58
|
+
|
|
59
|
+
this.#selectList = new SelectList(items, Math.min(items.length, 20), getSelectListTheme());
|
|
60
|
+
|
|
61
|
+
this.#selectList.onSelect = item => {
|
|
62
|
+
if (item.value === "__empty__") return;
|
|
63
|
+
const [name, marketplace] = splitPluginId(item.value);
|
|
64
|
+
if (name && marketplace) {
|
|
65
|
+
callbacks.onSelect(name, marketplace);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
this.#selectList.onCancel = () => {
|
|
70
|
+
callbacks.onCancel();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
this.addChild(this.#selectList);
|
|
74
|
+
this.addChild(new DynamicBorder());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getSelectList(): SelectList {
|
|
78
|
+
return this.#selectList;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function splitPluginId(id: string): [string, string] | [null, null] {
|
|
83
|
+
const atIdx = id.lastIndexOf("@");
|
|
84
|
+
if (atIdx <= 0) return [null, null];
|
|
85
|
+
return [id.slice(0, atIdx), id.slice(atIdx + 1)];
|
|
86
|
+
}
|
|
@@ -117,6 +117,15 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
117
117
|
{ value: "5", label: "5 retries" },
|
|
118
118
|
{ value: "10", label: "10 retries" },
|
|
119
119
|
],
|
|
120
|
+
// Retry fallback revert policy
|
|
121
|
+
"retry.fallbackRevertPolicy": [
|
|
122
|
+
{
|
|
123
|
+
value: "cooldown-expiry",
|
|
124
|
+
label: "Cooldown expiry",
|
|
125
|
+
description: "Return to the primary model after its suppression window ends",
|
|
126
|
+
},
|
|
127
|
+
{ value: "never", label: "Never", description: "Stay on the fallback model until manually changed" },
|
|
128
|
+
],
|
|
120
129
|
// Task max concurrency
|
|
121
130
|
"task.maxConcurrency": [
|
|
122
131
|
{ value: "0", label: "Unlimited" },
|
|
@@ -303,10 +312,6 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
303
312
|
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
304
313
|
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
305
314
|
],
|
|
306
|
-
"providers.codeSearch": [
|
|
307
|
-
{ value: "exa", label: "Exa", description: "Uses Exa public MCP code search" },
|
|
308
|
-
{ value: "grep", label: "grep.app", description: "Uses Vercel grep.app public code search" },
|
|
309
|
-
],
|
|
310
315
|
"providers.image": [
|
|
311
316
|
{ value: "auto", label: "Auto", description: "Priority: OpenRouter > Gemini" },
|
|
312
317
|
{ value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
|
|
@@ -534,6 +534,16 @@ export class EventController {
|
|
|
534
534
|
break;
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
case "retry_fallback_applied": {
|
|
538
|
+
this.ctx.showWarning(`Fallback: ${event.from} -> ${event.to}`);
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
case "retry_fallback_succeeded": {
|
|
543
|
+
this.ctx.showStatus(`Fallback succeeded on ${event.model}`);
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
|
|
537
547
|
case "ttsr_triggered": {
|
|
538
548
|
const component = new TtsrNotificationComponent(event.rules);
|
|
539
549
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
@@ -793,6 +793,20 @@ export class MCPCommandController {
|
|
|
793
793
|
}
|
|
794
794
|
}
|
|
795
795
|
|
|
796
|
+
// refreshMCPTools preserves the prior MCP tool selection, so tools from
|
|
797
|
+
// brand-new servers are registered in the registry but never activated.
|
|
798
|
+
// Explicitly activate the newly added server's tools now.
|
|
799
|
+
if (isConnected && this.ctx.mcpManager) {
|
|
800
|
+
const serverTools = this.ctx.mcpManager.getTools().filter(t => t.mcpServerName === name);
|
|
801
|
+
if (serverTools.length > 0) {
|
|
802
|
+
const currentActive = this.ctx.session.getActiveToolNames();
|
|
803
|
+
const toActivate = serverTools.map(t => t.name).filter(n => this.ctx.session.getToolByName(n));
|
|
804
|
+
if (toActivate.length > 0) {
|
|
805
|
+
await this.ctx.session.setActiveToolsByName([...new Set([...currentActive, ...toActivate])]);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
796
810
|
// Show success message
|
|
797
811
|
const scopeLabel = scope === "user" ? "user" : "project";
|
|
798
812
|
const lines = ["", theme.fg("success", `✓ Added server "${name}" to ${scopeLabel} config`), ""];
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
1
3
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
4
|
import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
3
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
6
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { getAgentDbPath, getConfigDirName, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { invalidate as invalidateFsCache } from "../../capability/fs";
|
|
6
9
|
import { getRoleInfo } from "../../config/model-registry";
|
|
7
10
|
import { settings } from "../../config/settings";
|
|
8
11
|
import { DebugSelectorComponent } from "../../debug";
|
|
9
12
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
13
|
+
import { clearClaudePluginRootsCache } from "../../discovery/helpers";
|
|
14
|
+
import {
|
|
15
|
+
getInstalledPluginsRegistryPath,
|
|
16
|
+
getMarketplacesCacheDir,
|
|
17
|
+
getMarketplacesRegistryPath,
|
|
18
|
+
getPluginsCacheDir,
|
|
19
|
+
MarketplaceManager,
|
|
20
|
+
} from "../../extensibility/plugins/marketplace";
|
|
10
21
|
import {
|
|
11
22
|
getAvailableThemes,
|
|
12
23
|
getSymbolTheme,
|
|
@@ -19,13 +30,7 @@ import {
|
|
|
19
30
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
20
31
|
import { type SessionInfo, SessionManager } from "../../session/session-manager";
|
|
21
32
|
import { FileSessionStorage } from "../../session/session-storage";
|
|
22
|
-
import {
|
|
23
|
-
isCodeSearchProviderId,
|
|
24
|
-
isSearchProviderPreference,
|
|
25
|
-
setPreferredCodeSearchProvider,
|
|
26
|
-
setPreferredImageProvider,
|
|
27
|
-
setPreferredSearchProvider,
|
|
28
|
-
} from "../../tools";
|
|
33
|
+
import { isSearchProviderPreference, setPreferredImageProvider, setPreferredSearchProvider } from "../../tools";
|
|
29
34
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
30
35
|
import { AgentDashboard } from "../components/agent-dashboard";
|
|
31
36
|
import { AssistantMessageComponent } from "../components/assistant-message";
|
|
@@ -33,6 +38,7 @@ import { ExtensionDashboard } from "../components/extensions";
|
|
|
33
38
|
import { HistorySearchComponent } from "../components/history-search";
|
|
34
39
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
35
40
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
41
|
+
import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
36
42
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
37
43
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
38
44
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
@@ -355,11 +361,6 @@ export class SelectorController {
|
|
|
355
361
|
setPreferredSearchProvider(value);
|
|
356
362
|
}
|
|
357
363
|
break;
|
|
358
|
-
case "providers.codeSearch":
|
|
359
|
-
if (typeof value === "string" && isCodeSearchProviderId(value)) {
|
|
360
|
-
setPreferredCodeSearchProvider(value);
|
|
361
|
-
}
|
|
362
|
-
break;
|
|
363
364
|
case "providers.image":
|
|
364
365
|
if (value === "auto" || value === "gemini" || value === "openrouter") {
|
|
365
366
|
setPreferredImageProvider(value);
|
|
@@ -425,6 +426,96 @@ export class SelectorController {
|
|
|
425
426
|
});
|
|
426
427
|
}
|
|
427
428
|
|
|
429
|
+
async showPluginSelector(mode: "install" | "uninstall" = "install"): Promise<void> {
|
|
430
|
+
const mgr = new MarketplaceManager({
|
|
431
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
432
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
433
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
434
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
435
|
+
clearPluginRootsCache: () => {
|
|
436
|
+
const home = os.homedir();
|
|
437
|
+
invalidateFsCache(path.join(home, ".claude", "plugins", "installed_plugins.json"));
|
|
438
|
+
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
439
|
+
clearClaudePluginRootsCache();
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const [marketplaces, installed] = await Promise.all([mgr.listMarketplaces(), mgr.listInstalledPlugins()]);
|
|
444
|
+
const installedIds = new Set(installed.map(p => p.id));
|
|
445
|
+
|
|
446
|
+
if (mode === "uninstall") {
|
|
447
|
+
// Show only installed plugins for uninstall
|
|
448
|
+
const items = installed.map(p => {
|
|
449
|
+
const entry = p.entries[0];
|
|
450
|
+
const atIdx = p.id.lastIndexOf("@");
|
|
451
|
+
const pluginName = atIdx > 0 ? p.id.slice(0, atIdx) : p.id;
|
|
452
|
+
const mkt = atIdx > 0 ? p.id.slice(atIdx + 1) : "unknown";
|
|
453
|
+
return {
|
|
454
|
+
plugin: { name: pluginName, version: entry?.version, description: undefined as string | undefined },
|
|
455
|
+
marketplace: mkt,
|
|
456
|
+
};
|
|
457
|
+
});
|
|
458
|
+
this.showSelector(done => {
|
|
459
|
+
const selector = new PluginSelectorComponent(marketplaces.length, items, new Set(), {
|
|
460
|
+
onSelect: async (name, marketplace) => {
|
|
461
|
+
done();
|
|
462
|
+
const pluginId = `${name}@${marketplace}`;
|
|
463
|
+
this.ctx.showStatus(`Uninstalling ${pluginId}...`);
|
|
464
|
+
this.ctx.ui.requestRender();
|
|
465
|
+
try {
|
|
466
|
+
await mgr.uninstallPlugin(pluginId);
|
|
467
|
+
this.ctx.showStatus(`Uninstalled ${pluginId}`);
|
|
468
|
+
} catch (err) {
|
|
469
|
+
this.ctx.showStatus(`Uninstall failed: ${err}`);
|
|
470
|
+
}
|
|
471
|
+
this.ctx.ui.requestRender();
|
|
472
|
+
},
|
|
473
|
+
onCancel: () => {
|
|
474
|
+
done();
|
|
475
|
+
this.ctx.ui.requestRender();
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
return { component: selector, focus: selector.getSelectList() };
|
|
479
|
+
});
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Install mode: show all available plugins from all marketplaces
|
|
484
|
+
const allPlugins: Array<{
|
|
485
|
+
plugin: { name: string; version?: string; description?: string };
|
|
486
|
+
marketplace: string;
|
|
487
|
+
}> = [];
|
|
488
|
+
for (const mkt of marketplaces) {
|
|
489
|
+
const plugins = await mgr.listAvailablePlugins(mkt.name);
|
|
490
|
+
for (const plugin of plugins) {
|
|
491
|
+
allPlugins.push({ plugin, marketplace: mkt.name });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
this.showSelector(done => {
|
|
496
|
+
const selector = new PluginSelectorComponent(marketplaces.length, allPlugins, installedIds, {
|
|
497
|
+
onSelect: async (name, marketplace) => {
|
|
498
|
+
done();
|
|
499
|
+
this.ctx.showStatus(`Installing ${name} from ${marketplace}...`);
|
|
500
|
+
this.ctx.ui.requestRender();
|
|
501
|
+
try {
|
|
502
|
+
const force = installedIds.has(`${name}@${marketplace}`);
|
|
503
|
+
await mgr.installPlugin(name, marketplace, { force });
|
|
504
|
+
this.ctx.showStatus(`Installed ${name} from ${marketplace}`);
|
|
505
|
+
} catch (err) {
|
|
506
|
+
this.ctx.showStatus(`Install failed: ${err}`);
|
|
507
|
+
}
|
|
508
|
+
this.ctx.ui.requestRender();
|
|
509
|
+
},
|
|
510
|
+
onCancel: () => {
|
|
511
|
+
done();
|
|
512
|
+
this.ctx.ui.requestRender();
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
return { component: selector, focus: selector.getSelectList() };
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
428
519
|
showUserMessageSelector(): void {
|
|
429
520
|
const userMessages = this.ctx.session.getUserMessagesForBranching();
|
|
430
521
|
|
|
@@ -306,6 +306,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
306
306
|
|
|
307
307
|
const startupQuiet = settings.get("startup.quiet");
|
|
308
308
|
|
|
309
|
+
for (const warning of this.session.configWarnings) {
|
|
310
|
+
this.ui.addChild(new Text(theme.fg("warning", `Warning: ${warning}`), 1, 0));
|
|
311
|
+
this.ui.addChild(new Spacer(1));
|
|
312
|
+
}
|
|
313
|
+
|
|
309
314
|
if (!startupQuiet) {
|
|
310
315
|
// Add welcome header
|
|
311
316
|
const welcome = new WelcomeComponent(this.#version, modelName, providerName, recentSessions, lspServerInfo);
|
|
@@ -1238,6 +1243,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1238
1243
|
this.#selectorController.showModelSelector(options);
|
|
1239
1244
|
}
|
|
1240
1245
|
|
|
1246
|
+
showPluginSelector(mode?: "install" | "uninstall"): void {
|
|
1247
|
+
void this.#selectorController.showPluginSelector(mode);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1241
1250
|
showUserMessageSelector(): void {
|
|
1242
1251
|
this.#selectorController.showUserMessageSelector();
|
|
1243
1252
|
}
|
package/src/modes/types.ts
CHANGED
|
@@ -198,6 +198,7 @@ export interface InteractiveModeContext {
|
|
|
198
198
|
showExtensionsDashboard(): void;
|
|
199
199
|
showAgentsDashboard(): void;
|
|
200
200
|
showModelSelector(options?: { temporaryOnly?: boolean }): void;
|
|
201
|
+
showPluginSelector(mode?: "install" | "uninstall"): void;
|
|
201
202
|
showUserMessageSelector(): void;
|
|
202
203
|
showTreeSelector(): void;
|
|
203
204
|
showSessionSelector(): void;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: reviewer
|
|
3
3
|
description: "Code review specialist for quality/security analysis"
|
|
4
4
|
tools: read, grep, find, bash, lsp, fetch, web_search, ast_grep, report_finding
|
|
5
|
-
spawns: explore
|
|
5
|
+
spawns: explore
|
|
6
6
|
model: pi/slow
|
|
7
7
|
thinking-level: high
|
|
8
8
|
blocking: true
|
|
@@ -62,9 +62,8 @@ Your goal is to identify bugs the author would want fixed before merge.
|
|
|
62
62
|
<procedure>
|
|
63
63
|
1. Run `git diff` (or `gh pr diff <number>`) to view patch
|
|
64
64
|
2. Read modified files for full context
|
|
65
|
-
3.
|
|
66
|
-
4. Call `
|
|
67
|
-
5. Call `submit_result` with verdict
|
|
65
|
+
3. Call `report_finding` per issue
|
|
66
|
+
4. Call `submit_result` with verdict
|
|
68
67
|
|
|
69
68
|
Bash is read-only: `git diff`, `git log`, `git show`, `gh pr diff`. You **MUST NOT** make file edits or trigger builds.
|
|
70
69
|
</procedure>
|
|
@@ -30,10 +30,10 @@ You **MUST** use specialized tools instead of bash for ALL file operations:
|
|
|
30
30
|
|---|---|
|
|
31
31
|
|`cat file`, `head -n N file`|`read(path="file", limit=N)`|
|
|
32
32
|
|`cat -n file \|sed -n '50,150p'`|`read(path="file", offset=50, limit=100)`|
|
|
33
|
-
|`grep -A 20 'pat' file`|`grep(pattern="pat", path="file", post=20)`|
|
|
33
|
+
{{#if hasGrep}}|`grep -A 20 'pat' file`|`grep(pattern="pat", path="file", post=20)`|
|
|
34
34
|
|`grep -rn 'pat' dir/`|`grep(pattern="pat", path="dir/")`|
|
|
35
|
-
|`rg 'pattern' dir/`|`grep(pattern="pattern", path="dir/")`|
|
|
36
|
-
|`find dir -name '*.ts'`|`find(pattern="dir/**/*.ts")`|
|
|
35
|
+
|`rg 'pattern' dir/`|`grep(pattern="pattern", path="dir/")`|{{/if}}
|
|
36
|
+
{{#if hasFind}}|`find dir -name '*.ts'`|`find(pattern="dir/**/*.ts")`|{{/if}}
|
|
37
37
|
|`ls dir/`|`read(path="dir/")`|
|
|
38
38
|
|`cat <<'EOF' > file`|`write(path="file", content="…")`|
|
|
39
39
|
|`sed -i 's/old/new/' file`|`edit(path="file", edits=[…])`|
|
package/src/sdk.ts
CHANGED
|
@@ -106,14 +106,12 @@ import {
|
|
|
106
106
|
GrepTool,
|
|
107
107
|
getSearchTools,
|
|
108
108
|
HIDDEN_TOOLS,
|
|
109
|
-
isCodeSearchProviderId,
|
|
110
109
|
isSearchProviderPreference,
|
|
111
110
|
loadSshTool,
|
|
112
111
|
PythonTool,
|
|
113
112
|
ReadTool,
|
|
114
113
|
ResolveTool,
|
|
115
114
|
renderSearchToolBm25Description,
|
|
116
|
-
setPreferredCodeSearchProvider,
|
|
117
115
|
setPreferredImageProvider,
|
|
118
116
|
setPreferredSearchProvider,
|
|
119
117
|
type Tool,
|
|
@@ -667,11 +665,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
667
665
|
setPreferredSearchProvider(webSearchProvider);
|
|
668
666
|
}
|
|
669
667
|
|
|
670
|
-
const codeSearchProvider = settings.get("providers.codeSearch");
|
|
671
|
-
if (typeof codeSearchProvider === "string" && isCodeSearchProviderId(codeSearchProvider)) {
|
|
672
|
-
setPreferredCodeSearchProvider(codeSearchProvider);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
668
|
const imageProvider = settings.get("providers.image");
|
|
676
669
|
if (imageProvider === "auto" || imageProvider === "gemini" || imageProvider === "openrouter") {
|
|
677
670
|
setPreferredImageProvider(imageProvider);
|