Consolidate the repeated console.error + emitError + .catch pattern
into a single private logAndEmitError method on CopilotKitCore. All 4
call sites (setDefaultThrottleMs, subscribeToAgentWithOptions validation,
safeCall reportError, unsupported-keys warning) now go through the helper.
- pushBundle now increments a pushToken before awaiting bundleHookSite and
re-checks it after the await; a second show() or handleFileChange() that
races past (or a dispose during the await) silently drops the stale
result instead of posting to a disposed webview. Deterministic
last-writer-wins even when 'load' and 'reload' interleave.
- onMessage replaces the Record<string, unknown> cast with a discriminated
union + isWebviewMsg guard. Eliminates four unchecked casts.
- Drop dead currentNonce field; the nonce is only needed when generating
HTML and lives locally in show().
- Add CSP inline comment explaining the 'style-src unsafe-inline' tradeoff
(React runtime style injection + CopilotKit transitive CSS-in-JS).
- bundle-loader: inline scripts swallow exceptions thrown during execution;
they fire as window 'error' events instead. Capture that event during the
insert/remove window and re-throw so executeBundle's caller's try/catch
actually sees bundle-time failures. Also assert __copilotkit_hookSite is
set after execution — a malformed bundle now throws 'did not set hookSite'
instead of stalling at 'Mounting host…'.
- Extract ControlsDispatch: a dedicated component containing the single
kind-switch over render-props. App.tsx's 6-branch switch with 13 'as never'
casts collapses to one dispatcher call with two typed casts per branch
(localized and narrow).
- mountError now reaches the extension host via postMessage instead of only
setting local state. Panel host (Task 20) can log these.
Bundling @copilotkit/react-core for the hook-preview webview transitively
pulls in:
- node-fetch + its builtin deps (crypto, stream, string_decoder, zlib, http, ...)
- tailwind v4 CSS via the chat component styles
- streamdown / hast-util-to-html / decode-named-character-reference which
statically name-import from JSON modules rolldown can't re-export
Rolldown 1.0.0-rc.16 removed experimental CSS bundling and refuses to load
Node builtins in a browser target. None of these paths are exercised on the
hook-preview runtime (we render user JSX directly; there's no chat UI,
markdown, or server fetch in scope), so we stub them to empty modules at
bundle time via a rolldown plugin.
The stub emits the specific named exports dependents statically import,
keeping rolldown's named-export analysis happy. Extend HOOK_PREVIEW_STUBBED_DEPS
if additional markdown-chain deps surface.
Previously RenderToolControls passed the full {...values, toolCallId} down to
ActionControls and spread the onChange result back over values. If ActionControls
ever added a toolCallId field, the spreads would collide. Explicitly separate
the action-shape values from toolCallId and recombine only at the nested
onChange boundary.
- CapturedRegistry: drop the misleading v1.actions field. Both V1
useCopilotAction and V2 useRenderTool register into the same internal V2
registry; expose it directly as renderToolCalls. Keep coAgentStateRenders
and chatComponents (those are genuine V1 state) at the top level too.
Downstream consumers (Task 19+) will read renderToolCalls for any
action-shaped hook.
- Harness: widen HarnessBoundary to also cover RegistryReader so a reader
crash routes through onMountError instead of bubbling to React's default
handler. A reader crash should block capture publishing, not leak.
- capture.test.tsx: replace the 20ms fixed setTimeout wait with testing-
library waitFor. Polls for the final capture result until the action is
present; robust on slow CI.
- installFetchInterceptor now returns an uninstall function, so callers
(tests, harness teardown) have an explicit restore path rather than
racing against module-scoped mutation.
- Guard against stacked wrappers on double-install (relevant under React
StrictMode and HMR remounts): uses a Symbol tag so the second call is
a no-op and its disposer doesn't unwind the first.
- 4 tests: noop prefix match, passthrough, disposer restores original,
double-install is a no-op.
- ArrayField.add(): drop dead '?? null' fallback — defaultForField is total
for required fields, and we force required:true on the seed clone. Inline
comment explains the two separate uses of field.items (seed vs. render).
- primitive-fields.test.tsx: cover BooleanField checked→unchecked transition.
- load() now rejects non-object / array / null values from the Memento
(protects against hand-edited workspaceState containing junk) and returns
undefined in those cases.
- JSDoc on the class documents the caller's path-normalization contract.
- Adds 4 tests: reset on a non-existent key (no-op), workspace-root scoping
so two roots don't collide, save overwrites (does not merge), and the new
load-rejects-scalar-junk defensive branch.
- human-in-the-loop: inline the body (8 lines) instead of the double-cast
alias. Self-contained and avoids the 'as unknown as Adapter<K>' gymnastics.
- invokeRender: add JSDoc explaining why the ADAPTERS[kind] as Adapter<K>
cast is runtime-sound despite being unsound at the type level.
- Tests: add coverage for render-tool (args → parameters rename, toolCallId),
human-in-the-loop (matches action shape, status-gated result), custom-messages,
activity-message, and the no-render no-op path (9 tests total).
- v1-params: omit description field when undefined (cleaner output object);
recursion on [] suffix now naturally handles nested arrays like string[][];
add test for both.
- json-schema: JSDoc on JSONSchemaNode documenting MVP scope (no $ref, tuple
items, allOf/anyOf/oneOf); add tests for integer type and array without items.
- normalize: treat empty-enum as unconstrained instead of rejecting every
string (prevents a required field with enum:[] getting stuck); add comments
on the shallow array/object match; add tests for array/object/raw-json
defaults and the empty-enum path.
When a field emits undefined (e.g. NumberField with an optional field cleared),
previously the merged payload would contain { count: undefined } which
downstream render callbacks would see as a present-but-null property.
Now the key is removed entirely, matching the runtime contract for
'optional field not provided'.
Also adds three tests: nested object change, array add, and the
undefined-drop behavior.
- Empty textarea no longer clobbers prior value with {}. Fields representing
arrays, strings, or null would previously be silently overwritten; now empty
input means 'no change' and clears any prior parse error.
- Adds aria-invalid={!!error} on the textarea.
- Adds two tests: empty-input path (no onChange, no alert) and error-recovery
path (alert disappears + onChange fires with the new valid JSON).
- Documents the mount-only local-state contract so future callers aren't
surprised that external value resets don't propagate.
- NumberField: emit undefined for empty / non-finite input instead of silently
coercing to 0, matching the value: number | undefined type. Widens onChange
to (v: number | undefined) => void. Adds a test for the empty-input path.
- All three fields: drop redundant aria-label (wrapping <label> + htmlFor/id
provides the accessible name; the aria-label was double-announcing in
screen readers).
- Set native required attribute on input/select/checkbox so assistive tech
exposes the constraint alongside the (optional) suffix.
Previously bundler.ts (catalog) and hook-bundler.ts duplicated ~80 lines of
rolldown + nodeResolveFallback wiring. Extracts the common shell into
extension/shared/iife-bundler.ts exposing bundleIife() and IifeBundleResult.
bundler.ts and hook-bundler.ts are now thin wrappers supplying their specific
iifeName, externals, globals, and skip prefixes. Catalog and hook bundler
tests still pass unchanged; type aliases (BundleResult, HookBundleResult)
preserved for call-site source compatibility.
- Replaces the separate loadGitignore() walk + per-file isIgnored() loop with a
single directory walk that inherits gitignore scopes from parent to child.
For a workspace with G gitignore files and F source files, cuts syscalls
from O(2*(F+G)) to O(F+G) and ignore-checks from O(F*G) to O(F*depth).
- Drops unused ScanWorkspaceOptions.roots and unused rootDir param.
- HARDCODED_EXCLUDES is now a case-insensitive Set.
## Summary
- Remove `@langchain/community` from `@copilotkit/sdk-js`
peerDependencies — it's declared as `^0.3.58` but never imported
anywhere in the package
- This was the only constraint blocking `@langchain/core@1.x` resolution
for consumers using the LangGraph JS starter
## Context
`@copilotkit/sdk-js` has zero imports from `@langchain/community`. The
`^0.3.58` peer dep forced npm to resolve `@langchain/community@0.3.x`,
which transitively pulled in `@getzep/zep-cloud` → `langchain@0.3.x`,
conflicting with `sdk-js`'s own `langchain@>=1.0.0` peer requirement.
Removing the unused peer dep eliminates this resolution conflict
entirely.
## Test plan
- [ ] `npm install` in a fresh project using `@copilotkit/sdk-js`
resolves without peer dep warnings for `@langchain/community`
- [ ] Existing LangGraph JS starter installs cleanly
- [ ] No runtime behavior change (nothing imported from the removed dep)
@copilotkit/sdk-js declares @langchain/community as a peer dependency
but never imports from it. The ^0.3.58 constraint was the only thing
blocking @langchain/core@1.x resolution for consumers. Remove it
entirely since it serves no purpose.