| Age | Commit message (Collapse) | Author |
|
|
|
The tmux popup editor and its per-agent detection (Cursor/Amp/Aider)
added maintenance surface without enough use to justify it; Codex and
Claude Code already support external-editor mode natively via Ctrl+G.
Drops internal/tmuxedit, cmd/hexai-tmux-edit, the [tmux_edit] config
schema, the Mage build target, and all related docs/README mentions.
Bump version to 0.42.0.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In runInTmuxChild, the primary failure is the child runFn error; the
echo-through failure is secondary context. Wrap the primary error with
%w (keeping copyErr as %v, since fmt.Errorf allows only one %w) so
errors.Is/As work across the chain.
Audited all other fmt.Errorf %v/%s sites in internal/ and cmd/: they
format strings, ints, status codes, or an []error aggregate (syncer),
none of which is a single primary error to unwrap, so they stay as-is.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The FeatureConfig section was a grab-bag mixing five unrelated non-LLM
subsystems (ignore filtering, stats, tmux popup editor, tmux action menu,
MCP server). Decompose it into named per-subsystem structs
(IgnoreConfig, StatsConfig, TmuxEditConfig, TmuxActionConfig, MCPConfig)
embedded into FeatureConfig so the subsystem boundaries are explicit.
Embedding keeps Go field promotion intact, so existing flat read access
(e.g. cfg.MCPPromptsDir) and the JSON/TOML on-disk shape are unchanged;
only composite literals that set these leaf fields directly were updated
to the nested form.
Add fine-grained, defensive-copy section accessors on App
(IgnoreSection, StatsSection, TmuxEditSection, TmuxActionSection,
MCPSection) so consumers can depend on a single subsystem's config
instead of the whole App God-struct. Decouple slashcommands.NewSyncer
to accept appconfig.MCPConfig rather than appconfig.App.
All tests pass with -race; appconfig coverage 91.5%, total 86.2%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Both filelock.AcquireExclusive and askcli.waitOrAcquireAskLockFD created a
single time.Timer and called Reset() on it each retry iteration before it had
necessarily fired. Per the Go timer API, Reset()-ing a timer that may still be
pending is unsafe: a stale value can already be queued on the channel, causing
a spurious early wake-up.
Replace the reused-timer + Reset() pattern with a fresh time.After(...) per
loop iteration, which guarantees a clean full retry interval (or context
cancellation) every time and removes the reuse-while-active hazard. Dropped the
now-unused *time.Timer parameter from waitOrAcquireAskLockFD and introduced
named retry-interval constants.
Add a context-cancel-while-blocked test for the runlock retry loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
When handleChatPrompt detects an in-editor chat prompt it captures the
line index, then spawns a goroutine (requestChatResponse) that later
calls applyChatEdits/buildChatHistory. If a concurrent didChange shrinks
the document in the meantime, the captured lineIdx can exceed
len(d.lines), causing an index-out-of-range panic in the chat goroutine.
- applyChatEdits: skip the stale edit (and log) when lineIdx is < 0 or
>= len(d.lines), rather than indexing d.lines[lineIdx] and panicking
(or corrupting the already-changed document at the wrong position).
- buildChatHistory: clamp the starting index to len(d.lines)-1 so the
upward walk over d.lines[i] cannot read past the end.
Adds regression test TestChatEdits_StaleLineIndexAfterShrink covering
the shrink-then-apply race for both functions, including the
one-past-the-end boundary and a negative index. Confirmed the test
panics without the fix and passes with it; full `mage test` (-race) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Accept ctx as the first parameter on the blocking I/O entry points and
propagate it to downstream blocking calls so the work is cancellable from
the process entry point:
- appconfig.Load / LoadWithOptions: honor ctx before the blocking file
reads, returning defaults on a cancelled context.
- LSP: lsp.Server.Run(ctx) ties the serve loop to the caller context via a
new watchParentContext bridge (cancels the server context, aborting
in-flight LLM work). Threaded through hexailsp.Run/RunWithConfig/
RunWithFactory and runtimeconfig.Store.Reload.
- MCP: mcp.Server.Run(ctx) stops accepting requests once ctx is cancelled;
threaded through hexaimcp.Run/RunWithFactory/RunBackfill.
- editor: RunEditor/OpenTempAndEdit/OpenFile take ctx and use
exec.CommandContext so a cancelled context kills the editor subprocess;
threaded through hexaicli, hexaiaction and askcli call sites.
Top-level callers (cmd/hexai-lsp-server, cmd/hexai-mcp-server) now build a
signal-cancelled context (SIGINT/SIGTERM) so shutdown tears the run down
cleanly. Updated comments to explain the cancellation flow and added
cancellation tests for the LSP/MCP loops, editor, and config load.
All tests pass with -race; cross-package coverage 86.2%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add the missing var _ Client / var _ Streamer compile-time satisfaction
check to anthropicClient, and clarify the existing assertion comments for
openAIClient, openRouterClient, ollamaClient (all Client+Streamer) and
youSearchClient (Client only; the You.com research API is non-streaming).
All providers use value receivers, so assertions use zero-value structs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The per-request HTTP timeouts (30s chat, 120s research), retry policy
(attempts, backoff, jitter), and circuit-breaker tuning (threshold,
cooldown) were previously scattered as bare literals across each provider
constructor and resilience.go/circuitbreaker.go, making the operational
policy hard to discover and prone to drift.
Introduce internal/llm/policy with documented named constants as the single
source of truth, and reference them from all provider constructors, the
default retry policy, and the shared circuit breaker. Update comments to
explain the policy and reasoning. Add tests validating the values and their
internal consistency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The CLI, LSP server and tmux code-action tool each carried near-identical
chat-running logic: invoking the LLM (streaming-aware), collecting the
response, and accounting sent/received bytes into the stats package.
Introduce internal/chatrun with:
- Invoke: streaming-aware LLM call that collects the full response and
optionally mirrors chunks to a writer (nil writer = collect only).
- SentBytes / Account: shared byte counting and stats.Update.
Wire all three surfaces to it:
- hexaicli: runChatRequest delegates to chatrun.Invoke; summarizeChatRun
uses chatrun.Account. Removed the duplicated streaming/simple helpers.
- hexaiaction: runOnce uses chatrun.Invoke + chatrun.Account, keeping the
tmux status update local.
- lsp: chatWithStats and the completion path use chatrun.SentBytes/Invoke;
extracted unavailableClientError to keep chatWithStats small.
chatrun has 100% test coverage; full suite passes with -race.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Follow the Go idiom "return concrete types, accept interfaces": the
provider constructors newOpenAI, newAnthropic, newOpenRouter, newOllama
and their *WithTimeout variants now return their concrete *Client value
types instead of the Client interface. The provider factories registered
in the registry still return Client, so NewFromConfig and the registry
keep working unchanged.
Updated comments to explain the reasoning, dropped the now-redundant
type assertions in tests, and switched the anthropic Streamer-capability
tests to assert via an explicit Client interface value.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The Server type accumulated two large, tangled feature subsystems (in-editor
chat and code completion) alongside its core LSP dispatch role. Pull them into
cohesive types that own their state and logic while delegating shared
infrastructure back to Server via a back-reference.
- Add completionService (completion_service.go): owns the completion
cache/throttle state (completionState) and all completion request-handling
logic (handleCompletion, plan/jobs/execute, provider-native path,
post-processing, message building, prefix heuristics). Methods moved off
Server in handlers_completion.go to completionService.
- Add chatService (chat_service.go + chat_handlers.go): owns the input-activity
clock (lastInput, with its own mutex instead of Server.mu) and all in-editor
chat logic (detection, transcript history, message building, edit
application, inline prompts) plus the slash-command handlers
(chat_commands.go).
- Server now holds chat/completion fields, wires them in NewServer, and
delegates (dispatch table, didChange, debounce gate) to them. Thin Server
shims preserve the existing completion-state API for callers/tests.
Pure refactor: no behavior or LSP protocol changes. Tests adjusted only to
reach the relocated methods/state via the services. All tests pass with -race;
coverage 86.1%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Wrap the shared provider HTTP choke point (doJSONRequest in llm/util.go,
used by openai, openrouter, anthropic and ollama) with two resilience
patterns implemented with the standard library only:
- Retry with exponential backoff + jitter (resilience.go): 3 attempts by
default, retrying transient network errors and retryable HTTP statuses
(429 and 5xx). Client errors (4xx) and successes are returned
immediately and never retried. Backoff waits are context-aware so
cancellation and deadlines are respected; retried response bodies are
drained and closed for connection reuse.
- Circuit breaker (circuitbreaker.go, own file as it has >3 methods):
classic closed/open/half-open breaker that trips after 5 consecutive
transient failures and stays open for a 30s cooldown, then allows a
single trial probe. Only transient failures count; 4xx never trips it.
A nil breaker is a valid no-op.
doJSONRequest now delegates to doJSONRequestResilient; the single-attempt
primitive is preserved as doJSONRequestOnce. Adds httptest-based unit
tests for retry/backoff/breaker logic with no real network calls;
new code coverage is >80%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
RegisterProvider now returns an error for an empty name, a nil factory,
or a duplicate registration instead of panicking, making registration
composable and testable without recover(). RegisterAllProviders caches
the one-time registration error (sync.Once cannot return a value) and
returns it to every caller.
Updated all call sites: hexailsp, hexaicli and hexaiaction propagate the
error with context; test TestMains fail fast via panic. Added unit tests
covering empty name, nil factory, duplicate, success and idempotent
re-registration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The commandTable type had mixed receiver kinds: get, rootCompletionEntries
and singleSelectorNames used value receivers while add used a pointer
receiver because it mutates the table. Standardize on pointer receivers for
all methods (the mutating add forces this choice) and return *commandTable
from newCommandTable. Call sites are unaffected since commandRegistry is an
addressable package-level variable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
builtin
The named return value 'close' in Server.inlineMarkers() shadowed the Go
builtin 'close'. Renamed it to 'closeStr' (matching the 'openStr' naming used
by callers) and added a comment explaining the reasoning. Callers use
positional returns, so no call sites changed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
|
|
Amp-Thread-ID: https://ampcode.com/threads/T-019e9b82-6ba0-77a4-b4a0-5c2cbf9bf39f
Co-authored-by: Amp <amp@ampcode.com>
|
|
- ask edit opens $EDITOR and creates a task from the (multi-line) content,
reusing the shared internal/editor package
- collapse newlines in list output and fish completion so multi-line
descriptions render on one line and don't break completion
Bump version to 0.40.0
Amp-Thread-ID: https://ampcode.com/threads/T-019e96a1-9c8e-73d6-95b4-b55cb12cc762
Co-authored-by: Amp <amp@ampcode.com>
|
|
|
|
detection
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Replace time.After with time.NewTimer + defer timer.Stop() to prevent
a leaked goroutine when the context is cancelled before the timer fires.
|
|
Updates go.mod from 1.21.0 to 1.24.0 because tests use testing.T.Chdir,
which requires Go 1.24. Also fixes pre-existing fmt.Errorf non-constant
format strings in task_selector.go that the printf vet analyzer flags
when running go test with the newer minimum version.
|
|
Implements `ask watch [subcommand...]` which re-runs a read-only
subcommand every 2 seconds and redraws output when it changes,
similar to gnu-watch.
Safety features:
- Restricted to read-only subcommands (list, all, ready, info,
urgency, help, dep list)
- Recursive watch is blocked
- Captures stderr so error messages are visible in the watched display
Also adds readOnly field to commandEntry registry for maintainability.
|
|
Amp-Thread-ID: https://ampcode.com/threads/T-019e45ff-4976-750c-b2e6-121d0e5991ef
Co-authored-by: Amp <amp@ampcode.com>
|
|
Amp-Thread-ID: https://ampcode.com/threads/T-019e45ff-4976-750c-b2e6-121d0e5991ef
Co-authored-by: Amp <amp@ampcode.com>
|
|
Amp-Thread-ID: https://ampcode.com/threads/T-019e45ff-4976-750c-b2e6-121d0e5991ef
Co-authored-by: Amp <amp@ampcode.com>
|
|
Amp-Thread-ID: https://ampcode.com/threads/T-019e45ff-4976-750c-b2e6-121d0e5991ef
Co-authored-by: Amp <amp@ampcode.com>
|
|
Amp-Thread-ID: https://ampcode.com/threads/T-019df49f-52a5-75b1-98d5-371a163ef100
Co-authored-by: Amp <amp@ampcode.com>
|
|
Two concurrent 'ask' invocations could race on the alias cache file:
both wrote the JSON to a shared '<path>.tmp' filename and then both
called os.Rename, so the loser failed with:
replace task alias cache: rename .../task-aliases-v2.json.tmp
.../task-aliases-v2.json: no such file or directory
The shared tempfile also enabled lost updates because each process
loaded the file independently before saving its own version on top.
Fix:
- Take an exclusive flock on a sentinel file (task-aliases-v2.json.lock)
in the cache directory around the full load/modify/save cycle in both
ensureTaskAliases and resolveTaskSelectorFromCache, using the existing
internal/filelock package.
- Switch save() to os.CreateTemp so each writer gets a unique tempfile
name; the loser's tempfile is removed cleanly on rename failure.
- Refactor resolveTaskSelectorFromCache by extracting
finalizeResolvedTaskSelector to keep functions under 50 lines.
Adds TestEnsureTaskAliases_ConcurrentCallsDoNotRaceOnTempFile, which
reproduces the original error reliably on the unfixed code and now
passes with -race.
Amp-Thread-ID: https://ampcode.com/threads/T-019df49f-52a5-75b1-98d5-371a163ef100
Co-authored-by: Amp <amp@ampcode.com>
|
|
The CLI now opens the config file only via hexai config (and --config).
README, usage, and configuration docs describe the subcommand. Version 0.38.4.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
|
Correct default Ollama Cloud model to gemma4:31b-cloud (bare gemma4 tag
returns 404 on Ollama Cloud; the hosted cloud model requires the explicit
:31b-cloud tag).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|