@oh-my-pi/pi-coding-agent 15.9.1 → 15.9.5
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 +68 -2
- package/dist/types/cli/classify-install-target.d.ts +5 -1
- package/dist/types/cli/dry-balance-cli.d.ts +104 -0
- package/dist/types/commands/dry-balance.d.ts +31 -0
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +3 -0
- package/dist/types/config/settings-schema.d.ts +13 -4
- package/dist/types/config/settings.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +1 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -3
- package/dist/types/hindsight/bank.d.ts +17 -9
- package/dist/types/hindsight/mental-models.d.ts +1 -1
- package/dist/types/hindsight/state.d.ts +9 -3
- package/dist/types/mcp/manager.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +11 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -1
- package/dist/types/modes/components/error-banner.d.ts +11 -0
- package/dist/types/modes/components/tool-execution.d.ts +15 -0
- package/dist/types/modes/components/transcript-container.d.ts +4 -2
- package/dist/types/modes/components/user-message.d.ts +1 -1
- package/dist/types/modes/image-references.d.ts +17 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -0
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/session/auth-storage.d.ts +2 -2
- package/dist/types/session/blob-store.d.ts +12 -11
- package/dist/types/session/session-manager.d.ts +5 -3
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/types.d.ts +2 -0
- package/dist/types/tiny/title-client.d.ts +16 -1
- package/dist/types/tool-discovery/mode.d.ts +8 -0
- package/dist/types/tools/archive-reader.d.ts +5 -1
- package/dist/types/tools/index.d.ts +16 -0
- package/dist/types/tools/path-utils.d.ts +11 -0
- package/dist/types/tui/hyperlink.d.ts +12 -0
- package/dist/types/web/search/render.d.ts +1 -2
- package/package.json +9 -9
- package/src/cli/classify-install-target.ts +31 -5
- package/src/cli/dry-balance-cli.ts +823 -0
- package/src/cli/plugin-cli.ts +45 -0
- package/src/cli/web-search-cli.ts +0 -1
- package/src/cli-commands.ts +1 -0
- package/src/commands/dry-balance.ts +43 -0
- package/src/config/model-registry.ts +60 -4
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +14 -4
- package/src/config/settings.ts +38 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -0
- package/src/discovery/github.ts +37 -1
- package/src/discovery/helpers.ts +3 -1
- package/src/eval/__tests__/agent-bridge.test.ts +72 -0
- package/src/eval/py/tool-bridge.ts +43 -5
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +31 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +245 -25
- package/src/hindsight/backend.ts +184 -35
- package/src/hindsight/bank.ts +32 -22
- package/src/hindsight/mental-models.ts +1 -1
- package/src/hindsight/state.ts +21 -7
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/internal-urls/omp-protocol.ts +8 -2
- package/src/main.ts +7 -1
- package/src/mcp/manager.ts +40 -21
- package/src/modes/components/assistant-message.ts +22 -0
- package/src/modes/components/custom-editor.ts +14 -2
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/tool-execution.ts +44 -0
- package/src/modes/components/transcript-container.ts +102 -30
- package/src/modes/components/tree-selector.ts +29 -2
- package/src/modes/components/user-message.ts +9 -2
- package/src/modes/controllers/event-controller.ts +42 -3
- package/src/modes/controllers/input-controller.ts +41 -3
- package/src/modes/image-references.ts +111 -0
- package/src/modes/interactive-mode.ts +48 -13
- package/src/modes/setup-wizard/scenes/sign-in.ts +27 -7
- package/src/modes/types.ts +10 -1
- package/src/modes/utils/ui-helpers.ts +23 -2
- package/src/prompts/agents/explore.md +1 -0
- package/src/prompts/agents/librarian.md +1 -0
- package/src/prompts/ci-green-request.md +5 -3
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/system/project-prompt.md +1 -0
- package/src/sdk.ts +99 -18
- package/src/session/agent-session.ts +103 -19
- package/src/session/auth-storage.ts +4 -0
- package/src/session/blob-store.ts +96 -9
- package/src/session/session-manager.ts +19 -10
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +6 -2
- package/src/task/index.ts +8 -7
- package/src/task/types.ts +2 -0
- package/src/tiny/title-client.ts +7 -1
- package/src/tool-discovery/mode.ts +24 -0
- package/src/tools/archive-reader.ts +339 -31
- package/src/tools/bash.ts +3 -4
- package/src/tools/fetch.ts +29 -9
- package/src/tools/gh.ts +65 -11
- package/src/tools/index.ts +22 -8
- package/src/tools/job.ts +3 -3
- package/src/tools/memory-reflect.ts +2 -2
- package/src/tools/path-utils.ts +21 -0
- package/src/tools/read.ts +58 -12
- package/src/tools/search-tool-bm25.ts +4 -6
- package/src/tools/search.ts +78 -12
- package/src/tui/hyperlink.ts +42 -7
- package/src/utils/file-mentions.ts +7 -107
- package/src/utils/title-generator.ts +58 -37
- package/src/web/search/index.ts +2 -2
- package/src/web/search/render.ts +20 -52
|
@@ -149,7 +149,10 @@ export async function generateSessionTitle(
|
|
|
149
149
|
// tiny title model can't reliably decline trivial input, so this happens
|
|
150
150
|
// deterministically before any model is invoked; the caller retries on the
|
|
151
151
|
// next user message while the session stays unnamed.
|
|
152
|
-
if (isLowSignalTitleInput(firstMessage))
|
|
152
|
+
if (isLowSignalTitleInput(firstMessage)) {
|
|
153
|
+
logger.debug("title-generator: skipped low-signal input", { sessionId, reason: "low-signal" });
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
153
156
|
|
|
154
157
|
const tinyModel = settings.get("providers.tinyModel");
|
|
155
158
|
if (tinyModel === ONLINE_TINY_TITLE_MODEL_KEY) {
|
|
@@ -159,7 +162,14 @@ export async function generateSessionTitle(
|
|
|
159
162
|
const onlineAbortController = new AbortController();
|
|
160
163
|
const localTitle = tinyTitleClient.generate(tinyModel, firstMessage).then(
|
|
161
164
|
title => title || null,
|
|
162
|
-
|
|
165
|
+
err => {
|
|
166
|
+
logger.warn("title-generator: local model error", {
|
|
167
|
+
sessionId,
|
|
168
|
+
model: tinyModel,
|
|
169
|
+
error: err instanceof Error ? err.message : String(err),
|
|
170
|
+
});
|
|
171
|
+
return null;
|
|
172
|
+
},
|
|
163
173
|
);
|
|
164
174
|
const startOnline = (): Promise<string | null> =>
|
|
165
175
|
generateTitleOnline(
|
|
@@ -188,49 +198,48 @@ export async function generateTitleOnline(
|
|
|
188
198
|
): Promise<string | null> {
|
|
189
199
|
const model = getTitleModel(registry, settings, currentModel);
|
|
190
200
|
if (!model) {
|
|
191
|
-
logger.
|
|
201
|
+
logger.warn("title-generator: no title model found", { sessionId, reason: "no-title-model" });
|
|
192
202
|
return null;
|
|
193
203
|
}
|
|
194
204
|
|
|
195
205
|
const userMessage = formatTitleUserMessage(firstMessage);
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
// Resolve metadata after getApiKey so the session-sticky credential for this
|
|
206
|
-
// request is already recorded; metadataResolver can then return the correct
|
|
207
|
-
// account_uuid rather than the snapshot-at-call-site value.
|
|
208
|
-
const metadata = metadataResolver?.(model.provider);
|
|
209
|
-
|
|
210
|
-
// Title generation is a 3-6 word task, but some reasoning backends ignore
|
|
211
|
-
// disableReasoning. Keep the normal cheap budget for non-reasoning models
|
|
212
|
-
// while reserving enough output room for reasoning models to still emit
|
|
213
|
-
// the forced tool call after any unavoidable thinking tokens.
|
|
214
|
-
const maxTokens = model.reasoning ? Math.max(TITLE_MAX_TOKENS, REASONING_SAFE_MAX_TOKENS) : TITLE_MAX_TOKENS;
|
|
215
|
-
const request = {
|
|
216
|
-
model: `${model.provider}/${model.id}`,
|
|
217
|
-
systemPrompt: TITLE_SYSTEM_PROMPT,
|
|
218
|
-
userMessage,
|
|
219
|
-
maxTokens,
|
|
206
|
+
const modelName = `${model.provider}/${model.id}`;
|
|
207
|
+
const modelContext = {
|
|
208
|
+
sessionId,
|
|
209
|
+
provider: model.provider,
|
|
210
|
+
id: model.id,
|
|
211
|
+
model: modelName,
|
|
220
212
|
};
|
|
221
|
-
logger.debug("title-generator:
|
|
213
|
+
logger.debug("title-generator: start", modelContext);
|
|
222
214
|
|
|
223
215
|
try {
|
|
216
|
+
const apiKey = await registry.getApiKey(model, sessionId);
|
|
217
|
+
if (!apiKey) {
|
|
218
|
+
logger.warn("title-generator: no API key", { ...modelContext, reason: "missing-api-key" });
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
// Resolve metadata after getApiKey so the session-sticky credential for this
|
|
222
|
+
// request is already recorded; metadataResolver can then return the correct
|
|
223
|
+
// account_uuid rather than the snapshot-at-call-site value.
|
|
224
|
+
const metadata = metadataResolver?.(model.provider);
|
|
225
|
+
|
|
226
|
+
// Title generation is a 3-6 word task, but some reasoning backends ignore
|
|
227
|
+
// disableReasoning. Keep the normal cheap budget for non-reasoning models
|
|
228
|
+
// while reserving enough output room for reasoning models to still emit
|
|
229
|
+
// the forced tool call after any unavoidable thinking tokens.
|
|
230
|
+
const maxTokens = model.reasoning ? Math.max(TITLE_MAX_TOKENS, REASONING_SAFE_MAX_TOKENS) : TITLE_MAX_TOKENS;
|
|
231
|
+
logger.debug("title-generator: request", { ...modelContext, maxTokens });
|
|
232
|
+
|
|
224
233
|
const response = await completeSimple(
|
|
225
234
|
model,
|
|
226
235
|
{
|
|
227
|
-
systemPrompt: [
|
|
228
|
-
messages: [{ role: "user", content:
|
|
236
|
+
systemPrompt: [TITLE_SYSTEM_PROMPT],
|
|
237
|
+
messages: [{ role: "user", content: userMessage, timestamp: Date.now() }],
|
|
229
238
|
tools: [setTitleTool],
|
|
230
239
|
},
|
|
231
240
|
{
|
|
232
241
|
apiKey,
|
|
233
|
-
maxTokens
|
|
242
|
+
maxTokens,
|
|
234
243
|
disableReasoning: true,
|
|
235
244
|
toolChoice: { type: "tool", name: SET_TITLE_TOOL_NAME },
|
|
236
245
|
metadata,
|
|
@@ -239,8 +248,9 @@ export async function generateTitleOnline(
|
|
|
239
248
|
);
|
|
240
249
|
|
|
241
250
|
if (response.stopReason === "error") {
|
|
242
|
-
logger.
|
|
243
|
-
|
|
251
|
+
logger.warn("title-generator: response error", {
|
|
252
|
+
...modelContext,
|
|
253
|
+
reason: "provider-response-error",
|
|
244
254
|
stopReason: response.stopReason,
|
|
245
255
|
errorMessage: response.errorMessage,
|
|
246
256
|
});
|
|
@@ -249,8 +259,18 @@ export async function generateTitleOnline(
|
|
|
249
259
|
|
|
250
260
|
const title = normalizeGeneratedTitle(extractGeneratedTitle(response.content));
|
|
251
261
|
|
|
252
|
-
|
|
253
|
-
|
|
262
|
+
if (!title) {
|
|
263
|
+
logger.debug("title-generator: no title returned", {
|
|
264
|
+
...modelContext,
|
|
265
|
+
reason: "model-returned-none",
|
|
266
|
+
usage: response.usage,
|
|
267
|
+
stopReason: response.stopReason,
|
|
268
|
+
});
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
logger.debug("title-generator: success", {
|
|
273
|
+
...modelContext,
|
|
254
274
|
title,
|
|
255
275
|
usage: response.usage,
|
|
256
276
|
stopReason: response.stopReason,
|
|
@@ -258,8 +278,9 @@ export async function generateTitleOnline(
|
|
|
258
278
|
|
|
259
279
|
return title;
|
|
260
280
|
} catch (err) {
|
|
261
|
-
logger.
|
|
262
|
-
|
|
281
|
+
logger.warn("title-generator: error", {
|
|
282
|
+
...modelContext,
|
|
283
|
+
reason: "exception",
|
|
263
284
|
error: err instanceof Error ? err.message : String(err),
|
|
264
285
|
});
|
|
265
286
|
return null;
|
package/src/web/search/index.ts
CHANGED
|
@@ -278,8 +278,8 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
|
|
|
278
278
|
return renderSearchCall(args, options, theme);
|
|
279
279
|
},
|
|
280
280
|
|
|
281
|
-
renderResult(result, options: RenderResultOptions, theme: Theme) {
|
|
282
|
-
return renderSearchResult(result, options, theme);
|
|
281
|
+
renderResult(result, options: RenderResultOptions, theme: Theme, args) {
|
|
282
|
+
return renderSearchResult(result, options, theme, args);
|
|
283
283
|
},
|
|
284
284
|
};
|
|
285
285
|
|
package/src/web/search/render.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
|
-
import {
|
|
8
|
+
import { Markdown, Text } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
10
|
-
import type
|
|
10
|
+
import { getMarkdownTheme, type Theme } from "../../modes/theme/theme";
|
|
11
11
|
import {
|
|
12
12
|
formatAge,
|
|
13
13
|
formatCount,
|
|
@@ -26,8 +26,6 @@ import { getSearchProviderLabel } from "./provider";
|
|
|
26
26
|
import type { SearchResponse } from "./types";
|
|
27
27
|
|
|
28
28
|
const MAX_COLLAPSED_ANSWER_LINES = PREVIEW_LIMITS.COLLAPSED_LINES;
|
|
29
|
-
const MAX_EXPANDED_ANSWER_LINES = PREVIEW_LIMITS.EXPANDED_LINES;
|
|
30
|
-
const MAX_ANSWER_LINE_LEN = TRUNCATE_LENGTHS.LINE;
|
|
31
29
|
const MAX_SNIPPET_LINES = 2;
|
|
32
30
|
const MAX_SNIPPET_LINE_LEN = TRUNCATE_LENGTHS.LINE;
|
|
33
31
|
const MAX_COLLAPSED_ITEMS = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
@@ -75,7 +73,6 @@ export function renderSearchResult(
|
|
|
75
73
|
theme: Theme,
|
|
76
74
|
args?: {
|
|
77
75
|
query?: string;
|
|
78
|
-
allowLongAnswer?: boolean;
|
|
79
76
|
maxAnswerLines?: number;
|
|
80
77
|
},
|
|
81
78
|
): Component {
|
|
@@ -104,13 +101,6 @@ export function renderSearchResult(
|
|
|
104
101
|
// Get answer text
|
|
105
102
|
const answerText = typeof response.answer === "string" ? response.answer.trim() : "";
|
|
106
103
|
const contentText = answerText || rawText;
|
|
107
|
-
const answerLines = contentText
|
|
108
|
-
? contentText
|
|
109
|
-
.split("\n")
|
|
110
|
-
.filter(l => l.trim())
|
|
111
|
-
.map(l => l.trim())
|
|
112
|
-
: [];
|
|
113
|
-
const totalAnswerLines = answerLines.length;
|
|
114
104
|
|
|
115
105
|
const providerLabel = provider !== "none" ? getSearchProviderLabel(provider) : "None";
|
|
116
106
|
const queryPreview = args?.query
|
|
@@ -159,6 +149,7 @@ export function renderSearchResult(
|
|
|
159
149
|
metaLines.push(`${theme.fg("muted", "Queries:")} ${theme.fg("text", queryList.join("; "))}${suffix}`);
|
|
160
150
|
}
|
|
161
151
|
|
|
152
|
+
const answerMarkdown = contentText ? new Markdown(contentText, 0, 0, getMarkdownTheme()) : undefined;
|
|
162
153
|
const outputBlock = new CachedOutputBlock();
|
|
163
154
|
|
|
164
155
|
return {
|
|
@@ -166,14 +157,22 @@ export function renderSearchResult(
|
|
|
166
157
|
// Read mutable state at render time
|
|
167
158
|
const { expanded } = options;
|
|
168
159
|
|
|
169
|
-
//
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
160
|
+
// Answer lines: full markdown when expanded, capped markdown preview when collapsed.
|
|
161
|
+
const answerWidth = Math.max(20, width - 3);
|
|
162
|
+
const renderedAnswer = answerMarkdown ? answerMarkdown.render(answerWidth) : [];
|
|
163
|
+
let answerLines: string[];
|
|
164
|
+
if (renderedAnswer.length === 0) {
|
|
165
|
+
answerLines = [theme.fg("muted", "No answer text returned")];
|
|
166
|
+
} else if (expanded) {
|
|
167
|
+
answerLines = renderedAnswer;
|
|
168
|
+
} else {
|
|
169
|
+
const collapsedCap = args?.maxAnswerLines ?? MAX_COLLAPSED_ANSWER_LINES;
|
|
170
|
+
answerLines = renderedAnswer.slice(0, collapsedCap);
|
|
171
|
+
const remaining = renderedAnswer.length - answerLines.length;
|
|
172
|
+
if (remaining > 0) {
|
|
173
|
+
answerLines.push(theme.fg("muted", formatMoreItems(remaining, "line")));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
177
176
|
|
|
178
177
|
const sourceTree = renderTreeList(
|
|
179
178
|
{
|
|
@@ -217,37 +216,6 @@ export function renderSearchResult(
|
|
|
217
216
|
theme,
|
|
218
217
|
);
|
|
219
218
|
|
|
220
|
-
// Build answer section
|
|
221
|
-
const answerState = sourceCount > 0 ? "success" : "warning";
|
|
222
|
-
const borderColor: "warning" | "dim" = answerState === "warning" ? "warning" : "dim";
|
|
223
|
-
const border = (t: string) => theme.fg(borderColor, t);
|
|
224
|
-
const contentPrefix = border(`${theme.boxSharp.vertical} `);
|
|
225
|
-
const contentSuffix = border(theme.boxSharp.vertical);
|
|
226
|
-
const contentWidth = Math.max(0, width - visibleWidth(contentPrefix) - visibleWidth(contentSuffix));
|
|
227
|
-
const answerTreeLines = answerPreview.length > 0 ? answerPreview : ["No answer text returned"];
|
|
228
|
-
const answerTree = renderTreeList(
|
|
229
|
-
{
|
|
230
|
-
items: answerTreeLines,
|
|
231
|
-
expanded: true,
|
|
232
|
-
maxCollapsed: answerTreeLines.length,
|
|
233
|
-
itemType: "line",
|
|
234
|
-
renderItem: (line, context) => {
|
|
235
|
-
const coloredLine =
|
|
236
|
-
line === "No answer text returned" ? theme.fg("muted", line) : theme.fg("dim", line);
|
|
237
|
-
if (!args?.allowLongAnswer) {
|
|
238
|
-
return coloredLine;
|
|
239
|
-
}
|
|
240
|
-
const prefixWidth = visibleWidth(context.continuePrefix);
|
|
241
|
-
const wrapWidth = Math.max(10, contentWidth - prefixWidth);
|
|
242
|
-
return wrapTextWithAnsi(coloredLine, wrapWidth);
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
theme,
|
|
246
|
-
);
|
|
247
|
-
if (remainingAnswer > 0) {
|
|
248
|
-
answerTree.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line")));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
219
|
return outputBlock.render(
|
|
252
220
|
{
|
|
253
221
|
header,
|
|
@@ -262,7 +230,7 @@ export function renderSearchResult(
|
|
|
262
230
|
: []),
|
|
263
231
|
{
|
|
264
232
|
label: theme.fg("toolTitle", "Answer"),
|
|
265
|
-
lines:
|
|
233
|
+
lines: answerLines,
|
|
266
234
|
},
|
|
267
235
|
{
|
|
268
236
|
label: theme.fg("toolTitle", "Sources"),
|