@oh-my-pi/pi-coding-agent 11.0.3 → 11.1.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 (83) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/package.json +7 -7
  3. package/src/cli/config-cli.ts +1 -1
  4. package/src/commit/agentic/tools/analyze-file.ts +1 -3
  5. package/src/commit/git/errors.ts +4 -6
  6. package/src/config/keybindings.ts +1 -3
  7. package/src/config/settings-schema.ts +10 -0
  8. package/src/exa/mcp-client.ts +5 -8
  9. package/src/extensibility/extensions/loader.ts +7 -10
  10. package/src/extensibility/extensions/runner.ts +5 -15
  11. package/src/extensibility/extensions/types.ts +1 -1
  12. package/src/extensibility/hooks/runner.ts +6 -9
  13. package/src/ipy/kernel.ts +10 -22
  14. package/src/lsp/clients/biome-client.ts +4 -7
  15. package/src/lsp/clients/lsp-linter-client.ts +4 -6
  16. package/src/lsp/index.ts +5 -4
  17. package/src/lsp/utils.ts +18 -0
  18. package/src/modes/components/armin.ts +1 -3
  19. package/src/modes/components/assistant-message.ts +4 -4
  20. package/src/modes/components/bash-execution.ts +5 -3
  21. package/src/modes/components/branch-summary-message.ts +1 -3
  22. package/src/modes/components/compaction-summary-message.ts +1 -3
  23. package/src/modes/components/custom-message.ts +4 -5
  24. package/src/modes/components/extensions/extension-dashboard.ts +10 -16
  25. package/src/modes/components/extensions/extension-list.ts +5 -5
  26. package/src/modes/components/footer.ts +1 -4
  27. package/src/modes/components/hook-editor.ts +7 -32
  28. package/src/modes/components/hook-message.ts +4 -5
  29. package/src/modes/components/plugin-settings.ts +16 -20
  30. package/src/modes/components/python-execution.ts +5 -5
  31. package/src/modes/components/session-selector.ts +6 -7
  32. package/src/modes/components/settings-defs.ts +49 -40
  33. package/src/modes/components/settings-selector.ts +8 -17
  34. package/src/modes/components/skill-message.ts +1 -3
  35. package/src/modes/components/status-line-segment-editor.ts +1 -3
  36. package/src/modes/components/status-line.ts +1 -3
  37. package/src/modes/components/todo-reminder.ts +5 -7
  38. package/src/modes/components/tree-selector.ts +10 -12
  39. package/src/modes/components/ttsr-notification.ts +1 -3
  40. package/src/modes/components/user-message-selector.ts +2 -4
  41. package/src/modes/components/welcome.ts +6 -18
  42. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  43. package/src/modes/controllers/input-controller.ts +6 -33
  44. package/src/modes/controllers/selector-controller.ts +1 -1
  45. package/src/modes/theme/theme.ts +2 -6
  46. package/src/patch/index.ts +1 -4
  47. package/src/prompts/agents/explore.md +1 -0
  48. package/src/prompts/agents/frontmatter.md +2 -1
  49. package/src/prompts/agents/init.md +1 -0
  50. package/src/prompts/agents/plan.md +1 -0
  51. package/src/prompts/agents/reviewer.md +1 -0
  52. package/src/prompts/system/subagent-submit-reminder.md +2 -0
  53. package/src/prompts/system/subagent-system-prompt.md +2 -0
  54. package/src/prompts/system/subagent-user-prompt.md +8 -0
  55. package/src/prompts/system/system-prompt.md +5 -3
  56. package/src/prompts/tools/task.md +216 -163
  57. package/src/session/agent-session.ts +6 -6
  58. package/src/session/auth-storage.ts +1 -3
  59. package/src/session/session-manager.ts +12 -16
  60. package/src/task/agents.ts +2 -0
  61. package/src/task/executor.ts +2 -7
  62. package/src/task/index.ts +43 -23
  63. package/src/task/render.ts +67 -64
  64. package/src/task/template.ts +17 -34
  65. package/src/task/types.ts +49 -22
  66. package/src/tools/ask.ts +1 -3
  67. package/src/tools/bash.ts +1 -4
  68. package/src/tools/browser.ts +1 -3
  69. package/src/tools/exit-plan-mode.ts +1 -4
  70. package/src/tools/fetch.ts +1 -3
  71. package/src/tools/find.ts +4 -3
  72. package/src/tools/grep.ts +4 -4
  73. package/src/tools/index.ts +2 -2
  74. package/src/tools/notebook.ts +1 -5
  75. package/src/tools/python.ts +4 -3
  76. package/src/tools/read.ts +1 -3
  77. package/src/tools/render-utils.ts +23 -0
  78. package/src/tools/ssh.ts +8 -12
  79. package/src/tools/todo-write.ts +1 -4
  80. package/src/tools/tool-errors.ts +1 -4
  81. package/src/tools/write.ts +1 -3
  82. package/src/utils/external-editor.ts +59 -0
  83. package/src/web/search/types.ts +5 -6
