@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.
Files changed (141) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/docs/tui.md +9 -9
  3. package/package.json +7 -7
  4. package/src/capability/mcp.ts +9 -0
  5. package/src/cli/file-processor.ts +8 -13
  6. package/src/cli/oclif-help.ts +1 -1
  7. package/src/cli.ts +14 -0
  8. package/src/commit/git/index.ts +16 -16
  9. package/src/config/file-lock.ts +1 -1
  10. package/src/config/keybindings.ts +11 -11
  11. package/src/config/model-registry.ts +31 -66
  12. package/src/config/settings.ts +88 -95
  13. package/src/config.ts +2 -2
  14. package/src/cursor.ts +4 -4
  15. package/src/debug/index.ts +28 -28
  16. package/src/discovery/builtin.ts +48 -0
  17. package/src/discovery/codex.ts +5 -13
  18. package/src/discovery/cursor.ts +2 -7
  19. package/src/discovery/mcp-json.ts +33 -0
  20. package/src/exa/mcp-client.ts +2 -2
  21. package/src/exa/websets.ts +2 -2
  22. package/src/export/html/index.ts +3 -3
  23. package/src/export/ttsr.ts +27 -27
  24. package/src/extensibility/custom-tools/loader.ts +9 -9
  25. package/src/extensibility/extensions/runner.ts +64 -64
  26. package/src/extensibility/hooks/runner.ts +46 -46
  27. package/src/extensibility/plugins/manager.ts +49 -49
  28. package/src/extensibility/slash-commands.ts +1 -0
  29. package/src/index.ts +0 -3
  30. package/src/internal-urls/router.ts +5 -5
  31. package/src/ipy/kernel.ts +61 -57
  32. package/src/lsp/client.ts +1 -1
  33. package/src/lsp/clients/biome-client.ts +2 -2
  34. package/src/lsp/clients/lsp-linter-client.ts +7 -7
  35. package/src/lsp/index.ts +9 -9
  36. package/src/mcp/config-writer.ts +194 -0
  37. package/src/mcp/config.ts +20 -6
  38. package/src/mcp/index.ts +4 -0
  39. package/src/mcp/loader.ts +6 -0
  40. package/src/mcp/manager.ts +139 -50
  41. package/src/mcp/oauth-discovery.ts +274 -0
  42. package/src/mcp/oauth-flow.ts +229 -0
  43. package/src/mcp/tool-bridge.ts +20 -20
  44. package/src/mcp/transports/http.ts +107 -66
  45. package/src/mcp/transports/stdio.ts +74 -59
  46. package/src/mcp/types.ts +15 -1
  47. package/src/modes/components/assistant-message.ts +25 -25
  48. package/src/modes/components/bash-execution.ts +51 -51
  49. package/src/modes/components/bordered-loader.ts +7 -7
  50. package/src/modes/components/branch-summary-message.ts +7 -7
  51. package/src/modes/components/compaction-summary-message.ts +7 -7
  52. package/src/modes/components/countdown-timer.ts +15 -15
  53. package/src/modes/components/custom-editor.ts +22 -22
  54. package/src/modes/components/custom-message.ts +21 -21
  55. package/src/modes/components/dynamic-border.ts +3 -3
  56. package/src/modes/components/extensions/extension-dashboard.ts +72 -72
  57. package/src/modes/components/extensions/extension-list.ts +99 -97
  58. package/src/modes/components/extensions/inspector-panel.ts +26 -26
  59. package/src/modes/components/footer.ts +36 -36
  60. package/src/modes/components/history-search.ts +52 -52
  61. package/src/modes/components/hook-editor.ts +20 -20
  62. package/src/modes/components/hook-input.ts +20 -20
  63. package/src/modes/components/hook-message.ts +22 -22
  64. package/src/modes/components/hook-selector.ts +52 -52
  65. package/src/modes/components/index.ts +0 -1
  66. package/src/modes/components/login-dialog.ts +57 -57
  67. package/src/modes/components/mcp-add-wizard.ts +1286 -0
  68. package/src/modes/components/model-selector.ts +173 -173
  69. package/src/modes/components/oauth-selector.ts +45 -45
  70. package/src/modes/components/plugin-settings.ts +52 -52
  71. package/src/modes/components/python-execution.ts +53 -53
  72. package/src/modes/components/queue-mode-selector.ts +7 -7
  73. package/src/modes/components/read-tool-group.ts +23 -23
  74. package/src/modes/components/session-selector.ts +40 -37
  75. package/src/modes/components/settings-selector.ts +80 -80
  76. package/src/modes/components/show-images-selector.ts +7 -7
  77. package/src/modes/components/skill-message.ts +27 -27
  78. package/src/modes/components/status-line-segment-editor.ts +81 -81
  79. package/src/modes/components/status-line.ts +73 -73
  80. package/src/modes/components/theme-selector.ts +11 -11
  81. package/src/modes/components/thinking-selector.ts +7 -7
  82. package/src/modes/components/todo-display.ts +19 -19
  83. package/src/modes/components/todo-reminder.ts +9 -9
  84. package/src/modes/components/tool-execution.ts +212 -216
  85. package/src/modes/components/tree-selector.ts +144 -144
  86. package/src/modes/components/ttsr-notification.ts +17 -17
  87. package/src/modes/components/user-message-selector.ts +18 -18
  88. package/src/modes/components/welcome.ts +10 -10
  89. package/src/modes/controllers/command-controller.ts +0 -7
  90. package/src/modes/controllers/event-controller.ts +23 -23
  91. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  92. package/src/modes/controllers/input-controller.ts +12 -9
  93. package/src/modes/controllers/mcp-command-controller.ts +1223 -0
  94. package/src/modes/interactive-mode.ts +240 -241
  95. package/src/modes/rpc/rpc-client.ts +77 -77
  96. package/src/modes/rpc/rpc-mode.ts +5 -5
  97. package/src/modes/theme/theme.ts +113 -113
  98. package/src/modes/types.ts +1 -1
  99. package/src/patch/index.ts +45 -45
  100. package/src/prompts/tools/task.md +22 -2
  101. package/src/sdk.ts +1 -0
  102. package/src/session/agent-session.ts +512 -476
  103. package/src/session/agent-storage.ts +72 -75
  104. package/src/session/auth-storage.ts +186 -252
  105. package/src/session/history-storage.ts +36 -38
  106. package/src/session/session-manager.ts +300 -299
  107. package/src/session/session-storage.ts +65 -90
  108. package/src/ssh/connection-manager.ts +9 -9
  109. package/src/system-prompt.ts +2 -3
  110. package/src/task/agents.ts +1 -1
  111. package/src/task/executor.ts +28 -40
  112. package/src/task/index.ts +13 -12
  113. package/src/task/subprocess-tool-registry.ts +5 -5
  114. package/src/task/worktree.ts +8 -5
  115. package/src/tools/ask.ts +7 -7
  116. package/src/tools/bash.ts +15 -10
  117. package/src/tools/browser.ts +130 -127
  118. package/src/tools/calculator.ts +46 -46
  119. package/src/tools/context.ts +9 -9
  120. package/src/tools/exit-plan-mode.ts +5 -5
  121. package/src/tools/fetch.ts +5 -5
  122. package/src/tools/find.ts +16 -16
  123. package/src/tools/grep.ts +12 -24
  124. package/src/tools/index.ts +1 -1
  125. package/src/tools/notebook.ts +6 -6
  126. package/src/tools/output-meta.ts +10 -2
  127. package/src/tools/python.ts +12 -11
  128. package/src/tools/read.ts +17 -17
  129. package/src/tools/ssh.ts +9 -9
  130. package/src/tools/submit-result.ts +13 -13
  131. package/src/tools/todo-write.ts +6 -6
  132. package/src/tools/write.ts +10 -10
  133. package/src/tui/output-block.ts +6 -6
  134. package/src/tui/utils.ts +9 -9
  135. package/src/utils/event-bus.ts +13 -11
  136. package/src/utils/frontmatter.ts +1 -1
  137. package/src/utils/ignore-files.ts +1 -1
  138. package/src/web/search/index.ts +5 -5
  139. package/src/web/search/providers/anthropic.ts +7 -2
  140. package/examples/hooks/snake.ts +0 -342
  141. package/src/modes/components/armin.ts +0 -379
