Skip to content

0.2.19#630

Merged
XingYu-Zhong merged 91 commits into
masterfrom
develop
Jun 28, 2026
Merged

0.2.19#630
XingYu-Zhong merged 91 commits into
masterfrom
develop

Conversation

@XingYu-Zhong

Copy link
Copy Markdown
Collaborator

No description provided.

luoye520ww and others added 30 commits June 26, 2026 02:24
…l-state

refactor(chat): split store initial state
feat(composer): add files and folders entry
…sibility

feat(settings): show MCP and Skill permission sources
…vior

refactor(main): split desktop behavior helpers
fix(app): address issues 571 572 573 and 580

# Conflicts:
#	src/main/index.ts
Let users fork an existing branch into another worktree, run git worktree add from the primary repo, and keep Kun worktree paths grouped under their source project in the sidebar.

Co-authored-by: Cursor <cursoragent@cursor.com>
The MCP permission-source preview (#596) interpolated the raw JSON.parse
error message, which can echo a slice of the malformed config text and
leak an unquoted secret. Show a generic "fix the JSON" message instead;
the preview's whole point is to expose counts/booleans, never values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First slice of the Claude Agent SDK fusion runtime (the subscription engine).
Re-projects the Agent SDK message stream onto kun's native RuntimeEvent
contract so a subscription turn renders in the GUI identically to a turn driven
by kun's own agent loop.

- sdk-protocol.ts: decoupled type surface for @anthropic-ai/claude-agent-sdk so
  the pure fusion modules typecheck/test without the (large, binary-bundling)
  package installed; only the runtime binds the real package.
- sdk-event-mapper.ts: assistant text/thinking deltas, tool_use -> tool_call_ready
  (+item_created), tool_result -> tool_call_finished, result -> usage. Usage
  mapping counts cache reads/creation into prompt tokens (anthropic input_tokens
  excludes them).
- 11 unit tests; kun typecheck clean.

Part of the kun-brain + Agent-SDK-engine fusion (dual engine routed by providerId).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inbound half of the fusion: re-expose kun's own tools to the Agent SDK so
generate_image / computer_use / memory / web / delegate_task etc. keep working
on a subscription turn, executing in-process via kun's real executor. Overlap
tools (read/bash/edit/write/grep/find/ls) use the SDK built-ins. delegate_task
is bridged rather than mapped to the SDK `agents` option to preserve kun's
richer delegation (async detach, live profile overlays, per-child deny-lists).

- selectBridgeableTools / mapKunResultToSdkContent / buildBridgedToolSpecs
  (pure, error-isolating handlers) + bridgedToolModelNames for allowedTools
- jsonSchemaToZodShape: best-effort param surface for the SDK tool() helper
- thin toSdkMcpServer binding
- 12 unit tests; kun typecheck clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the pure fusion core. Assembles ClaudeAgentOptions for a kun
subscription turn so the SDK runs its loop with kun's brain:
- buildClaudeSystemPrompt: kun persona appended onto the claude_code preset
- mapApprovalPolicyToPermissionMode: kun approval policy -> SDK permission mode
- buildCanUseTool: route every SDK tool call through kun's approval engine
  (fail-closed on error)
- buildScopedEnv: strip ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / cloud-provider
  vars that would outrank the subscription OAuth token, then inject
  CLAUDE_CODE_OAUTH_TOKEN (the auth-precedence gotcha)
- assembleSdkOptions: union SDK built-ins + bridged kun tools, partial streaming
- 14 unit tests; kun typecheck clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AgentSdkRuntime.runTurn drives the Agent SDK query() for a subscription turn:
bridges kun-exclusive tools into an in-process MCP server, wires canUseTool to
kun's approval, scopes the env (strip API keys, inject OAuth token), maps the SDK
stream onto kun events with milestone item persistence, saves the SDK session id
for resume, and finishes the turn (incl. abort + error paths).

Depends only on an injected SdkRuntimeDeps seam so the orchestration is
unit-testable with a fake SDK; the concrete kun-service binding lives in the
runtime factory (next).

6 new tests; 43 total green; kun typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ServeProviderConfigSchema: add kind:'agent-sdk' (baseUrl optional for it,
  still required for http via refine); apiKey carries CLAUDE_CODE_OAUTH_TOKEN
  (empty => rely on the host's Claude Code login).
- runtime-factory: agent-sdk providers skip the HTTP client; construct the
  AgentSdkRuntime (binds SdkRuntimeDeps to registry/turns/sessionStore/
  threadStore/events/prefix/ids) and inject it into AgentLoop.
- agent-loop runTurn: dispatch the whole turn to sdkRuntime when it owns the
  thread's provider; every other provider falls through to the native loop.
- agent-sdk-runtime-factory: loadTurnContext/executeKunTool/decideToolApproval/
  finishTurn over kun services; lazy string-specifier SDK import.
- add @anthropic-ai/claude-agent-sdk ^0.3.193.

Full kun typecheck clean; 47 tests green. Remaining: npm install (regen kun
lockfile), electron token/UI wiring, real Max-account E2E. GUI approval routing
and thread-model selection are flagged MVP TODOs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Threads the agent-sdk provider kind end-to-end so a user can add a
"Claude (Pro/Max 订阅)" provider whose turns route to the embedded Agent SDK:
- ModelProviderProfileV1 + ModelProviderPreset gain a `kind` field; new
  claude-subscription preset (kind:'agent-sdk', claude-* models)
- modelProviderPresetProfile + normalizeModelProviderProfile propagate kind
- IPC provider schema accepts kind (strict-schema passthrough)
- kun-process providersConfigForRuntime emits kind into serve.providers
  (apiKey carries CLAUDE_CODE_OAUTH_TOKEN; baseUrl present but unused for the kind)

web+node typecheck clean; 68 provider/IPC tests green.

Configurable today via the API Key field (paste a `claude setup-token` token, or
leave empty to use the host's Claude Code login). A dedicated login section +
ToS "personal use" banner are follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the bare "API Key" box for agent-sdk providers with a real login
section: detects an existing local Claude Code login, a button that runs
`claude setup-token` (opens the browser) and captures the OAuth token, a
manual-paste fallback, and a ToS "personal use" note. Empty token => host
Claude Code login.

- claude-subscription-auth.ts (main): status detect + setup-token spawn/capture
  (injectable spawn; defensive timeout / ENOENT / exit handling) + 4 tests
- IPC claude-subscription:status / :login + preload bridge + typed KunGuiApi
- ClaudeSubscriptionSection rendered in settings-section-providers when
  provider.kind === 'agent-sdk'; en/zh i18n

web+node typecheck clean; 73 tests green (4 new). The setup-token spawn needs
real-machine verification (no Claude account here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oviders

agent-sdk providers have no HTTP /models endpoint — the turn is delegated to the
Claude Agent SDK — so the generic probe hit api.anthropic.com/v1/models with an
x-api-key header and 401'd ("invalid x-api-key"). runProbe now short-circuits
agent-sdk providers to report Claude login readiness (a pasted token or a local
Claude Code login) instead of probing HTTP. +en/zh string.

web typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
providersConfigForRuntime skipped the runtime's active provider (it's wired as
the default HTTP client via CLI args). When that active provider is the Claude
subscription (agent-sdk kind), the skip meant it never entered serve.providers /
agentSdkProviderIds — so the runTurn dispatch couldn't claim it and the turn fell
through to the default HTTP client → POST api.anthropic.com/v1/messages with an
x-api-key header → 401 "invalid x-api-key".

Keep agent-sdk providers in serve.providers even when they're the active runtime
provider (and allow them with no baseUrl). The default client is still built but
the dispatch intercepts the turn before it's used.

node typecheck clean. The 4 failing kun-process tests are the pre-existing
EADDRINUSE:18899 port-conflict flakies (the running Kun app holds the port),
not this change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Installed the SDK (v0.3.193, ESM) + deps; kun/package-lock.json now pins it so
ensure-kun-install / `npm ci` resolves it. Verified against the real package:
exports (query / tool / createSdkMcpServer) and Options fields (systemPrompt,
mcpServers, canUseTool, hooks, permissionMode, resume, includePartialMessages,
env, abortController, agents, cwd, allowedTools, pathToClaudeCodeExecutable) all
match the protocol shim; tool() wants a ZodRawShape, which the bridge already
produces via jsonSchemaToZodShape. kun typecheck clean; 47 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The context window for preset providers is authoritative (withPresetModelProfiles
overwrites manual per-model edits on normalize), so editing it in the model page
didn't stick. claude-subscription is always agent-sdk, where contextWindow is
cosmetic (the embedded SDK enforces the real per-model limit) and Sonnet 4.x
reaches 1M — surface 1M on all three so the capacity gauge isn't falsely full.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…install

The Agent SDK ships a per-platform Claude Code binary as an optional dependency
(@anthropic-ai/claude-agent-sdk-<plat>-<arch>/claude) that supports `setup-token`.
The login button now resolves and spawns that bundled binary, so subscription
users need NO separate `npm i -g @anthropic-ai/claude-code`. Falls back to a
`claude` on PATH only if the bundled one can't be resolved.

- resolveBundledClaudeBinary() + runClaudeSetupToken({ binaryPath })
- login IPC resolves the binary under <appRoot>/kun (+ cwd) for dev & packaged
- ToS / error copy updated (Claude Code is bundled; one-time login only)
- 2 tests (binaryPath spawn, per-platform resolver); node+web typecheck clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… the main model

When Claude (Pro/Max) is the runtime's DEFAULT provider, a turn may carry no
thread.providerId and the dispatch missed it → fell to the HTTP default client →
401 invalid x-api-key. kun-process now signals the active provider's kind via
KUN_RUNTIME_PROVIDER_KIND; runtime-factory builds the SDK runtime when the
default is agent-sdk, and handlesProvider claims absent/default-provider turns
(explicit HTTP providers still route to HTTP). The default provider's token
(options.apiKey) is used when a turn doesn't target a specific provider.

+2 tests; kun + node typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
XingYu-Zhong and others added 29 commits June 27, 2026 14:18
- runtime model-error proxy hint now skips AbortError (user cancel /
  idle-timeout watchdog) so a working proxy is not wrongly blamed
- diagnostic direct re-probe uses a 5s budget so a failed test
  connection no longer takes up to 20s
- cover the abort case with a model-client test

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…settings

fix(provider): diagnose stale proxy settings
A failed provider probe embeds the full error (a URL, up to 300 chars of
response body, or the proxy-diagnosis text) into the inline notice.
Without word-breaking the message overflowed horizontally and stretched
the settings panel; the success notice is short so the breakage only
ever showed on the failure state. Add `min-w-0 break-words` to
InlineNoticeView so long messages wrap inside the container.

Fixes #617

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(settings): wrap long test-connection error messages (#617)
…ude model

A thread created while a non-subscription provider was active stores that
provider's model id (e.g. deepseek-v4-flash). Once the Claude subscription is
the active provider, every turn routes to the Claude Agent SDK — and passing a
non-Anthropic model id to Claude Code fails the turn with "There's an issue with
the selected model (deepseek-v4-flash). It may not exist or you may not have
access to it."

Add resolveSdkModel/isAnthropicModel: use the thread's model only when it is a
Claude id, otherwise fall back to the runtime's default Claude model (or omit so
Claude Code uses its built-in default). Wire the runtime default model into the
SDK runtime factory and apply the guard in loadTurnContext.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Claude Agent SDK validates the canUseTool return value with a runtime Zod
schema that is stricter than its TypeScript types: an `allow` result requires
`updatedInput` to be a record (the type marks it optional) and a `deny` result
requires a non-empty `message`. Returning a bare `{ behavior: 'allow' }` — which
kun did whenever it didn't rewrite the input — failed with a ZodError as soon as
the model called a native tool such as AskUserQuestion.

Always echo the original tool input through as `updatedInput` on allow, and
default the deny `message` when kun's decider doesn't provide one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ll it

A plan turn (SDD "下一步" / Plan mode) instructs the model to call create_plan,
but kun's create_plan tool is gated — its shouldAdvertise and executor require a
plan context (guiPlan / threadMode==='plan'). The SDK factory built its tool
context without that, so create_plan was never bridged: the model wrote the plan
as prose and the GUI reported "no matching create_plan result".

Resolve the turn's plan context (mirroring the native loop's candidate/stale
derivation) and thread it into the tool context used for BOTH listTools (so
create_plan is advertised + the kun tool set narrows to the plan-allowed set)
and executeKunTool (so create_plan resolves its reserved write path).

Also stop mapping kun's plan turn to the SDK's 'plan' permission mode: that mode
blocks tool execution, which would block the bridged create_plan itself. The
plan behavior now comes from advertising create_plan + the injected plan
instruction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The SDK's native AskUserQuestion has no UI in kun's embedding, so on a plan/
requirement turn the model would ask a clarifying question, get no answer, and
press on with a fabricated plan. kun already has a GUI input panel; wire the
subscription path to it instead.

- Bridge kun's user_input/request_user_input tools (drop them from the bridge's
  excluded set). They only advertise when the tool context carries
  awaitUserInput, so they stay hidden when no gate is wired.
- Wire awaitUserInput in the SDK factory to kun's UserInputGate: persist the
  request item + publish user_input_requested/resolved events (so the renderer
  shows the panel) and wait on the gate, cancelling on turn abort. Thread the
  turn's AbortSignal into executeKunTool for that cancellation.
- Suppress the SDK's native AskUserQuestion via disallowedTools so the model
  uses kun's tool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add isConversationWorkspacePath filter to WorkspaceProjectPicker
- Pass conversationWorkspaceRoot from useChatStore to buildWorkspaceProjectPickerOptions
- Update isWorkspaceProjectPickerRoot to accept and use conversationRoot parameter
- Add comprehensive test cases for conversation workspace exclusion
- Ensure consistency with SidebarProjectsSection filtering logic

This prevents conversation workspaces (e.g., 20260626-081633) created via 'New Conversation' from appearing in the project selection dropdown, keeping the UI clean and focused on actual project folders.
The conversation-workspace tests replaced (rather than extended) the
existing suite, leaving buildWorkspaceProjectPickerOptions' worktree
resolution/dedup logic — which is still live — untested. Re-add the two
worktree-grouping cases alongside the new conversation-exclusion ones and
drop a stray trailing-whitespace line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…spaces-from-project-picker

feat(ui): exclude conversation workspaces from project picker dropdown
#621)

A subagent-heavy turn on large content could wedge the runtime: the kun event
loop blocked long enough that /health timed out, the GUI watchdog killed a
runtime that was still working ("turn ended early"), recovery took ~5 min, and
the thread then stayed stuck on "a turn is already running" — even across
restarts.

Three linked fixes:

- Supervisor recovery (src/main/index.ts): ensureKunRuntime now detects a
  managed child that is alive but failing /health (hung, not absent) and
  restarts it in place on the same port, after a 10s confirmation window so a
  merely-busy runtime is not killed. Previously resolveAvailablePort skipped our
  own child and startKunChild early-returned while isChildRunning() stayed true,
  so a hung child could only be recovered by the ~90s watchdog.

- Orphan cleanup: reconcileOrphanedTurns now sweeps hidden `side` threads
  (delegated subagents run on one), DelegationRuntime gains
  reconcileOrphanedChildRuns() to fail queued/running child records on startup,
  and the renderer settles lingering running/pending blocks when the server
  reports the thread settled, so re-sends stop re-queuing forever.

- Event-loop hot spots: healLoadedHistoryItems detects changes by identity
  instead of two full-history JSON.stringify per step, and FileSessionStore
  loadItems dedups in O(n) (push+reverse) instead of O(n^2) unshift; a slow cold
  loadItems logs its size to attribute future stalls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a heartbeat-based event-loop monitor to the kun serve process. When the
single event loop is blocked, the heartbeat fires late and logs the stall
duration — exactly the window during which /health and SSE are unanswerable.

This disambiguates the two failure modes behind a watchdog restart: a logged
stall followed by recovery is CPU starvation (and its magnitude); the GUI
reporting the runtime unhealthy with no stall ever logged is a hard
hang/deadlock. Threshold is overridable via KUN_EVENT_LOOP_STALL_LOG_MS
(default 2000ms).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…liability

fix(runtime): stabilize endpoint health recovery
perf(renderer): lazy-load write and SDD workspaces
…ments

feat(attachments): add local files to conversations
Reworks the #626 acceptance loop per review: it was a forced, all-modes
behavior change. Now verify_changes is advisory and scoped to code work.

- drop the forced verify loop entirely (verificationRequiredByTurn, the
  required-tool enforcement, the stop-blocking `return 'continue'`, and the
  consecutive-failure/exhausted bounding); revert the empty-post-tool and
  required-tool paths to their pre-#626 form
- replace it with turnHasUnverifiedSourceChanges + a soft, optional nudge
  that only appears when the turn edited real source files (.ts/.js family)
  not yet covered by a verify_changes run; docs/HTML in write/design/SDD
  never trigger it (content-based gating, no GUI plumbing needed)
- verify tool: treat "no supported scripts / non-JS project" as a benign
  skip instead of isError, and spawn with shellSpawnEnv() to match bash
- update tests to the suggestion model + cover the benign-skip case

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(runtime): verify_changes as code-only optional suggestion (supersedes #626)
The goal popover and the active-goal floater used `bg-ds-card/95` /
`dark:bg-ds-card/90`. The `ds-card` token is `var(--ds-surface-card)` (a raw
CSS var with no `<alpha-value>` channel), so Tailwind 3.4 silently drops the
whole `background-color` rule for the `/NN` opacity modifier — the panels
rendered with no fill (just a faint blurred border), so clicking "追求目标"
appeared to do nothing / the menu just closed.

Switch both to the solid `bg-white dark:bg-ds-card` token already used by the
adjacent composer menu popover. Verified: Tailwind emits a real
background-color for these classes (and none for the `/95`/`/90` variants).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(composer): restore goal panel/floater background fill
The pending-change summary, attachment/file chips, context-capacity button,
and usage labels used `bg-ds-card/78|80|70|72`. Tailwind 3.4 drops the rule for
an opacity modifier on the raw-var `ds-card` token, so these rendered with no
fill (border only). Switch to the solid `bg-ds-card` token (= `--surface-1`,
itself ~0.9 alpha, so still a soft fill) — same root cause as the goal-panel
fix (#628). Web typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(composer): render composer chip/label backgrounds
The hover preview popup over sidebar thread rows only ever showed
empty/near-empty content ('暂无预览内容'), adding noise without value.
Remove the card render, no-op the open/close handlers, and drop the
now-dead state, type and component. Keep resolveThreadPreviewPosition
and the row preview props (still covered by tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@XingYu-Zhong XingYu-Zhong merged commit 5c6afc1 into master Jun 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants