0.2.19#630
Merged
Merged
Conversation
…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>
- 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.