@oh-my-pi/pi-coding-agent 13.9.6 → 13.9.12
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 +48 -0
- package/package.json +7 -7
- package/src/cli/args.ts +1 -0
- package/src/config/keybindings.ts +6 -0
- package/src/config/model-registry.ts +2 -12
- package/src/config/settings-schema.ts +1 -9
- package/src/debug/log-viewer.ts +11 -7
- package/src/exec/bash-executor.ts +15 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +11 -8
- package/src/modes/components/extensions/extension-list.ts +16 -8
- package/src/modes/components/settings-defs.ts +2 -2
- package/src/modes/components/status-line.ts +5 -9
- package/src/modes/components/tool-execution.ts +9 -5
- package/src/modes/components/tree-selector.ts +4 -6
- package/src/modes/components/welcome.ts +1 -0
- package/src/modes/controllers/command-controller.ts +47 -42
- package/src/modes/controllers/event-controller.ts +8 -2
- package/src/modes/controllers/input-controller.ts +41 -1
- package/src/modes/interactive-mode.ts +2 -10
- package/src/modes/prompt-action-autocomplete.ts +201 -0
- package/src/modes/utils/ui-helpers.ts +5 -1
- package/src/patch/index.ts +1 -1
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/librarian.md +2 -0
- package/src/prompts/system/handoff-document.md +46 -0
- package/src/prompts/system/subagent-system-prompt.md +3 -9
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/bash.md +2 -0
- package/src/session/agent-session.ts +76 -83
- package/src/session/session-manager.ts +5 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +147 -7
- package/src/tools/fetch.ts +52 -57
- package/src/web/kagi.ts +0 -42
- package/src/web/scrapers/docs-rs.ts +653 -0
- package/src/web/scrapers/index.ts +3 -0
- package/src/web/scrapers/youtube.ts +0 -17
- package/src/web/search/index.ts +3 -1
- package/src/web/search/provider.ts +4 -1
- package/src/web/search/providers/exa.ts +8 -0
- package/src/web/search/providers/tavily.ts +162 -0
- package/src/web/search/types.ts +1 -0
|
@@ -478,7 +478,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
478
478
|
},
|
|
479
479
|
this.#renderState,
|
|
480
480
|
theme,
|
|
481
|
-
this.#
|
|
481
|
+
this.#getCallArgsForRender(),
|
|
482
482
|
);
|
|
483
483
|
if (resultComponent) {
|
|
484
484
|
this.#contentBox.addChild(ensureInvalidate(resultComponent));
|
|
@@ -560,10 +560,14 @@ export class ToolExecutionComponent extends Container {
|
|
|
560
560
|
return Math.max(1, Math.min(maxSeconds, value));
|
|
561
561
|
};
|
|
562
562
|
|
|
563
|
-
if (this.#toolName === "bash"
|
|
564
|
-
//
|
|
565
|
-
|
|
566
|
-
|
|
563
|
+
if (this.#toolName === "bash") {
|
|
564
|
+
// Bash needs render context even before a result exists. The renderer uses the pending-call args
|
|
565
|
+
// plus this context to keep the inline command preview visible while tool-call JSON is still streaming.
|
|
566
|
+
if (this.#result) {
|
|
567
|
+
// Pass raw output and expanded state - renderer handles width-aware truncation
|
|
568
|
+
const output = this.#getTextOutput().trimEnd();
|
|
569
|
+
context.output = output;
|
|
570
|
+
}
|
|
567
571
|
context.expanded = this.#expanded;
|
|
568
572
|
context.previewLines = BASH_DEFAULT_PREVIEW_LINES;
|
|
569
573
|
context.timeout = normalizeTimeoutSeconds(this.#args?.timeout, 3600);
|
|
@@ -2,6 +2,7 @@ import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import {
|
|
3
3
|
type Component,
|
|
4
4
|
Container,
|
|
5
|
+
extractPrintableText,
|
|
5
6
|
Input,
|
|
6
7
|
matchesKey,
|
|
7
8
|
Spacer,
|
|
@@ -745,12 +746,9 @@ class TreeList implements Component {
|
|
|
745
746
|
this.onLabelEdit(selected.node.entry.id, selected.node.label);
|
|
746
747
|
}
|
|
747
748
|
} else {
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
});
|
|
752
|
-
if (!hasControlChars && keyData.length > 0) {
|
|
753
|
-
this.#searchQuery += keyData;
|
|
749
|
+
const printableText = extractPrintableText(keyData);
|
|
750
|
+
if (printableText) {
|
|
751
|
+
this.#searchQuery += printableText;
|
|
754
752
|
this.#applyFilter();
|
|
755
753
|
}
|
|
756
754
|
}
|
|
@@ -122,6 +122,7 @@ export class WelcomeComponent implements Component {
|
|
|
122
122
|
const rightLines = [
|
|
123
123
|
` ${theme.bold(theme.fg("accent", "Tips"))}`,
|
|
124
124
|
` ${theme.fg("dim", "?")}${theme.fg("muted", " for keyboard shortcuts")}`,
|
|
125
|
+
` ${theme.fg("dim", "#")}${theme.fg("muted", " for prompt actions")}`,
|
|
125
126
|
` ${theme.fg("dim", "/")}${theme.fg("muted", " for commands")}`,
|
|
126
127
|
` ${theme.fg("dim", "!")}${theme.fg("muted", " to run bash")}`,
|
|
127
128
|
` ${theme.fg("dim", "$")}${theme.fg("muted", " to run python")}`,
|
|
@@ -435,49 +435,54 @@ export class CommandController {
|
|
|
435
435
|
const expandToolsKey = this.ctx.keybindings.getDisplayString("expandTools") || "Ctrl+O";
|
|
436
436
|
const planModeKey = this.ctx.keybindings.getDisplayString("togglePlanMode") || "Alt+Shift+P";
|
|
437
437
|
const sttKey = this.ctx.keybindings.getDisplayString("toggleSTT") || "Alt+H";
|
|
438
|
+
const copyLineKey = this.ctx.keybindings.getDisplayString("copyLine") || "Alt+Shift+L";
|
|
439
|
+
const copyPromptKey = this.ctx.keybindings.getDisplayString("copyPrompt") || "Alt+Shift+C";
|
|
438
440
|
const hotkeys = `
|
|
439
|
-
**Navigation**
|
|
440
|
-
| Key | Action |
|
|
441
|
-
|-----|--------|
|
|
442
|
-
| \`Arrow keys\` | Move cursor / browse history (Up when empty) |
|
|
443
|
-
| \`Option+Left/Right\` | Move by word |
|
|
444
|
-
| \`Ctrl+A\` / \`Home\` / \`Cmd+Left\` | Start of line |
|
|
445
|
-
| \`Ctrl+E\` / \`End\` / \`Cmd+Right\` | End of line |
|
|
446
|
-
|
|
447
|
-
**Editing**
|
|
448
|
-
| Key | Action |
|
|
449
|
-
|-----|--------|
|
|
450
|
-
| \`Enter\` | Send message |
|
|
451
|
-
| \`Shift+Enter\` / \`Alt+Enter\` | New line |
|
|
452
|
-
| \`Ctrl+W\` / \`Option+Backspace\` | Delete word backwards |
|
|
453
|
-
| \`Ctrl+U\` | Delete to start of line |
|
|
454
|
-
| \`Ctrl+K\` | Delete to end of line |
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
|
460
|
-
|
|
461
|
-
| \`
|
|
462
|
-
| \`
|
|
463
|
-
| \`Ctrl+
|
|
464
|
-
| \`
|
|
465
|
-
| \`Ctrl+
|
|
466
|
-
| \`Shift+
|
|
467
|
-
| \`
|
|
468
|
-
| \`Ctrl+
|
|
469
|
-
|
|
|
470
|
-
| \`Ctrl+
|
|
471
|
-
| \`${
|
|
472
|
-
| \`Ctrl+
|
|
473
|
-
| \`
|
|
474
|
-
|
|
|
475
|
-
|
|
|
476
|
-
|
|
|
477
|
-
|
|
|
478
|
-
|
|
|
479
|
-
|
|
|
480
|
-
|
|
441
|
+
**Navigation**
|
|
442
|
+
| Key | Action |
|
|
443
|
+
|-----|--------|
|
|
444
|
+
| \`Arrow keys\` | Move cursor / browse history (Up when empty) |
|
|
445
|
+
| \`Option+Left/Right\` | Move by word |
|
|
446
|
+
| \`Ctrl+A\` / \`Home\` / \`Cmd+Left\` | Start of line |
|
|
447
|
+
| \`Ctrl+E\` / \`End\` / \`Cmd+Right\` | End of line |
|
|
448
|
+
|
|
449
|
+
**Editing**
|
|
450
|
+
| Key | Action |
|
|
451
|
+
|-----|--------|
|
|
452
|
+
| \`Enter\` | Send message |
|
|
453
|
+
| \`Shift+Enter\` / \`Alt+Enter\` | New line |
|
|
454
|
+
| \`Ctrl+W\` / \`Option+Backspace\` | Delete word backwards |
|
|
455
|
+
| \`Ctrl+U\` | Delete to start of line |
|
|
456
|
+
| \`Ctrl+K\` | Delete to end of line |
|
|
457
|
+
| \`${copyLineKey}\` | Copy current line |
|
|
458
|
+
| \`${copyPromptKey}\` | Copy whole prompt |
|
|
459
|
+
|
|
460
|
+
**Other**
|
|
461
|
+
| Key | Action |
|
|
462
|
+
|-----|--------|
|
|
463
|
+
| \`Tab\` | Path completion / accept autocomplete |
|
|
464
|
+
| \`Escape\` | Cancel autocomplete / abort streaming |
|
|
465
|
+
| \`Ctrl+C\` | Clear editor (first) / exit (second) |
|
|
466
|
+
| \`Ctrl+D\` | Exit (when editor is empty) |
|
|
467
|
+
| \`Ctrl+Z\` | Suspend to background |
|
|
468
|
+
| \`Shift+Tab\` | Cycle thinking level |
|
|
469
|
+
| \`Ctrl+P\` | Cycle role models (slow/default/smol) |
|
|
470
|
+
| \`Shift+Ctrl+P\` | Cycle role models (temporary) |
|
|
471
|
+
| \`Alt+P\` | Select model (temporary) |
|
|
472
|
+
| \`Ctrl+L\` | Select model (set roles) |
|
|
473
|
+
| \`${planModeKey}\` | Toggle plan mode |
|
|
474
|
+
| \`Ctrl+R\` | Search prompt history |
|
|
475
|
+
| \`${expandToolsKey}\` | Toggle tool output expansion |
|
|
476
|
+
| \`Ctrl+T\` | Toggle todo list expansion |
|
|
477
|
+
| \`Ctrl+G\` | Edit message in external editor |
|
|
478
|
+
| \`${sttKey}\` | Toggle speech-to-text recording |
|
|
479
|
+
| \`#\` | Open prompt actions |
|
|
480
|
+
| \`/\` | Slash commands |
|
|
481
|
+
| \`!\` | Run bash command |
|
|
482
|
+
| \`!!\` | Run bash command (excluded from context) |
|
|
483
|
+
| \`$\` | Run Python in shared kernel |
|
|
484
|
+
| \`$$\` | Run Python (excluded from context) |
|
|
485
|
+
`;
|
|
481
486
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
482
487
|
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
483
488
|
this.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Keyboard Shortcuts")), 1, 0));
|
|
@@ -184,13 +184,19 @@ export class EventController {
|
|
|
184
184
|
continue;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// Preserve the raw partial JSON for renderers that need to surface fields before the JSON object closes.
|
|
188
|
+
// Bash uses this to show inline env assignments during streaming instead of popping them in at completion.
|
|
189
|
+
const renderArgs =
|
|
190
|
+
"partialJson" in content
|
|
191
|
+
? { ...content.arguments, __partialJson: content.partialJson }
|
|
192
|
+
: content.arguments;
|
|
187
193
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
188
194
|
this.#resetReadGroup();
|
|
189
195
|
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
190
196
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
191
197
|
const component = new ToolExecutionComponent(
|
|
192
198
|
content.name,
|
|
193
|
-
|
|
199
|
+
renderArgs,
|
|
194
200
|
{
|
|
195
201
|
showImages: settings.get("terminal.showImages"),
|
|
196
202
|
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
@@ -206,7 +212,7 @@ export class EventController {
|
|
|
206
212
|
} else {
|
|
207
213
|
const component = this.ctx.pendingTools.get(content.id);
|
|
208
214
|
if (component) {
|
|
209
|
-
component.updateArgs(
|
|
215
|
+
component.updateArgs(renderArgs, content.id);
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
218
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import { type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import { copyToClipboard, readImageFromClipboard, sanitizeText } from "@oh-my-pi/pi-natives";
|
|
4
|
+
import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import { settings } from "../../config/settings";
|
|
7
|
+
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
6
8
|
import { theme } from "../../modes/theme/theme";
|
|
7
9
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
8
10
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
@@ -87,7 +89,8 @@ export class InputController {
|
|
|
87
89
|
this.ctx.editor.onCtrlG = () => void this.openExternalEditor();
|
|
88
90
|
this.ctx.editor.onQuestionMark = () => this.ctx.handleHotkeysCommand();
|
|
89
91
|
this.ctx.editor.onCtrlV = () => this.handleImagePaste();
|
|
90
|
-
this.ctx.
|
|
92
|
+
const copyPromptKeys = this.ctx.keybindings.getKeys("copyPrompt");
|
|
93
|
+
this.ctx.editor.onCopyPrompt = copyPromptKeys.includes("alt+shift+c") ? () => this.handleCopyPrompt() : undefined;
|
|
91
94
|
|
|
92
95
|
// Wire up extension shortcuts
|
|
93
96
|
this.registerExtensionShortcuts();
|
|
@@ -129,6 +132,13 @@ export class InputController {
|
|
|
129
132
|
for (const key of this.ctx.keybindings.getKeys("toggleSTT")) {
|
|
130
133
|
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleSTTToggle());
|
|
131
134
|
}
|
|
135
|
+
for (const key of this.ctx.keybindings.getKeys("copyLine")) {
|
|
136
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.handleCopyCurrentLine());
|
|
137
|
+
}
|
|
138
|
+
for (const key of copyPromptKeys) {
|
|
139
|
+
if (key === "alt+shift+c") continue;
|
|
140
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.handleCopyPrompt());
|
|
141
|
+
}
|
|
132
142
|
|
|
133
143
|
this.ctx.editor.onChange = (text: string) => {
|
|
134
144
|
const wasBashMode = this.ctx.isBashMode;
|
|
@@ -503,6 +513,36 @@ export class InputController {
|
|
|
503
513
|
}
|
|
504
514
|
}
|
|
505
515
|
|
|
516
|
+
createAutocompleteProvider(commands: SlashCommand[], basePath: string): AutocompleteProvider {
|
|
517
|
+
return createPromptActionAutocompleteProvider({
|
|
518
|
+
commands,
|
|
519
|
+
basePath,
|
|
520
|
+
keybindings: this.ctx.keybindings,
|
|
521
|
+
copyCurrentLine: () => this.handleCopyCurrentLine(),
|
|
522
|
+
copyPrompt: () => this.handleCopyPrompt(),
|
|
523
|
+
moveCursorToLineStart: () => this.ctx.editor.moveToLineStart(),
|
|
524
|
+
moveCursorToLineEnd: () => this.ctx.editor.moveToLineEnd(),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Copy the current editor line to the system clipboard. */
|
|
529
|
+
handleCopyCurrentLine(): void {
|
|
530
|
+
const { line } = this.ctx.editor.getCursor();
|
|
531
|
+
const text = this.ctx.editor.getLines()[line] || "";
|
|
532
|
+
if (!text) {
|
|
533
|
+
this.ctx.showStatus("Nothing to copy");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
copyToClipboard(text);
|
|
538
|
+
const sanitized = sanitizeText(text);
|
|
539
|
+
const preview = sanitized.length > 30 ? `${sanitized.slice(0, 30)}...` : sanitized;
|
|
540
|
+
this.ctx.showStatus(`Copied line: ${preview}`);
|
|
541
|
+
} catch {
|
|
542
|
+
this.ctx.showWarning("Failed to copy to clipboard");
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
506
546
|
/** Copy current prompt text to system clipboard. */
|
|
507
547
|
handleCopyPrompt(): void {
|
|
508
548
|
const text = this.ctx.editor.getText();
|
|
@@ -6,15 +6,7 @@ import * as path from "node:path";
|
|
|
6
6
|
import { type Agent, type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai";
|
|
8
8
|
import type { Component, Loader, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
9
|
-
import {
|
|
10
|
-
CombinedAutocompleteProvider,
|
|
11
|
-
Container,
|
|
12
|
-
Markdown,
|
|
13
|
-
ProcessTerminal,
|
|
14
|
-
Spacer,
|
|
15
|
-
Text,
|
|
16
|
-
TUI,
|
|
17
|
-
} from "@oh-my-pi/pi-tui";
|
|
9
|
+
import { Container, Markdown, ProcessTerminal, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
|
|
18
10
|
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
19
11
|
import chalk from "chalk";
|
|
20
12
|
import { KeybindingsManager } from "../config/keybindings";
|
|
@@ -389,7 +381,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
389
381
|
name: cmd.name,
|
|
390
382
|
description: cmd.description,
|
|
391
383
|
}));
|
|
392
|
-
const autocompleteProvider =
|
|
384
|
+
const autocompleteProvider = this.#inputController.createAutocompleteProvider(
|
|
393
385
|
[...this.#pendingSlashCommands, ...fileSlashCommands],
|
|
394
386
|
basePath,
|
|
395
387
|
);
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AutocompleteItem,
|
|
3
|
+
type AutocompleteProvider,
|
|
4
|
+
CombinedAutocompleteProvider,
|
|
5
|
+
getEditorKeybindings,
|
|
6
|
+
type SlashCommand,
|
|
7
|
+
} from "@oh-my-pi/pi-tui";
|
|
8
|
+
import { formatKeyHints, type KeybindingsManager } from "../config/keybindings";
|
|
9
|
+
|
|
10
|
+
interface PromptActionDefinition {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
description: string;
|
|
14
|
+
keywords: string[];
|
|
15
|
+
execute: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PromptActionAutocompleteItem extends AutocompleteItem {
|
|
19
|
+
actionId: string;
|
|
20
|
+
execute: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface PromptActionAutocompleteOptions {
|
|
24
|
+
commands: SlashCommand[];
|
|
25
|
+
basePath: string;
|
|
26
|
+
keybindings: KeybindingsManager;
|
|
27
|
+
copyCurrentLine: () => void;
|
|
28
|
+
copyPrompt: () => void;
|
|
29
|
+
moveCursorToLineStart: () => void;
|
|
30
|
+
moveCursorToLineEnd: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fuzzyMatch(query: string, target: string): boolean {
|
|
34
|
+
if (query.length === 0) return true;
|
|
35
|
+
if (query.length > target.length) return false;
|
|
36
|
+
|
|
37
|
+
let queryIndex = 0;
|
|
38
|
+
for (let targetIndex = 0; targetIndex < target.length && queryIndex < query.length; targetIndex += 1) {
|
|
39
|
+
if (query[queryIndex] === target[targetIndex]) {
|
|
40
|
+
queryIndex += 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return queryIndex === query.length;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fuzzyScore(query: string, target: string): number {
|
|
48
|
+
if (query.length === 0) return 1;
|
|
49
|
+
if (target === query) return 100;
|
|
50
|
+
if (target.startsWith(query)) return 80;
|
|
51
|
+
if (target.includes(query)) return 60;
|
|
52
|
+
|
|
53
|
+
let queryIndex = 0;
|
|
54
|
+
let gaps = 0;
|
|
55
|
+
let lastMatchIndex = -1;
|
|
56
|
+
for (let targetIndex = 0; targetIndex < target.length && queryIndex < query.length; targetIndex += 1) {
|
|
57
|
+
if (query[queryIndex] === target[targetIndex]) {
|
|
58
|
+
if (lastMatchIndex >= 0 && targetIndex - lastMatchIndex > 1) {
|
|
59
|
+
gaps += 1;
|
|
60
|
+
}
|
|
61
|
+
lastMatchIndex = targetIndex;
|
|
62
|
+
queryIndex += 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (queryIndex !== query.length) return 0;
|
|
67
|
+
return Math.max(1, 40 - gaps * 5);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isPromptActionItem(item: AutocompleteItem): item is PromptActionAutocompleteItem {
|
|
71
|
+
return (
|
|
72
|
+
"actionId" in item && "execute" in item && typeof (item as PromptActionAutocompleteItem).execute === "function"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getPromptActionPrefix(textBeforeCursor: string): string | null {
|
|
77
|
+
const hashIndex = textBeforeCursor.lastIndexOf("#");
|
|
78
|
+
if (hashIndex === -1) return null;
|
|
79
|
+
|
|
80
|
+
const query = textBeforeCursor.slice(hashIndex + 1);
|
|
81
|
+
if (/[\s]/.test(query)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return textBeforeCursor.slice(hashIndex);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
89
|
+
#baseProvider: CombinedAutocompleteProvider;
|
|
90
|
+
#actions: PromptActionDefinition[];
|
|
91
|
+
|
|
92
|
+
constructor(commands: SlashCommand[], basePath: string, actions: PromptActionDefinition[]) {
|
|
93
|
+
this.#baseProvider = new CombinedAutocompleteProvider(commands, basePath);
|
|
94
|
+
this.#actions = actions;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getSuggestions(
|
|
98
|
+
lines: string[],
|
|
99
|
+
cursorLine: number,
|
|
100
|
+
cursorCol: number,
|
|
101
|
+
): Promise<{ items: AutocompleteItem[]; prefix: string } | null> {
|
|
102
|
+
const currentLine = lines[cursorLine] || "";
|
|
103
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
104
|
+
const promptActionPrefix = getPromptActionPrefix(textBeforeCursor);
|
|
105
|
+
if (promptActionPrefix) {
|
|
106
|
+
const query = promptActionPrefix.slice(1).toLowerCase();
|
|
107
|
+
const items = this.#actions
|
|
108
|
+
.map(action => {
|
|
109
|
+
const searchable = [action.label, action.description, ...action.keywords].join(" ").toLowerCase();
|
|
110
|
+
if (!fuzzyMatch(query, searchable)) return null;
|
|
111
|
+
return {
|
|
112
|
+
value: action.label,
|
|
113
|
+
label: action.label,
|
|
114
|
+
description: action.description,
|
|
115
|
+
actionId: action.id,
|
|
116
|
+
execute: action.execute,
|
|
117
|
+
score: fuzzyScore(query, searchable),
|
|
118
|
+
} satisfies PromptActionAutocompleteItem & { score: number };
|
|
119
|
+
})
|
|
120
|
+
.filter(item => item !== null)
|
|
121
|
+
.sort((a, b) => b.score - a.score)
|
|
122
|
+
.map(({ score: _score, ...item }) => item);
|
|
123
|
+
if (items.length > 0) {
|
|
124
|
+
return { items, prefix: promptActionPrefix };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
applyCompletion(
|
|
132
|
+
lines: string[],
|
|
133
|
+
cursorLine: number,
|
|
134
|
+
cursorCol: number,
|
|
135
|
+
item: AutocompleteItem,
|
|
136
|
+
prefix: string,
|
|
137
|
+
): {
|
|
138
|
+
lines: string[];
|
|
139
|
+
cursorLine: number;
|
|
140
|
+
cursorCol: number;
|
|
141
|
+
onApplied?: () => void;
|
|
142
|
+
} {
|
|
143
|
+
if (prefix.startsWith("#") && isPromptActionItem(item)) {
|
|
144
|
+
const currentLine = lines[cursorLine] || "";
|
|
145
|
+
const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
|
|
146
|
+
const afterCursor = currentLine.slice(cursorCol);
|
|
147
|
+
const newLines = [...lines];
|
|
148
|
+
newLines[cursorLine] = beforePrefix + afterCursor;
|
|
149
|
+
return {
|
|
150
|
+
lines: newLines,
|
|
151
|
+
cursorLine,
|
|
152
|
+
cursorCol: beforePrefix.length,
|
|
153
|
+
onApplied: item.execute,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return this.#baseProvider.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getInlineHint(lines: string[], cursorLine: number, cursorCol: number): string | null {
|
|
161
|
+
return this.#baseProvider.getInlineHint?.(lines, cursorLine, cursorCol) ?? null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function createPromptActionAutocompleteProvider(
|
|
166
|
+
options: PromptActionAutocompleteOptions,
|
|
167
|
+
): PromptActionAutocompleteProvider {
|
|
168
|
+
const editorKeybindings = getEditorKeybindings();
|
|
169
|
+
const actions: PromptActionDefinition[] = [
|
|
170
|
+
{
|
|
171
|
+
id: "copy-line",
|
|
172
|
+
label: "Copy current line",
|
|
173
|
+
description: formatKeyHints(options.keybindings.getKeys("copyLine")),
|
|
174
|
+
keywords: ["copy", "line", "clipboard", "current"],
|
|
175
|
+
execute: options.copyCurrentLine,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "copy-prompt",
|
|
179
|
+
label: "Copy whole prompt",
|
|
180
|
+
description: formatKeyHints(options.keybindings.getKeys("copyPrompt")),
|
|
181
|
+
keywords: ["copy", "prompt", "clipboard", "message"],
|
|
182
|
+
execute: options.copyPrompt,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "cursor-line-start",
|
|
186
|
+
label: "Move cursor to beginning of line",
|
|
187
|
+
description: formatKeyHints(editorKeybindings.getKeys("cursorLineStart")),
|
|
188
|
+
keywords: ["move", "cursor", "line", "start", "beginning", "home"],
|
|
189
|
+
execute: options.moveCursorToLineStart,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "cursor-line-end",
|
|
193
|
+
label: "Move cursor to end of line",
|
|
194
|
+
description: formatKeyHints(editorKeybindings.getKeys("cursorLineEnd")),
|
|
195
|
+
keywords: ["move", "cursor", "line", "end"],
|
|
196
|
+
execute: options.moveCursorToLineEnd,
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
return new PromptActionAutocompleteProvider(options.commands, options.basePath, actions);
|
|
201
|
+
}
|
|
@@ -272,9 +272,13 @@ export class UiHelpers {
|
|
|
272
272
|
|
|
273
273
|
readGroup = null;
|
|
274
274
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
275
|
+
const renderArgs =
|
|
276
|
+
"partialJson" in content
|
|
277
|
+
? { ...content.arguments, __partialJson: content.partialJson }
|
|
278
|
+
: content.arguments;
|
|
275
279
|
const component = new ToolExecutionComponent(
|
|
276
280
|
content.name,
|
|
277
|
-
|
|
281
|
+
renderArgs,
|
|
278
282
|
{
|
|
279
283
|
showImages: settings.get("terminal.showImages"),
|
|
280
284
|
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
package/src/patch/index.ts
CHANGED
|
@@ -96,7 +96,7 @@ export type ReplaceParams = Static<typeof replaceEditSchema>;
|
|
|
96
96
|
export type PatchParams = Static<typeof patchEditSchema>;
|
|
97
97
|
|
|
98
98
|
/** Pattern matching hashline display format prefixes: `LINE#ID:CONTENT` and `#ID:CONTENT` */
|
|
99
|
-
const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*(?:\d+\s*#\s
|
|
99
|
+
const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*(?:\d+\s*#\s*|#\s*)[ZPMQVRWSNKTXJBYH]{2}:/;
|
|
100
100
|
|
|
101
101
|
/** Pattern matching a unified-diff added-line `+` prefix (but not `++`). Does NOT match `-` to avoid corrupting Markdown list items. */
|
|
102
102
|
const DIFF_PLUS_RE = /^[+](?![+])/;
|
|
@@ -99,6 +99,7 @@ Given a task, you rapidly investigate the codebase and return structured finding
|
|
|
99
99
|
<directives>
|
|
100
100
|
- You **MUST** use tools for broad pattern matching / code search as much as possible.
|
|
101
101
|
- You **SHOULD** invoke tools in parallel when possible—this is a short investigation, and you are supposed to finish in a few seconds.
|
|
102
|
+
- If a search returns empty results, you **MUST** try at least one alternate strategy (different pattern, broader path, or AST search) before concluding the target doesn't exist.
|
|
102
103
|
</directives>
|
|
103
104
|
|
|
104
105
|
<thoroughness>
|
|
@@ -111,6 +111,8 @@ Before acting, determine what kind of question this is:
|
|
|
111
111
|
- If you discover undocumented behavior or gotchas, you **MUST** populate `caveats`.
|
|
112
112
|
- When local `node_modules` has the package, you **SHOULD** prefer it over cloning — it reflects the version the project actually uses.
|
|
113
113
|
- You **SHOULD** use `web_search` to find the canonical repo URL and to check for known issues, but the definitive answer **MUST** come from reading source code.
|
|
114
|
+
- If a search or lookup returns empty or unexpectedly few results, you **MUST** try at least 2 fallback strategies (broader query, alternate path, different source) before concluding nothing exists.
|
|
115
|
+
- If the package is absent from local `node_modules` and cloning fails, you **MUST** fall back to `web_search` for official API documentation before reporting failure.
|
|
114
116
|
</directives>
|
|
115
117
|
|
|
116
118
|
<critical>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<critical>
|
|
2
|
+
Write a comprehensive handoff document for another instance of yourself.
|
|
3
|
+
The handoff **MUST** be sufficient for seamless continuation without access to this conversation.
|
|
4
|
+
Output ONLY the handoff document. No preamble, no commentary, no wrapper text.
|
|
5
|
+
</critical>
|
|
6
|
+
|
|
7
|
+
<instruction>
|
|
8
|
+
Capture exact technical state, not abstractions.
|
|
9
|
+
Include concrete file paths, symbol names, commands run, test results, observed failures, decisions made, and any partial work that materially affects the next step.
|
|
10
|
+
</instruction>
|
|
11
|
+
|
|
12
|
+
<output>
|
|
13
|
+
Use exactly this structure:
|
|
14
|
+
|
|
15
|
+
## Goal
|
|
16
|
+
[What the user is trying to accomplish]
|
|
17
|
+
|
|
18
|
+
## Constraints & Preferences
|
|
19
|
+
- [Any constraints, preferences, or requirements mentioned]
|
|
20
|
+
|
|
21
|
+
## Progress
|
|
22
|
+
### Done
|
|
23
|
+
- [x] [Completed tasks with specifics]
|
|
24
|
+
|
|
25
|
+
### In Progress
|
|
26
|
+
- [ ] [Current work if any]
|
|
27
|
+
|
|
28
|
+
### Pending
|
|
29
|
+
- [ ] [Tasks mentioned but not started]
|
|
30
|
+
|
|
31
|
+
## Key Decisions
|
|
32
|
+
- **[Decision]**: [Rationale]
|
|
33
|
+
|
|
34
|
+
## Critical Context
|
|
35
|
+
- [Code snippets, file paths, function/type names, error messages, or data essential to continue]
|
|
36
|
+
- [Repository state if relevant]
|
|
37
|
+
|
|
38
|
+
## Next Steps
|
|
39
|
+
1. [What should happen next]
|
|
40
|
+
</output>
|
|
41
|
+
|
|
42
|
+
{{#if additionalFocus}}
|
|
43
|
+
<instruction>
|
|
44
|
+
Additional focus: {{additionalFocus}}
|
|
45
|
+
</instruction>
|
|
46
|
+
{{/if}}
|
|
@@ -29,13 +29,7 @@ Your result **MUST** match this TypeScript interface:
|
|
|
29
29
|
{{/if}}
|
|
30
30
|
|
|
31
31
|
{{SECTION_SEPERATOR "Giving Up"}}
|
|
32
|
-
|
|
32
|
+
Giving up is a last resort. If truly blocked, you **MUST** call `submit_result` exactly once with `result.error` describing what you tried and the exact blocker.
|
|
33
|
+
You **MUST NOT** give up due to uncertainty, missing information obtainable via tools or repo context, or needing a design decision you can derive yourself.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
You **MUST NOT** give up due to uncertainty or missing information obtainable via tools or repo context.
|
|
36
|
-
You **MUST NOT** give up due to requiring a design, you can derive that yourself, more than capable of that.
|
|
37
|
-
|
|
38
|
-
Proceed with the best approach using the most reasonable option.
|
|
39
|
-
|
|
40
|
-
You **MUST** keep going until this ticket is closed.
|
|
41
|
-
This matters.
|
|
35
|
+
You **MUST** keep going until this ticket is closed. This matters.
|
|
@@ -313,8 +313,9 @@ Today is '{{date}}', and your work begins now. Get it right.
|
|
|
313
313
|
- You **MUST** use the most specialized tool, **NEVER** `cat` if there's tool.bash, `rg/grep`:tool.grep, `find`:tool.find, `sed`:tool.edit…
|
|
314
314
|
- Every turn **MUST** materially advance the deliverable.
|
|
315
315
|
- You **MUST** default to action. You **MUST NOT** ask for confirmation to continue work. If you hit an error, you **MUST** fix it. If you know the next step, you **MUST** take it. The user will intervene if needed.
|
|
316
|
-
- You **MUST NOT** make speculative edits before understanding the surrounding design.
|
|
317
316
|
- You **MUST** default to informed action. You **MUST NOT** ask for confirmation to continue work. If you hit an error, you **MUST** fix it. If you know the next step, you **MUST** take it. The user will intervene if needed.
|
|
317
|
+
- You **MUST NOT** make speculative edits before understanding the surrounding design.
|
|
318
|
+
- You **MUST NOT** stop calling tools to save round-trips when the task is incomplete. Completeness beats efficiency.
|
|
318
319
|
- You **MUST NOT** ask when the answer may be obtained from available tools or repo context/files.
|
|
319
320
|
- You **MUST** verify the effect. When a task involves a behavioral change, you **MUST** confirm the change is observable before yielding: run the specific test, command, or scenario that covers your change.
|
|
320
321
|
</critical>
|
|
@@ -2,6 +2,8 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- You **MUST** use `cwd` parameter to set working directory instead of `cd dir && …`
|
|
5
|
+
- Prefer `env: { NAME: "…" }` for multiline, quote-heavy, or untrusted values instead of inlining them into shell syntax; reference them from the command as `$NAME`
|
|
6
|
+
- Quote variable expansions like `"$NAME"` to preserve exact content and avoid shell parsing bugs
|
|
5
7
|
- PTY mode is opt-in: set `pty: true` only when command expects a real terminal (for example `sudo`, `ssh` where you need input from the user); default is `false`
|
|
6
8
|
- You **MUST** use `;` only when later commands should run regardless of earlier failures
|
|
7
9
|
- `skill://` URIs are auto-resolved to filesystem paths before execution
|