| Age | Commit message (Collapse) | Author |
|
Adds internal/provider/anthropic, the first genuinely new provider.Provider
implementation on top of Phase 1's provider-neutral tool-use loop, alongside
(not replacing) the existing Docker/CLI-subprocess ContainerRunner path for
the "claude" agent type:
- internal/provider/anthropic: translates the neutral ChatRequest/ChatResponse
shape to/from Anthropic's Messages API content-block format (system as a
top-level field, tool_use/tool_result blocks, no "tool" role -- tool
results become user-role messages), with a per-model-prefix pricing table
for CostUSD
- internal/retry: IsRateLimitError additively extended to recognize
Anthropic's rate_limit_error/overloaded_error/529 shapes
- internal/config: RunnersConfig.Anthropic/AnthropicEnabled() gate
- internal/cli/serve.go, run.go: register runners["anthropic"] as a
NativeRunner when [providers.anthropic].api_key is set and enabled --
tracked as a distinct executions.agent="anthropic" budget bucket, separate
from the CLI-subprocess "claude" runner even though both bill the same
Anthropic account
go build/vet/test -race all pass. No live Anthropic API key is available in
this environment, so verification is via fake-httptest-server adapter tests
(12 cases, incl. multi-turn tool_result round-trip and rate-limit error
matching) plus a Pool/NativeRunner routing test proving agent.type:
"anthropic" actually reaches the new provider. Live end-to-end verification
against the real API is a follow-up once a key is configured.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
|
|
Splits LocalRunner's OpenAI-specific agentic loop into reusable, provider-
agnostic pieces so later phases can add native Anthropic/OpenAI/Google/Groq/
OpenRouter adapters without duplicating the control flow:
- internal/provider: neutral Provider/ChatRequest/ChatResponse types, plus
an openaicompat adapter wrapping the existing internal/llm.Client unchanged
- internal/sandbox: Sandbox interface + HostSandbox (git clone/push/cleanup,
read_file/write_file/run_bash/glob), lifted verbatim from local.go/localtools.go
- internal/agentloop: the extracted tool-use loop (request/response/tool-
dispatch/loop, ask_user blocking, stream-json envelope, summary fallback)
- internal/agentchannel: AgentChannel/SubtaskSpec/BlockedError/ErrAgentBlocked
moved out of internal/executor so agentloop can use them without an import
cycle; internal/executor re-exports via type aliases, so no call site changes
- internal/executor/nativerunner.go: NativeRunner replaces LocalRunner,
wiring agentloop.Loop + openaicompat + HostSandbox together
- config.Providers map[string]ProviderConfig added (unused until Phase 2+)
Zero intended behavior change: go test -race ./... passes across all
packages, and end-to-end stream-json/summary/changestats output was verified
byte-compatible against a fake OpenAI-compatible server. Adds test coverage
for sandbox tool-dispatch (git clone/push, read/write/bash/glob) that
LocalRunner never had.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
|
|
loopback-only bind
Key features from OSS branch:
- Budget gating: rolling per-provider spend caps with BUDGET_EXCEEDED task state
- Agent MCP back-channel: runners mint tokens; /mcp endpoint resolves them
- Chatbot MCP server at /chatbot/mcp (requires api_token in config)
- Event timeline: unified event stream replacing ad-hoc question/summary flows
- Loopback-only default bind (127.0.0.1:8484); external_bind_allowed=true to expose
- repository_url required on task creation (enforces traceability)
- Auto-checker: spawns verification task after each execution
Conflict resolutions:
- serve.go: keep cfg.Runners.XEnabled() conditional registration from main + agentRegistry from oss
- config.go: keep RunnersConfig from main + BudgetConfig/ExternalBindAllowed from oss
- server.go: handleStaticFiles (base-path rewrite) kept; deduplicated duplicate from both sides
- executor/claude.go: accepted oss deletion (ClaudeRunner replaced by ContainerRunner)
- container_test.go: added noopChannel + AgentChannel parameter to Run call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
The server now defaults to binding 127.0.0.1 and refuses to bind a non-loopback
address (:8484, 0.0.0.0, a LAN IP, …) unless external_bind_allowed = true is set
in config — failing loud at startup instead of silently exposing itself.
External access is expected to go through a reverse proxy (Tailscale / Cloudflare
Access / Caddy), per the locked auth model.
- config: ExternalBindAllowed flag + ValidateBindAddr/isLoopbackHost (tested
across loopback, all-interfaces, LAN, and override cases).
- cli: --addr default is now 127.0.0.1:8484; serve() validates before binding.
Per-client bearer rotation stays deferred (single shared token for now).
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
Adds the budget accountant foundation:
- storage: a per-execution `agent` column (additive migration, populated by the
dispatcher) and SpendByProviderSince(since), summing cost_usd per provider in
a window. Accurate attribution survives a task running on different providers
across retries.
- internal/budget: Accountant over a SpendSource + Limits, exposing Headroom
(remaining/fraction per provider), Allow (would estCost breach the cap), and
All (one query, sorted). Unconfigured/local providers are unlimited.
- config: a [budget] section (window + provider_5h_usd map). No default cap —
gating is opt-in by configuring limits.
Fully unit-tested; dispatcher gating and the API/UI surface follow.
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
Adds a chatbot-facing MCP server mounted at /chatbot/mcp, guarded by the shared
API bearer token (new config api_token, wired through SetAPIToken). Tools:
submit_task, list_tasks, get_task, get_events, answer_question, accept_task,
reject_task, cancel_task. submit_task creates and immediately queues a task,
resolving the repository URL from a named project when not given explicitly.
To keep the REST API and the MCP surface from drifting, the accept/reject/
cancel/answer operations are extracted into a shared service layer (taskops.go)
with typed errors mapped to REST status codes; the existing HTTP handlers now
delegate to it. The endpoint is only served when a token is configured and
presented as a bearer.
https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
|
|
Adds RunnersConfig{Claude,Gemini,Local,Container *bool} to Config,
parsed from a [runners] TOML section. Each field uses the *bool pointer
pattern (nil = enabled by default, false = disabled) so existing installs
require no config changes.
serve.go / run.go now gate each runner registration on the corresponding
Enabled() method. The pool's runners map remains the authoritative
routing table — absent entries are already skipped by pickAgent and
fail-fast in getRunner, so no executor changes are needed.
Example:
[runners]
gemini = false # disable cloud gemini runner
local = true # explicit (same as default)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Merges 12 commits from github/main (formerly master) that were developed
independently. Key additions:
- LocalRunner: OpenAI-compatible local LLM execution (Ollama, LM Studio)
- Real GeminiRunner with full sandbox parity to ClaudeRunner
- llm.Client for enriching CI failures and elaboration via local model
- retry.ParseRetryAfter moved to shared package
- tokens_in/tokens_out columns in executions table
Conflict resolutions:
- Kept local main's VAPID/push, stories, projects, agent events schema
- Merged both sets of Config fields (local + LocalModel from github/main)
- Unified activePerAgent accounting (decActiveAgent helper)
- Removed duplicate helpers from claude.go (now in helpers.go)
- Fixed double-decrement bug in handleRunResult vs decActiveAgent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Phase 2 of "local OSS models as agents" plan. Adds a third elaboration
path that calls the local OpenAI-compatible LLM via the internal/llm
client, and reorders dispatch so the cheap path is tried first:
local → claude → gemini, with each next attempt only on hard failure
of the prior.
Wiring is opt-out, not opt-in: when [local_model].endpoint is set,
elaboration prefers local by default. Users with a slow or low-quality
local model can disable just elaboration via:
[local_model]
endpoint = "..."
prefer_for_elaborate = false
without giving up the runner or the classifier path.
Implementation:
- Server gains an optional *llm.Client field via SetLLM (matches the
existing SetNotifier/SetWorkspaceRoot setter pattern, no NewServer
signature break).
- elaborateWithLocal() reuses buildElaboratePrompt verbatim and asks
for response_format=json_object so we skip markdown-fence cleanup.
- handleElaborateTask reorders try chain; existing Claude-first
behavior is preserved exactly when SetLLM is not called.
- LocalModel.UseForElaborate() encapsulates the default-true gating
with a *bool so explicit-false survives TOML parse.
Tests:
- elaborateWithLocal: parses valid response, errors on nil client,
errors on bad JSON.
- handler: local preferred when wired; falls back to claude when
local fails; unchanged behavior when no LLM is configured.
- config: UseForElaborate gating across empty/default/explicit-true/
explicit-false cases.
Pre-existing test failures noted in docs/plans/local-oss-runner.md
(post-epic cleanup): TestGeminiLogs_ParsedCorrectly returns 404 for
gemini execution log fetch — predates this change.
Plan: docs/plans/local-oss-runner.md.
https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
|
|
Phase 1 of "local OSS models as agents" plan. Adds a third Runner
backed by any OpenAI-compatible HTTP server (Ollama, vLLM, LM Studio,
llama.cpp), and migrates the Gemini-CLI classifier to route through
the same client when configured.
Two-layer split: internal/llm.Client is the workhorse (HTTP, no Pool,
no DB) used directly by the classifier and any future internal helper
that needs cheap reasoning. internal/executor.LocalRunner is a thin
adapter implementing Runner for user-facing tasks. This avoids
Pool reentrancy/deadlock when sub-second internal calls fire from
inside Pool.execute().
Highlights:
- internal/retry: relocated runWithBackoff/IsRateLimitError/ParseRetryAfter
into a shared package reused by executor and llm.
- internal/llm: Chat (non-streaming) and ChatStream (SSE) over
/chat/completions with optional bearer auth, json_object response
format, retry on 429/503, Retry-After parsing.
- internal/executor/LocalRunner: streams deltas into stdout.log in the
same stream-json envelope ClaudeRunner emits, then writes one
consolidated assistant block plus a result terminator so existing
parsers (extractSummary, ParseChangestatFromOutput) work unchanged.
- internal/executor/Classifier: gains optional LLM field; uses
json_object response format (no markdown-fence cleanup needed).
Falls back to Gemini-CLI subprocess when LLM is nil.
- Pool.skipClassification: now skips only when the requested agent
type is registered, so unknown types still reach the load balancer.
- Storage: additive tokens_in/tokens_out ALTERs on executions; CLI
runners record cost_usd as before, LocalRunner records 0 + tokens.
- Config: [local_model] section (endpoint, model, timeout_seconds,
default_temperature, api_key). Empty endpoint = no LocalRunner
registered, classifier falls back to Gemini.
Pre-existing test issues fixed in passing:
- claude_test.go setupSandbox callsites updated to current signature.
- gemini_test.go TestParseGeminiStream skipped (asserts unimplemented
GeminiRunner stream-error parsing; tracked separately).
Plan: docs/plans/local-oss-runner.md.
https://claude.ai/code/session_017Edeq947TpSm1vQTxMhi1J
|
|
- Add workerWg to Pool; Shutdown() closes workCh and waits for all
in-flight execute/executeResume goroutines to finish
- Signal handler now shuts down HTTP first, then drains the pool
- ShutdownTimeout config field (toml: shutdown_timeout); default 3m
- Tests: WaitsForWorkers and TimesOut
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
The server runs as www-data whose HOME is /var/www — deriving
credentials from $HOME/.claude always produced an empty path.
Now reads from ClaudeConfigDir (default: /workspace/claudomator/credentials/claude),
which sync-credentials keeps populated with fresh OAuth tokens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
- Fix Critical Bug 1: Only remove workspace on success, preserve on failure/BLOCKED.
- Fix Critical Bug 2: Use correct Claude flag (--resume) and pass instructions via file.
- Fix Critical Bug 3: Actually mount and use the instructions file in the container.
- Address Design Issue 4: Implement Resume/BLOCKED detection and host-side workspace re-use.
- Address Design Issue 5: Consolidate RepositoryURL to Task level and fix API fallback.
- Address Design Issue 6: Make agent images configurable per runner type via CLI flags.
- Address Design Issue 7: Secure API keys via .claudomator-env file and --env-file flag.
- Address Code Quality 8: Add unit tests for ContainerRunner arg construction.
- Address Code Quality 9: Fix indentation regression in app.js.
- Address Code Quality 10: Clean up orphaned Claude/Gemini runner files and move helpers.
- Fix tests: Update server_test.go and executor_test.go to work with new model.
|
|
Web Push:
- WebPushNotifier with VAPID auth; urgency mapped to event type
(BLOCKED=urgent, FAILED=high, COMPLETED=low)
- Auto-generates VAPID keys on first serve, persists to config file
- push_subscriptions table in SQLite (upsert by endpoint)
- GET /api/push/vapid-key, POST/DELETE /api/push/subscribe endpoints
- Service worker (sw.js) handles push events and notification clicks
- Notification bell button in web UI; subscribes on click
File Drop:
- GET /api/drops, GET /api/drops/{filename}, POST /api/drops
- Persistent ~/.claudomator/drops/ directory
- CLAUDOMATOR_DROP_DIR env var passed to agent subprocesses
- Drops tab (📁) in web UI with file listing and download links
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds POST /api/webhooks/github that receives check_run and workflow_run
events and creates a Claudomator task to investigate and fix the failure.
- Config: new webhook_secret and [[projects]] fields in config.toml
- HMAC-SHA256 validation when webhook_secret is configured
- Ignores non-failure events (success, skipped, etc.) with 204
- Matches repo name to configured project dirs (case-insensitive)
- Falls back to single project when no name match found
- 11 new tests covering all acceptance criteria
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
The --config flag was registered but silently ignored. Now:
- config.LoadFile loads a TOML file on top of defaults
- PersistentPreRunE applies the file when --config is set
- Explicit CLI flags (--data-dir, --claude-bin) take precedence over the file
Tests: TestLoadFile_OverridesDefaults, TestLoadFile_MissingFile_ReturnsError,
TestRootCmd_ConfigFile_Loaded, TestRootCmd_ConfigFile_CLIFlagOverrides,
TestRootCmd_ConfigFile_Missing_ReturnsError
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add workspaceRoot field (default "/workspace") to Server struct
- Add SetWorkspaceRoot method on Server
- Update handleListWorkspaces to use s.workspaceRoot
- Add WorkspaceRoot field to Config with default "/workspace"
- Wire cfg.WorkspaceRoot into server in serve.go
- Expose --workspace-root flag on the serve command
- Add TestListWorkspaces_UsesConfiguredRoot integration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Resolve conflicts in API server, CLI, and executor.
- Maintain Gemini classification and assignment logic.
- Update UI to use generic agent config and project_dir.
- Fix ProjectDir/WorkingDir inconsistencies in Gemini runner.
- All tests passing after merge.
|
|
Default() now returns (*Config, error) so callers can detect TOML parse
failures rather than silently falling back to zero values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
Claudomator automation toolkit for Claude Code with:
- Task model with YAML parsing, validation, state machine (49 tests, 0 races)
- SQLite storage for tasks and executions
- Executor pool with bounded concurrency, timeout, cancellation
- REST API + WebSocket for mobile PWA integration
- Webhook/multi-notifier system
- CLI: init, run, serve, list, status commands
- Console, JSON, HTML reporters with cost tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|