@oh-my-pi/pi-coding-agent 8.0.20 → 8.1.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 +105 -0
- package/package.json +14 -11
- package/scripts/generate-wasm-b64.ts +24 -0
- package/src/capability/context-file.ts +1 -1
- package/src/capability/extension-module.ts +1 -1
- package/src/capability/extension.ts +1 -1
- package/src/capability/hook.ts +1 -1
- package/src/capability/instruction.ts +1 -1
- package/src/capability/mcp.ts +1 -1
- package/src/capability/prompt.ts +1 -1
- package/src/capability/rule.ts +1 -1
- package/src/capability/settings.ts +1 -1
- package/src/capability/skill.ts +1 -1
- package/src/capability/slash-command.ts +1 -1
- package/src/capability/ssh.ts +1 -1
- package/src/capability/system-prompt.ts +1 -1
- package/src/capability/tool.ts +1 -1
- package/src/cli/args.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -5
- package/src/commit/agentic/agent.ts +309 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +359 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +26 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +40 -0
- package/src/commit/agentic/state.ts +74 -0
- package/src/commit/agentic/tools/analyze-file.ts +131 -0
- package/src/commit/agentic/tools/git-file-diff.ts +194 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +84 -0
- package/src/commit/agentic/tools/index.ts +56 -0
- package/src/commit/agentic/tools/propose-changelog.ts +128 -0
- package/src/commit/agentic/tools/propose-commit.ts +154 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/split-commit.ts +284 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +200 -0
- package/src/commit/analysis/conventional.ts +169 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +114 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +36 -0
- package/src/commit/changelog/generate.ts +112 -0
- package/src/commit/changelog/index.ts +233 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +93 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/git/errors.ts +11 -0
- package/src/commit/git/index.ts +217 -0
- package/src/commit/git/operations.ts +53 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/.map-phase.ts.kate-swp +0 -0
- package/src/commit/map-reduce/index.ts +63 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +147 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +84 -0
- package/src/commit/pipeline.ts +242 -0
- package/src/commit/prompts/analysis-system.md +155 -0
- package/src/commit/prompts/analysis-user.md +41 -0
- package/src/commit/prompts/changelog-system.md +56 -0
- package/src/commit/prompts/changelog-user.md +19 -0
- package/src/commit/prompts/file-observer-system.md +26 -0
- package/src/commit/prompts/file-observer-user.md +9 -0
- package/src/commit/prompts/reduce-system.md +60 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +4 -0
- package/src/commit/prompts/summary-system.md +52 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/types.ts +109 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/config/file-lock.ts +111 -0
- package/src/config/model-registry.ts +16 -7
- package/src/config/settings-manager.ts +115 -40
- package/src/config.ts +5 -5
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/builtin.ts +1 -1
- package/src/discovery/claude.ts +1 -1
- package/src/discovery/cline.ts +1 -1
- package/src/discovery/codex.ts +1 -1
- package/src/discovery/cursor.ts +1 -1
- package/src/discovery/gemini.ts +1 -1
- package/src/discovery/github.ts +1 -1
- package/src/discovery/index.ts +11 -11
- package/src/discovery/mcp-json.ts +1 -1
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/vscode.ts +1 -1
- package/src/discovery/windsurf.ts +1 -1
- package/src/extensibility/custom-commands/loader.ts +1 -1
- package/src/extensibility/custom-commands/types.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/loader.ts +1 -1
- package/src/extensibility/hooks/types.ts +3 -3
- package/src/index.ts +10 -10
- package/src/ipy/executor.ts +97 -1
- package/src/lsp/index.ts +1 -1
- package/src/lsp/render.ts +90 -46
- package/src/main.ts +16 -3
- package/src/mcp/loader.ts +3 -3
- package/src/migrations.ts +3 -3
- package/src/modes/components/assistant-message.ts +29 -1
- package/src/modes/components/tool-execution.ts +5 -3
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -3
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-mode.ts +1 -4
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/modes/theme/mermaid-cache.ts +89 -0
- package/src/modes/theme/theme.ts +2 -0
- package/src/modes/types.ts +2 -2
- package/src/patch/index.ts +3 -9
- package/src/patch/shared.ts +33 -5
- package/src/prompts/tools/task.md +2 -0
- package/src/sdk.ts +60 -22
- package/src/session/agent-session.ts +3 -3
- package/src/session/agent-storage.ts +32 -28
- package/src/session/artifacts.ts +24 -1
- package/src/session/auth-storage.ts +25 -10
- package/src/session/storage-migration.ts +12 -53
- package/src/system-prompt.ts +2 -2
- package/src/task/.executor.ts.kate-swp +0 -0
- package/src/task/executor.ts +1 -1
- package/src/task/index.ts +10 -1
- package/src/task/output-manager.ts +94 -0
- package/src/task/render.ts +7 -12
- package/src/task/worker.ts +1 -1
- package/src/tools/ask.ts +35 -13
- package/src/tools/bash.ts +80 -87
- package/src/tools/calculator.ts +42 -40
- package/src/tools/complete.ts +1 -1
- package/src/tools/fetch.ts +67 -104
- package/src/tools/find.ts +83 -86
- package/src/tools/grep.ts +80 -96
- package/src/tools/index.ts +10 -7
- package/src/tools/ls.ts +39 -65
- package/src/tools/notebook.ts +48 -64
- package/src/tools/output-utils.ts +1 -1
- package/src/tools/python.ts +71 -183
- package/src/tools/read.ts +74 -15
- package/src/tools/render-utils.ts +1 -15
- package/src/tools/ssh.ts +43 -24
- package/src/tools/todo-write.ts +27 -15
- package/src/tools/write.ts +93 -64
- package/src/tui/code-cell.ts +115 -0
- package/src/tui/file-list.ts +48 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +73 -0
- package/src/tui/status-line.ts +40 -0
- package/src/tui/tree-list.ts +56 -0
- package/src/tui/types.ts +17 -0
- package/src/tui/utils.ts +49 -0
- package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
- package/src/web/search/auth.ts +1 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/render.ts +119 -163
- package/tsconfig.json +0 -42
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// TypeBox helper for string enums (convenience for custom tools)
|
|
4
4
|
// Re-export from pi-ai which uses the correct enum-based schema format
|
|
5
5
|
export { StringEnum } from "@oh-my-pi/pi-ai";
|
|
6
|
-
export type { FileDiagnosticsResult } from "@oh-my-pi/pi-coding-agent/lsp
|
|
6
|
+
export type { FileDiagnosticsResult } from "@oh-my-pi/pi-coding-agent/lsp";
|
|
7
7
|
// UI components for extensions
|
|
8
8
|
export {
|
|
9
9
|
ArminComponent,
|
|
@@ -37,7 +37,7 @@ export {
|
|
|
37
37
|
UserMessageComponent,
|
|
38
38
|
UserMessageSelectorComponent,
|
|
39
39
|
type VisualTruncateResult,
|
|
40
|
-
} from "@oh-my-pi/pi-coding-agent/modes/components
|
|
40
|
+
} from "@oh-my-pi/pi-coding-agent/modes/components";
|
|
41
41
|
// Theme utilities for custom tools
|
|
42
42
|
export {
|
|
43
43
|
getLanguageFromPath,
|
|
@@ -88,9 +88,9 @@ export type {
|
|
|
88
88
|
ExecResult,
|
|
89
89
|
LoadedCustomTool,
|
|
90
90
|
RenderResultOptions,
|
|
91
|
-
} from "./extensibility/custom-tools
|
|
91
|
+
} from "./extensibility/custom-tools";
|
|
92
92
|
// Custom tools
|
|
93
|
-
export { CustomToolLoader, discoverAndLoadCustomTools, loadCustomTools } from "./extensibility/custom-tools
|
|
93
|
+
export { CustomToolLoader, discoverAndLoadCustomTools, loadCustomTools } from "./extensibility/custom-tools";
|
|
94
94
|
export type {
|
|
95
95
|
AppAction,
|
|
96
96
|
Extension,
|
|
@@ -122,7 +122,7 @@ export type {
|
|
|
122
122
|
UserBashEventResult,
|
|
123
123
|
UserPythonEvent,
|
|
124
124
|
UserPythonEventResult,
|
|
125
|
-
} from "./extensibility/extensions
|
|
125
|
+
} from "./extensibility/extensions";
|
|
126
126
|
// Extension types and utilities
|
|
127
127
|
export {
|
|
128
128
|
discoverAndLoadExtensions,
|
|
@@ -135,9 +135,9 @@ export {
|
|
|
135
135
|
isLsToolResult,
|
|
136
136
|
isReadToolResult,
|
|
137
137
|
isWriteToolResult,
|
|
138
|
-
} from "./extensibility/extensions
|
|
138
|
+
} from "./extensibility/extensions";
|
|
139
139
|
// Hook system types (legacy re-export)
|
|
140
|
-
export type * from "./extensibility/hooks
|
|
140
|
+
export type * from "./extensibility/hooks";
|
|
141
141
|
// Skills
|
|
142
142
|
export {
|
|
143
143
|
type LoadSkillsFromDirOptions,
|
|
@@ -162,7 +162,7 @@ export {
|
|
|
162
162
|
type RpcEventListener,
|
|
163
163
|
runPrintMode,
|
|
164
164
|
runRpcMode,
|
|
165
|
-
} from "./modes
|
|
165
|
+
} from "./modes";
|
|
166
166
|
// SDK for programmatic usage
|
|
167
167
|
export {
|
|
168
168
|
// Factory
|
|
@@ -228,7 +228,7 @@ export {
|
|
|
228
228
|
prepareBranchEntries,
|
|
229
229
|
serializeConversation,
|
|
230
230
|
shouldCompact,
|
|
231
|
-
} from "./session/compaction
|
|
231
|
+
} from "./session/compaction";
|
|
232
232
|
export { convertToLlm } from "./session/messages";
|
|
233
233
|
export {
|
|
234
234
|
type BranchSummaryEntry,
|
|
@@ -275,5 +275,5 @@ export {
|
|
|
275
275
|
truncateLine,
|
|
276
276
|
truncateTail,
|
|
277
277
|
type WriteToolDetails,
|
|
278
|
-
} from "./tools
|
|
278
|
+
} from "./tools";
|
|
279
279
|
export { getShellConfig } from "./utils/shell";
|
package/src/ipy/executor.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { shutdownSharedGateway } from "@oh-my-pi/pi-coding-agent/ipy/gateway-coordinator";
|
|
1
2
|
import {
|
|
2
3
|
checkPythonKernelAvailability,
|
|
3
4
|
type KernelDisplayOutput,
|
|
@@ -8,6 +9,11 @@ import {
|
|
|
8
9
|
} from "@oh-my-pi/pi-coding-agent/ipy/kernel";
|
|
9
10
|
import { OutputSink } from "@oh-my-pi/pi-coding-agent/session/streaming-output";
|
|
10
11
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
12
|
+
|
|
13
|
+
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
14
|
+
const MAX_KERNEL_SESSIONS = 4;
|
|
15
|
+
const CLEANUP_INTERVAL_MS = 30 * 1000; // 30 seconds
|
|
16
|
+
|
|
11
17
|
export type PythonKernelMode = "session" | "per-call";
|
|
12
18
|
|
|
13
19
|
export interface PythonExecutorOptions {
|
|
@@ -77,8 +83,58 @@ interface KernelSession {
|
|
|
77
83
|
|
|
78
84
|
const kernelSessions = new Map<string, KernelSession>();
|
|
79
85
|
let cachedPreludeDocs: PreludeHelper[] | null = null;
|
|
86
|
+
let cleanupTimer: NodeJS.Timeout | null = null;
|
|
87
|
+
|
|
88
|
+
function startCleanupTimer(): void {
|
|
89
|
+
if (cleanupTimer) return;
|
|
90
|
+
cleanupTimer = setInterval(() => {
|
|
91
|
+
void cleanupIdleSessions();
|
|
92
|
+
}, CLEANUP_INTERVAL_MS);
|
|
93
|
+
cleanupTimer.unref();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function stopCleanupTimer(): void {
|
|
97
|
+
if (cleanupTimer) {
|
|
98
|
+
clearInterval(cleanupTimer);
|
|
99
|
+
cleanupTimer = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function cleanupIdleSessions(): Promise<void> {
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
const toDispose: KernelSession[] = [];
|
|
106
|
+
|
|
107
|
+
for (const session of kernelSessions.values()) {
|
|
108
|
+
if (session.dead || now - session.lastUsedAt > IDLE_TIMEOUT_MS) {
|
|
109
|
+
toDispose.push(session);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (toDispose.length > 0) {
|
|
114
|
+
logger.debug("Cleaning up idle kernel sessions", { count: toDispose.length });
|
|
115
|
+
await Promise.allSettled(toDispose.map((session) => disposeKernelSession(session)));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (kernelSessions.size === 0) {
|
|
119
|
+
stopCleanupTimer();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function evictOldestSession(): Promise<void> {
|
|
124
|
+
let oldest: KernelSession | null = null;
|
|
125
|
+
for (const session of kernelSessions.values()) {
|
|
126
|
+
if (!oldest || session.lastUsedAt < oldest.lastUsedAt) {
|
|
127
|
+
oldest = session;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (oldest) {
|
|
131
|
+
logger.debug("Evicting oldest kernel session", { id: oldest.id });
|
|
132
|
+
await disposeKernelSession(oldest);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
80
135
|
|
|
81
136
|
export async function disposeAllKernelSessions(): Promise<void> {
|
|
137
|
+
stopCleanupTimer();
|
|
82
138
|
const sessions = Array.from(kernelSessions.values());
|
|
83
139
|
await Promise.allSettled(sessions.map((session) => disposeKernelSession(session)));
|
|
84
140
|
}
|
|
@@ -132,12 +188,36 @@ export function resetPreludeDocsCache(): void {
|
|
|
132
188
|
cachedPreludeDocs = null;
|
|
133
189
|
}
|
|
134
190
|
|
|
191
|
+
function isResourceExhaustionError(error: unknown): boolean {
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
return (
|
|
194
|
+
message.includes("Too many open files") ||
|
|
195
|
+
message.includes("EMFILE") ||
|
|
196
|
+
message.includes("ENFILE") ||
|
|
197
|
+
message.includes("resource temporarily unavailable")
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function recoverFromResourceExhaustion(): Promise<void> {
|
|
202
|
+
logger.warn("Resource exhaustion detected, recovering by restarting shared gateway");
|
|
203
|
+
stopCleanupTimer();
|
|
204
|
+
const sessions = Array.from(kernelSessions.values());
|
|
205
|
+
for (const session of sessions) {
|
|
206
|
+
if (session.heartbeatTimer) {
|
|
207
|
+
clearInterval(session.heartbeatTimer);
|
|
208
|
+
}
|
|
209
|
+
kernelSessions.delete(session.id);
|
|
210
|
+
}
|
|
211
|
+
await shutdownSharedGateway();
|
|
212
|
+
}
|
|
213
|
+
|
|
135
214
|
async function createKernelSession(
|
|
136
215
|
sessionId: string,
|
|
137
216
|
cwd: string,
|
|
138
217
|
useSharedGateway?: boolean,
|
|
139
218
|
sessionFile?: string,
|
|
140
219
|
artifactsDir?: string,
|
|
220
|
+
isRetry?: boolean,
|
|
141
221
|
): Promise<KernelSession> {
|
|
142
222
|
const env: Record<string, string> | undefined =
|
|
143
223
|
sessionFile || artifactsDir
|
|
@@ -146,7 +226,18 @@ async function createKernelSession(
|
|
|
146
226
|
...(artifactsDir ? { ARTIFACTS: artifactsDir } : {}),
|
|
147
227
|
}
|
|
148
228
|
: undefined;
|
|
149
|
-
|
|
229
|
+
|
|
230
|
+
let kernel: PythonKernel;
|
|
231
|
+
try {
|
|
232
|
+
kernel = await PythonKernel.start({ cwd, useSharedGateway, env });
|
|
233
|
+
} catch (err) {
|
|
234
|
+
if (!isRetry && isResourceExhaustionError(err)) {
|
|
235
|
+
await recoverFromResourceExhaustion();
|
|
236
|
+
return createKernelSession(sessionId, cwd, useSharedGateway, sessionFile, artifactsDir, true);
|
|
237
|
+
}
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
|
|
150
241
|
const session: KernelSession = {
|
|
151
242
|
id: sessionId,
|
|
152
243
|
kernel,
|
|
@@ -218,8 +309,13 @@ async function withKernelSession<T>(
|
|
|
218
309
|
): Promise<T> {
|
|
219
310
|
let session = kernelSessions.get(sessionId);
|
|
220
311
|
if (!session) {
|
|
312
|
+
// Evict oldest session if at capacity
|
|
313
|
+
if (kernelSessions.size >= MAX_KERNEL_SESSIONS) {
|
|
314
|
+
await evictOldestSession();
|
|
315
|
+
}
|
|
221
316
|
session = await createKernelSession(sessionId, cwd, useSharedGateway, sessionFile, artifactsDir);
|
|
222
317
|
kernelSessions.set(sessionId, session);
|
|
318
|
+
startCleanupTimer();
|
|
223
319
|
}
|
|
224
320
|
|
|
225
321
|
const run = async (): Promise<T> => {
|
package/src/lsp/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
5
5
|
import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
|
|
6
6
|
import { type Theme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
7
7
|
import lspDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/lsp.md" with { type: "text" };
|
|
8
|
-
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools
|
|
8
|
+
import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
|
|
9
9
|
import { resolveToCwd } from "@oh-my-pi/pi-coding-agent/tools/path-utils";
|
|
10
10
|
import { throwIfAborted } from "@oh-my-pi/pi-coding-agent/tools/tool-errors";
|
|
11
11
|
import { logger, once, untilAborted } from "@oh-my-pi/pi-utils";
|
package/src/lsp/render.ts
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
TRUNCATE_LENGTHS,
|
|
17
17
|
truncate,
|
|
18
18
|
} from "@oh-my-pi/pi-coding-agent/tools/render-utils";
|
|
19
|
-
import {
|
|
19
|
+
import { renderOutputBlock, renderStatusLine } from "@oh-my-pi/pi-coding-agent/tui";
|
|
20
|
+
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
20
21
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
21
22
|
import type { LspParams, LspToolDetails } from "./types";
|
|
22
23
|
|
|
@@ -30,16 +31,13 @@ import type { LspParams, LspToolDetails } from "./types";
|
|
|
30
31
|
*/
|
|
31
32
|
export function renderCall(args: unknown, theme: Theme): Text {
|
|
32
33
|
const p = args as LspParams & { file?: string; files?: string[] };
|
|
33
|
-
|
|
34
|
-
let text = theme.fg("toolTitle", theme.bold("LSP"));
|
|
35
|
-
text += ` ${theme.fg("accent", p.action || "?")}`;
|
|
36
|
-
|
|
34
|
+
const meta: string[] = [];
|
|
37
35
|
if (p.file) {
|
|
38
|
-
|
|
36
|
+
meta.push(p.file);
|
|
39
37
|
} else if (p.files?.length) {
|
|
40
|
-
|
|
38
|
+
meta.push(`${p.files.length} file(s)`);
|
|
41
39
|
}
|
|
42
|
-
|
|
40
|
+
const text = renderStatusLine({ icon: "pending", title: "LSP", description: p.action || "?", meta }, theme);
|
|
43
41
|
return new Text(text, 0, 0);
|
|
44
42
|
}
|
|
45
43
|
|
|
@@ -55,40 +53,74 @@ export function renderResult(
|
|
|
55
53
|
result: AgentToolResult<LspToolDetails>,
|
|
56
54
|
options: RenderResultOptions,
|
|
57
55
|
theme: Theme,
|
|
58
|
-
|
|
56
|
+
args?: LspParams & { file?: string; files?: string[] },
|
|
57
|
+
): Component {
|
|
59
58
|
const content = result.content?.[0];
|
|
60
59
|
if (!content || content.type !== "text" || !("text" in content) || !content.text) {
|
|
61
|
-
|
|
60
|
+
const header = renderStatusLine({ icon: "warning", title: "LSP", description: "No result" }, theme);
|
|
61
|
+
return new Text([header, theme.fg("dim", "No result")].join("\n"), 0, 0);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const text = content.text;
|
|
65
|
-
const lines = text.split("\n")
|
|
65
|
+
const lines = text.split("\n");
|
|
66
66
|
const expanded = options.expanded;
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
let label = "Result";
|
|
69
|
+
let state: "success" | "warning" | "error" = "success";
|
|
70
|
+
let bodyLines: string[] = [];
|
|
71
|
+
|
|
69
72
|
const codeBlockMatch = text.match(/```(\w*)\n([\s\S]*?)```/);
|
|
70
73
|
if (codeBlockMatch) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
label = "Hover";
|
|
75
|
+
bodyLines = renderHover(codeBlockMatch, text, lines, expanded, theme);
|
|
76
|
+
} else {
|
|
77
|
+
const errorMatch = text.match(/(\d+)\s+error\(s\)/);
|
|
78
|
+
const warningMatch = text.match(/(\d+)\s+warning\(s\)/);
|
|
79
|
+
if (errorMatch || warningMatch || text.includes(theme.status.error)) {
|
|
80
|
+
label = "Diagnostics";
|
|
81
|
+
const errorCount = errorMatch ? Number.parseInt(errorMatch[1], 10) : 0;
|
|
82
|
+
const warnCount = warningMatch ? Number.parseInt(warningMatch[1], 10) : 0;
|
|
83
|
+
state = errorCount > 0 ? "error" : warnCount > 0 ? "warning" : "success";
|
|
84
|
+
bodyLines = renderDiagnostics(errorMatch, warningMatch, lines, expanded, theme);
|
|
85
|
+
} else {
|
|
86
|
+
const refMatch = text.match(/(\d+)\s+reference\(s\)/);
|
|
87
|
+
if (refMatch) {
|
|
88
|
+
label = "References";
|
|
89
|
+
bodyLines = renderReferences(refMatch, lines, expanded, theme);
|
|
90
|
+
} else {
|
|
91
|
+
const symbolsMatch = text.match(/Symbols in (.+):/);
|
|
92
|
+
if (symbolsMatch) {
|
|
93
|
+
label = "Symbols";
|
|
94
|
+
bodyLines = renderSymbols(symbolsMatch, lines, expanded, theme);
|
|
95
|
+
} else {
|
|
96
|
+
label = "Response";
|
|
97
|
+
bodyLines = renderGeneric(text, lines, expanded, theme);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
83
101
|
}
|
|
84
102
|
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
|
|
103
|
+
const meta: string[] = [];
|
|
104
|
+
if (args?.action) meta.push(args.action);
|
|
105
|
+
if (args?.file) {
|
|
106
|
+
meta.push(args.file);
|
|
107
|
+
} else if (args?.files?.length) {
|
|
108
|
+
meta.push(`${args.files.length} file(s)`);
|
|
88
109
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
const header = renderStatusLine({ icon: state, title: "LSP", description: label, meta }, theme);
|
|
111
|
+
return {
|
|
112
|
+
render: (width: number) =>
|
|
113
|
+
renderOutputBlock(
|
|
114
|
+
{
|
|
115
|
+
header,
|
|
116
|
+
state,
|
|
117
|
+
sections: [{ label: theme.fg("toolTitle", label), lines: bodyLines }],
|
|
118
|
+
width,
|
|
119
|
+
},
|
|
120
|
+
theme,
|
|
121
|
+
),
|
|
122
|
+
invalidate: () => {},
|
|
123
|
+
};
|
|
92
124
|
}
|
|
93
125
|
|
|
94
126
|
// =============================================================================
|
|
@@ -104,9 +136,11 @@ function renderHover(
|
|
|
104
136
|
_lines: string[],
|
|
105
137
|
expanded: boolean,
|
|
106
138
|
theme: Theme,
|
|
107
|
-
):
|
|
139
|
+
): string[] {
|
|
108
140
|
const lang = codeBlockMatch[1] || "";
|
|
109
141
|
const code = codeBlockMatch[2].trim();
|
|
142
|
+
const codeStart = codeBlockMatch.index ?? 0;
|
|
143
|
+
const beforeCode = fullText.slice(0, codeStart).trimEnd();
|
|
110
144
|
const afterCode = fullText.slice(fullText.indexOf("```", 3) + 3).trim();
|
|
111
145
|
|
|
112
146
|
const codeLines = highlightCode(code, lang, theme);
|
|
@@ -119,6 +153,11 @@ function renderHover(
|
|
|
119
153
|
const top = `${theme.boxSharp.topLeft}${h.repeat(3)}`;
|
|
120
154
|
const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
|
|
121
155
|
let output = `${icon}${langLabel}`;
|
|
156
|
+
if (beforeCode) {
|
|
157
|
+
for (const line of beforeCode.split("\n")) {
|
|
158
|
+
output += `\n ${theme.fg("muted", line)}`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
122
161
|
output += `\n ${theme.fg("mdCodeBlockBorder", top)}`;
|
|
123
162
|
for (const line of codeLines) {
|
|
124
163
|
output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${line}`;
|
|
@@ -127,15 +166,19 @@ function renderHover(
|
|
|
127
166
|
if (afterCode) {
|
|
128
167
|
output += `\n ${theme.fg("muted", afterCode)}`;
|
|
129
168
|
}
|
|
130
|
-
return
|
|
169
|
+
return output.split("\n");
|
|
131
170
|
}
|
|
132
171
|
|
|
133
172
|
// Collapsed view
|
|
134
173
|
const firstCodeLine = codeLines[0] || "";
|
|
135
|
-
const hasMore = codeLines.length > 1 || Boolean(afterCode);
|
|
174
|
+
const hasMore = codeLines.length > 1 || Boolean(afterCode) || Boolean(beforeCode);
|
|
136
175
|
const expandHint = formatExpandHint(theme, expanded, hasMore);
|
|
137
176
|
|
|
138
177
|
let output = `${icon}${langLabel}${expandHint}`;
|
|
178
|
+
if (beforeCode) {
|
|
179
|
+
const preview = truncate(beforeCode, TRUNCATE_LENGTHS.TITLE, theme.format.ellipsis);
|
|
180
|
+
output += `\n ${theme.fg("dim", theme.tree.branch)} ${theme.fg("muted", preview)}`;
|
|
181
|
+
}
|
|
139
182
|
const h = theme.boxSharp.horizontal;
|
|
140
183
|
const v = theme.boxSharp.vertical;
|
|
141
184
|
const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
|
|
@@ -155,7 +198,7 @@ function renderHover(
|
|
|
155
198
|
output += `\n ${theme.fg("mdCodeBlockBorder", bottom)}`;
|
|
156
199
|
}
|
|
157
200
|
|
|
158
|
-
return
|
|
201
|
+
return output.split("\n");
|
|
159
202
|
}
|
|
160
203
|
|
|
161
204
|
/**
|
|
@@ -206,7 +249,7 @@ function renderDiagnostics(
|
|
|
206
249
|
lines: string[],
|
|
207
250
|
expanded: boolean,
|
|
208
251
|
theme: Theme,
|
|
209
|
-
):
|
|
252
|
+
): string[] {
|
|
210
253
|
const errorCount = errorMatch ? Number.parseInt(errorMatch[1], 10) : 0;
|
|
211
254
|
const warnCount = warningMatch ? Number.parseInt(warningMatch[1], 10) : 0;
|
|
212
255
|
|
|
@@ -253,7 +296,7 @@ function renderDiagnostics(
|
|
|
253
296
|
)}`;
|
|
254
297
|
}
|
|
255
298
|
}
|
|
256
|
-
return
|
|
299
|
+
return output.split("\n");
|
|
257
300
|
}
|
|
258
301
|
|
|
259
302
|
// Collapsed view
|
|
@@ -285,7 +328,7 @@ function renderDiagnostics(
|
|
|
285
328
|
)}`;
|
|
286
329
|
}
|
|
287
330
|
|
|
288
|
-
return
|
|
331
|
+
return output.split("\n");
|
|
289
332
|
}
|
|
290
333
|
|
|
291
334
|
// =============================================================================
|
|
@@ -295,7 +338,7 @@ function renderDiagnostics(
|
|
|
295
338
|
/**
|
|
296
339
|
* Render references grouped by file.
|
|
297
340
|
*/
|
|
298
|
-
function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme):
|
|
341
|
+
function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): string[] {
|
|
299
342
|
const refCount = Number.parseInt(refMatch[1], 10);
|
|
300
343
|
const icon =
|
|
301
344
|
refCount > 0 ? theme.styledSymbol("status.success", "success") : theme.styledSymbol("status.warning", "warning");
|
|
@@ -369,10 +412,10 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
369
412
|
};
|
|
370
413
|
|
|
371
414
|
if (expanded) {
|
|
372
|
-
return
|
|
415
|
+
return renderGrouped(files.length, 3, false).split("\n");
|
|
373
416
|
}
|
|
374
417
|
|
|
375
|
-
return
|
|
418
|
+
return renderGrouped(3, 1, true).split("\n");
|
|
376
419
|
}
|
|
377
420
|
|
|
378
421
|
// =============================================================================
|
|
@@ -382,7 +425,7 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
|
|
|
382
425
|
/**
|
|
383
426
|
* Render document symbols in a hierarchical tree.
|
|
384
427
|
*/
|
|
385
|
-
function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme):
|
|
428
|
+
function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): string[] {
|
|
386
429
|
const fileName = symbolsMatch[1];
|
|
387
430
|
const icon = theme.styledSymbol("status.info", "accent");
|
|
388
431
|
|
|
@@ -450,7 +493,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
450
493
|
output += `\n${prefix}${theme.fg("dim", branch)} ${theme.fg("accent", sym.icon)} ${theme.fg("accent", sym.name)}`;
|
|
451
494
|
output += `\n${prefix}${theme.fg("dim", detailPrefix)}${theme.fg("muted", `line ${sym.line}`)}`;
|
|
452
495
|
}
|
|
453
|
-
return
|
|
496
|
+
return output.split("\n");
|
|
454
497
|
}
|
|
455
498
|
|
|
456
499
|
// Collapsed: show first 3 top-level symbols
|
|
@@ -474,7 +517,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
474
517
|
)}`;
|
|
475
518
|
}
|
|
476
519
|
|
|
477
|
-
return
|
|
520
|
+
return output.split("\n");
|
|
478
521
|
}
|
|
479
522
|
|
|
480
523
|
// =============================================================================
|
|
@@ -484,7 +527,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
|
|
|
484
527
|
/**
|
|
485
528
|
* Generic fallback rendering for unknown result types.
|
|
486
529
|
*/
|
|
487
|
-
function renderGeneric(text: string, lines: string[], expanded: boolean, theme: Theme):
|
|
530
|
+
function renderGeneric(text: string, lines: string[], expanded: boolean, theme: Theme): string[] {
|
|
488
531
|
const hasError = text.includes("Error:") || text.includes(theme.status.error);
|
|
489
532
|
const hasSuccess = text.includes(theme.status.success) || text.includes("Applied");
|
|
490
533
|
|
|
@@ -502,7 +545,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
502
545
|
const branch = isLast ? theme.tree.last : theme.tree.branch;
|
|
503
546
|
output += `\n ${theme.fg("dim", branch)} ${lines[i]}`;
|
|
504
547
|
}
|
|
505
|
-
return
|
|
548
|
+
return output.split("\n");
|
|
506
549
|
}
|
|
507
550
|
|
|
508
551
|
const firstLine = lines[0] || "No output";
|
|
@@ -530,7 +573,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
|
|
|
530
573
|
}
|
|
531
574
|
}
|
|
532
575
|
|
|
533
|
-
return
|
|
576
|
+
return output.split("\n");
|
|
534
577
|
}
|
|
535
578
|
|
|
536
579
|
// =============================================================================
|
|
@@ -574,4 +617,5 @@ function severityToColor(severity: string): "error" | "warning" | "accent" | "di
|
|
|
574
617
|
export const lspToolRenderer = {
|
|
575
618
|
renderCall,
|
|
576
619
|
renderResult,
|
|
620
|
+
mergeCallAndResult: true,
|
|
577
621
|
};
|
package/src/main.ts
CHANGED
|
@@ -21,15 +21,17 @@ import { selectSession } from "./cli/session-picker";
|
|
|
21
21
|
import { parseSetupArgs, printSetupHelp, runSetupCommand } from "./cli/setup-cli";
|
|
22
22
|
import { parseStatsArgs, printStatsHelp, runStatsCommand } from "./cli/stats-cli";
|
|
23
23
|
import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
|
|
24
|
+
import { runCommitCommand } from "./commit";
|
|
25
|
+
import { parseCommitArgs, printCommitHelp } from "./commit/cli";
|
|
24
26
|
import { findConfigFile, getModelsPath, VERSION } from "./config";
|
|
25
27
|
import type { ModelRegistry } from "./config/model-registry";
|
|
26
28
|
import { parseModelPattern, parseModelString, resolveModelScope, type ScopedModel } from "./config/model-resolver";
|
|
27
29
|
import { SettingsManager } from "./config/settings-manager";
|
|
28
30
|
import { initializeWithSettings } from "./discovery";
|
|
29
|
-
import { exportFromFile } from "./export/html
|
|
31
|
+
import { exportFromFile } from "./export/html";
|
|
30
32
|
import type { ExtensionUIContext } from "./extensibility/extensions/types";
|
|
31
33
|
import { runMigrations, showDeprecationWarnings } from "./migrations";
|
|
32
|
-
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes
|
|
34
|
+
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes";
|
|
33
35
|
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./sdk";
|
|
34
36
|
import type { AgentSession } from "./session/agent-session";
|
|
35
37
|
import { type SessionInfo, SessionManager } from "./session/session-manager";
|
|
@@ -85,7 +87,7 @@ async function runInteractiveMode(
|
|
|
85
87
|
initialMessages: string[],
|
|
86
88
|
setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
|
87
89
|
lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined,
|
|
88
|
-
mcpManager: import("./mcp
|
|
90
|
+
mcpManager: import("./mcp").MCPManager | undefined,
|
|
89
91
|
initialMessage?: string,
|
|
90
92
|
initialImages?: ImageContent[],
|
|
91
93
|
): Promise<void> {
|
|
@@ -532,6 +534,17 @@ export async function main(args: string[]) {
|
|
|
532
534
|
return;
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
// Handle commit subcommand
|
|
538
|
+
const commitCmd = parseCommitArgs(args);
|
|
539
|
+
if (commitCmd) {
|
|
540
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
541
|
+
printCommitHelp();
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
await runCommitCommand(commitCmd);
|
|
545
|
+
process.exit(0);
|
|
546
|
+
}
|
|
547
|
+
|
|
535
548
|
const parsed = parseArgs(args);
|
|
536
549
|
time("parseArgs");
|
|
537
550
|
await maybeAutoChdir(parsed);
|
package/src/mcp/loader.ts
CHANGED
|
@@ -36,10 +36,10 @@ export interface MCPToolsLoadOptions {
|
|
|
36
36
|
cacheStorage?: AgentStorage | null;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function resolveToolCache(storage: AgentStorage | null | undefined): MCPToolCache | null {
|
|
39
|
+
async function resolveToolCache(storage: AgentStorage | null | undefined): Promise<MCPToolCache | null> {
|
|
40
40
|
if (storage === null) return null;
|
|
41
41
|
try {
|
|
42
|
-
const resolved = storage ?? AgentStorage.open();
|
|
42
|
+
const resolved = storage ?? (await AgentStorage.open());
|
|
43
43
|
return new MCPToolCache(resolved);
|
|
44
44
|
} catch (error) {
|
|
45
45
|
logger.warn("MCP tool cache unavailable", { error: String(error) });
|
|
@@ -55,7 +55,7 @@ function resolveToolCache(storage: AgentStorage | null | undefined): MCPToolCach
|
|
|
55
55
|
* @returns MCP tools in LoadedCustomTool format for integration
|
|
56
56
|
*/
|
|
57
57
|
export async function discoverAndLoadMCPTools(cwd: string, options?: MCPToolsLoadOptions): Promise<MCPToolsLoadResult> {
|
|
58
|
-
const toolCache = resolveToolCache(options?.cacheStorage);
|
|
58
|
+
const toolCache = await resolveToolCache(options?.cacheStorage);
|
|
59
59
|
const manager = new MCPManager(cwd, toolCache);
|
|
60
60
|
|
|
61
61
|
let result: MCPLoadResult;
|
package/src/migrations.ts
CHANGED
|
@@ -15,11 +15,11 @@ import { getAgentDbPath, getAgentDir, getBinDir } from "./config";
|
|
|
15
15
|
*
|
|
16
16
|
* @returns Array of provider names that were migrated
|
|
17
17
|
*/
|
|
18
|
-
export function migrateAuthToAgentDb(): string[] {
|
|
18
|
+
export async function migrateAuthToAgentDb(): Promise<string[]> {
|
|
19
19
|
const agentDir = getAgentDir();
|
|
20
20
|
const oauthPath = join(agentDir, "oauth.json");
|
|
21
21
|
const settingsPath = join(agentDir, "settings.json");
|
|
22
|
-
const storage = AgentStorage.open(getAgentDbPath(agentDir));
|
|
22
|
+
const storage = await AgentStorage.open(getAgentDbPath(agentDir));
|
|
23
23
|
|
|
24
24
|
const migrated: Record<string, AuthCredential[]> = {};
|
|
25
25
|
const providers: string[] = [];
|
|
@@ -179,7 +179,7 @@ export async function runMigrations(_cwd: string): Promise<{
|
|
|
179
179
|
deprecationWarnings: string[];
|
|
180
180
|
}> {
|
|
181
181
|
// Then: run data migrations
|
|
182
|
-
const migratedAuthProviders = migrateAuthToAgentDb();
|
|
182
|
+
const migratedAuthProviders = await migrateAuthToAgentDb();
|
|
183
183
|
migrateSessionsFromAgentRoot();
|
|
184
184
|
migrateToolsToBin();
|
|
185
185
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { hasPendingMermaid, prerenderMermaid } from "@oh-my-pi/pi-coding-agent/modes/theme/mermaid-cache";
|
|
2
3
|
import { getMarkdownTheme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
3
|
-
import { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { Container, getCapabilities, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Component that renders a complete assistant message
|
|
@@ -9,6 +10,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
9
10
|
private contentContainer: Container;
|
|
10
11
|
private hideThinkingBlock: boolean;
|
|
11
12
|
private lastMessage?: AssistantMessage;
|
|
13
|
+
private prerenderInFlight = false;
|
|
12
14
|
|
|
13
15
|
constructor(message?: AssistantMessage, hideThinkingBlock = false) {
|
|
14
16
|
super();
|
|
@@ -35,12 +37,38 @@ export class AssistantMessageComponent extends Container {
|
|
|
35
37
|
this.hideThinkingBlock = hide;
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
private triggerMermaidPrerender(message: AssistantMessage): void {
|
|
41
|
+
const caps = getCapabilities();
|
|
42
|
+
if (!caps.images || this.prerenderInFlight) return;
|
|
43
|
+
|
|
44
|
+
// Check if any text content has pending mermaid blocks
|
|
45
|
+
const hasPending = message.content.some((c) => c.type === "text" && c.text.trim() && hasPendingMermaid(c.text));
|
|
46
|
+
if (!hasPending) return;
|
|
47
|
+
|
|
48
|
+
this.prerenderInFlight = true;
|
|
49
|
+
|
|
50
|
+
// Fire off background prerender
|
|
51
|
+
(async () => {
|
|
52
|
+
for (const content of message.content) {
|
|
53
|
+
if (content.type === "text" && content.text.trim() && hasPendingMermaid(content.text)) {
|
|
54
|
+
await prerenderMermaid(content.text);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.prerenderInFlight = false;
|
|
58
|
+
// Invalidate to re-render with cached images
|
|
59
|
+
this.invalidate();
|
|
60
|
+
})();
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
updateContent(message: AssistantMessage): void {
|
|
39
64
|
this.lastMessage = message;
|
|
40
65
|
|
|
41
66
|
// Clear content container
|
|
42
67
|
this.contentContainer.clear();
|
|
43
68
|
|
|
69
|
+
// Trigger background mermaid pre-rendering if needed
|
|
70
|
+
this.triggerMermaidPrerender(message);
|
|
71
|
+
|
|
44
72
|
const hasVisibleContent = message.content.some(
|
|
45
73
|
(c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()),
|
|
46
74
|
);
|