@oh-my-pi/pi-coding-agent 3.14.0 → 3.15.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 +79 -0
- package/docs/theme.md +38 -5
- package/examples/sdk/11-sessions.ts +2 -2
- package/package.json +7 -4
- package/src/cli/file-processor.ts +51 -2
- package/src/cli/plugin-cli.ts +25 -19
- package/src/cli/update-cli.ts +4 -3
- package/src/core/agent-session.ts +31 -4
- package/src/core/compaction/branch-summarization.ts +4 -32
- package/src/core/compaction/compaction.ts +6 -84
- package/src/core/compaction/utils.ts +2 -3
- package/src/core/custom-tools/types.ts +2 -0
- package/src/core/export-html/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +0 -1
- package/src/core/hooks/types.ts +2 -2
- package/src/core/plugins/doctor.ts +9 -1
- package/src/core/sdk.ts +2 -1
- package/src/core/session-manager.ts +518 -40
- package/src/core/settings-manager.ts +174 -0
- package/src/core/system-prompt.ts +9 -14
- package/src/core/title-generator.ts +2 -8
- package/src/core/tools/ask.ts +19 -37
- package/src/core/tools/bash.ts +2 -37
- package/src/core/tools/edit.ts +2 -9
- package/src/core/tools/exa/render.ts +52 -48
- package/src/core/tools/find.ts +10 -8
- package/src/core/tools/grep.ts +45 -17
- package/src/core/tools/ls.ts +22 -2
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +3 -0
- package/src/core/tools/lsp/index.ts +107 -55
- package/src/core/tools/lsp/render.ts +192 -79
- package/src/core/tools/lsp/types.ts +27 -0
- package/src/core/tools/lsp/utils.ts +62 -22
- package/src/core/tools/notebook.ts +9 -1
- package/src/core/tools/output.ts +37 -14
- package/src/core/tools/read.ts +349 -34
- package/src/core/tools/renderers.ts +290 -89
- package/src/core/tools/review.ts +12 -5
- package/src/core/tools/task/agents.ts +5 -5
- package/src/core/tools/task/commands.ts +3 -3
- package/src/core/tools/task/executor.ts +33 -1
- package/src/core/tools/task/index.ts +93 -6
- package/src/core/tools/task/render.ts +147 -66
- package/src/core/tools/task/types.ts +14 -9
- package/src/core/tools/web-fetch.ts +242 -103
- package/src/core/tools/web-search/index.ts +64 -20
- package/src/core/tools/web-search/providers/exa.ts +68 -172
- package/src/core/tools/web-search/render.ts +264 -74
- package/src/core/tools/write.ts +2 -8
- package/src/main.ts +10 -6
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +9 -4
- package/src/modes/interactive/components/bash-execution.ts +6 -3
- package/src/modes/interactive/components/branch-summary-message.ts +1 -1
- package/src/modes/interactive/components/compaction-summary-message.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +4 -5
- package/src/modes/interactive/components/extensions/extension-list.ts +18 -16
- package/src/modes/interactive/components/extensions/inspector-panel.ts +8 -8
- package/src/modes/interactive/components/hook-message.ts +2 -2
- package/src/modes/interactive/components/hook-selector.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +22 -9
- package/src/modes/interactive/components/oauth-selector.ts +20 -4
- package/src/modes/interactive/components/plugin-settings.ts +4 -2
- package/src/modes/interactive/components/session-selector.ts +9 -6
- package/src/modes/interactive/components/settings-defs.ts +285 -1
- package/src/modes/interactive/components/settings-selector.ts +176 -3
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +170 -223
- package/src/modes/interactive/components/tool-execution.ts +446 -211
- package/src/modes/interactive/components/tree-selector.ts +17 -6
- package/src/modes/interactive/components/ttsr-notification.ts +4 -4
- package/src/modes/interactive/components/welcome.ts +27 -19
- package/src/modes/interactive/interactive-mode.ts +98 -13
- package/src/modes/interactive/theme/dark.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +3 -2
- package/src/modes/interactive/theme/theme-schema.json +120 -4
- package/src/modes/interactive/theme/theme.ts +1228 -14
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/init.md +30 -0
- package/src/{core/tools/task/bundled-agents → prompts}/reviewer.md +6 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/{core/tools/task/bundled-agents → prompts}/task.md +2 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/commands/init.md +0 -20
- /package/src/{core/tools/task/bundled-commands → prompts}/architect-plan.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/browser.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/explore.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement-with-critic.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/plan.md +0 -0
|
@@ -10,17 +10,11 @@ import type { Theme } from "../../../modes/interactive/theme/theme";
|
|
|
10
10
|
import type { RenderResultOptions } from "../../custom-tools/types";
|
|
11
11
|
import type { WebSearchResponse } from "./types";
|
|
12
12
|
|
|
13
|
-
// Tree formatting constants
|
|
14
|
-
const TREE_MID = "├─";
|
|
15
|
-
const TREE_END = "└─";
|
|
16
|
-
const TREE_PIPE = "│";
|
|
17
|
-
const TREE_SPACE = " ";
|
|
18
|
-
const TREE_HOOK = "⎿";
|
|
19
|
-
|
|
20
13
|
/** Truncate text to max length with ellipsis */
|
|
21
|
-
export function truncate(text: string, maxLen: number): string {
|
|
14
|
+
export function truncate(text: string, maxLen: number, ellipsis: string): string {
|
|
22
15
|
if (text.length <= maxLen) return text;
|
|
23
|
-
|
|
16
|
+
const sliceLen = Math.max(0, maxLen - ellipsis.length);
|
|
17
|
+
return `${text.slice(0, sliceLen)}${ellipsis}`;
|
|
24
18
|
}
|
|
25
19
|
|
|
26
20
|
/** Extract domain from URL */
|
|
@@ -51,9 +45,55 @@ export function formatAge(ageSeconds: number | null | undefined): string {
|
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
/** Get first N lines of text as preview */
|
|
54
|
-
export function getPreviewLines(text: string, maxLines: number, maxLineLen: number): string[] {
|
|
48
|
+
export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis: string): string[] {
|
|
55
49
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
56
|
-
return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen));
|
|
50
|
+
return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen, ellipsis));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const MAX_COLLAPSED_ANSWER_LINES = 3;
|
|
54
|
+
const MAX_EXPANDED_ANSWER_LINES = 12;
|
|
55
|
+
const MAX_ANSWER_LINE_LEN = 110;
|
|
56
|
+
const MAX_SNIPPET_LINES = 2;
|
|
57
|
+
const MAX_SNIPPET_LINE_LEN = 110;
|
|
58
|
+
const MAX_RELATED_QUESTIONS = 6;
|
|
59
|
+
const MAX_QUERY_PREVIEW = 2;
|
|
60
|
+
const MAX_QUERY_LEN = 90;
|
|
61
|
+
const MAX_REQUEST_ID_LEN = 36;
|
|
62
|
+
|
|
63
|
+
function formatCount(label: string, count: number): string {
|
|
64
|
+
const safeCount = Number.isFinite(count) ? count : 0;
|
|
65
|
+
return `${safeCount} ${label}${safeCount === 1 ? "" : "s"}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderFallbackText(contentText: string, expanded: boolean, theme: Theme): Component {
|
|
69
|
+
const lines = contentText.split("\n").filter((line) => line.trim());
|
|
70
|
+
const maxLines = expanded ? lines.length : 6;
|
|
71
|
+
const displayLines = lines.slice(0, maxLines).map((line) => truncate(line.trim(), 110, theme.format.ellipsis));
|
|
72
|
+
const remaining = lines.length - displayLines.length;
|
|
73
|
+
|
|
74
|
+
const headerIcon = theme.fg("warning", theme.status.warning);
|
|
75
|
+
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
76
|
+
let text = `${headerIcon} ${theme.fg("toolTitle", "Web Search")}${expandHint}`;
|
|
77
|
+
|
|
78
|
+
if (displayLines.length === 0) {
|
|
79
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", "No response data")}`;
|
|
80
|
+
return new Text(text, 0, 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
84
|
+
const isLast = i === displayLines.length - 1 && remaining === 0;
|
|
85
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
86
|
+
text += `\n ${theme.fg("dim", branch)} ${theme.fg("dim", displayLines[i])}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!expanded && remaining > 0) {
|
|
90
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
91
|
+
"muted",
|
|
92
|
+
`${theme.format.ellipsis} ${remaining} more line${remaining === 1 ? "" : "s"}`,
|
|
93
|
+
)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return new Text(text, 0, 0);
|
|
57
97
|
}
|
|
58
98
|
|
|
59
99
|
export interface WebSearchRenderDetails {
|
|
@@ -75,95 +115,245 @@ export function renderWebSearchResult(
|
|
|
75
115
|
return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
76
116
|
}
|
|
77
117
|
|
|
118
|
+
const rawText = result.content?.find((block) => block.type === "text")?.text?.trim() ?? "";
|
|
78
119
|
const response = details?.response;
|
|
79
120
|
if (!response) {
|
|
80
|
-
return
|
|
121
|
+
return renderFallbackText(rawText, expanded, theme);
|
|
81
122
|
}
|
|
82
123
|
|
|
83
|
-
const sources = response.sources
|
|
124
|
+
const sources = Array.isArray(response.sources) ? response.sources : [];
|
|
84
125
|
const sourceCount = sources.length;
|
|
85
|
-
const
|
|
126
|
+
const citations = Array.isArray(response.citations) ? response.citations : [];
|
|
127
|
+
const citationCount = citations.length;
|
|
128
|
+
const related = Array.isArray(response.relatedQuestions)
|
|
129
|
+
? response.relatedQuestions.filter((item) => typeof item === "string")
|
|
130
|
+
: [];
|
|
131
|
+
const relatedCount = related.length;
|
|
132
|
+
const searchQueries = Array.isArray(response.searchQueries)
|
|
133
|
+
? response.searchQueries.filter((item) => typeof item === "string")
|
|
134
|
+
: [];
|
|
86
135
|
const provider = response.provider;
|
|
87
136
|
|
|
88
|
-
// Build header:
|
|
89
|
-
const
|
|
137
|
+
// Build header: status icon Web Search (provider) · counts
|
|
138
|
+
const providerLabel =
|
|
139
|
+
provider === "anthropic"
|
|
140
|
+
? "Anthropic"
|
|
141
|
+
: provider === "perplexity"
|
|
142
|
+
? "Perplexity"
|
|
143
|
+
: provider === "exa"
|
|
144
|
+
? "Exa"
|
|
145
|
+
: "Unknown";
|
|
146
|
+
const headerIcon =
|
|
147
|
+
sourceCount > 0 ? theme.fg("success", theme.status.success) : theme.fg("warning", theme.status.warning);
|
|
90
148
|
const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
|
|
91
|
-
|
|
92
|
-
let text = `${icon} ${theme.fg("toolTitle", "Web Search")} ${theme.fg("dim", `(${providerLabel})`)} · ${theme.fg(
|
|
149
|
+
let text = `${headerIcon} ${theme.fg("toolTitle", "Web Search")} ${theme.fg("dim", `(${providerLabel})`)}${theme.sep.dot}${theme.fg(
|
|
93
150
|
"dim",
|
|
94
|
-
|
|
151
|
+
formatCount("source", sourceCount),
|
|
95
152
|
)}${expandHint}`;
|
|
96
153
|
|
|
97
154
|
// Get answer text
|
|
98
|
-
const
|
|
155
|
+
const answerText = typeof response.answer === "string" ? response.answer.trim() : "";
|
|
156
|
+
const contentText = answerText || rawText;
|
|
157
|
+
const totalAnswerLines = contentText ? contentText.split("\n").filter((l) => l.trim()).length : 0;
|
|
158
|
+
const answerLimit = expanded ? MAX_EXPANDED_ANSWER_LINES : MAX_COLLAPSED_ANSWER_LINES;
|
|
159
|
+
const answerPreview = contentText
|
|
160
|
+
? getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN, theme.format.ellipsis)
|
|
161
|
+
: [];
|
|
99
162
|
|
|
100
163
|
if (!expanded) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
for (const line of previewLines) {
|
|
104
|
-
text += `\n ${theme.fg("dim", TREE_PIPE)} ${theme.fg("dim", line)}`;
|
|
105
|
-
}
|
|
106
|
-
const totalLines = contentText.split("\n").filter((l) => l.trim()).length;
|
|
107
|
-
if (totalLines > 3) {
|
|
108
|
-
text += `\n ${theme.fg("dim", TREE_PIPE)} ${theme.fg("muted", `… ${totalLines - 3} more lines`)}`;
|
|
109
|
-
}
|
|
164
|
+
const answerTitle = `${theme.fg("accent", theme.status.info)} ${theme.fg("accent", "Answer")}`;
|
|
165
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${answerTitle}`;
|
|
110
166
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg(
|
|
167
|
+
if (answerPreview.length === 0) {
|
|
168
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${theme.fg("dim", `${theme.tree.hook} `)}${theme.fg(
|
|
114
169
|
"muted",
|
|
115
|
-
|
|
170
|
+
"No answer text returned",
|
|
116
171
|
)}`;
|
|
172
|
+
} else {
|
|
173
|
+
for (const line of answerPreview) {
|
|
174
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${theme.fg("dim", `${theme.tree.hook} `)}${theme.fg(
|
|
175
|
+
"dim",
|
|
176
|
+
line,
|
|
177
|
+
)}`;
|
|
178
|
+
}
|
|
117
179
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
180
|
+
|
|
181
|
+
const remaining = totalAnswerLines - answerPreview.length;
|
|
182
|
+
if (remaining > 0) {
|
|
183
|
+
text += `\n ${theme.fg("dim", theme.tree.vertical)} ${theme.fg("dim", `${theme.tree.hook} `)}${theme.fg(
|
|
184
|
+
"muted",
|
|
185
|
+
`${theme.format.ellipsis} ${remaining} more line${remaining === 1 ? "" : "s"}`,
|
|
186
|
+
)}`;
|
|
123
187
|
}
|
|
124
188
|
|
|
125
|
-
|
|
126
|
-
|
|
189
|
+
const summary = [
|
|
190
|
+
formatCount("source", sourceCount),
|
|
191
|
+
formatCount("citation", citationCount),
|
|
192
|
+
formatCount("related", relatedCount),
|
|
193
|
+
].join(theme.sep.dot);
|
|
194
|
+
text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", summary)}`;
|
|
195
|
+
return new Text(text, 0, 0);
|
|
196
|
+
}
|
|
127
197
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
198
|
+
const answerLines = answerPreview.length > 0 ? answerPreview : ["No answer text returned"];
|
|
199
|
+
const answerSectionLines = answerLines.map((line) =>
|
|
200
|
+
line === "No answer text returned" ? theme.fg("muted", line) : theme.fg("text", line),
|
|
201
|
+
);
|
|
202
|
+
const remainingAnswer = totalAnswerLines - answerPreview.length;
|
|
203
|
+
if (remainingAnswer > 0) {
|
|
204
|
+
answerSectionLines.push(
|
|
205
|
+
theme.fg("muted", `${theme.format.ellipsis} ${remainingAnswer} more line${remainingAnswer === 1 ? "" : "s"}`),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
132
208
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
209
|
+
const sourceLines: string[] = [];
|
|
210
|
+
if (sourceCount === 0) {
|
|
211
|
+
sourceLines.push(theme.fg("muted", "No sources returned"));
|
|
212
|
+
} else {
|
|
213
|
+
for (const src of sources) {
|
|
214
|
+
const titleText =
|
|
215
|
+
typeof src.title === "string" && src.title.trim()
|
|
216
|
+
? src.title
|
|
217
|
+
: typeof src.url === "string" && src.url.trim()
|
|
218
|
+
? src.url
|
|
219
|
+
: "Untitled";
|
|
220
|
+
const title = truncate(titleText, 70, theme.format.ellipsis);
|
|
221
|
+
const url = typeof src.url === "string" ? src.url : "";
|
|
222
|
+
const domain = url ? getDomain(url) : "";
|
|
223
|
+
const age = formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
|
|
224
|
+
const metaParts: string[] = [];
|
|
225
|
+
if (domain) {
|
|
226
|
+
metaParts.push(theme.fg("dim", `(${domain})`));
|
|
227
|
+
}
|
|
228
|
+
if (typeof src.author === "string" && src.author.trim()) {
|
|
229
|
+
metaParts.push(theme.fg("muted", src.author));
|
|
230
|
+
}
|
|
231
|
+
if (age) {
|
|
232
|
+
metaParts.push(theme.fg("muted", age));
|
|
233
|
+
}
|
|
234
|
+
const metaSep = theme.fg("dim", theme.sep.dot);
|
|
235
|
+
const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
|
|
236
|
+
sourceLines.push(`${theme.fg("accent", title)}${metaSuffix}`);
|
|
139
237
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
238
|
+
const snippetText = typeof src.snippet === "string" ? src.snippet : "";
|
|
239
|
+
if (snippetText.trim()) {
|
|
240
|
+
const snippetLines = getPreviewLines(
|
|
241
|
+
snippetText,
|
|
242
|
+
MAX_SNIPPET_LINES,
|
|
243
|
+
MAX_SNIPPET_LINE_LEN,
|
|
244
|
+
theme.format.ellipsis,
|
|
245
|
+
);
|
|
246
|
+
for (const snippetLine of snippetLines) {
|
|
247
|
+
sourceLines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
145
250
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
`(${domain})`,
|
|
149
|
-
)}${agePart}`;
|
|
150
|
-
text += `\n ${theme.fg("dim", indent)} ${theme.fg("dim", `${cont} ${TREE_HOOK} `)}${theme.fg(
|
|
151
|
-
"mdLinkUrl",
|
|
152
|
-
src.url,
|
|
153
|
-
)}`;
|
|
251
|
+
if (url) {
|
|
252
|
+
sourceLines.push(theme.fg("mdLinkUrl", url));
|
|
154
253
|
}
|
|
155
254
|
}
|
|
255
|
+
}
|
|
156
256
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
257
|
+
const relatedLines: string[] = [];
|
|
258
|
+
if (relatedCount === 0) {
|
|
259
|
+
relatedLines.push(theme.fg("muted", "No related questions"));
|
|
260
|
+
} else {
|
|
261
|
+
const maxRelated = Math.min(MAX_RELATED_QUESTIONS, related.length);
|
|
262
|
+
for (let i = 0; i < maxRelated; i++) {
|
|
263
|
+
relatedLines.push(theme.fg("muted", `${theme.format.dash} ${related[i]}`));
|
|
264
|
+
}
|
|
265
|
+
if (relatedCount > maxRelated) {
|
|
266
|
+
relatedLines.push(
|
|
267
|
+
theme.fg(
|
|
268
|
+
"muted",
|
|
269
|
+
`${theme.format.ellipsis} ${relatedCount - maxRelated} more question${
|
|
270
|
+
relatedCount - maxRelated === 1 ? "" : "s"
|
|
271
|
+
}`,
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const metaLines: string[] = [];
|
|
278
|
+
metaLines.push(`${theme.fg("muted", "Provider:")} ${theme.fg("text", providerLabel)}`);
|
|
279
|
+
if (response.model) {
|
|
280
|
+
metaLines.push(`${theme.fg("muted", "Model:")} ${theme.fg("text", response.model)}`);
|
|
281
|
+
}
|
|
282
|
+
metaLines.push(`${theme.fg("muted", "Sources:")} ${theme.fg("text", String(sourceCount))}`);
|
|
283
|
+
if (citationCount > 0) {
|
|
284
|
+
metaLines.push(`${theme.fg("muted", "Citations:")} ${theme.fg("text", String(citationCount))}`);
|
|
285
|
+
}
|
|
286
|
+
if (relatedCount > 0) {
|
|
287
|
+
metaLines.push(`${theme.fg("muted", "Related:")} ${theme.fg("text", String(relatedCount))}`);
|
|
288
|
+
}
|
|
289
|
+
if (response.usage) {
|
|
290
|
+
const usageParts: string[] = [];
|
|
291
|
+
if (response.usage.inputTokens !== undefined) usageParts.push(`in ${response.usage.inputTokens}`);
|
|
292
|
+
if (response.usage.outputTokens !== undefined) usageParts.push(`out ${response.usage.outputTokens}`);
|
|
293
|
+
if (response.usage.totalTokens !== undefined) usageParts.push(`total ${response.usage.totalTokens}`);
|
|
294
|
+
if (response.usage.searchRequests !== undefined) usageParts.push(`search ${response.usage.searchRequests}`);
|
|
295
|
+
if (usageParts.length > 0) {
|
|
296
|
+
metaLines.push(`${theme.fg("muted", "Usage:")} ${theme.fg("text", usageParts.join(theme.sep.dot))}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (response.requestId) {
|
|
300
|
+
metaLines.push(
|
|
301
|
+
`${theme.fg("muted", "Request:")} ${theme.fg(
|
|
302
|
+
"text",
|
|
303
|
+
truncate(response.requestId, MAX_REQUEST_ID_LEN, theme.format.ellipsis),
|
|
304
|
+
)}`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (searchQueries.length > 0) {
|
|
308
|
+
metaLines.push(`${theme.fg("muted", "Search queries:")} ${theme.fg("text", String(searchQueries.length))}`);
|
|
309
|
+
const queryPreview = searchQueries.slice(0, MAX_QUERY_PREVIEW);
|
|
310
|
+
for (const q of queryPreview) {
|
|
311
|
+
metaLines.push(theme.fg("muted", `${theme.format.dash} ${truncate(q, MAX_QUERY_LEN, theme.format.ellipsis)}`));
|
|
312
|
+
}
|
|
313
|
+
if (searchQueries.length > MAX_QUERY_PREVIEW) {
|
|
314
|
+
metaLines.push(
|
|
315
|
+
theme.fg(
|
|
316
|
+
"muted",
|
|
317
|
+
`${theme.format.ellipsis} ${searchQueries.length - MAX_QUERY_PREVIEW} more query${
|
|
318
|
+
searchQueries.length - MAX_QUERY_PREVIEW === 1 ? "" : "s"
|
|
319
|
+
}`,
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const sections: Array<{ title: string; icon: string; lines: string[] }> = [
|
|
326
|
+
{
|
|
327
|
+
title: "Answer",
|
|
328
|
+
icon: theme.fg("accent", theme.status.info),
|
|
329
|
+
lines: answerSectionLines,
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
title: "Sources",
|
|
333
|
+
icon: sourceCount > 0 ? theme.fg("success", theme.status.success) : theme.fg("warning", theme.status.warning),
|
|
334
|
+
lines: sourceLines,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
title: "Related",
|
|
338
|
+
icon: relatedCount > 0 ? theme.fg("accent", theme.status.info) : theme.fg("warning", theme.status.warning),
|
|
339
|
+
lines: relatedLines,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
title: "Meta",
|
|
343
|
+
icon: theme.fg("accent", theme.status.info),
|
|
344
|
+
lines: metaLines,
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
for (let i = 0; i < sections.length; i++) {
|
|
349
|
+
const section = sections[i];
|
|
350
|
+
const isLast = i === sections.length - 1;
|
|
351
|
+
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
352
|
+
const indent = isLast ? " " : theme.tree.vertical;
|
|
353
|
+
|
|
354
|
+
text += `\n ${theme.fg("dim", branch)} ${section.icon} ${theme.fg("accent", section.title)}`;
|
|
355
|
+
for (const line of section.lines) {
|
|
356
|
+
text += `\n ${theme.fg("dim", indent)} ${theme.fg("dim", `${theme.tree.hook} `)}${line}`;
|
|
167
357
|
}
|
|
168
358
|
}
|
|
169
359
|
|
|
@@ -176,7 +366,7 @@ export function renderWebSearchCall(
|
|
|
176
366
|
theme: Theme,
|
|
177
367
|
): Component {
|
|
178
368
|
const provider = args.provider ?? "auto";
|
|
179
|
-
const query = truncate(args.query, 80);
|
|
369
|
+
const query = truncate(args.query, 80, theme.format.ellipsis);
|
|
180
370
|
const text = `${theme.fg("toolTitle", "Web Search")} ${theme.fg("dim", `(${provider})`)} ${theme.fg("muted", query)}`;
|
|
181
371
|
return new Text(text, 0, 0);
|
|
182
372
|
}
|
package/src/core/tools/write.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import writeDescription from "../../prompts/tools/write.md" with { type: "text" };
|
|
3
4
|
import { type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "./lsp/index";
|
|
4
5
|
import { resolveToCwd } from "./path-utils";
|
|
5
6
|
|
|
@@ -26,14 +27,7 @@ export function createWriteTool(
|
|
|
26
27
|
return {
|
|
27
28
|
name: "write",
|
|
28
29
|
label: "Write",
|
|
29
|
-
description:
|
|
30
|
-
|
|
31
|
-
Usage:
|
|
32
|
-
- This tool will overwrite the existing file if there is one at the provided path.
|
|
33
|
-
- If this is an existing file, you MUST use the read tool first to read the file's contents. This tool will fail if you did not read the file first.
|
|
34
|
-
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
35
|
-
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
|
36
|
-
- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.`,
|
|
30
|
+
description: writeDescription,
|
|
37
31
|
parameters: writeSchema,
|
|
38
32
|
execute: async (
|
|
39
33
|
_toolCallId: string,
|
package/src/main.ts
CHANGED
|
@@ -176,15 +176,15 @@ function getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager):
|
|
|
176
176
|
return undefined;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
function createSessionManager(parsed: Args, cwd: string): SessionManager | undefined {
|
|
179
|
+
async function createSessionManager(parsed: Args, cwd: string): Promise<SessionManager | undefined> {
|
|
180
180
|
if (parsed.noSession) {
|
|
181
181
|
return SessionManager.inMemory();
|
|
182
182
|
}
|
|
183
183
|
if (parsed.session) {
|
|
184
|
-
return SessionManager.open(parsed.session, parsed.sessionDir);
|
|
184
|
+
return await SessionManager.open(parsed.session, parsed.sessionDir);
|
|
185
185
|
}
|
|
186
186
|
if (parsed.continue) {
|
|
187
|
-
return SessionManager.continueRecent(cwd, parsed.sessionDir);
|
|
187
|
+
return await SessionManager.continueRecent(cwd, parsed.sessionDir);
|
|
188
188
|
}
|
|
189
189
|
// --resume is handled separately (needs picker UI)
|
|
190
190
|
// If --session-dir provided without --continue/--resume, create new session there
|
|
@@ -294,6 +294,10 @@ async function buildSessionOptions(
|
|
|
294
294
|
export async function main(args: string[]) {
|
|
295
295
|
time("start");
|
|
296
296
|
|
|
297
|
+
// Initialize theme early with defaults (CLI commands need symbols)
|
|
298
|
+
// Will be re-initialized with user preferences later
|
|
299
|
+
initTheme();
|
|
300
|
+
|
|
297
301
|
// Handle plugin subcommand before regular parsing
|
|
298
302
|
const pluginCmd = parsePluginArgs(args);
|
|
299
303
|
if (pluginCmd) {
|
|
@@ -385,7 +389,7 @@ export async function main(args: string[]) {
|
|
|
385
389
|
settingsManager.applyOverrides({ modelRoles: roleOverrides });
|
|
386
390
|
}
|
|
387
391
|
|
|
388
|
-
initTheme(settingsManager.getTheme(), isInteractive);
|
|
392
|
+
initTheme(settingsManager.getTheme(), isInteractive, settingsManager.getSymbolPreset());
|
|
389
393
|
time("initTheme");
|
|
390
394
|
|
|
391
395
|
let scopedModels: ScopedModel[] = [];
|
|
@@ -396,7 +400,7 @@ export async function main(args: string[]) {
|
|
|
396
400
|
}
|
|
397
401
|
|
|
398
402
|
// Create session manager based on CLI flags
|
|
399
|
-
let sessionManager = createSessionManager(parsed, cwd);
|
|
403
|
+
let sessionManager = await createSessionManager(parsed, cwd);
|
|
400
404
|
time("createSessionManager");
|
|
401
405
|
|
|
402
406
|
// Handle --resume: show session picker
|
|
@@ -413,7 +417,7 @@ export async function main(args: string[]) {
|
|
|
413
417
|
console.log(chalk.dim("No session selected"));
|
|
414
418
|
return;
|
|
415
419
|
}
|
|
416
|
-
sessionManager = SessionManager.open(selectedPath);
|
|
420
|
+
sessionManager = await SessionManager.open(selectedPath);
|
|
417
421
|
}
|
|
418
422
|
|
|
419
423
|
const sessionOptions = await buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async cleanup registry for graceful shutdown on signals.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Registry of async cleanup callbacks to run on shutdown/signals */
|
|
6
|
+
const asyncCleanupCallbacks: (() => Promise<void>)[] = [];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register an async cleanup callback to be run on process signals (SIGINT, SIGTERM, SIGHUP).
|
|
10
|
+
* Returns an unsubscribe function.
|
|
11
|
+
*/
|
|
12
|
+
export function registerAsyncCleanup(callback: () => Promise<void>): () => void {
|
|
13
|
+
asyncCleanupCallbacks.push(callback);
|
|
14
|
+
return () => {
|
|
15
|
+
const index = asyncCleanupCallbacks.indexOf(callback);
|
|
16
|
+
if (index >= 0) asyncCleanupCallbacks.splice(index, 1);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Run all registered async cleanup callbacks, settling all promises */
|
|
21
|
+
export async function runAsyncCleanup(): Promise<void> {
|
|
22
|
+
await Promise.allSettled(asyncCleanupCallbacks.map((cb) => cb()));
|
|
23
|
+
}
|
package/src/modes/index.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { emergencyTerminalRestore } from "@oh-my-pi/pi-tui";
|
|
6
|
+
import { runAsyncCleanup } from "./cleanup";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Install handlers that restore terminal state on crash/signal.
|
|
@@ -13,17 +14,21 @@ export function installTerminalCrashHandlers(): void {
|
|
|
13
14
|
emergencyTerminalRestore();
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
// Signals
|
|
17
|
+
// Signals - run async cleanup before exit
|
|
18
|
+
process.on("SIGINT", () => {
|
|
19
|
+
cleanup();
|
|
20
|
+
void runAsyncCleanup().finally(() => process.exit(128 + 2));
|
|
21
|
+
});
|
|
17
22
|
process.on("SIGTERM", () => {
|
|
18
23
|
cleanup();
|
|
19
|
-
process.exit(128 + 15);
|
|
24
|
+
void runAsyncCleanup().finally(() => process.exit(128 + 15));
|
|
20
25
|
});
|
|
21
26
|
process.on("SIGHUP", () => {
|
|
22
27
|
cleanup();
|
|
23
|
-
process.exit(128 + 1);
|
|
28
|
+
void runAsyncCleanup().finally(() => process.exit(128 + 1));
|
|
24
29
|
});
|
|
25
30
|
|
|
26
|
-
// Crashes
|
|
31
|
+
// Crashes - exit immediately (async cleanup may not be safe in corrupted state)
|
|
27
32
|
process.on("uncaughtException", (err) => {
|
|
28
33
|
cleanup();
|
|
29
34
|
console.error("Uncaught exception:", err);
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type TruncationResult,
|
|
11
11
|
truncateTail,
|
|
12
12
|
} from "../../../core/tools/truncate";
|
|
13
|
-
import { theme } from "../theme/theme";
|
|
13
|
+
import { getSymbolTheme, theme } from "../theme/theme";
|
|
14
14
|
import { DynamicBorder } from "./dynamic-border";
|
|
15
15
|
import { truncateToVisualLines } from "./visual-truncate";
|
|
16
16
|
|
|
@@ -55,7 +55,8 @@ export class BashExecutionComponent extends Container {
|
|
|
55
55
|
ui,
|
|
56
56
|
(spinner) => theme.fg("bashMode", spinner),
|
|
57
57
|
(text) => theme.fg("muted", text),
|
|
58
|
-
|
|
58
|
+
`Running${theme.format.ellipsis} (esc to cancel)`,
|
|
59
|
+
getSymbolTheme().spinnerFrames,
|
|
59
60
|
);
|
|
60
61
|
this.contentContainer.addChild(this.loader);
|
|
61
62
|
|
|
@@ -159,7 +160,9 @@ export class BashExecutionComponent extends Container {
|
|
|
159
160
|
|
|
160
161
|
// Show how many lines are hidden (collapsed preview)
|
|
161
162
|
if (hiddenLineCount > 0) {
|
|
162
|
-
statusParts.push(
|
|
163
|
+
statusParts.push(
|
|
164
|
+
theme.fg("dim", `${theme.format.ellipsis} ${hiddenLineCount} more lines (ctrl+o to expand)`),
|
|
165
|
+
);
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
if (this.status === "cancelled") {
|
|
@@ -24,7 +24,7 @@ export class BranchSummaryMessageComponent extends Box {
|
|
|
24
24
|
private updateDisplay(): void {
|
|
25
25
|
this.clear();
|
|
26
26
|
|
|
27
|
-
const label = theme.fg("customMessageLabel",
|
|
27
|
+
const label = theme.fg("customMessageLabel", theme.bold("[branch]"));
|
|
28
28
|
this.addChild(new Text(label, 0, 0));
|
|
29
29
|
this.addChild(new Spacer(1));
|
|
30
30
|
|
|
@@ -25,7 +25,7 @@ export class CompactionSummaryMessageComponent extends Box {
|
|
|
25
25
|
this.clear();
|
|
26
26
|
|
|
27
27
|
const tokenStr = this.message.tokensBefore.toLocaleString();
|
|
28
|
-
const label = theme.fg("customMessageLabel",
|
|
28
|
+
const label = theme.fg("customMessageLabel", theme.bold("[compaction]"));
|
|
29
29
|
this.addChild(new Text(label, 0, 0));
|
|
30
30
|
this.addChild(new Spacer(1));
|
|
31
31
|
|
|
@@ -117,18 +117,17 @@ export class ExtensionDashboard extends Container {
|
|
|
117
117
|
label += ` (${tab.count})`;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
const displayLabel = isDisabled ? `${label.split("").join("\u0336")}\u0336` : label;
|
|
120
|
+
const displayLabel = isDisabled ? `${theme.status.disabled} ${label}` : label;
|
|
122
121
|
|
|
123
122
|
if (isActive) {
|
|
124
123
|
// Active tab: background highlight
|
|
125
124
|
parts.push(theme.bg("selectedBg", ` ${displayLabel} `));
|
|
126
125
|
} else if (isDisabled) {
|
|
127
|
-
// Disabled provider:
|
|
126
|
+
// Disabled provider: dim
|
|
128
127
|
parts.push(theme.fg("dim", ` ${displayLabel} `));
|
|
129
128
|
} else if (isEmpty) {
|
|
130
129
|
// Empty enabled provider: very dim, unselectable
|
|
131
|
-
parts.push(
|
|
130
|
+
parts.push(theme.fg("dim", ` ${label} `));
|
|
132
131
|
} else {
|
|
133
132
|
// Normal enabled provider
|
|
134
133
|
parts.push(theme.fg("muted", ` ${label} `));
|
|
@@ -278,7 +277,7 @@ class TwoColumnBody implements Component {
|
|
|
278
277
|
|
|
279
278
|
const maxLines = Math.max(leftLines.length, rightLines.length);
|
|
280
279
|
const combined: string[] = [];
|
|
281
|
-
const separator = theme.fg("dim",
|
|
280
|
+
const separator = theme.fg("dim", ` ${theme.boxSharp.vertical} `);
|
|
282
281
|
|
|
283
282
|
for (let i = 0; i < maxLines; i++) {
|
|
284
283
|
const left = truncateToWidth(leftLines[i] ?? "", leftWidth);
|