@@ -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
  });
@@ -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,
@@ -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
  };
@@ -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
- public readonly name: string;
221
- public readonly label: string;
220
+ readonly name: string;
221
+ readonly label: string;
222
222
 
223
223
  constructor(
224
224
  private readonly config: MCPToolWrapperConfig,
@@ -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: ReturnType<typeof Type.Object>,
16
+ parameters: TObject<TProperties>,
17
17
  mcpToolName: string,
18
18
  ): CustomTool<any, ExaRenderDetails> {
19
19
  return {
@@ -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: ReturnType<SessionManager["getHeader"]>;
96
- entries: ReturnType<SessionManager["getEntries"]>;
95
+ header: SessionHeader | null;
96
+ entries: SessionEntry[];
97
97
  leafId: string | null;
98
98
  systemPrompt?: string;
99
99
  tools?: { name: string; description: string }[];
@@ -28,29 +28,29 @@ const DEFAULT_SETTINGS: Required<TtsrSettings> = {
28
28
  };
29
29
 
30
30
  export class TtsrManager {
31
- private readonly settings: Required<TtsrSettings>;
32
- private readonly rules = new Map<string, TtsrEntry>();
33
- private readonly injectionRecords = new Map<string, InjectionRecord>();
34
- private buffer = "";
35
- private messageCount = 0;
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.settings = { ...DEFAULT_SETTINGS, ...settings };
38
+ this.#settings = { ...DEFAULT_SETTINGS, ...settings };
39
39
  }
40
40
 
41
41
  /** Check if a rule can be triggered based on repeat settings */
42
- private canTrigger(ruleName: string): boolean {
43
- const record = this.injectionRecords.get(ruleName);
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.settings.repeatMode === "once") {
48
+ if (this.#settings.repeatMode === "once") {
49
49
  return false;
50
50
  }
51
51
 
52
- const gap = this.messageCount - record.lastInjectedAt;
53
- return gap >= this.settings.repeatGap;
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.rules.has(rule.name)) {
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.rules.set(rule.name, { rule, regex });
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.rules) {
87
- if (!this.canTrigger(name)) {
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.injectionRecords.set(rule.name, { lastInjectedAt: this.messageCount });
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.messageCount,
110
- repeatMode: this.settings.repeatMode,
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.injectionRecords.keys());
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.injectionRecords.set(name, { lastInjectedAt: 0 });
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.buffer = "";
132
+ this.#buffer = "";
133
133
  }
134
134
 
135
135
  /** Get current stream buffer */
136
136
  getBuffer(): string {
137
- return this.buffer;
137
+ return this.#buffer;
138
138
  }
139
139
 
140
140
  /** Append to stream buffer */
141
141
  appendToBuffer(text: string): void {
142
- this.buffer += text;
142
+ this.#buffer += text;
143
143
  }
144
144
 
145
145
  /** Check if any TTSRs are registered */
146
146
  hasRules(): boolean {
147
- return this.rules.size > 0;
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.messageCount++;
152
+ this.#messageCount++;
153
153
  }
154
154
 
155
155
  /** Get current message count */
156
156
  getMessageCount(): number {
157
- return this.messageCount;
157
+ return this.#messageCount;
158
158
  }
159
159
 
160
160
  /** Get settings */
161
161
  getSettings(): Required<TtsrSettings> {
162
- return this.settings;
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
- private sharedApi: CustomToolAPI;
123
- private seenNames: Set<string>;
122
+ #sharedApi: CustomToolAPI;
123
+ #seenNames: Set<string>;
124
124
 
125
125
  constructor(cwd: string, builtInToolNames: string[]) {
126
- this.sharedApi = {
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.seenNames = new Set<string>(builtInToolNames);
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.sharedApi.cwd, this.sharedApi, source);
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.seenNames.has(loadedTool.tool.name)) {
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.seenNames.add(loadedTool.tool.name);
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.sharedApi.ui = uiContext;
169
- this.sharedApi.hasUI = hasUI;
168
+ this.#sharedApi.ui = uiContext;
169
+ this.#sharedApi.hasUI = hasUI;
170
170
  }
171
171
  }
172
172