mirror of
https://github.com/Gitlawb/openclaude.git
synced 2026-11-06 20:58:14 -04:00
v0.31.0
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6d09260291 |
chore(types): reduce any usage with safety tooling + 7-batch phase 1 (#2208)
* chore(types): add any-usage baseline + lint budget gate (Phase 0)
Phase 0 of a multi-phase plan to reduce explicit `any` usage safely.
No source files modified in this PR.
- Add eslint@9 + @typescript-eslint deps (lint only, no fix rules).
- New minimal flat eslint.config.js enabling only
`@typescript-eslint/no-explicit-any` as warn. Stub plugins registered
for legacy `custom-rules`, `eslint-plugin-n`, and `react-hooks`
references in existing `// eslint-disable` comments so the comments
stay valid (no rule name resolution errors).
- New `scripts/any-usage-report.ts`: walks src/, tests/, scripts/ and
buckets `any` usage into `: any` annotations, `as any` casts,
`[key: ...]: any` index signatures, `// @ts-ignore` directives, and
generic `any` args. Emits `reports/any-usage.{json,md}`. `--check`
mode exits non-zero if the count grows vs the baseline.
- Lock the baseline at 1,031 occurrences across 198 files.
- Add `lint`, `lint:any-budget`, `check:strict` scripts.
- Plan: docs/plans/imperative-hugging-sphinx.md
Verification:
- `bun run typecheck` clean
- `bun run lint` exit 0 (845 warnings, 0 errors)
- `bun run lint:any-budget` ok (current=1031 baseline=1031)
* chore(types): narrow any in 6 small src files (Phase 1, batch 1)
Phase 1 of the plan to reduce `any` usage. Targets only files with
1–2 lint hits where the concrete type is reachable in scope.
- src/services/api/withRetry.ts: isQuotaExhausted signature `any` → `unknown`,
add local structural narrowing for `.message`/`.status` access.
- src/utils/validation.ts: assertFunction predicate signature `any[]`/`any` →
`unknown[]`/`unknown`.
- src/utils/conversationArc.ts: extractTextFromContent — replace `(block: any)`
in filter/map with explicit type predicate narrowing.
- src/utils/optionalRuntimeModule.ts: dynamic-import wrapper returns
`Promise<unknown>` instead of `Promise<any>`.
- src/entrypoints/mcp.ts: tighten block mapping with the existing structural
type already in scope; replace `(finalResult as any).isError` with a
narrow cast.
- src/components/EffortPicker.tsx: drop redundant `: any` on useAppState
selector — the function's generic infers `AppState` for free.
Verification:
- bun run typecheck clean
- bun run lint: 835 warnings (was 845)
- bun run lint:any-budget: 1022 / 1031 baseline (delta -9)
- bun test src/utils/optionalRuntimeModule.test.ts + conversationArc.test.ts:
36 pass / 0 fail
- bun test src/services/api/withRetry.test.ts: 44 pass / 0 fail
* chore(types): narrow any in WebSearchTool + adapters (Phase 1, batch 2)
Phase 1 continues. These are all boundary code (HTTP responses from
external search APIs), so every `any` is at a module boundary per the
plan. Each adapter now types its response with `unknown` + structural
narrowing instead of `any`.
- providers/types.ts: firstMatch/normalizeHit accept `unknown` and narrow
to `Record<string, unknown>` before index access.
- providers/timeout.ts: fetchJsonWithWebSearchTimeout returns
Promise<unknown>.
- providers/{bing,brave,jina,linkup,mojeek,tavily,you,exa,custom}.ts: each
adapter's response data is cast to a minimal shape (`{ results?: unknown }`
etc.), then array elements are narrowed to `Record<string, unknown>` with
`typeof` checks per field.
- WebSearchTool.ts: getCodexSources and extractCodexWebSearchFailure now
take `Record<string, unknown>`; nested `action`/`error`/`result` are
cast locally to `Record<string, unknown>` so `?.field` chains type-check.
Verification:
- bun run typecheck clean
- bun run lint: 805 warnings (was 835, -30)
- bun run lint:any-budget: 992 / 1022 baseline
- bun test src/tools/WebSearchTool: 138 pass / 0 fail across 9 files
* chore(types): narrow any in 6 mid-size src files (Phase 1, batch 3)
Phase 1 continues. Each replacement uses a precise structural cast
(`as { uuid?: string }` etc.) or `unknown` with typeof/Array.isArray
narrows at use sites.
- src/utils/settings/types.ts: zod preprocess callback `any` → `unknown`,
copy via `Record<string, unknown>`.
- src/entrypoints/sdk/v2.ts: initialMessages param/var `any[]` → `Message[]`;
local cast at the boundary with stripChainFields.
- src/entrypoints/sdk/query.ts: msg `.uuid` access uses
`as { uuid?: string }`; `fileHistoryCanRestore` etc. accept `UUID` (from
`crypto`); `mcpServerStatus` accesses `client.error` / `client.config`
directly since the union members already have them.
- src/commands/cache-probe/cache-probe.ts: getField reduce `any` → `unknown`
with typeof guard; catch (err: any) → `err: unknown` with
`err instanceof Error`; mainMakeUsage/mainConvertChunkUsage take `unknown`
and read fields via typeof checks.
- src/services/compact/snipProjection.ts: snipMetadata / uuid accessed via
narrow structural casts, no longer `as any`.
- src/utils/messages.ts: tool_use block destructuring for extra_content uses
`as { extra_content?: unknown; [k: string]: unknown }` instead of
`as any`.
Public SDK surface (entrypoints/sdk.d.ts, entrypoints/sdk/index.ts
SdkMcpToolDefinition<Schema = any>) is intentionally permissive for
consumer ergonomics and is left untouched per the plan's do-not-touch
list.
Verification:
- bun run typecheck clean
- bun run lint: 782 warnings (was 805, -23)
- bun run lint:any-budget: 969 / 992 baseline
- bun test (sdk v2/factories + compact + messages): 91 pass / 0 fail
* fix(types): correct BetaContentBlock cast in messages.ts (Phase 1 fix)
Verifier caught that the `as { [k: string]: unknown; extra_content?: unknown }`
cast on a `BetaToolUseBlock` did not sufficiently overlap (BetaToolUseBlock
lacks an index signature), so the destructured spread of `restBlock`
yielded a malformed union member that failed to assign to BetaContentBlock[].
Fix: cast through `unknown` first, then assert the final return value as
`BetaContentBlock` (the declared element type of the outer `.map`). This
narrows from the prior `as any` while keeping the previous structural
intent (preserve all block fields, optionally inject `extra_content`).
Verification:
- bun run typecheck clean
- bun test src/utils/messages/: 79 pass / 0 fail
* chore(types): narrow any in 5 dense src + 1 script (Phase 1, batch 4)
Each replacement preserves runtime behavior while using concrete types
where reachable and `unknown` with structural narrowing where not.
- src/components/ClaudeMdExternalIncludesDialog.tsx: 4 helper updaters
(acceptProject/User, declineProject/User) take `ProjectConfig` from
`src/utils/config.ts` instead of `any` (matches the
`saveCurrentProjectConfig` updater signature).
- src/components/ProviderManager.tsx: env-clearing objects in
`activateGithubProvider` and the GitHub disable path used
`undefined as any`. The intent is "delete this key on merge" but the
zod schema coerces to string. Replaced with
`undefined as unknown as string` (honest about the lie) and added a
comment explaining intent.
- src/services/compact/snipCompact.ts: introduced a structural
`SnipMessage` type for the public function params. Tests construct
fake messages with `uuid: 'u4'` (not branded UUID), so a strict
`Message[]` would reject them; the structural type accepts both
production messages and test fixtures. `snipCompactIfNeeded` is
generic `<T extends SnipMessage>` so callers get the same shape back.
Internal content blocks use `unknown` + type-predicate narrowing.
- src/query.ts: yield site for the boundary message casts to `Message`
since the value is constructed as a real `Message` in production.
- scripts/grpc-cli.ts: proto descriptor, stream, and message callback
typed as `unknown` / `ClientDuplexStream<unknown, unknown>`. The
dynamic `.proto` payload is cast once at the boundary into a
structural shape so per-field access (`text_chunk.text` etc.) is
tracked explicitly.
- scripts/generate-sdk-types.ts: the JSON-Schema-walking converter
takes `unknown` everywhere and narrows via typeof / Array.isArray at
each access. `Map<unknown, string>` for placeholder identity
comparison, `Set<string | undefined>` no longer used here.
Verification:
- bun run typecheck clean
- bun run lint: 728 warnings (was 782, -54)
- bun run lint:any-budget: 916 / 969 baseline
- bun test src/services/compact: 91 pass / 0 fail
* chore(types): remove 3 eslint-disable no-explicit-any suppressions
Tighten three small call sites where the `any` was used as a lazy
escape hatch and a real type was reachable in scope.
- src/main.tsx: `(global as any).require('inspector')` becomes
`(global as unknown as { require: (s: string) => { url: () => string } })`.
The dynamic require is being phased out, but while it remains the
narrow structural cast documents the shape we actually consume.
- src/ink/reconciler.ts: catch (error: any) → catch (error: unknown);
`error.code === 'ERR_MODULE_NOT_FOUND'` accesses the code via
`(error as { code?: unknown }).code`.
- src/components/wizard/WizardProvider.tsx:
`createContext<WizardContextValue<any> | null>(null)` →
`createContext<WizardContextValue | null>(null)`. The
`WizardContextValue` generic already defaults to
`Record<string, unknown>`, so the `<any>` was redundant; consumers
that need a tighter type can supply it via
`useContext<WizardContextValue<T>>`.
Note: most remaining lint warnings come from file-level
`/* eslint-disable @typescript-eslint/no-explicit-any */` blocks at the
top of do-not-touch files (src/types/{message,utils,tools}.ts,
src/constants/querySource.ts, src/entrypoints/sdk/controlTypes.ts) and
from public SDK surface (src/entrypoints/sdk{,.d.ts,v2.ts}). Both are
intentional per the plan.
Verification:
- bun run typecheck clean
* chore(types): refresh any-usage baseline
* fix(types): address PR review feedback on any-reduction tooling
- Pin eslint to resolved version (9.39.5) so lint warning totals stay
stable across lockfile updates.
- Wire lint:any-budget into the regular `check` gate (was only in
check:strict), so any-usage regressions block PRs.
- any-usage-report: use lstat and skip symlinks so a self-referencing
link under src/tests/scripts can't stall the budget scan.
- any-usage-report --check: exit nonzero when the baseline file is
missing or invalid so the gate can't be silently disabled.
- Remove dangling references to docs/plans/imperative-hugging-sphinx.md
from eslint.config.js and scripts/any-usage-report.ts headers.
- mcp.ts: defensively skip non-object tool-output entries (primitives
/ null) instead of crashing on boundary data.
- messages.ts: realign the mis-indented tool_search-off branch so the
blockExtra + return block reads at the right level.
No budget delta (913 -> 913). Typecheck, lint, budget, and the affected
unit tests all green.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(types): narrow any in src/grpc/server.ts (5 warnings)
- protoDescriptor cast: replace `as any` with a structural type that only
declares the AgentService shape we actually use.
- handleChat: ServerDuplexStream<any, any> -> <unknown, unknown>; the
clientMessage is narrowed locally where it's read.
- previousMessages: any[] -> Message[] (already imported, already used
as the type of this.sessions).
- catch (err: any) -> catch (err); err.message is now gated behind an
instanceof Error check.
Typecheck, lint, budget, and the 2 grpc unit tests all green.
Lint 728 -> 723 (-5). Budget 913 -> 909 (-4).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(types): narrow any in src/utils/knowledgeGraph.ts (18 warnings)
Add explicit LegacyEntity / LegacyRelation / LegacySummary / LegacyData
types so the migration path stops round-tripping through `any`.
- LegacySource.data, mergeLegacySources, normalizeLegacyData,
readLegacySqliteStore, SqliteReadResult.data, and doMigration all take
the new LegacyData shape.
- LegacyData.entities is typed `Record<string, LegacyEntity> | LegacyEntity[]`
because older JSON dumps stored entities as an array. normalizeLegacyData
converts the array form to the record form so downstream code can keep
using Object.entries. (Old loop relied on the same `id ?? entryKey`
fallback, so behavior is preserved; verified by all 20 unit tests.)
- SQLite row accessor now returns `Record<string, unknown>` (was `any[]`);
per-row readers narrow `row.id`, `row.attributes`, `row.keywords`,
`row.content` from `unknown` and the existing typeof guards preserve
the previous fail-soft behavior on malformed rows.
Lint 723 -> 705 (-18). Budget 909 -> 894 (-15). All 20 knowledgeGraph
unit tests pass; typecheck clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(types): narrow SDK tool() generic and handler signature (12 warnings)
The public SDK MCP tool() helper previously defaulted its Schema generic
to `any` and typed handler args as `any`. Switch both to `unknown`:
- SdkMcpToolDefinition<Schema = unknown>
- tool<Schema = unknown> with handler (args: Schema, extra: unknown)
- annotations?: any -> ToolAnnotations (imported from MCP SDK types)
This is a strictly tighter API: callers who pass a concrete schema get
the same behavior; callers who relied on implicit any now have to
validate handler args (which they should have been doing anyway).
All 4 SDK MCP tool wiring tests pass; typecheck clean.
Lint 705 -> 695 (-10). Budget 894 -> 884 (-10).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(types): address PR feedback on SDK + grpc type hardening
Apply 4 of 5 CodeRabbit review items (skipped item 5: SDK resume flow
JSONL validation, deferred as larger refactor).
- sdk.d.ts: switch MCP type imports to the package's public
`@modelcontextprotocol/sdk/types.js` subpath (was the internal
dist/esm/types.js path).
- sdk.d.ts: handler return Promise<any> -> Promise<CallToolResult> in
both SdkMcpToolDefinition and the tool() helper, matching the
runtime contract and the sibling declarations in sdk/index.ts and
sdk/v2.ts.
- grpc/server.ts: introduce a local ClientMessage interface (matching
the openclaude.proto oneof payload) and type the duplex-stream
request as ServerDuplexStream<ClientMessage, unknown>. The handler
body was already reading these fields by name; the type now
documents them.
- grpc/server.ts: ServiceDefinition generic was being passed a
ServerDuplexStream, which isn't a valid T (T must be a record
keyed by method name). Switch to ServiceDefinition<{ Chat: never }>
so the call to addService accepts a real implementation-map key.
Typecheck, lint, budget, and the 6 affected unit tests all green.
Lint 695 -> 693 (-2). Budget 884 -> 882 (-2).
* fix(types): address 6 reviewer findings on PR #2208
Six findings (four P2, two P3) from the second review pass.
1. knowledgeGraph legacy array normalization: previously dropped entries
whose `id` was neither string nor number, breaking recovery for
idless array entries. Use the array index as the fallback key so
mergeLegacySources' existing `id ?? entryKey` logic continues to
recover the entry. All 20 unit tests still pass.
2. SDK tool() generic conflated schema descriptor with invocation
value type, breaking the test fixture (and any external consumer)
that declares the handler with a concrete value type. Restore
`args: any` for the handler across the factory, SdkMcpToolDefinition,
and the .d.ts declaration; keep `Schema` as the generic for
inputSchema typing.
3. sdk.d.ts now declares ToolAnnotations and CallToolResult locally
instead of importing from @modelcontextprotocol/sdk, preserving
the optional peer contract. A consumer without MCP installed no
longer gets TS2307 on a no-op SDKSession type import.
4. any-usage-report now strips comments, string literals, and
template-literal contents before applying the annotation regex,
so `// example: any`, `': any'`, and similar non-type occurrences
no longer count. @ts-ignore / @ts-expect-error / @ts-nocheck
directives are preserved for the ignore category. Budget went
from 882 to 776.
5. generate-sdk-types: schemas module cast as Record<string, () => unknown>
was wrong because HOOK_EVENTS is a non-callable; cast as
Record<string, unknown> and narrow per-access. def.getter() and
Reflect.get(schema, 'description') both narrowed to a callable /
object shape before use. Generated output unchanged (diff empty).
6. any-usage-report walk(): readdir failures now propagate instead
of being silently swallowed, so a partial scan cannot publish a
too-low replacement baseline or pass --check.
Typecheck, lint, budget, and 26 affected unit tests all green.
* chore(types): refresh any-usage baseline after upstream rebase
Baseline 776 -> 779. The +3 comes from upstream #2196 (Command Code
gateway) test mocks merged to main after the baseline was recorded:
2x (payload: any) in ProviderManager.test.tsx, 1x 'as any' in
providerProfiles.test.ts. Symptom-level narrowing of another merged
PR's test fixtures is out of scope; re-baseline to the rebased tree.
---------
Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|
||
|
|
26eef92fe7 |
feat: add headless gRPC server for external agent integration (#278)
* gRPC Server
* gRPC fix
* UpdProto
* fix: address PR review feedback for gRPC server
- Update bun.lock for new dependencies (frozen-lockfile CI fix)
- Add multi-turn session persistence via initialMessages
- Replace hardcoded done payload with real token counts
- Default bind to localhost instead of 0.0.0.0
* fix(grpc): startup parity, cancel interrupt, and cli text fallback
- Replace enableConfigs() with await init() in start-grpc.ts for full
bootstrap parity with the main CLI (env vars, CA certs, mTLS, proxy,
OAuth, Windows shell)
- Call engine.interrupt() before call.end() in the cancel handler so
in-flight model/tool execution is actually stopped
- Show done.full_text in the CLI client when no text_chunk was received,
preventing silent drops when streaming is unavailable
* fix(grpc): wire session_id end-to-end and remove dead provider field
- Move session_id from ClientMessage into ChatRequest to fix proto-loader
oneofs encoding bug and make the field functional
- Implement in-memory session store so reconnecting with the same
session_id resumes conversation context across streams
- Remove ChatRequest.provider — per-request provider routing requires
global process.env mutation, unsafe for concurrent clients; provider
is configured via env vars at server startup
* fix(grpc): mirror CLI auth bootstrap in start-grpc and fix tool_name field
scripts/start-grpc.ts now runs the same provider/auth bootstrap as the
normal CLI entrypoint: enableConfigs, safe env vars, Gemini/GitHub token
hydration, saved-profile resolution with warn-and-fallback, and provider
validation before the server binds.
ToolCallResult.tool_name was being populated with the tool_use_id UUID.
Added a toolNameById map (filled in canUseTool) so tool_name now carries
the actual tool name (e.g. "Bash"). The UUID moves to a new tool_use_id
field (proto field 4) for client-side correlation.
* fix(grpc): add tool_use_id to ToolCallStart and interrupt engine on stream close
Two blocker-level issues flagged in code review:
- ToolCallStart was missing tool_use_id, making it impossible for clients
to correlate tool_start events with tool_result when the same tool runs
multiple times. Added tool_use_id = 3 to the proto message and populated
it from the toolUseID parameter in canUseTool.
- On stream close without an explicit CancelSignal the server only nulled
the engine reference, leaving the underlying model/tool work running
as an orphan. Added engine.interrupt() in the call.on('end') handler
to stop work immediately when the client disconnects.
* fix(grpc): resolve pending promises on disconnect and guard post-cancel writes
Four lifecycle and contract issues identified during proactive review:
- Pending permission Promises in canUseTool would hang forever if the
client disconnected mid-stream. On call 'end', all pending resolvers
are now called with 'no' so the engine can unblock and terminate.
- The done message and session save could fire after call.end() when
a CancelSignal arrived mid-generation. Added an `interrupted` flag
set on both cancel and stream close to gate all post-loop writes.
- The session map had no eviction policy, allowing unbounded memory
growth. Capped at MAX_SESSIONS=1000 with FIFO eviction of the
oldest entry.
- Field 3 was silently absent from ChatRequest. Added `reserved 3`
to document the gap and prevent accidental reuse in future.
* fix(grpc): reset previousMessages on each new request to prevent session history leak
previousMessages was declared at stream scope and only overwritten when
the incoming session_id already existed in the session store. A second
request on the same stream with a new session_id would silently inherit
the first request's conversation history in initialMessages instead of
starting fresh, violating the session contract.
Fix: reset previousMessages to [] at the start of each ChatRequest
before the session-store lookup.
* fix(grpc): reset interrupted flag between requests and guard against concurrent ChatRequest
Two stream-scoped state bugs found during proactive audit:
- The `interrupted` flag was never reset between requests on the same
stream. If the first request was cancelled, all subsequent requests
would silently skip the done message, causing the client to hang.
- A second ChatRequest arriving while the first was still processing
would overwrite the engine reference, corrupting the lifecycle of
both requests. Now returns ALREADY_EXISTS error instead. Engine is
nulled after the for-await loop completes so subsequent requests
can proceed normally.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
|