@oh-my-pi/pi-coding-agent 11.8.2 → 11.8.3
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/docs/tui.md +9 -9
- package/package.json +7 -7
- package/src/cli/file-processor.ts +8 -13
- package/src/cli/oclif-help.ts +1 -1
- package/src/cli.ts +14 -0
- package/src/commit/git/index.ts +16 -16
- package/src/config/keybindings.ts +11 -11
- package/src/config/model-registry.ts +31 -66
- package/src/config/settings.ts +88 -95
- package/src/config.ts +2 -2
- package/src/cursor.ts +4 -4
- package/src/debug/index.ts +28 -28
- package/src/discovery/codex.ts +5 -13
- package/src/discovery/cursor.ts +2 -7
- package/src/exa/mcp-client.ts +2 -2
- package/src/exa/websets.ts +2 -2
- package/src/export/html/index.ts +3 -3
- package/src/export/ttsr.ts +27 -27
- package/src/extensibility/custom-tools/loader.ts +9 -9
- package/src/extensibility/extensions/runner.ts +64 -64
- package/src/extensibility/hooks/runner.ts +46 -46
- package/src/extensibility/plugins/manager.ts +49 -49
- package/src/index.ts +0 -1
- package/src/internal-urls/router.ts +5 -5
- package/src/ipy/kernel.ts +61 -57
- package/src/lsp/client.ts +1 -1
- package/src/lsp/clients/biome-client.ts +2 -2
- package/src/lsp/clients/lsp-linter-client.ts +7 -7
- package/src/lsp/index.ts +9 -9
- package/src/mcp/manager.ts +47 -47
- package/src/mcp/tool-bridge.ts +12 -12
- package/src/mcp/transports/http.ts +34 -34
- package/src/mcp/transports/stdio.ts +47 -47
- package/src/modes/components/assistant-message.ts +25 -25
- package/src/modes/components/bash-execution.ts +51 -51
- package/src/modes/components/bordered-loader.ts +7 -7
- package/src/modes/components/branch-summary-message.ts +7 -7
- package/src/modes/components/compaction-summary-message.ts +7 -7
- package/src/modes/components/countdown-timer.ts +15 -15
- package/src/modes/components/custom-editor.ts +22 -22
- package/src/modes/components/custom-message.ts +21 -21
- package/src/modes/components/dynamic-border.ts +3 -3
- package/src/modes/components/extensions/extension-dashboard.ts +72 -72
- package/src/modes/components/extensions/extension-list.ts +99 -97
- package/src/modes/components/extensions/inspector-panel.ts +26 -26
- package/src/modes/components/footer.ts +36 -36
- package/src/modes/components/history-search.ts +52 -52
- package/src/modes/components/hook-editor.ts +20 -20
- package/src/modes/components/hook-input.ts +20 -20
- package/src/modes/components/hook-message.ts +22 -22
- package/src/modes/components/hook-selector.ts +52 -52
- package/src/modes/components/index.ts +0 -1
- package/src/modes/components/login-dialog.ts +57 -57
- package/src/modes/components/model-selector.ts +173 -173
- package/src/modes/components/oauth-selector.ts +45 -45
- package/src/modes/components/plugin-settings.ts +52 -52
- package/src/modes/components/python-execution.ts +53 -53
- package/src/modes/components/queue-mode-selector.ts +7 -7
- package/src/modes/components/read-tool-group.ts +23 -23
- package/src/modes/components/session-selector.ts +40 -37
- package/src/modes/components/settings-selector.ts +80 -80
- package/src/modes/components/show-images-selector.ts +7 -7
- package/src/modes/components/skill-message.ts +27 -27
- package/src/modes/components/status-line-segment-editor.ts +81 -81
- package/src/modes/components/status-line.ts +73 -73
- package/src/modes/components/theme-selector.ts +11 -11
- package/src/modes/components/thinking-selector.ts +7 -7
- package/src/modes/components/todo-display.ts +19 -19
- package/src/modes/components/todo-reminder.ts +9 -9
- package/src/modes/components/tool-execution.ts +204 -196
- package/src/modes/components/tree-selector.ts +144 -144
- package/src/modes/components/ttsr-notification.ts +17 -17
- package/src/modes/components/user-message-selector.ts +18 -18
- package/src/modes/components/welcome.ts +10 -10
- package/src/modes/controllers/command-controller.ts +0 -7
- package/src/modes/controllers/event-controller.ts +23 -23
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +4 -9
- package/src/modes/interactive-mode.ts +234 -241
- package/src/modes/rpc/rpc-client.ts +77 -77
- package/src/modes/rpc/rpc-mode.ts +5 -5
- package/src/modes/theme/theme.ts +113 -113
- package/src/modes/types.ts +0 -1
- package/src/patch/index.ts +45 -45
- package/src/prompts/tools/task.md +22 -2
- package/src/session/agent-session.ts +463 -476
- package/src/session/agent-storage.ts +72 -75
- package/src/session/auth-storage.ts +186 -252
- package/src/session/history-storage.ts +36 -38
- package/src/session/session-manager.ts +300 -299
- package/src/session/session-storage.ts +65 -90
- package/src/ssh/connection-manager.ts +9 -9
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +2 -2
- package/src/task/index.ts +13 -12
- package/src/task/subprocess-tool-registry.ts +5 -5
- package/src/tools/ask.ts +7 -7
- package/src/tools/bash.ts +8 -7
- package/src/tools/browser.ts +123 -123
- package/src/tools/calculator.ts +46 -46
- package/src/tools/context.ts +9 -9
- package/src/tools/exit-plan-mode.ts +5 -5
- package/src/tools/fetch.ts +5 -5
- package/src/tools/find.ts +16 -16
- package/src/tools/grep.ts +10 -10
- package/src/tools/notebook.ts +6 -6
- package/src/tools/output-meta.ts +10 -2
- package/src/tools/python.ts +12 -11
- package/src/tools/read.ts +17 -17
- package/src/tools/ssh.ts +9 -9
- package/src/tools/submit-result.ts +13 -13
- package/src/tools/todo-write.ts +6 -6
- package/src/tools/write.ts +10 -10
- package/src/tui/output-block.ts +6 -6
- package/src/tui/utils.ts +9 -9
- package/src/utils/event-bus.ts +10 -10
- package/src/utils/frontmatter.ts +1 -1
- package/src/utils/ignore-files.ts +1 -1
- package/src/web/search/index.ts +5 -5
- package/src/web/search/providers/anthropic.ts +7 -2
- package/examples/hooks/snake.ts +0 -342
- package/src/modes/components/armin.ts +0 -379
package/src/discovery/codex.ts
CHANGED
|
@@ -28,7 +28,7 @@ import type { SlashCommand } from "../capability/slash-command";
|
|
|
28
28
|
import { slashCommandCapability } from "../capability/slash-command";
|
|
29
29
|
import type { CustomTool } from "../capability/tool";
|
|
30
30
|
import { toolCapability } from "../capability/tool";
|
|
31
|
-
import type { LoadContext, LoadResult } from "../capability/types";
|
|
31
|
+
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
32
32
|
import { parseFrontmatter } from "../utils/frontmatter";
|
|
33
33
|
import {
|
|
34
34
|
createSourceMeta,
|
|
@@ -276,8 +276,7 @@ async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashComm
|
|
|
276
276
|
const projectCommandsDir = path.join(codexDir, "commands");
|
|
277
277
|
|
|
278
278
|
const transformCommand =
|
|
279
|
-
(level: "user" | "project") =>
|
|
280
|
-
(name: string, content: string, path: string, source: ReturnType<typeof createSourceMeta>) => {
|
|
279
|
+
(level: "user" | "project") => (name: string, content: string, path: string, source: SourceMeta) => {
|
|
281
280
|
const { frontmatter, body } = parseFrontmatter(content, { source: path });
|
|
282
281
|
const commandName = frontmatter.name || name.replace(/\.md$/, "");
|
|
283
282
|
return {
|
|
@@ -315,12 +314,7 @@ async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
|
|
|
315
314
|
const codexDir = getProjectCodexDir(ctx);
|
|
316
315
|
const projectPromptsDir = path.join(codexDir, "prompts");
|
|
317
316
|
|
|
318
|
-
const transformPrompt = (
|
|
319
|
-
name: string,
|
|
320
|
-
content: string,
|
|
321
|
-
path: string,
|
|
322
|
-
source: ReturnType<typeof createSourceMeta>,
|
|
323
|
-
) => {
|
|
317
|
+
const transformPrompt = (name: string, content: string, path: string, source: SourceMeta) => {
|
|
324
318
|
const { frontmatter, body } = parseFrontmatter(content, { source: path });
|
|
325
319
|
const promptName = frontmatter.name || name.replace(/\.md$/, "");
|
|
326
320
|
return {
|
|
@@ -359,8 +353,7 @@ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
|
359
353
|
const projectHooksDir = path.join(codexDir, "hooks");
|
|
360
354
|
|
|
361
355
|
const transformHook =
|
|
362
|
-
(level: "user" | "project") =>
|
|
363
|
-
(name: string, _content: string, path: string, source: ReturnType<typeof createSourceMeta>) => {
|
|
356
|
+
(level: "user" | "project") => (name: string, _content: string, path: string, source: SourceMeta) => {
|
|
364
357
|
const baseName = name.replace(/\.(ts|js)$/, "");
|
|
365
358
|
const match = baseName.match(/^(pre|post)-(.+)$/);
|
|
366
359
|
const hookType = (match?.[1] as "pre" | "post") || "pre";
|
|
@@ -402,8 +395,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
402
395
|
const projectToolsDir = path.join(codexDir, "tools");
|
|
403
396
|
|
|
404
397
|
const transformTool =
|
|
405
|
-
(level: "user" | "project") =>
|
|
406
|
-
(name: string, _content: string, path: string, source: ReturnType<typeof createSourceMeta>) => {
|
|
398
|
+
(level: "user" | "project") => (name: string, _content: string, path: string, source: SourceMeta) => {
|
|
407
399
|
const toolName = name.replace(/\.(ts|js)$/, "");
|
|
408
400
|
return {
|
|
409
401
|
name: toolName,
|
package/src/discovery/cursor.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { Rule } from "../capability/rule";
|
|
|
20
20
|
import { ruleCapability } from "../capability/rule";
|
|
21
21
|
import type { Settings } from "../capability/settings";
|
|
22
22
|
import { settingsCapability } from "../capability/settings";
|
|
23
|
-
import type { LoadContext, LoadResult } from "../capability/types";
|
|
23
|
+
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
24
24
|
import { parseFrontmatter } from "../utils/frontmatter";
|
|
25
25
|
import {
|
|
26
26
|
createSourceMeta,
|
|
@@ -136,12 +136,7 @@ async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
|
|
|
136
136
|
return { items, warnings };
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function transformMDCRule(
|
|
140
|
-
name: string,
|
|
141
|
-
content: string,
|
|
142
|
-
path: string,
|
|
143
|
-
source: ReturnType<typeof createSourceMeta>,
|
|
144
|
-
): Rule {
|
|
139
|
+
function transformMDCRule(name: string, content: string, path: string, source: SourceMeta): Rule {
|
|
145
140
|
const { frontmatter, body } = parseFrontmatter(content, { source: path });
|
|
146
141
|
|
|
147
142
|
// Extract frontmatter fields
|
package/src/exa/mcp-client.ts
CHANGED
|
@@ -217,8 +217,8 @@ export async function fetchMCPToolSchema(
|
|
|
217
217
|
* reducing drift when MCP servers add new parameters.
|
|
218
218
|
*/
|
|
219
219
|
export class MCPWrappedTool implements CustomTool<TSchema, ExaRenderDetails> {
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
readonly name: string;
|
|
221
|
+
readonly label: string;
|
|
222
222
|
|
|
223
223
|
constructor(
|
|
224
224
|
private readonly config: MCPToolWrapperConfig,
|
package/src/exa/websets.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* CRUD operations for websets, items, searches, enrichments, and monitoring.
|
|
5
5
|
*/
|
|
6
|
-
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { type TObject, type TProperties, Type } from "@sinclair/typebox";
|
|
7
7
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
8
8
|
import { callWebsetsTool, findApiKey } from "./mcp-client";
|
|
9
9
|
import type { ExaRenderDetails } from "./types";
|
|
@@ -13,7 +13,7 @@ function createWebsetTool(
|
|
|
13
13
|
name: string,
|
|
14
14
|
label: string,
|
|
15
15
|
description: string,
|
|
16
|
-
parameters:
|
|
16
|
+
parameters: TObject<TProperties>,
|
|
17
17
|
mcpToolName: string,
|
|
18
18
|
): CustomTool<any, ExaRenderDetails> {
|
|
19
19
|
return {
|
package/src/export/html/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AgentState } from "@oh-my-pi/pi-agent-core";
|
|
|
3
3
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { APP_NAME } from "../../config";
|
|
5
5
|
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme";
|
|
6
|
-
import { SessionManager } from "../../session/session-manager";
|
|
6
|
+
import { type SessionEntry, type SessionHeader, SessionManager } from "../../session/session-manager";
|
|
7
7
|
// Pre-generated template (created by scripts/generate-template.ts at publish time)
|
|
8
8
|
import { TEMPLATE } from "./template.generated";
|
|
9
9
|
|
|
@@ -92,8 +92,8 @@ async function generateThemeVars(themeName?: string): Promise<string> {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
interface SessionData {
|
|
95
|
-
header:
|
|
96
|
-
entries:
|
|
95
|
+
header: SessionHeader | null;
|
|
96
|
+
entries: SessionEntry[];
|
|
97
97
|
leafId: string | null;
|
|
98
98
|
systemPrompt?: string;
|
|
99
99
|
tools?: { name: string; description: string }[];
|
package/src/export/ttsr.ts
CHANGED
|
@@ -28,29 +28,29 @@ const DEFAULT_SETTINGS: Required<TtsrSettings> = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export class TtsrManager {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
readonly #settings: Required<TtsrSettings>;
|
|
32
|
+
readonly #rules = new Map<string, TtsrEntry>();
|
|
33
|
+
readonly #injectionRecords = new Map<string, InjectionRecord>();
|
|
34
|
+
#buffer = "";
|
|
35
|
+
#messageCount = 0;
|
|
36
36
|
|
|
37
37
|
constructor(settings?: TtsrSettings) {
|
|
38
|
-
this
|
|
38
|
+
this.#settings = { ...DEFAULT_SETTINGS, ...settings };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** Check if a rule can be triggered based on repeat settings */
|
|
42
|
-
|
|
43
|
-
const record = this
|
|
42
|
+
#canTrigger(ruleName: string): boolean {
|
|
43
|
+
const record = this.#injectionRecords.get(ruleName);
|
|
44
44
|
if (!record) {
|
|
45
45
|
return true;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
if (this
|
|
48
|
+
if (this.#settings.repeatMode === "once") {
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const gap = this
|
|
53
|
-
return gap >= this
|
|
52
|
+
const gap = this.#messageCount - record.lastInjectedAt;
|
|
53
|
+
return gap >= this.#settings.repeatGap;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/** Add a TTSR rule to be monitored */
|
|
@@ -59,13 +59,13 @@ export class TtsrManager {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (this
|
|
62
|
+
if (this.#rules.has(rule.name)) {
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
try {
|
|
67
67
|
const regex = new RegExp(rule.ttsrTrigger);
|
|
68
|
-
this
|
|
68
|
+
this.#rules.set(rule.name, { rule, regex });
|
|
69
69
|
logger.debug("TTSR rule registered", {
|
|
70
70
|
ruleName: rule.name,
|
|
71
71
|
pattern: rule.ttsrTrigger,
|
|
@@ -83,8 +83,8 @@ export class TtsrManager {
|
|
|
83
83
|
check(streamBuffer: string): Rule[] {
|
|
84
84
|
const matches: Rule[] = [];
|
|
85
85
|
|
|
86
|
-
for (const [name, entry] of this
|
|
87
|
-
if (!this
|
|
86
|
+
for (const [name, entry] of this.#rules) {
|
|
87
|
+
if (!this.#canTrigger(name)) {
|
|
88
88
|
continue;
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -103,24 +103,24 @@ export class TtsrManager {
|
|
|
103
103
|
/** Mark rules as injected (won't trigger again until conditions allow) */
|
|
104
104
|
markInjected(rulesToMark: Rule[]): void {
|
|
105
105
|
for (const rule of rulesToMark) {
|
|
106
|
-
this
|
|
106
|
+
this.#injectionRecords.set(rule.name, { lastInjectedAt: this.#messageCount });
|
|
107
107
|
logger.debug("TTSR rule marked as injected", {
|
|
108
108
|
ruleName: rule.name,
|
|
109
|
-
messageCount: this
|
|
110
|
-
repeatMode: this
|
|
109
|
+
messageCount: this.#messageCount,
|
|
110
|
+
repeatMode: this.#settings.repeatMode,
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/** Get names of all injected rules (for persistence) */
|
|
116
116
|
getInjectedRuleNames(): string[] {
|
|
117
|
-
return Array.from(this
|
|
117
|
+
return Array.from(this.#injectionRecords.keys());
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/** Restore injected state from a list of rule names */
|
|
121
121
|
restoreInjected(ruleNames: string[]): void {
|
|
122
122
|
for (const name of ruleNames) {
|
|
123
|
-
this
|
|
123
|
+
this.#injectionRecords.set(name, { lastInjectedAt: 0 });
|
|
124
124
|
}
|
|
125
125
|
if (ruleNames.length > 0) {
|
|
126
126
|
logger.debug("TTSR injected state restored", { ruleNames });
|
|
@@ -129,36 +129,36 @@ export class TtsrManager {
|
|
|
129
129
|
|
|
130
130
|
/** Reset stream buffer (called on new turn) */
|
|
131
131
|
resetBuffer(): void {
|
|
132
|
-
this
|
|
132
|
+
this.#buffer = "";
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/** Get current stream buffer */
|
|
136
136
|
getBuffer(): string {
|
|
137
|
-
return this
|
|
137
|
+
return this.#buffer;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/** Append to stream buffer */
|
|
141
141
|
appendToBuffer(text: string): void {
|
|
142
|
-
this
|
|
142
|
+
this.#buffer += text;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/** Check if any TTSRs are registered */
|
|
146
146
|
hasRules(): boolean {
|
|
147
|
-
return this
|
|
147
|
+
return this.#rules.size > 0;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/** Increment message counter (call after each turn) */
|
|
151
151
|
incrementMessageCount(): void {
|
|
152
|
-
this
|
|
152
|
+
this.#messageCount++;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/** Get current message count */
|
|
156
156
|
getMessageCount(): number {
|
|
157
|
-
return this
|
|
157
|
+
return this.#messageCount;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/** Get settings */
|
|
161
161
|
getSettings(): Required<TtsrSettings> {
|
|
162
|
-
return this
|
|
162
|
+
return this.#settings;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
@@ -119,11 +119,11 @@ interface ToolPathWithSource {
|
|
|
119
119
|
export class CustomToolLoader {
|
|
120
120
|
tools: LoadedCustomTool[] = [];
|
|
121
121
|
errors: ToolLoadError[] = [];
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
#sharedApi: CustomToolAPI;
|
|
123
|
+
#seenNames: Set<string>;
|
|
124
124
|
|
|
125
125
|
constructor(cwd: string, builtInToolNames: string[]) {
|
|
126
|
-
this
|
|
126
|
+
this.#sharedApi = {
|
|
127
127
|
cwd,
|
|
128
128
|
exec: (command: string, args: string[], options?: ExecOptions) =>
|
|
129
129
|
execCommand(command, args, options?.cwd ?? cwd, options),
|
|
@@ -133,12 +133,12 @@ export class CustomToolLoader {
|
|
|
133
133
|
typebox,
|
|
134
134
|
pi: piCodingAgent,
|
|
135
135
|
};
|
|
136
|
-
this
|
|
136
|
+
this.#seenNames = new Set<string>(builtInToolNames);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
async load(pathsWithSources: ToolPathWithSource[]): Promise<void> {
|
|
140
140
|
for (const { path: toolPath, source } of pathsWithSources) {
|
|
141
|
-
const { tools: loadedTools, error } = await loadTool(toolPath, this
|
|
141
|
+
const { tools: loadedTools, error } = await loadTool(toolPath, this.#sharedApi.cwd, this.#sharedApi, source);
|
|
142
142
|
|
|
143
143
|
if (error) {
|
|
144
144
|
this.errors.push(error);
|
|
@@ -148,7 +148,7 @@ export class CustomToolLoader {
|
|
|
148
148
|
if (loadedTools) {
|
|
149
149
|
for (const loadedTool of loadedTools) {
|
|
150
150
|
// Check for name conflicts
|
|
151
|
-
if (this
|
|
151
|
+
if (this.#seenNames.has(loadedTool.tool.name)) {
|
|
152
152
|
this.errors.push({
|
|
153
153
|
path: toolPath,
|
|
154
154
|
error: `Tool name "${loadedTool.tool.name}" conflicts with existing tool`,
|
|
@@ -157,7 +157,7 @@ export class CustomToolLoader {
|
|
|
157
157
|
continue;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
this
|
|
160
|
+
this.#seenNames.add(loadedTool.tool.name);
|
|
161
161
|
this.tools.push(loadedTool);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -165,8 +165,8 @@ export class CustomToolLoader {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
setUIContext(uiContext: HookUIContext, hasUI: boolean): void {
|
|
168
|
-
this
|
|
169
|
-
this
|
|
168
|
+
this.#sharedApi.ui = uiContext;
|
|
169
|
+
this.#sharedApi.hasUI = hasUI;
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -153,23 +153,23 @@ const noOpUIContext: ExtensionUIContext = {
|
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
export class ExtensionRunner {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
156
|
+
#uiContext: ExtensionUIContext;
|
|
157
|
+
#errorListeners: Set<ExtensionErrorListener> = new Set();
|
|
158
|
+
#getModel: () => Model | undefined = () => undefined;
|
|
159
|
+
#isIdleFn: () => boolean = () => true;
|
|
160
|
+
#waitForIdleFn: () => Promise<void> = async () => {};
|
|
161
|
+
#abortFn: () => void = () => {};
|
|
162
|
+
#hasPendingMessagesFn: () => boolean = () => false;
|
|
163
|
+
#getContextUsageFn: () => ContextUsage | undefined = () => undefined;
|
|
164
|
+
#compactFn: (instructionsOrOptions?: string | CompactOptions) => Promise<void> = async () => {};
|
|
165
|
+
#getSystemPromptFn: () => string = () => "";
|
|
166
|
+
#newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });
|
|
167
|
+
#branchHandler: BranchHandler = async () => ({ cancelled: false });
|
|
168
|
+
#navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });
|
|
169
|
+
#switchSessionHandler: SwitchSessionHandler = async () => ({ cancelled: false });
|
|
170
|
+
#reloadHandler: () => Promise<void> = async () => {};
|
|
171
|
+
#shutdownHandler: ShutdownHandler = () => {};
|
|
172
|
+
#commandDiagnostics: Array<{ type: string; message: string; path: string }> = [];
|
|
173
173
|
|
|
174
174
|
constructor(
|
|
175
175
|
private readonly extensions: Extension[],
|
|
@@ -178,7 +178,7 @@ export class ExtensionRunner {
|
|
|
178
178
|
private readonly sessionManager: SessionManager,
|
|
179
179
|
private readonly modelRegistry: ModelRegistry,
|
|
180
180
|
) {
|
|
181
|
-
this
|
|
181
|
+
this.#uiContext = noOpUIContext;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
initialize(
|
|
@@ -200,34 +200,34 @@ export class ExtensionRunner {
|
|
|
200
200
|
this.runtime.setThinkingLevel = actions.setThinkingLevel;
|
|
201
201
|
|
|
202
202
|
// Context actions (required)
|
|
203
|
-
this
|
|
204
|
-
this
|
|
205
|
-
this
|
|
206
|
-
this
|
|
207
|
-
this
|
|
208
|
-
this
|
|
203
|
+
this.#getModel = contextActions.getModel;
|
|
204
|
+
this.#isIdleFn = contextActions.isIdle;
|
|
205
|
+
this.#abortFn = contextActions.abort;
|
|
206
|
+
this.#hasPendingMessagesFn = contextActions.hasPendingMessages;
|
|
207
|
+
this.#shutdownHandler = contextActions.shutdown;
|
|
208
|
+
this.#getSystemPromptFn = contextActions.getSystemPrompt;
|
|
209
209
|
|
|
210
210
|
// Command context actions (optional, only for interactive mode)
|
|
211
211
|
if (commandContextActions) {
|
|
212
|
-
this
|
|
213
|
-
this
|
|
214
|
-
this
|
|
215
|
-
this
|
|
216
|
-
this
|
|
217
|
-
this
|
|
218
|
-
this
|
|
219
|
-
this
|
|
212
|
+
this.#waitForIdleFn = commandContextActions.waitForIdle;
|
|
213
|
+
this.#newSessionHandler = commandContextActions.newSession;
|
|
214
|
+
this.#branchHandler = commandContextActions.branch;
|
|
215
|
+
this.#navigateTreeHandler = commandContextActions.navigateTree;
|
|
216
|
+
this.#switchSessionHandler = commandContextActions.switchSession;
|
|
217
|
+
this.#reloadHandler = commandContextActions.reload;
|
|
218
|
+
this.#getContextUsageFn = commandContextActions.getContextUsage;
|
|
219
|
+
this.#compactFn = commandContextActions.compact;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
this
|
|
222
|
+
this.#uiContext = uiContext ?? noOpUIContext;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
getUIContext(): ExtensionUIContext {
|
|
226
|
-
return this
|
|
226
|
+
return this.#uiContext;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
hasUI(): boolean {
|
|
230
|
-
return this
|
|
230
|
+
return this.#uiContext !== noOpUIContext;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
getExtensionPaths(): string[] {
|
|
@@ -259,7 +259,7 @@ export class ExtensionRunner {
|
|
|
259
259
|
this.runtime.flagValues.set(name, value);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
static readonly #RESERVED_SHORTCUTS = new Set([
|
|
263
263
|
"ctrl+c",
|
|
264
264
|
"ctrl+d",
|
|
265
265
|
"ctrl+z",
|
|
@@ -282,7 +282,7 @@ export class ExtensionRunner {
|
|
|
282
282
|
for (const [key, shortcut] of ext.shortcuts) {
|
|
283
283
|
const normalizedKey = key.toLowerCase() as KeyId;
|
|
284
284
|
|
|
285
|
-
if (ExtensionRunner
|
|
285
|
+
if (ExtensionRunner.#RESERVED_SHORTCUTS.has(normalizedKey)) {
|
|
286
286
|
logger.warn("Extension shortcut conflicts with built-in shortcut", {
|
|
287
287
|
key,
|
|
288
288
|
extensionPath: shortcut.extensionPath,
|
|
@@ -305,12 +305,12 @@ export class ExtensionRunner {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
onError(listener: ExtensionErrorListener): () => void {
|
|
308
|
-
this
|
|
309
|
-
return () => this
|
|
308
|
+
this.#errorListeners.add(listener);
|
|
309
|
+
return () => this.#errorListeners.delete(listener);
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
emitError(error: ExtensionError): void {
|
|
313
|
-
for (const listener of this
|
|
313
|
+
for (const listener of this.#errorListeners) {
|
|
314
314
|
listener(error);
|
|
315
315
|
}
|
|
316
316
|
}
|
|
@@ -336,14 +336,14 @@ export class ExtensionRunner {
|
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
getRegisteredCommands(reserved?: Set<string>): RegisteredCommand[] {
|
|
339
|
-
this
|
|
339
|
+
this.#commandDiagnostics = [];
|
|
340
340
|
|
|
341
341
|
const commands: RegisteredCommand[] = [];
|
|
342
342
|
for (const ext of this.extensions) {
|
|
343
343
|
for (const command of ext.commands.values()) {
|
|
344
344
|
if (reserved?.has(command.name)) {
|
|
345
345
|
const message = `Extension command '${command.name}' from ${ext.path} conflicts with built-in commands. Skipping.`;
|
|
346
|
-
this
|
|
346
|
+
this.#commandDiagnostics.push({ type: "warning", message, path: ext.path });
|
|
347
347
|
if (!this.hasUI()) {
|
|
348
348
|
logger.warn(message);
|
|
349
349
|
}
|
|
@@ -357,7 +357,7 @@ export class ExtensionRunner {
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
getCommandDiagnostics(): Array<{ type: string; message: string; path: string }> {
|
|
360
|
-
return this
|
|
360
|
+
return this.#commandDiagnostics;
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
getCommand(name: string): RegisteredCommand | undefined {
|
|
@@ -371,11 +371,11 @@ export class ExtensionRunner {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
createContext(): ExtensionContext {
|
|
374
|
-
const getModel = this
|
|
374
|
+
const getModel = this.#getModel;
|
|
375
375
|
return {
|
|
376
|
-
ui: this
|
|
377
|
-
getContextUsage: () => this
|
|
378
|
-
compact: instructionsOrOptions => this
|
|
376
|
+
ui: this.#uiContext,
|
|
377
|
+
getContextUsage: () => this.#getContextUsageFn(),
|
|
378
|
+
compact: instructionsOrOptions => this.#compactFn(instructionsOrOptions),
|
|
379
379
|
hasUI: this.hasUI(),
|
|
380
380
|
cwd: this.cwd,
|
|
381
381
|
sessionManager: this.sessionManager,
|
|
@@ -383,12 +383,12 @@ export class ExtensionRunner {
|
|
|
383
383
|
get model() {
|
|
384
384
|
return getModel();
|
|
385
385
|
},
|
|
386
|
-
isIdle: () => this
|
|
387
|
-
abort: () => this
|
|
388
|
-
hasPendingMessages: () => this
|
|
389
|
-
shutdown: () => this
|
|
390
|
-
getSystemPrompt: () => this
|
|
391
|
-
hasQueuedMessages: () => this
|
|
386
|
+
isIdle: () => this.#isIdleFn(),
|
|
387
|
+
abort: () => this.#abortFn(),
|
|
388
|
+
hasPendingMessages: () => this.#hasPendingMessagesFn(),
|
|
389
|
+
shutdown: () => this.#shutdownHandler(),
|
|
390
|
+
getSystemPrompt: () => this.#getSystemPromptFn(),
|
|
391
|
+
hasQueuedMessages: () => this.#hasPendingMessagesFn(), // deprecated alias
|
|
392
392
|
};
|
|
393
393
|
}
|
|
394
394
|
|
|
@@ -396,24 +396,24 @@ export class ExtensionRunner {
|
|
|
396
396
|
* Request a graceful shutdown. Called by extension tools and event handlers.
|
|
397
397
|
*/
|
|
398
398
|
shutdown(): void {
|
|
399
|
-
this
|
|
399
|
+
this.#shutdownHandler();
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
createCommandContext(): ExtensionCommandContext {
|
|
403
403
|
return {
|
|
404
404
|
...this.createContext(),
|
|
405
|
-
getContextUsage: () => this
|
|
406
|
-
waitForIdle: () => this
|
|
407
|
-
newSession: options => this
|
|
408
|
-
branch: entryId => this
|
|
409
|
-
navigateTree: (targetId, options) => this
|
|
410
|
-
switchSession: sessionPath => this
|
|
411
|
-
reload: () => this
|
|
412
|
-
compact: instructionsOrOptions => this
|
|
405
|
+
getContextUsage: () => this.#getContextUsageFn(),
|
|
406
|
+
waitForIdle: () => this.#waitForIdleFn(),
|
|
407
|
+
newSession: options => this.#newSessionHandler(options),
|
|
408
|
+
branch: entryId => this.#branchHandler(entryId),
|
|
409
|
+
navigateTree: (targetId, options) => this.#navigateTreeHandler(targetId, options),
|
|
410
|
+
switchSession: sessionPath => this.#switchSessionHandler(sessionPath),
|
|
411
|
+
reload: () => this.#reloadHandler(),
|
|
412
|
+
compact: instructionsOrOptions => this.#compactFn(instructionsOrOptions),
|
|
413
413
|
};
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
#isSessionBeforeEvent(event: RunnerEmitEvent): event is SessionBeforeEvent {
|
|
417
417
|
return (
|
|
418
418
|
event.type === "session_before_switch" ||
|
|
419
419
|
event.type === "session_before_branch" ||
|
|
@@ -434,7 +434,7 @@ export class ExtensionRunner {
|
|
|
434
434
|
try {
|
|
435
435
|
const handlerResult = await handler(event, ctx);
|
|
436
436
|
|
|
437
|
-
if (this
|
|
437
|
+
if (this.#isSessionBeforeEvent(event) && handlerResult) {
|
|
438
438
|
result = handlerResult as SessionBeforeEventResult;
|
|
439
439
|
if (result.cancel) {
|
|
440
440
|
return result as RunnerEmitResult<TEvent>;
|