@oh-my-pi/pi-coding-agent 11.0.3 → 11.2.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 +199 -49
- package/README.md +1 -1
- package/docs/config-usage.md +3 -4
- package/docs/sdk.md +6 -5
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +1 -1
- package/package.json +19 -11
- package/src/cli/args.ts +11 -94
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/oclif-help.ts +26 -0
- package/src/cli/web-search-cli.ts +148 -0
- package/src/cli.ts +8 -2
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/index/index.ts +136 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +39 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +50 -0
- package/src/commit/agentic/index.ts +3 -2
- package/src/commit/agentic/tools/analyze-file.ts +1 -3
- package/src/commit/git/errors.ts +4 -6
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +1 -3
- package/src/config/model-registry.ts +89 -162
- package/src/config/settings-schema.ts +10 -0
- package/src/config.ts +202 -132
- package/src/exa/mcp-client.ts +8 -41
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/extensions/loader.ts +7 -10
- package/src/extensibility/extensions/runner.ts +5 -15
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/runner.ts +6 -9
- package/src/index.ts +0 -1
- package/src/ipy/kernel.ts +10 -22
- package/src/lsp/clients/biome-client.ts +4 -7
- package/src/lsp/clients/lsp-linter-client.ts +4 -6
- package/src/lsp/index.ts +5 -4
- package/src/lsp/utils.ts +18 -0
- package/src/main.ts +86 -181
- package/src/mcp/json-rpc.ts +2 -2
- package/src/mcp/transports/http.ts +12 -49
- package/src/modes/components/armin.ts +1 -3
- package/src/modes/components/assistant-message.ts +4 -4
- package/src/modes/components/bash-execution.ts +5 -3
- package/src/modes/components/branch-summary-message.ts +1 -3
- package/src/modes/components/compaction-summary-message.ts +1 -3
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/extensions/extension-dashboard.ts +10 -16
- package/src/modes/components/extensions/extension-list.ts +5 -5
- package/src/modes/components/footer.ts +1 -4
- package/src/modes/components/hook-editor.ts +7 -32
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/plugin-settings.ts +16 -20
- package/src/modes/components/python-execution.ts +5 -5
- package/src/modes/components/session-selector.ts +6 -7
- package/src/modes/components/settings-defs.ts +49 -40
- package/src/modes/components/settings-selector.ts +8 -17
- package/src/modes/components/skill-message.ts +1 -3
- package/src/modes/components/status-line-segment-editor.ts +1 -3
- package/src/modes/components/status-line.ts +1 -3
- package/src/modes/components/todo-reminder.ts +5 -7
- package/src/modes/components/tree-selector.ts +10 -12
- package/src/modes/components/ttsr-notification.ts +1 -3
- package/src/modes/components/user-message-selector.ts +2 -4
- package/src/modes/components/welcome.ts +6 -18
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +7 -34
- package/src/modes/controllers/selector-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +27 -1
- package/src/modes/rpc/rpc-client.ts +2 -5
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/theme/theme.ts +2 -6
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -1
- package/src/patch/index.ts +1 -4
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/frontmatter.md +2 -1
- package/src/prompts/agents/init.md +1 -0
- package/src/prompts/agents/plan.md +1 -0
- package/src/prompts/agents/reviewer.md +1 -0
- package/src/prompts/system/subagent-submit-reminder.md +2 -0
- package/src/prompts/system/subagent-system-prompt.md +2 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/system-prompt.md +5 -3
- package/src/prompts/system/web-search.md +6 -4
- package/src/prompts/tools/task.md +216 -163
- package/src/sdk.ts +11 -110
- package/src/session/agent-session.ts +117 -83
- package/src/session/auth-storage.ts +10 -51
- package/src/session/messages.ts +17 -3
- package/src/session/session-manager.ts +30 -30
- package/src/session/streaming-output.ts +1 -1
- package/src/ssh/ssh-executor.ts +6 -3
- package/src/task/agents.ts +2 -0
- package/src/task/discovery.ts +1 -1
- package/src/task/executor.ts +5 -10
- package/src/task/index.ts +43 -23
- package/src/task/render.ts +67 -64
- package/src/task/template.ts +17 -34
- package/src/task/types.ts +49 -22
- package/src/tools/ask.ts +1 -3
- package/src/tools/bash.ts +1 -4
- package/src/tools/browser.ts +5 -7
- package/src/tools/exit-plan-mode.ts +1 -4
- package/src/tools/fetch.ts +1 -3
- package/src/tools/find.ts +4 -3
- package/src/tools/gemini-image.ts +24 -55
- package/src/tools/grep.ts +4 -4
- package/src/tools/index.ts +12 -14
- package/src/tools/notebook.ts +1 -5
- package/src/tools/python.ts +4 -3
- package/src/tools/read.ts +2 -4
- package/src/tools/render-utils.ts +23 -0
- package/src/tools/ssh.ts +8 -12
- package/src/tools/todo-write.ts +1 -4
- package/src/tools/tool-errors.ts +1 -4
- package/src/tools/write.ts +1 -3
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-mentions.ts +39 -1
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +4 -4
- package/src/web/search/auth.ts +3 -33
- package/src/web/search/index.ts +73 -139
- package/src/web/search/provider.ts +58 -0
- package/src/web/search/providers/anthropic.ts +53 -14
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/codex.ts +38 -16
- package/src/web/search/providers/exa.ts +30 -6
- package/src/web/search/providers/gemini.ts +56 -20
- package/src/web/search/providers/jina.ts +28 -5
- package/src/web/search/providers/perplexity.ts +103 -36
- package/src/web/search/render.ts +84 -74
- package/src/web/search/types.ts +285 -59
- package/src/migrations.ts +0 -175
- package/src/session/storage-migration.ts +0 -173
|
@@ -5,14 +5,12 @@ import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
|
5
5
|
import type { CustomMessage, SkillPromptDetails } from "../../session/messages";
|
|
6
6
|
|
|
7
7
|
export class SkillMessageComponent extends Container {
|
|
8
|
-
private message: CustomMessage<SkillPromptDetails>;
|
|
9
8
|
private box: Box;
|
|
10
9
|
private contentComponent?: Component;
|
|
11
10
|
private _expanded = false;
|
|
12
11
|
|
|
13
|
-
constructor(message: CustomMessage<SkillPromptDetails>) {
|
|
12
|
+
constructor(private readonly message: CustomMessage<SkillPromptDetails>) {
|
|
14
13
|
super();
|
|
15
|
-
this.message = message;
|
|
16
14
|
this.addChild(new Spacer(1));
|
|
17
15
|
|
|
18
16
|
this.box = new Box(1, 1, t => theme.bg("customMessageBg", t));
|
|
@@ -53,15 +53,13 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
53
53
|
private segments: SegmentState[];
|
|
54
54
|
private selectedIndex: number = 0;
|
|
55
55
|
private focusColumn: "left" | "right" | "disabled" = "left";
|
|
56
|
-
private callbacks: SegmentEditorCallbacks;
|
|
57
56
|
|
|
58
57
|
constructor(
|
|
59
58
|
currentLeft: StatusLineSegmentId[],
|
|
60
59
|
currentRight: StatusLineSegmentId[],
|
|
61
|
-
callbacks: SegmentEditorCallbacks,
|
|
60
|
+
private readonly callbacks: SegmentEditorCallbacks,
|
|
62
61
|
) {
|
|
63
62
|
super();
|
|
64
|
-
this.callbacks = callbacks;
|
|
65
63
|
|
|
66
64
|
// Initialize segment states
|
|
67
65
|
this.segments = [];
|
|
@@ -60,7 +60,6 @@ function findGitHeadPath(): string | null {
|
|
|
60
60
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
61
61
|
|
|
62
62
|
export class StatusLineComponent implements Component {
|
|
63
|
-
private session: AgentSession;
|
|
64
63
|
private settings: StatusLineSettings = {};
|
|
65
64
|
private cachedBranch: string | null | undefined = undefined;
|
|
66
65
|
private gitWatcher: fs.FSWatcher | null = null;
|
|
@@ -75,8 +74,7 @@ export class StatusLineComponent implements Component {
|
|
|
75
74
|
private cachedGitStatus: { staged: number; unstaged: number; untracked: number } | null = null;
|
|
76
75
|
private gitStatusLastFetch = 0;
|
|
77
76
|
|
|
78
|
-
constructor(session: AgentSession) {
|
|
79
|
-
this.session = session;
|
|
77
|
+
constructor(private readonly session: AgentSession) {
|
|
80
78
|
this.settings = {
|
|
81
79
|
preset: settings.get("statusLine.preset"),
|
|
82
80
|
leftSegments: settings.get("statusLine.leftSegments"),
|
|
@@ -7,16 +7,14 @@ import type { TodoItem } from "../../tools/todo-write";
|
|
|
7
7
|
* Shows when the agent stops with incomplete todos.
|
|
8
8
|
*/
|
|
9
9
|
export class TodoReminderComponent extends Container {
|
|
10
|
-
private todos: TodoItem[];
|
|
11
|
-
private attempt: number;
|
|
12
|
-
private maxAttempts: number;
|
|
13
10
|
private box: Box;
|
|
14
11
|
|
|
15
|
-
constructor(
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly todos: TodoItem[],
|
|
14
|
+
private readonly attempt: number,
|
|
15
|
+
private readonly maxAttempts: number,
|
|
16
|
+
) {
|
|
16
17
|
super();
|
|
17
|
-
this.todos = todos;
|
|
18
|
-
this.attempt = attempt;
|
|
19
|
-
this.maxAttempts = maxAttempts;
|
|
20
18
|
|
|
21
19
|
this.addChild(new Spacer(1));
|
|
22
20
|
|
|
@@ -50,8 +50,6 @@ class TreeList implements Component {
|
|
|
50
50
|
private flatNodes: FlatNode[] = [];
|
|
51
51
|
private filteredNodes: FlatNode[] = [];
|
|
52
52
|
private selectedIndex = 0;
|
|
53
|
-
private currentLeafId: string | null;
|
|
54
|
-
private maxVisibleLines: number;
|
|
55
53
|
private filterMode: FilterMode = "default";
|
|
56
54
|
private searchQuery = "";
|
|
57
55
|
private toolCallMap: Map<string, ToolCallInfo> = new Map();
|
|
@@ -62,9 +60,11 @@ class TreeList implements Component {
|
|
|
62
60
|
public onCancel?: () => void;
|
|
63
61
|
public onLabelEdit?: (entryId: string, currentLabel: string | undefined) => void;
|
|
64
62
|
|
|
65
|
-
constructor(
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
constructor(
|
|
64
|
+
tree: SessionTreeNode[],
|
|
65
|
+
private readonly currentLeafId: string | null,
|
|
66
|
+
private readonly maxVisibleLines: number,
|
|
67
|
+
) {
|
|
68
68
|
this.multipleRoots = tree.length > 1;
|
|
69
69
|
this.flatNodes = this.flattenTree(tree);
|
|
70
70
|
this.buildActivePath();
|
|
@@ -745,12 +745,13 @@ class SearchLine implements Component {
|
|
|
745
745
|
/** Label input component shown when editing a label */
|
|
746
746
|
class LabelInput implements Component {
|
|
747
747
|
private input: Input;
|
|
748
|
-
private entryId: string;
|
|
749
748
|
public onSubmit?: (entryId: string, label: string | undefined) => void;
|
|
750
749
|
public onCancel?: () => void;
|
|
751
750
|
|
|
752
|
-
constructor(
|
|
753
|
-
|
|
751
|
+
constructor(
|
|
752
|
+
private readonly entryId: string,
|
|
753
|
+
currentLabel: string | undefined,
|
|
754
|
+
) {
|
|
754
755
|
this.input = new Input();
|
|
755
756
|
if (currentLabel) {
|
|
756
757
|
this.input.setValue(currentLabel);
|
|
@@ -789,7 +790,6 @@ export class TreeSelectorComponent extends Container {
|
|
|
789
790
|
private labelInput: LabelInput | null = null;
|
|
790
791
|
private labelInputContainer: Container;
|
|
791
792
|
private treeContainer: Container;
|
|
792
|
-
private onLabelChangeCallback?: (entryId: string, label: string | undefined) => void;
|
|
793
793
|
|
|
794
794
|
constructor(
|
|
795
795
|
tree: SessionTreeNode[],
|
|
@@ -797,11 +797,9 @@ export class TreeSelectorComponent extends Container {
|
|
|
797
797
|
terminalHeight: number,
|
|
798
798
|
onSelect: (entryId: string) => void,
|
|
799
799
|
onCancel: () => void,
|
|
800
|
-
|
|
800
|
+
private readonly onLabelChangeCallback?: (entryId: string, label: string | undefined) => void,
|
|
801
801
|
) {
|
|
802
802
|
super();
|
|
803
|
-
|
|
804
|
-
this.onLabelChangeCallback = onLabelChange;
|
|
805
803
|
const maxVisibleLines = Math.max(5, Math.floor(terminalHeight / 2));
|
|
806
804
|
|
|
807
805
|
this.treeList = new TreeList(tree, currentLeafId, maxVisibleLines);
|
|
@@ -7,13 +7,11 @@ import { theme } from "../../modes/theme/theme";
|
|
|
7
7
|
* Shows when a rule violation is detected and the stream is being rewound.
|
|
8
8
|
*/
|
|
9
9
|
export class TtsrNotificationComponent extends Container {
|
|
10
|
-
private rules: Rule[];
|
|
11
10
|
private box: Box;
|
|
12
11
|
private _expanded = false;
|
|
13
12
|
|
|
14
|
-
constructor(rules: Rule[]) {
|
|
13
|
+
constructor(private readonly rules: Rule[]) {
|
|
15
14
|
super();
|
|
16
|
-
this.rules = rules;
|
|
17
15
|
|
|
18
16
|
this.addChild(new Spacer(1));
|
|
19
17
|
|
|
@@ -12,17 +12,15 @@ interface UserMessageItem {
|
|
|
12
12
|
* Custom user message list component with selection
|
|
13
13
|
*/
|
|
14
14
|
class UserMessageList implements Component {
|
|
15
|
-
private messages: UserMessageItem[] = [];
|
|
16
15
|
private selectedIndex: number = 0;
|
|
17
16
|
public onSelect?: (entryId: string) => void;
|
|
18
17
|
public onCancel?: () => void;
|
|
19
18
|
private maxVisible: number = 10; // Max messages visible
|
|
20
19
|
|
|
21
|
-
constructor(messages: UserMessageItem[]) {
|
|
20
|
+
constructor(private readonly messages: UserMessageItem[]) {
|
|
22
21
|
// Store messages in chronological order (oldest to newest)
|
|
23
|
-
this.messages = messages;
|
|
24
22
|
// Start with the last (most recent) message selected
|
|
25
|
-
this.selectedIndex = Math.max(0, messages.length - 1);
|
|
23
|
+
this.selectedIndex = Math.max(0, this.messages.length - 1);
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
invalidate(): void {
|
|
@@ -17,25 +17,13 @@ export interface LspServerInfo {
|
|
|
17
17
|
* Premium welcome screen with block-based OMP logo and two-column layout.
|
|
18
18
|
*/
|
|
19
19
|
export class WelcomeComponent implements Component {
|
|
20
|
-
private version: string;
|
|
21
|
-
private modelName: string;
|
|
22
|
-
private providerName: string;
|
|
23
|
-
private recentSessions: RecentSession[];
|
|
24
|
-
private lspServers: LspServerInfo[];
|
|
25
|
-
|
|
26
20
|
constructor(
|
|
27
|
-
version: string,
|
|
28
|
-
modelName: string,
|
|
29
|
-
providerName: string,
|
|
30
|
-
recentSessions: RecentSession[] = [],
|
|
31
|
-
lspServers: LspServerInfo[] = [],
|
|
32
|
-
) {
|
|
33
|
-
this.version = version;
|
|
34
|
-
this.modelName = modelName;
|
|
35
|
-
this.providerName = providerName;
|
|
36
|
-
this.recentSessions = recentSessions;
|
|
37
|
-
this.lspServers = lspServers;
|
|
38
|
-
}
|
|
21
|
+
private readonly version: string,
|
|
22
|
+
private modelName: string,
|
|
23
|
+
private providerName: string,
|
|
24
|
+
private recentSessions: RecentSession[] = [],
|
|
25
|
+
private lspServers: LspServerInfo[] = [],
|
|
26
|
+
) {}
|
|
39
27
|
|
|
40
28
|
invalidate(): void {}
|
|
41
29
|
|
|
@@ -269,6 +269,7 @@ export class EventController {
|
|
|
269
269
|
this.ctx.streamingComponent = undefined;
|
|
270
270
|
this.ctx.streamingMessage = undefined;
|
|
271
271
|
}
|
|
272
|
+
await this.ctx.flushPendingModelSwitch();
|
|
272
273
|
this.ctx.pendingTools.clear();
|
|
273
274
|
this.ctx.ui.requestRender();
|
|
274
275
|
this.sendCompletionNotification();
|
|
@@ -282,7 +282,7 @@ export class ExtensionUiController {
|
|
|
282
282
|
return true;
|
|
283
283
|
},
|
|
284
284
|
getThinkingLevel: () => this.ctx.session.thinkingLevel,
|
|
285
|
-
setThinkingLevel: level => this.ctx.session.setThinkingLevel(level),
|
|
285
|
+
setThinkingLevel: (level, persist) => this.ctx.session.setThinkingLevel(level, persist),
|
|
286
286
|
};
|
|
287
287
|
const contextActions: ExtensionContextActions = {
|
|
288
288
|
getModel: () => this.ctx.session.model,
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import * as fs from "node:fs/promises";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
2
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
3
|
import { readImageFromClipboard } from "@oh-my-pi/pi-natives";
|
|
7
|
-
import { $env
|
|
4
|
+
import { $env } from "@oh-my-pi/pi-utils";
|
|
8
5
|
import type { SettingPath, SettingValue } from "../../config/settings";
|
|
9
6
|
import { settings } from "../../config/settings";
|
|
10
7
|
import { theme } from "../../modes/theme/theme";
|
|
11
8
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
12
9
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
13
10
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
11
|
+
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
14
12
|
import { resizeImage } from "../../utils/image-resize";
|
|
15
13
|
import { generateSessionTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
16
14
|
|
|
@@ -601,7 +599,7 @@ export class InputController {
|
|
|
601
599
|
try {
|
|
602
600
|
const image = await readImageFromClipboard();
|
|
603
601
|
if (image) {
|
|
604
|
-
const base64Data =
|
|
602
|
+
const base64Data = image.data.toBase64();
|
|
605
603
|
let imageData = { data: base64Data, mimeType: image.mimeType };
|
|
606
604
|
if (settings.get("images.autoResize")) {
|
|
607
605
|
try {
|
|
@@ -730,61 +728,36 @@ export class InputController {
|
|
|
730
728
|
}
|
|
731
729
|
|
|
732
730
|
async openExternalEditor(): Promise<void> {
|
|
733
|
-
|
|
734
|
-
const editorCmd = $env.VISUAL || $env.EDITOR;
|
|
731
|
+
const editorCmd = getEditorCommand();
|
|
735
732
|
if (!editorCmd) {
|
|
736
733
|
this.ctx.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
|
|
737
734
|
return;
|
|
738
735
|
}
|
|
739
736
|
|
|
740
737
|
const currentText = this.ctx.editor.getText();
|
|
741
|
-
const tmpFile = path.join(os.tmpdir(), `omp-editor-${Snowflake.next()}.omp.md`);
|
|
742
738
|
|
|
743
739
|
let ttyHandle: fs.FileHandle | null = null;
|
|
744
740
|
try {
|
|
745
|
-
// Write current content to temp file
|
|
746
|
-
await Bun.write(tmpFile, currentText);
|
|
747
|
-
|
|
748
|
-
// Stop TUI to release terminal
|
|
749
741
|
ttyHandle = await this.openEditorTerminalHandle();
|
|
750
742
|
this.ctx.ui.stop();
|
|
751
743
|
|
|
752
|
-
// Split by space to support editor arguments (e.g., "code --wait")
|
|
753
|
-
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
754
|
-
|
|
755
744
|
const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle
|
|
756
745
|
? [ttyHandle.fd, ttyHandle.fd, ttyHandle.fd]
|
|
757
746
|
: ["inherit", "inherit", "inherit"];
|
|
758
747
|
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
child.once("error", error => reject(error));
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
// On successful exit (exitCode 0), replace editor content
|
|
766
|
-
if (exitCode === 0) {
|
|
767
|
-
const newContent = (await Bun.file(tmpFile).text()).replace(/\n$/, "");
|
|
768
|
-
this.ctx.editor.setText(newContent);
|
|
748
|
+
const result = await openInEditor(editorCmd, currentText, { extension: ".omp.md", stdio });
|
|
749
|
+
if (result !== null) {
|
|
750
|
+
this.ctx.editor.setText(result);
|
|
769
751
|
}
|
|
770
|
-
// On non-zero exit, keep original text (no action needed)
|
|
771
752
|
} catch (error) {
|
|
772
753
|
this.ctx.showWarning(
|
|
773
754
|
`Failed to open external editor: ${error instanceof Error ? error.message : String(error)}`,
|
|
774
755
|
);
|
|
775
756
|
} finally {
|
|
776
|
-
// Clean up temp file
|
|
777
|
-
try {
|
|
778
|
-
await fs.rm(tmpFile, { force: true });
|
|
779
|
-
} catch {
|
|
780
|
-
// Ignore cleanup errors
|
|
781
|
-
}
|
|
782
|
-
|
|
783
757
|
if (ttyHandle) {
|
|
784
758
|
await ttyHandle.close();
|
|
785
759
|
}
|
|
786
760
|
|
|
787
|
-
// Restart TUI
|
|
788
761
|
this.ctx.ui.start();
|
|
789
762
|
this.ctx.ui.requestRender();
|
|
790
763
|
}
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "../../modes/theme/theme";
|
|
28
28
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
29
29
|
import { SessionManager } from "../../session/session-manager";
|
|
30
|
-
import { setPreferredImageProvider,
|
|
30
|
+
import { setPreferredImageProvider, setPreferredSearchProvider } from "../../tools";
|
|
31
31
|
|
|
32
32
|
export class SelectorController {
|
|
33
33
|
constructor(private ctx: InteractiveModeContext) {}
|
|
@@ -180,7 +180,7 @@ export class SelectorController {
|
|
|
180
180
|
break;
|
|
181
181
|
case "thinkingLevel":
|
|
182
182
|
case "defaultThinkingLevel":
|
|
183
|
-
this.ctx.session.setThinkingLevel(value as ThinkingLevel);
|
|
183
|
+
this.ctx.session.setThinkingLevel(value as ThinkingLevel, true);
|
|
184
184
|
this.ctx.statusLine.invalidate();
|
|
185
185
|
this.ctx.updateEditorBorderColor();
|
|
186
186
|
break;
|
|
@@ -258,7 +258,7 @@ export class SelectorController {
|
|
|
258
258
|
|
|
259
259
|
// Provider settings - update runtime preferences
|
|
260
260
|
case "webSearchProvider":
|
|
261
|
-
|
|
261
|
+
setPreferredSearchProvider(value as "auto" | "exa" | "perplexity" | "anthropic");
|
|
262
262
|
break;
|
|
263
263
|
case "imageProvider":
|
|
264
264
|
setPreferredImageProvider(value as "auto" | "gemini" | "openrouter");
|
|
@@ -136,6 +136,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
136
136
|
private readonly changelogMarkdown: string | undefined;
|
|
137
137
|
private planModePreviousTools: string[] | undefined;
|
|
138
138
|
private planModePreviousModel: Model | undefined;
|
|
139
|
+
private pendingModelSwitch: Model | undefined;
|
|
139
140
|
private planModeHasEntered = false;
|
|
140
141
|
public readonly lspServers:
|
|
141
142
|
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>
|
|
@@ -533,6 +534,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
533
534
|
return;
|
|
534
535
|
}
|
|
535
536
|
this.planModePreviousModel = currentModel;
|
|
537
|
+
if (this.session.isStreaming) {
|
|
538
|
+
this.pendingModelSwitch = planModel;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
536
541
|
try {
|
|
537
542
|
await this.session.setModelTemporary(planModel);
|
|
538
543
|
} catch (error) {
|
|
@@ -542,6 +547,20 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
542
547
|
}
|
|
543
548
|
}
|
|
544
549
|
|
|
550
|
+
/** Apply any deferred model switch after the current stream ends. */
|
|
551
|
+
async flushPendingModelSwitch(): Promise<void> {
|
|
552
|
+
const model = this.pendingModelSwitch;
|
|
553
|
+
if (!model) return;
|
|
554
|
+
this.pendingModelSwitch = undefined;
|
|
555
|
+
try {
|
|
556
|
+
await this.session.setModelTemporary(model);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
this.showWarning(
|
|
559
|
+
`Failed to switch model after streaming: ${error instanceof Error ? error.message : String(error)}`,
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
545
564
|
private async enterPlanMode(options?: {
|
|
546
565
|
planFilePath?: string;
|
|
547
566
|
workflow?: "parallel" | "iterative";
|
|
@@ -569,6 +588,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
569
588
|
workflow: options?.workflow ?? "parallel",
|
|
570
589
|
reentry: this.planModeHasEntered,
|
|
571
590
|
});
|
|
591
|
+
if (this.session.isStreaming) {
|
|
592
|
+
await this.session.sendPlanModeContext({ deliverAs: "steer" });
|
|
593
|
+
}
|
|
572
594
|
this.planModeHasEntered = true;
|
|
573
595
|
await this.applyPlanModeModel();
|
|
574
596
|
this.updatePlanModeStatus();
|
|
@@ -585,7 +607,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
585
607
|
await this.session.setActiveToolsByName(previousTools);
|
|
586
608
|
}
|
|
587
609
|
if (this.planModePreviousModel) {
|
|
588
|
-
|
|
610
|
+
if (this.session.isStreaming) {
|
|
611
|
+
this.pendingModelSwitch = this.planModePreviousModel;
|
|
612
|
+
} else {
|
|
613
|
+
await this.session.setModelTemporary(this.planModePreviousModel);
|
|
614
|
+
}
|
|
589
615
|
}
|
|
590
616
|
|
|
591
617
|
this.session.setPlanModeState(undefined);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
8
|
-
import {
|
|
8
|
+
import { createTextLineSplitter, ptree } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { BashResult } from "../../exec/bash-executor";
|
|
10
10
|
import type { SessionStats } from "../../session/agent-session";
|
|
11
11
|
import type { CompactionResult } from "../../session/compaction";
|
|
@@ -119,10 +119,7 @@ export class RpcClient {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
// Process lines in background
|
|
122
|
-
const lines = this.process.stdout
|
|
123
|
-
.pipeThrough(createTextDecoderStream())
|
|
124
|
-
.pipeThrough(createSanitizerStream())
|
|
125
|
-
.pipeThrough(createSplitterStream("\n"));
|
|
122
|
+
const lines = this.process.stdout.pipeThrough(createTextLineSplitter(true));
|
|
126
123
|
this.lineReader = lines;
|
|
127
124
|
void (async () => {
|
|
128
125
|
try {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Events: AgentSessionEvent objects streamed as they occur
|
|
11
11
|
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { createTextLineSplitter, Snowflake } from "@oh-my-pi/pi-utils";
|
|
14
14
|
import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../../extensibility/extensions";
|
|
15
15
|
import { type Theme, theme } from "../../modes/theme/theme";
|
|
16
16
|
import type { AgentSession } from "../../session/agent-session";
|
|
@@ -633,7 +633,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
633
633
|
}
|
|
634
634
|
|
|
635
635
|
// Listen for JSON input using Bun's stdin
|
|
636
|
-
for await (const line of
|
|
636
|
+
for await (const line of Bun.stdin.stream().pipeThrough(createTextLineSplitter())) {
|
|
637
637
|
if (!line.trim()) continue;
|
|
638
638
|
|
|
639
639
|
const result = Bun.JSONL.parseChunk(`${line}\n`);
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1221,19 +1221,15 @@ const langMap: Record<string, SymbolKey> = {
|
|
|
1221
1221
|
export class Theme {
|
|
1222
1222
|
private fgColors: Record<ThemeColor, string>;
|
|
1223
1223
|
private bgColors: Record<ThemeBg, string>;
|
|
1224
|
-
private mode: ColorMode;
|
|
1225
1224
|
private symbols: SymbolMap;
|
|
1226
|
-
private symbolPreset: SymbolPreset;
|
|
1227
1225
|
|
|
1228
1226
|
constructor(
|
|
1229
1227
|
fgColors: Record<ThemeColor, string | number>,
|
|
1230
1228
|
bgColors: Record<ThemeBg, string | number>,
|
|
1231
|
-
mode: ColorMode,
|
|
1232
|
-
symbolPreset: SymbolPreset,
|
|
1229
|
+
private readonly mode: ColorMode,
|
|
1230
|
+
private readonly symbolPreset: SymbolPreset,
|
|
1233
1231
|
symbolOverrides: Partial<Record<SymbolKey, string>>,
|
|
1234
1232
|
) {
|
|
1235
|
-
this.mode = mode;
|
|
1236
|
-
this.symbolPreset = symbolPreset;
|
|
1237
1233
|
this.fgColors = {} as Record<ThemeColor, string>;
|
|
1238
1234
|
for (const [key, value] of Object.entries(fgColors) as [ThemeColor, string | number][]) {
|
|
1239
1235
|
this.fgColors[key] = fgAnsi(value, mode);
|
package/src/modes/types.ts
CHANGED
|
@@ -114,6 +114,7 @@ export interface InteractiveModeContext {
|
|
|
114
114
|
queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
|
|
115
115
|
flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void>;
|
|
116
116
|
flushPendingBashComponents(): void;
|
|
117
|
+
flushPendingModelSwitch(): Promise<void>;
|
|
117
118
|
setWorkingMessage(message?: string): void;
|
|
118
119
|
applyPendingWorkingMessage(): void;
|
|
119
120
|
isKnownSlashCommand(text: string): boolean;
|
|
@@ -127,10 +127,15 @@ export class UiHelpers {
|
|
|
127
127
|
case "fileMention": {
|
|
128
128
|
// Render compact file mention display
|
|
129
129
|
for (const file of message.files) {
|
|
130
|
+
const suffix = file.image
|
|
131
|
+
? "(image)"
|
|
132
|
+
: file.lineCount === undefined
|
|
133
|
+
? "(unknown lines)"
|
|
134
|
+
: `(${file.lineCount} lines)`;
|
|
130
135
|
const text = `${theme.fg("dim", `${theme.tree.last} `)}${theme.fg("muted", "Read")} ${theme.fg(
|
|
131
136
|
"accent",
|
|
132
137
|
file.path,
|
|
133
|
-
)} ${theme.fg("dim",
|
|
138
|
+
)} ${theme.fg("dim", suffix)}`;
|
|
134
139
|
this.ctx.chatContainer.addChild(new Text(text, 0, 0));
|
|
135
140
|
}
|
|
136
141
|
break;
|
package/src/patch/index.ts
CHANGED
|
@@ -205,15 +205,12 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
205
205
|
public readonly nonAbortable = true;
|
|
206
206
|
public readonly concurrency = "exclusive";
|
|
207
207
|
|
|
208
|
-
private readonly session: ToolSession;
|
|
209
208
|
private readonly allowFuzzy: boolean;
|
|
210
209
|
private readonly fuzzyThreshold: number;
|
|
211
210
|
private readonly writethrough: WritethroughCallback;
|
|
212
211
|
private readonly envEditVariant: string;
|
|
213
212
|
|
|
214
|
-
constructor(session: ToolSession) {
|
|
215
|
-
this.session = session;
|
|
216
|
-
|
|
213
|
+
constructor(private readonly session: ToolSession) {
|
|
217
214
|
const {
|
|
218
215
|
PI_EDIT_FUZZY: editFuzzy = "auto",
|
|
219
216
|
PI_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto",
|
|
@@ -3,6 +3,7 @@ name: explore
|
|
|
3
3
|
description: Fast read-only codebase scout returning compressed context for handoff
|
|
4
4
|
tools: read, grep, find, bash
|
|
5
5
|
model: pi/smol, haiku-4.5, haiku-4-5, gemini-flash-latest, gemini-3-flash, zai-glm-4.7, glm-4.7-flash, glm-4.5-flash, gpt-5.1-codex-mini, haiku, flash, mini
|
|
6
|
+
thinking-level: minimal
|
|
6
7
|
output:
|
|
7
8
|
properties:
|
|
8
9
|
query:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
+
|
|
2
3
|
name: {{jsonStringify name}}
|
|
3
4
|
description: {{jsonStringify description}}
|
|
4
5
|
{{#if spawns}}spawns: {{jsonStringify spawns}}
|
|
5
6
|
{{/if}}{{#if model}}model: {{jsonStringify model}}
|
|
6
|
-
{{/if}}{{#if thinkingLevel}}
|
|
7
|
+
{{/if}}{{#if thinkingLevel}}thinking-level: {{jsonStringify thinkingLevel}}
|
|
7
8
|
{{/if}}---
|
|
8
9
|
{{body}}
|
|
@@ -4,6 +4,7 @@ description: Software architect for complex multi-file architectural decisions.
|
|
|
4
4
|
tools: read, grep, find, bash
|
|
5
5
|
spawns: explore
|
|
6
6
|
model: pi/plan, pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt, opus-4.5, opus-4-5, gemini-3-pro
|
|
7
|
+
thinking-level: high
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
<critical>
|
|
@@ -5,5 +5,7 @@ Your only available action now is to call submit_result. Choose one:
|
|
|
5
5
|
- If task is complete: call submit_result with your result data
|
|
6
6
|
- If task failed or was interrupted: call submit_result with status="aborted" and describe what happened
|
|
7
7
|
|
|
8
|
+
Do NOT choose aborted if you can still complete the task through exploration (using available tools or repo context). If you must abort, include what you tried and the exact blocker.
|
|
9
|
+
|
|
8
10
|
Do NOT output text without a tool call. You must call submit_result to finish.
|
|
9
11
|
</system-reminder>
|
|
@@ -27,5 +27,7 @@ For additional parent conversation context, check {{contextFile}} (`tail -100` o
|
|
|
27
27
|
```
|
|
28
28
|
{{/if}}
|
|
29
29
|
- If cannot complete, call `submit_result` exactly once with result indicating failure/abort status (use failure/notes field if available). Do not claim success.
|
|
30
|
+
- Do NOT abort due to uncertainty or missing info that can be obtained via tools or repo context. Use `find`/`grep`/`read` first, then proceed with reasonable defaults if multiple options are acceptable.
|
|
31
|
+
- Aborting is only acceptable when truly blocked after exhausting tools and reasonable attempts. If you abort, include what you tried and the exact blocker in the result.
|
|
30
32
|
- Keep going until request is fully fulfilled. This matters.
|
|
31
33
|
</critical>
|
|
@@ -37,10 +37,11 @@ State assumptions before non-trivial work. Format:
|
|
|
37
37
|
ASSUMPTIONS:
|
|
38
38
|
1. [assumption]
|
|
39
39
|
2. [assumption]
|
|
40
|
-
→ Then proceed. User will interrupt if wrong.
|
|
41
40
|
```
|
|
41
|
+
Proceed without confirmation. User can interrupt if wrong.
|
|
42
42
|
|
|
43
43
|
Do NOT use ask tool to confirm assumptions. State them, then act. Asking for confirmation wastes a round-trip on questions where "yes, proceed" is the obvious answer.
|
|
44
|
+
Do NOT ask for file paths the user implies or you can resolve from repo context. If a file is referenced, locate and read it.
|
|
44
45
|
|
|
45
46
|
Before finishing (within requested scope):
|
|
46
47
|
- Can this be simpler?
|
|
@@ -154,7 +155,7 @@ Continue non-destructively; someone's work may live there.
|
|
|
154
155
|
|
|
155
156
|
<procedure>
|
|
156
157
|
## Before action
|
|
157
|
-
0. **CHECKPOINT** — multi-step/multi-file/ambiguous tasks:
|
|
158
|
+
0. **CHECKPOINT** — multi-step/multi-file/ambiguous tasks: do a brief internal checkpoint, then continue in the same response (do not wait for user input):
|
|
158
159
|
- Distinct work streams? Dependencies?
|
|
159
160
|
{{#has tools "task"}}
|
|
160
161
|
- Parallel via Task tool, or sequential?
|
|
@@ -169,7 +170,7 @@ Continue non-destructively; someone's work may live there.
|
|
|
169
170
|
1. Plan if task has weight: 3–7 bullets, no more.
|
|
170
171
|
2. Before each tool call: state intent in one sentence.
|
|
171
172
|
3. After each tool call: interpret, decide, move; no echo.
|
|
172
|
-
4. Requirements conflict/unclear:
|
|
173
|
+
4. Requirements conflict/unclear: if genuinely blocked **ONLY AFTER** exhausting your exploration with tools/context/files, ask.
|
|
173
174
|
5. If requested change includes refactor: remove now-unused elements; note removals.
|
|
174
175
|
|
|
175
176
|
## Verification
|
|
@@ -328,6 +329,7 @@ Keep going until finished.
|
|
|
328
329
|
- Blocked: show evidence, what tried, ask minimum question.
|
|
329
330
|
- Quote only needed; rest noise.
|
|
330
331
|
- Don't claim unverified correctness.
|
|
332
|
+
- Do not ask when it may be obtained from available tools or repo context/files.
|
|
331
333
|
- CHECKPOINT step 0 not optional.
|
|
332
334
|
- Touch only requested; no incidental refactors/cleanup.
|
|
333
335
|
{{#has tools "ask"}}- If files differ from expectations: ask before discarding uncommitted work.{{/has}}
|