@oh-my-pi/pi-coding-agent 11.8.2 → 11.9.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 +42 -0
- package/docs/tui.md +9 -9
- package/package.json +7 -7
- package/src/capability/mcp.ts +9 -0
- 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/file-lock.ts +1 -1
- 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/builtin.ts +48 -0
- package/src/discovery/codex.ts +5 -13
- package/src/discovery/cursor.ts +2 -7
- package/src/discovery/mcp-json.ts +33 -0
- 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/extensibility/slash-commands.ts +1 -0
- package/src/index.ts +0 -3
- 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/config-writer.ts +194 -0
- package/src/mcp/config.ts +20 -6
- package/src/mcp/index.ts +4 -0
- package/src/mcp/loader.ts +6 -0
- package/src/mcp/manager.ts +139 -50
- package/src/mcp/oauth-discovery.ts +274 -0
- package/src/mcp/oauth-flow.ts +229 -0
- package/src/mcp/tool-bridge.ts +20 -20
- package/src/mcp/transports/http.ts +107 -66
- package/src/mcp/transports/stdio.ts +74 -59
- package/src/mcp/types.ts +15 -1
- 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/mcp-add-wizard.ts +1286 -0
- 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 +212 -216
- 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 +12 -9
- package/src/modes/controllers/mcp-command-controller.ts +1223 -0
- package/src/modes/interactive-mode.ts +240 -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 +1 -1
- package/src/patch/index.ts +45 -45
- package/src/prompts/tools/task.md +22 -2
- package/src/sdk.ts +1 -0
- package/src/session/agent-session.ts +512 -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/system-prompt.ts +2 -3
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +28 -40
- package/src/task/index.ts +13 -12
- package/src/task/subprocess-tool-registry.ts +5 -5
- package/src/task/worktree.ts +8 -5
- package/src/tools/ask.ts +7 -7
- package/src/tools/bash.ts +15 -10
- package/src/tools/browser.ts +130 -127
- 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 +12 -24
- package/src/tools/index.ts +1 -1
- 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 +13 -11
- 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/builtin.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* .pi is an alias for backwards compatibility.
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
8
9
|
import { registerProvider } from "../capability";
|
|
9
10
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
10
11
|
import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
|
|
@@ -79,13 +80,60 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
79
80
|
const expanded = expandEnvVarsDeep(data.mcpServers);
|
|
80
81
|
for (const [serverName, config] of Object.entries(expanded)) {
|
|
81
82
|
const serverConfig = config as Record<string, unknown>;
|
|
83
|
+
|
|
84
|
+
// Validate enabled: coerce string "true"/"false", warn on other types
|
|
85
|
+
let enabled: boolean | undefined;
|
|
86
|
+
if (serverConfig.enabled === undefined || serverConfig.enabled === null) {
|
|
87
|
+
enabled = undefined;
|
|
88
|
+
} else if (typeof serverConfig.enabled === "boolean") {
|
|
89
|
+
enabled = serverConfig.enabled;
|
|
90
|
+
} else if (typeof serverConfig.enabled === "string") {
|
|
91
|
+
const lower = serverConfig.enabled.toLowerCase();
|
|
92
|
+
if (lower === "false" || lower === "0") enabled = false;
|
|
93
|
+
else if (lower === "true" || lower === "1") enabled = true;
|
|
94
|
+
else {
|
|
95
|
+
logger.warn(`MCP server "${serverName}": invalid enabled value "${serverConfig.enabled}", ignoring`);
|
|
96
|
+
enabled = undefined;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
logger.warn(`MCP server "${serverName}": invalid enabled type ${typeof serverConfig.enabled}, ignoring`);
|
|
100
|
+
enabled = undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Validate timeout: coerce numeric strings, warn on invalid
|
|
104
|
+
let timeout: number | undefined;
|
|
105
|
+
if (serverConfig.timeout === undefined || serverConfig.timeout === null) {
|
|
106
|
+
timeout = undefined;
|
|
107
|
+
} else if (typeof serverConfig.timeout === "number") {
|
|
108
|
+
if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) {
|
|
109
|
+
timeout = serverConfig.timeout;
|
|
110
|
+
} else {
|
|
111
|
+
logger.warn(`MCP server "${serverName}": invalid timeout ${serverConfig.timeout}, ignoring`);
|
|
112
|
+
timeout = undefined;
|
|
113
|
+
}
|
|
114
|
+
} else if (typeof serverConfig.timeout === "string") {
|
|
115
|
+
const parsed = Number(serverConfig.timeout);
|
|
116
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
117
|
+
timeout = parsed;
|
|
118
|
+
} else {
|
|
119
|
+
logger.warn(`MCP server "${serverName}": invalid timeout "${serverConfig.timeout}", ignoring`);
|
|
120
|
+
timeout = undefined;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
logger.warn(`MCP server "${serverName}": invalid timeout type ${typeof serverConfig.timeout}, ignoring`);
|
|
124
|
+
timeout = undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
82
127
|
result.push({
|
|
83
128
|
name: serverName,
|
|
129
|
+
enabled,
|
|
130
|
+
timeout,
|
|
84
131
|
command: serverConfig.command as string | undefined,
|
|
85
132
|
args: serverConfig.args as string[] | undefined,
|
|
86
133
|
env: serverConfig.env as Record<string, string> | undefined,
|
|
87
134
|
url: serverConfig.url as string | undefined,
|
|
88
135
|
headers: serverConfig.headers as Record<string, string> | undefined,
|
|
136
|
+
auth: serverConfig.auth as { type: "oauth" | "apikey"; credentialId?: string } | undefined,
|
|
89
137
|
transport: serverConfig.type as "stdio" | "sse" | "http" | undefined,
|
|
90
138
|
_source: createSourceMeta(PROVIDER_ID, path, level),
|
|
91
139
|
});
|
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
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Priority: 5 (low, as this is a fallback after tool-specific providers)
|
|
8
8
|
*/
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
10
11
|
import { registerProvider } from "../capability";
|
|
11
12
|
import { readFile } from "../capability/fs";
|
|
12
13
|
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
@@ -23,11 +24,17 @@ interface MCPConfigFile {
|
|
|
23
24
|
mcpServers?: Record<
|
|
24
25
|
string,
|
|
25
26
|
{
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
timeout?: number;
|
|
26
29
|
command?: string;
|
|
27
30
|
args?: string[];
|
|
28
31
|
env?: Record<string, string>;
|
|
29
32
|
url?: string;
|
|
30
33
|
headers?: Record<string, string>;
|
|
34
|
+
auth?: {
|
|
35
|
+
type: "oauth" | "apikey";
|
|
36
|
+
credentialId?: string;
|
|
37
|
+
};
|
|
31
38
|
type?: "stdio" | "sse" | "http";
|
|
32
39
|
}
|
|
33
40
|
>;
|
|
@@ -41,13 +48,39 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta): MCPServe
|
|
|
41
48
|
|
|
42
49
|
if (config.mcpServers) {
|
|
43
50
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
51
|
+
// Runtime type validation for user-controlled JSON values
|
|
52
|
+
let enabled: boolean | undefined;
|
|
53
|
+
if (serverConfig.enabled !== undefined) {
|
|
54
|
+
if (typeof serverConfig.enabled === "boolean") {
|
|
55
|
+
enabled = serverConfig.enabled;
|
|
56
|
+
} else {
|
|
57
|
+
logger.warn("MCP server has invalid 'enabled' value, ignoring", { name, value: serverConfig.enabled });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let timeout: number | undefined;
|
|
62
|
+
if (serverConfig.timeout !== undefined) {
|
|
63
|
+
if (
|
|
64
|
+
typeof serverConfig.timeout === "number" &&
|
|
65
|
+
Number.isFinite(serverConfig.timeout) &&
|
|
66
|
+
serverConfig.timeout > 0
|
|
67
|
+
) {
|
|
68
|
+
timeout = serverConfig.timeout;
|
|
69
|
+
} else {
|
|
70
|
+
logger.warn("MCP server has invalid 'timeout' value, ignoring", { name, value: serverConfig.timeout });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
44
74
|
const server: MCPServer = {
|
|
45
75
|
name,
|
|
76
|
+
enabled,
|
|
77
|
+
timeout,
|
|
46
78
|
command: serverConfig.command,
|
|
47
79
|
args: serverConfig.args,
|
|
48
80
|
env: serverConfig.env,
|
|
49
81
|
url: serverConfig.url,
|
|
50
82
|
headers: serverConfig.headers,
|
|
83
|
+
auth: serverConfig.auth,
|
|
51
84
|
transport: serverConfig.type,
|
|
52
85
|
_source: source,
|
|
53
86
|
};
|
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
|
|