decorated-pi 0.5.4 → 0.6.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/.codegraph/daemon.pid +6 -0
- package/AGENTS.md +92 -0
- package/README.md +53 -64
- package/commands/dp-model.ts +23 -0
- package/commands/dp-settings.ts +23 -0
- package/commands/mcp-status.ts +62 -0
- package/commands/retry.ts +19 -0
- package/commands/usage.ts +544 -0
- package/hooks/compaction.ts +204 -0
- package/hooks/externalize.ts +70 -0
- package/hooks/image-vision.ts +132 -0
- package/hooks/inject-agents-md.ts +164 -0
- package/hooks/mcp.ts +340 -0
- package/hooks/normalize-codeblocks.ts +88 -0
- package/hooks/pi-tool-filter.ts +28 -0
- package/{extensions/safety → hooks/redact}/types.ts +1 -8
- package/hooks/redact.ts +104 -0
- package/{extensions → hooks}/rtk.ts +92 -115
- package/hooks/session-title.ts +54 -0
- package/hooks/skeleton.ts +212 -0
- package/hooks/smart-at.ts +318 -0
- package/{extensions/file-times.ts → hooks/track-mtime.ts} +40 -38
- package/{extensions → hooks}/wakatime.ts +120 -122
- package/index.ts +155 -1
- package/package.json +5 -4
- package/{extensions/settings.ts → settings.ts} +32 -0
- package/{extensions → tools}/lsp/client.ts +0 -25
- package/{extensions → tools}/lsp/format.ts +2 -99
- package/{extensions → tools}/lsp/index.ts +1 -1
- package/{extensions → tools}/lsp/servers.ts +1 -1
- package/{extensions → tools}/lsp/tools.ts +1 -66
- package/{extensions → tools}/lsp/types.ts +0 -11
- package/tools/mcp/builtin/codegraph.ts +45 -0
- package/tools/mcp/builtin/context7.ts +10 -0
- package/tools/mcp/builtin/exa.ts +10 -0
- package/tools/mcp/builtin/index.ts +19 -0
- package/tools/mcp/cache.ts +124 -0
- package/{extensions → tools}/mcp/client.ts +2 -2
- package/{extensions/mcp/builtin.ts → tools/mcp/config.ts} +21 -181
- package/tools/mcp/index.ts +44 -0
- package/tools/mcp/tool-definition.ts +112 -0
- package/{extensions/patch.ts → tools/patch/core.ts} +336 -65
- package/{extensions/io.ts → tools/patch/index.ts} +29 -205
- package/tsconfig.json +10 -1
- package/ui/mcp-status.ts +202 -0
- package/ui/model-picker.ts +162 -0
- package/ui/module-settings.ts +83 -0
- package/ui/usage.ts +396 -0
- package/extensions/index.ts +0 -154
- package/extensions/io-tool-output.ts +0 -33
- package/extensions/mcp/index.ts +0 -435
- package/extensions/model-integration.ts +0 -531
- package/extensions/providers/ark-coding.ts +0 -75
- package/extensions/providers/index.ts +0 -9
- package/extensions/providers/ollama-cloud.ts +0 -101
- package/extensions/providers/qianfan-coding.ts +0 -71
- package/extensions/safety/index.ts +0 -102
- package/extensions/session-title.ts +0 -40
- package/extensions/slash.ts +0 -458
- package/extensions/smart-at.ts +0 -481
- package/extensions/subdir-agents.ts +0 -151
- /package/{extensions/safety → hooks/redact}/detect.ts +0 -0
- /package/{extensions/safety → hooks/redact}/entropy.ts +0 -0
- /package/{extensions/safety → hooks/redact}/patterns.ts +0 -0
- /package/{extensions → tools}/lsp/env.ts +0 -0
- /package/{extensions → tools}/lsp/manager.ts +0 -0
- /package/{extensions → tools}/lsp/prompt.ts +0 -0
- /package/{extensions → tools}/lsp/protocol.ts +0 -0
package/extensions/slash.ts
DELETED
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slash — 所有扩展命令
|
|
3
|
-
*
|
|
4
|
-
* /dp-model → 模型选择器 (TAB 切换 Image/Compact)
|
|
5
|
-
* /dp-settings → 模块开关 (patch / safety / lsp / smart-at)
|
|
6
|
-
* /retry → 中断后继续
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { ExtensionAPI, ExtensionContext, Theme as PiTheme } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import { ModelPickerComponent } from "./model-integration.js";
|
|
11
|
-
import { getAllModuleSettings, setModuleEnabled, type ModuleSettings } from "./settings.js";
|
|
12
|
-
import { getMcpStatus, refreshServerCache, updateConfigEnabled } from "./mcp/index.js";
|
|
13
|
-
import { toggleMcpServerEnabled } from "./mcp/builtin.js";
|
|
14
|
-
import { Container, SettingsList, Spacer, Text, type TUI, type SettingsListTheme, type Component, getKeybindings } from "@earendil-works/pi-tui";
|
|
15
|
-
|
|
16
|
-
// ─── Border component (matches native DynamicBorder) ────────────────────────
|
|
17
|
-
|
|
18
|
-
class DynamicBorder implements Component {
|
|
19
|
-
private colorFn: (str: string) => string;
|
|
20
|
-
|
|
21
|
-
constructor(theme: PiTheme) {
|
|
22
|
-
this.colorFn = (str: string) => theme.fg("border", str);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
invalidate() {}
|
|
26
|
-
|
|
27
|
-
render(width: number): string[] {
|
|
28
|
-
return [this.colorFn("─".repeat(Math.max(1, width)))];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ─── SettingsList Theme (matches native getSettingsListTheme) ───────────────
|
|
33
|
-
|
|
34
|
-
function getSettingsListTheme(theme: PiTheme): SettingsListTheme {
|
|
35
|
-
return {
|
|
36
|
-
label: (text: string, selected: boolean) => selected ? theme.fg("accent", text) : text,
|
|
37
|
-
value: (text: string, selected: boolean) => selected ? theme.fg("accent", text) : theme.fg("muted", text),
|
|
38
|
-
description: (text: string) => theme.fg("dim", text),
|
|
39
|
-
cursor: theme.fg("accent", "→ "),
|
|
40
|
-
hint: (text: string) => theme.fg("dim", text),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ─── /dp-model ─────────────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
function setupDpModelCommand(pi: ExtensionAPI) {
|
|
47
|
-
pi.registerCommand("dp-model", {
|
|
48
|
-
description: "Configure image and compact models",
|
|
49
|
-
handler: async (_args, ctx) => {
|
|
50
|
-
if (ctx.hasUI) {
|
|
51
|
-
await ctx.ui.custom<void>(
|
|
52
|
-
(tui, theme, _kb, done) =>
|
|
53
|
-
new ModelPickerComponent(tui, theme, ctx.modelRegistry, () => done(undefined))
|
|
54
|
-
);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
ctx.ui.notify("dp-model requires interactive mode.", "warning");
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ─── /dp-settings ──────────────────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
const MODULE_LABELS: Record<keyof ModuleSettings, string> = {
|
|
65
|
-
patch: "patch",
|
|
66
|
-
safety: "Secret Redaction",
|
|
67
|
-
lsp: "LSP",
|
|
68
|
-
"smart-at": "@ overload",
|
|
69
|
-
mcp: "MCP",
|
|
70
|
-
wakatime: "WakaTime",
|
|
71
|
-
"rtk": "RTK",
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const MODULE_DESCS: Record<keyof ModuleSettings, string> = {
|
|
75
|
-
patch: "Replace edit/write with patch tool (old_str/new_str replacement + overwrite)",
|
|
76
|
-
safety: "Redact secrets from read / bash output before they enter model context",
|
|
77
|
-
lsp: "Language server diagnostics, hover, definition, references, symbols, rename",
|
|
78
|
-
"smart-at": "Project-aware file search replacing default autocomplete",
|
|
79
|
-
mcp: "MCP client for context7 and exa (zero-config)",
|
|
80
|
-
wakatime: "Send coding activity heartbeats to WakaTime",
|
|
81
|
-
"rtk": "Rewrite bash through system RTK when available",
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
class ModuleSettingsComponent extends Container {
|
|
85
|
-
private settingsList: SettingsList;
|
|
86
|
-
|
|
87
|
-
constructor(tui: TUI, theme: PiTheme, onDone: () => void) {
|
|
88
|
-
super();
|
|
89
|
-
const modules = getAllModuleSettings();
|
|
90
|
-
const keys = Object.keys(MODULE_LABELS) as (keyof ModuleSettings)[];
|
|
91
|
-
|
|
92
|
-
const items = keys.map(k => ({
|
|
93
|
-
id: k,
|
|
94
|
-
label: MODULE_LABELS[k],
|
|
95
|
-
description: MODULE_DESCS[k],
|
|
96
|
-
currentValue: modules[k] ? "on" : "off",
|
|
97
|
-
values: ["on", "off"],
|
|
98
|
-
}));
|
|
99
|
-
|
|
100
|
-
this.addChild(new DynamicBorder(theme));
|
|
101
|
-
|
|
102
|
-
this.settingsList = new SettingsList(
|
|
103
|
-
items, 10, getSettingsListTheme(theme),
|
|
104
|
-
(id: string, newValue: string) => {
|
|
105
|
-
setModuleEnabled(id as keyof ModuleSettings, newValue === "on");
|
|
106
|
-
tui.requestRender();
|
|
107
|
-
},
|
|
108
|
-
() => onDone(),
|
|
109
|
-
{ enableSearch: true },
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
this.addChild(this.settingsList);
|
|
113
|
-
this.addChild(new DynamicBorder(theme));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
handleInput(data: string) {
|
|
117
|
-
this.settingsList.handleInput(data);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function setupDpSettingsCommand(pi: ExtensionAPI) {
|
|
122
|
-
pi.registerCommand("dp-settings", {
|
|
123
|
-
description: "Toggle decorated-pi modules on/off",
|
|
124
|
-
handler: async (_args, ctx) => {
|
|
125
|
-
if (ctx.hasUI) {
|
|
126
|
-
await ctx.ui.custom<void>(
|
|
127
|
-
(tui, theme, _kb, done) =>
|
|
128
|
-
new ModuleSettingsComponent(tui, theme, () => done(undefined))
|
|
129
|
-
);
|
|
130
|
-
ctx.ui.notify("Module settings updated. /reload to apply.", "warning");
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
ctx.ui.notify("dp-settings requires interactive mode.", "warning");
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ─── /mcp ──────────────────────────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
class McpStatusComponent extends Container {
|
|
141
|
-
private linesComponent: Text;
|
|
142
|
-
private hintComponent: Text;
|
|
143
|
-
private notifyComponent: Text;
|
|
144
|
-
private tui: TUI;
|
|
145
|
-
private theme: PiTheme;
|
|
146
|
-
private done: () => void;
|
|
147
|
-
private registry: any;
|
|
148
|
-
private selected = 0;
|
|
149
|
-
private servers: ReturnType<typeof getMcpStatus> = [];
|
|
150
|
-
private notifyText = "";
|
|
151
|
-
private notifyTimer: ReturnType<typeof setTimeout> | null = null;
|
|
152
|
-
private refreshing = new Set<string>();
|
|
153
|
-
private autoRefreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
154
|
-
|
|
155
|
-
private cwd: string;
|
|
156
|
-
|
|
157
|
-
constructor(tui: TUI, theme: PiTheme, registry: any, onDone: () => void, cwd: string) {
|
|
158
|
-
super();
|
|
159
|
-
this.tui = tui;
|
|
160
|
-
this.theme = theme;
|
|
161
|
-
this.done = onDone;
|
|
162
|
-
this.registry = registry;
|
|
163
|
-
this.cwd = cwd;
|
|
164
|
-
|
|
165
|
-
this.addChild(new DynamicBorder(theme));
|
|
166
|
-
this.addChild(new Spacer(1));
|
|
167
|
-
|
|
168
|
-
this.linesComponent = new Text("", 1, 0);
|
|
169
|
-
this.addChild(this.linesComponent);
|
|
170
|
-
|
|
171
|
-
this.addChild(new Spacer(1));
|
|
172
|
-
|
|
173
|
-
this.notifyComponent = new Text("", 1, 0);
|
|
174
|
-
this.addChild(this.notifyComponent);
|
|
175
|
-
|
|
176
|
-
this.hintComponent = new Text("", 1, 0);
|
|
177
|
-
this.addChild(this.hintComponent);
|
|
178
|
-
|
|
179
|
-
this.addChild(new Spacer(1));
|
|
180
|
-
this.addChild(new DynamicBorder(theme));
|
|
181
|
-
|
|
182
|
-
this.updateServers();
|
|
183
|
-
this.renderView();
|
|
184
|
-
|
|
185
|
-
// Auto-refresh while any server is still connecting
|
|
186
|
-
this.autoRefreshTimer = setInterval(() => {
|
|
187
|
-
this.updateServers();
|
|
188
|
-
this.renderView();
|
|
189
|
-
const allSettled = this.servers.every((s) => s.state !== "connecting");
|
|
190
|
-
if (allSettled && this.autoRefreshTimer) {
|
|
191
|
-
clearInterval(this.autoRefreshTimer);
|
|
192
|
-
this.autoRefreshTimer = null;
|
|
193
|
-
}
|
|
194
|
-
}, 500);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private updateServers() {
|
|
198
|
-
this.servers = getMcpStatus();
|
|
199
|
-
if (this.selected >= this.servers.length) {
|
|
200
|
-
this.selected = Math.max(0, this.servers.length - 1);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
private renderView() {
|
|
205
|
-
if (this.servers.length === 0) {
|
|
206
|
-
this.linesComponent.setText("No MCP servers configured.");
|
|
207
|
-
this.hintComponent.setText(this.theme.fg("dim", "q close"));
|
|
208
|
-
this.tui.requestRender();
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const lines: string[] = [`MCP servers (${this.servers.length}):`, ""];
|
|
213
|
-
|
|
214
|
-
const namePad = Math.max(...this.servers.map((s) => s.name.length), 12);
|
|
215
|
-
|
|
216
|
-
for (let i = 0; i < this.servers.length; i++) {
|
|
217
|
-
const s = this.servers[i];
|
|
218
|
-
const isSelected = i === this.selected;
|
|
219
|
-
const isDisabled = s.state === "disabled";
|
|
220
|
-
const cursor = isSelected ? this.theme.fg("accent", "→ ") : " ";
|
|
221
|
-
|
|
222
|
-
let statusIcon: string;
|
|
223
|
-
let statusColor: (s: string) => string;
|
|
224
|
-
if (s.state === "connected") {
|
|
225
|
-
statusIcon = "🟢";
|
|
226
|
-
statusColor = (str: string) => this.theme.fg("accent", str);
|
|
227
|
-
} else if (s.state === "connecting") {
|
|
228
|
-
statusIcon = "🟡";
|
|
229
|
-
statusColor = (str: string) => this.theme.fg("warning", str);
|
|
230
|
-
} else if (s.state === "disabled") {
|
|
231
|
-
statusIcon = "⚪";
|
|
232
|
-
statusColor = (str: string) => this.theme.fg("dim", str);
|
|
233
|
-
} else {
|
|
234
|
-
statusIcon = "🔴";
|
|
235
|
-
statusColor = (str: string) => this.theme.fg("error", str);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const name = isDisabled
|
|
239
|
-
? this.theme.fg("dim", s.name)
|
|
240
|
-
: isSelected
|
|
241
|
-
? this.theme.fg("accent", s.name)
|
|
242
|
-
: s.name;
|
|
243
|
-
const namePadding = " ".repeat(Math.max(0, namePad - s.name.length));
|
|
244
|
-
const desc = s.description ? ` — ${s.description.slice(0, 50)}` : "";
|
|
245
|
-
const descDim = isDisabled ? this.theme.fg("dim", desc) : desc;
|
|
246
|
-
lines.push(
|
|
247
|
-
`${cursor}${name}${namePadding} ${statusIcon} ${statusColor(s.state)}${descDim}`
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
if (isSelected) {
|
|
251
|
-
lines.push(` ${this.theme.fg("dim", s.url)}`);
|
|
252
|
-
if (s.error) {
|
|
253
|
-
lines.push(` ${this.theme.fg("error", `Error: ${s.error}`)}`);
|
|
254
|
-
}
|
|
255
|
-
if (s.tools.length > 0) {
|
|
256
|
-
lines.push(` ${s.toolCount} tool${s.toolCount === 1 ? "" : "s"}:`);
|
|
257
|
-
for (const tool of s.tools.slice(0, 6)) {
|
|
258
|
-
const flat = (tool.description ?? "").replace(/\s+/g, " ").trim();
|
|
259
|
-
const td = flat
|
|
260
|
-
? flat.length > 55
|
|
261
|
-
? ` — ${flat.slice(0, 55)}…`
|
|
262
|
-
: ` — ${flat}`
|
|
263
|
-
: "";
|
|
264
|
-
lines.push(` ${tool.name}${td}`);
|
|
265
|
-
}
|
|
266
|
-
if (s.tools.length > 6) {
|
|
267
|
-
lines.push(` ... and ${s.tools.length - 6} more`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
lines.push("");
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
this.linesComponent.setText(lines.join("\n"));
|
|
275
|
-
|
|
276
|
-
const s = this.servers[this.selected];
|
|
277
|
-
const toggleHint = s?.state === "disabled" ? "space enable" : "space disable";
|
|
278
|
-
const hintParts = ["↑↓ navigate", toggleHint, "r refresh", "q close"];
|
|
279
|
-
this.hintComponent.setText(this.theme.fg("dim", hintParts.join(" | ")));
|
|
280
|
-
this.notifyComponent.setText(
|
|
281
|
-
this.notifyText ? this.theme.fg("warning", this.notifyText) : ""
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
this.tui.requestRender();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
private showNotify(text: string) {
|
|
288
|
-
this.notifyText = text;
|
|
289
|
-
this.renderView();
|
|
290
|
-
if (this.notifyTimer) clearTimeout(this.notifyTimer);
|
|
291
|
-
this.notifyTimer = setTimeout(() => {
|
|
292
|
-
this.notifyText = "";
|
|
293
|
-
this.renderView();
|
|
294
|
-
}, 3000);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
private clearNotify() {
|
|
298
|
-
if (this.notifyTimer) {
|
|
299
|
-
clearTimeout(this.notifyTimer);
|
|
300
|
-
this.notifyTimer = null;
|
|
301
|
-
}
|
|
302
|
-
this.notifyText = "";
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
handleInput(data: string) {
|
|
306
|
-
const kb = getKeybindings();
|
|
307
|
-
|
|
308
|
-
// Navigation
|
|
309
|
-
if (kb.matches(data, "tui.select.up")) {
|
|
310
|
-
this.selected = Math.max(0, this.selected - 1);
|
|
311
|
-
this.clearNotify();
|
|
312
|
-
this.renderView();
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (kb.matches(data, "tui.select.down")) {
|
|
316
|
-
this.selected = Math.min(this.servers.length - 1, this.selected + 1);
|
|
317
|
-
this.clearNotify();
|
|
318
|
-
this.renderView();
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Quit
|
|
323
|
-
if (
|
|
324
|
-
data === "q" ||
|
|
325
|
-
data === "\r" ||
|
|
326
|
-
data === "\n" ||
|
|
327
|
-
kb.matches(data, "tui.select.cancel")
|
|
328
|
-
) {
|
|
329
|
-
if (this.autoRefreshTimer) { clearInterval(this.autoRefreshTimer); this.autoRefreshTimer = null; }
|
|
330
|
-
if (this.notifyTimer) { clearTimeout(this.notifyTimer); this.notifyTimer = null; }
|
|
331
|
-
this.done();
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (this.servers.length === 0) return;
|
|
336
|
-
const s = this.servers[this.selected];
|
|
337
|
-
|
|
338
|
-
// Toggle enable/disable
|
|
339
|
-
if (data === " ") {
|
|
340
|
-
const scope = s.source === "project" ? "project" : "global";
|
|
341
|
-
const newEnabled = s.state === "disabled";
|
|
342
|
-
const ok = toggleMcpServerEnabled(s.name, newEnabled, scope, this.cwd || undefined);
|
|
343
|
-
if (ok) {
|
|
344
|
-
updateConfigEnabled(s.name, newEnabled);
|
|
345
|
-
}
|
|
346
|
-
this.showNotify(
|
|
347
|
-
ok
|
|
348
|
-
? `${newEnabled ? "Enabled" : "Disabled"} "${s.name}". Use /reload to apply.`
|
|
349
|
-
: `Failed to toggle "${s.name}".`
|
|
350
|
-
);
|
|
351
|
-
this.updateServers();
|
|
352
|
-
this.renderView();
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Refresh cache (reconnect + update)
|
|
357
|
-
if (data === "r" || data === "r") {
|
|
358
|
-
if (this.refreshing.has(s.name)) return;
|
|
359
|
-
this.refreshing.add(s.name);
|
|
360
|
-
const targetName = s.name;
|
|
361
|
-
const targetIndex = this.selected;
|
|
362
|
-
this.showNotify(`Refreshing "${targetName}"...`);
|
|
363
|
-
(async () => {
|
|
364
|
-
const result = await refreshServerCache(targetName, this.registry);
|
|
365
|
-
this.refreshing.delete(targetName);
|
|
366
|
-
// Only update UI if user hasn't navigated away
|
|
367
|
-
if (this.selected === targetIndex) {
|
|
368
|
-
this.updateServers();
|
|
369
|
-
this.renderView();
|
|
370
|
-
this.showNotify(result.ok ? `Refreshed "${targetName}".` : `Refresh failed: ${result.error}`);
|
|
371
|
-
}
|
|
372
|
-
})();
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
dispose() {
|
|
380
|
-
if (this.autoRefreshTimer) { clearInterval(this.autoRefreshTimer); this.autoRefreshTimer = null; }
|
|
381
|
-
if (this.notifyTimer) { clearTimeout(this.notifyTimer); this.notifyTimer = null; }
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function setupMcpCommand(pi: ExtensionAPI) {
|
|
386
|
-
pi.registerCommand("mcp", {
|
|
387
|
-
description: "Show active MCP servers and their tools",
|
|
388
|
-
handler: async (_args, ctx) => {
|
|
389
|
-
if (ctx.hasUI) {
|
|
390
|
-
await ctx.ui.custom<void>(
|
|
391
|
-
(tui, theme, _kb, done) => new McpStatusComponent(tui, theme, ctx.modelRegistry, () => done(undefined), ctx.cwd)
|
|
392
|
-
);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Fallback for non-interactive (print / RPC) mode.
|
|
397
|
-
const servers = getMcpStatus();
|
|
398
|
-
if (servers.length === 0) {
|
|
399
|
-
ctx.ui.notify("No MCP servers configured.", "info");
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const lines: string[] = [`MCP servers (${servers.length}):`, ""];
|
|
404
|
-
for (const s of servers) {
|
|
405
|
-
lines.push(`• ${s.name} (${s.source})`);
|
|
406
|
-
lines.push(` URL: ${s.url}`);
|
|
407
|
-
if (s.state === "connecting") {
|
|
408
|
-
lines.push(` Status: connecting...`);
|
|
409
|
-
} else if (s.state === "failed") {
|
|
410
|
-
lines.push(` Status: failed — ${s.error ?? "unknown error"}`);
|
|
411
|
-
} else {
|
|
412
|
-
lines.push(` Tools: ${s.toolCount}`);
|
|
413
|
-
for (const tool of s.tools) {
|
|
414
|
-
const desc = tool.description ? ` — ${tool.description.slice(0, 60)}` : "";
|
|
415
|
-
lines.push(` - ${tool.name}${desc}`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
lines.push("");
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
pi.sendMessage({ customType: "mcp-status", content: lines.join("\n"), display: true }, { triggerTurn: false });
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// ─── /retry ────────────────────────────────────────────────────────────────
|
|
427
|
-
|
|
428
|
-
function setupRetryCommand(pi: ExtensionAPI) {
|
|
429
|
-
let retryInProgress = false;
|
|
430
|
-
|
|
431
|
-
pi.registerCommand("retry", {
|
|
432
|
-
description: "Continue after interruption",
|
|
433
|
-
handler: async (_args, ctx) => {
|
|
434
|
-
if (retryInProgress) {
|
|
435
|
-
ctx.ui.notify("Retry is already in progress", "warning");
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
if (!ctx.isIdle()) ctx.abort();
|
|
439
|
-
|
|
440
|
-
retryInProgress = true;
|
|
441
|
-
pi.sendMessage(
|
|
442
|
-
{ customType: "retry-trigger", content: "Continue.", display: false },
|
|
443
|
-
{ triggerTurn: true }
|
|
444
|
-
);
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
pi.on("agent_start", () => { retryInProgress = false; });
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// ─── 入口 ───────────────────────────────────────────────────────────────────
|
|
452
|
-
|
|
453
|
-
export function setupSlash(pi: ExtensionAPI) {
|
|
454
|
-
setupDpModelCommand(pi);
|
|
455
|
-
setupDpSettingsCommand(pi);
|
|
456
|
-
setupMcpCommand(pi);
|
|
457
|
-
setupRetryCommand(pi);
|
|
458
|
-
}
|