OpenAiProvider gains a custom id and a chat-only mode; `OpenAiProvider::opencode`
builds an OpenCode Zen provider (id `opencode`, defaults to Zen's gateway, always
chat-completions so even gpt-*/o* model ids route correctly). Registered from a
`providers.opencode` config block / OPENCODE_API_KEY env, so it coexists with a
real `openai` provider. Models are referenced as `opencode/<model>`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TUI: Ctrl+J (or /jobs) opens a jobs pane listing the current session's board —
alias, agent, state, objective, files read — populated on load and kept live via
JobUpdated events; Enter drills into a subtask's child session. Snapshot test added.
Engine: optional orchestration reminders (off by default) injected as synthetic,
non-persisted turn-start blocks and, after a file tool runs, an after-file-tool
block on the following turn. Processor reports file-tool usage via StepOutcome.
This completes M4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A ContextReporter wired into subagent sessions lets the read tool advertise the
files it reads (≥10 lines) on its job board entry, so a completed specialist
shows what it already looked at. Root sessions report nothing (no parent board).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `task` tool delegates to a SubagentSpawner (owned by the composition root):
resolves the agent, enforces the depth limit, applies permission intersection,
filters tools per-agent, and runs the child session foreground or background.
Foreground returns the child's final text; background registers on the job board
and detaches under the parent run token. The run loop injects the board into
primary-agent requests and reconciles terminal jobs each step.
Integration tests cover foreground run + alias reuse continuing the same child
session, depth-limit and unknown-agent rejection, and board prompt injection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
evaluate_intersected() returns the more restrictive of a parent-effective
and child verdict (deny > ask > allow). JobBoard tracks subagent tasks with
per-agent aliases, reuse/LRU trimming, context-file reporting, reconcile, and
a formatForPrompt injection block; persisted to the `job` table so resume works.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Offline-verifiable building blocks for GitHub Copilot; network paths are the
flagged live-verification risk and are covered by pure unit tests only.
- copilot/device_flow.rs: OAuth device flow (RFC 8628). No hardcoded client id —
ai-harness must register its own GitHub OAuth app and pass it in. Pure
parse_poll_response (pending/slow_down/success/failed) + slow_down interval bump;
request_device_code/poll_once/poll_for_token (injected sleep).
- copilot/token.rs: copilot_internal/v2/token exchange with cache + single-flight
refresh (tokio::Mutex) and direct-Bearer fallback on 401/403/404; needs_refresh
honors a 120s skew and expires_at==0 = never.
- copilot/provider.rs: CopilotProvider over api.githubcopilot.com, routing each
model to chat/responses/anthropic codec by its /models supported_endpoints
(parse_models + codec_for_endpoints); headers (X-GitHub-Api-Version, Openai-Intent,
x-initiator, Copilot-Vision-Request, anthropic-beta for anthropic models).
- 11 unit tests (device-flow parsing, token refresh math, codec routing, /models
parsing).
Deferred (needs live API + TUI work): EngineHandle::login device-flow modal,
startup /models fetch, auth.json-backed registry wiring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- modelsdev.rs: ModelCatalog parses models.dev api.json → ModelInfo keyed by
(provider, model); 24h file cache at ~/.cache/ai-harness/models.json with a
baked assets/models-snapshot.json fallback so cost/limits work offline.
load_cached_or_baked (no network) + refresh/refresh_default_cache (background).
- App: loads the catalog at init (cached-or-baked, never blocks), warms the cache
in a background task, and passes the session model's pricing into RunConfig.cost
so session cost accrues for real.
- TUI: AppState tracks session_cost/session_tokens from Session events; status bar
shows "<tokens> · $<cost>". Snapshots updated.
- 7 modelsdev tests (parse, defaults, unknown-model, baked snapshot, cache TTL,
cached-vs-baked load).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- types::ModelCost {input, output, cache_read, cache_write} (USD per 1M tokens)
with cost_of(usage); reasoning tokens are billed within output by our providers
so they are not charged separately. ModelInfo gains cost + reasoning/tool_call/
attachment capability flags (all #[serde(default)] for forward-compat).
- Engine: RunConfig.cost threads pricing into process_step; on_finish now stamps
the real dollar cost onto the StepFinish part; StepOutcome carries per-step cost.
- run_session accumulates each step's usage and cost onto the session (previously
never updated) and republishes SessionUpdated — best-effort, store errors logged
not fatal.
- Store gains a single-session getter (Session cmd + get_session).
- App passes cost: None for now (real rates land with models.dev wiring).
- Tests: ModelCost::cost_of math (+ reasoning exclusion), and the multi-step
engine test now asserts session usage/cost accumulation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- auth.rs: AuthRecord (OAuth {access, refresh, expires} | Api {key}) with
is_expired(now, skew) honoring expires==0 = never; AuthStorage keyed by
provider id over ~/.local/share/ai-harness/auth.json.
- Read-modify-write on every op so concurrent refresh/login don't clobber;
writes go through a temp-file rename set to 0600 (unix) to avoid truncated
auth files.
- 6 tempdir tests: missing→empty, set/get/overwrite, multi-provider coexist,
scoped remove (+ no-op on absent), expiry skew/never, 0600 perms.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>