@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
|
@@ -5,41 +5,43 @@
|
|
|
5
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
8
|
-
import { theme } from "../../modes/interactive/theme/theme";
|
|
8
|
+
import { type Theme, theme } from "../../modes/interactive/theme/theme";
|
|
9
|
+
import { logger } from "../logger";
|
|
9
10
|
import type { ModelRegistry } from "../model-registry";
|
|
10
11
|
import type { SessionManager } from "../session-manager";
|
|
11
12
|
import type {
|
|
12
|
-
AppendEntryHandler,
|
|
13
13
|
BeforeAgentStartEvent,
|
|
14
14
|
BeforeAgentStartEventResult,
|
|
15
15
|
ContextEvent,
|
|
16
16
|
ContextEventResult,
|
|
17
|
+
Extension,
|
|
18
|
+
ExtensionActions,
|
|
17
19
|
ExtensionCommandContext,
|
|
20
|
+
ExtensionCommandContextActions,
|
|
18
21
|
ExtensionContext,
|
|
22
|
+
ExtensionContextActions,
|
|
19
23
|
ExtensionError,
|
|
20
24
|
ExtensionEvent,
|
|
21
25
|
ExtensionFlag,
|
|
26
|
+
ExtensionRuntime,
|
|
22
27
|
ExtensionShortcut,
|
|
23
28
|
ExtensionUIContext,
|
|
24
|
-
GetActiveToolsHandler,
|
|
25
|
-
GetAllToolsHandler,
|
|
26
|
-
LoadedExtension,
|
|
27
29
|
MessageRenderer,
|
|
28
30
|
RegisteredCommand,
|
|
29
31
|
RegisteredTool,
|
|
30
|
-
SendMessageHandler,
|
|
31
32
|
SessionBeforeCompactResult,
|
|
32
33
|
SessionBeforeTreeResult,
|
|
33
|
-
SetActiveToolsHandler,
|
|
34
34
|
ToolCallEvent,
|
|
35
35
|
ToolCallEventResult,
|
|
36
36
|
ToolResultEventResult,
|
|
37
|
+
UserBashEvent,
|
|
38
|
+
UserBashEventResult,
|
|
37
39
|
} from "./types";
|
|
38
40
|
|
|
39
41
|
/** Combined result from all before_agent_start handlers */
|
|
40
42
|
interface BeforeAgentStartCombinedResult {
|
|
41
43
|
messages?: NonNullable<BeforeAgentStartEventResult["message"]>[];
|
|
42
|
-
|
|
44
|
+
systemPrompt?: string;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export type ExtensionErrorListener = (error: ExtensionError) => void;
|
|
@@ -56,27 +58,49 @@ export type NavigateTreeHandler = (
|
|
|
56
58
|
options?: { summarize?: boolean },
|
|
57
59
|
) => Promise<{ cancelled: boolean }>;
|
|
58
60
|
|
|
61
|
+
export type ShutdownHandler = () => void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Helper function to emit session_shutdown event to extensions.
|
|
65
|
+
* Returns true if the event was emitted, false if there were no handlers.
|
|
66
|
+
*/
|
|
67
|
+
export async function emitSessionShutdownEvent(extensionRunner: ExtensionRunner | undefined): Promise<boolean> {
|
|
68
|
+
if (extensionRunner?.hasHandlers("session_shutdown")) {
|
|
69
|
+
await extensionRunner.emit({
|
|
70
|
+
type: "session_shutdown",
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
59
77
|
const noOpUIContext: ExtensionUIContext = {
|
|
60
|
-
select: async () => undefined,
|
|
61
|
-
confirm: async () => false,
|
|
62
|
-
input: async () => undefined,
|
|
78
|
+
select: async (_title, _options, _dialogOptions) => undefined,
|
|
79
|
+
confirm: async (_title, _message, _dialogOptions) => false,
|
|
80
|
+
input: async (_title, _placeholder, _dialogOptions) => undefined,
|
|
63
81
|
notify: () => {},
|
|
64
82
|
setStatus: () => {},
|
|
65
83
|
setWidget: () => {},
|
|
84
|
+
setFooter: () => {},
|
|
85
|
+
setHeader: () => {},
|
|
66
86
|
setTitle: () => {},
|
|
67
87
|
custom: async () => undefined as never,
|
|
68
88
|
setEditorText: () => {},
|
|
69
89
|
getEditorText: () => "",
|
|
70
90
|
editor: async () => undefined,
|
|
91
|
+
setEditorComponent: () => {},
|
|
71
92
|
get theme() {
|
|
72
93
|
return theme;
|
|
73
94
|
},
|
|
95
|
+
getAllThemes: () => [],
|
|
96
|
+
getTheme: () => undefined,
|
|
97
|
+
setTheme: (_theme: string | Theme) => ({ success: false, error: "UI not available" }),
|
|
74
98
|
};
|
|
75
99
|
|
|
76
100
|
export class ExtensionRunner {
|
|
77
|
-
private extensions:
|
|
101
|
+
private extensions: Extension[];
|
|
102
|
+
private runtime: ExtensionRuntime;
|
|
78
103
|
private uiContext: ExtensionUIContext;
|
|
79
|
-
private hasUI: boolean;
|
|
80
104
|
private cwd: string;
|
|
81
105
|
private sessionManager: SessionManager;
|
|
82
106
|
private modelRegistry: ModelRegistry;
|
|
@@ -89,72 +113,64 @@ export class ExtensionRunner {
|
|
|
89
113
|
private newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });
|
|
90
114
|
private branchHandler: BranchHandler = async () => ({ cancelled: false });
|
|
91
115
|
private navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });
|
|
116
|
+
private shutdownHandler: ShutdownHandler = () => {};
|
|
92
117
|
|
|
93
118
|
constructor(
|
|
94
|
-
extensions:
|
|
119
|
+
extensions: Extension[],
|
|
120
|
+
runtime: ExtensionRuntime,
|
|
95
121
|
cwd: string,
|
|
96
122
|
sessionManager: SessionManager,
|
|
97
123
|
modelRegistry: ModelRegistry,
|
|
98
124
|
) {
|
|
99
125
|
this.extensions = extensions;
|
|
126
|
+
this.runtime = runtime;
|
|
100
127
|
this.uiContext = noOpUIContext;
|
|
101
|
-
this.hasUI = false;
|
|
102
128
|
this.cwd = cwd;
|
|
103
129
|
this.sessionManager = sessionManager;
|
|
104
130
|
this.modelRegistry = modelRegistry;
|
|
105
131
|
}
|
|
106
132
|
|
|
107
|
-
initialize(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
this.
|
|
126
|
-
this.
|
|
127
|
-
this.abortFn =
|
|
128
|
-
this.hasPendingMessagesFn =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this.navigateTreeHandler = options.navigateTreeHandler;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
for (const ext of this.extensions) {
|
|
141
|
-
ext.setSendMessageHandler(options.sendMessageHandler);
|
|
142
|
-
ext.setAppendEntryHandler(options.appendEntryHandler);
|
|
143
|
-
ext.setGetActiveToolsHandler(options.getActiveToolsHandler);
|
|
144
|
-
ext.setGetAllToolsHandler(options.getAllToolsHandler);
|
|
145
|
-
ext.setSetActiveToolsHandler(options.setActiveToolsHandler);
|
|
133
|
+
initialize(
|
|
134
|
+
actions: ExtensionActions,
|
|
135
|
+
contextActions: ExtensionContextActions,
|
|
136
|
+
commandContextActions?: ExtensionCommandContextActions,
|
|
137
|
+
uiContext?: ExtensionUIContext,
|
|
138
|
+
): void {
|
|
139
|
+
// Copy actions into the shared runtime (all extension APIs reference this)
|
|
140
|
+
this.runtime.sendMessage = actions.sendMessage;
|
|
141
|
+
this.runtime.sendUserMessage = actions.sendUserMessage;
|
|
142
|
+
this.runtime.appendEntry = actions.appendEntry;
|
|
143
|
+
this.runtime.getActiveTools = actions.getActiveTools;
|
|
144
|
+
this.runtime.getAllTools = actions.getAllTools;
|
|
145
|
+
this.runtime.setActiveTools = actions.setActiveTools;
|
|
146
|
+
this.runtime.setModel = actions.setModel;
|
|
147
|
+
this.runtime.getThinkingLevel = actions.getThinkingLevel;
|
|
148
|
+
this.runtime.setThinkingLevel = actions.setThinkingLevel;
|
|
149
|
+
|
|
150
|
+
// Context actions (required)
|
|
151
|
+
this.getModel = contextActions.getModel;
|
|
152
|
+
this.isIdleFn = contextActions.isIdle;
|
|
153
|
+
this.abortFn = contextActions.abort;
|
|
154
|
+
this.hasPendingMessagesFn = contextActions.hasPendingMessages;
|
|
155
|
+
this.shutdownHandler = contextActions.shutdown;
|
|
156
|
+
|
|
157
|
+
// Command context actions (optional, only for interactive mode)
|
|
158
|
+
if (commandContextActions) {
|
|
159
|
+
this.waitForIdleFn = commandContextActions.waitForIdle;
|
|
160
|
+
this.newSessionHandler = commandContextActions.newSession;
|
|
161
|
+
this.branchHandler = commandContextActions.branch;
|
|
162
|
+
this.navigateTreeHandler = commandContextActions.navigateTree;
|
|
146
163
|
}
|
|
147
164
|
|
|
148
|
-
this.uiContext =
|
|
149
|
-
this.hasUI = options.hasUI ?? false;
|
|
165
|
+
this.uiContext = uiContext ?? noOpUIContext;
|
|
150
166
|
}
|
|
151
167
|
|
|
152
|
-
getUIContext(): ExtensionUIContext
|
|
168
|
+
getUIContext(): ExtensionUIContext {
|
|
153
169
|
return this.uiContext;
|
|
154
170
|
}
|
|
155
171
|
|
|
156
|
-
|
|
157
|
-
return this.
|
|
172
|
+
hasUI(): boolean {
|
|
173
|
+
return this.uiContext !== noOpUIContext;
|
|
158
174
|
}
|
|
159
175
|
|
|
160
176
|
getExtensionPaths(): string[] {
|
|
@@ -183,11 +199,7 @@ export class ExtensionRunner {
|
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
setFlagValue(name: string, value: boolean | string): void {
|
|
186
|
-
|
|
187
|
-
if (ext.flags.has(name)) {
|
|
188
|
-
ext.setFlagValue(name, value);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
202
|
+
this.runtime.flagValues.set(name, value);
|
|
191
203
|
}
|
|
192
204
|
|
|
193
205
|
private static readonly RESERVED_SHORTCUTS = new Set([
|
|
@@ -214,17 +226,20 @@ export class ExtensionRunner {
|
|
|
214
226
|
const normalizedKey = key.toLowerCase() as KeyId;
|
|
215
227
|
|
|
216
228
|
if (ExtensionRunner.RESERVED_SHORTCUTS.has(normalizedKey)) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
229
|
+
logger.warn("Extension shortcut conflicts with built-in shortcut", {
|
|
230
|
+
key,
|
|
231
|
+
extensionPath: shortcut.extensionPath,
|
|
232
|
+
});
|
|
220
233
|
continue;
|
|
221
234
|
}
|
|
222
235
|
|
|
223
236
|
const existing = allShortcuts.get(normalizedKey);
|
|
224
237
|
if (existing) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
238
|
+
logger.warn("Extension shortcut conflict", {
|
|
239
|
+
key,
|
|
240
|
+
extensionPath: shortcut.extensionPath,
|
|
241
|
+
existingExtensionPath: existing.extensionPath,
|
|
242
|
+
});
|
|
228
243
|
}
|
|
229
244
|
allShortcuts.set(normalizedKey, shortcut);
|
|
230
245
|
}
|
|
@@ -283,10 +298,10 @@ export class ExtensionRunner {
|
|
|
283
298
|
return undefined;
|
|
284
299
|
}
|
|
285
300
|
|
|
286
|
-
|
|
301
|
+
createContext(): ExtensionContext {
|
|
287
302
|
return {
|
|
288
303
|
ui: this.uiContext,
|
|
289
|
-
hasUI: this.hasUI,
|
|
304
|
+
hasUI: this.hasUI(),
|
|
290
305
|
cwd: this.cwd,
|
|
291
306
|
sessionManager: this.sessionManager,
|
|
292
307
|
modelRegistry: this.modelRegistry,
|
|
@@ -294,10 +309,18 @@ export class ExtensionRunner {
|
|
|
294
309
|
isIdle: () => this.isIdleFn(),
|
|
295
310
|
abort: () => this.abortFn(),
|
|
296
311
|
hasPendingMessages: () => this.hasPendingMessagesFn(),
|
|
312
|
+
shutdown: () => this.shutdownHandler(),
|
|
297
313
|
hasQueuedMessages: () => this.hasPendingMessagesFn(),
|
|
298
314
|
};
|
|
299
315
|
}
|
|
300
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Request a graceful shutdown. Called by extension tools and event handlers.
|
|
319
|
+
*/
|
|
320
|
+
shutdown(): void {
|
|
321
|
+
this.shutdownHandler();
|
|
322
|
+
}
|
|
323
|
+
|
|
301
324
|
createCommandContext(): ExtensionCommandContext {
|
|
302
325
|
return {
|
|
303
326
|
...this.createContext(),
|
|
@@ -394,6 +417,35 @@ export class ExtensionRunner {
|
|
|
394
417
|
return result;
|
|
395
418
|
}
|
|
396
419
|
|
|
420
|
+
async emitUserBash(event: UserBashEvent): Promise<UserBashEventResult | undefined> {
|
|
421
|
+
const ctx = this.createContext();
|
|
422
|
+
|
|
423
|
+
for (const ext of this.extensions) {
|
|
424
|
+
const handlers = ext.handlers.get("user_bash");
|
|
425
|
+
if (!handlers || handlers.length === 0) continue;
|
|
426
|
+
|
|
427
|
+
for (const handler of handlers) {
|
|
428
|
+
try {
|
|
429
|
+
const handlerResult = await handler(event, ctx);
|
|
430
|
+
if (handlerResult) {
|
|
431
|
+
return handlerResult as UserBashEventResult;
|
|
432
|
+
}
|
|
433
|
+
} catch (err) {
|
|
434
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
435
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
436
|
+
this.emitError({
|
|
437
|
+
extensionPath: ext.path,
|
|
438
|
+
event: "user_bash",
|
|
439
|
+
error: message,
|
|
440
|
+
stack,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
397
449
|
async emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {
|
|
398
450
|
const ctx = this.createContext();
|
|
399
451
|
let currentMessages = structuredClone(messages);
|
|
@@ -428,11 +480,13 @@ export class ExtensionRunner {
|
|
|
428
480
|
|
|
429
481
|
async emitBeforeAgentStart(
|
|
430
482
|
prompt: string,
|
|
431
|
-
images
|
|
483
|
+
images: ImageContent[] | undefined,
|
|
484
|
+
systemPrompt: string,
|
|
432
485
|
): Promise<BeforeAgentStartCombinedResult | undefined> {
|
|
433
486
|
const ctx = this.createContext();
|
|
434
487
|
const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
|
|
435
|
-
|
|
488
|
+
let currentSystemPrompt = systemPrompt;
|
|
489
|
+
let systemPromptModified = false;
|
|
436
490
|
|
|
437
491
|
for (const ext of this.extensions) {
|
|
438
492
|
const handlers = ext.handlers.get("before_agent_start");
|
|
@@ -440,7 +494,12 @@ export class ExtensionRunner {
|
|
|
440
494
|
|
|
441
495
|
for (const handler of handlers) {
|
|
442
496
|
try {
|
|
443
|
-
const event: BeforeAgentStartEvent = {
|
|
497
|
+
const event: BeforeAgentStartEvent = {
|
|
498
|
+
type: "before_agent_start",
|
|
499
|
+
prompt,
|
|
500
|
+
images,
|
|
501
|
+
systemPrompt: currentSystemPrompt,
|
|
502
|
+
};
|
|
444
503
|
const handlerResult = await handler(event, ctx);
|
|
445
504
|
|
|
446
505
|
if (handlerResult) {
|
|
@@ -448,8 +507,9 @@ export class ExtensionRunner {
|
|
|
448
507
|
if (result.message) {
|
|
449
508
|
messages.push(result.message);
|
|
450
509
|
}
|
|
451
|
-
if (result.
|
|
452
|
-
|
|
510
|
+
if (result.systemPrompt !== undefined) {
|
|
511
|
+
currentSystemPrompt = result.systemPrompt;
|
|
512
|
+
systemPromptModified = true;
|
|
453
513
|
}
|
|
454
514
|
}
|
|
455
515
|
} catch (err) {
|
|
@@ -465,10 +525,10 @@ export class ExtensionRunner {
|
|
|
465
525
|
}
|
|
466
526
|
}
|
|
467
527
|
|
|
468
|
-
if (messages.length > 0 ||
|
|
528
|
+
if (messages.length > 0 || systemPromptModified) {
|
|
469
529
|
return {
|
|
470
530
|
messages: messages.length > 0 ? messages : undefined,
|
|
471
|
-
|
|
531
|
+
systemPrompt: systemPromptModified ? currentSystemPrompt : undefined,
|
|
472
532
|
};
|
|
473
533
|
}
|
|
474
534
|
|