From 06c6c324d773564decb5429adc744624a9229cfc Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 19 May 2026 23:15:39 +0200 Subject: [PATCH] chore: enforce erasable TypeScript syntax --- AGENTS.md | 1 + package.json | 3 +- packages/agent/CHANGELOG.md | 4 ++ packages/agent/src/agent.ts | 5 +- packages/agent/src/harness/types.ts | 69 +++++++++---------- packages/ai/CHANGELOG.md | 4 ++ packages/ai/src/utils/event-stream.ts | 9 +-- packages/coding-agent/CHANGELOG.md | 4 ++ .../src/core/agent-session-runtime.ts | 23 +++++-- .../coding-agent/src/core/auth-storage.ts | 10 ++- .../coding-agent/src/core/model-registry.ts | 9 +-- .../interactive/components/countdown-timer.ts | 13 ++-- .../modes/interactive/components/footer.ts | 10 +-- .../interactive/components/login-dialog.ts | 4 +- .../interactive/components/tree-selector.ts | 6 +- .../src/modes/interactive/interactive-mode.ts | 17 +++-- .../coding-agent/src/modes/rpc/rpc-client.ts | 5 +- 17 files changed, 122 insertions(+), 74 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 40438dc4..03b1f4eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,7 @@ - Check node_modules for external API type definitions instead of guessing - **NEVER use inline imports** - no `await import("./foo.js")`, no `import("pkg").Type` in type positions, no dynamic imports for types. Always use standard top-level imports. - NEVER remove or downgrade code to fix type errors from outdated dependencies; upgrade the dependency instead +- In core source packages (`packages/ai/src`, `packages/agent/src`, `packages/coding-agent/src`), use only erasable TypeScript syntax compatible with Node strip-only mode. Do not use constructor parameter properties, `enum`, `namespace`/`module`, `import =`, `export =`, or other TypeScript constructs that require JavaScript emit. Use explicit fields and constructor assignments instead of parameter properties. - Always ask before removing functionality or code that appears to be intentional - Do not preserve backward compatibility unless the user explicitly asks for it - Never hardcode key checks with, eg. `matchesKey(keyData, "ctrl+x")`. All keybindings must be configurable. Add default to matching object (`DEFAULT_EDITOR_KEYBINDINGS` or `DEFAULT_APP_KEYBINDINGS`) diff --git a/package.json b/package.json index af486099..4e31d158 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build && cd ../web-ui && npm run build", "dev": "concurrently --names \"ai,agent,coding-agent,web-ui,tui\" --prefix-colors \"cyan,yellow,red,green,magenta\" \"cd packages/ai && npm run dev\" \"cd packages/agent && npm run dev\" \"cd packages/coding-agent && npm run dev\" \"cd packages/web-ui && npm run dev\" \"cd packages/tui && npm run dev\"", "dev:tsc": "concurrently --names \"ai,web-ui\" --prefix-colors \"cyan,green\" \"cd packages/ai && npm run dev:tsc\" \"cd packages/web-ui && npm run dev:tsc\"", - "check": "biome check --write --error-on-warnings . && tsgo --noEmit && npm run check:browser-smoke && cd packages/web-ui && npm run check", + "check": "biome check --write --error-on-warnings . && tsgo --noEmit && npm run check:erasable-types && npm run check:browser-smoke && cd packages/web-ui && npm run check", + "check:erasable-types": "tsgo -p packages/ai/tsconfig.build.json --noEmit --erasableSyntaxOnly && tsgo -p packages/agent/tsconfig.build.json --noEmit --erasableSyntaxOnly && tsgo -p packages/coding-agent/tsconfig.build.json --noEmit --erasableSyntaxOnly", "check:browser-smoke": "node scripts/check-browser-smoke.mjs", "profile:tui": "node scripts/profile-coding-agent-node.mjs --mode tui", "profile:rpc": "node scripts/profile-coding-agent-node.mjs --mode rpc", diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 62318b07..81b71b75 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Changed source syntax to avoid TypeScript constructs that require JavaScript emit, keeping the package compatible with Node.js strip-only TypeScript checks. + ### Fixed - Fixed tool-call preflight to stop preparing sibling tool calls after the run is aborted ([#4276](https://github.com/earendil-works/pi/issues/4276)). diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index 59c06415..b9e0484c 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -117,8 +117,11 @@ export interface AgentOptions { class PendingMessageQueue { private messages: AgentMessage[] = []; + public mode: QueueMode; - constructor(public mode: QueueMode) {} + constructor(mode: QueueMode) { + this.mode = mode; + } enqueue(message: AgentMessage): void { this.messages.push(message); diff --git a/packages/agent/src/harness/types.ts b/packages/agent/src/harness/types.ts index 96b60f63..c9773be4 100644 --- a/packages/agent/src/harness/types.ts +++ b/packages/agent/src/harness/types.ts @@ -120,16 +120,16 @@ export type FileErrorCode = /** Error returned by {@link FileSystem} file operations. */ export class FileError extends Error { - constructor( - /** Backend-independent error code. */ - public code: FileErrorCode, - message: string, - /** Absolute addressed path associated with the failure, when available. */ - public path?: string, - cause?: Error, - ) { + /** Backend-independent error code. */ + public code: FileErrorCode; + /** Absolute addressed path associated with the failure, when available. */ + public path?: string; + + constructor(code: FileErrorCode, message: string, path?: string, cause?: Error) { super(message, cause === undefined ? undefined : { cause }); this.name = "FileError"; + this.code = code; + this.path = path; } } @@ -144,14 +144,13 @@ export type ExecutionErrorCode = /** Error returned by {@link ExecutionEnv.exec}. */ export class ExecutionError extends Error { - constructor( - /** Backend-independent error code. */ - public code: ExecutionErrorCode, - message: string, - cause?: Error, - ) { + /** Backend-independent error code. */ + public code: ExecutionErrorCode; + + constructor(code: ExecutionErrorCode, message: string, cause?: Error) { super(message, cause === undefined ? undefined : { cause }); this.name = "ExecutionError"; + this.code = code; } } @@ -160,14 +159,13 @@ export type CompactionErrorCode = "aborted" | "summarization_failed" | "invalid_ /** Error returned by compaction helpers. */ export class CompactionError extends Error { - constructor( - /** Backend-independent error code. */ - public code: CompactionErrorCode, - message: string, - cause?: Error, - ) { + /** Backend-independent error code. */ + public code: CompactionErrorCode; + + constructor(code: CompactionErrorCode, message: string, cause?: Error) { super(message, cause === undefined ? undefined : { cause }); this.name = "CompactionError"; + this.code = code; } } @@ -176,14 +174,13 @@ export type BranchSummaryErrorCode = "aborted" | "summarization_failed" | "inval /** Error returned by branch summarization helpers. */ export class BranchSummaryError extends Error { - constructor( - /** Backend-independent error code. */ - public code: BranchSummaryErrorCode, - message: string, - cause?: Error, - ) { + /** Backend-independent error code. */ + public code: BranchSummaryErrorCode; + + constructor(code: BranchSummaryErrorCode, message: string, cause?: Error) { super(message, cause === undefined ? undefined : { cause }); this.name = "BranchSummaryError"; + this.code = code; } } @@ -197,14 +194,13 @@ export type SessionErrorCode = /** Error thrown by session storage, repositories, and session tree operations. */ export class SessionError extends Error { - constructor( - /** Session subsystem error code. */ - public code: SessionErrorCode, - message: string, - cause?: Error, - ) { + /** Session subsystem error code. */ + public code: SessionErrorCode; + + constructor(code: SessionErrorCode, message: string, cause?: Error) { super(message, cause === undefined ? undefined : { cause }); this.name = "SessionError"; + this.code = code; } } @@ -221,13 +217,12 @@ export type AgentHarnessErrorCode = /** Public AgentHarness failure with a stable top-level classification. */ export class AgentHarnessError extends Error { - constructor( - public code: AgentHarnessErrorCode, - message: string, - cause?: Error, - ) { + public code: AgentHarnessErrorCode; + + constructor(code: AgentHarnessErrorCode, message: string, cause?: Error) { super(message, cause === undefined ? undefined : { cause }); this.name = "AgentHarnessError"; + this.code = code; } } diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 001a10a2..1b6d144a 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Changed source syntax to avoid TypeScript constructs that require JavaScript emit, keeping the package compatible with Node.js strip-only TypeScript checks. + ### Fixed - Fixed OpenAI-compatible `streamSimple()` requests to stop sending model-derived default output token caps, avoiding context-window reservation failures on servers such as vLLM while preserving explicit `maxTokens` and required Anthropic `max_tokens` handling ([#4675](https://github.com/earendil-works/pi/issues/4675)). diff --git a/packages/ai/src/utils/event-stream.ts b/packages/ai/src/utils/event-stream.ts index f4a7ceba..43461ea2 100644 --- a/packages/ai/src/utils/event-stream.ts +++ b/packages/ai/src/utils/event-stream.ts @@ -7,11 +7,12 @@ export class EventStream implements AsyncIterable { private done = false; private finalResultPromise: Promise; private resolveFinalResult!: (result: R) => void; + private isComplete: (event: T) => boolean; + private extractResult: (event: T) => R; - constructor( - private isComplete: (event: T) => boolean, - private extractResult: (event: T) => R, - ) { + constructor(isComplete: (event: T) => boolean, extractResult: (event: T) => R) { + this.isComplete = isComplete; + this.extractResult = extractResult; this.finalResultPromise = new Promise((resolve) => { this.resolveFinalResult = resolve; }); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c190751a..9625a075 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Changed source syntax to avoid TypeScript constructs that require JavaScript emit, keeping core sources compatible with Node.js strip-only TypeScript checks. + ### Fixed - Fixed the system prompt to tell models to resolve pi docs and examples under the absolute package paths before reading topic-specific relative references ([#4752](https://github.com/earendil-works/pi/issues/4752)). diff --git a/packages/coding-agent/src/core/agent-session-runtime.ts b/packages/coding-agent/src/core/agent-session-runtime.ts index b64def1b..eebd7575 100644 --- a/packages/coding-agent/src/core/agent-session-runtime.ts +++ b/packages/coding-agent/src/core/agent-session-runtime.ts @@ -67,14 +67,25 @@ function extractUserMessageText(content: string | Array<{ type: string; text?: s export class AgentSessionRuntime { private rebindSession?: (session: AgentSession) => Promise; private beforeSessionInvalidate?: () => void; + private _session: AgentSession; + private _services: AgentSessionServices; + private readonly createRuntime: CreateAgentSessionRuntimeFactory; + private _diagnostics: AgentSessionRuntimeDiagnostic[]; + private _modelFallbackMessage?: string; constructor( - private _session: AgentSession, - private _services: AgentSessionServices, - private readonly createRuntime: CreateAgentSessionRuntimeFactory, - private _diagnostics: AgentSessionRuntimeDiagnostic[] = [], - private _modelFallbackMessage?: string, - ) {} + _session: AgentSession, + _services: AgentSessionServices, + createRuntime: CreateAgentSessionRuntimeFactory, + _diagnostics: AgentSessionRuntimeDiagnostic[] = [], + _modelFallbackMessage?: string, + ) { + this._session = _session; + this._services = _services; + this.createRuntime = createRuntime; + this._diagnostics = _diagnostics; + this._modelFallbackMessage = _modelFallbackMessage; + } get services(): AgentSessionServices { return this._services; diff --git a/packages/coding-agent/src/core/auth-storage.ts b/packages/coding-agent/src/core/auth-storage.ts index 5b57424e..1522cc67 100644 --- a/packages/coding-agent/src/core/auth-storage.ts +++ b/packages/coding-agent/src/core/auth-storage.ts @@ -50,7 +50,11 @@ export interface AuthStorageBackend { } export class FileAuthStorageBackend implements AuthStorageBackend { - constructor(private authPath: string = join(getAgentDir(), "auth.json")) {} + private authPath: string; + + constructor(authPath: string = join(getAgentDir(), "auth.json")) { + this.authPath = authPath; + } private ensureParentDir(): void { const dir = dirname(this.authPath); @@ -194,8 +198,10 @@ export class AuthStorage { private fallbackResolver?: (provider: string) => string | undefined; private loadError: Error | null = null; private errors: Error[] = []; + private storage: AuthStorageBackend; - private constructor(private storage: AuthStorageBackend) { + private constructor(storage: AuthStorageBackend) { + this.storage = storage; this.reload(); } diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts index a30ee210..6cf8c40f 100644 --- a/packages/coding-agent/src/core/model-registry.ts +++ b/packages/coding-agent/src/core/model-registry.ts @@ -334,11 +334,12 @@ export class ModelRegistry { private modelRequestHeaders: Map> = new Map(); private registeredProviders: Map = new Map(); private loadError: string | undefined = undefined; + readonly authStorage: AuthStorage; + private modelsJsonPath: string | undefined; - private constructor( - readonly authStorage: AuthStorage, - private modelsJsonPath: string | undefined, - ) { + private constructor(authStorage: AuthStorage, modelsJsonPath: string | undefined) { + this.authStorage = authStorage; + this.modelsJsonPath = modelsJsonPath; this.loadModels(); } diff --git a/packages/coding-agent/src/modes/interactive/components/countdown-timer.ts b/packages/coding-agent/src/modes/interactive/components/countdown-timer.ts index ac730644..73ec2734 100644 --- a/packages/coding-agent/src/modes/interactive/components/countdown-timer.ts +++ b/packages/coding-agent/src/modes/interactive/components/countdown-timer.ts @@ -7,13 +7,14 @@ import type { TUI } from "@earendil-works/pi-tui"; export class CountdownTimer { private intervalId: ReturnType | undefined; private remainingSeconds: number; + private tui: TUI | undefined; + private onTick: (seconds: number) => void; + private onExpire: () => void; - constructor( - timeoutMs: number, - private tui: TUI | undefined, - private onTick: (seconds: number) => void, - private onExpire: () => void, - ) { + constructor(timeoutMs: number, tui: TUI | undefined, onTick: (seconds: number) => void, onExpire: () => void) { + this.tui = tui; + this.onTick = onTick; + this.onExpire = onExpire; this.remainingSeconds = Math.ceil(timeoutMs / 1000); this.onTick(this.remainingSeconds); diff --git a/packages/coding-agent/src/modes/interactive/components/footer.ts b/packages/coding-agent/src/modes/interactive/components/footer.ts index 8f0653e7..7e8109a5 100644 --- a/packages/coding-agent/src/modes/interactive/components/footer.ts +++ b/packages/coding-agent/src/modes/interactive/components/footer.ts @@ -32,11 +32,13 @@ function formatTokens(count: number): string { */ export class FooterComponent implements Component { private autoCompactEnabled = true; + private session: AgentSession; + private footerData: ReadonlyFooterDataProvider; - constructor( - private session: AgentSession, - private footerData: ReadonlyFooterDataProvider, - ) {} + constructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) { + this.session = session; + this.footerData = footerData; + } setSession(session: AgentSession): void { this.session = session; diff --git a/packages/coding-agent/src/modes/interactive/components/login-dialog.ts b/packages/coding-agent/src/modes/interactive/components/login-dialog.ts index 737e8b27..a9a6f2b0 100644 --- a/packages/coding-agent/src/modes/interactive/components/login-dialog.ts +++ b/packages/coding-agent/src/modes/interactive/components/login-dialog.ts @@ -15,6 +15,7 @@ export class LoginDialogComponent extends Container implements Focusable { private abortController = new AbortController(); private inputResolver?: (value: string) => void; private inputRejecter?: (error: Error) => void; + private onComplete: (success: boolean, message?: string) => void; // Focusable implementation - propagate to input for IME cursor positioning private _focused = false; @@ -29,12 +30,13 @@ export class LoginDialogComponent extends Container implements Focusable { constructor( tui: TUI, providerId: string, - private onComplete: (success: boolean, message?: string) => void, + onComplete: (success: boolean, message?: string) => void, providerNameOverride?: string, titleOverride?: string, ) { super(); this.tui = tui; + this.onComplete = onComplete; const providerInfo = getOAuthProviders().find((p) => p.id === providerId); const providerName = providerNameOverride || providerInfo?.name || providerId; diff --git a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts index 29962d7e..d895a892 100644 --- a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts @@ -1056,7 +1056,11 @@ class TreeList implements Component { /** Component that displays the current search query */ class SearchLine implements Component { - constructor(private treeList: TreeList) {} + private treeList: TreeList; + + constructor(treeList: TreeList) { + this.treeList = treeList; + } invalidate(): void {} diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 616e6218..7fe94ded 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -147,14 +147,19 @@ function isExpandable(obj: unknown): obj is Expandable { } class ExpandableText extends Text implements Expandable { + private readonly getCollapsedText: () => string; + private readonly getExpandedText: () => string; + constructor( - private readonly getCollapsedText: () => string, - private readonly getExpandedText: () => string, + getCollapsedText: () => string, + getExpandedText: () => string, expanded = false, paddingX = 0, paddingY = 0, ) { super(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY); + this.getCollapsedText = getCollapsedText; + this.getExpandedText = getExpandedText; } setExpanded(expanded: boolean): void { @@ -334,6 +339,8 @@ export class InteractiveMode { // Custom header from extension (undefined = use built-in header) private customHeader: (Component & { dispose?(): void }) | undefined = undefined; + private options: InteractiveModeOptions; + // Convenience accessors private get session(): AgentSession { return this.runtimeHost.session; @@ -348,11 +355,9 @@ export class InteractiveMode { return this.session.settingsManager; } - constructor( - runtimeHost: AgentSessionRuntime, - private options: InteractiveModeOptions = {}, - ) { + constructor(runtimeHost: AgentSessionRuntime, options: InteractiveModeOptions = {}) { this.runtimeHost = runtimeHost; + this.options = options; this.runtimeHost.setBeforeSessionInvalidate(() => { this.resetExtensionUI(); }); diff --git a/packages/coding-agent/src/modes/rpc/rpc-client.ts b/packages/coding-agent/src/modes/rpc/rpc-client.ts index 00fd3cd9..194f479e 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-client.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-client.ts @@ -59,8 +59,11 @@ export class RpcClient { new Map(); private requestId = 0; private stderr = ""; + private options: RpcClientOptions; - constructor(private options: RpcClientOptions = {}) {} + constructor(options: RpcClientOptions = {}) { + this.options = options; + } /** * Start the RPC agent process.