@oh-my-pi/pi-coding-agent 3.15.0 → 3.20.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 +61 -1
- package/docs/extensions.md +1055 -0
- package/docs/rpc.md +69 -13
- package/docs/session-tree-plan.md +1 -1
- package/examples/extensions/README.md +141 -0
- package/examples/extensions/api-demo.ts +87 -0
- package/examples/extensions/chalk-logger.ts +26 -0
- package/examples/extensions/hello.ts +33 -0
- package/examples/extensions/pirate.ts +44 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/todo.ts +299 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/sdk/02-custom-model.ts +3 -3
- package/examples/sdk/05-tools.ts +7 -3
- package/examples/sdk/06-extensions.ts +81 -0
- package/examples/sdk/06-hooks.ts +14 -13
- package/examples/sdk/08-prompt-templates.ts +42 -0
- package/examples/sdk/08-slash-commands.ts +17 -12
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/12-full-control.ts +6 -6
- package/package.json +11 -7
- package/src/capability/extension-module.ts +34 -0
- package/src/cli/args.ts +22 -7
- package/src/cli/file-processor.ts +38 -67
- package/src/cli/list-models.ts +1 -1
- package/src/config.ts +25 -14
- package/src/core/agent-session.ts +505 -242
- package/src/core/auth-storage.ts +33 -21
- package/src/core/compaction/branch-summarization.ts +4 -4
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +430 -0
- package/src/core/custom-commands/loader.ts +9 -0
- package/src/core/custom-tools/wrapper.ts +5 -0
- package/src/core/event-bus.ts +59 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/extensions/index.ts +100 -0
- package/src/core/extensions/loader.ts +501 -0
- package/src/core/extensions/runner.ts +477 -0
- package/src/core/extensions/types.ts +712 -0
- package/src/core/extensions/wrapper.ts +147 -0
- package/src/core/hooks/types.ts +2 -2
- package/src/core/index.ts +10 -21
- package/src/core/keybindings.ts +199 -0
- package/src/core/messages.ts +26 -7
- package/src/core/model-registry.ts +123 -46
- package/src/core/model-resolver.ts +7 -5
- package/src/core/prompt-templates.ts +242 -0
- package/src/core/sdk.ts +378 -295
- package/src/core/session-manager.ts +72 -58
- package/src/core/settings-manager.ts +118 -22
- package/src/core/system-prompt.ts +24 -1
- package/src/core/terminal-notify.ts +37 -0
- package/src/core/tools/context.ts +4 -4
- package/src/core/tools/exa/mcp-client.ts +5 -4
- package/src/core/tools/exa/render.ts +176 -131
- package/src/core/tools/gemini-image.ts +361 -0
- package/src/core/tools/git.ts +216 -0
- package/src/core/tools/index.ts +28 -15
- package/src/core/tools/lsp/config.ts +5 -4
- package/src/core/tools/lsp/index.ts +17 -12
- package/src/core/tools/lsp/render.ts +39 -47
- package/src/core/tools/read.ts +66 -29
- package/src/core/tools/render-utils.ts +268 -0
- package/src/core/tools/renderers.ts +243 -225
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +66 -58
- package/src/core/tools/task/index.ts +29 -10
- package/src/core/tools/task/model-resolver.ts +8 -13
- package/src/core/tools/task/omp-command.ts +24 -0
- package/src/core/tools/task/render.ts +35 -60
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/web-fetch.ts +29 -28
- package/src/core/tools/web-search/index.ts +6 -5
- package/src/core/tools/web-search/providers/exa.ts +6 -5
- package/src/core/tools/web-search/render.ts +66 -111
- package/src/core/voice-controller.ts +135 -0
- package/src/core/voice-supervisor.ts +1003 -0
- package/src/core/voice.ts +308 -0
- package/src/discovery/builtin.ts +75 -1
- package/src/discovery/claude.ts +47 -1
- package/src/discovery/codex.ts +54 -2
- package/src/discovery/gemini.ts +55 -2
- package/src/discovery/helpers.ts +100 -1
- package/src/discovery/index.ts +2 -0
- package/src/index.ts +14 -9
- package/src/lib/worktree/collapse.ts +179 -0
- package/src/lib/worktree/constants.ts +14 -0
- package/src/lib/worktree/errors.ts +23 -0
- package/src/lib/worktree/git.ts +110 -0
- package/src/lib/worktree/index.ts +23 -0
- package/src/lib/worktree/operations.ts +216 -0
- package/src/lib/worktree/session.ts +114 -0
- package/src/lib/worktree/stats.ts +67 -0
- package/src/main.ts +61 -37
- package/src/migrations.ts +37 -7
- package/src/modes/interactive/components/bash-execution.ts +6 -4
- package/src/modes/interactive/components/custom-editor.ts +55 -0
- package/src/modes/interactive/components/custom-message.ts +95 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/extensions/types.ts +1 -0
- package/src/modes/interactive/components/footer.ts +324 -0
- package/src/modes/interactive/components/hook-editor.ts +1 -0
- package/src/modes/interactive/components/hook-selector.ts +3 -3
- package/src/modes/interactive/components/model-selector.ts +7 -6
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/settings-defs.ts +55 -6
- package/src/modes/interactive/components/status-line/separators.ts +4 -4
- package/src/modes/interactive/components/status-line.ts +45 -35
- package/src/modes/interactive/components/tool-execution.ts +95 -23
- package/src/modes/interactive/interactive-mode.ts +644 -113
- package/src/modes/interactive/theme/defaults/alabaster.json +99 -0
- package/src/modes/interactive/theme/defaults/amethyst.json +103 -0
- package/src/modes/interactive/theme/defaults/anthracite.json +100 -0
- package/src/modes/interactive/theme/defaults/basalt.json +90 -0
- package/src/modes/interactive/theme/defaults/birch.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-abyss.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-aurora.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-cavern.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-copper.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-cosmos.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-eclipse.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-ember.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-equinox.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-lavender.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-lunar.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-midnight.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-nebula.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-rainforest.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-reef.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-sakura.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-slate.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-solstice.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-starfall.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-swamp.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-taiga.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-terminal.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-tundra.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-twilight.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-volcanic.json +97 -0
- package/src/modes/interactive/theme/defaults/graphite.json +99 -0
- package/src/modes/interactive/theme/defaults/index.ts +128 -0
- package/src/modes/interactive/theme/defaults/light-aurora-day.json +97 -0
- package/src/modes/interactive/theme/defaults/light-canyon.json +97 -0
- package/src/modes/interactive/theme/defaults/light-cirrus.json +96 -0
- package/src/modes/interactive/theme/defaults/light-coral.json +94 -0
- package/src/modes/interactive/theme/defaults/light-dawn.json +96 -0
- package/src/modes/interactive/theme/defaults/light-dunes.json +97 -0
- package/src/modes/interactive/theme/defaults/light-eucalyptus.json +94 -0
- package/src/modes/interactive/theme/defaults/light-frost.json +94 -0
- package/src/modes/interactive/theme/defaults/light-glacier.json +97 -0
- package/src/modes/interactive/theme/defaults/light-haze.json +96 -0
- package/src/modes/interactive/theme/defaults/light-honeycomb.json +94 -0
- package/src/modes/interactive/theme/defaults/light-lagoon.json +97 -0
- package/src/modes/interactive/theme/defaults/light-lavender.json +94 -0
- package/src/modes/interactive/theme/defaults/light-meadow.json +97 -0
- package/src/modes/interactive/theme/defaults/light-mint.json +94 -0
- package/src/modes/interactive/theme/defaults/light-opal.json +97 -0
- package/src/modes/interactive/theme/defaults/light-orchard.json +97 -0
- package/src/modes/interactive/theme/defaults/light-paper.json +94 -0
- package/src/modes/interactive/theme/defaults/light-prism.json +96 -0
- package/src/modes/interactive/theme/defaults/light-sand.json +94 -0
- package/src/modes/interactive/theme/defaults/light-savanna.json +97 -0
- package/src/modes/interactive/theme/defaults/light-soleil.json +96 -0
- package/src/modes/interactive/theme/defaults/light-wetland.json +97 -0
- package/src/modes/interactive/theme/defaults/light-zenith.json +95 -0
- package/src/modes/interactive/theme/defaults/limestone.json +100 -0
- package/src/modes/interactive/theme/defaults/mahogany.json +104 -0
- package/src/modes/interactive/theme/defaults/marble.json +99 -0
- package/src/modes/interactive/theme/defaults/obsidian.json +90 -0
- package/src/modes/interactive/theme/defaults/onyx.json +90 -0
- package/src/modes/interactive/theme/defaults/pearl.json +99 -0
- package/src/modes/interactive/theme/defaults/porcelain.json +90 -0
- package/src/modes/interactive/theme/defaults/quartz.json +102 -0
- package/src/modes/interactive/theme/defaults/sandstone.json +101 -0
- package/src/modes/interactive/theme/defaults/titanium.json +89 -0
- package/src/modes/print-mode.ts +14 -72
- package/src/modes/rpc/rpc-client.ts +23 -9
- package/src/modes/rpc/rpc-mode.ts +137 -125
- package/src/modes/rpc/rpc-types.ts +46 -24
- package/src/prompts/task.md +1 -0
- package/src/prompts/tools/gemini-image.md +4 -0
- package/src/prompts/tools/git.md +9 -0
- package/src/prompts/voice-summary.md +12 -0
- package/src/utils/image-convert.ts +26 -0
- package/src/utils/image-resize.ts +215 -0
- package/src/utils/shell-snapshot.ts +22 -20
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool wrappers for extensions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
6
|
+
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
8
|
+
import type { ExtensionRunner } from "./runner";
|
|
9
|
+
import type { ExtensionContext, RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wrap a RegisteredTool into an AgentTool.
|
|
13
|
+
*/
|
|
14
|
+
export function wrapRegisteredTool(registeredTool: RegisteredTool, getContext: () => ExtensionContext): AgentTool {
|
|
15
|
+
const { definition } = registeredTool;
|
|
16
|
+
return {
|
|
17
|
+
name: definition.name,
|
|
18
|
+
label: definition.label,
|
|
19
|
+
description: definition.description,
|
|
20
|
+
parameters: definition.parameters,
|
|
21
|
+
execute: (toolCallId, params, signal, onUpdate) =>
|
|
22
|
+
definition.execute(toolCallId, params, onUpdate, getContext(), signal),
|
|
23
|
+
renderCall: definition.renderCall ? (args, theme) => definition.renderCall?.(args, theme as Theme) : undefined,
|
|
24
|
+
renderResult: definition.renderResult
|
|
25
|
+
? (result, options, theme) =>
|
|
26
|
+
definition.renderResult?.(
|
|
27
|
+
result,
|
|
28
|
+
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
|
29
|
+
theme as Theme,
|
|
30
|
+
)
|
|
31
|
+
: undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Wrap all registered tools into AgentTools.
|
|
37
|
+
*/
|
|
38
|
+
export function wrapRegisteredTools(
|
|
39
|
+
registeredTools: RegisteredTool[],
|
|
40
|
+
getContext: () => ExtensionContext,
|
|
41
|
+
): AgentTool[] {
|
|
42
|
+
return registeredTools.map((rt) => wrapRegisteredTool(rt, getContext));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Wrap a tool with extension callbacks for interception.
|
|
47
|
+
* - Emits tool_call event before execution (can block)
|
|
48
|
+
* - Emits tool_result event after execution (can modify result)
|
|
49
|
+
*/
|
|
50
|
+
export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: ExtensionRunner): AgentTool<any, T> {
|
|
51
|
+
return {
|
|
52
|
+
...tool,
|
|
53
|
+
execute: async (
|
|
54
|
+
toolCallId: string,
|
|
55
|
+
params: Record<string, unknown>,
|
|
56
|
+
signal?: AbortSignal,
|
|
57
|
+
onUpdate?: AgentToolUpdateCallback<T>,
|
|
58
|
+
context?: AgentToolContext,
|
|
59
|
+
) => {
|
|
60
|
+
// Emit tool_call event - extensions can block execution
|
|
61
|
+
if (runner.hasHandlers("tool_call")) {
|
|
62
|
+
try {
|
|
63
|
+
const callResult = (await runner.emitToolCall({
|
|
64
|
+
type: "tool_call",
|
|
65
|
+
toolName: tool.name,
|
|
66
|
+
toolCallId,
|
|
67
|
+
input: params,
|
|
68
|
+
})) as ToolCallEventResult | undefined;
|
|
69
|
+
|
|
70
|
+
if (callResult?.block) {
|
|
71
|
+
const reason = callResult.reason || "Tool execution was blocked by an extension";
|
|
72
|
+
throw new Error(reason);
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err instanceof Error) {
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Extension failed, blocking execution: ${String(err)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Execute the actual tool
|
|
83
|
+
let result: { content: any; details: T };
|
|
84
|
+
let executionError: Error | undefined;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
result = await tool.execute(toolCallId, params, signal, onUpdate, context);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
executionError = err instanceof Error ? err : new Error(String(err));
|
|
90
|
+
result = {
|
|
91
|
+
content: [{ type: "text", text: executionError.message }],
|
|
92
|
+
details: undefined as T,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Emit tool_result event - extensions can modify the result and error status
|
|
97
|
+
if (runner.hasHandlers("tool_result")) {
|
|
98
|
+
const resultResult = (await runner.emit({
|
|
99
|
+
type: "tool_result",
|
|
100
|
+
toolName: tool.name,
|
|
101
|
+
toolCallId,
|
|
102
|
+
input: params,
|
|
103
|
+
content: result.content,
|
|
104
|
+
details: result.details,
|
|
105
|
+
isError: !!executionError,
|
|
106
|
+
})) as ToolResultEventResult | undefined;
|
|
107
|
+
|
|
108
|
+
if (resultResult) {
|
|
109
|
+
const modifiedContent: (TextContent | ImageContent)[] = resultResult.content ?? result.content;
|
|
110
|
+
const modifiedDetails = (resultResult.details ?? result.details) as T;
|
|
111
|
+
|
|
112
|
+
// Extension can override error status
|
|
113
|
+
if (resultResult.isError === true && !executionError) {
|
|
114
|
+
// Extension marks a successful result as error
|
|
115
|
+
const textBlocks = (modifiedContent ?? []).filter((c): c is TextContent => c.type === "text");
|
|
116
|
+
const errorText =
|
|
117
|
+
textBlocks.map((t) => t.text).join("\n") || "Tool result marked as error by extension";
|
|
118
|
+
throw new Error(errorText);
|
|
119
|
+
}
|
|
120
|
+
if (resultResult.isError === false && executionError) {
|
|
121
|
+
// Extension clears the error - return success
|
|
122
|
+
return { content: modifiedContent, details: modifiedDetails };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Error status unchanged, but content/details may be modified
|
|
126
|
+
if (executionError) {
|
|
127
|
+
throw executionError;
|
|
128
|
+
}
|
|
129
|
+
return { content: modifiedContent, details: modifiedDetails };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// No extension modification
|
|
134
|
+
if (executionError) {
|
|
135
|
+
throw executionError;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Wrap all tools with extension callbacks.
|
|
144
|
+
*/
|
|
145
|
+
export function wrapToolsWithExtensions<T>(tools: AgentTool<any, T>[], runner: ExtensionRunner): AgentTool<any, T>[] {
|
|
146
|
+
return tools.map((tool) => wrapToolWithExtensions(tool, runner));
|
|
147
|
+
}
|
package/src/core/hooks/types.ts
CHANGED
|
@@ -268,7 +268,7 @@ export interface SessionCompactEvent {
|
|
|
268
268
|
type: "session_compact";
|
|
269
269
|
compactionEntry: CompactionEntry;
|
|
270
270
|
/** Whether the compaction entry was provided by a hook */
|
|
271
|
-
|
|
271
|
+
fromExtension: boolean;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
/** Fired on process exit (SIGINT/SIGTERM) */
|
|
@@ -309,7 +309,7 @@ export interface SessionTreeEvent {
|
|
|
309
309
|
/** Branch summary entry if one was created */
|
|
310
310
|
summaryEntry?: BranchSummaryEntry;
|
|
311
311
|
/** Whether summary came from hook */
|
|
312
|
-
|
|
312
|
+
fromExtension?: boolean;
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
/** Union of all session event types */
|
package/src/core/index.ts
CHANGED
|
@@ -14,27 +14,16 @@ export {
|
|
|
14
14
|
export { type BashExecutorOptions, type BashResult, executeBash } from "./bash-executor";
|
|
15
15
|
export type { CompactionResult } from "./compaction/index";
|
|
16
16
|
export {
|
|
17
|
-
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} from "./custom-tools/index";
|
|
28
|
-
export {
|
|
29
|
-
type HookAPI,
|
|
30
|
-
type HookContext,
|
|
31
|
-
type HookError,
|
|
32
|
-
type HookEvent,
|
|
33
|
-
type HookFactory,
|
|
34
|
-
HookRunner,
|
|
35
|
-
type HookUIContext,
|
|
36
|
-
loadHooks,
|
|
37
|
-
} from "./hooks/index";
|
|
17
|
+
discoverAndLoadExtensions,
|
|
18
|
+
type ExtensionAPI,
|
|
19
|
+
type ExtensionCommandContext,
|
|
20
|
+
type ExtensionContext,
|
|
21
|
+
type ExtensionFactory,
|
|
22
|
+
ExtensionRunner,
|
|
23
|
+
type ExtensionUIContext,
|
|
24
|
+
loadExtensionFromFactory,
|
|
25
|
+
type ToolDefinition,
|
|
26
|
+
} from "./extensions/index";
|
|
38
27
|
export {
|
|
39
28
|
createMCPManager,
|
|
40
29
|
discoverAndLoadMCPTools,
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_EDITOR_KEYBINDINGS,
|
|
5
|
+
type EditorAction,
|
|
6
|
+
type EditorKeybindingsConfig,
|
|
7
|
+
EditorKeybindingsManager,
|
|
8
|
+
type KeyId,
|
|
9
|
+
matchesKey,
|
|
10
|
+
setEditorKeybindings,
|
|
11
|
+
} from "@oh-my-pi/pi-tui";
|
|
12
|
+
import { getAgentDir } from "../config";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Application-level actions (coding agent specific).
|
|
16
|
+
*/
|
|
17
|
+
export type AppAction =
|
|
18
|
+
| "interrupt"
|
|
19
|
+
| "clear"
|
|
20
|
+
| "exit"
|
|
21
|
+
| "suspend"
|
|
22
|
+
| "cycleThinkingLevel"
|
|
23
|
+
| "cycleModelForward"
|
|
24
|
+
| "cycleModelBackward"
|
|
25
|
+
| "selectModel"
|
|
26
|
+
| "expandTools"
|
|
27
|
+
| "toggleThinking"
|
|
28
|
+
| "externalEditor"
|
|
29
|
+
| "followUp";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* All configurable actions.
|
|
33
|
+
*/
|
|
34
|
+
export type KeyAction = AppAction | EditorAction;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Full keybindings configuration (app + editor actions).
|
|
38
|
+
*/
|
|
39
|
+
export type KeybindingsConfig = {
|
|
40
|
+
[K in KeyAction]?: KeyId | KeyId[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default application keybindings.
|
|
45
|
+
*/
|
|
46
|
+
export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
|
|
47
|
+
interrupt: "escape",
|
|
48
|
+
clear: "ctrl+c",
|
|
49
|
+
exit: "ctrl+d",
|
|
50
|
+
suspend: "ctrl+z",
|
|
51
|
+
cycleThinkingLevel: "shift+tab",
|
|
52
|
+
cycleModelForward: "ctrl+p",
|
|
53
|
+
cycleModelBackward: "shift+ctrl+p",
|
|
54
|
+
selectModel: "ctrl+l",
|
|
55
|
+
expandTools: "ctrl+o",
|
|
56
|
+
toggleThinking: "ctrl+t",
|
|
57
|
+
externalEditor: "ctrl+g",
|
|
58
|
+
followUp: "alt+enter",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* All default keybindings (app + editor).
|
|
63
|
+
*/
|
|
64
|
+
export const DEFAULT_KEYBINDINGS: Required<KeybindingsConfig> = {
|
|
65
|
+
...DEFAULT_EDITOR_KEYBINDINGS,
|
|
66
|
+
...DEFAULT_APP_KEYBINDINGS,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// App actions list for type checking
|
|
70
|
+
const APP_ACTIONS: AppAction[] = [
|
|
71
|
+
"interrupt",
|
|
72
|
+
"clear",
|
|
73
|
+
"exit",
|
|
74
|
+
"suspend",
|
|
75
|
+
"cycleThinkingLevel",
|
|
76
|
+
"cycleModelForward",
|
|
77
|
+
"cycleModelBackward",
|
|
78
|
+
"selectModel",
|
|
79
|
+
"expandTools",
|
|
80
|
+
"toggleThinking",
|
|
81
|
+
"externalEditor",
|
|
82
|
+
"followUp",
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
function isAppAction(action: string): action is AppAction {
|
|
86
|
+
return APP_ACTIONS.includes(action as AppAction);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Manages all keybindings (app + editor).
|
|
91
|
+
*/
|
|
92
|
+
export class KeybindingsManager {
|
|
93
|
+
private config: KeybindingsConfig;
|
|
94
|
+
private appActionToKeys: Map<AppAction, KeyId[]>;
|
|
95
|
+
|
|
96
|
+
private constructor(config: KeybindingsConfig) {
|
|
97
|
+
this.config = config;
|
|
98
|
+
this.appActionToKeys = new Map();
|
|
99
|
+
this.buildMaps();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create from config file and set up editor keybindings.
|
|
104
|
+
*/
|
|
105
|
+
static async create(agentDir: string = getAgentDir()): Promise<KeybindingsManager> {
|
|
106
|
+
const configPath = join(agentDir, "keybindings.json");
|
|
107
|
+
const config = await KeybindingsManager.loadFromFile(configPath);
|
|
108
|
+
const manager = new KeybindingsManager(config);
|
|
109
|
+
|
|
110
|
+
// Set up editor keybindings globally
|
|
111
|
+
const editorConfig: EditorKeybindingsConfig = {};
|
|
112
|
+
for (const [action, keys] of Object.entries(config)) {
|
|
113
|
+
if (!isAppAction(action)) {
|
|
114
|
+
editorConfig[action as EditorAction] = keys;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
setEditorKeybindings(new EditorKeybindingsManager(editorConfig));
|
|
118
|
+
|
|
119
|
+
return manager;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Create in-memory.
|
|
124
|
+
*/
|
|
125
|
+
static inMemory(config: KeybindingsConfig = {}): KeybindingsManager {
|
|
126
|
+
return new KeybindingsManager(config);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static async loadFromFile(path: string): Promise<KeybindingsConfig> {
|
|
130
|
+
if (!existsSync(path)) return {};
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
133
|
+
} catch {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private buildMaps(): void {
|
|
139
|
+
this.appActionToKeys.clear();
|
|
140
|
+
|
|
141
|
+
// Set defaults for app actions
|
|
142
|
+
for (const [action, keys] of Object.entries(DEFAULT_APP_KEYBINDINGS)) {
|
|
143
|
+
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
144
|
+
this.appActionToKeys.set(action as AppAction, [...keyArray]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Override with user config (app actions only)
|
|
148
|
+
for (const [action, keys] of Object.entries(this.config)) {
|
|
149
|
+
if (keys === undefined || !isAppAction(action)) continue;
|
|
150
|
+
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
151
|
+
this.appActionToKeys.set(action, keyArray);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if input matches an app action.
|
|
157
|
+
*/
|
|
158
|
+
matches(data: string, action: AppAction): boolean {
|
|
159
|
+
const keys = this.appActionToKeys.get(action);
|
|
160
|
+
if (!keys) return false;
|
|
161
|
+
for (const key of keys) {
|
|
162
|
+
if (matchesKey(data, key)) return true;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get keys bound to an app action.
|
|
169
|
+
*/
|
|
170
|
+
getKeys(action: AppAction): KeyId[] {
|
|
171
|
+
return this.appActionToKeys.get(action) ?? [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get display string for an action.
|
|
176
|
+
*/
|
|
177
|
+
getDisplayString(action: AppAction): string {
|
|
178
|
+
const keys = this.getKeys(action);
|
|
179
|
+
if (keys.length === 0) return "";
|
|
180
|
+
if (keys.length === 1) return keys[0]!;
|
|
181
|
+
return keys.join("/");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get the full effective config.
|
|
186
|
+
*/
|
|
187
|
+
getEffectiveConfig(): Required<KeybindingsConfig> {
|
|
188
|
+
const result = { ...DEFAULT_KEYBINDINGS };
|
|
189
|
+
for (const [action, keys] of Object.entries(this.config)) {
|
|
190
|
+
if (keys !== undefined) {
|
|
191
|
+
(result as KeybindingsConfig)[action as KeyAction] = keys;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Re-export for convenience
|
|
199
|
+
export type { EditorAction, KeyId };
|
package/src/core/messages.ts
CHANGED
|
@@ -35,11 +35,24 @@ export interface BashExecutionMessage {
|
|
|
35
35
|
truncated: boolean;
|
|
36
36
|
fullOutputPath?: string;
|
|
37
37
|
timestamp: number;
|
|
38
|
+
/** If true, this message is excluded from LLM context (!! prefix) */
|
|
39
|
+
excludeFromContext?: boolean;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
|
-
* Message type for
|
|
42
|
-
|
|
43
|
+
* Message type for extension-injected messages via sendMessage().
|
|
44
|
+
*/
|
|
45
|
+
export interface CustomMessage<T = unknown> {
|
|
46
|
+
role: "custom";
|
|
47
|
+
customType: string;
|
|
48
|
+
content: string | (TextContent | ImageContent)[];
|
|
49
|
+
display: boolean;
|
|
50
|
+
details?: T;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Legacy hook message type (pre-extensions). Kept for session migration.
|
|
43
56
|
*/
|
|
44
57
|
export interface HookMessage<T = unknown> {
|
|
45
58
|
role: "hookMessage";
|
|
@@ -78,9 +91,11 @@ export interface FileMentionMessage {
|
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
// Extend CustomAgentMessages via declaration merging
|
|
94
|
+
// Legacy hookMessage is kept for migration; new code should use custom.
|
|
81
95
|
declare module "@oh-my-pi/pi-agent-core" {
|
|
82
96
|
interface CustomAgentMessages {
|
|
83
97
|
bashExecution: BashExecutionMessage;
|
|
98
|
+
custom: CustomMessage;
|
|
84
99
|
hookMessage: HookMessage;
|
|
85
100
|
branchSummary: BranchSummaryMessage;
|
|
86
101
|
compactionSummary: CompactionSummaryMessage;
|
|
@@ -125,22 +140,22 @@ export function createCompactionSummaryMessage(
|
|
|
125
140
|
): CompactionSummaryMessage {
|
|
126
141
|
return {
|
|
127
142
|
role: "compactionSummary",
|
|
128
|
-
summary
|
|
143
|
+
summary,
|
|
129
144
|
tokensBefore,
|
|
130
145
|
timestamp: new Date(timestamp).getTime(),
|
|
131
146
|
};
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
/** Convert CustomMessageEntry to AgentMessage format */
|
|
135
|
-
export function
|
|
150
|
+
export function createCustomMessage(
|
|
136
151
|
customType: string,
|
|
137
152
|
content: string | (TextContent | ImageContent)[],
|
|
138
153
|
display: boolean,
|
|
139
154
|
details: unknown | undefined,
|
|
140
155
|
timestamp: string,
|
|
141
|
-
):
|
|
156
|
+
): CustomMessage {
|
|
142
157
|
return {
|
|
143
|
-
role: "
|
|
158
|
+
role: "custom",
|
|
144
159
|
customType,
|
|
145
160
|
content,
|
|
146
161
|
display,
|
|
@@ -155,18 +170,22 @@ export function createHookMessage(
|
|
|
155
170
|
* This is used by:
|
|
156
171
|
* - Agent's transormToLlm option (for prompt calls and queued messages)
|
|
157
172
|
* - Compaction's generateSummary (for summarization)
|
|
158
|
-
* - Custom
|
|
173
|
+
* - Custom extensions and tools
|
|
159
174
|
*/
|
|
160
175
|
export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
161
176
|
return messages
|
|
162
177
|
.map((m): Message | undefined => {
|
|
163
178
|
switch (m.role) {
|
|
164
179
|
case "bashExecution":
|
|
180
|
+
if (m.excludeFromContext) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
165
183
|
return {
|
|
166
184
|
role: "user",
|
|
167
185
|
content: [{ type: "text", text: bashExecutionToText(m) }],
|
|
168
186
|
timestamp: m.timestamp,
|
|
169
187
|
};
|
|
188
|
+
case "custom":
|
|
170
189
|
case "hookMessage": {
|
|
171
190
|
const content = typeof m.content === "string" ? [{ type: "text" as const, text: m.content }] : m.content;
|
|
172
191
|
return {
|