@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/package.json +7 -7
  3. package/src/cli/args.ts +1 -0
  4. package/src/config/keybindings.ts +6 -0
  5. package/src/config/model-registry.ts +2 -12
  6. package/src/config/settings-schema.ts +1 -9
  7. package/src/debug/log-viewer.ts +11 -7
  8. package/src/exec/bash-executor.ts +15 -1
  9. package/src/internal-urls/docs-index.generated.ts +1 -1
  10. package/src/modes/components/agent-dashboard.ts +11 -8
  11. package/src/modes/components/extensions/extension-list.ts +16 -8
  12. package/src/modes/components/settings-defs.ts +2 -2
  13. package/src/modes/components/status-line.ts +5 -9
  14. package/src/modes/components/tool-execution.ts +9 -5
  15. package/src/modes/components/tree-selector.ts +4 -6
  16. package/src/modes/components/welcome.ts +1 -0
  17. package/src/modes/controllers/command-controller.ts +47 -42
  18. package/src/modes/controllers/event-controller.ts +8 -2
  19. package/src/modes/controllers/input-controller.ts +41 -1
  20. package/src/modes/interactive-mode.ts +2 -10
  21. package/src/modes/prompt-action-autocomplete.ts +201 -0
  22. package/src/modes/utils/ui-helpers.ts +5 -1
  23. package/src/patch/index.ts +1 -1
  24. package/src/prompts/agents/explore.md +1 -0
  25. package/src/prompts/agents/librarian.md +2 -0
  26. package/src/prompts/system/handoff-document.md +46 -0
  27. package/src/prompts/system/subagent-system-prompt.md +3 -9
  28. package/src/prompts/system/system-prompt.md +2 -1
  29. package/src/prompts/tools/bash.md +2 -0
  30. package/src/session/agent-session.ts +76 -83
  31. package/src/session/session-manager.ts +5 -0
  32. package/src/tools/bash-interactive.ts +8 -3
  33. package/src/tools/bash.ts +147 -7
  34. package/src/tools/fetch.ts +52 -57
  35. package/src/web/kagi.ts +0 -42
  36. package/src/web/scrapers/docs-rs.ts +653 -0
  37. package/src/web/scrapers/index.ts +3 -0
  38. package/src/web/scrapers/youtube.ts +0 -17
  39. package/src/web/search/index.ts +3 -1
  40. package/src/web/search/provider.ts +4 -1
  41. package/src/web/search/providers/exa.ts +8 -0
  42. package/src/web/search/providers/tavily.ts +162 -0
  43. 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.#args, // Pass args for tools that need them
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" && this.#result) {
564
- // Pass raw output and expanded state - renderer handles width-aware truncation
565
- const output = this.#getTextOutput().trimEnd();
566
- context.output = output;
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 hasControlChars = [...keyData].some(ch => {
749
- const code = ch.charCodeAt(0);
750
- return code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);
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
- **Other**
457
- | Key | Action |
458
- |-----|--------|
459
- | \`Tab\` | Path completion / accept autocomplete |
460
- | \`Escape\` | Cancel autocomplete / abort streaming |
461
- | \`Ctrl+C\` | Clear editor (first) / exit (second) |
462
- | \`Ctrl+D\` | Exit (when editor is empty) |
463
- | \`Ctrl+Z\` | Suspend to background |
464
- | \`Shift+Tab\` | Cycle thinking level |
465
- | \`Ctrl+P\` | Cycle role models (slow/default/smol) |
466
- | \`Shift+Ctrl+P\` | Cycle role models (temporary) |
467
- | \`Alt+P\` | Select model (temporary) |
468
- | \`Ctrl+L\` | Select model (set roles) |
469
- | \`${planModeKey}\` | Toggle plan mode |
470
- | \`Ctrl+R\` | Search prompt history |
471
- | \`${expandToolsKey}\` | Toggle tool output expansion |
472
- | \`Ctrl+T\` | Toggle todo list expansion |
473
- | \`Ctrl+G\` | Edit message in external editor |
474
- | \`${sttKey}\` | Toggle speech-to-text recording |
475
- | \`/\` | Slash commands |
476
- | \`!\` | Run bash command |
477
- | \`!!\` | Run bash command (excluded from context) |
478
- | \`$\` | Run Python in shared kernel |
479
- | \`$$\` | Run Python (excluded from context) |
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
- content.arguments,
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(content.arguments, content.id);
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.editor.onCopyPrompt = () => this.handleCopyPrompt();
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 = new CombinedAutocompleteProvider(
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
- content.arguments,
281
+ renderArgs,
278
282
  {
279
283
  showImages: settings.get("terminal.showImages"),
280
284
  editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
@@ -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*|#)\s*[0-9a-zA-Z]{1,16}:/;
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
- If you cannot complete the assignment, you **MUST** call `submit_result` exactly once with `result.error` describing what you tried and the exact blocker.
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
- Giving up is a last resort.
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