@oh-my-pi/pi-coding-agent 15.13.3 → 16.0.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 +40 -0
- package/dist/cli.js +506 -443
- package/dist/types/advisor/__tests__/advisor.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +58 -0
- package/dist/types/advisor/index.d.ts +3 -0
- package/dist/types/advisor/runtime.d.ts +52 -0
- package/dist/types/advisor/watchdog.d.ts +5 -0
- package/dist/types/config/model-roles.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +44 -5
- package/dist/types/modes/components/advisor-message.d.ts +9 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/sdk.d.ts +3 -3
- package/dist/types/session/agent-session.d.ts +71 -2
- package/dist/types/session/session-history-format.d.ts +4 -0
- package/dist/types/session/yield-queue.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/report-tool-issue.d.ts +0 -1
- package/package.json +13 -13
- package/src/advisor/__tests__/advisor.test.ts +586 -0
- package/src/advisor/advise-tool.ts +87 -0
- package/src/advisor/index.ts +3 -0
- package/src/advisor/runtime.ts +248 -0
- package/src/advisor/watchdog.ts +83 -0
- package/src/config/model-roles.ts +13 -1
- package/src/config/settings-schema.ts +42 -5
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/main.ts +4 -0
- package/src/modes/components/advisor-message.ts +99 -0
- package/src/modes/components/agent-hub.ts +7 -0
- package/src/modes/components/assistant-message.ts +86 -0
- package/src/modes/components/status-line/segments.ts +20 -7
- package/src/modes/controllers/command-controller.ts +69 -2
- package/src/modes/interactive-mode.ts +12 -2
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/ui-helpers.ts +9 -0
- package/src/prompts/advisor/advise-tool.md +1 -0
- package/src/prompts/advisor/system.md +31 -0
- package/src/sdk.ts +52 -13
- package/src/session/agent-session.ts +560 -13
- package/src/session/session-dump-format.ts +15 -131
- package/src/session/session-history-format.ts +30 -11
- package/src/session/yield-queue.ts +5 -1
- package/src/slash-commands/builtin-registry.ts +102 -4
- package/src/system-prompt.ts +1 -1
- package/src/tools/path-utils.ts +33 -2
- package/src/tools/report-tool-issue.ts +2 -7
- package/src/web/scrapers/docs-rs.ts +2 -3
|
@@ -2,22 +2,10 @@
|
|
|
2
2
|
* Plain-text / markdown session formatting (same shape as /dump clipboard export).
|
|
3
3
|
*/
|
|
4
4
|
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { canonicalizeMessage } from "../utils/thinking-display";
|
|
10
|
-
import {
|
|
11
|
-
type BashExecutionMessage,
|
|
12
|
-
type BranchSummaryMessage,
|
|
13
|
-
bashExecutionToText,
|
|
14
|
-
type CompactionSummaryMessage,
|
|
15
|
-
type CustomMessage,
|
|
16
|
-
type FileMentionMessage,
|
|
17
|
-
type HookMessage,
|
|
18
|
-
type PythonExecutionMessage,
|
|
19
|
-
pythonExecutionToText,
|
|
20
|
-
} from "./messages";
|
|
5
|
+
import type { Model, ToolExample, TSchema } from "@oh-my-pi/pi-ai";
|
|
6
|
+
import { getDialectDefinition, renderToolInventory } from "@oh-my-pi/pi-ai/dialect";
|
|
7
|
+
import { preferredDialect } from "@oh-my-pi/pi-catalog/identity";
|
|
8
|
+
import { convertToLlm } from "./messages";
|
|
21
9
|
|
|
22
10
|
/** Minimal tool shape for dump output (matches AgentTool fields used by formatSessionDumpText). */
|
|
23
11
|
export interface SessionDumpToolInfo {
|
|
@@ -40,7 +28,7 @@ export interface FormatSessionDumpTextOptions {
|
|
|
40
28
|
*/
|
|
41
29
|
export function formatSessionDumpText(options: FormatSessionDumpTextOptions): string {
|
|
42
30
|
const lines: string[] = [];
|
|
43
|
-
const
|
|
31
|
+
const definition = getDialectDefinition(preferredDialect(options.model?.id ?? ""));
|
|
44
32
|
|
|
45
33
|
const systemPrompt = options.systemPrompt?.filter(prompt => prompt.length > 0) ?? [];
|
|
46
34
|
if (systemPrompt.length > 0) {
|
|
@@ -62,125 +50,21 @@ export function formatSessionDumpText(options: FormatSessionDumpTextOptions): st
|
|
|
62
50
|
lines.push("\n");
|
|
63
51
|
|
|
64
52
|
const tools = options.tools ?? [];
|
|
65
|
-
|
|
53
|
+
const inventoryTools = tools.map(tool => ({
|
|
54
|
+
name: tool.name,
|
|
55
|
+
description: tool.description,
|
|
56
|
+
parameters: tool.parameters as TSchema,
|
|
57
|
+
examples: tool.examples,
|
|
58
|
+
}));
|
|
59
|
+
if (inventoryTools.length > 0) {
|
|
66
60
|
lines.push("## Available Tools\n");
|
|
67
|
-
const inventoryTools = tools.map(tool => ({
|
|
68
|
-
name: tool.name,
|
|
69
|
-
description: tool.description,
|
|
70
|
-
parameters: tool.parameters as TSchema,
|
|
71
|
-
examples: tool.examples,
|
|
72
|
-
}));
|
|
73
61
|
lines.push(renderToolInventory(inventoryTools, options.model?.id ?? ""));
|
|
74
62
|
lines.push("\n");
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (typeof msg.content === "string") {
|
|
81
|
-
lines.push(msg.content);
|
|
82
|
-
} else {
|
|
83
|
-
for (const c of msg.content) {
|
|
84
|
-
if (c.type === "text") {
|
|
85
|
-
lines.push(c.text);
|
|
86
|
-
} else if (c.type === "image") {
|
|
87
|
-
lines.push("[Image]");
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
lines.push("\n");
|
|
92
|
-
} else if (msg.role === "assistant") {
|
|
93
|
-
const assistantMsg = msg as AssistantMessage;
|
|
94
|
-
lines.push("## Assistant\n");
|
|
95
|
-
|
|
96
|
-
for (const c of assistantMsg.content) {
|
|
97
|
-
if (c.type === "text") {
|
|
98
|
-
lines.push(c.text);
|
|
99
|
-
} else if (c.type === "thinking") {
|
|
100
|
-
const thinking = canonicalizeMessage(c.thinking);
|
|
101
|
-
if (thinking.length === 0) continue;
|
|
102
|
-
lines.push("<thinking>");
|
|
103
|
-
lines.push(thinking);
|
|
104
|
-
lines.push("</thinking>\n");
|
|
105
|
-
} else if (c.type === "toolCall") {
|
|
106
|
-
const args = { ...(c.arguments as Record<string, unknown>) };
|
|
107
|
-
delete args[INTENT_FIELD];
|
|
108
|
-
lines.push(grammar.renderToolCall({ ...c, arguments: args }));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
lines.push("");
|
|
112
|
-
} else if (msg.role === "toolResult") {
|
|
113
|
-
lines.push(`### Tool Result: ${msg.toolName}`);
|
|
114
|
-
if (msg.isError) {
|
|
115
|
-
lines.push("(error)");
|
|
116
|
-
}
|
|
117
|
-
for (const c of msg.content) {
|
|
118
|
-
if (c.type === "text") {
|
|
119
|
-
lines.push("```");
|
|
120
|
-
lines.push(c.text);
|
|
121
|
-
lines.push("```");
|
|
122
|
-
} else if (c.type === "image") {
|
|
123
|
-
lines.push("[Image output]");
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
lines.push("");
|
|
127
|
-
} else if (msg.role === "bashExecution") {
|
|
128
|
-
const bashMsg = msg as BashExecutionMessage;
|
|
129
|
-
if (!bashMsg.excludeFromContext) {
|
|
130
|
-
lines.push("## Bash Execution\n");
|
|
131
|
-
lines.push(bashExecutionToText(bashMsg));
|
|
132
|
-
lines.push("\n");
|
|
133
|
-
}
|
|
134
|
-
} else if (msg.role === "pythonExecution") {
|
|
135
|
-
const pythonMsg = msg as PythonExecutionMessage;
|
|
136
|
-
if (!pythonMsg.excludeFromContext) {
|
|
137
|
-
lines.push("## Python Execution\n");
|
|
138
|
-
lines.push(pythonExecutionToText(pythonMsg));
|
|
139
|
-
lines.push("\n");
|
|
140
|
-
}
|
|
141
|
-
} else if (msg.role === "custom" || msg.role === "hookMessage") {
|
|
142
|
-
const customMsg = msg as CustomMessage | HookMessage;
|
|
143
|
-
lines.push(`## ${customMsg.customType}\n`);
|
|
144
|
-
if (typeof customMsg.content === "string") {
|
|
145
|
-
lines.push(customMsg.content);
|
|
146
|
-
} else {
|
|
147
|
-
for (const c of customMsg.content) {
|
|
148
|
-
if (c.type === "text") {
|
|
149
|
-
lines.push(c.text);
|
|
150
|
-
} else if (c.type === "image") {
|
|
151
|
-
lines.push("[Image]");
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
lines.push("\n");
|
|
156
|
-
} else if (msg.role === "branchSummary") {
|
|
157
|
-
const branchMsg = msg as BranchSummaryMessage;
|
|
158
|
-
lines.push("## Branch Summary\n");
|
|
159
|
-
lines.push(`(from branch: ${branchMsg.fromId})\n`);
|
|
160
|
-
lines.push(branchMsg.summary);
|
|
161
|
-
lines.push("\n");
|
|
162
|
-
} else if (msg.role === "compactionSummary") {
|
|
163
|
-
const compactMsg = msg as CompactionSummaryMessage;
|
|
164
|
-
lines.push("## Compaction Summary\n");
|
|
165
|
-
lines.push(`(${compactMsg.tokensBefore} tokens before compaction)\n`);
|
|
166
|
-
lines.push(compactMsg.summary);
|
|
167
|
-
lines.push("\n");
|
|
168
|
-
} else if (msg.role === "fileMention") {
|
|
169
|
-
const fileMsg = msg as FileMentionMessage;
|
|
170
|
-
lines.push("## File Mention\n");
|
|
171
|
-
for (const file of fileMsg.files) {
|
|
172
|
-
lines.push(`<file path="${file.path}">`);
|
|
173
|
-
if (file.content) {
|
|
174
|
-
lines.push(file.content);
|
|
175
|
-
}
|
|
176
|
-
if (file.image) {
|
|
177
|
-
lines.push("[Image attached]");
|
|
178
|
-
}
|
|
179
|
-
lines.push("</file>\n");
|
|
180
|
-
}
|
|
181
|
-
lines.push("\n");
|
|
182
|
-
}
|
|
183
|
-
}
|
|
65
|
+
lines.push("## Transcript\n");
|
|
66
|
+
lines.push(definition.renderTranscript(convertToLlm([...options.messages]), { tools: inventoryTools }));
|
|
67
|
+
lines.push("\n");
|
|
184
68
|
|
|
185
69
|
return lines.join("\n").trim();
|
|
186
70
|
}
|
|
@@ -22,6 +22,10 @@ import type {
|
|
|
22
22
|
export interface HistoryFormatOptions {
|
|
23
23
|
/** Optional H1 prepended to the transcript. */
|
|
24
24
|
title?: string;
|
|
25
|
+
/** Render assistant thinking blocks (default: elided). */
|
|
26
|
+
includeThinking?: boolean;
|
|
27
|
+
/** Render tool intent comment before tool call lines. */
|
|
28
|
+
includeToolIntent?: boolean;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/** Max length of the primary-arg summary inside `→ tool(...)` lines. */
|
|
@@ -100,17 +104,30 @@ function toolCallLine(
|
|
|
100
104
|
name: string,
|
|
101
105
|
args: Record<string, unknown> | undefined,
|
|
102
106
|
result: ToolResultMessage | undefined,
|
|
107
|
+
includeToolIntent?: boolean,
|
|
103
108
|
): string {
|
|
104
109
|
const head = `→ ${name}(${primaryArg(args)})`;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
110
|
+
let base: string;
|
|
111
|
+
if (!result) {
|
|
112
|
+
base = `${head} ⇒ pending`;
|
|
113
|
+
} else {
|
|
114
|
+
const text = contentToText(result.content);
|
|
115
|
+
const lines = lineCount(text);
|
|
116
|
+
const count = `${lines} ${lines === 1 ? "line" : "lines"}`;
|
|
117
|
+
if (result.isError) {
|
|
118
|
+
const firstLine = oneLine(text.split("\n", 1)[0] ?? "");
|
|
119
|
+
base = firstLine ? `${head} ⇒ error · ${count} — ${firstLine}` : `${head} ⇒ error · ${count}`;
|
|
120
|
+
} else {
|
|
121
|
+
base = `${head} ⇒ ok · ${count}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const intent = includeToolIntent ? args?.[INTENT_FIELD] : undefined;
|
|
126
|
+
if (typeof intent === "string" && intent.trim()) {
|
|
127
|
+
const formattedIntent = oneLine(intent, 80);
|
|
128
|
+
return `# ${formattedIntent}\n${base}`;
|
|
112
129
|
}
|
|
113
|
-
return
|
|
130
|
+
return base;
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
/** One line for a user-initiated `!`/`$` execution. */
|
|
@@ -193,9 +210,11 @@ export function formatSessionHistoryMarkdown(messages: unknown[], opts?: History
|
|
|
193
210
|
} else if (block.type === "toolCall") {
|
|
194
211
|
const result = resultsByCallId.get(block.id);
|
|
195
212
|
if (result) consumed.add(block.id);
|
|
196
|
-
body.push(toolCallLine(block.name, block.arguments, result));
|
|
213
|
+
body.push(toolCallLine(block.name, block.arguments, result, opts?.includeToolIntent));
|
|
214
|
+
} else if (opts?.includeThinking && block.type === "thinking" && block.thinking.trim()) {
|
|
215
|
+
body.push(`_thinking:_ ${block.thinking}`);
|
|
197
216
|
}
|
|
198
|
-
//
|
|
217
|
+
// redactedThinking elided entirely (no readable text)
|
|
199
218
|
}
|
|
200
219
|
if (body.length === 0) break;
|
|
201
220
|
lines.push("## assistant", "", ...body, "");
|
|
@@ -204,7 +223,7 @@ export function formatSessionHistoryMarkdown(messages: unknown[], opts?: History
|
|
|
204
223
|
case "toolResult": {
|
|
205
224
|
// Normally consumed by its toolCall; orphans (e.g. truncated history) get their own line.
|
|
206
225
|
if (consumed.has(msg.toolCallId)) break;
|
|
207
|
-
lines.push(toolCallLine(msg.toolName, undefined, msg), "");
|
|
226
|
+
lines.push(toolCallLine(msg.toolName, undefined, msg, opts?.includeToolIntent), "");
|
|
208
227
|
break;
|
|
209
228
|
}
|
|
210
229
|
case "bashExecution": {
|
|
@@ -6,6 +6,8 @@ export interface YieldDispatcher<P> {
|
|
|
6
6
|
isStale?(entry: P): boolean;
|
|
7
7
|
/** Produce one batched AgentMessage from non-stale entries. Return null to skip. */
|
|
8
8
|
build(survivors: P[]): AgentMessage | null;
|
|
9
|
+
/** If true, entries for this kind are drained only by {@link drainLazy} and never trigger the idle flush. */
|
|
10
|
+
skipIdleFlush?: boolean;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface YieldQueueOptions {
|
|
@@ -20,6 +22,7 @@ type YieldFlushMode = "streaming" | "idle";
|
|
|
20
22
|
interface StoredDispatcher {
|
|
21
23
|
isStale?: (entry: unknown) => boolean;
|
|
22
24
|
build: (survivors: unknown[]) => AgentMessage | null;
|
|
25
|
+
skipIdleFlush?: boolean;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
function formatError(error: unknown): string {
|
|
@@ -40,6 +43,7 @@ export class YieldQueue {
|
|
|
40
43
|
const stored: StoredDispatcher = {
|
|
41
44
|
...(dispatcher.isStale ? { isStale: entry => dispatcher.isStale?.(entry as P) ?? false } : {}),
|
|
42
45
|
build: survivors => dispatcher.build(survivors as P[]),
|
|
46
|
+
...(dispatcher.skipIdleFlush ? { skipIdleFlush: true } : {}),
|
|
43
47
|
};
|
|
44
48
|
this.#dispatchers.set(kind, stored);
|
|
45
49
|
return () => {
|
|
@@ -60,7 +64,7 @@ export class YieldQueue {
|
|
|
60
64
|
this.#entries.set(kind, entries);
|
|
61
65
|
}
|
|
62
66
|
entries.push(entry);
|
|
63
|
-
if (!this.#options.isStreaming()) {
|
|
67
|
+
if (!this.#options.isStreaming() && !this.#dispatchers.get(kind)!.skipIdleFlush) {
|
|
64
68
|
this.#scheduleIdleFlush();
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -419,6 +419,100 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
419
419
|
runtime.ctx.editor.setText("");
|
|
420
420
|
},
|
|
421
421
|
},
|
|
422
|
+
{
|
|
423
|
+
name: "advisor",
|
|
424
|
+
description: "Toggle the advisor (a second model that reviews each turn and injects notes)",
|
|
425
|
+
acpDescription: "Toggle advisor",
|
|
426
|
+
acpInputHint: "[on|off|status|dump [raw]]",
|
|
427
|
+
subcommands: [
|
|
428
|
+
{ name: "on", description: "Enable the advisor" },
|
|
429
|
+
{ name: "off", description: "Disable the advisor" },
|
|
430
|
+
{ name: "status", description: "Show advisor status" },
|
|
431
|
+
{ name: "dump", description: "Copy the advisor's transcript to clipboard", usage: "[raw]" },
|
|
432
|
+
],
|
|
433
|
+
allowArgs: true,
|
|
434
|
+
handle: async (command, runtime) => {
|
|
435
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
436
|
+
if (!verb || verb === "toggle") {
|
|
437
|
+
const active = runtime.session.toggleAdvisorEnabled();
|
|
438
|
+
const configured = runtime.session.settings.get("advisor.enabled") as boolean;
|
|
439
|
+
if (active) {
|
|
440
|
+
await runtime.output("Advisor enabled.");
|
|
441
|
+
} else if (configured) {
|
|
442
|
+
await runtime.output("Advisor setting enabled, but no model is assigned to the 'advisor' role.");
|
|
443
|
+
} else {
|
|
444
|
+
await runtime.output("Advisor disabled.");
|
|
445
|
+
}
|
|
446
|
+
return commandConsumed();
|
|
447
|
+
}
|
|
448
|
+
if (verb === "on") {
|
|
449
|
+
const active = runtime.session.setAdvisorEnabled(true);
|
|
450
|
+
await runtime.output(
|
|
451
|
+
active ? "Advisor enabled." : "Advisor setting enabled, but no model is assigned to the 'advisor' role.",
|
|
452
|
+
);
|
|
453
|
+
return commandConsumed();
|
|
454
|
+
}
|
|
455
|
+
if (verb === "off") {
|
|
456
|
+
runtime.session.setAdvisorEnabled(false);
|
|
457
|
+
await runtime.output("Advisor disabled.");
|
|
458
|
+
return commandConsumed();
|
|
459
|
+
}
|
|
460
|
+
if (verb === "status") {
|
|
461
|
+
await runtime.output(runtime.session.formatAdvisorStatus());
|
|
462
|
+
return commandConsumed();
|
|
463
|
+
}
|
|
464
|
+
if (verb === "dump") {
|
|
465
|
+
const isRaw = rest.toLowerCase() === "raw";
|
|
466
|
+
const text = runtime.session.formatAdvisorHistoryAsText({ compact: !isRaw });
|
|
467
|
+
await runtime.output(text ?? "Advisor is not active for this session.");
|
|
468
|
+
return commandConsumed();
|
|
469
|
+
}
|
|
470
|
+
return usage("Usage: /advisor [on|off|status|dump [raw]]", runtime);
|
|
471
|
+
},
|
|
472
|
+
handleTui: async (command, runtime) => {
|
|
473
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
474
|
+
if (!verb || verb === "toggle") {
|
|
475
|
+
const active = runtime.ctx.session.toggleAdvisorEnabled();
|
|
476
|
+
const configured = runtime.ctx.session.settings.get("advisor.enabled") as boolean;
|
|
477
|
+
if (active) {
|
|
478
|
+
runtime.ctx.showStatus("Advisor enabled.");
|
|
479
|
+
} else if (configured) {
|
|
480
|
+
runtime.ctx.showStatus("Advisor setting enabled, but no model is assigned to the 'advisor' role.");
|
|
481
|
+
} else {
|
|
482
|
+
runtime.ctx.showStatus("Advisor disabled.");
|
|
483
|
+
}
|
|
484
|
+
runtime.ctx.editor.setText("");
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (verb === "on") {
|
|
488
|
+
const active = runtime.ctx.session.setAdvisorEnabled(true);
|
|
489
|
+
runtime.ctx.showStatus(
|
|
490
|
+
active ? "Advisor enabled." : "Advisor setting enabled, but no model is assigned to the 'advisor' role.",
|
|
491
|
+
);
|
|
492
|
+
runtime.ctx.editor.setText("");
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (verb === "off") {
|
|
496
|
+
runtime.ctx.session.setAdvisorEnabled(false);
|
|
497
|
+
runtime.ctx.showStatus("Advisor disabled.");
|
|
498
|
+
runtime.ctx.editor.setText("");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (verb === "status") {
|
|
502
|
+
await runtime.ctx.handleAdvisorStatusCommand();
|
|
503
|
+
runtime.ctx.editor.setText("");
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (verb === "dump") {
|
|
507
|
+
const isRaw = rest.toLowerCase() === "raw";
|
|
508
|
+
runtime.ctx.handleAdvisorDumpCommand(isRaw);
|
|
509
|
+
runtime.ctx.editor.setText("");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
runtime.ctx.showStatus("Usage: /advisor [on|off|status|dump [raw]]");
|
|
513
|
+
runtime.ctx.editor.setText("");
|
|
514
|
+
},
|
|
515
|
+
},
|
|
422
516
|
{
|
|
423
517
|
name: "export",
|
|
424
518
|
description: "Export session to HTML file",
|
|
@@ -450,13 +544,17 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
450
544
|
name: "dump",
|
|
451
545
|
description: "Copy session transcript to clipboard",
|
|
452
546
|
acpDescription: "Return full transcript as plain text",
|
|
453
|
-
|
|
454
|
-
|
|
547
|
+
inlineHint: "[raw]",
|
|
548
|
+
allowArgs: true,
|
|
549
|
+
handle: async (command, runtime) => {
|
|
550
|
+
const isRaw = command.args.trim().toLowerCase() === "raw";
|
|
551
|
+
const text = runtime.session.formatSessionAsText({ compact: !isRaw });
|
|
455
552
|
await runtime.output(text || "No messages to dump yet.");
|
|
456
553
|
return commandConsumed();
|
|
457
554
|
},
|
|
458
|
-
handleTui:
|
|
459
|
-
|
|
555
|
+
handleTui: (command, runtime) => {
|
|
556
|
+
const isRaw = command.args.trim().toLowerCase() === "raw";
|
|
557
|
+
runtime.ctx.handleDumpCommand(isRaw);
|
|
460
558
|
runtime.ctx.editor.setText("");
|
|
461
559
|
},
|
|
462
560
|
},
|
package/src/system-prompt.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { ToolExample, TSchema } from "@oh-my-pi/pi-ai";
|
|
8
|
-
import { renderToolInventory } from "@oh-my-pi/pi-ai/
|
|
8
|
+
import { renderToolInventory } from "@oh-my-pi/pi-ai/dialect";
|
|
9
9
|
import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import { $ } from "bun";
|
|
11
11
|
import { contextFileCapability } from "./capability/context-file";
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -143,6 +143,37 @@ export function expandPath(filePath: string): string {
|
|
|
143
143
|
const normalized = stripFileUrl(normalizeUnicodeSpaces(normalizeAtPrefix(filePath)));
|
|
144
144
|
return expandTilde(normalized);
|
|
145
145
|
}
|
|
146
|
+
|
|
147
|
+
function isAsciiDriveLetter(value: string): boolean {
|
|
148
|
+
if (value.length !== 1) return false;
|
|
149
|
+
const code = value.charCodeAt(0);
|
|
150
|
+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function windowsDriveAliasPath(filePath: string): string | undefined {
|
|
154
|
+
if (!filePath.startsWith("/")) return undefined;
|
|
155
|
+
const parts = filePath.split("/");
|
|
156
|
+
if (parts[0] !== "") return undefined;
|
|
157
|
+
|
|
158
|
+
let drive: string | undefined;
|
|
159
|
+
let tailStart = 2;
|
|
160
|
+
if (parts.length >= 2 && isAsciiDriveLetter(parts[1] ?? "")) {
|
|
161
|
+
drive = parts[1]!.toUpperCase();
|
|
162
|
+
} else if (parts.length >= 3 && (parts[1] ?? "").toLowerCase() === "mnt" && isAsciiDriveLetter(parts[2] ?? "")) {
|
|
163
|
+
drive = parts[2]!.toUpperCase();
|
|
164
|
+
tailStart = 3;
|
|
165
|
+
}
|
|
166
|
+
if (!drive) return undefined;
|
|
167
|
+
|
|
168
|
+
const tail = parts.slice(tailStart).filter(Boolean).join("\\");
|
|
169
|
+
return tail ? `${drive}:\\${tail}` : `${drive}:\\`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function normalizeWindowsDriveAliasPath(filePath: string, platform: NodeJS.Platform = process.platform): string {
|
|
173
|
+
if (platform !== "win32") return filePath;
|
|
174
|
+
return windowsDriveAliasPath(filePath) ?? filePath;
|
|
175
|
+
}
|
|
176
|
+
|
|
146
177
|
/**
|
|
147
178
|
* Inclusive line range describing one selector segment (e.g. `50-100`,
|
|
148
179
|
* `301-`, or `50+10`). `endLine` is `undefined` for open-ended ranges.
|
|
@@ -353,7 +384,7 @@ export function isInternalUrlPath(filePath: string): boolean {
|
|
|
353
384
|
*/
|
|
354
385
|
export function resolveToCwd(filePath: string, cwd: string): string {
|
|
355
386
|
const normalized = normalizeLocalScheme(filePath);
|
|
356
|
-
const expanded = expandPath(normalized);
|
|
387
|
+
const expanded = normalizeWindowsDriveAliasPath(expandPath(normalized));
|
|
357
388
|
const expandedAndNormalized = normalizeLocalScheme(expanded);
|
|
358
389
|
|
|
359
390
|
assertNotInternalUrl(expandedAndNormalized, normalized);
|
|
@@ -530,8 +561,8 @@ export async function splitDelimitedPathEntry(
|
|
|
530
561
|
}
|
|
531
562
|
|
|
532
563
|
return (
|
|
533
|
-
(await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "comma", false)) ??
|
|
534
564
|
(await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "semicolon", false)) ??
|
|
565
|
+
(await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "comma", false)) ??
|
|
535
566
|
(await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "whitespace", true)) ??
|
|
536
567
|
(await tryDelimitedPathSplit(normalizedEntry, cwd, splitter, "mixed", true))
|
|
537
568
|
);
|
|
@@ -20,10 +20,9 @@
|
|
|
20
20
|
* never blocked on the network and never throws.
|
|
21
21
|
*/
|
|
22
22
|
import { Database } from "bun:sqlite";
|
|
23
|
-
import path from "node:path";
|
|
24
23
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
25
24
|
import type { FetchImpl } from "@oh-my-pi/pi-ai";
|
|
26
|
-
import { $env, $flag,
|
|
25
|
+
import { $env, $flag, getAutoQaDbDir, getInstallId, logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
27
26
|
import { z } from "zod/v4";
|
|
28
27
|
import type { Settings } from "..";
|
|
29
28
|
import type { ToolSession } from "./index";
|
|
@@ -184,10 +183,6 @@ export async function resolveAutoQaConsent(settings: Settings | undefined): Prom
|
|
|
184
183
|
return consentInFlight;
|
|
185
184
|
}
|
|
186
185
|
|
|
187
|
-
export function getAutoQaDbPath(): string {
|
|
188
|
-
return path.join(getAgentDir(), "autoqa.db");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
186
|
let cachedDb: Database | null = null;
|
|
192
187
|
|
|
193
188
|
/**
|
|
@@ -205,7 +200,7 @@ let cachedDb: Database | null = null;
|
|
|
205
200
|
export function openAutoQaDb(): Database | null {
|
|
206
201
|
if (cachedDb) return cachedDb;
|
|
207
202
|
try {
|
|
208
|
-
const db = new Database(
|
|
203
|
+
const db = new Database(getAutoQaDbDir());
|
|
209
204
|
// Install the busy handler BEFORE any lock-taking statement. See #2421.
|
|
210
205
|
db.run("PRAGMA busy_timeout = 5000");
|
|
211
206
|
db.run(`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { gunzipSync } from "node:zlib";
|
|
4
|
-
import {
|
|
4
|
+
import { getDocsRsCacheDir, isEnoent, logger, ptree, tryParseJson } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { ToolAbortError } from "../../tools/tool-errors";
|
|
6
6
|
import type { RenderResult, SpecialHandler } from "./types";
|
|
7
7
|
import { buildResult, MAX_BYTES } from "./types";
|
|
@@ -276,7 +276,6 @@ function findItemInModule(mod_: RustdocItem, name: string, index: Record<string,
|
|
|
276
276
|
return null;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
const DOCS_RS_CACHE_ROOT = "webcache";
|
|
280
279
|
const DOCS_RS_CACHE_FILENAME = "rustdoc.json";
|
|
281
280
|
|
|
282
281
|
function sanitizeCacheSegment(value: string): string {
|
|
@@ -291,7 +290,7 @@ function getDocsRsCacheVersionSegment(version: string, now = new Date()): string
|
|
|
291
290
|
function getDocsRsCachePath(target: DocsRsTarget, now = new Date()): string {
|
|
292
291
|
const crate = sanitizeCacheSegment(target.crateName);
|
|
293
292
|
const version = getDocsRsCacheVersionSegment(target.version, now);
|
|
294
|
-
return path.join(
|
|
293
|
+
return path.join(getDocsRsCacheDir(), `docsrs_${crate}_${version}`, DOCS_RS_CACHE_FILENAME);
|
|
295
294
|
}
|
|
296
295
|
|
|
297
296
|
async function readCachedRustdocCrate(
|