mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-06-01 15:27:41 +00:00
* feat(sdk): add SDK foundation — type declarations, errors, and utilities Adds standalone SDK building blocks with no SDK source dependencies: - sdk.d.ts: ambient type declarations for SDK bundle - coreSchemas.ts + coreTypes.generated.ts: Zod schemas and generated types - errors.ts: SDK-specific error classes - validation.ts: input validation utilities - messageFilters.ts: extracted message filter logic - handlePromptSubmit.ts: imports from messageFilters - 16 generated-types tests * fix(sdk): narrow assertFunction type from broad Function to callable signature Code review finding: assertFunction used `asserts value is Function` which accepts any function-like value without narrowing. Changed to `(...args: any[]) => any` for better type safety. * fix(sdk): update sdk.d.ts header — manually maintained, not generated Reviewer noted the header said "Generated from index.ts" but no generator produces this file. Updated to "Manually maintained — keep in sync with index.ts". Drift detection added in validate-externals.ts (PR 3). * fix(sdk): align sdk.d.ts types with canonical coreTypes.generated.ts Tighten SDK public type contract to resolve reviewer blockers: - PermissionResult: unknown[] → precise 6-shape discriminated union (addRules/replaceRules/removeRules/setMode/addDirectories/removeDirectories) - SDKSessionInfo: snake_case → camelCase (sessionId, lastModified, etc.) - ForkSessionResult: session_id → sessionId - SDKPermissionRequestMessage: uuid + session_id now required - SDKPermissionTimeoutMessage: added uuid + session_id - SessionMessage: parent_uuid → parentUuid - SDKMessage/SDKUserMessage/SDKResultMessage: replaced loose inline definitions with re-exports from coreTypes.generated.ts * feat(sdk): wire existing code modules + SDK shared utilities Modifies core modules for SDK integration: - QueryEngine, tools, state, commands: SDK type hooks - SDK shared utilities (shared.ts, permissions.ts) - 21 SDK tests (shared-utils, permissions) Stack: main ← pr1-foundation ← pr2-sdk-core * feat(sdk): add snake_case ↔ camelCase key mapping utilities casing.ts provides recursive key transformation for the SDK boundary layer. Internal runtime uses snake_case; public API exposes camelCase. Will be used by shared.ts, sessions.ts, query.ts at export boundaries. * test(sdk): add tests for snake_case ↔ camelCase mapping utilities Covers snakeToCamel, camelToSnake, mapKeysToCamel, mapKeysToSnake including nested objects, arrays, null/undefined, and round-trips. * feat(sdk): add SDK runtime — query engine, sessions, build pipeline Completes the SDK implementation: - SDK build target (dist/sdk.mjs) with TUI dependency stubbing - External dependency lists (scripts/externals.ts) - SDK type generation from Zod schemas (scripts/generate-sdk-types.ts) - External validation (scripts/validate-externals.ts) - SDK source: index, query, v2, sessions modules - agentSdkTypes: re-exports SDK functions (query, createSession, etc.) - 136 SDK tests + 7 build scanner tests Stack: main ← pr1-foundation ← pr2-sdk-core ← pr3-sdk-runtime * fix(sdk): align internal SDK types with camelCase public contract shared.ts: SDKSessionInfo, ForkSessionResult, SessionMessage fields now use camelCase matching sdk.d.ts. SDKPermissionRequestMessage and SDKPermissionTimeoutMessage gain required uuid + session_id fields. permissions.ts: onPermissionRequest/onTimeout callbacks now include uuid and session_id in emitted messages. * fix(sdk): update runtime modules to use camelCase field names sessions.ts: toSDKSessionInfo outputs camelCase keys, entryToSessionMessage uses parentUuid, forkSession returns sessionId. query.ts: reads sessionId from listSessions/forkSession results instead of snake_case session_id. * fix(test): update session tests to use camelCase field names session_id → sessionId in forkSession result assertions and getSessionMessages calls. * fix(sdk): prevent permission timeout race condition with once-only resolve wrapper Add createOnceOnlyResolve utility to prevent double-resolution of promises when timeout and host response happen simultaneously. This ensures deterministic behavior in the permission handling flow. * fix(sdk): improve race condition test robustness * fix(sdk): handle consecutive underscores in snakeToCamel conversion Changes: - Use _+([a-z]) regex to match multiple consecutive underscores before letters - Add lookahead (?=. ) to preserve underscore-letter pairs at string end - Handle dunder names (__proto__, __typename) by stripping wrapper and capitalizing - Add tests for consecutive underscores and trailing underscore preservation * fix(sdk): include original error message in permission callback denial When a canUseTool callback throws an error, the catch block now includes the original error message in the denial message, making debugging easier for SDK consumers. * feat(sdk): add optional timeout to env mutex for deadlock prevention Add timeout parameter to acquireEnvMutex() to prevent infinite waits in deadlock scenarios. The timeout is optional and defaults to no timeout (wait forever) for backward compatibility. Returns a MutexAcquireResult object with acquired status and optional timeout reason for failed acquisitions. * fix(sdk): remove timed-out callback from mutex queue to prevent deadlock * test(sdk): add missing error path and timeout scenario tests Add tests for timeout scenarios when host doesn't respond to permission requests, fallback behavior when no onPermissionRequest callback, and MCP connection edge cases for undefined/empty config. * fix(sdk): address code review issues - race conditions, validation, error handling - Add createPermissionTarget() factory that applies onceOnlyResolve at registration time, fixing race condition where timeout and host response could both try to resolve the same promise - Add try-catch to releaseEnvMutex() to prevent permanent lock if callback throws - Extract DEFAULT_PERMISSION_TIMEOUT_MS constant (30 seconds) - Add MCP config validation rejecting null, non-objects, and arrays - Preserve error stack traces in MCP connection failures - Add runtime validation to mapMessageToSDK for null/non-object/invalid type - Update tests to use createPermissionTarget and add validation tests * fix(sdk): syntax fixes and MCP connection error handling - Remove extra closing parenthesis in permissions.ts - Remove extra closing braces in shared.ts type definitions - Wrap MCP connection in try/catch to continue without MCP tools on failure * fix(sdk): syntax fixes, MCP error handling, and logic clarity - Remove extra closing parenthesis in permissions.ts - Remove extra closing braces in shared.ts type definitions - Wrap MCP connection in try/catch to continue without MCP tools on failure - Clarify thinkingConfig logic: use ?? true instead of !== false - Add explanatory comment about thinkingEnabled default behavior - Apply createOnceOnlyResolve wrapper in QueryImpl.registerPendingPermission * fix(sdk): comprehensive error handling and resource cleanup - Add try-catch around injectAgents() to gracefully handle plugin agent tool validation failures (prevents test crashes from unknown 'LS' tool) - Add console.warn logging to agent loading/injection catch blocks for debugging visibility (matches v2.ts pattern) - Add pendingPermissionPrompts.clear() to close() and interrupt() methods in both query.ts and v2.ts to prevent memory accumulation - Add close() method to SDKSession interface and SDKSessionImpl - Wrap MCP connection in query.ts with try-catch (matches v2.ts behavior) - Add timeoutQueue cleanup in finally blocks (query.ts + v2.ts) - Remove error.stack from MCP error messages to prevent internal path leak All 208 SDK tests pass. TypeScript errors are pre-existing. * fix(sdk): address code review non-blocking issues - Add SDKAgentLoadFailureMessage type for agent load failure events - Emit agent definition/injection failures to SDK message stream - Add tool name to permission timeout denial message - Replace 'as any' casts with proper typed state access - Fix supportedCommands to use correct mcp.commands/plugins.commands paths - Update test for correct AppState structure * fix(sdk): address code review blocking and non-blocking issues Blocking Issues Fixed: - MCP cleanup missing on session/query close - now disconnects MCP clients to prevent resource leaks in long-running processes with multiple sessions - Engine reference not cleared on close - now sets _engine = null to prevent memory leaks - Added MCP cleanup tests (9 new tests covering cleanup scenarios) Non-Blocking Issues Fixed: - Removed redundant catch block that just rethrew errors (query.ts) - Fixed inconsistent timeout denial message format (permissions.ts) - Fixed hardcoded tool name 'Bash' in test (permissions.test.ts) - Exported PermissionResolveDecision type for SDK consumers (index.ts) All 217 SDK tests pass. * fix(sdk): address code review type consistency issues - Add close() method to SDKSession interface (documented but missing from type) - Fix SDKSessionInfo, ForkSessionResult, SessionMessage field naming: snake_case → camelCase to match sdk.d.ts public contract and implementation - Add uuid and session_id to SDKPermissionTimeoutMessage for correlation - Fix JSDoc comment in forkSession to use sessionId (not session_id) These changes align internal types (shared.ts) with the public SDK contract (sdk.d.ts) and actual implementation output. The merge from origin/main introduced snake_case types that mismatched camelCase implementation and tests. * fix: restore openclaude.json comment in REPL.tsx Merge0f3aa7aincorrectly took main's side for this comment, reverting PR2 fixc725c48. Project has migrated to ~/.openclaude.json, not ~/.claude.json. This is the only PR2 fix lost during merge - all other PR2 fixes (permissions.ts race conditions, state.ts parentSessionId, etc.) are preserved in PR3 via subsequent fix commits. * fix(sdk): add missing type declarations to sdk.d.ts Add SDKAgentLoadFailureMessage and PermissionResolveDecision to sdk.d.ts to resolve type declaration drift detected by build validation. - SDKAgentLoadFailureMessage: Agent loading failure notification (stage: definitions/injection, error_message) - PermissionResolveDecision: SDK-specific permission resolution result (allow with updatedInput, deny with message + decisionReason) Build validation now passes: 56 exports match between index.ts and sdk.d.ts. * fix(sdk): resource leak and null safety in close/interrupt paths - unstable_v2_prompt: wrap session in try/finally to guarantee session.close() on both success and error paths, preventing MCP connection and engine resource leaks - QueryImpl.interrupt(): add null guard on _engine so calling interrupt() after close() is a safe no-op instead of throwing - SDKSessionImpl.interrupt(): add matching null guard for v2 sessions, consistent with the Query fix - QueryImpl.close(): call this.interrupt() before cleanup to properly stop in-flight engine operations, matching v2's close() pattern and ensuring engine.interrupt() runs before nulling * fix(sdk): abort AbortController in SDKSessionImpl.close() to prevent resource leak SDKSessionImpl.close() was not aborting the AbortController, unlike QueryImpl.close() which does. This meant in-flight HTTP requests and async operations could continue running after session closure. - Store AbortController reference via _abortController field + late-bind setter - Abort and null the controller in close(), mirroring QueryImpl pattern - Also null _appStateStore in close() to release state snapshots - Wire abortController through createEngineFromOptions return value * fix(sdk): index ALL entries in byUuid for compact preserved segment The byUuid map must index system compact_boundary entries, not just user/assistant. When anchorUuid === boundary.uuid, the relink walk needs to find the boundary in byUuid. Changes: - query.ts: Index ALL non-sidechain entries (user, assistant, system) - v2.ts: Same fix — index ALL entries, leaf selection user/assistant only - Add regression test: boundary.uuid as anchorUuid scenario Test verifies preserved messages kept, stale pre-compact dropped, post-boundary chain intact when anchorUuid points to boundary itself. * fix(sdk): complete preserved segment handling for compact resumes Multiple fixes for compact-aware transcript loading: 1. Index ALL entries in byUuid (including system compact_boundary) - Needed when anchorUuid === boundary.uuid 2. Keep anchorUuid when pruning preserved segment entries - The anchor is the parent of preserved head after relink - Deleting it breaks the conversation chain 3. Filter system entries from final messages - compact_boundary is metadata, shouldn't pass to engine 4. Fix test timestamp format (ISO 8601 requires 2-digit hours) - '2025-01-04T0:00:00Z' → '2025-01-04T00:00:00Z' 5. Update test expectations for anchor inclusion - When anchor is a stale entry, it appears in messages - preserved(4) + anchor(1) + post(4) = 9 max All 224 SDK tests pass. * fix(sdk): MCP type:sdk tools properly convert SdkMcpToolDefinition to Tool - Import MCPTool base from tools/MCPTool/MCPTool.js - Spread MCPTool properties for proper Tool interface compliance - Add tools field to SdkMcpSdkConfig type declaration - Add regression tests for type:sdk tools wiring Fix ensures in-process SDK tools match Tool interface expected by QueryEngine and permission handlers. * test(sdk): strengthen preserved segment and MCP tools tests Preserved segment test improvements: - Fix content extraction (access message.content, not message) - Add exact count assert: messages.length === 6 - Add exact content asserts: preserved turn 1/2, post-boundary present - Assert no stale, no system entries in final messages MCP tools test additions: - Direct test of connectSdkMcpServers() function - Assert clients.length === 0 (in-process, no MCP connections) - Assert tools.length === 1 with proper name/description - Verify handler works via direct call (not via Tool.call which needs context) * fix(sdk): published types complete, init errors fatal, permission session IDs Three fixes for SDK production readiness: 1. HIGH: Published SDK types incomplete - Add coreTypes.generated.d.ts to package.json "files" array - sdk.d.ts re-exports from ./sdk/coreTypes.generated.js which was missing - TypeScript consumers would get module resolution errors 2. MEDIUM: query() swallows real init() failures - Add _engineWasInjected field to track pre-injected vs fresh engine - Check _engineWasInjected, not _engine !== null (always true after setEngine) - Auth/config/init errors now properly fatal for normal query() calls 3. MEDIUM: SDK permission events lose real session id - Pass sessionId to createExternalCanUseTool() in both query.ts and v2.ts - Permission_request/timeout messages now have correct session_id - Hosts can correlate permission callbacks to sessions Test result: 225 pass, 0 fail * fix(sdk): complete package types + dynamic permission session_id Two fixes for SDK production readiness: 1. Published SDK types now include actual definitions - Replace 215-byte wrapper with 63KB coreTypes.generated.ts - TypeScript consumers get full type definitions (SDKMessage, etc.) - npm pack now includes real generated types 2. Permission event session_id dynamic for all query() paths - createExternalCanUseTool accepts string | (() => string | undefined) - query.ts passes () => queryImpl.sessionId getter - Fresh/fork/continue queries emit correct session_id at event time - V2 passes static sessionId (stable at creation/resume) - Add 4 tests: static sessionId, getter resolution, undefined fallback, timeout Test result: 229 pass, 0 fail * fix(sdk): fix sdk.d.ts for real TypeScript consumer compilation Two issues prevented external consumers from compiling against packed SDK types: 1. SDKRateLimitError used constructor parameter properties (readonly resetsAt, readonly rateLimitType) which are invalid in .d.ts declarations — moved to class properties with separate constructor signature. 2. Re-exported SDKMessage/SDKUserMessage/SDKResultMessage were not imported into local scope — added import type alongside export type so TypeScript can resolve them for use in other declarations within the same file. Added package-consumer-types.test.ts that compiles a real temp project against the SDK types with skipLibCheck:false, catching both regressions. * fix(sdk): eliminate React/Ink imports from SDK bundle SDK bundle leaked React/Ink imports via tool UI modules, keybindings, react-compiler-runtime, and spawnMultiAgent's static React import. Changes: - Stub root ink.js barrel, tool UI.js, keybindings/, react-compiler-runtime, It2SetupPrompt, and React hook files in SDK build - Add local no-op stub for react/jsx-dev-runtime (jsxDEV returns null) - Convert spawnMultiAgent's static React/It2SetupPrompt imports to dynamic await import() — spawnTeammate logic stays fully intact - Add post-build leakage validation (fails on from "react"/"ink"/jsx-dev-runtime) - Remove react/jsx-dev-runtime from SDK externals (now handled by build plugin) * fix(sdk): wire disallowedTools through permission context QueryOptions.disallowedTools was declared but never used. buildPermissionContext() now passes it to alwaysDenyRules.cliArg so getTools() filters denied tools from the model-visible list. Also added to V2 SDKSessionOptions for API consistency. * fix(sdk): defer permission warning to execution time createDefaultCanUseTool() warned at construction time even when the caller provided canUseTool/onPermissionRequest. Move warning to first actual default denial so valid SDK consumers never see false warnings. Add tests for disallowedTools filtering, tool exclusion, and warning timing. * refactor(sdk): extract transcript helpers + fix permission typing - Extract shared transcript utilities to transcript.ts (parseJsonlEntries, findLastCompactBoundary, applyPreservedSegmentRelinks, buildConversationChain, stripExtraFields) deduplicating query.ts and v2.ts - Add PermissionTarget interface to hide internal pendingPermissionPrompts map from createExternalCanUseTool, with deletePendingPermission and denyPendingPermission methods on QueryImpl and SDKSessionImpl - Fix sessionId stability: preserve constructor UUID for fresh queries when continue:true finds no existing sessions, and when explicit sessionId does not resolve to a valid transcript file - Add getMcpClients/setMcpClients to QueryEngine for SDK cleanup access * fix(sdk): resolve remaining TypeScript errors in SDK modules - Fix PermissionDecision type compatibility: import from types/permissions and cast PermissionResolveDecision to PermissionDecision properly - Fix AsyncIterator/AsyncGenerator: async generators must return AsyncGenerator (which implements AsyncIterable), not AsyncIterator - Fix Map method callable errors: cast additionalWorkingDirectories to Map<string, unknown> before calling .set() and .keys() - Fix ApiKeySource type: map internal ApiKeySource to SDK's narrower type using conversion function, spread info before apiKeySource to avoid override - Fix MCP config scope type: cast 'session' scope to ScopedMcpServerConfig for connectToServer compatibility - Add PermissionMode import and cast for decisionReason.mode - Deny pending permissions in interrupt(): resolve all pending promises with deny before clearing the map (both query.ts and v2.ts) * fix(sdk): correct init skip logic and test mocks - query.ts: skip init() entirely for injected engines (mocks, SDK host overrides) instead of calling init() and swallowing errors. Pass { injected: false } from query() factory to distinguish real engine from test mocks. - mock-engine.ts: add getMcpClients() and setMcpClients() methods to match QueryEngine API added in this PR. - permissions.test.ts: use filterToolsByDenyRules instead of getTools for disallowedTools tests, with proper base tool fixtures. * fix: address code review feedback for exports and build script package.json exports (Breaking Change Mitigation): - Add "./package.json": "./package.json" for tool compatibility - Add "./dist/cli.mjs": "./dist/cli.mjs" for CLI bundle access - Keep ./sdk as sole library entrypoint - Root import intentionally blocked (CLI-first package, no main field) build.ts (Bug Fix): - Add | undefined to result/sdkResult type declarations - Add optional chaining: result?.success, sdkResult?.success - Prevents TypeError masking actual build errors when Bun.build throws tests/sdk/package-consumer-types.test.ts: - Update simulated exports to match real package.json - Add tests verifying exports map structure and file existence --------- Co-authored-by: Ali Alakbarli <ali.alakbarli@users.noreply.github.com>
103 lines
3.6 KiB
TypeScript
103 lines
3.6 KiB
TypeScript
/**
|
|
* Validates that all package.json dependencies are accounted for
|
|
* in the external lists or explicitly marked as intentionally bundled.
|
|
*
|
|
* Run as part of the build to catch missing externals early.
|
|
*/
|
|
import { readFileSync } from 'fs'
|
|
import { CLI_EXTERNALS, SDK_EXTERNALS, INTENTIONALLY_BUNDLED } from './externals.js'
|
|
|
|
const pkg = JSON.parse(readFileSync('package.json', 'utf8'))
|
|
const allDeps = new Set([
|
|
...Object.keys(pkg.dependencies || {}),
|
|
...Object.keys(pkg.peerDependencies || {}),
|
|
])
|
|
|
|
function validate(bundleName: string, externals: string[]): boolean {
|
|
const externalSet = new Set(externals)
|
|
const intentionallyBundledSet = new Set(INTENTIONALLY_BUNDLED)
|
|
|
|
const missing = [...allDeps].filter(
|
|
d => !externalSet.has(d) && !intentionallyBundledSet.has(d),
|
|
)
|
|
|
|
if (missing.length > 0) {
|
|
console.error(`❌ ${bundleName}: Dependencies missing from externals:`)
|
|
for (const dep of missing) {
|
|
console.error(` - ${dep}`)
|
|
}
|
|
console.error(
|
|
`\n Either add them to scripts/externals.ts or to INTENTIONALLY_BUNDLED.`,
|
|
)
|
|
return false
|
|
}
|
|
|
|
const extra = [...externalSet].filter(d => !allDeps.has(d))
|
|
if (extra.length > 0) {
|
|
console.warn(`⚠️ ${bundleName}: External entries not in package.json (may be ok):`)
|
|
for (const dep of extra) {
|
|
console.warn(` - ${dep}`)
|
|
}
|
|
}
|
|
|
|
console.log(`✓ ${bundleName}: All dependencies accounted for (${missing.length} missing, ${externalSet.size} external)`)
|
|
return true
|
|
}
|
|
|
|
const cliOk = validate('CLI bundle', CLI_EXTERNALS)
|
|
const sdkOk = validate('SDK bundle', SDK_EXTERNALS)
|
|
|
|
if (!cliOk || !sdkOk) {
|
|
console.error(`\n❌ External list validation failed. Fix scripts/externals.ts before committing.`)
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log('\n✓ All external lists valid.')
|
|
|
|
// ============================================================================
|
|
// Validate sdk.d.ts ↔ index.ts export drift
|
|
// ============================================================================
|
|
|
|
const SDK_DTS_PATH = 'src/entrypoints/sdk.d.ts'
|
|
const SDK_INDEX_PATH = 'src/entrypoints/sdk/index.ts'
|
|
|
|
function extractExportNames(filePath: string): Set<string> {
|
|
const content = readFileSync(filePath, 'utf8')
|
|
const names = new Set<string>()
|
|
// Match: export { name1, name2 } / export type { name1 } / export class/function/interface/const/type Name
|
|
for (const match of content.matchAll(/export\s+(?:type\s+)?\{([^}]+)\}/g)) {
|
|
for (const name of match[1].split(',')) {
|
|
const trimmed = name.trim().split(/\s+as\s+/)[0].trim()
|
|
if (trimmed) names.add(trimmed)
|
|
}
|
|
}
|
|
for (const match of content.matchAll(
|
|
/export\s+(?:type\s+)?(?:class|function|interface|const|type)\s+(\w+)/g,
|
|
)) {
|
|
names.add(match[1])
|
|
}
|
|
return names
|
|
}
|
|
|
|
const dtsExports = extractExportNames(SDK_DTS_PATH)
|
|
const indexExports = extractExportNames(SDK_INDEX_PATH)
|
|
|
|
const inDtsNotIndex = [...dtsExports].filter(n => !indexExports.has(n))
|
|
const inIndexNotDts = [...indexExports].filter(n => !dtsExports.has(n))
|
|
|
|
if (inDtsNotIndex.length > 0 || inIndexNotDts.length > 0) {
|
|
console.error(`\n❌ SDK type declaration drift detected:`)
|
|
if (inDtsNotIndex.length > 0) {
|
|
console.error(` In sdk.d.ts but not in index.ts:`)
|
|
for (const name of inDtsNotIndex) console.error(` - ${name}`)
|
|
}
|
|
if (inIndexNotDts.length > 0) {
|
|
console.error(` In index.ts but not in sdk.d.ts:`)
|
|
for (const name of inIndexNotDts) console.error(` - ${name}`)
|
|
}
|
|
console.error(`\n Keep sdk.d.ts in sync with src/entrypoints/sdk/index.ts.`)
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log(`✓ SDK type declarations in sync (${dtsExports.size} exports match).`)
|