* feat: resolve @prompt_name saved-prompt references in workflow step prompts
Workflow step prompts, the GitHub review-watch task prompt, and the CI
auto-fix prompt now resolve "@name" references to saved custom prompts
(Settings > Prompts) at invocation time, appending the referenced
content as hidden context while keeping the mention visible - matching
how chat messages and message_task already resolve @mentions. Editing
a saved prompt now propagates to every workflow step that references
it without needing to update each step individually.
Adds a Monaco "@" completion provider to the step-prompt and
review-watch editors, alongside the existing "{{placeholder}}"
provider.
* fix: address PR review findings on prompt-reference expansion
- Make AppendReferenceExpansions idempotent: a prompt that already
contains a previously-appended expansion block is returned unchanged,
since the block's own "### @name" headers would otherwise be
re-scanned as fresh mentions on a second call.
- Skip "@name" expansion entirely for CLI-passthrough sessions: their
prompts are written raw to a PTY with no <kandev-system> stripping,
so the hidden block would otherwise leak into the terminal verbatim.
* fix: fail safe on passthrough lookup error in startTask
A session-lookup error while resolving isPassthrough for the Kandev
MCP wrap and prompt-reference expansion previously defaulted to
false (non-passthrough), risking a hidden <kandev-system> block
leaking into a real passthrough session's PTY on a transient error.
Extracted resolveIsPassthroughForLaunch, which now fails safe by
defaulting to true (skip injection) and logs a warning.
* fix: scope expansion idempotency marker to system blocks
The idempotency guard added in a prior commit checked for the
expansion marker anywhere in the prompt, which could falsely
suppress a legitimate first-time expansion if caller-controlled
text happened to contain that literal phrase. Add
sysprompt.HasSystemMarker, mirroring the existing HasKandevContext
pattern, so the marker is only recognized inside a formatter-
produced <kandev-system> block. Also strengthens a passthrough test
assertion that didn't actually distinguish the snapshot path from
the fail-safe default.
* fix: match exact expansion-block prefix in idempotency guard
The prior scoping fix accepted the marker anywhere inside any
<kandev-system> block, which could still false-positive against an
unrelated system block (e.g. a plan-mode prefix or MCP context
wrap) that happens to mention the marker phrase without it being
the block's own content. Check for the exact literal prefix this
package's formatter produces instead: the opening tag immediately
followed by the marker, with nothing in between.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
* feat(web): add app status bar
* feat(web): add app status bar visibility toggle
* feat(web): persist status bar item order
* fix(web): refine app status bar behavior
* fix(web): polish app status bar presentation
* test(web): split plugin slot coverage groups
* fix(web): address status surface review feedback
* fix(web): stabilize status bar overlay tests
* feat(plugins): add main-top-bar slot for the default app top bar
Plugins could only contribute to the session/chat top bar via the
chat-top-bar slot. Add a main-top-bar slot, mounted on the default
app top bar (Home / Kanban / Tasks) across desktop, tablet, and
mobile headers, so plugins can surface workspace-scoped status or
actions outside of a task session.
slotProps: { workspaceId, workspaceLabel, currentPage } — no task or
session ids, since the bar isn't scoped to a task.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: narrow main-top-bar currentPage slot prop to a literal union
Addresses Claude Code Review feedback on PR #1841: MainTopBarSlotProps.currentPage
was typed as a plain string even though every caller only ever passes
"kanban" | "tasks" (matching KanbanHeaderProps.currentPage). Narrow the
plugin-facing contract to match so plugin authors get real type safety.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test(e2e): cover the main-top-bar plugin slot
Extend the kandev-plugin-e2e fixture bundle to also register a
main-top-bar slot component, and assert it renders on the default
app top bar (Home) alongside the existing task-sidebar coverage.
Verified locally: apps/web e2e plugins.spec.ts passes, plugin-fixture
Go tests pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* docs: add plugin system guide, authoring tutorial, and manifest reference
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: update plugin docs for host data API, config, and nav additions
Cherry-picked plugin docs (d853b66b8) were written before four follow-up
PRs merged: typed capability-gated Host data API (#1753, ADR 0043),
richer frontend SDK (#1759), per-plugin settings pages (#1761), and
sidebar Integrations nav items (#1767). Re-verify against the merged
code and bring the docs current:
- plugins.md: per-plugin settings/config page, expanded Host RPC list,
updated capability vocabulary (api_read/api_write), sidebar nav note.
- plugins-authoring.md: GetConfig/GetSecret/SetSecret/DeleteSecret and
the six Host data readers, registerRoute options/PluginPageChrome,
NavItem icon/integrations section, host.api.baseUrl/navigate, and the
full host.ui component list.
- plugins-manifest.md: corrected api_read/api_write semantics (now
enforced, not reserved), config_schema validation/secret-masking
behavior, and new Host data API / config-schema sections.
- configuration.md, feature-status.md: document the KANDEV_FEATURES_PLUGINS
flag alongside the existing Office flag.
- coverage.json: new plugin-system coverage area so the public-docs
validator accounts for the three new pages.
* docs: fix plugin config route, signing claim, and data-dir layout
Address automated review findings on PR #1774:
- plugins-manifest.md: PATCH route for config is /api/plugins/{id}, not
/api/plugins/{id}/config (only GET uses the /config suffix).
- plugins-authoring.md: host.store is not read-only — setState is
genuinely exposed and writable; correct the misleading inline comment.
- plugins.md: signature verification is not currently wired up in
production (pkgtar.VerifySignature is nil, never assigned outside
tests), so every package installs and is reported as unsigned today —
correct the 'kandev verifies it' claim in both the install pipeline
step and the signed/unsigned section.
- plugins.md: KANDEV_PLUGIN_DATA_DIR is <id>/data, a sibling of the
<version>/ directory (shared across versions), not nested inside it —
fix the on-disk layout diagram.
* docs: mark plugin docs as experimental
The plugin system is feature-flagged and off by default in production
(KANDEV_FEATURES_PLUGINS). Mark all three plugin pages with
status: experimental frontmatter, matching the k8s.md convention for a
page that is wholly about an experimental feature.
* docs: add plugin UI screenshots and a capture spec
Embed four operator-facing screenshots in the plugin docs (the Plugins
list, the install dialog, a per-plugin settings page with its manifest
card, and a native plugin route). Captured from an isolated e2e backend
with the kandev-plugin-hello reference example installed.
Add e2e/tests/plugins/plugins-docs-screenshots.spec.ts to regenerate them:
it reuses the plugins e2e harness (isolated backend, plugins flag on) and
is skipped unless CAPTURE_DOCS_MEDIA=1, so it adds no CI cost. Defaults to
the repo's own e2e fixture package; override DOCS_PLUGIN_* to point at the
polished hello example (as the committed captures were taken).
* docs: fix plugin screenshot paths for the landing publisher
The Cloudflare preview build failed because markdown images under
docs/public/media/ compile to build-time module imports that the docs
site can't resolve — media/ is reserved for <DocsVideo> assets. The
landing publisher only rewrites and copies images referenced under
screenshots/ (README convention). Move the four plugin captures to
docs/screenshots/plugin-*.png and reference them as ../screenshots/*.png,
and update the capture spec to write there.
* docs: convert plugin how-it-works diagram to mermaid
The hand-drawn ASCII pipeline rendered with broken alignment and stray
syntax highlighting on the docs site. Replace it with a mermaid flowchart
(same convention as architecture.md), which renders as a clean SVG.
* docs: give plugins its own nav section
* docs: clarify plugin tools are declared/listed, not agent-invocable yet
Plugin tools[] are validated and exposed via GET /api/plugins/tools, and
the gRPC InvokeTool transport is implemented and tested, but nothing in
production calls it — no agent, MCP bridge, or HTTP route. The docs
overstated this as agent-invocable. Correct the Plugin interface comment,
the manifest tools[] rows, the how-it-works diagram, and the example
plugin description, and add a note in both the authoring and manifest
pages that InvokeTool stays unused until the agent-tool wiring ships.
* docs: clarify webhook relay in how-it-works diagram
The diagram collapsed two hops into one node, reading as if the plugin
receives the external HTTP POST directly. Split it: an external caller
POSTs to kandev's webhook route, which relays the request to the plugin
over the gRPC HandleWebhook method (the plugin serves no HTTP itself).
Also label the DeliverEvent/InvokeTool/HandleWebhook edges by their real
proto method names.
* docs: note the plugin backend is any go-plugin gRPC executable, Go SDK only
* docs: remove plugin tools from the docs
Plugin tools[] overlap with MCP (kandev's established agent-tool surface)
and the invocation path was never wired to agents. Rather than document a
half-feature that implies plugins are an alternate MCP, drop it entirely:
the Plugin interface (InvokeTool), the manifest tools[] block and field
rows, the how-it-works diagram branch, and the example-plugin mention.
Removal from the backend code is tracked separately.
* docs: replace how-it-works mermaid with a designed diagram image
The mermaid flowchart rendered poorly. Replace it with a hand-authored
HTML/CSS architecture figure (install pipeline + the runtime gRPC
interactions), rendered to docs/screenshots/plugin-architecture.png. The
diagram source lives in the docs-screenshot capture spec (skipped unless
CAPTURE_DOCS_MEDIA=1) so it regenerates deterministically at 2x.
* docs: make the how-it-works figure a real box-and-arrow diagram
The previous figure read as a list of rows. Replace it with a hand-drawn
SVG architecture diagram: the install pipeline feeds a spawned plugin
subprocess, then kandev and the plugin communicate over one gRPC
connection (DeliverEvent, HandleWebhook, and the plugin's Host API
callbacks), with the external webhook caller and the browser SPA as
edge nodes. Source lives in the capture spec's ARCH_HTML.
* docs: make the how-it-works diagram a clean monochrome flowchart
Drop the multi-color component diagram for a calm top-down flowchart
(mermaid's shape, drawn properly): install → verify → extract → spawn,
branching into event delivery and webhook relay, converging on the Host
API callbacks, with an optional native-UI step. Grayscale, no accent
colors. Source stays in the capture spec's ARCH_HTML.
* docs: dark plugin diagram + space out the two page images
- Make the how-it-works flowchart a dark rounded card (matches the docs
page in dark mode; reads as an embedded card in light). Rendered with
omitBackground so the corners are transparent.
- Move the Settings > Plugins list screenshot out of the intro (where it
sat right before the diagram) down into the "Installing a plugin"
section, so text separates the two images.
* docs: update install-dialog screenshot for aligned footer actions
Recaptured after fix(plugins): align install-dialog footer actions
(#1780) — Cancel and Install now sit together in one right-aligned
footer row instead of the Install button being stranded under the
dropzone.
* docs: account for /settings/system/storage in coverage.json
The storage settings route (added by #1699) was never recorded in the
public-docs coverage inventory — the docs validator is path-filtered to
docs/public/** so it didn't run for that PR. This docs PR is the first to
trip it. Add the route to system-health-and-maintenance.
* docs: review fixes — remove stale tools refs, tighten accuracy
Review pass against the now-merged main (tools removed in #1776):
- extending-kandev.md: drop "serves agent-invocable tools" from the plugin
overview (the feature no longer exists).
- plugins-manifest.md: the legacy endpoints block is health/events/webhooks
only (no tools path); and the `signed` field is always false today since
signature verification isn't wired.
- plugins.md: install validation covers the categories/UI-surface enums (not
a "capability vocabulary"); AutoMTLS secures the channel on all platforms,
not just Windows; and status vs signing are separate badges.
* docs: explain what Host state is (GetState/SetState)
The authoring guide listed the state RPCs but never said what 'state'
means. Add a plain-language explanation: a per-plugin, kandev-persisted
key/value store keyed by (scope, scopeID, key) holding JSON objects,
namespaced per plugin, scoped to instance/workspace/task/agent, and how it
differs from the writable data dir. Make the example a real increment.
* docs: note NavItem section:'settings' is not rendered
A NavItem with section:'settings' is accepted by the type but no sidebar
section renders it (plugin-nav-items renders main/default, the Integrations
sections render 'integrations'); annotate that settings pages use
registerSettingsRoute. Addresses repeated review feedback.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(web): improve mobile task and kanban navigation
* feat(web): refine mobile task navigation
* fix(web): address mobile navigation review feedback
* test(web): cover task title field modes
* fix(web): keep mobile board navigator open across workflows
* fix(web): address mobile workflow review feedback
* feat(web): surface Integrations section in mobile nav
Add a mobile-reachable entry point for the sidebar Integrations section.
The global AppSidebar is desktop-only (hidden md:block), so on a phone
viewport neither the first-party integration links nor plugin nav items
with section: "integrations" were reachable. Wire the pre-existing
MobileIntegrationsSection into the kanban hamburger sheet and extend it
to render plugin items (gated on the plugins flag), matching the desktop
IntegrationsSection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(web): track registered plugin ids for cleanup in mobile integrations tests
Route all plugin nav-item registrations through a tracking helper and
unregister the collected ids in a suite-scoped afterEach, so a future test
that registers a new plugin id cannot leak items into subsequent tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(web): stabilize sidebar toggle animation
Keep Dockview panes fixed while sidebar chrome animates between its open and closed widths.
* ci: align docs workflow pnpm version
Match pnpm/action-setup to the workspace packageManager pin.
* fix(web): address sidebar review feedback
Harden sidebar E2E assertions and restore the shared repository fixture after mutation.
* feat(plugins): per-plugin settings pages driven by manifest config_schema
Settings > Plugins rows now open /settings/plugins/:id — a detail page
showing the manifest plus a settings form generated from the plugin
author's config_schema. Secret fields (secret: true / format: password)
are masked on the operator API and kept on masked re-submits; a running
plugin restarts on save and reads its config (cleartext secrets included)
through the new Host GetConfig gRPC RPC.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): write plugin config file owner-only (0600)
Operator config can now carry cleartext secret values (config_schema
fields marked secret), so <id>.config.yml must not be world-readable —
match the 0600 mode the record file already uses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(plugins): vault-backed plugin secrets — Get/Set/DeleteSecret RPCs and encrypted config secrets
Plugins get vault-backed secret primitives on the Host API
(GetSecret/SetSecret/DeleteSecret, capability-gated, namespaced to
plugin:<id>㊙️<key> so a plugin can only touch its own entries;
purged on uninstall). Secret config_schema fields now live in kandev's
AES-256-GCM vault too — <id>.config.yml persists only a vault reference,
and the Host GetConfig RPC resolves it back to cleartext for the plugin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(plugins): serve PR screenshots from the media/pr-1761-screenshots ref instead of the repo
The PR body now points at raw URLs pinned to an unmerged media ref, so
the binaries never land in main (PRs are squash-merged; only the branch
tip tree merges).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): address review feedback on settings validation and save flow
- integer form fields reject non-integral input instead of parseInt-truncating
- enum selections keep their schema-declared types (numeric enums round-trip)
- backend enum validation normalizes int-vs-float64 before comparison
- maskSecrets masks any non-zero secret value regardless of runtime type
- save flow splits PATCH from refetch: a refetch failure no longer reports a
successful save as an error, and typed secrets are masked on screen
- optional enum fields gain a 'Not set' option so they can be cleared
- resolveConfigSecrets copies before mutating; restart-failure SetStatus
errors are logged; SDK test fake returns a non-nil empty config map
- add missing tests: usePluginConfigForm hook, getPluginConfig client,
integer/enum regression cases
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): harden secret config invariants from review
- reject non-string values on declared secret fields at validation time,
closing the path where a numeric/boolean secret bypassed the vault and
persisted cleartext in the config file
- delete removed secret fields' vault entries only AFTER the config commit
succeeds, so a failed SetConfig never leaves the still-current config
referencing a deleted vault entry
- uninstall fails closed: the plugin's vault namespace is purged before any
destructive step, and a cleanup failure aborts the uninstall (retryable)
instead of letting a reinstall inherit stale secrets
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): failure-atomic config secrets, quiesced uninstall purge, ctx propagation
- UpdateConfig is now failure-atomic for secret overwrites: storeConfigSecrets
snapshots each vault entry before writing and returns a rollback that
restores prior values (or deletes newly-created ones). A vault.Set failure
mid-loop or a failed SetConfig commit rolls back, so a request reported as
failed never changes the value a still-current config ref resolves to.
- Uninstall stops the plugin process BEFORE purging its vault namespace, so
the plugin can no longer race the cleanup with a SetSecret between ListIDs
and the deletes; the purge stays fail-closed and retryable.
- Thread the request context through Uninstall -> deletePluginSecrets instead
of context.Background(), per the pass-context-through-chains convention.
- Consolidate the 'secret not found:' prefix check into isSecretNotFound
(host.go + service rollback share one definition).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): detach rollback context, fail closed without a vault, reconcile uninstall status
- vault rollback closures now run on context.WithoutCancel(ctx): a request
cancelled mid-save (browser closed) can no longer abort the rollback and
leave the vault holding new values while the unchanged config still points
at them (Greptile P1).
- storeConfigSecrets fails closed (errSecretVaultRequired) when a plugin
declares secret config fields but no vault is wired, instead of silently
persisting the secret in cleartext.
- vaultRestoreFunc distinguishes not-found from a genuine Reveal error: a
backend read failure aborts the update before writing rather than risk a
rollback that deletes a real secret; rollback now returns errors, joined
into UpdateConfig's failure so an inconsistent vault is surfaced, not just
logged.
- Uninstall reconciles lifecycle status when a secret-purge failure aborts
after the process was stopped: a previously-running plugin transitions to
error and notifies observers instead of being reported active with no
process running.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): detach removed-secret cleanup ctx and clear stale save dirty flag
- cleanupRemovedConfigSecrets now runs on context.WithoutCancel(ctx), matching
the rollback path, so a client disconnect right after the config commit can
no longer cancel the delete and orphan now-unreferenced vault entries.
- tests: assert GetConfig succeeds before treating an empty map as proof that
nothing persisted (a swallowed read error would be a false pass).
- web: on a post-save refetch failure, rebase initialValues onto the masked
snapshot so the saved form no longer reads as dirty.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): fail tests on discarded GetConfig errors, drop setState-in-updater
- config_test: capture and fatal on GetConfig errors at the four remaining
positive-assertion sites (a nil from a failed read would panic on the next
map access instead of giving a clear failure), matching the earlier fix.
- web hook: compute the masked post-save snapshot from the current values and
apply it to setValues + setInitialValues directly, instead of calling
setInitialValues inside a setValues functional updater (a React anti-pattern
that runs twice under StrictMode).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(plugins): skip validation of a secret field's own vault ref
- validateConfigSchema takes the plugin id and skips a secret field whose
value is that field's config vault ref (an internal storage marker,
already validated when first set). Without this, a field declaring both
secret: true and enum: [...] 400s on every save that keeps the mask,
because the enum check saw the vault ref instead of a user value. Freshly
entered secret values are still validated; a foreign plugin's ref is not
skipped. Covered by unit + service-level regression tests.
- surface mid-loop vault rollback failures in storeConfigSecrets via a
warning instead of discarding them (double-fault observability).
- web: integer inputs get step=1 so browsers flag non-integral input in
place (serializeConfigValues remains the backstop).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(plugins): split config secret tests into config_secrets_test.go
config_test.go had grown past the 800-line backend file-length limit
(revive), failing CI lint. Move the vault-backed config and plugin-scoped
secret tests (plus their fake-vault/store helpers) into a sibling file;
no test logic changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>