package/CHANGELOG.md CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [11.1.0] - 2026-02-05
6
+
7
+ ### Added
8
+
9
+ - Added `sortDiagnostics()` utility function to sort diagnostics by severity, location, and message for consistent output ordering
10
+ - Added `task.isolation.enabled` setting to control whether subagents run in isolated git worktrees
11
+ - Added dynamic task schema that conditionally includes `isolated` parameter based on isolation setting
12
+ - Added `openInEditor()` utility function to centralize external editor handling with support for custom file extensions and stdio configuration
13
+ - Added `getEditorCommand()` utility function to retrieve the user's preferred editor from $VISUAL or $EDITOR environment variables
14
+
15
+ ### Changed
16
+
17
+ - Changed diagnostic output to sort results by severity (errors first), then by file location and message for improved readability
18
+ - Changed task tool to validate isolation setting and reject `isolated` parameter when isolation is disabled
19
+ - Changed task API to use `assignment` field instead of `args` for per-task instructions, with shared `context` prepended to every task
20
+ - Changed task template rendering to use structured context/assignment separation with `<swarm_context>` wrapper instead of placeholder-based substitution
21
+ - Changed task item schema to require `assignment` string (complete per-task instructions) instead of optional `args` object
22
+ - Changed `TaskItem` to remove `args` field and add `assignment` field for clearer per-task instruction semantics
23
+ - Changed agent frontmatter to use `thinking-level` field name instead of `thinkingLevel` for consistency
24
+ - Refactored task rendering to display full task text instead of args in progress and result views
25
+ - Changed `SubmenuSettingDef.getOptions()` method to `options` getter property for cleaner API access
26
+ - Converted static option providers from functions to direct array definitions for improved performance
27
+ - Added `createSubmenuSettingDef()` helper function to support both static and dynamic option providers
28
+ - Modified `setThinkingLevel()` API to accept optional `persist` parameter (defaults to false) for controlling whether thinking level changes are saved to settings
29
+ - Refactored hook editor and input controller to use shared external editor utilities, reducing code duplication
30
+
31
+ ### Removed
32
+
33
+ - Removed `context` parameter from `ExecutorOptions` — context now prepended at template level before task execution
34
+ - Removed `args` field from `AgentProgress` and `SingleResult` interfaces
35
+ - Removed placeholder-based template rendering in favor of structured context/assignment model
36
+
5
37
  ## [11.0.3] - 2026-02-05
6
38
  ### Added
7
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "11.0.3",
3
+ "version": "11.1.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "ompConfig": {
@@ -80,12 +80,12 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@mozilla/readability": "0.6.0",
83
- "@oh-my-pi/omp-stats": "11.0.3",
84
- "@oh-my-pi/pi-agent-core": "11.0.3",
85
- "@oh-my-pi/pi-ai": "11.0.3",
86
- "@oh-my-pi/pi-natives": "11.0.3",
87
- "@oh-my-pi/pi-tui": "11.0.3",
88
- "@oh-my-pi/pi-utils": "11.0.3",
83
+ "@oh-my-pi/omp-stats": "11.1.0",
84
+ "@oh-my-pi/pi-agent-core": "11.1.0",
85
+ "@oh-my-pi/pi-ai": "11.1.0",
86
+ "@oh-my-pi/pi-natives": "11.1.0",
87
+ "@oh-my-pi/pi-tui": "11.1.0",
88
+ "@oh-my-pi/pi-utils": "11.1.0",
89
89
  "@openai/agents": "^0.4.5",
