@oh-my-pi/pi-coding-agent 4.0.1 → 4.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 +49 -0
- package/README.md +2 -1
- package/docs/sdk.md +0 -3
- package/package.json +6 -5
- package/src/config.ts +9 -0
- package/src/core/agent-storage.ts +450 -0
- package/src/core/auth-storage.ts +111 -184
- package/src/core/compaction/branch-summarization.ts +5 -4
- package/src/core/compaction/compaction.ts +7 -6
- package/src/core/compaction/utils.ts +6 -11
- package/src/core/custom-commands/bundled/review/index.ts +22 -94
- package/src/core/custom-share.ts +66 -0
- package/src/core/history-storage.ts +174 -0
- package/src/core/index.ts +1 -0
- package/src/core/keybindings.ts +3 -0
- package/src/core/prompt-templates.ts +271 -1
- package/src/core/sdk.ts +14 -3
- package/src/core/settings-manager.ts +100 -34
- package/src/core/slash-commands.ts +4 -1
- package/src/core/storage-migration.ts +215 -0
- package/src/core/system-prompt.ts +87 -289
- package/src/core/title-generator.ts +3 -2
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +2 -1
- package/src/core/tools/calculator.ts +2 -1
- package/src/core/tools/edit.ts +2 -1
- package/src/core/tools/find.ts +2 -1
- package/src/core/tools/gemini-image.ts +2 -1
- package/src/core/tools/git.ts +2 -2
- package/src/core/tools/grep.ts +2 -1
- package/src/core/tools/index.test.ts +0 -28
- package/src/core/tools/index.ts +0 -6
- package/src/core/tools/lsp/index.ts +2 -1
- package/src/core/tools/output.ts +2 -1
- package/src/core/tools/read.ts +4 -1
- package/src/core/tools/ssh.ts +4 -2
- package/src/core/tools/task/agents.ts +56 -30
- package/src/core/tools/task/commands.ts +9 -8
- package/src/core/tools/task/index.ts +7 -15
- package/src/core/tools/web-fetch.ts +2 -1
- package/src/core/tools/web-search/auth.ts +106 -16
- package/src/core/tools/web-search/index.ts +3 -2
- package/src/core/tools/web-search/providers/anthropic.ts +44 -6
- package/src/core/tools/write.ts +2 -1
- package/src/core/voice.ts +3 -1
- package/src/main.ts +1 -1
- package/src/migrations.ts +20 -20
- package/src/modes/interactive/components/custom-editor.ts +7 -0
- package/src/modes/interactive/components/history-search.ts +158 -0
- package/src/modes/interactive/controllers/command-controller.ts +527 -0
- package/src/modes/interactive/controllers/event-controller.ts +340 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +600 -0
- package/src/modes/interactive/controllers/input-controller.ts +585 -0
- package/src/modes/interactive/controllers/selector-controller.ts +585 -0
- package/src/modes/interactive/interactive-mode.ts +370 -3115
- package/src/modes/interactive/theme/theme.ts +5 -5
- package/src/modes/interactive/types.ts +189 -0
- package/src/modes/interactive/utils/ui-helpers.ts +449 -0
- package/src/modes/interactive/utils/voice-manager.ts +96 -0
- package/src/prompts/{explore.md → agents/explore.md} +7 -5
- package/src/prompts/agents/frontmatter.md +7 -0
- package/src/prompts/{plan.md → agents/plan.md} +3 -3
- package/src/prompts/{task.md → agents/task.md} +1 -1
- package/src/prompts/review-request.md +44 -8
- package/src/prompts/system/custom-system-prompt.md +80 -0
- package/src/prompts/system/file-operations.md +12 -0
- package/src/prompts/system/system-prompt.md +232 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +9 -3
- package/src/core/tools/rulebook.ts +0 -132
- package/src/prompts/system-prompt.md +0 -43
- package/src/prompts/title-system.md +0 -8
- /package/src/prompts/{architect-plan.md → agents/architect-plan.md} +0 -0
- /package/src/prompts/{implement-with-critic.md → agents/implement-with-critic.md} +0 -0
- /package/src/prompts/{implement.md → agents/implement.md} +0 -0
- /package/src/prompts/{init.md → agents/init.md} +0 -0
- /package/src/prompts/{reviewer.md → agents/reviewer.md} +0 -0
- /package/src/prompts/{branch-summary-preamble.md → compaction/branch-summary-preamble.md} +0 -0
- /package/src/prompts/{branch-summary.md → compaction/branch-summary.md} +0 -0
- /package/src/prompts/{compaction-summary.md → compaction/compaction-summary.md} +0 -0
- /package/src/prompts/{compaction-turn-prefix.md → compaction/compaction-turn-prefix.md} +0 -0
- /package/src/prompts/{compaction-update-summary.md → compaction/compaction-update-summary.md} +0 -0
- /package/src/prompts/{summarization-system.md → system/summarization-system.md} +0 -0
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import reviewRequestTemplate from "../../../../prompts/review-request.md" with { type: "text" };
|
|
16
16
|
import type { HookCommandContext } from "../../../hooks/types";
|
|
17
|
+
import { renderPromptTemplate } from "../../../prompt-templates";
|
|
17
18
|
import type { CustomCommand, CustomCommandAPI } from "../../types";
|
|
18
19
|
|
|
19
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -164,20 +165,6 @@ function getRecommendedAgentCount(stats: DiffStats): number {
|
|
|
164
165
|
return Math.min(16, fileCount);
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
/**
|
|
168
|
-
* Format diff stats as a markdown table for the prompt.
|
|
169
|
-
*/
|
|
170
|
-
function formatFileTable(files: FileDiff[]): string {
|
|
171
|
-
if (files.length === 0) return "_No files to review._";
|
|
172
|
-
|
|
173
|
-
const rows = files.map((f) => {
|
|
174
|
-
const ext = getFileExt(f.path);
|
|
175
|
-
return `| ${f.path} | +${f.linesAdded}/-${f.linesRemoved} | ${ext} |`;
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
return `| File | +/- | Type |\n|------|-----|------|\n${rows.join("\n")}`;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
168
|
/**
|
|
182
169
|
* Extract first N lines of actual diff content (excluding headers) for preview.
|
|
183
170
|
*/
|
|
@@ -203,33 +190,6 @@ function getDiffPreview(hunks: string, maxLines: number): string {
|
|
|
203
190
|
return contentLines.join("\n");
|
|
204
191
|
}
|
|
205
192
|
|
|
206
|
-
/**
|
|
207
|
-
* Format condensed diff previews for large changesets.
|
|
208
|
-
*/
|
|
209
|
-
function formatDiffPreviews(files: FileDiff[], linesPerFile: number): string {
|
|
210
|
-
const parts: string[] = [];
|
|
211
|
-
|
|
212
|
-
for (const f of files) {
|
|
213
|
-
const preview = getDiffPreview(f.hunks, linesPerFile);
|
|
214
|
-
if (preview.trim()) {
|
|
215
|
-
parts.push(`#### ${f.path}\n\`\`\`diff\n${preview}\n\`\`\``);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return parts.join("\n\n");
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Format excluded files list for the prompt.
|
|
224
|
-
*/
|
|
225
|
-
function formatExcluded(excluded: DiffStats["excluded"]): string {
|
|
226
|
-
if (excluded.length === 0) return "";
|
|
227
|
-
|
|
228
|
-
const items = excluded.map((e) => `- \`${e.path}\` (+${e.linesAdded}/-${e.linesRemoved}) — ${e.reason}`);
|
|
229
|
-
|
|
230
|
-
return `### Excluded Files (${excluded.length})\n\n${items.join("\n")}`;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
193
|
// Thresholds for diff inclusion
|
|
234
194
|
const MAX_DIFF_CHARS = 50_000; // Don't include diff above this
|
|
235
195
|
const MAX_FILES_FOR_INLINE_DIFF = 20; // Don't include diff if more files than this
|
|
@@ -241,59 +201,27 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): str
|
|
|
241
201
|
const agentCount = getRecommendedAgentCount(stats);
|
|
242
202
|
const skipDiff = rawDiff.length > MAX_DIFF_CHARS || stats.files.length > MAX_FILES_FOR_INLINE_DIFF;
|
|
243
203
|
const totalLines = stats.totalAdded + stats.totalRemoved;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
<diff>
|
|
267
|
-
${rawDiff.trim()}
|
|
268
|
-
</diff>`;
|
|
269
|
-
} else {
|
|
270
|
-
const linesPerFile = Math.max(5, Math.floor(100 / stats.files.length));
|
|
271
|
-
diffSection = `### Diff Previews
|
|
272
|
-
|
|
273
|
-
_Full diff too large (${stats.files.length} files). Showing first ~${linesPerFile} lines per file. Reviewers should fetch full diffs for assigned files._
|
|
274
|
-
|
|
275
|
-
${formatDiffPreviews(stats.files, linesPerFile)}`;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Build diff instruction
|
|
279
|
-
const diffInstruction = skipDiff
|
|
280
|
-
? "Run `git diff` or `git show` to get the diff for assigned files"
|
|
281
|
-
: "Use the diff hunks provided below (don't re-run git diff)";
|
|
282
|
-
|
|
283
|
-
// Replace template variables
|
|
284
|
-
return reviewRequestTemplate
|
|
285
|
-
.replace("{MODE}", mode)
|
|
286
|
-
.replace("{FILE_COUNT}", String(stats.files.length))
|
|
287
|
-
.replace("{LINES_ADDED}", String(stats.totalAdded))
|
|
288
|
-
.replace("{LINES_REMOVED}", String(stats.totalRemoved))
|
|
289
|
-
.replace("{FILE_TABLE}", formatFileTable(stats.files))
|
|
290
|
-
.replace("{EXCLUDED_SECTION}", stats.excluded.length > 0 ? formatExcluded(stats.excluded) : "")
|
|
291
|
-
.replace("{DISTRIBUTION_GUIDANCE}", distributionGuidance)
|
|
292
|
-
.replace("{GROUPING_GUIDANCE}", groupingGuidance)
|
|
293
|
-
.replace("{DIFF_INSTRUCTION}", diffInstruction)
|
|
294
|
-
.replace("{DIFF_SECTION}", diffSection)
|
|
295
|
-
.replace(/\n{3,}/g, "\n\n") // Collapse multiple blank lines
|
|
296
|
-
.trim();
|
|
204
|
+
const linesPerFile = skipDiff ? Math.max(5, Math.floor(100 / stats.files.length)) : 0;
|
|
205
|
+
|
|
206
|
+
const filesWithExt = stats.files.map((f) => ({
|
|
207
|
+
...f,
|
|
208
|
+
ext: getFileExt(f.path),
|
|
209
|
+
hunksPreview: skipDiff ? getDiffPreview(f.hunks, linesPerFile) : "",
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
return renderPromptTemplate(reviewRequestTemplate, {
|
|
213
|
+
mode,
|
|
214
|
+
files: filesWithExt,
|
|
215
|
+
excluded: stats.excluded,
|
|
216
|
+
totalAdded: stats.totalAdded,
|
|
217
|
+
totalRemoved: stats.totalRemoved,
|
|
218
|
+
totalLines,
|
|
219
|
+
agentCount,
|
|
220
|
+
multiAgent: agentCount > 1,
|
|
221
|
+
skipDiff,
|
|
222
|
+
rawDiff: rawDiff.trim(),
|
|
223
|
+
linesPerFile,
|
|
224
|
+
});
|
|
297
225
|
}
|
|
298
226
|
|
|
299
227
|
export function createReviewCommand(api: CustomCommandAPI): CustomCommand {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom share script loader.
|
|
3
|
+
*
|
|
4
|
+
* Allows users to define a custom share handler at ~/.omp/agent/share.ts
|
|
5
|
+
* that will be used instead of the default GitHub Gist sharing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { getAgentDir } from "../config";
|
|
11
|
+
|
|
12
|
+
export interface CustomShareResult {
|
|
13
|
+
/** URL to display/open (optional - script may handle everything itself) */
|
|
14
|
+
url?: string;
|
|
15
|
+
/** Additional message to show the user */
|
|
16
|
+
message?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type CustomShareFn = (htmlPath: string) => Promise<CustomShareResult | string | undefined>;
|
|
20
|
+
|
|
21
|
+
interface LoadedCustomShare {
|
|
22
|
+
path: string;
|
|
23
|
+
fn: CustomShareFn;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SHARE_SCRIPT_CANDIDATES = ["share.ts", "share.js", "share.mjs"];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the path to the custom share script if it exists.
|
|
30
|
+
*/
|
|
31
|
+
export function getCustomSharePath(): string | null {
|
|
32
|
+
const agentDir = getAgentDir();
|
|
33
|
+
|
|
34
|
+
for (const candidate of SHARE_SCRIPT_CANDIDATES) {
|
|
35
|
+
const scriptPath = join(agentDir, candidate);
|
|
36
|
+
if (existsSync(scriptPath)) {
|
|
37
|
+
return scriptPath;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load the custom share script if it exists.
|
|
46
|
+
*/
|
|
47
|
+
export async function loadCustomShare(): Promise<LoadedCustomShare | null> {
|
|
48
|
+
const scriptPath = getCustomSharePath();
|
|
49
|
+
if (!scriptPath) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const module = await import(scriptPath);
|
|
55
|
+
const fn = module.default;
|
|
56
|
+
|
|
57
|
+
if (typeof fn !== "function") {
|
|
58
|
+
throw new Error("share script must export a default function");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { path: scriptPath, fn };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
throw new Error(`Failed to load share script: ${message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getAgentDir } from "../config";
|
|
4
|
+
import { logger } from "./logger";
|
|
5
|
+
|
|
6
|
+
export interface HistoryEntry {
|
|
7
|
+
id: number;
|
|
8
|
+
prompt: string;
|
|
9
|
+
created_at: number;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Statement = ReturnType<Database["prepare"]>;
|
|
14
|
+
|
|
15
|
+
type HistoryRow = {
|
|
16
|
+
id: number;
|
|
17
|
+
prompt: string;
|
|
18
|
+
created_at: number;
|
|
19
|
+
cwd: string | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class HistoryStorage {
|
|
23
|
+
private db: Database;
|
|
24
|
+
private static instance?: HistoryStorage;
|
|
25
|
+
|
|
26
|
+
// Prepared statements
|
|
27
|
+
private insertStmt: Statement;
|
|
28
|
+
private recentStmt: Statement;
|
|
29
|
+
private searchStmt: Statement;
|
|
30
|
+
private lastPromptStmt: Statement;
|
|
31
|
+
|
|
32
|
+
// In-memory cache of last prompt to avoid sync DB reads on add
|
|
33
|
+
private lastPromptCache: string | null = null;
|
|
34
|
+
|
|
35
|
+
private constructor(dbPath: string) {
|
|
36
|
+
this.ensureDir(dbPath);
|
|
37
|
+
|
|
38
|
+
this.db = new Database(dbPath);
|
|
39
|
+
|
|
40
|
+
const hasFts = this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='history_fts'").get();
|
|
41
|
+
|
|
42
|
+
this.db.exec(`
|
|
43
|
+
PRAGMA journal_mode=WAL;
|
|
44
|
+
PRAGMA synchronous=NORMAL;
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS history (
|
|
47
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
48
|
+
prompt TEXT NOT NULL,
|
|
49
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
50
|
+
cwd TEXT
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_history_created_at ON history(created_at DESC);
|
|
53
|
+
|
|
54
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS history_fts USING fts5(prompt, content='history', content_rowid='id');
|
|
55
|
+
|
|
56
|
+
CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
|
|
57
|
+
INSERT INTO history_fts(rowid, prompt) VALUES (new.id, new.prompt);
|
|
58
|
+
END;
|
|
59
|
+
`);
|
|
60
|
+
|
|
61
|
+
if (!hasFts) {
|
|
62
|
+
try {
|
|
63
|
+
this.db.run("INSERT INTO history_fts(history_fts) VALUES('rebuild')");
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.warn("HistoryStorage FTS rebuild failed", { error: String(error) });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.insertStmt = this.db.prepare("INSERT INTO history (prompt, cwd) VALUES (?, ?)");
|
|
70
|
+
this.recentStmt = this.db.prepare(
|
|
71
|
+
"SELECT id, prompt, created_at, cwd FROM history ORDER BY created_at DESC, id DESC LIMIT ?",
|
|
72
|
+
);
|
|
73
|
+
this.searchStmt = this.db.prepare(
|
|
74
|
+
"SELECT h.id, h.prompt, h.created_at, h.cwd FROM history_fts f JOIN history h ON h.id = f.rowid WHERE history_fts MATCH ? ORDER BY h.created_at DESC, h.id DESC LIMIT ?",
|
|
75
|
+
);
|
|
76
|
+
this.lastPromptStmt = this.db.prepare("SELECT prompt FROM history ORDER BY id DESC LIMIT 1");
|
|
77
|
+
|
|
78
|
+
const last = this.lastPromptStmt.get() as { prompt?: string } | undefined;
|
|
79
|
+
this.lastPromptCache = last?.prompt ?? null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static open(dbPath: string = join(getAgentDir(), "history.db")): HistoryStorage {
|
|
83
|
+
if (!HistoryStorage.instance) {
|
|
84
|
+
HistoryStorage.instance = new HistoryStorage(dbPath);
|
|
85
|
+
}
|
|
86
|
+
return HistoryStorage.instance;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
add(prompt: string, cwd?: string): void {
|
|
90
|
+
const trimmed = prompt.trim();
|
|
91
|
+
if (!trimmed) return;
|
|
92
|
+
if (this.lastPromptCache === trimmed) return;
|
|
93
|
+
|
|
94
|
+
this.lastPromptCache = trimmed;
|
|
95
|
+
|
|
96
|
+
setImmediate(() => {
|
|
97
|
+
try {
|
|
98
|
+
this.insertStmt.run(trimmed, cwd ?? null);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
logger.error("HistoryStorage add failed", { error: String(error) });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getRecent(limit: number): HistoryEntry[] {
|
|
106
|
+
const safeLimit = this.normalizeLimit(limit);
|
|
107
|
+
if (safeLimit === 0) return [];
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const rows = this.recentStmt.all(safeLimit) as HistoryRow[];
|
|
111
|
+
return rows.map((row) => this.toEntry(row));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.error("HistoryStorage getRecent failed", { error: String(error) });
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
search(query: string, limit: number): HistoryEntry[] {
|
|
119
|
+
const safeLimit = this.normalizeLimit(limit);
|
|
120
|
+
if (safeLimit === 0) return [];
|
|
121
|
+
|
|
122
|
+
const ftsQuery = this.buildFtsQuery(query);
|
|
123
|
+
if (!ftsQuery) return [];
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const rows = this.searchStmt.all(ftsQuery, safeLimit) as HistoryRow[];
|
|
127
|
+
return rows.map((row) => this.toEntry(row));
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logger.error("HistoryStorage search failed", { error: String(error) });
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private ensureDir(dbPath: string): void {
|
|
135
|
+
const dir = dirname(dbPath);
|
|
136
|
+
const result = Bun.spawnSync(["mkdir", "-p", dir]);
|
|
137
|
+
if (result.exitCode !== 0) {
|
|
138
|
+
const stderr = result.stderr ? new TextDecoder().decode(result.stderr) : "";
|
|
139
|
+
throw new Error(`Failed to create history directory: ${dir} ${stderr}`.trim());
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private normalizeLimit(limit: number): number {
|
|
144
|
+
if (!Number.isFinite(limit)) return 0;
|
|
145
|
+
const clamped = Math.max(0, Math.floor(limit));
|
|
146
|
+
return Math.min(clamped, 1000);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private buildFtsQuery(query: string): string | null {
|
|
150
|
+
const tokens = query
|
|
151
|
+
.trim()
|
|
152
|
+
.split(/\s+/)
|
|
153
|
+
.map((token) => token.trim())
|
|
154
|
+
.filter(Boolean);
|
|
155
|
+
|
|
156
|
+
if (tokens.length === 0) return null;
|
|
157
|
+
|
|
158
|
+
return tokens
|
|
159
|
+
.map((token) => {
|
|
160
|
+
const escaped = token.replace(/"/g, '""');
|
|
161
|
+
return `"${escaped}"*`;
|
|
162
|
+
})
|
|
163
|
+
.join(" ");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private toEntry(row: HistoryRow): HistoryEntry {
|
|
167
|
+
return {
|
|
168
|
+
id: row.id,
|
|
169
|
+
prompt: row.prompt,
|
|
170
|
+
created_at: row.created_at,
|
|
171
|
+
cwd: row.cwd ?? undefined,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/core/index.ts
CHANGED
package/src/core/keybindings.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type AppAction =
|
|
|
26
26
|
| "expandTools"
|
|
27
27
|
| "toggleThinking"
|
|
28
28
|
| "externalEditor"
|
|
29
|
+
| "historySearch"
|
|
29
30
|
| "followUp"
|
|
30
31
|
| "dequeue";
|
|
31
32
|
|
|
@@ -53,6 +54,7 @@ export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
|
|
|
53
54
|
cycleModelForward: "ctrl+p",
|
|
54
55
|
cycleModelBackward: "shift+ctrl+p",
|
|
55
56
|
selectModel: "ctrl+l",
|
|
57
|
+
historySearch: "ctrl+r",
|
|
56
58
|
expandTools: "ctrl+o",
|
|
57
59
|
toggleThinking: "ctrl+t",
|
|
58
60
|
externalEditor: "ctrl+g",
|
|
@@ -78,6 +80,7 @@ const APP_ACTIONS: AppAction[] = [
|
|
|
78
80
|
"cycleModelForward",
|
|
79
81
|
"cycleModelBackward",
|
|
80
82
|
"selectModel",
|
|
83
|
+
"historySearch",
|
|
81
84
|
"expandTools",
|
|
82
85
|
"toggleThinking",
|
|
83
86
|
"externalEditor",
|