cdd0bf8cbd288b354dfd6dbcc5a02364452cbdfa
151 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e9634fa636 | feat: allow detaching subtasks from parent tasks (#1781) | ||
|
|
21f811d5ec | feat: add idle storage maintenance (#1699) | ||
|
|
1ba81a7cd0 |
refactor(plugins): remove the unused tools feature (#1776)
Plugin `tools[]` overlapped with MCP, kandev's established mechanism for exposing tools to agents (`internal/mcp/`), and the `InvokeTool` path was never wired into agent tool sets — the `listTools` handler was listing-only. Rather than maintain a half-built parallel surface, remove it. If plugins ever contribute agent tools, they should feed through MCP, not a separate `InvokeTool` RPC. Removes: - proto `InvokeTool` RPC + `ToolRequest`/`ToolResponse`/`ToolContext` messages (regenerated `*.pb.go` / `*_grpc.pb.go`) - pkg/pluginsdk `InvokeTool` from the `Plugin` interface and `UnimplementedPlugin`, the Tool* Go-native types, and the `RemotePlugin`/`GRPCServer` dispatch - internal/plugins `Service.InvokeTool`, the `listTools` handler + `GET /api/plugins/tools` route, `PluginToolDTO`, and the manifest `Tools` field + tool-name uniqueness validation - frontend `PluginTool`/`PluginToolDTO` types, `listPluginTools`, and the manifest-card tools list - fixture/e2e `InvokeTool` echo impl and the `tools:` manifest block Test doubles that used the "crash" tool to exercise crash-restart now use a "crash" webhook key instead. Internal specs (GRPC-CONTRACT, spec) drop the tool RPC/feature; docs/public was already updated in #1774. Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fae2303db3 |
feat(plugins): per-plugin settings pages driven by manifest config_schema (#1761)
* 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> |
||
|
|
9e484d7e89 | feat: improve ACP model configuration summaries (#1723) | ||
|
|
16da501fc4 | feat(office): route agents through execution profiles (#1725) | ||
|
|
e145d48f22 |
feat: add plugin system with grpc runtime and native ui plugins (#1742)
* feat: add plugin system with grpc runtime and native ui plugins - go plugin backends ship as release tarballs (per-platform binaries, checksums) that kandev installs from url/upload or filesystem sideload+sync, then spawns and supervises via hashicorp/go-plugin (unix socket / loopback tcp+automtls) - strict typed grpc contract (kandev.plugin.v1): DeliverEvent/InvokeTool/HandleWebhook plus a capability-gated host service (state, secrets, emit); author sdk in pkg/pluginsdk; packaging cli in cmd/plugin-pack - native frontend bundles: window.registerKandevPlugin + registry/host loader with dynamic routes, nav items, slots and ws handlers; plugins settings page with install dialog, sync button and enable/disable/uninstall - gated behind the plugins feature flag (prod off, dev/e2e on); covered by unit and integration tests plus playwright e2e against a real packaged fixture plugin Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): hydrate base app context on plugin routes Plugin-owned SPA routes classify as RouteUnknown, so the boot payload shipped only feature flags — no workspaces, workflows, or kanban context. Native plugin UI (e.g. host.ui.TaskCreateDialog) then had an empty store. Hydrate the same base context as the Home route for unknown SPA routes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(plugins): expose TaskCreateDialog on host.ui Lets a native plugin open kandev's real create-task modal (prefilled via initialValues) instead of POSTing directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): harden filesystem/URL sinks against CodeQL findings Add sink-level guards on the plugin trust boundary: securejoin containment on archive extraction (zip-slip), plugin-id validation before every fs_store path, http/https scheme validation on install-from-URL, and non-interpolated ws-handler error logging. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): address review findings (races, resource caps, lifecycle) - serialize per-plugin startup to close a TOCTOU double-spawn race, and let a plugin restart after exhausting attempts - bound plugin startup with an explicit 30s timeout - reject backslash paths and cap decompressed bytes by actual size in pkgtar; only mark a package Signed when a verifier actually ran and passed - delete plugin_state on uninstall; validate webhook keys against the manifest and cap webhook body size; restart an active plugin when a new version installs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): sanitize plugin-id path sinks for CodeQL Pin the plugin id to a single path segment with filepath.Base at recordPath/ configPath and pkgtar.Remove — a barrier the taint analysis recognizes. Behaviour-identical for valid ids (already validated by safePluginID); clears the go/path-injection alerts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): inline plugin-id validation at path sinks for CodeQL filepath.Base does not strip '..', so it is not a path-injection barrier. Call the recognized safePluginID / id-pattern guard inline in Delete/GetConfig/ SetConfig and pkgtar.Remove — the same barrier that already cleared the Get sink. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): address code-review findings (backend) - add capability-gating tests for the Host RPC service (was untested) - close a stop-during-start orphan race so Disable/Uninstall can't leave a supervised subprocess behind - on failed upgrade, remove only the new version dir (not all versions + data); restart the prior version - validate manifest version and reject ids ending in .config (record-file alias) - make Deliverer.Refresh non-blocking and cancel in-flight delivery on worker stop - clamp out-of-range webhook status, skip-and-warn corrupt records in List, write records atomically, semver-aware sideload version pick Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): address code-review findings (frontend) - wrap plugin routes + settings routes in a shared PluginErrorBoundary so a throwing plugin page shows a fallback instead of white-screening the SPA - fix disable/re-enable in one session: keep the cached registration and re-run initialize instead of relying on a cached ES-module re-import - prefix root-relative bundle/style URLs with apiBaseUrl (split-origin/Tauri) - surface partial-install warnings via toast.warning instead of a bare success - dedupe/remove injected style links on unload; gate PluginNavItems on the flag Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(plugins): serialize per-plugin lifecycle + guard config writes - add a per-plugin keyed lock in Service so concurrent Enable/Disable/Install/ Uninstall/UpdateConfig for one id are serialized (no status-machine race) - take s.mu in SetConfig/GetConfig and write config atomically - bound each plugin initialize() with a timeout so one hung bundle can't stall the rest of the boot load Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(plugins): split service_test.go to satisfy file-length lint Move the install/URL test group into service_install_test.go; no logic change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: build and ship the e2e fixture plugin package to E2E shards The plugin E2E specs' global-setup requires apps/backend/.build/kandev-plugin-e2e-1.0.0.tar.gz. Build it in the E2E build job, upload it as an artifact, and download it in both the regular and container shard jobs. 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> |
||
|
|
ea84b913b4 |
feat: choose worktree when opening IDE on multi-repo tasks (#1743)
* feat: choose worktree when opening IDE on multi-repo tasks On multi-repo tasks the open-in-IDE button always opened the first worktree. The editors menu now shows a worktree picker (repo + branch) when a session has multiple worktrees, and each editor in the dropdown expands into a per-worktree submenu. - open-editor/open-folder accept an optional worktree_id; empty keeps the first-worktree fallback so existing clients are unaffected - session DTOs now serialize the full worktrees list instead of only the flattened first-worktree fields - useSessionWorktrees unions API worktrees with the WS-populated list - new Playwright spec covers the picker on a real multi-repo session * fix: address review feedback on worktree picker - fall back on empty-string worktree fields, not only null/undefined - bind optional open-folder body via EOF check instead of ContentLength so chunked requests keep their worktree_id - wrap disabled editor buttons in a focusable span so tooltips work - add hook test asserting worktreeId is forwarded as worktree_id * fix: map editor service errors to HTTP status, drop e2e sleep - open-editor/open-folder return 404/400/409 for known service errors (unknown worktree or editor, invalid config, unavailable editor) instead of a blanket 500 - replace the picker spec's 750ms sleep with an explicit composer click so the autofocus race is deterministic --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
3c869b7bad | feat(chat): lazy load shell command output (#1739) | ||
|
|
9381a6a9f7 |
feat(mcp): session spawning, sibling messaging, and renameable tabs (#1734)
* fix(scripts): launch dev-isolated backend via __backend subcommand The kandev binary became a bundle launcher with the restart supervisor (ADR 0019) and exits without KANDEV_BUNDLE_DIR when invoked bare; the isolated dev instance must run the backend entrypoint directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(mcp): session spawning, sibling messaging, and renameable tabs - spawn_session_kandev spawns an additional agent session on an existing task (default: the caller's own) via LaunchSession(IntentStart), with optional agent_profile_id (defaults to the spawner's profile) and a session name; the initial prompt carries spawner attribution and reply instructions. - message_task_kandev accepts an optional session_id to target a specific session, enabling same-task sibling messaging. Explicit targets are pinned through dispatch so the idle-path primary-session re-resolution cannot reroute a sibling message back to its sender (regression-tested). - task_sessions gains a name column (migration ordered after the agent_execution_id-drop rebuild), a session.rename WS action, and inline tab rename UI; custom names take precedence over derived agent/model labels across the tab, agents dropdown, mobile list, chat header, and dockview panel titles. - Inter-session messages stamp sender_session_name into metadata so the sender badge shows which session sent the message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): address review findings on session spawn/rename/messaging - include name in task-session DTOs (list + full, no omitempty) so persisted tab names survive list hydration and clears propagate - sanitize sender task/session ids embedded in <kandev-system> blocks - reject cross-workspace spawn targets; require sender_task_id - truncate session names by runes, not bytes - distinguish not-found from internal errors in message target lookup - guard TabRenameInput blur-commit after Enter/Escape resolution - cap optimistic rename length client-side and toast on rename failure - add tests: spawn launch-failure and cross-workspace paths, renameSession API client Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(mcp): round-two review fixes for spawn/rename lint and hardening - extract spawn validation/authorization helpers (cyclop/nestif limits) - make stripSystemTag replace until stable to block nested tag evasion - load session before rename so a durable rename can never skip its broadcast; surface lookup failures to the caller instead - drop redundant sessionLauncher nil check (guarded registration) - add maxLength to the rename input matching the server cap - add tests: nested sanitizer evasion, spawn rename-failure tolerance 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> |
||
|
|
2a1f7c2edf |
feat: sync workflows from a configured github repo (#1721)
* feat: sync workflows from a configured github repo Per-workspace setting pointing at a GitHub repo directory of portable workflow export files. A background poller fetches it on an interval and creates, updates, or deletes the matching workflows; a force-sync endpoint bypasses the content-hash check. Synced workflows carry source/source_path provenance and coexist with manual ones; changes that cannot be applied safely (steps or workflows that still hold tasks, ambiguous step names, unparseable files) surface as warnings in the new settings section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): fill workflow sync form from a pasted github link A new Repository link field parses plain repo URLs, SSH remotes, and tree/blob links (which also carry branch and directory) into the structured owner/repo/branch/path fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): simplify workflow sync form to link, branch, and interval The repository link is now the only repo input: owner, repo, and directory resolve from the pasted URL (echoed as a summary under the field), and a stored config renders back into a canonical tree link via buildGitHubRepoUrl. Branch and poll interval stay individually editable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): collapse workflow sync into a button, dialog, and status card The Workflows settings page now shows only a GitHub Sync button by default; the link/branch/interval form lives in a dialog that closes on successful save or removal. Once configured, a compact status card (mirroring the GitHub connection-status card) shows the synced repo, branch, directory, last result, warnings, and a Sync now action. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): move github sync button into the workflows page header The GitHub Sync trigger now stacks under the Workspace settings button on the top right, and the extra separator around the sync area is gone — unconfigured workspaces show no sync chrome at all, configured ones show just the status card above the workflow list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): move github sync button next to the workflow actions GitHub Sync now sits in the Workflows section toolbar alongside Export All, Import, and Add Workflow; the status card renders inside the section above the workflow list. The toolbar moved to its own component to keep the page under the file-size limit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): separate sync status card from the workflow list Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(web): align sync status card with the workflow list gutter Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: repair local edits to synced workflows on periodic sync Periodic syncs previously skipped applying when the repo content hash was unchanged, so local UI edits to a synced workflow were only reverted by a manual Sync now. Every sync now reconciles against the repo; the applier compares each step against its definition and only writes (and broadcasts) actual differences, so a no-drift sync stays silent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: make github-synced workflows read-only Workflows with source=github reject UI mutations: workflow rename, description, agent-profile, and delete return 409 from the task-domain handlers; step create/update/delete/reorder and template application are guarded in the workflow controller. The sync applier writes through the service/repo layer and stays exempt. The frontend disables the card's inputs, save/delete, and the step pipeline editor for synced workflows, with a tooltip pointing at the synced repository. Drift repair on sync remains as the backstop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: optional workflow sync polling and repo-derived branch picker A per-workspace auto-sync toggle (poll_enabled, default on) controls the background polling loop; when off the workspace only syncs via Sync now. The dialog's branch field becomes a select fed by the repository's actual branches (same loader the task-create dialog uses), falling back to free text until branches load. Existing dev databases gain the new column via an idempotent ALTER. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: release synced workflows when the sync config is removed Deleting a workspace's workflow sync configuration converts its GitHub-sourced workflows back to manual ownership (source=manual), so they become editable instead of being stranded read-only. Release runs before the config row is deleted so a failure keeps the config for retry. The settings page reloads after removal (and after syncs that changed workflows) so cards unlock without a manual refresh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(web): drop branch selector and compact the auto-sync row The pasted link already carries the branch (main by default), so the dialog no longer needs a branch field — the resolved summary echoes it. The auto-sync toggle is now one compact row with the interval inline and a single short helper line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: address PR review feedback Backend: fail-open read-only guard on workflow lookup errors (fixes MCP step handler CI failures); poller Stop holds its mutex through drain; per-workspace lock serializes syncs against config delete/replace; rollback workflow creation when provenance stamping fails; idiomatic duplicate-column migration via db.IsDuplicateColumnError; branch-name validation and a 30-day interval cap; generic 500s with logged detail; WS read-only rejections use CONFLICT with the real reason; e2e reset aborts when sync-config cleanup fails; test hygiene (t.Cleanup, SetMaxOpenConns). Frontend: malformed percent escapes parse as invalid instead of throwing; save disabled during initial load and for invalid intervals; Remove disabled while saving; stale async config responses discarded; delete-dialog confirms re-check read-only; focusable Synced badge; explicit button types; failed polls labeled 'last attempt'; unique warning list keys; test cleanup via afterEach. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: guard workflow sync test fake against concurrent poller access The poller invokes the fake applier from its own goroutine while the synctest test asserts on call counts; -race caught the unguarded reads. Also documents that the per-workspace lock is deliberately held across the GitHub fetch. 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> |
||
|
|
1a1ece3a8d | feat: add repository context to quick chats (#1679) | ||
|
|
55c8b461be |
feat: surface agent subscription usage in settings and chat tooltip (#1697)
* feat: surface agent subscription usage in settings and chat tooltip Adds live subscription rate-limit visibility for Claude Code and Codex: - GET /api/v1/agents/usage lists per-agent utilization windows (plan, percent, reset time) for host agents with subscription credentials; ?fresh=true bypasses the 5-minute cache with a 15s per-provider clamp. - Fixes both existing usage clients: Claude parsed float utilization as int (decode failure) and now reads the richer limits[] array incl. per-model weekly windows; Codex read a nonexistent auth.json field and response headers - now reads tokens.access_token, parses the body windows, and refreshes OAuth tokens on 401. Token refresh persists without dropping unknown credential fields. - Settings > Agents gains a Subscription Usage section; the chat context doughnut tooltip shows the session agent's windows, fetched fresh on hover. - Shared UsageWindowRows component (bars, Good/High/Critical status, reset countdowns) keeps settings, tooltip, and office surfaces consistent. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: recompute agent billing type on every read Subscription billing detection for Claude and Codex was cached with sync.OnceValue for the process lifetime, so logging in with OAuth after the backend started never flipped billing badges or office usage checks until restart. Detection now re-reads the credential files on each call and reuses the usage clients' HasSubscriptionCredentials checks. Also adds a regression assertion that fractional-second resets_at timestamps parse under the RFC3339 layout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address subscription usage review feedback - serialize empty utilization windows as arrays, not null - sanitize per-agent usage errors in the API (raw provider errors are logged server-side; they can carry credential paths/response bodies) - persist refreshed CLI credentials via atomic temp-file rename - keep previous settings usage on transient refresh failure; drop stale tooltip usage (and its memo) when a fresh fetch fails - add controller tests for SubscriptionUsage mapping and fresh propagation Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: coalesce concurrent usage fetches and guard codex token refresh - serialize usage-cache fetches per key with a double-checked lock so concurrent fresh requests make one provider call instead of a burst - reject codex refresh responses with an empty access_token instead of persisting them and wiping working credentials - pin the usage-rows reset fixture 30s under the day boundary so the formatted value is deterministic on fast runners Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: coalesce failed usage fetches with short negative caching Concurrent lookups after a fetch error each retried the provider once the per-key lock rotated. Failures are now cached for 15 seconds (never longer than the caller's staleness bound), so bursts share one error result and the provider gets a breather before the next retry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: keep stale usage through failed refreshes and skip caching cancellations Track the last success and last failure independently in the usage cache: a failed fresh refresh no longer evicts a still-valid success (non-fresh callers keep the stale value while fresh callers share the recent error), and a caller-cancelled context no longer records a negative entry that would penalize other callers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: synchronize concurrent usage-cache tests with a channel barrier Replaces the sleep-widened race window in the coalescing tests with a started/release barrier per the backend testing guidelines, and dedupes the two concurrent-caller tests into one helper. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
8b265d9b82 | perf(web): upgrade Vitest and cap local workers (#1696) | ||
|
|
7bcb10bfef |
feat: unarchive tasks with worktree branch recovery (#1687)
* feat: unarchive tasks with worktree branch recovery Archived tasks can now be restored. Unarchive works for manual/legacy archives (empty cascade id, root-only) and cascade archives. Archive deletes the local branch, so worktree recreate now falls back to fetching the branch from origin, and unarchive writes the old branch back as checkout_branch so a fresh session resumes the prior work. Unrecoverable branches (never pushed) surface a warning. WS archive now routes through the handoff cascade so it stays stamped and reversible. UI: unarchive action on /tasks archived rows and the task top bar. * fix: address PR review feedback on unarchive recovery Forward PRNumber to the recreate fetch so fork-PR heads (only reachable via pull/<N>/head) are recoverable, publish task.updated per task in the cascade unarchive loop so the UI restores without a reload, and re-publish task.updated after a checkout_branch restore since the unarchive event goes out with the pre-restore repository rows. * fix: harden unarchive recovery per review feedback Propagate branch-probe errors instead of misreporting recoverable work as gone, and reserve ErrBranchUnrecoverable for a confirmed-missing remote ref (transient fetch failures keep their own error). Validate the archive root exists so WS/HTTP archive no longer reports success for unknown task IDs, CAS-guard manual unarchive on an empty cascade stamp, detach branch recovery from the request context, and skip checkout_branch restore for multi-branch repos. UI: in-flight guard on the list unarchive button, pluralized unrecoverable-branch toast. * docs: document CAS guard on UnarchiveTask interface contract --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
0f266c319d | feat: link GitHub issues to tasks (#1672) (#1676) | ||
|
|
248e8bf48e | feat: add workflow WIP pull system (#1613) | ||
|
|
fe392e60f5 | feat: support multiple concurrent Sentry instances (#1469) | ||
|
|
3cb965d0fd | feat: add durable plan implement action (#1645) | ||
|
|
3e237e177a |
feat: add agent-authored code walkthroughs (#1647)
* feat: add code-walkthrough backend (MCP tool, service, persistence, WS events) * feat: render agent code walkthroughs as line-anchored cards + editor window * test: add code-walkthrough re-emit mock-agent scenario * fix: rework code-walkthrough UX (floating card, line reveal, minimize, clickable file) * fix: improve code walkthrough editor UX * fix: backfill walkthrough launcher after websocket connect * fix: improve walkthrough requests and editor anchors * fix: clear walkthrough highlights on close * feat: add walkthrough discard and prompt customization * fix: send walkthrough requests through saved prompt * fix: support postgres built-in prompt seeding * fix: address walkthrough review feedback * fix: address walkthrough review follow-ups * fix: harden walkthrough lifecycle races * fix: reuse walkthrough notification cleanup * fix: guard stale walkthrough file opens * fix: abort stale file opens before paint * fix: stabilize github settings e2e readiness * fix: use stable github settings e2e selector |
||
|
|
1ed2c59997 |
feat: persist and expire quick chats (#1612)
* feat: persist and expire quick chats * fix: address quick chat review feedback * fix: resolve quick chat review findings * fix: close remaining quick chat review gaps * fix: avoid repeated quick chat expiration rechecks * fix: guard quick chat expiration deletes * fix: expire abandoned starting quick chats * fix: ignore missing quick chat expiration candidates |
||
|
|
8b90ec2593 | feat: improve office onboarding and workspace flows (#1606) | ||
|
|
164b80ac54 | feat(integrations): single workspace switcher with deep links and copy-config dialog (#1616) | ||
|
|
c3f713e494 | feat: scope GitHub integration settings by workspace (#1572) | ||
|
|
26773b28d1 | feat(automation): delete buttons for individual and all runs in Recent Runs (#1586) | ||
|
|
c18a13c361 | feat(tasks): adopt Office-style /tasks list UX (#1593) | ||
|
|
d451e8f9f8 | feat: filter Jira tickets by project statuses (#1588) (#1595) | ||
|
|
e27a9e84aa | fix: support postgres database stats page (#1589) | ||
|
|
a30dd39566 | fix: avoid stale task worktree repository launches (#1568) | ||
|
|
365a3bdd24 | fix: recreate github review tasks after reset (#1492) | ||
|
|
4d19fb32f1 | feat: link tasks to github references (#1471) | ||
|
|
d58d9d03bb | fix: support issue links in remote task dialog (#1462) | ||
|
|
7ada858f17 | feat: add CI PR automation controls (#1446) | ||
|
|
2a890c7975 | feat: sync task preferences across devices (#1440) | ||
|
|
aee1846cd5 | feat: remove nextjs production runtime (#1389) | ||
|
|
327f7d4ec0 | fix: persist agent error dismissal (#1397) | ||
|
|
61acc82b74 | feat: add reset action for integration watches (#1381) | ||
|
|
fd421848fb | feat: add feature toggles settings (#1372) | ||
|
|
3a3fca7f0e | feat: add resource metrics topbar (#1373) | ||
|
|
010948767a | feat: add file-backed prompt attachments (#1367) | ||
|
|
a03d65fdf8 | feat(sentry): support self-hosted instances via configurable URL (#1320) | ||
|
|
64140ae942 | fix(acpdbg): read claude agent models and modes from configOptions (#1310) | ||
|
|
31d4ba67fa |
perf(stats): split stats endpoint per-section and rewrite GetGlobalStats (#1289)
* perf(stats): split stats endpoint per-section and rewrite GetGlobalStats Stats page used to wait on a single endpoint that ran 7 SQL queries in parallel and rendered nothing until the slowest one returned. Cold first-paint was the bottleneck users actually hit. - Backend: split /api/v1/workspaces/:id/stats into 7 per-section endpoints (global, tasks, daily-activity, completed-activity, agent-usage, repositories, git). Each handler runs one repo call and returns its DTO. - Backend: rewrote GetGlobalStats from 9 independent correlated subqueries into 4 CTEs (task / session / turn / message). Each underlying table is now scanned at most once per request instead of 1-3 times. - Frontend: replaced the monolithic fetchStats + single PanelState with useStatsSections hook (7 parallel fetches, per-section SectionStatus). Each panel renders skeleton -> data -> error independently; light queries (overview, agent usage, git) paint immediately, heavy ones stream in. - Tests: added TestGetGlobalStats_AggregatesAcrossTables to lock the CTE-based query against regressions for turns, user/tool messages, and total duration. * fix(stats): address PR review feedback - Restore cache: "no-store" on section fetches; Gin emits no Cache-Control headers so the browser could otherwise serve stale stats on revisit (Greptile P1, Cubic). - Stop blanking the Overview panel when /git fails: render OverviewCards as soon as `global` is ready and treat `git` as optional, falling back to the Averages card. Updates GitOrAveragesCard / OverviewCards types (Claude). - Fix TaskStatsHasMore off-by-one at exactly taskStatsLimit rows: probe taskStatsLimit+1, slice back to the page size, only flag hasMore on a confirmed extra row (Claude, Cubic). - Rename anyError → firstError with a comment documenting the deterministic insertion-order iteration; clearer signal that callers should use it as "any section failed?" and surface per-section errors in their own panels (Claude). --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
48a621a566 |
fix(utility): surface inference-agent probe status + add refresh endpoint (#1287)
* fix(utility): surface probe status in inference-agents endpoint
GET /api/v1/utility/inference-agents previously filtered out agents whose
host-utility probe didn't reach StatusOK, so a Claude entry with auth or
install issues vanished from the response. The frontend then defaulted
agent_id to "claude-acp", failed to match anything in the list, and
silently rendered an empty Model picker with no explanation.
Include every registered ACP-capable agent in the response with a new
`status` field (and sanitized `status_message`) so the UI can render an
inline note + Refresh button. Add a companion
POST /api/v1/utility/inference-agents/:id/refresh endpoint backed by the
existing hostutility.Manager.Refresh so users can re-probe after fixing
auth or installing the CLI without restarting kandev.
Credential-looking substrings ("api_key=...", "token=...") in probe
errors are stripped before reaching the wire.
Refs #1269, #1263.
* fix(web): show probe status note + Refresh on utility-agent model picker
Previously the Model picker rendered empty and disabled whenever the
selected inference agent was missing from /inference-agents (probe
hadn't reached StatusOK) or advertised no models, with no way for the
user to understand or recover.
Wire the new `status` field through the API client and render an inline
note ("Sign in to Claude to load models.", "Claude CLI is not installed
on this machine.", "Setting up Claude…") with a Refresh button that
calls the new POST /inference-agents/:id/refresh endpoint and merges the
updated agent back into local state — no full refetch, so a slow probe
on a different agent doesn't block the UI.
Both call sites — the create/edit dialog and the Default Model row in
the settings page — render the note. Component-level unit tests cover
the status-to-copy mapping, fallback ("no longer available") copy, and
the in-flight Refresh button disabled state. The e2e test asserts the
auth_required → click Refresh → status:ok state machine end to end.
Refs #1269.
* test(utility): add HTTP integration tests for inference-agents endpoints
End-to-end coverage of the new /api/v1/utility/inference-agents and
/api/v1/utility/inference-agents/:id/refresh routes against a real gin
router with a stateful in-memory host stub.
- ProbeStateMachine: GET (auth_required) → POST refresh → GET (ok+models)
- RefreshUnknownAgent: 404 without invoking the host probe
- RefreshPathFuzz: traversal, null byte, 512-char id — no 5xx, no probe
- ConcurrentRefresh: 50 parallel POSTs under -race, no panic
- StatusMessageSanitizationOnTheWire: 110KB payload + unicode probe
errors — no secret material reaches the wire, unicode prose preserved
Caught the gap unit tests had: the multi-request GET→POST→GET sequence
on a shared cache, and the wire format (Content-Type, JSON shape)
through the real router.
* fix(utility): tighten credential regex + drop Go 1.22 loop-var shadow
Address two inline review comments from claude bot on PR #1287:
- Drop `ia := ia` shadow in httpRefreshInferenceAgent — pre-Go 1.22
pointer-capture workaround, dead weight under Go 1.24+ (per
apps/backend/AGENTS.md).
- Split the credential sanitizer into two patterns so prose without a
real `=`/`:` separator is no longer mangled. The single regex
previously turned "access token was revoked" into
"access token=<redacted> revoked" by consuming the next word as the
value. New shape:
- credentialKVPattern — requires `[:=]` (api_key, token, secret,
password)
- credentialBearerPattern — allows bare whitespace, anchored to
"bearer" only
Existing redaction cases still pass; two new prose cases guard the
regression.
* fix(utility): address cubic + greptile review findings on PR #1287
Round-2 review feedback:
- P0 (cubic): postEmpty called `t.Fatalf` from `go func()` in
TestIntegration_ConcurrentRefresh. Go panics on t.Fatalf from non-test
goroutines. Split into `postEmptyE` (error-returning, goroutine-safe)
and `postEmpty` (main-goroutine wrapper). Concurrent test now
forwards the error through the existing `errs` channel.
- P1 (cubic + greptile): tightened the credential sanitizer regex.
* `api[ _-]?key` now also matches a literal space ("invalid api key=…")
* separators are restricted to `[^\S\n]` so multi-line stderr like
"invalid token\ncaused by network" no longer eats words across lines
Test coverage added for both regressions (and for tab as a valid
horizontal-whitespace separator).
- P2 (cubic): refresh-failure log now feeds `err.Error()` through
sanitizeStatusMessage before zapping it — same wire-side guard, but
applied to server logs too. Closes the "credentials in journalctl"
hole.
- P2 (cubic): InferenceAgent.status is now optional in the TS type and
the status note treats an undefined status with `models.length > 0`
as healthy, so older API responses don't flash a spurious warning.
Deferred (acknowledged in PR replies, not changed here):
- cubic P2 useInferenceAgents drift — the hook is currently called in
both the dialog and the parent section. Drift is bounded by the
dialog's own `onSuccess` refetch and only visible inside an open
dialog. Lifting state to the parent and prop-drilling is the right
fix but touches multiple files; out of scope for this review round.
* docs(utility): fix stale comments flagged by claude review on PR #1287
Two doc-only items, no code change:
- handlers_integration_test.go: TestIntegration_ConcurrentRefresh
comment said "we don't assert on call count" but the test DOES
assert `host.refreshCalls != N` two lines later. Updated to match
reality (singleflight lives in the real Manager, the stub has none
→ every call must reach the stub).
- utility-agents.spec.ts: the ACP-probe E2E said "backend filters out
non-StatusOK agents", which is the exact behavior this PR removes.
Updated to explain the real reason only Mock appears (KANDEV_MOCK_AGENT=only
registers only `mock-agent`).
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
|
||
|
|
d83c1dd982 |
feat(diff): use stored base_branch for diff stats + per-task compare picker (#1273)
* feat(diff): use stored base_branch for diff stats + per-task compare picker
Task-card additions/deletions and the Changes panel were computed against a
hardcoded origin/main → master priority list inside agentctl. For tasks
branched off a release/staging branch this inflated the counts by every
commit since the actual fork point, and the Changes panel grouped unrelated
files alongside the task's real work.
This wires the per-repo task_repositories.base_branch end-to-end into the
agentctl WorkspaceTracker so each repo's diff stats resolve against the
recorded base instead of the integration-branch fallback. A picker on the
Changes panel branch hover card lets the user change the base mid-task; the
new value is persisted via PATCH /tasks/:id/repositories/:repo_id, pushed
live to the running agentctl instance, and the session's cached
base_commit_sha is cleared so the commits panel + cumulative diff refetch
against the new base without a session restart.
Falls back to the existing origin/main → master list for legacy tasks
(empty base_branch) and to the branch tip when no merge-base exists with
HEAD (unrelated histories — local rebase backups, etc.) so the stats and
commits panels stay anchored on the same SHA in every failure mode.
* fix(agentctl): sanitize user-controlled base_branch refs before git exec
CodeQL flagged the picker's user-controlled base_branch payload reaching
exec.Command("git", ..., baseBranch) inside workspace_git_cmd.go as a
command-built-from-user-controlled-source vulnerability. A malicious caller
could smuggle a leading-"-" argument like "-upload-pack=..." that git
itself reinterprets as a flag, even though exec.Command keeps args
separate from the shell.
Adds an IsSafeGitRef sanitizer at every boundary the value crosses on its
way to git: the service write path, the agentctl HTTP handlers
(/workspace/base-branches, /git/log, /git/cumulative-diff), and
WorkspaceTracker.SetBaseBranch itself. Unsafe values are rejected at the
service tier and silently downgraded to the no-override fallback inside
agentctl so a misbehaving caller can't half-break the tracker.
* fix(diff): address PR review blockers and CI lint
- Split UpdateRepositoryBaseBranch into validate/load/apply helpers to drop
cyclomatic complexity below the cyclop limit that gated Run Backend Tests.
- Replace direct m.cfg.BaseBranches reads/writes with getBaseBranches /
setBaseBranches under a dedicated RWMutex. UpdateBaseBranches used to
write under repoTrackersMu while rescan + lazy-subpath callers read under
workspaceTrackersMu, leaving an unsynchronized data race on the map.
- Drop the false 'PR target is auto-wired' claim from the MCP tool
description — GitOperator.CreatePR takes baseBranch as an explicit arg
and never reads the tracker's value.
- Picker no-op guard now compares against currentBase instead of storedBase
so selecting the displayed fallback (legacy task before any picker
change) is treated as no-op instead of triggering a spurious save.
- HTTP handler now maps non-validation service errors to 500 instead of
400, classifying via the existing isValidationError helper plus the new
unsafe-git-ref message.
* fix(diff): regex-based ref sanitiser at every git subprocess sink
CodeQL kept flagging workspace_git_cmd.go:43 even after SetBaseBranch
rejected unsafe values — the taint tracker doesn't follow sanitised stores
through struct fields, so it still saw user input flowing into
exec.Command("git", ..., baseBranch).
Switches IsSafeGitRef to a single regex check (a pattern CodeQL recognises
as a sanitiser barrier) and adds SanitizeGitRef calls inline at every
agentctl call site that splices a user-controlled ref into a git arg:
- workspace_git_status.resolveStoredRef (before the rev-parse probes)
- workspace_git_status.computeBaseCommit (before merge-base/rev-parse)
- workspace_git_status.getAheadBehindCounts (before rev-list)
- agentctl/server/api/git.go runGitLogForRepo + handleGitCumulativeDiff
(before computeMergeBase/GetRevParse on the HTTP TargetBranch)
Behaviour is unchanged for valid refs — only narrows the allowlist (now
[A-Za-z0-9_./-]) so the sanitiser barrier is recognisable to static
analysis. SetBaseBranch's field-store sanitiser stays as defence in depth.
* fix(diff): transform sanitiser at every map read for CodeQL taint flow
CodeQL still flagged workspace_git_cmd.go:43 because:
- handleSetBaseBranches used an IsSafeGitRef bool guard before a verbatim
map[k] = v copy. CodeQL recognises regex-transforming sanitisers but not
bool-guarded passthroughs.
- lookupBaseBranch returned m.cfg.BaseBranches[k] verbatim. Even though
the map was sanitised on write, taint flow across map stores + field
stores loses the sanitised state in the analyser.
Switches both sites to the transforming SanitizeGitRef helper so every
read produces a freshly-sanitised value before it flows to a downstream
git argument. Behaviour is unchanged for valid refs.
* fix(diff): regex transformer sanitiser + guard live push from empty map
CodeQL still flagged the previous SanitizeGitRef because it returned the
input string unchanged on success — an identity passthrough that the taint
tracker doesn't recognise as a sanitiser barrier.
Switches to safeGitRefPattern.FindString so the output comes from the
regex match (not the parameter). IsSafeGitRef now wraps SanitizeGitRef so
the predicate and the transformer share one allowlist.
Also addresses two Claude review blockers:
- collectTaskBaseBranches returns a nil/empty map when the task has no
recorded base_branches; pushing that to agentctl wipes every tracker's
override. Now skipped — the DB row is the source of truth for the next
session launch.
- applyBaseBranchSideEffects now runs on context.WithoutCancel so an HTTP
/ WS request cancel after the task_repositories UPDATE commits can't
leave session.base_commit_sha stale + the live agentctl tracker map
un-pushed, drifting from the persisted row.
* fix(diff): share securityutil.IsValidBranchName + harden error classification
CodeQL still flagged the picker's regex sanitiser as taint passthrough.
Refactors SanitizeGitRef / isSafeBaseBranchRef to delegate to the existing
securityutil.IsValidBranchName helper that agentctl's Rebase / Merge /
RenameBranch already use without being flagged. The shared allowlist also
fixes a Claude review blocker: 'feature/@2024' used to pass the service
validator but get dropped by the agentctl-side check at push time,
silently wiping every tracker's override.
Plus the two error-classification blockers Claude raised on the previous
HEAD:
- WS handler (task_ws_handlers.go wsUpdateTaskRepository) now distinguishes
validation errors (surfaced verbatim) from internal faults (opaque
500-style message). HTTP path already did this via the existing
isValidationError helper.
- MCP handler (handlers.handleUpdateRepositoryBaseBranch) gets the same
treatment via a new isValidationError local to the mcp/handlers
package so raw service error strings stop leaking across the MCP
boundary.
* fix(diff): inline securityutil.IsValidBranchName at every git subprocess sink
CodeQL's interprocedural taint tracking wasn't recognising SanitizeGitRef
as a sanitiser even after it delegated to securityutil.IsValidBranchName
— the wrapper helper + map/field hops between source and sink kept the
flow tainted in the analyser.
Inlines the IsValidBranchName allowlist check (already used inline by
Rebase/Merge in git.go without being flagged) at every call site that
splices a user-controlled ref into a git subprocess argument:
- workspace_git_status.resolveStoredRef (before rev-parse --verify)
- workspace_git_status.computeBaseCommit (before merge-base / rev-parse)
- workspace_git_status.getAheadBehindCounts (before rev-list)
- agentctl/server/api/git.go runGitLogForRepo + handleGitCumulativeDiff
- agentctl/server/api/workspace_base_branches.go handleSetBaseBranches
Behaviour unchanged: same allowlist as Rebase / Merge / RenameBranch
already use. SanitizeGitRef / IsSafeGitRef survive as defence-in-depth
helpers for callers that want a one-line check; they delegate to the same
securityutil helper so the rules stay aligned.
* fix(diff): inline regexp.MatchString at every git subprocess sink
CodeQL still flagged workspace_git_cmd.go:43 even with the inline
securityutil.IsValidBranchName helper — the taint tracker apparently does
not follow into helper functions for the go/command-injection rule on
this path.
Hoists the regex to package-scope vars (safeBranchRefPattern in both
process and api packages) and calls regexp.MatchString directly at each
sink site. Behaviour is identical to securityutil.IsValidBranchName; only
the call form changes.
* fix(diff): SHA-shape allowlist on BaseCommit before downstream git diff
CodeQL still flagged workspace_git_cmd.go:43 because update.BaseCommit
is the OUTPUT of an earlier merge-base / rev-parse call whose argument
list contained a user-controlled branch ref — the analyser treats that
command's output as still tainted even when the input ref was sanitised.
Adds an inline 40-hex SHA-shape allowlist check in enrichWithBranchDiff
right before `git diff --numstat <update.BaseCommit>` so the sanitiser
barrier sits in the same function as the next subprocess call. Behaviour
is unchanged: merge-base / rev-parse already produce SHA-shaped output.
* fix(diff): async tracker refresh + tighten init spread order
Two CodeRabbit/Claude review fixes:
- UpdateBaseBranches stamps each tracker's new baseBranch synchronously
then runs RefreshGitStatus on a background goroutine instead of inside
the picker-save HTTP handler. Per-tracker git subprocesses can each
take ~3-5 calls; serialising them on the request goroutine made the
PATCH response wait for every refresh to finish. The picker UI re-fetches
via the existing WS stream once Refresh emits, so the handler doesn't
need to block.
- updateTaskRepositoryBaseBranch now spreads options.init BEFORE the
required method/body fields so an upstream caller's init overrides
can't replace the PATCH method or the base_branch payload. Matches
the convention deleteTask / moveTask already follow.
* fix(diff): narrow mcp isValidationError + opaque 500 + post-sanitize log count
Three final Claude review threads on the latest commit:
- mcp/handlers isValidationError drops the broad 'invalid' substring so
DB / IO errors whose messages happen to contain 'invalid' don't
misclassify as ErrorCodeValidation.
- task_http_handlers httpUpdateTaskRepository now logs the raw service
error server-side and returns an opaque 'failed to update task
repository' on the 500 path. Stops potential DB / IO detail leakage
through the HTTP boundary.
- workspace_base_branches debug log reports accepted + rejected counts
(post-sanitisation) instead of the pre-filter total so the metric
actually reflects what hit UpdateBaseBranches.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
|
||
|
|
08189ec539 | feat(integrations): add Sentry integration with issue watcher (#1133) | ||
|
|
bf7762d939 | fix(agents): split codex acp flags from agentctl auto-approve (#1253) | ||
|
|
dfaab11959 |
feat: full GitLab integration — parity with GitHub (#1120)
* feat(backend): full GitLab parity — store, service, poller, WS, presets Add the watcher/poll/write-action surface on top of the existing GitLab client + service skeleton: * 5 new SQLite tables (mr_watches, review_watches, review_mr_tasks, issue_watches, issue_watch_tasks, action_presets) + CRUD * Client + Service: MergeMR, GetProjectMergeMethods, GetProtectedBranch, ListUserProjects, SearchProjects, SetMRLabels, SetMRAssignees * Service: review/issue/MR watch CRUD + Check/Trigger, GetStats, CleanupAllReview/IssueTasks (cleanup-policy aware), TriggerMRSync, action-presets CRUD with defaults * Background poller (MR / review / issue loops) wired from main.go * ~35 GitLab WS action constants + handlers.go registrations * Mock controller for E2E seeding (MRs, issues, pipelines, discussions, approvals, branches) * eventBus + taskDeleter + taskSessionChecker plumbed through Service * Event types: GitLabMRFeedback / NewReviewMR / NewIssue / TaskMRUpdated * feat(web): GitLab frontend parity — types, API, store slice, domain hooks Extend the frontend GitLab integration on top of the new backend surface: * HTTP API client expansion (gitlab-api.ts): full CRUD for review/issue/MR watches, MR write actions (merge/approve/labels/assignees), MR files/commits/feedback, action presets, projects autocomplete, stats * Store slice expansion: review/issue/MR watches lists, action presets by workspace, stats + status with loading flags. Properties prefixed with gitlab to avoid collision with GitHub slice * Domain hooks: use-gitlab-status, use-gitlab-stats, use-gitlab-review-watches, use-gitlab-issue-watches, use-gitlab-action-presets — mirror github hook shapes * Type model: ReviewWatch, IssueWatch, MRWatch, ActionPresets, Stats, ProjectMergeMethods, MRApproval, Pipeline, MRFeedback, etc. * Backend controller HTTP routes for watches/presets/write-actions/ projects/stats so the frontend HTTP API client has endpoints to hit All passes: backend (build + vet + lint + tests), web (typecheck + lint + 2400 tests). * fix(gitlab): wire orchestrator event handlers, fix label-filter drop, add tests Address Claude review blockers on the parity PR: 1. Wire orchestrator subscriptions for GitLab review/issue watch events. Add event_handlers_gitlab.go with handleGitLabNewReviewMR / handleGitLabNewIssue, dedup reservation handshake, and task-creator interfaces (mirrors event_handlers_github.go). main.go now calls orchestratorSvc.SetGitLabService so the dedup APIs are usable 2. Fix silent label-filter drop: when a custom_query is set on an issue watch, labels were being appended to the unused filter arg. Fold labels into customQuery (or default filter) so they actually reach GitLab; extracted appendLabelsToQuery helper with explicit handling of pre-existing labels= clauses 3. Switch event payloads to pointer types (mirrors GitHub) so handlers can type-assert pointer events 4. Backend tests: store_watches_test.go (CRUD + reserve/assign for all 4 new tables + presets), service_watches_test.go (label-filter helper, cleanup policy, project normalisation), action_presets_test.go 5. Frontend slice tests: review/issue watch CRUD round-trips, action presets + stats reducer coverage All green: backend (build+vet+lint+tests), web (typecheck+lint+2403 tests). * fix(gitlab): address remaining blockers — cleanup wiring, safe defaults, nil guards Address the second round of Claude review blockers: 1. main.go: wire SetTaskDeleter + SetTaskSessionChecker on services.GitLab (mirrors GitHub). Without these, manual cleanup sweeps would always error with "task deleter not configured" 2. service_cleanup.go: default cleanup policy on transient DB error is now CleanupPolicyNever (preserve tasks) instead of CleanupPolicyAuto (silently delete). Genuine "watch was deleted" path also falls under the safe-side default; user can manually delete the orphan tasks 3. service_watches.go: add nil guards on requireStore() so the 6 MR-watch list/get/delete methods return an errStoreUnavailable error instead of panicking when the SQLite store failed to construct at boot 4. use-gitlab-status.ts + use-gitlab-stats.ts: add per-mount attemptedRef so an unreachable GitLab doesn't trigger an infinite re-fetch loop — the previous useEffect re-ran every render because the failure path left status null, satisfying the !status guard * fix(gitlab): propagate session-check errors, clamp poll interval on update Address remaining Claude blockers: * service_cleanup.go: HasUserAuthoredMessage transient errors are now preserved (return false → skip delete) instead of silently ignored; the alternative was occasionally deleting tasks a user had touched * service_watches.go: extract clampPollInterval helper applying the same bounds (0 → default, <60 → 60) as the create path; both applyReviewWatchPatch and applyIssueWatchPatch use it so user-supplied zero or tiny values via UpdateXxxWatchRequest no longer hammer GitLab * fix(gitlab): clear dupl lint, fix preset retry loop + per-workspace watch cache * service_watches.go: applyReviewWatchPatch / applyIssueWatchPatch get nolint:dupl markers — they share shape but per-domain field validation lives in the create paths, so deduplicating via generics would obscure the contract. Restores backend lint to 0 issues * use-gitlab-action-presets.ts: per-workspace attemptedRef set so a failing preset fetch doesn't retry on every render * use-gitlab-review-watches.ts + use-gitlab-issue-watches.ts: track lastFetchedRef per consumer so a workspace switch triggers a refetch. The slice-level loaded flag is shared across instances and can't double as a per-workspace cache key * fix(gitlab): add nil-store guards across review/issue watch + cleanup paths Round-3 Claude finding: ReviewWatch / IssueWatch / preset / reservation methods still called s.requireStore().X() directly. If NewStore fails at boot (table migration error), the service struct's store is nil and every list/get/update/create/reserve method panics on first request. Apply the same store-nil → errStoreUnavailable pattern from f95a929 to every remaining method: * CreateReviewWatch / CreateIssueWatch * GetReviewWatch / ListReviewWatches / ListAllReviewWatches / UpdateReviewWatch / TriggerReviewWatchAll * GetIssueWatch / ListIssueWatches / ListAllIssueWatches / UpdateIssueWatch / TriggerIssueWatchAll * ReserveReviewMRTask / AssignReviewMRTaskID / ReleaseReviewMRTask * ReserveIssueWatchTask / AssignIssueWatchTaskID / ReleaseIssueWatchTask * lookupReviewPolicy / lookupIssuePolicy (return CleanupPolicyNever) * fix(gitlab): nil-store guards on CheckXxxWatch + DeleteXxxWatch + cleanup paths Round-4 Claude finding: 4 remaining panic sites the previous nil-guard pass missed — these run from the background poller (every 5 min) and the manual delete/cleanup flows, so a boot-time NewStore failure would crash the orchestrator process on first tick. * service_watches.go: add store-nil guards to DeleteReviewWatch / DeleteIssueWatch / CheckReviewWatch / CheckIssueWatch * service_cleanup.go: guard CleanupAllReviewTasks / CleanupAllIssueTasks entry points; wrap the trailing DeleteReviewMRTask / DeleteIssueWatchTask calls in `if store := s.requireStore(); store != nil` blocks so the cleanup succeeds even when the dedup-row delete is unreachable * refactor(gitlab): split service_watches.go to stay under 800-line revive limit CI's --new-from-rev lint caught service_watches.go at 808 lines (limit 800). Split into three focused files: * service_events.go (94 LOC) — publish helpers for MR feedback / new review MR / new issue / watch lifecycle events * service_issue_watches.go (302 LOC) — Issue watch CRUD + Check + Trigger + fetch + helpers * service_reservations.go (59 LOC) — Reserve/Assign/Release dedup handles used by the orchestrator event handlers service_watches.go is now 505 lines and contains MR watch + Review watch only. No behavioral change. * fix(gitlab): address inline code-review feedback Address actionable inline comments from Claude/CodeRabbit/cubic/Greptile: * appendLabelsToQuery: use url.ParseQuery for exact key match instead of strings.Contains; previously false-matched keys like mylabels= and silently dropped the watch's labels (test added) * fetchReviewMRs: drop dead `filter = watch.CustomQuery` assignment — SearchMRs's buildMRSearchQuery returns customQuery verbatim and ignores filter when customQuery is non-empty * store.go: add workspace_id indexes on gitlab_review_watches and gitlab_issue_watches (the 5-min poller and HTTP list endpoints did full table scans) * mock_client.SearchProjects: switch from == to case-insensitive Contains so partial-query autocomplete returns the seeded project, matching the doc comment * Poller: add sync.Mutex to guard `started` field; Start/Stop are now safe to call concurrently (go test -race would have flagged the previous read/write race) * Add ErrWatchNotFound sentinel; controller_watches.go maps it to HTTP 404 via httpRespondError helper. Update/Trigger handlers previously returned 500 for missing watches, hiding the distinction from real server faults * service_events.go: log Publish errors on watch lifecycle events instead of swallowing them silently * handlers.go: reject malformed JSON payloads in list-style WS handlers rather than silently falling through to broader list/search behavior * event_handlers_gitlab.go: remove dead gitlabRepoSlug helper + blank-identifier suppression * ci: re-trigger to clear flaky E2E shard 3 * fix(gitlab): address final review round — nil guards, store merge, logs, body validation - CheckMRWatch, UpdateReviewWatch, UpdateIssueWatch: nil guards for store/req - handlers: wsNewDiscussionNote rejects empty body - action_presets: Update reads raw stored row (no default freezing); Reset validates workspace_id - service_search.GetStats: log Warn per sub-call failure before zero - event_handlers_gitlab: log Release* dedup-row errors with context - web/store.ts: post-slice merge restores every GitLab sub-state field; extract buildStateOverrides helper - web/default-state.ts: add GitLab fields to defaultState + mergeInitialState via mergeGitLabFields helper - web/gitlab-api.ts: UpdateXxxWatchRequest now Omit workspace_id Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
041a19f060 |
feat: service-managed UI self-update (#1210)
* feat: add service-managed self-update * chore: add temporary self-update test scripts * chore: simplify temporary script markdown fences * chore: run frontend in temporary self-update scripts * fix: show self-update job progress after apply * chore: add real npm self-update test script * fix: isolate npm self-update prefix * fix: detect npm service installs from bin shim * chore: reset service before real npm update test * chore: expand real npm status diagnostics * fix: preserve service home during self-update * fix(cli): tee self-update helper output to a log file * fix(cli): avoid launchd bootstrap race when reloading the macOS service * fix(backend): run macOS self-update helper once via transient launchd plist * fix(web): show self-update progress and lock the apply button until the update lands * fix(cli): reinstall via the kandev wrapper on Homebrew self-update * chore: remove temp self-update test scripts * fix(web): drop unused apply path from useUpdates hook * fix(backend): snapshot install state once in apply preflight * docs: align self-update docs with transient LaunchAgent flow * chore(release): run cli bundle from apps workspace root * fix(backend): guard against concurrent self-update applies * fix(backend): check scheme and helper paths in self-update apply * fix(backend): drop duplicate binary name from npx manual command * fix(cli): allow --help for service self-update without --intent * fix(cli): harden self-update dir perms and darwin uid resolution * fix(web): set required POST fields last in applyUpdate * docs: use JSON body notation for the apply endpoint * fix(backend): expire self-update guard so a stuck helper cannot wedge apply * refactor(cli): collapse redundant writeSync branch in self-update log |
||
|
|
1fdeda904b |
feat: add voice mode for chat input (#1159)
* feat: add voice mode for chat input Adds a voice input button to the chat composer with three transcription engines: in-browser Web Speech API, in-browser Whisper via @huggingface/transformers, and a server-side OpenAI Whisper fallback gated on KANDEV_VOICE_OPENAI_API_KEY. User-configurable from a new /settings/voice-mode page: - Master on/off toggle (defaults to on; opt-out, not opt-in) - Engine preference (auto / Web Speech / Whisper Web / Whisper Server) - Language hint (auto-detect or BCP-47 pinned) - Activation mode (click-to-toggle or hold-to-talk) - Auto-send after transcription - Whisper Web model size (tiny / base / small) - Editable Cmd+Shift+M keyboard shortcut Voice settings live under user_settings.voice_mode as a nested JSON object — no schema migration. Setting changes broadcast via the existing user.settings.updated WS event so any open chat tab updates live. The mic button is rendered on the right of the composer alongside the send button (round primary fill; destructive fill with pulse ring while recording). On browsers without a usable engine (e.g. iOS Chrome over HTTP) the button stays visible as a muted greyed icon — tapping it surfaces a toast with the reason (typically a missing secure context). Whisper Web runs in a Web Worker with fp16 decoder weights (avoids the MatMulNBits scale-missing crash that q4/q8 hit on WASM). Models are pulled from the onnx-community/* mirror and cached by transformers.js in the Cache Storage on first use. * fix: address voice mode PR review feedback and E2E selector clash Bot-review fixes from #1159: - Map http.MaxBytesError from c.FormFile to 413 instead of bare 400. - Strip BCP-47 region suffix before passing language hint to Whisper Web. - Recover from worker crash: terminate + null worker + reset ready flag so next init/transcribe creates a fresh worker instead of hanging. - Read live store state via storeApi.getState() in voice-mode settings savers to avoid stale-closure clobbering concurrent settings updates. - Detach Web Speech callbacks before abort() so trailing onend/onerror events don't mutate hook state after cancel(). - Spread caller init before method/body in voice-api fetch so a caller- supplied init can't override the multipart POST. - Replace t.Fatalf with t.Errorf+return inside test HTTP handler goroutine (FailNow from non-test goroutine is unsafe). - Add Enabled/AutoSend zeroing assertions in partial-update test so the bool-overwrite-on-PATCH behavior is locked in explicitly. E2E fix: - toolbar-overflow spec used button.rounded-full which now matches both the send and voice buttons (strict-mode violation). Switched to the submit-message-button testid for an unambiguous locator. * fix: scope voice settings rollback + whisper worker error to changed keys Round 2 of PR review feedback on #1159: - voice-mode settings rollback now restores only the keys patched by the failing request (not the whole snapshot), preserving concurrent edits to unrelated voiceMode fields. - Shortcut rollback diffs previous vs next and reverts only the changed keys (restore prior value or delete if absent), preserving other concurrent shortcut edits. - Whisper worker error handler captures the worker reference at attach time and only clears refs when this.worker still matches it — a stale error from a previously replaced worker no longer nukes the active one. - Update stale whisper-web-models JSDoc that still pointed at Xenova mirrors. * fix: voice mode progress scale, hold-mode race, race-aware rollback Round 3 of PR review feedback on #1159: - Normalise Whisper progress to 0–1 in ensureWhisperClient (transformers.js emits 0–100, the button's display + the `ready: 1` convention expect 0–1 — fixes the 5000% mid-download display). - Claim driverRef synchronously at the top of finishCapture so concurrent pointerup + pointerleave invocations early-return instead of racing and clobbering a freshly-started recording's ref. - Race-aware rollback: voice-mode and shortcut catch branches now skip reverting a key when latest[key] !== next[key]. A failed earlier request no longer overwrites a later successful save to the same key. - Update the Worker wire-protocol comment to show onnx-community/whisper-base instead of the stale Xenova/* example. --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |