@oh-my-pi/pi-coding-agent 3.37.1 → 4.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 +87 -0
- package/README.md +44 -3
- package/docs/extensions.md +29 -4
- package/docs/sdk.md +3 -3
- package/package.json +5 -5
- package/src/cli/args.ts +8 -0
- package/src/config.ts +5 -15
- package/src/core/agent-session.ts +193 -47
- package/src/core/auth-storage.ts +16 -3
- package/src/core/bash-executor.ts +79 -14
- package/src/core/custom-commands/types.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/export-html/index.ts +33 -1
- package/src/core/export-html/template.css +99 -0
- package/src/core/export-html/template.generated.ts +1 -1
- package/src/core/export-html/template.js +133 -8
- package/src/core/extensions/index.ts +22 -4
- package/src/core/extensions/loader.ts +152 -214
- package/src/core/extensions/runner.ts +139 -79
- package/src/core/extensions/types.ts +143 -19
- package/src/core/extensions/wrapper.ts +5 -8
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +2 -1
- package/src/core/keybindings.ts +4 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +35 -26
- package/src/core/sdk.ts +96 -76
- package/src/core/settings-manager.ts +45 -14
- package/src/core/system-prompt.ts +5 -15
- package/src/core/tools/bash.ts +115 -54
- package/src/core/tools/find.ts +86 -7
- package/src/core/tools/grep.ts +27 -6
- package/src/core/tools/index.ts +15 -6
- package/src/core/tools/ls.ts +49 -18
- package/src/core/tools/render-utils.ts +2 -1
- package/src/core/tools/task/worker.ts +35 -12
- package/src/core/tools/web-search/auth.ts +37 -32
- package/src/core/tools/web-search/providers/anthropic.ts +35 -22
- package/src/index.ts +101 -9
- package/src/main.ts +60 -20
- package/src/migrations.ts +47 -2
- package/src/modes/index.ts +2 -2
- package/src/modes/interactive/components/assistant-message.ts +25 -7
- package/src/modes/interactive/components/bash-execution.ts +5 -0
- package/src/modes/interactive/components/branch-summary-message.ts +5 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +5 -0
- package/src/modes/interactive/components/countdown-timer.ts +38 -0
- package/src/modes/interactive/components/custom-editor.ts +8 -0
- package/src/modes/interactive/components/custom-message.ts +5 -0
- package/src/modes/interactive/components/footer.ts +2 -5
- package/src/modes/interactive/components/hook-input.ts +29 -20
- package/src/modes/interactive/components/hook-selector.ts +52 -38
- package/src/modes/interactive/components/index.ts +39 -0
- package/src/modes/interactive/components/login-dialog.ts +160 -0
- package/src/modes/interactive/components/model-selector.ts +10 -2
- package/src/modes/interactive/components/session-selector.ts +5 -1
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line/segments.ts +3 -3
- package/src/modes/interactive/components/tool-execution.ts +9 -16
- package/src/modes/interactive/components/tree-selector.ts +1 -6
- package/src/modes/interactive/interactive-mode.ts +466 -215
- package/src/modes/interactive/theme/theme.ts +50 -2
- package/src/modes/print-mode.ts +78 -31
- package/src/modes/rpc/rpc-mode.ts +186 -78
- package/src/modes/rpc/rpc-types.ts +10 -3
- package/src/prompts/system-prompt.md +36 -28
- package/src/utils/clipboard.ts +90 -50
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/tools-manager.ts +2 -2
package/src/index.ts
CHANGED
|
@@ -60,11 +60,40 @@ export type {
|
|
|
60
60
|
RenderResultOptions,
|
|
61
61
|
} from "./core/custom-tools/index";
|
|
62
62
|
export { discoverAndLoadCustomTools, loadCustomTools } from "./core/custom-tools/index";
|
|
63
|
-
// Extension types
|
|
64
|
-
export type {
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
// Extension types and utilities
|
|
64
|
+
export type {
|
|
65
|
+
AppAction,
|
|
66
|
+
Extension,
|
|
67
|
+
ExtensionActions,
|
|
68
|
+
ExtensionAPI,
|
|
69
|
+
ExtensionCommandContext,
|
|
70
|
+
ExtensionCommandContextActions,
|
|
71
|
+
ExtensionContext,
|
|
72
|
+
ExtensionContextActions,
|
|
73
|
+
ExtensionError,
|
|
74
|
+
ExtensionEvent,
|
|
75
|
+
ExtensionFactory,
|
|
76
|
+
ExtensionFlag,
|
|
77
|
+
ExtensionHandler,
|
|
78
|
+
ExtensionRuntime,
|
|
79
|
+
ExtensionShortcut,
|
|
80
|
+
ExtensionUIContext,
|
|
81
|
+
ExtensionUIDialogOptions,
|
|
82
|
+
KeybindingsManager,
|
|
83
|
+
LoadExtensionsResult,
|
|
84
|
+
MessageRenderer,
|
|
85
|
+
MessageRenderOptions,
|
|
86
|
+
RegisteredCommand,
|
|
87
|
+
ToolResultEvent,
|
|
88
|
+
TurnEndEvent,
|
|
89
|
+
TurnStartEvent,
|
|
90
|
+
UserBashEvent,
|
|
91
|
+
UserBashEventResult,
|
|
92
|
+
} from "./core/extensions/index";
|
|
67
93
|
export {
|
|
94
|
+
createExtensionRuntime,
|
|
95
|
+
discoverAndLoadExtensions,
|
|
96
|
+
ExtensionRunner,
|
|
68
97
|
isBashToolResult,
|
|
69
98
|
isEditToolResult,
|
|
70
99
|
isFindToolResult,
|
|
@@ -72,7 +101,9 @@ export {
|
|
|
72
101
|
isLsToolResult,
|
|
73
102
|
isReadToolResult,
|
|
74
103
|
isWriteToolResult,
|
|
75
|
-
} from "./core/
|
|
104
|
+
} from "./core/extensions/index";
|
|
105
|
+
// Hook system types (legacy re-export)
|
|
106
|
+
export type * from "./core/hooks/index";
|
|
76
107
|
// Logging
|
|
77
108
|
export { type Logger, logger } from "./core/logger";
|
|
78
109
|
export { convertToLlm } from "./core/messages";
|
|
@@ -153,25 +184,86 @@ export {
|
|
|
153
184
|
} from "./core/skills";
|
|
154
185
|
// Slash commands
|
|
155
186
|
export { type FileSlashCommand, loadSlashCommands as discoverSlashCommands } from "./core/slash-commands";
|
|
156
|
-
// Tools (detail types
|
|
187
|
+
// Tools (detail types and utilities)
|
|
157
188
|
export {
|
|
189
|
+
type BashOperations,
|
|
158
190
|
type BashToolDetails,
|
|
191
|
+
DEFAULT_MAX_BYTES,
|
|
192
|
+
DEFAULT_MAX_LINES,
|
|
193
|
+
type FindOperations,
|
|
159
194
|
type FindToolDetails,
|
|
195
|
+
type FindToolOptions,
|
|
196
|
+
formatSize,
|
|
160
197
|
type GitToolDetails,
|
|
198
|
+
type GrepOperations,
|
|
161
199
|
type GrepToolDetails,
|
|
200
|
+
type GrepToolOptions,
|
|
162
201
|
gitTool,
|
|
202
|
+
type LsOperations,
|
|
163
203
|
type LsToolDetails,
|
|
204
|
+
type LsToolOptions,
|
|
164
205
|
type ReadToolDetails,
|
|
206
|
+
type TruncationOptions,
|
|
165
207
|
type TruncationResult,
|
|
208
|
+
truncateHead,
|
|
209
|
+
truncateLine,
|
|
210
|
+
truncateTail,
|
|
166
211
|
type WriteToolDetails,
|
|
167
212
|
} from "./core/tools/index";
|
|
168
213
|
export type { FileDiagnosticsResult } from "./core/tools/lsp/index";
|
|
169
214
|
// Main entry point
|
|
170
215
|
export { main } from "./main";
|
|
171
|
-
//
|
|
172
|
-
export {
|
|
216
|
+
// Run modes for programmatic SDK usage
|
|
217
|
+
export {
|
|
218
|
+
InteractiveMode,
|
|
219
|
+
type InteractiveModeOptions,
|
|
220
|
+
type PrintModeOptions,
|
|
221
|
+
runPrintMode,
|
|
222
|
+
runRpcMode,
|
|
223
|
+
} from "./modes/index";
|
|
224
|
+
// UI components for extensions
|
|
225
|
+
export {
|
|
226
|
+
ArminComponent,
|
|
227
|
+
AssistantMessageComponent,
|
|
228
|
+
BashExecutionComponent,
|
|
229
|
+
BorderedLoader,
|
|
230
|
+
BranchSummaryMessageComponent,
|
|
231
|
+
CompactionSummaryMessageComponent,
|
|
232
|
+
CustomEditor,
|
|
233
|
+
CustomMessageComponent,
|
|
234
|
+
DynamicBorder,
|
|
235
|
+
FooterComponent,
|
|
236
|
+
HookEditorComponent as ExtensionEditorComponent,
|
|
237
|
+
HookInputComponent as ExtensionInputComponent,
|
|
238
|
+
HookSelectorComponent as ExtensionSelectorComponent,
|
|
239
|
+
LoginDialogComponent,
|
|
240
|
+
ModelSelectorComponent,
|
|
241
|
+
OAuthSelectorComponent,
|
|
242
|
+
type RenderDiffOptions,
|
|
243
|
+
renderDiff,
|
|
244
|
+
SessionSelectorComponent,
|
|
245
|
+
type SettingsCallbacks,
|
|
246
|
+
SettingsSelectorComponent,
|
|
247
|
+
ShowImagesSelectorComponent,
|
|
248
|
+
ThemeSelectorComponent,
|
|
249
|
+
ThinkingSelectorComponent,
|
|
250
|
+
ToolExecutionComponent,
|
|
251
|
+
type ToolExecutionOptions,
|
|
252
|
+
TreeSelectorComponent,
|
|
253
|
+
truncateToVisualLines,
|
|
254
|
+
UserMessageComponent,
|
|
255
|
+
UserMessageSelectorComponent,
|
|
256
|
+
type VisualTruncateResult,
|
|
257
|
+
} from "./modes/interactive/components/index";
|
|
173
258
|
// Theme utilities for custom tools
|
|
174
|
-
export {
|
|
259
|
+
export {
|
|
260
|
+
getMarkdownTheme,
|
|
261
|
+
getSelectListTheme,
|
|
262
|
+
getSettingsListTheme,
|
|
263
|
+
initTheme,
|
|
264
|
+
Theme,
|
|
265
|
+
type ThemeColor,
|
|
266
|
+
} from "./modes/interactive/theme/theme";
|
|
175
267
|
|
|
176
268
|
// TypeBox helper for string enums (convenience for custom tools)
|
|
177
269
|
import { type TSchema, Type } from "@sinclair/typebox";
|
package/src/main.ts
CHANGED
|
@@ -30,7 +30,6 @@ import { runMigrations, showDeprecationWarnings } from "./migrations";
|
|
|
30
30
|
import { InteractiveMode, installTerminalCrashHandlers, runPrintMode, runRpcMode } from "./modes/index";
|
|
31
31
|
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme";
|
|
32
32
|
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
|
|
33
|
-
import { ensureTool } from "./utils/tools-manager";
|
|
34
33
|
|
|
35
34
|
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
|
|
36
35
|
try {
|
|
@@ -63,9 +62,8 @@ async function runInteractiveMode(
|
|
|
63
62
|
lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined,
|
|
64
63
|
initialMessage?: string,
|
|
65
64
|
initialImages?: ImageContent[],
|
|
66
|
-
fdPath: string | undefined = undefined,
|
|
67
65
|
): Promise<void> {
|
|
68
|
-
const mode = new InteractiveMode(session, version, changelogMarkdown, setExtensionUIContext, lspServers
|
|
66
|
+
const mode = new InteractiveMode(session, version, changelogMarkdown, setExtensionUIContext, lspServers);
|
|
69
67
|
|
|
70
68
|
await mode.init();
|
|
71
69
|
|
|
@@ -145,6 +143,28 @@ async function prepareInitialMessage(
|
|
|
145
143
|
};
|
|
146
144
|
}
|
|
147
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Resolve a session argument to a file path.
|
|
148
|
+
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
|
|
149
|
+
*/
|
|
150
|
+
function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): string {
|
|
151
|
+
// If it looks like a file path, use as-is
|
|
152
|
+
if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
|
|
153
|
+
return sessionArg;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Try to match as session ID (full or partial UUID)
|
|
157
|
+
const sessions = SessionManager.list(cwd, sessionDir);
|
|
158
|
+
const matches = sessions.filter((session) => session.id.startsWith(sessionArg));
|
|
159
|
+
|
|
160
|
+
if (matches.length >= 1) {
|
|
161
|
+
return matches[0].path; // Already sorted by modified time (most recent first)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// No match - return original (will create new session)
|
|
165
|
+
return sessionArg;
|
|
166
|
+
}
|
|
167
|
+
|
|
148
168
|
function getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): string | undefined {
|
|
149
169
|
if (parsed.continue || parsed.resume) {
|
|
150
170
|
return undefined;
|
|
@@ -175,7 +195,8 @@ async function createSessionManager(parsed: Args, cwd: string): Promise<SessionM
|
|
|
175
195
|
return SessionManager.inMemory();
|
|
176
196
|
}
|
|
177
197
|
if (parsed.session) {
|
|
178
|
-
|
|
198
|
+
const resolvedPath = resolveSessionPath(parsed.session, cwd, parsed.sessionDir);
|
|
199
|
+
return await SessionManager.open(resolvedPath, parsed.sessionDir);
|
|
179
200
|
}
|
|
180
201
|
if (parsed.continue) {
|
|
181
202
|
return await SessionManager.continueRecent(cwd, parsed.sessionDir);
|
|
@@ -299,13 +320,24 @@ async function buildSessionOptions(
|
|
|
299
320
|
// Thinking level
|
|
300
321
|
if (parsed.thinking) {
|
|
301
322
|
options.thinkingLevel = parsed.thinking;
|
|
302
|
-
} else if (
|
|
323
|
+
} else if (
|
|
324
|
+
scopedModels.length > 0 &&
|
|
325
|
+
scopedModels[0].explicitThinkingLevel === true &&
|
|
326
|
+
!parsed.continue &&
|
|
327
|
+
!parsed.resume
|
|
328
|
+
) {
|
|
303
329
|
options.thinkingLevel = scopedModels[0].thinkingLevel;
|
|
304
330
|
}
|
|
305
331
|
|
|
306
|
-
// Scoped models for Ctrl+P cycling
|
|
332
|
+
// Scoped models for Ctrl+P cycling - fill in default thinking levels when not explicit
|
|
307
333
|
if (scopedModels.length > 0) {
|
|
308
|
-
|
|
334
|
+
const defaultThinkingLevel = settingsManager.getDefaultThinkingLevel() ?? "off";
|
|
335
|
+
options.scopedModels = scopedModels.map((scopedModel) => ({
|
|
336
|
+
model: scopedModel.model,
|
|
337
|
+
thinkingLevel: scopedModel.explicitThinkingLevel
|
|
338
|
+
? (scopedModel.thinkingLevel ?? defaultThinkingLevel)
|
|
339
|
+
: defaultThinkingLevel,
|
|
340
|
+
}));
|
|
309
341
|
}
|
|
310
342
|
|
|
311
343
|
// API key from CLI - set in authStorage
|
|
@@ -321,7 +353,9 @@ async function buildSessionOptions(
|
|
|
321
353
|
}
|
|
322
354
|
|
|
323
355
|
// Tools
|
|
324
|
-
if (parsed.
|
|
356
|
+
if (parsed.noTools) {
|
|
357
|
+
options.toolNames = parsed.tools && parsed.tools.length > 0 ? parsed.tools : [];
|
|
358
|
+
} else if (parsed.tools) {
|
|
325
359
|
options.toolNames = parsed.tools;
|
|
326
360
|
}
|
|
327
361
|
|
|
@@ -344,6 +378,10 @@ async function buildSessionOptions(
|
|
|
344
378
|
options.additionalExtensionPaths = cliExtensionPaths;
|
|
345
379
|
}
|
|
346
380
|
|
|
381
|
+
if (parsed.noExtensions) {
|
|
382
|
+
options.disableExtensionDiscovery = true;
|
|
383
|
+
}
|
|
384
|
+
|
|
347
385
|
return options;
|
|
348
386
|
}
|
|
349
387
|
|
|
@@ -504,7 +542,7 @@ export async function main(args: string[]) {
|
|
|
504
542
|
}
|
|
505
543
|
|
|
506
544
|
time("buildSessionOptions");
|
|
507
|
-
const { session,
|
|
545
|
+
const { session, setToolUIContext, modelFallbackMessage, lspServers } = await createAgentSession(sessionOptions);
|
|
508
546
|
time("createAgentSession");
|
|
509
547
|
|
|
510
548
|
// Re-parse CLI args with extension flags and apply values
|
|
@@ -550,19 +588,17 @@ export async function main(args: string[]) {
|
|
|
550
588
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
551
589
|
const changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);
|
|
552
590
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
591
|
+
const scopedModelsForDisplay = sessionOptions.scopedModels ?? scopedModels;
|
|
592
|
+
if (scopedModelsForDisplay.length > 0) {
|
|
593
|
+
const modelList = scopedModelsForDisplay
|
|
594
|
+
.map((scopedModel) => {
|
|
595
|
+
const thinkingStr = scopedModel.thinkingLevel !== "off" ? `:${scopedModel.thinkingLevel}` : "";
|
|
596
|
+
return `${scopedModel.model.id}${thinkingStr}`;
|
|
558
597
|
})
|
|
559
598
|
.join(", ");
|
|
560
599
|
console.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray("(Ctrl+P to cycle)")}`));
|
|
561
600
|
}
|
|
562
601
|
|
|
563
|
-
const fdPath = await ensureTool("fd");
|
|
564
|
-
time("ensureTool(fd)");
|
|
565
|
-
|
|
566
602
|
installTerminalCrashHandlers();
|
|
567
603
|
printTimings();
|
|
568
604
|
await runInteractiveMode(
|
|
@@ -574,14 +610,18 @@ export async function main(args: string[]) {
|
|
|
574
610
|
migratedProviders,
|
|
575
611
|
versionCheckPromise,
|
|
576
612
|
parsed.messages,
|
|
577
|
-
|
|
613
|
+
setToolUIContext,
|
|
578
614
|
lspServers,
|
|
579
615
|
initialMessage,
|
|
580
616
|
initialImages,
|
|
581
|
-
fdPath,
|
|
582
617
|
);
|
|
583
618
|
} else {
|
|
584
|
-
await runPrintMode(session,
|
|
619
|
+
await runPrintMode(session, {
|
|
620
|
+
mode,
|
|
621
|
+
messages: parsed.messages,
|
|
622
|
+
initialMessage,
|
|
623
|
+
initialImages,
|
|
624
|
+
});
|
|
585
625
|
stopThemeWatcher();
|
|
586
626
|
if (process.stdout.writableLength > 0) {
|
|
587
627
|
await new Promise<void>((resolve) => process.stdout.once("drain", resolve));
|
package/src/migrations.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* One-time migrations that run on startup.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import chalk from "chalk";
|
|
8
|
-
import { getAgentDir } from "./config";
|
|
8
|
+
import { getAgentDir, getBinDir } from "./config";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Migrate PI_* environment variables to OMP_* equivalents.
|
|
@@ -143,6 +143,50 @@ export function migrateSessionsFromAgentRoot(): void {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Move fd/rg binaries from tools/ to bin/ if they exist.
|
|
148
|
+
*/
|
|
149
|
+
function migrateToolsToBin(): void {
|
|
150
|
+
const agentDir = getAgentDir();
|
|
151
|
+
const toolsDir = join(agentDir, "tools");
|
|
152
|
+
const binDir = getBinDir();
|
|
153
|
+
|
|
154
|
+
if (!existsSync(toolsDir)) return;
|
|
155
|
+
|
|
156
|
+
const binaries = ["fd", "rg", "fd.exe", "rg.exe"];
|
|
157
|
+
let movedAny = false;
|
|
158
|
+
|
|
159
|
+
for (const bin of binaries) {
|
|
160
|
+
const oldPath = join(toolsDir, bin);
|
|
161
|
+
const newPath = join(binDir, bin);
|
|
162
|
+
|
|
163
|
+
if (existsSync(oldPath)) {
|
|
164
|
+
if (!existsSync(binDir)) {
|
|
165
|
+
mkdirSync(binDir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
if (!existsSync(newPath)) {
|
|
168
|
+
try {
|
|
169
|
+
renameSync(oldPath, newPath);
|
|
170
|
+
movedAny = true;
|
|
171
|
+
} catch {
|
|
172
|
+
// Ignore errors
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
// Target exists, just delete the old one
|
|
176
|
+
try {
|
|
177
|
+
rmSync(oldPath, { force: true });
|
|
178
|
+
} catch {
|
|
179
|
+
// Ignore
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (movedAny) {
|
|
186
|
+
console.log(chalk.green(`Migrated managed binaries tools/ → bin/`));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
146
190
|
/**
|
|
147
191
|
* Run all migrations. Called once on startup.
|
|
148
192
|
*
|
|
@@ -159,6 +203,7 @@ export async function runMigrations(_cwd: string): Promise<{
|
|
|
159
203
|
// Then: run data migrations
|
|
160
204
|
const migratedAuthProviders = migrateAuthToAuthJson();
|
|
161
205
|
migrateSessionsFromAgentRoot();
|
|
206
|
+
migrateToolsToBin();
|
|
162
207
|
|
|
163
208
|
// Collect deprecation warnings
|
|
164
209
|
const deprecationWarnings: string[] = [];
|
package/src/modes/index.ts
CHANGED
|
@@ -41,8 +41,8 @@ export function installTerminalCrashHandlers(): void {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export { InteractiveMode } from "./interactive/interactive-mode";
|
|
45
|
-
export { runPrintMode } from "./print-mode";
|
|
44
|
+
export { InteractiveMode, type InteractiveModeOptions } from "./interactive/interactive-mode";
|
|
45
|
+
export { type PrintModeOptions, runPrintMode } from "./print-mode";
|
|
46
46
|
export { type ModelInfo, RpcClient, type RpcClientOptions, type RpcEventListener } from "./rpc/rpc-client";
|
|
47
47
|
export { runRpcMode } from "./rpc/rpc-mode";
|
|
48
48
|
export type { RpcCommand, RpcResponse, RpcSessionState } from "./rpc/rpc-types";
|
|
@@ -8,6 +8,7 @@ import { getMarkdownTheme, theme } from "../theme/theme";
|
|
|
8
8
|
export class AssistantMessageComponent extends Container {
|
|
9
9
|
private contentContainer: Container;
|
|
10
10
|
private hideThinkingBlock: boolean;
|
|
11
|
+
private lastMessage?: AssistantMessage;
|
|
11
12
|
|
|
12
13
|
constructor(message?: AssistantMessage, hideThinkingBlock = false) {
|
|
13
14
|
super();
|
|
@@ -23,20 +24,28 @@ export class AssistantMessageComponent extends Container {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
override invalidate(): void {
|
|
28
|
+
super.invalidate();
|
|
29
|
+
if (this.lastMessage) {
|
|
30
|
+
this.updateContent(this.lastMessage);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
setHideThinkingBlock(hide: boolean): void {
|
|
27
35
|
this.hideThinkingBlock = hide;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
updateContent(message: AssistantMessage): void {
|
|
39
|
+
this.lastMessage = message;
|
|
40
|
+
|
|
31
41
|
// Clear content container
|
|
32
42
|
this.contentContainer.clear();
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
) {
|
|
44
|
+
const hasVisibleContent = message.content.some(
|
|
45
|
+
(c) => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (hasVisibleContent) {
|
|
40
49
|
this.contentContainer.addChild(new Spacer(1));
|
|
41
50
|
}
|
|
42
51
|
|
|
@@ -75,7 +84,16 @@ export class AssistantMessageComponent extends Container {
|
|
|
75
84
|
const hasToolCalls = message.content.some((c) => c.type === "toolCall");
|
|
76
85
|
if (!hasToolCalls) {
|
|
77
86
|
if (message.stopReason === "aborted") {
|
|
78
|
-
|
|
87
|
+
const abortMessage =
|
|
88
|
+
message.errorMessage && message.errorMessage !== "Request was aborted"
|
|
89
|
+
? message.errorMessage
|
|
90
|
+
: "Operation aborted";
|
|
91
|
+
if (hasVisibleContent) {
|
|
92
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
93
|
+
} else {
|
|
94
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
95
|
+
}
|
|
96
|
+
this.contentContainer.addChild(new Text(theme.fg("error", abortMessage), 1, 0));
|
|
79
97
|
} else if (message.stopReason === "error") {
|
|
80
98
|
const errorMsg = message.errorMessage || "Unknown error";
|
|
81
99
|
this.contentContainer.addChild(new Spacer(1));
|
|
@@ -72,6 +72,11 @@ export class BashExecutionComponent extends Container {
|
|
|
72
72
|
this.updateDisplay();
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
override invalidate(): void {
|
|
76
|
+
super.invalidate();
|
|
77
|
+
this.updateDisplay();
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
appendOutput(chunk: string): void {
|
|
76
81
|
// Strip ANSI codes and normalize line endings
|
|
77
82
|
// Note: binary data is already sanitized in tui-renderer.ts executeBashCommand
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable countdown timer for dialog components.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TUI } from "@oh-my-pi/pi-tui";
|
|
6
|
+
|
|
7
|
+
export class CountdownTimer {
|
|
8
|
+
private intervalId: ReturnType<typeof setInterval> | undefined;
|
|
9
|
+
private remainingSeconds: number;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
timeoutMs: number,
|
|
13
|
+
private tui: TUI | undefined,
|
|
14
|
+
private onTick: (seconds: number) => void,
|
|
15
|
+
private onExpire: () => void,
|
|
16
|
+
) {
|
|
17
|
+
this.remainingSeconds = Math.ceil(timeoutMs / 1000);
|
|
18
|
+
this.onTick(this.remainingSeconds);
|
|
19
|
+
|
|
20
|
+
this.intervalId = setInterval(() => {
|
|
21
|
+
this.remainingSeconds--;
|
|
22
|
+
this.onTick(this.remainingSeconds);
|
|
23
|
+
this.tui?.requestRender();
|
|
24
|
+
|
|
25
|
+
if (this.remainingSeconds <= 0) {
|
|
26
|
+
this.dispose();
|
|
27
|
+
this.onExpire();
|
|
28
|
+
}
|
|
29
|
+
}, 1000);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
dispose(): void {
|
|
33
|
+
if (this.intervalId) {
|
|
34
|
+
clearInterval(this.intervalId);
|
|
35
|
+
this.intervalId = undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -38,6 +38,8 @@ export class CustomEditor extends Editor {
|
|
|
38
38
|
public onCtrlY?: () => void;
|
|
39
39
|
/** Called when Ctrl+V is pressed. Returns true if handled (image found), false to fall through to text paste. */
|
|
40
40
|
public onCtrlV?: () => Promise<boolean>;
|
|
41
|
+
/** Called when Alt+Up is pressed (dequeue keybinding). */
|
|
42
|
+
public onAltUp?: () => void;
|
|
41
43
|
|
|
42
44
|
/** Custom key handlers from extensions */
|
|
43
45
|
private customKeyHandlers = new Map<KeyId, () => void>();
|
|
@@ -157,6 +159,12 @@ export class CustomEditor extends Editor {
|
|
|
157
159
|
return;
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
// Intercept Alt+Up for dequeue (restore queued message to editor)
|
|
163
|
+
if (matchesKey(data, "alt+up") && this.onAltUp) {
|
|
164
|
+
this.onAltUp();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
160
168
|
// Intercept ? when editor is empty to show hotkeys
|
|
161
169
|
if (data === "?" && this.getText().length === 0 && this.onQuestionMark) {
|
|
162
170
|
this.onQuestionMark();
|
|
@@ -3,6 +3,7 @@ import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
|
3
3
|
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
5
|
import type { AgentSession } from "../../../core/agent-session";
|
|
6
|
+
import { shortenPath } from "../../../core/tools/render-utils";
|
|
6
7
|
import { theme } from "../theme/theme";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -199,11 +200,7 @@ export class FooterComponent implements Component {
|
|
|
199
200
|
};
|
|
200
201
|
|
|
201
202
|
// Replace home directory with ~
|
|
202
|
-
let pwd = process.cwd();
|
|
203
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
204
|
-
if (home && pwd.startsWith(home)) {
|
|
205
|
-
pwd = `~${pwd.slice(home.length)}`;
|
|
206
|
-
}
|
|
203
|
+
let pwd = shortenPath(process.cwd());
|
|
207
204
|
|
|
208
205
|
// Add git branch if available
|
|
209
206
|
const branch = this.getCurrentBranch();
|
|
@@ -2,63 +2,72 @@
|
|
|
2
2
|
* Simple text input component for hooks.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Container, Input, isEnter, isEscape, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { Container, Input, isEnter, isEscape, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { theme } from "../theme/theme";
|
|
7
|
+
import { CountdownTimer } from "./countdown-timer";
|
|
7
8
|
import { DynamicBorder } from "./dynamic-border";
|
|
8
9
|
|
|
10
|
+
export interface HookInputOptions {
|
|
11
|
+
tui?: TUI;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
export class HookInputComponent extends Container {
|
|
10
16
|
private input: Input;
|
|
11
17
|
private onSubmitCallback: (value: string) => void;
|
|
12
18
|
private onCancelCallback: () => void;
|
|
19
|
+
private titleText: Text;
|
|
20
|
+
private baseTitle: string;
|
|
21
|
+
private countdown: CountdownTimer | undefined;
|
|
13
22
|
|
|
14
23
|
constructor(
|
|
15
24
|
title: string,
|
|
16
25
|
_placeholder: string | undefined,
|
|
17
26
|
onSubmit: (value: string) => void,
|
|
18
27
|
onCancel: () => void,
|
|
28
|
+
opts?: HookInputOptions,
|
|
19
29
|
) {
|
|
20
30
|
super();
|
|
21
31
|
|
|
22
32
|
this.onSubmitCallback = onSubmit;
|
|
23
33
|
this.onCancelCallback = onCancel;
|
|
34
|
+
this.baseTitle = title;
|
|
24
35
|
|
|
25
|
-
// Add top border
|
|
26
36
|
this.addChild(new DynamicBorder());
|
|
27
37
|
this.addChild(new Spacer(1));
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
this.addChild(
|
|
39
|
+
this.titleText = new Text(theme.fg("accent", title), 1, 0);
|
|
40
|
+
this.addChild(this.titleText);
|
|
31
41
|
this.addChild(new Spacer(1));
|
|
32
42
|
|
|
33
|
-
|
|
43
|
+
if (opts?.timeout && opts.timeout > 0 && opts.tui) {
|
|
44
|
+
this.countdown = new CountdownTimer(
|
|
45
|
+
opts.timeout,
|
|
46
|
+
opts.tui,
|
|
47
|
+
(s) => this.titleText.setText(theme.fg("accent", `${this.baseTitle} (${s}s)`)),
|
|
48
|
+
() => this.onCancelCallback(),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
this.input = new Input();
|
|
35
53
|
this.addChild(this.input);
|
|
36
|
-
|
|
37
54
|
this.addChild(new Spacer(1));
|
|
38
|
-
|
|
39
|
-
// Add hint
|
|
40
55
|
this.addChild(new Text(theme.fg("dim", "enter submit esc cancel"), 1, 0));
|
|
41
|
-
|
|
42
56
|
this.addChild(new Spacer(1));
|
|
43
|
-
|
|
44
|
-
// Add bottom border
|
|
45
57
|
this.addChild(new DynamicBorder());
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
handleInput(keyData: string): void {
|
|
49
|
-
// Enter
|
|
50
61
|
if (isEnter(keyData) || keyData === "\n") {
|
|
51
62
|
this.onSubmitCallback(this.input.getValue());
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Escape to cancel
|
|
56
|
-
if (isEscape(keyData)) {
|
|
63
|
+
} else if (isEscape(keyData)) {
|
|
57
64
|
this.onCancelCallback();
|
|
58
|
-
|
|
65
|
+
} else {
|
|
66
|
+
this.input.handleInput(keyData);
|
|
59
67
|
}
|
|
68
|
+
}
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
this.
|
|
70
|
+
dispose(): void {
|
|
71
|
+
this.countdown?.dispose();
|
|
63
72
|
}
|
|
64
73
|
}
|