@oh-my-pi/pi-coding-agent 5.5.0 → 5.6.70
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 +105 -0
- package/docs/python-repl.md +77 -0
- package/examples/hooks/snake.ts +7 -7
- package/package.json +5 -5
- package/src/bun-imports.d.ts +6 -0
- package/src/cli/args.ts +7 -0
- package/src/cli/setup-cli.ts +231 -0
- package/src/cli.ts +2 -0
- package/src/core/agent-session.ts +118 -15
- package/src/core/bash-executor.ts +3 -84
- package/src/core/compaction/compaction.ts +10 -5
- package/src/core/extensions/index.ts +2 -0
- package/src/core/extensions/loader.ts +13 -1
- package/src/core/extensions/runner.ts +50 -2
- package/src/core/extensions/types.ts +67 -2
- package/src/core/keybindings.ts +51 -1
- package/src/core/prompt-templates.ts +15 -0
- package/src/core/python-executor-display.test.ts +42 -0
- package/src/core/python-executor-lifecycle.test.ts +99 -0
- package/src/core/python-executor-mapping.test.ts +41 -0
- package/src/core/python-executor-per-call.test.ts +49 -0
- package/src/core/python-executor-session.test.ts +103 -0
- package/src/core/python-executor-streaming.test.ts +77 -0
- package/src/core/python-executor-timeout.test.ts +35 -0
- package/src/core/python-executor.lifecycle.test.ts +139 -0
- package/src/core/python-executor.result.test.ts +49 -0
- package/src/core/python-executor.test.ts +180 -0
- package/src/core/python-executor.ts +313 -0
- package/src/core/python-gateway-coordinator.ts +832 -0
- package/src/core/python-kernel-display.test.ts +54 -0
- package/src/core/python-kernel-env.test.ts +138 -0
- package/src/core/python-kernel-session.test.ts +87 -0
- package/src/core/python-kernel-ws.test.ts +104 -0
- package/src/core/python-kernel.lifecycle.test.ts +249 -0
- package/src/core/python-kernel.test.ts +461 -0
- package/src/core/python-kernel.ts +1182 -0
- package/src/core/python-modules.test.ts +102 -0
- package/src/core/python-modules.ts +110 -0
- package/src/core/python-prelude.py +889 -0
- package/src/core/python-prelude.test.ts +140 -0
- package/src/core/python-prelude.ts +3 -0
- package/src/core/sdk.ts +24 -6
- package/src/core/session-manager.ts +174 -82
- package/src/core/settings-manager-python.test.ts +23 -0
- package/src/core/settings-manager.ts +202 -0
- package/src/core/streaming-output.test.ts +26 -0
- package/src/core/streaming-output.ts +100 -0
- package/src/core/system-prompt.python.test.ts +17 -0
- package/src/core/system-prompt.ts +3 -1
- package/src/core/timings.ts +1 -1
- package/src/core/tools/bash.ts +13 -2
- package/src/core/tools/edit-diff.ts +9 -1
- package/src/core/tools/index.test.ts +50 -23
- package/src/core/tools/index.ts +83 -1
- package/src/core/tools/python-execution.test.ts +68 -0
- package/src/core/tools/python-fallback.test.ts +72 -0
- package/src/core/tools/python-renderer.test.ts +36 -0
- package/src/core/tools/python-tool-mode.test.ts +43 -0
- package/src/core/tools/python.test.ts +121 -0
- package/src/core/tools/python.ts +760 -0
- package/src/core/tools/renderers.ts +2 -0
- package/src/core/tools/schema-validation.test.ts +1 -0
- package/src/core/tools/task/executor.ts +146 -3
- package/src/core/tools/task/worker-protocol.ts +32 -2
- package/src/core/tools/task/worker.ts +182 -15
- package/src/index.ts +6 -0
- package/src/main.ts +136 -40
- package/src/modes/interactive/components/custom-editor.ts +16 -31
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +5 -16
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -13
- package/src/modes/interactive/components/history-search.ts +5 -8
- package/src/modes/interactive/components/hook-editor.ts +3 -4
- package/src/modes/interactive/components/hook-input.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +5 -15
- package/src/modes/interactive/components/index.ts +1 -0
- package/src/modes/interactive/components/keybinding-hints.ts +66 -0
- package/src/modes/interactive/components/model-selector.ts +53 -66
- package/src/modes/interactive/components/oauth-selector.ts +5 -5
- package/src/modes/interactive/components/session-selector.ts +29 -23
- package/src/modes/interactive/components/settings-defs.ts +404 -196
- package/src/modes/interactive/components/settings-selector.ts +14 -10
- package/src/modes/interactive/components/status-line-segment-editor.ts +7 -7
- package/src/modes/interactive/components/tool-execution.ts +8 -0
- package/src/modes/interactive/components/tree-selector.ts +29 -23
- package/src/modes/interactive/components/user-message-selector.ts +6 -17
- package/src/modes/interactive/controllers/command-controller.ts +86 -37
- package/src/modes/interactive/controllers/event-controller.ts +8 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +51 -0
- package/src/modes/interactive/controllers/input-controller.ts +42 -6
- package/src/modes/interactive/interactive-mode.ts +56 -30
- package/src/modes/interactive/theme/theme-schema.json +2 -2
- package/src/modes/interactive/types.ts +6 -1
- package/src/modes/interactive/utils/ui-helpers.ts +2 -1
- package/src/modes/print-mode.ts +23 -0
- package/src/modes/rpc/rpc-mode.ts +21 -0
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/system/system-prompt.md +32 -1
- package/src/prompts/tools/python.md +91 -0
|
@@ -59,16 +59,28 @@ export class InputController {
|
|
|
59
59
|
this.ctx.ui.onDebug = () => this.ctx.handleDebugCommand();
|
|
60
60
|
this.ctx.editor.onCtrlL = () => this.ctx.showModelSelector();
|
|
61
61
|
this.ctx.editor.onCtrlR = () => this.ctx.showHistorySearch();
|
|
62
|
-
this.ctx.editor.onCtrlO = () => this.toggleToolOutputExpansion();
|
|
63
62
|
this.ctx.editor.onCtrlT = () => this.ctx.toggleTodoExpansion();
|
|
64
63
|
this.ctx.editor.onCtrlG = () => this.openExternalEditor();
|
|
65
64
|
this.ctx.editor.onQuestionMark = () => this.ctx.handleHotkeysCommand();
|
|
66
65
|
this.ctx.editor.onCtrlV = () => this.handleImagePaste();
|
|
67
|
-
this.ctx.editor.onAltUp = () => this.handleDequeue();
|
|
68
66
|
|
|
69
67
|
// Wire up extension shortcuts
|
|
70
68
|
this.registerExtensionShortcuts();
|
|
71
69
|
|
|
70
|
+
const expandToolsKeys = this.ctx.keybindings.getKeys("expandTools");
|
|
71
|
+
this.ctx.editor.onCtrlO = expandToolsKeys.includes("ctrl+o") ? () => this.toggleToolOutputExpansion() : undefined;
|
|
72
|
+
for (const key of expandToolsKeys) {
|
|
73
|
+
if (key === "ctrl+o") continue;
|
|
74
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.toggleToolOutputExpansion());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const dequeueKeys = this.ctx.keybindings.getKeys("dequeue");
|
|
78
|
+
this.ctx.editor.onAltUp = dequeueKeys.includes("alt+up") ? () => this.handleDequeue() : undefined;
|
|
79
|
+
for (const key of dequeueKeys) {
|
|
80
|
+
if (key === "alt+up") continue;
|
|
81
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.handleDequeue());
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
this.ctx.editor.onChange = (text: string) => {
|
|
73
85
|
const wasBashMode = this.ctx.isBashMode;
|
|
74
86
|
this.ctx.isBashMode = text.trimStart().startsWith("!");
|
|
@@ -122,13 +134,33 @@ export class InputController {
|
|
|
122
134
|
|
|
123
135
|
if (!text) return;
|
|
124
136
|
|
|
137
|
+
const runner = this.ctx.session.extensionRunner;
|
|
138
|
+
let inputImages = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
139
|
+
|
|
140
|
+
if (runner?.hasHandlers("input")) {
|
|
141
|
+
const result = await runner.emitInput(text, inputImages, "interactive");
|
|
142
|
+
if (result?.handled) {
|
|
143
|
+
this.ctx.editor.setText("");
|
|
144
|
+
this.ctx.pendingImages = [];
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (result?.text !== undefined) {
|
|
148
|
+
text = result.text.trim();
|
|
149
|
+
}
|
|
150
|
+
if (result?.images !== undefined) {
|
|
151
|
+
inputImages = result.images;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!text) return;
|
|
156
|
+
|
|
125
157
|
// Handle slash commands
|
|
126
158
|
if (text === "/settings") {
|
|
127
159
|
this.ctx.showSettingsSelector();
|
|
128
160
|
this.ctx.editor.setText("");
|
|
129
161
|
return;
|
|
130
162
|
}
|
|
131
|
-
if (text === "/model") {
|
|
163
|
+
if (text === "/model" || text === "/models") {
|
|
132
164
|
this.ctx.showModelSelector();
|
|
133
165
|
this.ctx.editor.setText("");
|
|
134
166
|
return;
|
|
@@ -246,7 +278,11 @@ export class InputController {
|
|
|
246
278
|
try {
|
|
247
279
|
const content = fs.readFileSync(skillPath, "utf-8");
|
|
248
280
|
const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
249
|
-
const
|
|
281
|
+
const metaLines = [`Skill: ${skillPath}`];
|
|
282
|
+
if (args) {
|
|
283
|
+
metaLines.push(`User: ${args}`);
|
|
284
|
+
}
|
|
285
|
+
const message = `${body}\n\n---\n\n${metaLines.join("\n")}`;
|
|
250
286
|
await this.ctx.session.prompt(message);
|
|
251
287
|
} catch (err) {
|
|
252
288
|
this.ctx.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -288,7 +324,7 @@ export class InputController {
|
|
|
288
324
|
if (this.ctx.session.isStreaming) {
|
|
289
325
|
this.ctx.editor.addToHistory(text);
|
|
290
326
|
this.ctx.editor.setText("");
|
|
291
|
-
const images =
|
|
327
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
292
328
|
this.ctx.pendingImages = [];
|
|
293
329
|
await this.ctx.session.prompt(text, { streamingBehavior: "steer", images });
|
|
294
330
|
this.ctx.updatePendingMessagesDisplay();
|
|
@@ -317,7 +353,7 @@ export class InputController {
|
|
|
317
353
|
|
|
318
354
|
if (this.ctx.onInputCallback) {
|
|
319
355
|
// Include any pending images from clipboard paste
|
|
320
|
-
const images =
|
|
356
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
321
357
|
this.ctx.pendingImages = [];
|
|
322
358
|
this.ctx.onInputCallback({ text, images });
|
|
323
359
|
}
|
|
@@ -19,8 +19,9 @@ import {
|
|
|
19
19
|
import chalk from "chalk";
|
|
20
20
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
21
21
|
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
22
|
+
import type { CompactOptions } from "../../core/extensions/types";
|
|
22
23
|
import { HistoryStorage } from "../../core/history-storage";
|
|
23
|
-
import
|
|
24
|
+
import { KeybindingsManager } from "../../core/keybindings";
|
|
24
25
|
import { logger } from "../../core/logger";
|
|
25
26
|
import type { SessionContext, SessionManager } from "../../core/session-manager";
|
|
26
27
|
import { getRecentSessions } from "../../core/session-manager";
|
|
@@ -71,6 +72,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
71
72
|
public session: AgentSession;
|
|
72
73
|
public sessionManager: SessionManager;
|
|
73
74
|
public settingsManager: SettingsManager;
|
|
75
|
+
public keybindings: KeybindingsManager;
|
|
74
76
|
public agent: AgentSession["agent"];
|
|
75
77
|
public voiceSupervisor: VoiceSupervisor;
|
|
76
78
|
public historyStorage?: HistoryStorage;
|
|
@@ -126,8 +128,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
126
128
|
private cleanupUnsubscribe?: () => void;
|
|
127
129
|
private readonly version: string;
|
|
128
130
|
private readonly changelogMarkdown: string | undefined;
|
|
129
|
-
|
|
131
|
+
public readonly lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined =
|
|
130
132
|
undefined;
|
|
133
|
+
public mcpManager?: import("../../core/mcp/index").MCPManager;
|
|
131
134
|
private readonly toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
132
135
|
|
|
133
136
|
private readonly commandController: CommandController;
|
|
@@ -144,23 +147,25 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
144
147
|
changelogMarkdown: string | undefined = undefined,
|
|
145
148
|
setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
|
|
146
149
|
lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined = undefined,
|
|
150
|
+
mcpManager?: import("../../core/mcp/index").MCPManager,
|
|
147
151
|
) {
|
|
148
152
|
this.session = session;
|
|
149
153
|
this.sessionManager = session.sessionManager;
|
|
150
154
|
this.settingsManager = session.settingsManager;
|
|
155
|
+
this.keybindings = KeybindingsManager.inMemory();
|
|
151
156
|
this.agent = session.agent;
|
|
152
157
|
this.version = version;
|
|
153
158
|
this.changelogMarkdown = changelogMarkdown;
|
|
154
159
|
this.toolUiContextSetter = setToolUIContext;
|
|
155
160
|
this.lspServers = lspServers;
|
|
161
|
+
this.mcpManager = mcpManager;
|
|
156
162
|
|
|
157
|
-
this.ui = new TUI(new ProcessTerminal());
|
|
163
|
+
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
|
158
164
|
this.chatContainer = new Container();
|
|
159
165
|
this.pendingMessagesContainer = new Container();
|
|
160
166
|
this.statusContainer = new Container();
|
|
161
167
|
this.todoContainer = new Container();
|
|
162
168
|
this.editor = new CustomEditor(getEditorTheme());
|
|
163
|
-
this.editor.setUseTerminalCursor(true);
|
|
164
169
|
this.editor.onAutocompleteCancel = () => {
|
|
165
170
|
this.ui.requestRender(true);
|
|
166
171
|
};
|
|
@@ -226,6 +231,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
226
231
|
const hookCommands: SlashCommand[] = (this.session.extensionRunner?.getRegisteredCommands() ?? []).map((cmd) => ({
|
|
227
232
|
name: cmd.name,
|
|
228
233
|
description: cmd.description ?? "(hook command)",
|
|
234
|
+
getArgumentCompletions: cmd.getArgumentCompletions,
|
|
229
235
|
}));
|
|
230
236
|
|
|
231
237
|
// Convert custom commands (TypeScript) to SlashCommand format
|
|
@@ -259,6 +265,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
259
265
|
async init(): Promise<void> {
|
|
260
266
|
if (this.isInitialized) return;
|
|
261
267
|
|
|
268
|
+
this.keybindings = await KeybindingsManager.create();
|
|
269
|
+
|
|
262
270
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
263
271
|
this.cleanupUnsubscribe = registerAsyncCleanup(() => this.sessionManager.flush());
|
|
264
272
|
|
|
@@ -295,8 +303,34 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
295
303
|
fileTypes: s.fileTypes,
|
|
296
304
|
})) ?? [];
|
|
297
305
|
|
|
298
|
-
|
|
299
|
-
|
|
306
|
+
const startupQuiet = this.settingsManager.getStartupQuiet();
|
|
307
|
+
|
|
308
|
+
if (!startupQuiet) {
|
|
309
|
+
// Add welcome header
|
|
310
|
+
const welcome = new WelcomeComponent(this.version, modelName, providerName, recentSessions, lspServerInfo);
|
|
311
|
+
|
|
312
|
+
// Setup UI layout
|
|
313
|
+
this.ui.addChild(new Spacer(1));
|
|
314
|
+
this.ui.addChild(welcome);
|
|
315
|
+
this.ui.addChild(new Spacer(1));
|
|
316
|
+
|
|
317
|
+
// Add changelog if provided
|
|
318
|
+
if (this.changelogMarkdown) {
|
|
319
|
+
this.ui.addChild(new DynamicBorder());
|
|
320
|
+
if (this.settingsManager.getCollapseChangelog()) {
|
|
321
|
+
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
|
322
|
+
const latestVersion = versionMatch ? versionMatch[1] : this.version;
|
|
323
|
+
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
324
|
+
this.ui.addChild(new Text(condensedText, 1, 0));
|
|
325
|
+
} else {
|
|
326
|
+
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
327
|
+
this.ui.addChild(new Spacer(1));
|
|
328
|
+
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
|
|
329
|
+
this.ui.addChild(new Spacer(1));
|
|
330
|
+
}
|
|
331
|
+
this.ui.addChild(new DynamicBorder());
|
|
332
|
+
}
|
|
333
|
+
}
|
|
300
334
|
|
|
301
335
|
// Set terminal title if session already has one (resumed session)
|
|
302
336
|
const existingTitle = this.sessionManager.getSessionTitle();
|
|
@@ -304,28 +338,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
304
338
|
setTerminalTitle(`pi: ${existingTitle}`);
|
|
305
339
|
}
|
|
306
340
|
|
|
307
|
-
// Setup UI layout
|
|
308
|
-
this.ui.addChild(new Spacer(1));
|
|
309
|
-
this.ui.addChild(welcome);
|
|
310
|
-
this.ui.addChild(new Spacer(1));
|
|
311
|
-
|
|
312
|
-
// Add changelog if provided
|
|
313
|
-
if (this.changelogMarkdown) {
|
|
314
|
-
this.ui.addChild(new DynamicBorder());
|
|
315
|
-
if (this.settingsManager.getCollapseChangelog()) {
|
|
316
|
-
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
|
317
|
-
const latestVersion = versionMatch ? versionMatch[1] : this.version;
|
|
318
|
-
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
319
|
-
this.ui.addChild(new Text(condensedText, 1, 0));
|
|
320
|
-
} else {
|
|
321
|
-
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
322
|
-
this.ui.addChild(new Spacer(1));
|
|
323
|
-
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
|
|
324
|
-
this.ui.addChild(new Spacer(1));
|
|
325
|
-
}
|
|
326
|
-
this.ui.addChild(new DynamicBorder());
|
|
327
|
-
}
|
|
328
|
-
|
|
329
341
|
this.ui.addChild(this.chatContainer);
|
|
330
342
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
331
343
|
this.ui.addChild(this.statusContainer);
|
|
@@ -344,6 +356,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
344
356
|
// Start the UI
|
|
345
357
|
this.ui.start();
|
|
346
358
|
this.isInitialized = true;
|
|
359
|
+
this.ui.requestRender(true);
|
|
347
360
|
|
|
348
361
|
// Set initial terminal title (will be updated when session title is generated)
|
|
349
362
|
this.ui.terminal.setTitle("π");
|
|
@@ -369,6 +382,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
369
382
|
|
|
370
383
|
// Initial top border update
|
|
371
384
|
this.updateEditorTopBorder();
|
|
385
|
+
|
|
386
|
+
if (!startupQuiet) {
|
|
387
|
+
const templateNames = this.session.promptTemplates.map((template) => template.name).sort();
|
|
388
|
+
if (templateNames.length > 0) {
|
|
389
|
+
const preview = templateNames.slice(0, 3).join(", ");
|
|
390
|
+
const suffix = templateNames.length > 3 ? ` +${templateNames.length - 3} more` : "";
|
|
391
|
+
this.showStatus(`Loaded prompt templates: ${preview}${suffix}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
372
394
|
}
|
|
373
395
|
|
|
374
396
|
async getUserInput(): Promise<{ text: string; images?: ImageContent[] }> {
|
|
@@ -510,6 +532,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
510
532
|
// Emit shutdown event to hooks
|
|
511
533
|
await this.session.emitCustomToolSessionEvent("shutdown");
|
|
512
534
|
|
|
535
|
+
if (this.isInitialized) {
|
|
536
|
+
await this.ui.waitForRender();
|
|
537
|
+
}
|
|
538
|
+
|
|
513
539
|
this.stop();
|
|
514
540
|
process.exit(0);
|
|
515
541
|
}
|
|
@@ -654,8 +680,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
654
680
|
return this.commandController.handleCompactCommand(customInstructions);
|
|
655
681
|
}
|
|
656
682
|
|
|
657
|
-
executeCompaction(
|
|
658
|
-
return this.commandController.executeCompaction(
|
|
683
|
+
executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean): Promise<void> {
|
|
684
|
+
return this.commandController.executeCompaction(customInstructionsOrOptions, isAuto);
|
|
659
685
|
}
|
|
660
686
|
|
|
661
687
|
openInBrowser(urlOrPath: string): void {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"title": "
|
|
4
|
-
"description": "Theme schema for OMP coding agent",
|
|
3
|
+
"title": "OMP Coding Agent Theme",
|
|
4
|
+
"description": "Theme schema for the OMP coding agent",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["name", "colors"],
|
|
7
7
|
"properties": {
|
|
@@ -3,8 +3,10 @@ import type { AssistantMessage, ImageContent, Message } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import type { Component, Container, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
5
5
|
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
6
|
+
import type { CompactOptions } from "../../core/extensions/types";
|
|
6
7
|
import type { HistoryStorage } from "../../core/history-storage";
|
|
7
8
|
import type { KeybindingsManager } from "../../core/keybindings";
|
|
9
|
+
import type { MCPManager } from "../../core/mcp/index";
|
|
8
10
|
import type { SessionContext, SessionManager } from "../../core/session-manager";
|
|
9
11
|
import type { SettingsManager } from "../../core/settings-manager";
|
|
10
12
|
import type { VoiceSupervisor } from "../../core/voice-supervisor";
|
|
@@ -45,9 +47,12 @@ export interface InteractiveModeContext {
|
|
|
45
47
|
session: AgentSession;
|
|
46
48
|
sessionManager: SessionManager;
|
|
47
49
|
settingsManager: SettingsManager;
|
|
50
|
+
keybindings: KeybindingsManager;
|
|
48
51
|
agent: AgentSession["agent"];
|
|
49
52
|
voiceSupervisor: VoiceSupervisor;
|
|
50
53
|
historyStorage?: HistoryStorage;
|
|
54
|
+
mcpManager?: MCPManager;
|
|
55
|
+
lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>;
|
|
51
56
|
|
|
52
57
|
// State
|
|
53
58
|
isInitialized: boolean;
|
|
@@ -140,7 +145,7 @@ export interface InteractiveModeContext {
|
|
|
140
145
|
handleArminSaysHi(): void;
|
|
141
146
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
142
147
|
handleCompactCommand(customInstructions?: string): Promise<void>;
|
|
143
|
-
executeCompaction(
|
|
148
|
+
executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean): Promise<void>;
|
|
144
149
|
openInBrowser(urlOrPath: string): void;
|
|
145
150
|
|
|
146
151
|
// Selector handling
|
|
@@ -340,7 +340,8 @@ export class UiHelpers {
|
|
|
340
340
|
const queuedText = theme.fg("dim", `${entry.label}: ${entry.message}`);
|
|
341
341
|
this.ctx.pendingMessagesContainer.addChild(new TruncatedText(queuedText, 1, 0));
|
|
342
342
|
}
|
|
343
|
-
const
|
|
343
|
+
const dequeueKey = this.ctx.keybindings.getDisplayString("dequeue") || "Alt+Up";
|
|
344
|
+
const hintText = theme.fg("dim", `${theme.tree.hook} ${dequeueKey} to edit`);
|
|
344
345
|
this.ctx.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
|
345
346
|
}
|
|
346
347
|
}
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -58,6 +58,9 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
58
58
|
appendEntry: (customType, data) => {
|
|
59
59
|
session.sessionManager.appendCustomEntry(customType, data);
|
|
60
60
|
},
|
|
61
|
+
setLabel: (targetId, label) => {
|
|
62
|
+
session.sessionManager.appendLabelChange(targetId, label);
|
|
63
|
+
},
|
|
61
64
|
getActiveTools: () => session.getActiveToolNames(),
|
|
62
65
|
getAllTools: () => session.getAllToolNames(),
|
|
63
66
|
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
@@ -77,9 +80,19 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
77
80
|
abort: () => session.abort(),
|
|
78
81
|
hasPendingMessages: () => session.queuedMessageCount > 0,
|
|
79
82
|
shutdown: () => {},
|
|
83
|
+
getContextUsage: () => session.getContextUsage(),
|
|
84
|
+
compact: async (instructionsOrOptions) => {
|
|
85
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
86
|
+
const options =
|
|
87
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
88
|
+
? instructionsOrOptions
|
|
89
|
+
: undefined;
|
|
90
|
+
await session.compact(instructions, options);
|
|
91
|
+
},
|
|
80
92
|
},
|
|
81
93
|
// ExtensionCommandContextActions - commands invokable via prompt("/command")
|
|
82
94
|
{
|
|
95
|
+
getContextUsage: () => session.getContextUsage(),
|
|
83
96
|
waitForIdle: () => session.agent.waitForIdle(),
|
|
84
97
|
newSession: async (options) => {
|
|
85
98
|
const success = await session.newSession({ parentSession: options?.parentSession });
|
|
@@ -96,6 +109,14 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
96
109
|
const result = await session.navigateTree(targetId, { summarize: options?.summarize });
|
|
97
110
|
return { cancelled: result.cancelled };
|
|
98
111
|
},
|
|
112
|
+
compact: async (instructionsOrOptions) => {
|
|
113
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
114
|
+
const options =
|
|
115
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
116
|
+
? instructionsOrOptions
|
|
117
|
+
: undefined;
|
|
118
|
+
await session.compact(instructions, options);
|
|
119
|
+
},
|
|
99
120
|
},
|
|
100
121
|
// No UI context
|
|
101
122
|
);
|
|
@@ -157,4 +178,6 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
157
178
|
else resolve();
|
|
158
179
|
});
|
|
159
180
|
});
|
|
181
|
+
|
|
182
|
+
await session.dispose();
|
|
160
183
|
}
|
|
@@ -289,6 +289,9 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
289
289
|
appendEntry: (customType, data) => {
|
|
290
290
|
session.sessionManager.appendCustomEntry(customType, data);
|
|
291
291
|
},
|
|
292
|
+
setLabel: (targetId, label) => {
|
|
293
|
+
session.sessionManager.appendLabelChange(targetId, label);
|
|
294
|
+
},
|
|
292
295
|
getActiveTools: () => session.getActiveToolNames(),
|
|
293
296
|
getAllTools: () => session.getAllToolNames(),
|
|
294
297
|
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
@@ -310,9 +313,19 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
310
313
|
shutdown: () => {
|
|
311
314
|
shutdownState.requested = true;
|
|
312
315
|
},
|
|
316
|
+
getContextUsage: () => session.getContextUsage(),
|
|
317
|
+
compact: async (instructionsOrOptions) => {
|
|
318
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
319
|
+
const options =
|
|
320
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
321
|
+
? instructionsOrOptions
|
|
322
|
+
: undefined;
|
|
323
|
+
await session.compact(instructions, options);
|
|
324
|
+
},
|
|
313
325
|
},
|
|
314
326
|
// ExtensionCommandContextActions - commands invokable via prompt("/command")
|
|
315
327
|
{
|
|
328
|
+
getContextUsage: () => session.getContextUsage(),
|
|
316
329
|
waitForIdle: () => session.agent.waitForIdle(),
|
|
317
330
|
newSession: async (options) => {
|
|
318
331
|
const success = await session.newSession({ parentSession: options?.parentSession });
|
|
@@ -330,6 +343,14 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
330
343
|
const result = await session.navigateTree(targetId, { summarize: options?.summarize });
|
|
331
344
|
return { cancelled: result.cancelled };
|
|
332
345
|
},
|
|
346
|
+
compact: async (instructionsOrOptions) => {
|
|
347
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
348
|
+
const options =
|
|
349
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
350
|
+
? instructionsOrOptions
|
|
351
|
+
: undefined;
|
|
352
|
+
await session.compact(instructions, options);
|
|
353
|
+
},
|
|
333
354
|
},
|
|
334
355
|
createExtensionUIContext(),
|
|
335
356
|
);
|
|
@@ -43,7 +43,7 @@ You are a senior engineer reviewing a proposed code change. Your goal: identify
|
|
|
43
43
|
4. Call `report_finding` for each issue
|
|
44
44
|
5. Call `complete` with your verdict — **review is incomplete until `complete` is called**
|
|
45
45
|
|
|
46
|
-
Bash is read-only: `git diff`, `git log`, `git show`, `gh pr diff`. No file modifications or builds.
|
|
46
|
+
Bash is read-only here: `git diff`, `git log`, `git show`, `gh pr diff`. No file modifications or builds.
|
|
47
47
|
|
|
48
48
|
# What to Flag
|
|
49
49
|
|
|
@@ -67,6 +67,7 @@ This matters. Get it right.
|
|
|
67
67
|
|
|
68
68
|
Every tool is a choice. The wrong choice is friction. The right choice is invisible.
|
|
69
69
|
|
|
70
|
+
{{#has tools "bash"}}
|
|
70
71
|
### What bash IS for
|
|
71
72
|
File and system operations:
|
|
72
73
|
- `mv`, `cp`, `rm`, `ln -s` — moving, copying, deleting, symlinking
|
|
@@ -95,6 +96,35 @@ Specialized tools exist. Use them.
|
|
|
95
96
|
{{#has tools "edit"}}- Content-addressed edits: `edit` finds text. Use bash for position/pattern (append, line N, regex).{{/has}}
|
|
96
97
|
{{#has tools "git"}}- Git operations: `git` tool has guards. Bash git has none.{{/has}}
|
|
97
98
|
|
|
99
|
+
{{/has}}
|
|
100
|
+
|
|
101
|
+
{{#has tools "python"}}
|
|
102
|
+
### What python IS for
|
|
103
|
+
Python is your scripting language. Bash is for build tools and system commands only.
|
|
104
|
+
|
|
105
|
+
**Use Python for:**
|
|
106
|
+
- Loops, conditionals, any multi-step logic
|
|
107
|
+
- Text processing (sorting, filtering, column extraction, regex)
|
|
108
|
+
- File operations (copy, move, concat, batch transforms)
|
|
109
|
+
- Displaying content to the user
|
|
110
|
+
- Anything you'd write a bash script for
|
|
111
|
+
|
|
112
|
+
**Use bash only for:**
|
|
113
|
+
- Build commands: `cargo`, `npm`, `make`, `docker`
|
|
114
|
+
- Git operations (when git tool unavailable)
|
|
115
|
+
- System commands with no Python equivalent
|
|
116
|
+
|
|
117
|
+
The prelude provides shell-like helpers: `cat()`, `sed()`, `rsed()`, `find()`, `grep()`, `batch()`.
|
|
118
|
+
Do not write bash loops, sed pipelines, or awk scripts. Write Python.
|
|
119
|
+
|
|
120
|
+
### Python for user-facing output
|
|
121
|
+
When the user asks you to display, concatenate, merge, or transform content:
|
|
122
|
+
→ Python. One operation. Clean output.
|
|
123
|
+
|
|
124
|
+
Do not read files individually just to print them back. That's mechanical and wasteful.
|
|
125
|
+
Read/grep are for YOUR reconnaissance. Python is for THE USER's request.
|
|
126
|
+
{{/has}}
|
|
127
|
+
|
|
98
128
|
### Hierarchy of trust
|
|
99
129
|
The most constrained tool is the most trustworthy.
|
|
100
130
|
|
|
@@ -104,7 +134,8 @@ The most constrained tool is the most trustworthy.
|
|
|
104
134
|
{{#has tools "read"}}4. **read** — content truth{{/has}}
|
|
105
135
|
{{#has tools "edit"}}5. **edit** — surgical change{{/has}}
|
|
106
136
|
{{#has tools "git"}}6. **git** — versioned change with safety{{/has}}
|
|
107
|
-
7. **bash** — everything else ({{#unless (includes tools "git")}}git, {{/unless}}npm, docker, make, cargo)
|
|
137
|
+
{{#has tools "bash"}}7. **bash** — everything else ({{#unless (includes tools "git")}}git, {{/unless}}npm, docker, make, cargo){{/has}}
|
|
138
|
+
{{#unless (includes tools "bash")}}{{#has tools "python"}}7. **python** — stateful scripting and REPL work{{/has}}{{/unless}}
|
|
108
139
|
|
|
109
140
|
{{#has tools "lsp"}}
|
|
110
141
|
### LSP knows what grep guesses
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Executes Python code in an IPython kernel (session or per-call) with optional timeout.
|
|
2
|
+
|
|
3
|
+
## When to use Python
|
|
4
|
+
|
|
5
|
+
**Use Python for user-facing operations:**
|
|
6
|
+
- Displaying, concatenating, or merging files → `cat(*paths)`
|
|
7
|
+
- Batch transformations across files → `batch(paths, fn)`, `rsed()`
|
|
8
|
+
- Formatted output, tables, summaries
|
|
9
|
+
- Any loop, conditional, or multi-step logic
|
|
10
|
+
- Anything you'd write a bash script for
|
|
11
|
+
|
|
12
|
+
**Use specialized tools for YOUR reconnaissance:**
|
|
13
|
+
- Reading to understand code → Read tool
|
|
14
|
+
- Searching to locate something → Grep tool
|
|
15
|
+
- Finding files to identify targets → Find tool
|
|
16
|
+
|
|
17
|
+
The distinction: Read/Grep/Find gather info for *your* decisions. Python executes *the user's* request.
|
|
18
|
+
|
|
19
|
+
**Prefer Python over bash for:**
|
|
20
|
+
- Loops and iteration → Python for-loops, not bash for/while
|
|
21
|
+
- Text processing → `sed()`, `cols()`, `sort_lines()`, not sed/awk/cut
|
|
22
|
+
- File operations → prelude helpers, not mv/cp/rm commands
|
|
23
|
+
- Conditionals → Python if/else, not bash [[ ]]
|
|
24
|
+
|
|
25
|
+
**Shell commands:** Use `sh()` or `run()`, never raw `subprocess`:
|
|
26
|
+
```python
|
|
27
|
+
# Good
|
|
28
|
+
sh("bun run check")
|
|
29
|
+
run("cargo build --release")
|
|
30
|
+
|
|
31
|
+
# Bad - never use subprocess directly
|
|
32
|
+
import subprocess
|
|
33
|
+
subprocess.run(["bun", "run", "check"], ...)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Prelude helpers
|
|
37
|
+
|
|
38
|
+
All helpers auto-print results and return values for chaining.
|
|
39
|
+
|
|
40
|
+
{{#if categories.length}}
|
|
41
|
+
{{#each categories}}
|
|
42
|
+
### {{name}}
|
|
43
|
+
```
|
|
44
|
+
{{#each functions}}
|
|
45
|
+
{{name}}{{signature}}
|
|
46
|
+
{{docstring}}
|
|
47
|
+
{{/each}}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
{{/each}}
|
|
51
|
+
{{else}}
|
|
52
|
+
(Documentation unavailable — Python kernel failed to start)
|
|
53
|
+
{{/if}}
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# Concatenate all markdown files in docs/
|
|
59
|
+
cat(*find("*.md", "docs"))
|
|
60
|
+
|
|
61
|
+
# Mass rename: foo -> bar across all .py files
|
|
62
|
+
rsed(r'\bfoo\b', 'bar', glob_pattern="*.py")
|
|
63
|
+
|
|
64
|
+
# Process files in batch
|
|
65
|
+
batch(find("*.json"), lambda p: json.loads(p.read_text()))
|
|
66
|
+
|
|
67
|
+
# Sort and deduplicate lines
|
|
68
|
+
sort_lines(read("data.txt"), unique=True)
|
|
69
|
+
|
|
70
|
+
# Extract columns 0 and 2 from TSV
|
|
71
|
+
cols(read("data.tsv"), 0, 2, sep="\t")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Notes
|
|
75
|
+
|
|
76
|
+
- Code executes as IPython cells; users see the full cell output (including rendered figures, tables, etc.)
|
|
77
|
+
- Kernel persists for the session by default; per-call mode uses a fresh kernel each call. Use `reset: true` to clear state when session mode is active
|
|
78
|
+
- Use `workdir` parameter instead of `os.chdir()` in tool call
|
|
79
|
+
- Use `plt.show()` to display figures
|
|
80
|
+
- Use `display()` from IPython.display for rich output (HTML, Markdown, images, etc.)
|
|
81
|
+
- Output streams in real time, truncated after 50KB
|
|
82
|
+
|
|
83
|
+
## Rich output rendering
|
|
84
|
+
|
|
85
|
+
The user sees output like a Jupyter notebook—rich displays are fully rendered:
|
|
86
|
+
- `display(JSON(data))` → interactive JSON tree
|
|
87
|
+
- `display(HTML(...))` → rendered HTML
|
|
88
|
+
- `display(Markdown(...))` → formatted markdown
|
|
89
|
+
- `plt.show()` → inline figures
|
|
90
|
+
|
|
91
|
+
**You will see object repr** (e.g., `<IPython.core.display.JSON object>`) **but the user sees the rendered output.** Trust that `display()` calls work correctly—do not assume the user sees only the repr.
|