@oh-my-pi/pi-coding-agent 3.37.0 → 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 +89 -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
|
@@ -1012,10 +1012,12 @@ function detectColorMode(): ColorMode {
|
|
|
1012
1012
|
return "truecolor";
|
|
1013
1013
|
}
|
|
1014
1014
|
const term = process.env.TERM || "";
|
|
1015
|
-
|
|
1015
|
+
// Only fall back to 256color for truly limited terminals
|
|
1016
|
+
if (term === "dumb" || term === "" || term === "linux") {
|
|
1016
1017
|
return "256color";
|
|
1017
1018
|
}
|
|
1018
|
-
|
|
1019
|
+
// Assume truecolor for everything else - virtually all modern terminals support it
|
|
1020
|
+
return "truecolor";
|
|
1019
1021
|
}
|
|
1020
1022
|
|
|
1021
1023
|
function hexToRgb(hex: string): { r: number; g: number; b: number } {
|
|
@@ -1599,6 +1601,35 @@ export function getAvailableThemes(): string[] {
|
|
|
1599
1601
|
return Array.from(themes).sort();
|
|
1600
1602
|
}
|
|
1601
1603
|
|
|
1604
|
+
export interface ThemeInfo {
|
|
1605
|
+
name: string;
|
|
1606
|
+
path: string | undefined;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
export function getAvailableThemesWithPaths(): ThemeInfo[] {
|
|
1610
|
+
const result: ThemeInfo[] = [];
|
|
1611
|
+
|
|
1612
|
+
// Built-in themes (embedded, no file path)
|
|
1613
|
+
for (const name of Object.keys(getBuiltinThemes())) {
|
|
1614
|
+
result.push({ name, path: undefined });
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// Custom themes
|
|
1618
|
+
const customThemesDir = getCustomThemesDir();
|
|
1619
|
+
if (fs.existsSync(customThemesDir)) {
|
|
1620
|
+
for (const file of fs.readdirSync(customThemesDir)) {
|
|
1621
|
+
if (file.endsWith(".json")) {
|
|
1622
|
+
const name = file.slice(0, -5);
|
|
1623
|
+
if (!result.some((themeInfo) => themeInfo.name === name)) {
|
|
1624
|
+
result.push({ name, path: path.join(customThemesDir, file) });
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1602
1633
|
function loadThemeJson(name: string): ThemeJson {
|
|
1603
1634
|
const builtinThemes = getBuiltinThemes();
|
|
1604
1635
|
if (name in builtinThemes) {
|
|
@@ -1679,6 +1710,14 @@ function loadTheme(name: string, mode?: ColorMode, symbolPresetOverride?: Symbol
|
|
|
1679
1710
|
return createTheme(themeJson, mode, symbolPresetOverride);
|
|
1680
1711
|
}
|
|
1681
1712
|
|
|
1713
|
+
export function getThemeByName(name: string): Theme | undefined {
|
|
1714
|
+
try {
|
|
1715
|
+
return loadTheme(name);
|
|
1716
|
+
} catch {
|
|
1717
|
+
return undefined;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1682
1721
|
function detectTerminalBackground(): "dark" | "light" {
|
|
1683
1722
|
const colorfgbg = process.env.COLORFGBG || "";
|
|
1684
1723
|
if (colorfgbg) {
|
|
@@ -1748,6 +1787,15 @@ export function setTheme(name: string, enableWatcher: boolean = false): { succes
|
|
|
1748
1787
|
}
|
|
1749
1788
|
}
|
|
1750
1789
|
|
|
1790
|
+
export function setThemeInstance(themeInstance: Theme): void {
|
|
1791
|
+
theme = themeInstance;
|
|
1792
|
+
currentThemeName = "<in-memory>";
|
|
1793
|
+
stopThemeWatcher();
|
|
1794
|
+
if (onThemeChangeCallback) {
|
|
1795
|
+
onThemeChangeCallback();
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1751
1799
|
/**
|
|
1752
1800
|
* Set the symbol preset override, recreating the theme with the new preset.
|
|
1753
1801
|
*/
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -9,43 +9,90 @@
|
|
|
9
9
|
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import type { AgentSession } from "../core/agent-session";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Options for print mode.
|
|
14
|
+
*/
|
|
15
|
+
export interface PrintModeOptions {
|
|
16
|
+
/** Output mode: "text" for final response only, "json" for all events */
|
|
17
|
+
mode: "text" | "json";
|
|
18
|
+
/** Array of additional prompts to send after initialMessage */
|
|
19
|
+
messages?: string[];
|
|
20
|
+
/** First message to send (may contain @file content) */
|
|
21
|
+
initialMessage?: string;
|
|
22
|
+
/** Images to attach to the initial message */
|
|
23
|
+
initialImages?: ImageContent[];
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
/**
|
|
13
27
|
* Run in print (single-shot) mode.
|
|
14
28
|
* Sends prompts to the agent and outputs the result.
|
|
15
|
-
*
|
|
16
|
-
* @param session The agent session
|
|
17
|
-
* @param mode Output mode: "text" for final response only, "json" for all events
|
|
18
|
-
* @param messages Array of prompts to send
|
|
19
|
-
* @param initialMessage Optional first message (may contain @file content)
|
|
20
|
-
* @param initialImages Optional images for the initial message
|
|
21
29
|
*/
|
|
22
|
-
export async function runPrintMode(
|
|
23
|
-
|
|
24
|
-
mode
|
|
25
|
-
messages: string[],
|
|
26
|
-
initialMessage?: string,
|
|
27
|
-
initialImages?: ImageContent[],
|
|
28
|
-
): Promise<void> {
|
|
29
|
-
// Extension runner already has no-op UI context by default (set in loader)
|
|
30
|
-
// Set up extensions for print mode (no UI)
|
|
30
|
+
export async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void> {
|
|
31
|
+
const { mode, messages = [], initialMessage, initialImages } = options;
|
|
32
|
+
// Set up extensions for print mode (no UI, no command context)
|
|
31
33
|
const extensionRunner = session.extensionRunner;
|
|
32
34
|
if (extensionRunner) {
|
|
33
|
-
extensionRunner.initialize(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
extensionRunner.initialize(
|
|
36
|
+
// ExtensionActions
|
|
37
|
+
{
|
|
38
|
+
sendMessage: (message, options) => {
|
|
39
|
+
session.sendCustomMessage(message, options).catch((e) => {
|
|
40
|
+
process.stderr.write(`Extension sendMessage failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
sendUserMessage: (content, options) => {
|
|
44
|
+
session.sendUserMessage(content, options).catch((e) => {
|
|
45
|
+
process.stderr.write(
|
|
46
|
+
`Extension sendUserMessage failed: ${e instanceof Error ? e.message : String(e)}\n`,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
appendEntry: (customType, data) => {
|
|
51
|
+
session.sessionManager.appendCustomEntry(customType, data);
|
|
52
|
+
},
|
|
53
|
+
getActiveTools: () => session.getActiveToolNames(),
|
|
54
|
+
getAllTools: () => session.getAllToolNames(),
|
|
55
|
+
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
56
|
+
setModel: async (model) => {
|
|
57
|
+
const key = await session.modelRegistry.getApiKey(model);
|
|
58
|
+
if (!key) return false;
|
|
59
|
+
await session.setModel(model);
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
getThinkingLevel: () => session.thinkingLevel,
|
|
63
|
+
setThinkingLevel: (level) => session.setThinkingLevel(level),
|
|
39
64
|
},
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
// ExtensionContextActions
|
|
66
|
+
{
|
|
67
|
+
getModel: () => session.model,
|
|
68
|
+
isIdle: () => !session.isStreaming,
|
|
69
|
+
abort: () => session.abort(),
|
|
70
|
+
hasPendingMessages: () => session.queuedMessageCount > 0,
|
|
71
|
+
shutdown: () => {},
|
|
42
72
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
// ExtensionCommandContextActions - commands invokable via prompt("/command")
|
|
74
|
+
{
|
|
75
|
+
waitForIdle: () => session.agent.waitForIdle(),
|
|
76
|
+
newSession: async (options) => {
|
|
77
|
+
const success = await session.newSession({ parentSession: options?.parentSession });
|
|
78
|
+
if (success && options?.setup) {
|
|
79
|
+
await options.setup(session.sessionManager);
|
|
80
|
+
}
|
|
81
|
+
return { cancelled: !success };
|
|
82
|
+
},
|
|
83
|
+
branch: async (entryId) => {
|
|
84
|
+
const result = await session.branch(entryId);
|
|
85
|
+
return { cancelled: result.cancelled };
|
|
86
|
+
},
|
|
87
|
+
navigateTree: async (targetId, options) => {
|
|
88
|
+
const result = await session.navigateTree(targetId, { summarize: options?.summarize });
|
|
89
|
+
return { cancelled: result.cancelled };
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
// No UI context
|
|
93
|
+
);
|
|
47
94
|
extensionRunner.onError((err) => {
|
|
48
|
-
|
|
95
|
+
process.stderr.write(`Extension error (${err.extensionPath}): ${err.error}\n`);
|
|
49
96
|
});
|
|
50
97
|
// Emit session_start event
|
|
51
98
|
await extensionRunner.emit({
|
|
@@ -57,7 +104,7 @@ export async function runPrintMode(
|
|
|
57
104
|
session.subscribe((event) => {
|
|
58
105
|
// In JSON mode, output all events
|
|
59
106
|
if (mode === "json") {
|
|
60
|
-
|
|
107
|
+
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
61
108
|
}
|
|
62
109
|
});
|
|
63
110
|
|
|
@@ -81,14 +128,14 @@ export async function runPrintMode(
|
|
|
81
128
|
|
|
82
129
|
// Check for error/aborted
|
|
83
130
|
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
|
|
84
|
-
|
|
131
|
+
process.stderr.write(`${assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`}\n`);
|
|
85
132
|
process.exit(1);
|
|
86
133
|
}
|
|
87
134
|
|
|
88
135
|
// Output text content
|
|
89
136
|
for (const content of assistantMsg.content) {
|
|
90
137
|
if (content.type === "text") {
|
|
91
|
-
|
|
138
|
+
process.stdout.write(`${content.text}\n`);
|
|
92
139
|
}
|
|
93
140
|
}
|
|
94
141
|
}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { nanoid } from "nanoid";
|
|
15
15
|
import type { AgentSession } from "../../core/agent-session";
|
|
16
|
-
import type { ExtensionUIContext } from "../../core/extensions/index";
|
|
17
|
-
import { theme } from "../interactive/theme/theme";
|
|
16
|
+
import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../../core/extensions/index";
|
|
17
|
+
import { type Theme, theme } from "../interactive/theme/theme";
|
|
18
18
|
import type {
|
|
19
19
|
RpcCommand,
|
|
20
20
|
RpcExtensionUIRequest,
|
|
@@ -38,7 +38,7 @@ export type {
|
|
|
38
38
|
*/
|
|
39
39
|
export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
40
40
|
const output = (obj: RpcResponse | RpcExtensionUIRequest | object) => {
|
|
41
|
-
|
|
41
|
+
process.stdout.write(`${JSON.stringify(obj)}\n`);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
const success = <T extends RpcCommand["type"]>(
|
|
@@ -57,71 +57,101 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// Pending extension UI requests waiting for response
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
type PendingExtensionRequest = {
|
|
61
|
+
resolve: (response: RpcExtensionUIResponse) => void;
|
|
62
|
+
reject: (error: Error) => void;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
|
|
66
|
+
|
|
67
|
+
// Shutdown request flag (wrapped in object to allow mutation with const)
|
|
68
|
+
const shutdownState = { requested: false };
|
|
69
|
+
|
|
70
|
+
/** Helper for dialog methods with signal/timeout support */
|
|
71
|
+
function createDialogPromise<T>(
|
|
72
|
+
opts: ExtensionUIDialogOptions | undefined,
|
|
73
|
+
defaultValue: T,
|
|
74
|
+
request: Record<string, unknown>,
|
|
75
|
+
parseResponse: (response: RpcExtensionUIResponse) => T,
|
|
76
|
+
): Promise<T> {
|
|
77
|
+
if (opts?.signal?.aborted) return Promise.resolve(defaultValue);
|
|
78
|
+
|
|
79
|
+
const id = nanoid();
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
82
|
+
|
|
83
|
+
const cleanup = () => {
|
|
84
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
85
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
86
|
+
pendingExtensionRequests.delete(id);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const onAbort = () => {
|
|
90
|
+
cleanup();
|
|
91
|
+
resolve(defaultValue);
|
|
92
|
+
};
|
|
93
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
94
|
+
|
|
95
|
+
if (opts?.timeout !== undefined) {
|
|
96
|
+
timeoutId = setTimeout(() => {
|
|
97
|
+
cleanup();
|
|
98
|
+
resolve(defaultValue);
|
|
99
|
+
}, opts.timeout);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pendingExtensionRequests.set(id, {
|
|
103
|
+
resolve: (response: RpcExtensionUIResponse) => {
|
|
104
|
+
cleanup();
|
|
105
|
+
resolve(parseResponse(response));
|
|
106
|
+
},
|
|
107
|
+
reject,
|
|
108
|
+
});
|
|
109
|
+
output({ type: "extension_ui_request", id, ...request } as RpcExtensionUIRequest);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
64
112
|
|
|
65
113
|
/**
|
|
66
114
|
* Create an extension UI context that uses the RPC protocol.
|
|
67
115
|
*/
|
|
68
116
|
const createExtensionUIContext = (): ExtensionUIContext => ({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
async input(title: string, placeholder?: string): Promise<string | undefined> {
|
|
108
|
-
const id = nanoid();
|
|
109
|
-
return new Promise((resolve, reject) => {
|
|
110
|
-
pendingExtensionRequests.set(id, {
|
|
111
|
-
resolve: (response: RpcExtensionUIResponse) => {
|
|
112
|
-
if ("cancelled" in response && response.cancelled) {
|
|
113
|
-
resolve(undefined);
|
|
114
|
-
} else if ("value" in response) {
|
|
115
|
-
resolve(response.value);
|
|
116
|
-
} else {
|
|
117
|
-
resolve(undefined);
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
reject,
|
|
121
|
-
});
|
|
122
|
-
output({ type: "extension_ui_request", id, method: "input", title, placeholder } as RpcExtensionUIRequest);
|
|
123
|
-
});
|
|
124
|
-
},
|
|
117
|
+
select: (title, options, dialogOptions) =>
|
|
118
|
+
createDialogPromise(
|
|
119
|
+
dialogOptions,
|
|
120
|
+
undefined,
|
|
121
|
+
{ method: "select", title, options, timeout: dialogOptions?.timeout },
|
|
122
|
+
(response) =>
|
|
123
|
+
"cancelled" in response && response.cancelled
|
|
124
|
+
? undefined
|
|
125
|
+
: "value" in response
|
|
126
|
+
? response.value
|
|
127
|
+
: undefined,
|
|
128
|
+
),
|
|
129
|
+
|
|
130
|
+
confirm: (title, message, dialogOptions) =>
|
|
131
|
+
createDialogPromise(
|
|
132
|
+
dialogOptions,
|
|
133
|
+
false,
|
|
134
|
+
{ method: "confirm", title, message, timeout: dialogOptions?.timeout },
|
|
135
|
+
(response) =>
|
|
136
|
+
"cancelled" in response && response.cancelled
|
|
137
|
+
? false
|
|
138
|
+
: "confirmed" in response
|
|
139
|
+
? response.confirmed
|
|
140
|
+
: false,
|
|
141
|
+
),
|
|
142
|
+
|
|
143
|
+
input: (title, placeholder, dialogOptions) =>
|
|
144
|
+
createDialogPromise(
|
|
145
|
+
dialogOptions,
|
|
146
|
+
undefined,
|
|
147
|
+
{ method: "input", title, placeholder, timeout: dialogOptions?.timeout },
|
|
148
|
+
(response) =>
|
|
149
|
+
"cancelled" in response && response.cancelled
|
|
150
|
+
? undefined
|
|
151
|
+
: "value" in response
|
|
152
|
+
? response.value
|
|
153
|
+
: undefined,
|
|
154
|
+
),
|
|
125
155
|
|
|
126
156
|
notify(message: string, type?: "info" | "warning" | "error"): void {
|
|
127
157
|
// Fire and forget - no response needed
|
|
@@ -195,6 +225,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
195
225
|
return new Promise((resolve, reject) => {
|
|
196
226
|
pendingExtensionRequests.set(id, {
|
|
197
227
|
resolve: (response: RpcExtensionUIResponse) => {
|
|
228
|
+
pendingExtensionRequests.delete(id);
|
|
198
229
|
if ("cancelled" in response && response.cancelled) {
|
|
199
230
|
resolve(undefined);
|
|
200
231
|
} else if ("value" in response) {
|
|
@@ -212,27 +243,88 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
212
243
|
get theme() {
|
|
213
244
|
return theme;
|
|
214
245
|
},
|
|
246
|
+
|
|
247
|
+
getAllThemes() {
|
|
248
|
+
return [];
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
getTheme(_name: string) {
|
|
252
|
+
return undefined;
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
setTheme(_theme: string | Theme) {
|
|
256
|
+
// Theme switching not supported in RPC mode
|
|
257
|
+
return { success: false, error: "Theme switching not supported in RPC mode" };
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
setFooter() {},
|
|
261
|
+
setHeader() {},
|
|
262
|
+
setEditorComponent() {},
|
|
215
263
|
});
|
|
216
264
|
|
|
217
265
|
// Set up extensions with RPC-based UI context
|
|
218
266
|
const extensionRunner = session.extensionRunner;
|
|
219
267
|
if (extensionRunner) {
|
|
220
|
-
extensionRunner.initialize(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
268
|
+
extensionRunner.initialize(
|
|
269
|
+
// ExtensionActions
|
|
270
|
+
{
|
|
271
|
+
sendMessage: (message, options) => {
|
|
272
|
+
session.sendCustomMessage(message, options).catch((e) => {
|
|
273
|
+
output(error(undefined, "extension_send", e.message));
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
sendUserMessage: (content, options) => {
|
|
277
|
+
session.sendUserMessage(content, options).catch((e) => {
|
|
278
|
+
output(error(undefined, "extension_send_user", e.message));
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
appendEntry: (customType, data) => {
|
|
282
|
+
session.sessionManager.appendCustomEntry(customType, data);
|
|
283
|
+
},
|
|
284
|
+
getActiveTools: () => session.getActiveToolNames(),
|
|
285
|
+
getAllTools: () => session.getAllToolNames(),
|
|
286
|
+
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
287
|
+
setModel: async (model) => {
|
|
288
|
+
const key = await session.modelRegistry.getApiKey(model);
|
|
289
|
+
if (!key) return false;
|
|
290
|
+
await session.setModel(model);
|
|
291
|
+
return true;
|
|
292
|
+
},
|
|
293
|
+
getThinkingLevel: () => session.thinkingLevel,
|
|
294
|
+
setThinkingLevel: (level) => session.setThinkingLevel(level),
|
|
226
295
|
},
|
|
227
|
-
|
|
228
|
-
|
|
296
|
+
// ExtensionContextActions
|
|
297
|
+
{
|
|
298
|
+
getModel: () => session.agent.state.model,
|
|
299
|
+
isIdle: () => !session.isStreaming,
|
|
300
|
+
abort: () => session.abort(),
|
|
301
|
+
hasPendingMessages: () => session.queuedMessageCount > 0,
|
|
302
|
+
shutdown: () => {
|
|
303
|
+
shutdownState.requested = true;
|
|
304
|
+
},
|
|
229
305
|
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
306
|
+
// ExtensionCommandContextActions - commands invokable via prompt("/command")
|
|
307
|
+
{
|
|
308
|
+
waitForIdle: () => session.agent.waitForIdle(),
|
|
309
|
+
newSession: async (options) => {
|
|
310
|
+
const success = await session.newSession({ parentSession: options?.parentSession });
|
|
311
|
+
// Note: setup callback runs but no UI feedback in RPC mode
|
|
312
|
+
if (success && options?.setup) {
|
|
313
|
+
await options.setup(session.sessionManager);
|
|
314
|
+
}
|
|
315
|
+
return { cancelled: !success };
|
|
316
|
+
},
|
|
317
|
+
branch: async (entryId) => {
|
|
318
|
+
const result = await session.branch(entryId);
|
|
319
|
+
return { cancelled: result.cancelled };
|
|
320
|
+
},
|
|
321
|
+
navigateTree: async (targetId, options) => {
|
|
322
|
+
const result = await session.navigateTree(targetId, { summarize: options?.summarize });
|
|
323
|
+
return { cancelled: result.cancelled };
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
createExtensionUIContext(),
|
|
327
|
+
);
|
|
236
328
|
extensionRunner.onError((err) => {
|
|
237
329
|
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
|
|
238
330
|
});
|
|
@@ -466,6 +558,20 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
466
558
|
}
|
|
467
559
|
};
|
|
468
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Check if shutdown was requested and perform shutdown if so.
|
|
563
|
+
* Called after handling each command when waiting for the next command.
|
|
564
|
+
*/
|
|
565
|
+
async function checkShutdownRequested(): Promise<void> {
|
|
566
|
+
if (!shutdownState.requested) return;
|
|
567
|
+
|
|
568
|
+
if (extensionRunner?.hasHandlers("session_shutdown")) {
|
|
569
|
+
await extensionRunner.emit({ type: "session_shutdown" });
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
process.exit(0);
|
|
573
|
+
}
|
|
574
|
+
|
|
469
575
|
// Listen for JSON input using Bun's stdin
|
|
470
576
|
const decoder = new TextDecoder();
|
|
471
577
|
let buffer = "";
|
|
@@ -486,7 +592,6 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
486
592
|
const response = parsed as RpcExtensionUIResponse;
|
|
487
593
|
const pending = pendingExtensionRequests.get(response.id);
|
|
488
594
|
if (pending) {
|
|
489
|
-
pendingExtensionRequests.delete(response.id);
|
|
490
595
|
pending.resolve(response);
|
|
491
596
|
}
|
|
492
597
|
continue;
|
|
@@ -496,6 +601,9 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
496
601
|
const command = parsed as RpcCommand;
|
|
497
602
|
const response = await handleCommand(command);
|
|
498
603
|
output(response);
|
|
604
|
+
|
|
605
|
+
// Check for deferred shutdown request (idle between commands)
|
|
606
|
+
await checkShutdownRequested();
|
|
499
607
|
} catch (e: any) {
|
|
500
608
|
output(error(undefined, "parse", `Failed to parse command: ${e.message}`));
|
|
501
609
|
}
|
|
@@ -180,9 +180,16 @@ export type RpcResponse =
|
|
|
180
180
|
|
|
181
181
|
/** Emitted when an extension needs user input */
|
|
182
182
|
export type RpcExtensionUIRequest =
|
|
183
|
-
| { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[] }
|
|
184
|
-
| { type: "extension_ui_request"; id: string; method: "confirm"; title: string; message: string }
|
|
185
|
-
| {
|
|
183
|
+
| { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[]; timeout?: number }
|
|
184
|
+
| { type: "extension_ui_request"; id: string; method: "confirm"; title: string; message: string; timeout?: number }
|
|
185
|
+
| {
|
|
186
|
+
type: "extension_ui_request";
|
|
187
|
+
id: string;
|
|
188
|
+
method: "input";
|
|
189
|
+
title: string;
|
|
190
|
+
placeholder?: string;
|
|
191
|
+
timeout?: number;
|
|
192
|
+
}
|
|
186
193
|
| { type: "extension_ui_request"; id: string; method: "editor"; title: string; prefill?: string }
|
|
187
194
|
| {
|
|
188
195
|
type: "extension_ui_request";
|
|
@@ -1,35 +1,43 @@
|
|
|
1
|
-
You are
|
|
1
|
+
You are a senior software engineer with deep expertise in debugging, refactoring, and system design. You read files, execute commands, edit code, and write new files to complete coding tasks.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
{{guidelines}}
|
|
7
|
-
|
|
8
|
-
Core behavior:
|
|
9
|
-
- Keep going until the task is fully resolved; do not stop early.
|
|
10
|
-
- Verify with tools; ask for clarification when required.
|
|
11
|
-
- Before tool calls, send a brief preamble describing the next action.
|
|
12
|
-
- Provide short progress updates for long tasks; give a brief heads-up before writing large changes.
|
|
13
|
-
- Follow AGENTS.md instructions by scope: nearest file applies, deeper files override higher-level ones.
|
|
14
|
-
- If update_plan is available, use it for non-trivial multi-step work and keep it updated; skip planning for simple tasks.
|
|
15
|
-
- If a command fails due to sandboxing or needs elevated access, request approval and rerun.
|
|
16
|
-
- Follow project validation/testing guidance; if checks are not run, suggest them in next steps.
|
|
17
|
-
- Resolve blockers before yielding; do not guess.
|
|
18
|
-
- Use tools to ground answers when external or deterministic info is needed; avoid speculation when a tool can verify.
|
|
19
|
-
- Ask for missing or ambiguous tool parameters instead of guessing; confirm before actions.
|
|
20
|
-
- Minimize tool calls and context usage by narrowing queries and summarizing only what is needed.
|
|
21
|
-
- After each tool result, check relevance; iterate or clarify if results conflict or are insufficient.
|
|
22
|
-
- Use concise, scannable responses; include file paths in backticks; use short bullets for multi-item lists; avoid dumping large files.
|
|
3
|
+
<critical>
|
|
4
|
+
Keep working until the user's task is fully resolved. Use tools to verify—never guess.
|
|
5
|
+
</critical>
|
|
23
6
|
|
|
24
7
|
<environment>
|
|
25
8
|
{{environmentInfo}}
|
|
26
9
|
</environment>
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
11
|
+
<tools>
|
|
12
|
+
{{toolsList}}
|
|
13
|
+
</tools>
|
|
14
|
+
{{antiBashSection}}
|
|
15
|
+
<guidelines>
|
|
16
|
+
{{guidelines}}
|
|
17
|
+
</guidelines>
|
|
18
|
+
|
|
19
|
+
<instructions>
|
|
20
|
+
## Execution
|
|
21
|
+
- Before each tool call, state the action in one sentence.
|
|
22
|
+
- After each result, verify relevance; iterate if results conflict or are insufficient.
|
|
23
|
+
- Plan multi-step work with update_plan when available; skip for simple tasks.
|
|
24
|
+
- On sandbox/permission failures, request approval and retry.
|
|
25
|
+
|
|
26
|
+
## Verification
|
|
27
|
+
- Ground answers with tools when deterministic info is needed.
|
|
28
|
+
- Ask for missing parameters instead of assuming.
|
|
29
|
+
- Follow project testing guidance; suggest validation if not run.
|
|
30
|
+
|
|
31
|
+
## Communication
|
|
32
|
+
- Concise, scannable responses; file paths in backticks.
|
|
33
|
+
- Brief progress updates on long tasks; heads-up before large changes.
|
|
34
|
+
- Short bullets for lists; avoid dumping large files.
|
|
35
|
+
|
|
36
|
+
## Project Integration
|
|
37
|
+
- Follow AGENTS.md by scope: nearest file applies, deeper overrides higher.
|
|
38
|
+
- Resolve blockers before yielding.
|
|
39
|
+
</instructions>
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
<critical>
|
|
42
|
+
Complete the full user request before ending your turn. This matters.
|
|
43
|
+
</critical>
|