90
90
  "@sinclair/typebox": "^0.34.48",
91
91
  "ajv": "^8.17.1",
@@ -48,7 +48,7 @@ function getSettingValues(def: SettingDef): readonly string[] | undefined {
48
48
  return def.values;
49
49
  }
50
50
  if (def.type === "submenu") {
51
- const options = def.getOptions();
51
+ const options = def.options;
52
52
  if (options.length > 0) {
53
53
  return options.map(o => o.value);
54
54
  }
@@ -62,7 +62,6 @@ export function createAnalyzeFileTool(options: {
62
62
  async execute(toolCallId, params, onUpdate, ctx, signal) {
63
63
  const toolSession = buildToolSession(ctx, options);
64
64
  const taskTool = await TaskTool.create(toolSession);
65
- const context = "{{prompt}}";
66
65
  const numstat = options.state.overview?.numstat ?? [];
67
66
  const tasks = params.files.map((file, index) => {
68
67
  const relatedFiles = formatRelatedFiles(params.files, file, numstat);
@@ -74,12 +73,11 @@ export function createAnalyzeFileTool(options: {
74
73
  return {
75
74
  id: `AnalyzeFile${index + 1}`,
76
75
  description: `Analyze ${file}`,
77
- args: { prompt },
76
+ assignment: prompt,
78
77
  };
79
78
  });
80
79
  const taskParams: TaskParams = {
81
80
  agent: "quick_task",
82
- context,
83
81
  schema: analyzeFileOutputSchema,
84
82
  tasks,
85
83
  };
@@ -1,11 +1,9 @@
1
1
  export class GitError extends Error {
2
- readonly command: string;
3
- readonly stderr: string;
4
-
5
- constructor(command: string, stderr: string) {
2
+ constructor(
3
+ readonly command: string,
4
+ readonly stderr: string,
5
+ ) {
6
6
  super(`${command} failed: ${stderr || "unknown error"}`);
7
- this.command = command;
8
- this.stderr = stderr;
9
7
  this.name = "GitError";
10
8
  }
11
9
  }
@@ -151,11 +151,9 @@ export function formatKeyHints(keys: KeyId | KeyId[]): string {
151
151
  * Manages all keybindings (app + editor).
152
152
  */
153
153
  export class KeybindingsManager {
154
- private config: KeybindingsConfig;
155
154
  private appActionToKeys: Map<AppAction, KeyId[]>;
156
155
 
157
- private constructor(config: KeybindingsConfig) {
158
- this.config = config;
156
+ private constructor(private readonly config: KeybindingsConfig) {
159
157
  this.appActionToKeys = new Map();
160
158
  this.buildMaps();
161
159
  }
@@ -399,6 +399,16 @@ export const SETTINGS_SCHEMA = {
399
399
  // ─────────────────────────────────────────────────────────────────────────
400
400
  // Task tool settings
401
401
  // ─────────────────────────────────────────────────────────────────────────
402
+ "task.isolation.enabled": {
403
+ type: "boolean",
404
+ default: false,
405
+ ui: {
406
+ tab: "tools",
407
+ label: "Task isolation",
408
+ description: "Run subagents in isolated git worktrees",
409
+ submenu: true,
410
+ },
411
+ },
402
412
  "task.maxConcurrency": {
403
413
  type: "number",
404
414
  default: 32,
@@ -249,17 +249,14 @@ export async function fetchMCPToolSchema(
249
249
  export class MCPWrappedTool implements CustomTool<TSchema, ExaRenderDetails> {
250
250
  public readonly name: string;
251
251
  public readonly label: string;
252
- public readonly description: string;
253
- public readonly parameters: TSchema;
254
252
 
255
- private readonly config: MCPToolWrapperConfig;
256
-
257
- constructor(config: MCPToolWrapperConfig, schema: TSchema, description: string) {
258
- this.config = config;
253
+ constructor(
254
+ private readonly config: MCPToolWrapperConfig,
255
+ public readonly parameters: TSchema,
256
+ public readonly description: string,
257
+ ) {
259
258
  this.name = config.name;
260
259
  this.label = config.label;
261
- this.description = description;
262
- this.parameters = schema;
263
260
  }
264
261
 
265
262
  async execute(
@@ -103,17 +103,14 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
103
103
  readonly logger = logger;
104
104
  readonly typebox = TypeBox;
105
105
  readonly pi = piCodingAgent;
106
- readonly events: EventBus;
107
106
  readonly flagValues = new Map<string, boolean | string>();
108
107
 
109
108
  constructor(
110
- private extension: Extension,
111
- private runtime: IExtensionRuntime,
112
- private cwd: string,
113
- eventBus: EventBus,
114
- ) {
115
- this.events = eventBus;
116
- }
109
+ private readonly extension: Extension,
110
+ private readonly runtime: IExtensionRuntime,
111
+ private readonly cwd: string,
112
+ public readonly events: EventBus,
113
+ ) {}
117
114
 
118
115
  on<F extends HandlerFn>(event: string, handler: F): void {
119
116
  const list = this.extension.handlers.get(event) ?? [];
@@ -214,8 +211,8 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
214
211
  return this.runtime.getThinkingLevel();
215
212
  }
216
213
 
217
- setThinkingLevel(level: ThinkingLevel): void {
218
- this.runtime.setThinkingLevel(level);
214
+ setThinkingLevel(level: ThinkingLevel, persist?: boolean): void {
215
+ this.runtime.setThinkingLevel(level, persist);
219
216
  }
220
217
  }
221
218
 
@@ -105,12 +105,7 @@ const noOpUIContext: ExtensionUIContext = {
105
105
  };
106
106
 
107
107
  export class ExtensionRunner {
108
- private extensions: Extension[];
109
- private runtime: ExtensionRuntime;
110
108
  private uiContext: ExtensionUIContext;
111
- private cwd: string;
112
- private sessionManager: SessionManager;
113
- private modelRegistry: ModelRegistry;
114
109
  private errorListeners: Set<ExtensionErrorListener> = new Set();
115
110
  private getModel: () => Model | undefined = () => undefined;
116
111
  private isIdleFn: () => boolean = () => true;
@@ -125,18 +120,13 @@ export class ExtensionRunner {
125
120
  private shutdownHandler: ShutdownHandler = () => {};
126
121
 
127
122
  constructor(
128
- extensions: Extension[],
129
- runtime: ExtensionRuntime,
130
- cwd: string,
131
- sessionManager: SessionManager,
132
- modelRegistry: ModelRegistry,
123
+ private readonly extensions: Extension[],
124
+ private readonly runtime: ExtensionRuntime,
125
+ private readonly cwd: string,
126
+ private readonly sessionManager: SessionManager,
127
+ private readonly modelRegistry: ModelRegistry,
133
128
  ) {
134
- this.extensions = extensions;
135
- this.runtime = runtime;
136
129
  this.uiContext = noOpUIContext;
137
- this.cwd = cwd;
138
- this.sessionManager = sessionManager;
139
- this.modelRegistry = modelRegistry;
140
130
  }
141
131
 
142
132
  initialize(
@@ -839,7 +839,7 @@ export type SetModelHandler = (model: Model) => Promise<boolean>;
839
839
 
840
840
  export type GetThinkingLevelHandler = () => ThinkingLevel;
841
841
 
842
- export type SetThinkingLevelHandler = (level: ThinkingLevel) => void;
842
+ export type SetThinkingLevelHandler = (level: ThinkingLevel, persist?: boolean) => void;
843
843
 
844
844
  /** Shared state created by loader, used during registration and runtime. */
845
845
  export interface ExtensionRuntimeState {
@@ -62,12 +62,8 @@ const noOpUIContext: HookUIContext = {
62
62
  * HookRunner executes hooks and manages event emission.
63
63
  */
64
64
  export class HookRunner {
65
- private hooks: LoadedHook[];
66
65
  private uiContext: HookUIContext;
67
66
  private hasUI: boolean;
68
- private cwd: string;
69
- private sessionManager: SessionManager;
70
- private modelRegistry: ModelRegistry;
71
67
  private errorListeners: Set<HookErrorListener> = new Set();
72
68
  private getModel: () => Model | undefined = () => undefined;
73
69
  private isIdleFn: () => boolean = () => true;
@@ -78,13 +74,14 @@ export class HookRunner {
78
74
  private branchHandler: BranchHandler = async () => ({ cancelled: false });
79
75
  private navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });
80
76
 
81
- constructor(hooks: LoadedHook[], cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry) {
82
- this.hooks = hooks;
77
+ constructor(
78
+ private readonly hooks: LoadedHook[],
79
+ private readonly cwd: string,
80
+ private readonly sessionManager: SessionManager,
81
+ private readonly modelRegistry: ModelRegistry,
82
+ ) {
83
83
  this.uiContext = noOpUIContext;
84
84
  this.hasUI = false;
85
- this.cwd = cwd;
86
- this.sessionManager = sessionManager;
87
- this.modelRegistry = modelRegistry;
88
85
  }
89
86
 
90
87
  /**
package/src/ipy/kernel.ts CHANGED
@@ -16,11 +16,11 @@ const PRELUDE_INTROSPECTION_SNIPPET = "import json\nprint(json.dumps(__omp_prelu
16
16
  const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
17
17
 
18
18
  class SharedGatewayCreateError extends Error {
19
- readonly status: number;
20
-
21
- constructor(status: number, message: string) {
19
+ constructor(
20
+ readonly status: number,
21
+ message: string,
22
+ ) {
22
23
  super(message);
23
- this.status = status;
24
24
  }
25
25
  }
26
26
 
@@ -265,12 +265,6 @@ export function serializeWebSocketMessage(msg: JupyterMessage): ArrayBuffer {
265
265
  }
266
266
 
267
267
  export class PythonKernel {
268
- readonly id: string;
269
- readonly kernelId: string;
270
- readonly gatewayUrl: string;
271
- readonly sessionId: string;
272
- readonly username: string;
273
- readonly isSharedGateway: boolean;
274
268
  readonly #authToken?: string;
275
269
 
276
270
  #ws: WebSocket | null = null;
@@ -281,20 +275,14 @@ export class PythonKernel {
281
275
  #pendingExecutions = new Map<string, (reason: string) => void>();
282
276
 
283
277
  private constructor(
284
- id: string,
285
- kernelId: string,
286
- gatewayUrl: string,
287
- sessionId: string,
288
- username: string,
289
- isSharedGateway: boolean,
278
+ readonly id: string,
279
+ readonly kernelId: string,
280
+ readonly gatewayUrl: string,
281
+ readonly sessionId: string,
282
+ readonly username: string,
283
+ readonly isSharedGateway: boolean,
290
284
  authToken?: string,
291
285
  ) {
292
- this.id = id;
293
- this.kernelId = kernelId;
294
- this.gatewayUrl = gatewayUrl;
295
- this.sessionId = sessionId;
296
- this.username = username;
297
- this.isSharedGateway = isSharedGateway;
298
286
  this.#authToken = authToken;
299
287
  }
300
288
 
@@ -107,18 +107,15 @@ async function runBiome(
107
107
  * Parses Biome's --reporter=json output into LSP Diagnostic format.
108
108
  */
109
109
  export class BiomeClient implements LinterClient {
110
- private config: ServerConfig;
111
- private cwd: string;
112
-
113
110
  /** Factory method for creating BiomeClient instances */
114
111
  static create(config: ServerConfig, cwd: string): LinterClient {
115
112
  return new BiomeClient(config, cwd);
116
113
  }
117
114
 
118
- constructor(config: ServerConfig, cwd: string) {
119
- this.config = config;
120
- this.cwd = cwd;
121
- }
115
+ constructor(
116
+ private readonly config: ServerConfig,
117
+ private readonly cwd: string,
118
+ ) {}
122
119
 
123
120
  async format(filePath: string, content: string): Promise<string> {
124
121
  // Write content to file first
@@ -21,8 +21,6 @@ const DEFAULT_FORMAT_OPTIONS = {
21
21
  * Wraps the existing LSP client infrastructure.
22
22
  */
23
23
  export class LspLinterClient implements LinterClient {
24
- private config: ServerConfig;
25
- private cwd: string;
26
24
  private client: LspClient | null = null;
27
25
 
28
26
  /** Factory method for creating LspLinterClient instances */
@@ -30,10 +28,10 @@ export class LspLinterClient implements LinterClient {
30
28
  return new LspLinterClient(config, cwd);
31
29
  }
32
30
 
33
- constructor(config: ServerConfig, cwd: string) {
34
- this.config = config;
35
- this.cwd = cwd;
36
- }
31
+ constructor(
32
+ private readonly config: ServerConfig,
33
+ private readonly cwd: string,
34
+ ) {}
37
35
 
38
36
  private async getClient(): Promise<LspClient> {
39
37
  if (!this.client) {
package/src/lsp/index.ts CHANGED
@@ -50,6 +50,7 @@ import {
50
50
  formatLocation,
51
51
  formatSymbolInformation,
52
52
  formatWorkspaceEdit,
53
+ sortDiagnostics,
53
54
  symbolKindToIcon,
54
55
  } from "./utils";
55
56
 
@@ -458,6 +459,7 @@ async function getDiagnosticsForFile(
458
459
  }
459
460
  }
460
461
 
462
+ sortDiagnostics(uniqueDiagnostics);
461
463
  const formatted = uniqueDiagnostics.map(d => formatDiagnostic(d, relPath));
462
464
  const limited = limitDiagnosticMessages(formatted);
463
465
  const summary = formatDiagnosticsSummary(uniqueDiagnostics);
@@ -858,10 +860,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
858
860
  public readonly mergeCallAndResult = true;
859
861
  public readonly inline = true;
860
862
 
861
- private readonly session: ToolSession;
862
-
863
- constructor(session: ToolSession) {
864
- this.session = session;
863
+ constructor(private readonly session: ToolSession) {
865
864
  this.description = renderPromptTemplate(lspDescription);
866
865
  }
867
866
 
@@ -966,6 +965,8 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
966
965
  }
967
966
  }
968
967
 
968
+ sortDiagnostics(uniqueDiagnostics);
969
+
969
970
  if (!detailed && targets.length === 1) {
970
971
  if (uniqueDiagnostics.length === 0) {
971
972
  return {
package/src/lsp/utils.ts CHANGED
@@ -205,6 +205,24 @@ export function severityToString(severity?: DiagnosticSeverity): string {
205
205
  return SEVERITY_NAMES[severity ?? 1] ?? "unknown";
206
206
  }
207
207
 
208
+ /**
209
+ * Sort diagnostics by severity, then by location and message.
210
+ */
211
+ export function sortDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
212
+ return diagnostics.sort((a, b) => {
213
+ const aSeverity = a.severity ?? 1;
214
+ const bSeverity = b.severity ?? 1;
215
+ if (aSeverity !== bSeverity) return aSeverity - bSeverity;
216
+ const aLine = a.range.start.line;
217
+ const bLine = b.range.start.line;
218
+ if (aLine !== bLine) return aLine - bLine;
219
+ const aCol = a.range.start.character;
220
+ const bCol = b.range.start.character;
221
+ if (aCol !== bCol) return aCol - bCol;
222
+ return a.message.localeCompare(b.message);
223
+ });
224
+ }
225
+
208
226
  /**
209
227
  * Get icon for diagnostic severity.
210
228
  */
@@ -57,7 +57,6 @@ function buildFinalGrid(): string[][] {
57
57
  }
58
58
 
59
59
  export class ArminComponent implements Component {
60
- private ui: TUI;
61
60
  private interval: ReturnType<typeof setInterval> | null = null;
62
61
  private effect: Effect;
63
62
  private finalGrid: string[][];
@@ -68,8 +67,7 @@ export class ArminComponent implements Component {
68
67
  private gridVersion = 0;
69
68
  private cachedVersion = -1;
70
69
 
71
- constructor(ui: TUI) {
72
- this.ui = ui;
70
+ constructor(private readonly ui: TUI) {
73
71
  this.effect = EFFECTS[Math.floor(Math.random() * EFFECTS.length)];
74
72
  this.finalGrid = buildFinalGrid();
75
73
  this.currentGrid = this.createEmptyGrid();
@@ -8,15 +8,15 @@ import { getMarkdownTheme, theme } from "../../modes/theme/theme";
8
8
  */
9
9
  export class AssistantMessageComponent extends Container {
10
10
  private contentContainer: Container;
11
- private hideThinkingBlock: boolean;
12
11
  private lastMessage?: AssistantMessage;
13
12
  private prerenderInFlight = false;
14
13
 
15
- constructor(message?: AssistantMessage, hideThinkingBlock = false) {
14
+ constructor(
15
+ message?: AssistantMessage,
16
+ private hideThinkingBlock = false,
17
+ ) {
16
18
  super();
17
19
 
18
- this.hideThinkingBlock = hideThinkingBlock;
19
-
20
20
  // Container for text/thinking content
21
21
  this.contentContainer = new Container();
22
22
  this.addChild(this.contentContainer);
@@ -12,7 +12,6 @@ import { truncateToVisualLines } from "./visual-truncate";
12
12
  const PREVIEW_LINES = 20;
13
13
 
14
14
  export class BashExecutionComponent extends Container {
15
- private command: string;
16
15
  private outputLines: string[] = [];
17
16
  private status: "running" | "complete" | "cancelled" | "error" = "running";
18
17
  private exitCode: number | undefined = undefined;
@@ -21,9 +20,12 @@ export class BashExecutionComponent extends Container {
21
20
  private expanded = false;
22
21
  private contentContainer: Container;
23
22
 
24
- constructor(command: string, ui: TUI, excludeFromContext = false) {
23
+ constructor(
24
+ private readonly command: string,
25
+ ui: TUI,
26
+ excludeFromContext = false,
27
+ ) {
25
28
  super();
26
- this.command = command;
27
29
 
28
30
  // Use dim border for excluded-from-context commands (!! prefix)
29
31
  const colorKey = excludeFromContext ? "dim" : "bashMode";
@@ -8,11 +8,9 @@ import type { BranchSummaryMessage } from "../../session/messages";
8
8
  */
9
9
  export class BranchSummaryMessageComponent extends Box {
10
10
  private expanded = false;
11
- private message: BranchSummaryMessage;
12
11
 
13
- constructor(message: BranchSummaryMessage) {
12
+ constructor(private readonly message: BranchSummaryMessage) {
14
13
  super(1, 1, t => theme.bg("customMessageBg", t));
15
- this.message = message;
16
14
  this.updateDisplay();
17
15
  }
18
16
 
@@ -8,11 +8,9 @@ import type { CompactionSummaryMessage } from "../../session/messages";
8
8
  */
9
9
  export class CompactionSummaryMessageComponent extends Box {
10
10
  private expanded = false;
11
- private message: CompactionSummaryMessage;
12
11
 
13
- constructor(message: CompactionSummaryMessage) {
12
+ constructor(private readonly message: CompactionSummaryMessage) {
14
13
  super(1, 1, t => theme.bg("customMessageBg", t));
15
- this.message = message;
16
14
  this.updateDisplay();
17
15
  }
18
16
 
@@ -10,16 +10,15 @@ import type { CustomMessage } from "../../session/messages";
10
10
  * Uses distinct styling to differentiate from user messages.
11
11
  */
12
12
  export class CustomMessageComponent extends Container {
13
- private message: CustomMessage<unknown>;
14
- private customRenderer?: MessageRenderer;
15
13
  private box: Box;
16
14
  private customComponent?: Component;
17
15
  private _expanded = false;
18
16
 
19
- constructor(message: CustomMessage<unknown>, customRenderer?: MessageRenderer) {
17
+ constructor(
18
+ private readonly message: CustomMessage<unknown>,
19
+ private readonly customRenderer?: MessageRenderer,
20
+ ) {
20
21
  super();
21
- this.message = message;
22
- this.customRenderer = customRenderer;
23
22
 
24
23
  this.addChild(new Spacer(1));
25
24
 
@@ -33,17 +33,15 @@ export class ExtensionDashboard extends Container {
33
33
  private state!: DashboardState;
34
34
  private mainList!: ExtensionList;
35
35
  private inspector!: InspectorPanel;
36
- private settingsInstance: Settings | null;
37
- private cwd: string;
38
- private terminalHeight: number;
39
36
 
40
37
  public onClose?: () => void;
41
38
 
42
- private constructor(cwd: string, settingsInstance: Settings | null, terminalHeight: number) {
39
+ private constructor(
40
+ private readonly cwd: string,
41
+ private readonly settingsInstance: Settings | null,
42
+ private readonly terminalHeight: number,
43
+ ) {
43
44
  super();
44
- this.cwd = cwd;
45
- this.settingsInstance = settingsInstance;
46
- this.terminalHeight = terminalHeight;
47
45
  }
48
46
 
49
47
  static async create(
@@ -292,15 +290,11 @@ export class ExtensionDashboard extends Container {
292
290
  * Two-column body component for side-by-side rendering.
293
291
  */
294
292
  class TwoColumnBody implements Component {
295
- private leftPane: ExtensionList;
296
- private rightPane: InspectorPanel;
297
- private maxHeight: number;
298
-
299
- constructor(left: ExtensionList, right: InspectorPanel, maxHeight: number) {
300
- this.leftPane = left;
301
- this.rightPane = right;
302
- this.maxHeight = maxHeight;
303
- }
293
+ constructor(
294
+ private readonly leftPane: ExtensionList,
295
+ private readonly rightPane: InspectorPanel,
296
+ private readonly maxHeight: number,
297
+ ) {}
304
298
 
305
299
  render(width: number): string[] {
306
300
  const leftWidth = Math.floor(width * 0.5);
@@ -31,19 +31,19 @@ type ListItem =
31
31
  | { type: "extension"; item: Extension };
32
32
 
33
33
  export class ExtensionList implements Component {
34
- private extensions: Extension[] = [];
35
34
  private listItems: ListItem[] = [];
36
35
  private selectedIndex = 0;
37
36
  private scrollOffset = 0;
38
37
  private searchQuery = "";
39
38
  private focused = false;
40
- private callbacks: ExtensionListCallbacks;
41
39
  private masterSwitchProvider: string | null = null;
42
40
  private maxVisible: number;
43
41
 
44
- constructor(extensions: Extension[], callbacks: ExtensionListCallbacks = {}, maxVisible?: number) {
45
- this.extensions = extensions;
46
- this.callbacks = callbacks;
42
+ constructor(
43
+ private extensions: Extension[],
44
+ private readonly callbacks: ExtensionListCallbacks = {},
45
+ maxVisible?: number,
46
+ ) {
47
47
  this.masterSwitchProvider = callbacks.masterSwitchProvider ?? null;
48
48
  this.maxVisible = maxVisible ?? DEFAULT_MAX_VISIBLE;
49
49
  this.rebuildList();
@@ -42,16 +42,13 @@ async function findGitHeadPath(): Promise<{ path: string; content: string } | nu
42
42
  * Footer component that shows pwd, token stats, and context usage
43
43
  */
44
44
  export class FooterComponent implements Component {
45
- private session: AgentSession;
46
45
  private cachedBranch: string | null | undefined = undefined; // undefined = not checked yet, null = not in git repo, string = branch name
47
46
  private gitWatcher: fs.FSWatcher | null = null;
48
47
  private onBranchChange: (() => void) | null = null;
49
48
  private autoCompactEnabled: boolean = true;
50
49
  private extensionStatuses: Map<string, string> = new Map();
51
50
 
52
- constructor(session: AgentSession) {
53
- this.session = session;
54
- }
51
+ constructor(private readonly session: AgentSession) {}
55
52
 
56
53
  setAutoCompactEnabled(enabled: boolean): void {
57
54
  this.autoCompactEnabled = enabled;