* feat(kit): isolate viper config per Kit instance + add NewAgent (#40)
- Give each kit.New()/NewAgent() call an isolated *viper.Viper store so
multiple Kit instances in one process no longer clobber each other's
config; runtime mutators (SetModel, SetThinkingLevel) touch only the
owning instance, making subagent spawning and multi-Kit embedding
race-free
- Thread the per-instance store through internal/config, internal/models
(ProviderConfig.ConfigStore), internal/kitsetup, and the extension
runner, with a nil -> process-global fallback so the CLI is unaffected
- Share the global store when Options.CLI != nil to preserve cobra flag
bindings (also opted in for internal/acpserver)
- Remove viperInitMu; preserve the tri-state IsSet precedence contract
and sdkDefaultMaxTokens floor
- Add ergonomic NewAgent + functional options (WithModel, WithStreaming,
Ephemeral, etc.); NewAgent defaults streaming on, opt out via
WithStreaming(false). New(ctx, *Options) behavior is unchanged
- Add config-isolation regression test and NewAgent/option coverage;
document NewAgent and per-instance isolation in README
Fixes#40
* docs(sdk): document NewAgent options and per-instance config isolation
- Add "Functional options (NewAgent)" and "Per-instance config isolation"
sections to the docs site SDK overview, with an options table and a
"when to use which" constructor comparison
- Cross-reference NewAgent from the SDK options page and correct the now
per-instance ProviderAPIKey precedence wording
- Document NewAgent + With* helpers and config isolation in pkg/kit/README
and list NewAgent/Option in the API reference
- Show the NewAgent constructor in the SDK examples getting-started snippet
* fix(kit): correct config loading and isolate ACP sessions
- Isolate each ACP session's config store instead of sharing the global
viper, preventing per-session SetModel/SetThinkingLevel races; seed the
root-command flag values (model, thinking-level, provider URL/key) so
`kit acp -m <model>` is still honored
- Run initConfig for isolated SDK stores by gating on opts.CLI instead of
v.GetString("model"), which setSDKDefaults always populates and thus
skipped .kit.yml / KIT_* loading for SDK callers
- Configure KIT_* env overrides unconditionally in initConfig so passing an
explicit config file no longer disables environment variable support
- Wrap config unmarshal/validate errors with %w to preserve the error chain
* fix(kit): make Options.Streaming a *bool to honor unset
- Change Options.Streaming from bool to *bool so a zero-valued Options no
longer forces stream=false; New only sets the key when non-nil, letting
streaming resolve through the precedence chain (env -> config -> default
true). This also fixes the CLI path, which never set the field
- Mirror the existing sampling-parameter pointer pattern instead of adding
a separate StreamingSet sentinel, keeping Options internally consistent
- Update WithStreaming/NewAgent, subagent, and ACP callers to the pointer
form; add regression tests for the nil-default and explicit opt-out paths
- Update SDK docs (README, pkg/kit/README, options page) with the ptrBool
helper and *bool semantics
* fix(kit): inherit parent provider config in subagents
- Copy the parent's effective provider/runtime config (API key, URL,
TLS, thinking level, max-tokens, samplers) onto child Options in
Kit.Subagent. After the per-instance viper isolation, the child's
isolated store only re-loaded .kit.yml / KIT_*, silently dropping
config the parent set via programmatic Options or runtime setters
like SetThinkingLevel
- Preserve the IsSet tri-state for max-tokens and samplers so per-model
defaults still apply on the child when the parent left them unset
- Add TestInheritProviderConfig covering propagation, unset keys, and
nil-safety
* feat(core): expose no-core-tools via CLI flag and config file
Allow users to disable all built-in core tools (bash, read, write,
edit, grep, find, ls, subagent) without recompiling, using a CLI flag,
environment variable, or .kit.yml config key.
Changes
-------
cmd/root.go
- Declare noCoreToolsFlag bool alongside noExtensionsFlag.
- Register --no-core-tools persistent flag with a descriptive help string
listing the affected tools.
- Bind the flag to viper key "no-core-tools" so the config file and
KIT_NO_CORE_TOOLS env var also work (viper's standard precedence:
CLI flag > env var > config file > default).
- Set kitOpts.DisableCoreTools = viper.GetBool("no-core-tools") when
assembling the Options struct in runNormalMode.
pkg/kit/kit.go
- Add disableCoreTools local variable inside the viperInitMu-protected
snapshot block, mirroring the noExtensions pattern exactly.
- Resolve it as opts.DisableCoreTools || viper.GetBool("no-core-tools")
so the SDK option and the viper key are both respected (OR semantics:
either source can enable the flag).
- Pass the resolved disableCoreTools into kitsetup.AgentSetupOptions
instead of the raw opts.DisableCoreTools, completing the chain.
Usage
-----
# CLI flag
kit --no-core-tools
# Environment variable
KIT_NO_CORE_TOOLS=true kit
# .kit.yml config file
no-core-tools: true
# SDK (unchanged, was already supported)
kit.New(ctx, &kit.Options{DisableCoreTools: true})
The downstream path (kitsetup → agent.AgentConfig.DisableCoreTools →
agent.NewAgent nil-tool branch) was already in place and required no
changes.
* docs(readme): document no-core-tools flag, config key, and env var
Update three locations in README.md to reflect the new no-core-tools
control surface introduced in the previous commit:
CLI Reference → Global Flags
Add --no-core-tools under the Extensions and tools section alongside
--no-extensions, with a description listing the affected tools.
Configuration → Basic Configuration
Add no-core-tools: false to the example .kit.yml block so users know
it is a valid config file key (equivalent to the CLI flag and env var).
Go SDK → With Options
Expand the DisableCoreTools comment to note that the same behaviour is
also available via --no-core-tools, KIT_NO_CORE_TOOLS, and
no-core-tools: true in .kit.yml, making the cross-surface relationship
explicit for SDK consumers.
* style: gofmt cmd/root.go
* feat(sdk): runtime skills and context-file management (#36)
Let SDK consumers add, remove, and replace skills and AGENTS.md-style
context files after Kit construction. Every mutation recomposes the
system prompt and applies it to the agent so the next turn picks up
the new instructions without restarting Kit.
- AddSkill / LoadAndAddSkill / RemoveSkill / SetSkills on *kit.Kit
- AddContextFile / AddContextFileContent / LoadAndAddContextFile /
RemoveContextFile / SetContextFiles on *kit.Kit
- RefreshSystemPrompt to force a manual recomposition
- agent.SetSystemPrompt / GetSystemPrompt on the internal agent so
the composed prompt rebuilds the fantasy agent on the next call
- Per-instance runtimeMu guards skills/contextFiles; GetSkills and
GetContextFiles return defensive snapshots safe for concurrent use
- Capture the resolved basePrompt during New so recomposition keeps
per-model overrides and --system-prompt file resolution intact
- Skills dedupe by Name; context files dedupe by Path (opaque ID,
not required to be a real filesystem path)
Tests cover add/remove/set/replace semantics, validation errors,
disk loading round-trips, prompt composition, and an 8-goroutine
race-stress sweep (go test -race clean).
Docs: pkg/kit/README, root README Go SDK section, www sdk/overview
"Runtime skills and context files" section, www sdk/options callout
cross-referencing the new API.
Fixes#36
* fix(agent): synchronize SetSystemPrompt against concurrent rebuilds
- add promptMu to Agent guarding systemPrompt writes and the fantasy
agent rebuild, fixing a data race when Kit.applyComposedSystemPrompt
is invoked concurrently
- read systemPrompt under the same lock in GetSystemPrompt
- update the thread-safety stress test to use a non-nil agent so the
SetSystemPrompt path is actually exercised under -race
fantasy v0.25.1+ bumps kaptinlin/jsonschema to v0.7.14+, which transitively
pulls in github.com/agentable/go-intl. Its internal/cldr/displaynames
package contains a ~143k-line / 5.4MB generated CLDR map literal that
compiles at ~6.7GB RSS, OOM-killing release builds on 7GB GitHub runners.
Pinning fantasy v0.25.0 (jsonschema v0.7.13, go-i18n v0.4.5,
messageformat-go v0.6.0) removes go-intl from the build graph entirely and
restores clean-build peak RSS from ~6.6GB back to ~1GB. Upstream issue filed
against charmbracelet/fantasy.
A single clean cross-build peaks at ~7GB RSS (internal/extensions yaegi
symbol table). goreleaser builds targets in parallel by default, which
exhausts the 7GB ubuntu-latest runner and OOM-kills the build with no
error output. Force --parallelism 1 and cap go compiler with GOFLAGS=-p=2.
- Remove unused SetOpenAICredentials/validateOpenAIAPIKey (internal/auth)
- Remove unused SudoPasswordRequiredMetadata/IsSudoPasswordRequiredResult
(internal/core)
- Add Extension* type aliases in pkg/kit/extension_api.go so the public
ExtensionAPI interface no longer exposes internal/extensions types
- Extract bridgeObserve generic helper and llmToContextMessages /
contextMessagesToLLM in pkg/kit/extensions_bridge.go (~150 lines saved)
- Extract parseHeaders and buildOAuthConfig in connection_pool.go to
deduplicate SSE/Streamable client construction (~60 lines saved)
- Eliminate redundant second buildInteractiveExtensionContext call in
cmd/root.go; swap print closures on the same context instead
- Replace 'Fantasy' with 'agent' in internal comment (pkg/kit/kit.go)
Previously GenerateWithCallbacks stored the most recent tool call's args
in a single shared variable, which got clobbered when a provider emitted
multiple tool_use blocks in a single step. Every OnToolResult callback
then received the args of the last OnToolCall, regardless of which call
it was actually resolving — breaking any downstream UI, log, or trace
that derived its description from the toolArgs parameter.
- Replace the shared currentToolArgs with a map keyed by ToolCallID,
guarded by a sync.Mutex in case the streaming layer dispatches
callbacks from multiple goroutines.
- Delete each entry in OnToolResult so the map cannot accumulate
across steps.
- Add a regression test driving the streaming wrapper with a fake
fantasy.Agent that emits two parallel tool calls before either
result, asserting each callback sees its own args.
Fixes#33
Open the /resume session picker faster by extracting per-file metadata
across a GOMAXPROCS-sized worker pool instead of sequentially. Each
extractSessionInfo call is I/O + JSON-parse bound and independent, so
wall time drops roughly proportionally to core count — meaningful for
users with many sessions, where ListSessions + ListAllSessions ran
back-to-back on the UI goroutine before the picker rendered.
- Switch NotifyWidgetUpdate from leading-only to leading+trailing edge
coalescing so a rapid SetWidget→RemoveWidget pair (e.g. emitted by
subagent-monitor on SubagentEnd) is never silently dropped.
- Without the trailing send the TUI keeps the pre-removal widget
height, leaving empty rows below the status bar until some other
event re-renders the layout.
- Match View() and getItemAndLineAtY() row counts for empty items so
streaming-reasoning placeholders no longer offset hit-testing by one
row each (exposed when extension widgets like subagent-monitor shrink
the scrollback).
- Honor IsLineInRange's endCol=-1 'to end of line' sentinel in
HighlightLine and ExtractText so the start row of a multi-line drag
actually renders highlighted and is included in clipboard copies.
- Add regression tests for both invariants in scrolllist and selection.
- Add prompts.GlobalDir() resolving $XDG_CONFIG_HOME/kit/prompts/
(default ~/.config/kit/prompts/) so prompt templates live alongside
extensions and skills under the same XDG-aligned root.
- LoadAll now discovers templates from both the legacy ~/.kit/prompts/
and the XDG location; existing legacy paths keep precedence.
- Include GlobalDir() in the prompts/skills file watcher so edits
under ~/.config/kit/prompts/ hot-reload automatically.
- Surface a visible 'Extensions reloaded.' (or error) message when
the extension watcher fires, matching /reload-ext feedback.
- Restore examples/extensions/subagent-monitor.go alongside its test
and update the test load path; previous move left the test broken.
- Remove .kit/extensions/go-edit-lint.go from the repo since the
extension is now installed under ~/.config/kit/extensions/ for
per-user use across all projects.
- Add ExtensionInfo type and Loaded() method to the public ExtensionAPI
so SDK consumers can inspect which extensions are active.
- Introduce ui.ExtensionItem and thread ExtensionItems/GetExtensionItems
through AppModelOptions, mirroring the existing SkillItem pattern.
- Render an [Extensions] row in AddStartupMessageToScrollList showing
the filename of each loaded extension (with a (N tools) suffix when
extensions register tools). Falls back to tool count only when items
are unavailable, and is omitted entirely when no extensions load.
- Refresh the list on /reload-ext via a new refreshExtensionItems hook
so the banner stays accurate across hot-reloads.
- Add buildExtensionItems helper in cmd/root.go that strips .go and
resolves subdirectory extensions to their parent dir name, tagging
each as project or user scope based on cwd.
- Lock viewport scroll while a drag-select is active so highlighted
content stays under the cursor (SetItems, appendStreamingChunk,
MouseWheelDown all now honor IsMouseDown).
- HandleMouseDrag defensively clears autoScroll on every update so a
racy re-enable can't shift the row mid-drag.
- Recompute scrollback yOffset/viewport height on each mouse event
via currentScrollbackBounds() instead of relying on stale values
cached during the previous View() pass.
- Account for canceling/ctrlCPressedOnce warning rows in
distributeHeight and mark layoutDirty when those flags toggle so
the height budget and mouse origin stay in sync.
- Add ScrollList regression tests covering the three invariants.
- Register /copy (alias /cp) in the System command category
- Walk the scrollback to find the last user/assistant/reasoning
message, skipping transient system messages
- Reuse internal/ui/clipboard.CopyToClipboard for OSC 52 + native
clipboard support (works over SSH)
- Document the command in /help
After /compact, BuildContext emitted [summary, post-compact, kept]
which placed an older kept user/assistant turn after the latest
post-compaction turn. This broke user/assistant alternation and caused
the model to respond as if the post-compaction turn never happened on
the next user message.
- Emit kept messages chronologically before post-compaction messages
- Mirror the same order in GetContextEntryIDs so cut-point to entry-ID
mapping stays aligned across repeat compactions
- Update TestCompactionWithNewMessagesAfterCompaction to assert the
correct chronological order
* fix(sdk): stop leaking fantasy types through pkg/kit.AgentConfig (#30)
Replace the alias-based AgentConfig and handler types with SDK-owned
structs and function types. CoreTools / ExtraTools / ToolWrapper now
accept []kit.Tool, and the handler types (ToolCallHandler,
ToolExecutionHandler, ToolResultHandler, ResponseHandler,
StreamingResponseHandler, ToolCallContentHandler) plus SpinnerFunc are
declared in pkg/kit/ with signatures that reference only SDK types.
Consumers no longer need to import charm.land/fantasy to populate an
AgentConfig or assign a handler. go doc pkg/kit AgentConfig output no
longer mentions fantasy.*.
- Add unexported (*AgentConfig).toInternal() to convert at the SDK
boundary; Tool is still an alias for the underlying tool type, so
slice and function fields convert without allocation.
- Add agent_config_internal_test.go covering nil receiver, scalar
fields, tool slices, ToolWrapper invocation, OnMCPServerLoaded, and
auth/token-factory wiring.
- Add types_test.go cases that populate AgentConfig and SpinnerFunc
without importing fantasy -- the file compiling is the regression
proof for the leak.
- Update pkg/kit/README.md Re-exported Types section to record that
AgentConfig and the handler types are now Kit-owned.
Fixes#30
* fix(sdk): add DebugLogger and MCPTaskConfig to kit.AgentConfig (#30)
The first revision of the SDK-owned AgentConfig dropped two fields that
internal/agent.AgentConfig carried: DebugLogger (tools.DebugLogger) and
MCPTaskConfig (tools.MCPTaskConfig). Restore them with SDK-owned
equivalents and wire them through toInternal().
- Add kit.DebugLogger interface (LogDebug / IsDebugEnabled) mirroring
tools.DebugLogger. Interface-to-interface assignment is automatic
because the method sets match.
- Add kit.MCPTaskConfig struct mirroring tools.MCPTaskConfig with SDK
types (MCPTaskMode, MCPTaskProgressHandler) and a toToolsConfig()
helper that converts at the SDK boundary.
- Wire both new fields in (*AgentConfig).toInternal().
- Extend agent_config_internal_test.go with cases for both fields.
- Document the additions in pkg/kit/README.md.
The MCP adapter previously wrapped any error returned by MCPToolManager.ExecuteTool
into a Go error returned from the fantasy.AgentTool.Run interface. The fantasy
agent loop treats those as critical errors and aborts the entire turn —
discarding all prior reasoning, tool calls, and results.
In practice that meant a single misbehaved MCP server returning a JSON-RPC
"-32602 Invalid params" (e.g. a Zod schema mismatch on the server's input
validation) would kill an in-progress turn after the model had already done
dozens of seconds of useful work, with no way for the model to see the
validation message and self-correct.
This mismatched the contract that native Kit tools follow: native tools
return errors via kit.ErrorResult(...), which become soft tool-result errors
that the model reads and can act on (retry with corrected args, try a
different tool, give up gracefully).
Make the MCP path behave the same way:
- JSON-RPC protocol errors, transport failures, and server-side schema
rejections are now returned as fantasy.NewTextErrorResponse(...) with
err == nil, so the agent loop continues and the model sees the failure
in-band as a tool result it can reason about.
- Context cancellation (ctx.Err() != nil) remains a critical error so
callers can abort turns deterministically. This is the only case where
bubbling up is correct — the caller intentionally tore the turn down
and the agent must not keep spinning.
- Server-side soft errors (CallToolResult{ isError: true }) and the
happy path are unchanged.
The agent loop's MaxSteps cap already bounds the worst case for a
permanently broken MCP server, so there is no risk of unbounded retries.
Side effect: extracted a tiny mcpExecutor interface for the one method the
adapter uses (ExecuteTool), purely so the adapter is unit-testable in
isolation without standing up a full MCPToolManager + connection pool.
Behavior change note for downstream consumers: code that relied on
host.PromptResult / Stream returning a Go error containing
"mcp tool execution failed" will no longer see those errors — the
failure information is now in the assistant's final response (or in the
OnAfterToolResult / OnToolResult hooks, where IsError will be true).
Context cancellation continues to surface as an error from those calls
as before.
Co-authored-by: space_cowboy <space_cowboy@mark3labs.com>
- register loaded skills into the input autocomplete under category
"Skills" with HasArgs so Enter populates "/skill:name " instead of
auto-submitting, leaving room for trailing args
- prefix descriptions with [project] or [user] to disambiguate
colliding skill names across sources
- extend refreshSkillItems to prune & re-add Skills entries on
ContentReloadEvent, matching the pattern used for prompt templates
and MCP prompts
- add Description field to ui.SkillItem and populate it from
kit.Skill.Description in both initial build and hot-reload paths
- Add unexported steerDrainFn test seam on App so unit tests can
inject fake steer items without standing up a full *kit.Kit
(Options.Kit is a concrete struct, not an interface).
- releaseBusyAfterCompact now prefers the seam over Kit.DrainSteer
via a small switch; production behaviour is unchanged when the
field is nil.
- Add TestReleaseBusyAfterCompact_splicesSteerAheadOfQueue, which
pre-populates both fake steer items and ordinary queue prompts,
invokes releaseBusyAfterCompact, and asserts the first dispatched
prompt is the steer item — proving steer messages retain 'act now'
priority and that drainQueue is actually launched (the bug from
#27).
- Add releaseBusyAfterCompact() shared deferred tail used by both
CompactConversation and CompactAsync. It drains the SDK steer
channel, splices steer items in front of any queued prompts, and
hands off to drainQueue so messages received during compaction
are dispatched automatically once compaction finishes.
- Previously, busy was simply cleared on completion and the queue
sat idle until the user submitted another prompt, which then
flushed everything together.
- Honor the closed flag so a teardown during compaction discards
pending items instead of spawning drainQueue against a torn-down
App.
- Add regression tests covering the queued-flush, idle-empty, and
closed-during-compact paths.
Fixes#27
SetupCLIForNonInteractive returns nil when --quiet is active, matching
the pre-existing nil checks elsewhere in the same block (e.g. the
buffered debug-message branch). Without this guard the new
'System Prompt loaded' notice panicked on quiet, non-interactive runs.
Discovered via tmux smoke test of the #25 fix.
When system-prompt was a file path (via --system-prompt, config entry,
or SDK Options.SystemPrompt), the path string itself was used as the
base prompt because config.LoadSystemPrompt only ran later in
BuildProviderConfig — by which point viper had been overwritten with
the path-augmented composed text. The LLM received the path instead of
the prompt contents.
- Call config.LoadSystemPrompt on the raw viper value in New() before
PromptBuilder composes runtime context (AGENTS.md / skills / date).
- Add HasCustomSystemPrompt() and GetSystemPromptSource() so SDK callers
can inspect prompt state without reaching into viper.
- Display 'System Prompt loaded: <source>' at startup in CLI and TUI
modes, paralleling the per-server 'MCP server loaded' notice.
- Add regression tests covering both file-path and inline prompt paths.
Fixes#25
Addresses two CodeRabbit feedback items on PR #24:
* Docstring coverage warning (was 57.14%, threshold 80%): adds godoc
comments to the four test functions added or substantially rewritten
in this PR — TestLoadAndSaveManifest, TestAddAndRemoveFromManifest,
TestFindInManifest, TestHighlightFileTokensInjectsANSI.
* Quick-win nitpick: replaces the manual os.Setenv/os.Unsetenv +
defer pattern in TestFindInManifest with t.Setenv, which restores
the env var automatically on cleanup even on panic or t.Fatal.
go test -race ./... still passes.
Per AGENTS.md 'Yaegi function field bug', named function/method
references assigned to extensions.Context fields return zero values
across the interpreter boundary. The two SetContext literals in
runNormalMode (now consolidated in buildInteractiveExtensionContext)
inherited 9 bare references that need to be anonymous closure literals:
PrintBlock, GetChildren, GetAvailableSkills, ParseTemplate,
RenderTemplate, ParseArguments, SimpleParseArguments,
ResolveModelChain, CheckModelAvailable
Each is now wrapped as 'func(args) ret { return <orig>(args) }'.
Behaviour unchanged in regular Go; Yaegi extensions that consume these
fields will now see callable closures instead of zero values.
Verified with go test -race ./...
The TestUserBlockHighlightsFileTokens test was rewritten to call
HighlightFileTokens directly (UserBlock was deleted in the dead-code
sweep). That left testTypography with no callers, so staticcheck U1000
flagged it.
The previous runNormalMode contained two nearly-identical 400-line
extensions.Context literal expressions:
* the startup-time literal (cmd/root.go:853-1307) that buffered
Print* calls into startupExtensionMessages
* the runtime literal (cmd/root.go:1311-1605) that routed Print*
through appInstance.PrintFromExtension
Every other field — Compact, SendMultimodalMessage, the four prompt
factories, all 25+ data-access fields, all four bridge phases — was
duplicated byte-for-byte. Maintainers had to remember to update both
copies whenever an extension Context field was added.
cmd/root.go is now 1463 lines (was 2225). The new helper lives in
cmd/extension_context.go (455 lines, mostly the closures verbatim) and
returns an extensions.Context with every field populated except
Print/PrintInfo/PrintError, which each call site sets afterwards to
match its phase. This preserves AGENTS.md's 'function field bug'
guarantee — all assignments remain anonymous closure literals.
Output of 'kit --version' / 'kit --help' unchanged. Full test suite
passes.
The same ~40-line block — building a kit.SubagentConfig, wrapping
OnEvent through sdkEventToSubagentEvent, calling kitInstance.Subagent,
and translating the SDK result into extensions.SubagentResult — was
copy-pasted three times:
* cmd/root.go (interactive TUI Context, line 1148)
* cmd/root.go (post-SessionStart runtime Context, line 1446)
* internal/acpserver/session.go (ACP server Context, line 154)
A separate sdkEventToSubagentEvent function was duplicated byte-for-byte
between cmd/root.go and internal/acpserver/session.go.
Both are now consolidated in a new internal/extbridge package which is
the only module-internal home that can legitimately import both
pkg/kit/ (the public SDK) and internal/extensions/. cmd/ and
internal/acpserver/ both import it, so SDK-event-to-extension-event
schema changes only have one site to update.
Also fixes pkg/kit/events.go godoc comment that named the underlying
LLM library, per AGENTS.md 'No Dependency Name Leakage' rule for
exported SDK symbols.
go test -race ./... passes.
Removes ~600 lines of unreferenced code surfaced by deadcode + manual
audit (none of it reachable from production code paths or test setup):
- internal/models/pool.go: ProviderPool was never wired into kitsetup
or the agent; the global pool singleton had zero callers.
- internal/ui/debug_logger.go: CLIDebugLogger was unreachable; debug
routing goes through internal/tools/buffered_logger.go instead.
- internal/ui/tool_approval_input.go: tea.Model never instantiated;
approvals are handled inline in model.go.
- internal/ui/cli.go: DisplayAssistantMessage / DisplayCancellation /
GetDebugLogger had zero callers (the *WithModel variant is what
event_handler.go uses).
- internal/ui/style/enhanced.go: Style{Card,Header,Subheader,Muted,
Success,Error,Warning,Info} + Create{Separator,ProgressBar} — none
used. CreateBadge stays (used by model.go).
- internal/ui/style/themes.go: RefreshThemeRegistry — never called.
- internal/ui/block_renderer.go: With{FullWidth,MarginTop,Padding{Left,
Right},Background,Foreground,Width} — option helpers nobody calls.
- internal/ui/render/blocks.go: UserBlock, ToolBlock — replaced by
inline rendering elsewhere; the test for UserBlock was rewritten to
directly exercise HighlightFileTokens (which is what the test really
cared about).
- internal/ui/commands/commands.go: GetAllCommandNames — no callers.
- internal/ui/message_items.go: NewTextMessageItem,
NewSystemMessageItem + the entire SystemMessageItem type — model.go
uses NewStyledMessageItem instead.
- internal/prompts/loader.go: Deduplicate — the loader does dedup
internally; standalone helper was unused.
- internal/models/cache_options.go: mergeProviderOptions + its
test-only consumer.
- internal/extensions/installer.go: Installer.GetInstalledPackages —
intended for a 'kit ext list' command that was never built.
- internal/extensions/manifest.go: saveManifestToScope,
saveManifestToPath, GetGlobalManifest, GetProjectManifest,
addEntryToManifest, removeEntryFromManifest — package-level
duplicates of *Installer methods. Tests rewritten to exercise the
live Installer methods instead, which fixes a latent path-resolution
inconsistency between manifestPathForScope and Installer.manifestPath
(the former hard-coded paths, the latter respects projectGitRoot).
- internal/extensions/subagent.go: SpawnSubagent + helpers
(generateSubagentID, findKitBinary, subagentJSONOutput). The
subprocess-spawn implementation is unreachable; production code
routes through kit.Kit.Subagent (in-process). Types
(SubagentConfig/Result/Handle/etc.) and the SubagentHandle methods
remain because they are exposed to extensions via Yaegi symbols and
the Context.SpawnSubagent field.
- cmd/root.go: LoadConfigWithEnvSubstitution — one-line wrapper around
kit.LoadConfigWithEnvSubstitution with zero callers.
go test -race ./... passes.
- Stale comment showed ~/.kit/sessions/--<cwd-path>--/ which does not
match the actual encoding (no leading/trailing dashes)
- Update to reflect the real format and point to encodeCwdForDir for
full rules
- Encode cwd via new encodeCwdForDir helper that handles both `/`
and `\` separators and strips characters illegal in Windows
directory names (`: < > " | ? *`)
- Fixes session creation on Windows where the drive-letter colon
produced names like `C:--test` and caused mkdir to fail
- Add regression tests covering Unix paths, Windows drive roots,
secondary drives, mixed separators, and other illegal chars
Fixes#18
Address two review findings on the MCP Tasks PR.
- Config.Validate() now rejects unknown tasksMode values with a clear
error naming the server and bad value. Without this a typo (e.g.
"alwasy") was silently downgraded to "auto" by the runtime parser.
- Kit.Subagent() now propagates the parent's six MCP task options
(mode map, timeout, TTL, poll interval, max poll interval, progress
callback) onto the child via a new inheritMCPTaskOptions helper.
Without this, child subagents always saw default polling and no
progress feedback regardless of parent configuration.
The propagation logic lives in a helper so the test exercises the real
code path instead of duplicating it; future task fields only need to be
added in one place.
- README: add tasksMode YAML example and MCP Tasks subsection with
SDK opt-in snippet
- pkg/kit/README: add MCP Tasks subsection covering MCPTaskMode,
progress callbacks, and List/Get/Cancel methods
- www/configuration: document the tasksMode server field plus a
per-mode behaviour table
- www/sdk/options: extend the Compaction & MCP table with the six
new Options fields and add a top-level MCP Tasks section
- www/sdk/overview: add a brief MCP Tasks section between MCP
prompts/resources and Context & compaction
All examples verified against the public symbols in pkg/kit/mcp_tasks.go;
docs site builds cleanly via npx tome build.
Implement Phase 1 of the MCP Tasks spec so long-running tools/call
requests can run asynchronously, survive proxy timeouts, and be
cancelled mid-flight.
- connection pool now advertises mcp.NewTasksCapability() during
initialize and captures the InitializeResult so callers can detect
per-server task support
- new MCPServerConfig.TasksMode (auto|never|always, default auto)
parsed from both new and legacy mcp.json shapes
- ExecuteTool augments tools/call with TaskParams when policy and
capability allow, polls tasks/get / tasks/result until terminal,
and best-effort tasks/cancel on context cancellation
- new MCPToolManager methods: SetTaskConfig, ListServerTasks,
GetServerTask, CancelServerTask
- public SDK surface in pkg/kit: MCPTask, MCPTaskStatus, MCPTaskMode,
MCPTaskProgress, MCPTaskProgressHandler, plus Options fields
(MCPTaskMode, MCPTaskTimeout, MCPTaskTTL, MCPTaskPollInterval,
MCPTaskMaxPollInterval, MCPTaskProgress) and Kit.{List,Get,Cancel}
MCPTask methods
- works around two upstream mcp-go v0.51.0 parser bugs
(ParseCallToolResult rejects task responses; ParseTaskResultResult
looks for content under a non-existent nested key) by decoding the
wire shape directly via the transport
- defaults to MCPTaskModeAuto so servers that don't advertise task
support behave exactly as before
Fixes#21