Commit graph

9 commits

Author SHA1 Message Date
Snider
060f5abb66 feat(agent/php): ProfileSelector + ShapeClassifier services per Phase 3 (#826)
ShapeClassifier::classify($ticket) returns 'A'|'B'|'C' per policy v1:
- (severity=critical OR priority=urgent) → A
- has tag in ['security','crypto','core'] → A
- (severity=major OR priority=high) → B
- everything else → C

ProfileSelector::pickFor($ticket) walks AgentProfile::active(), matches
capability tags case-insensitively against ticket.tags:
- Class A: cheapest matching profile (cost_class alphabetic order)
- Class B: any active profile with quota_headroom_pct >= 25
- Class C: deterministic round-robin via last_dispatched_at

Pest Unit tests cover Good (matching profile picked), Bad (no match → null),
Ugly (all profiles disabled → null), plus class A/B headroom gating + class C
round-robin determinism.

Codex note: php -l clean; pest skipped — no vendor/ at this repo root
(downstream lab/lthn.ai owns composer install).

Closes tasks.lthn.sh/view.php?id=826

Co-authored-by: Codex <noreply@openai.com>
2026-04-26 00:48:37 +01:00
Snider
b0118ef8ef feat(core/events): add WebhookRegistering lifecycle event (HIGH)
WebhookRegistering event exposes:
- register(string $type, array $spec): add a webhook type to the
  registry
- types(): array — queryable post-dispatch registry

CoreServiceProvider dispatches the event at app boot and exposes the
collected registry via webhookTypes() — matches the existing
ApiRoutesRegistering / ConsoleBooting / ClientRoutesRegistering
event-driven module pattern.

Pairs with #1034 ofm.bot WebhookRegistrar (just landed) — that
service can now also be wired through this event, allowing OTHER
modules and external apps using Core to register webhook types via
the standard Core lifecycle.

Note: real Core lifecycle dispatcher lives in a sibling read-only
framework checkout. CoreServiceProvider here is a local shim that
mirrors the dispatch behaviour. Upstream patch needed when that
sibling lands.

Pest covers: instantiation + register, boot-time dispatch, post-boot
registry lookup.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=1013
2026-04-25 19:37:23 +01:00
Snider
4c1fa56d17 fix(brain): wire Qdrant api-key header from BRAIN_QDRANT_API_KEY
BrainService::http() was building a PendingRequest with no auth
header, so when Qdrant has auth enabled (the production lthn.sh
deploy does) every upsert/lookup returned 401. The circuit breaker
logged the 401 via Cache::store('file'), which was the red-herring
cache-write error chased in the first #97 iteration.

Changes:
- BrainService loads + trims a Qdrant api key from
  config('brain.qdrant.api_key') in the constructor.
- New qdrantHttp() helper returns a PendingRequest with the
  api-key header when the key is non-empty, or the plain client
  otherwise. Ollama + Elasticsearch call sites still use http()
  (separate auth shapes).
- php/config.php adds a brain.qdrant.api_key entry reading
  env('BRAIN_QDRANT_API_KEY').
- Good/Bad/Ugly Pest tests cover: configured key → header sent,
  unset → header absent, empty-string → header absent.

Closes tasks.lthn.sh/view.php?id=97

Co-authored-by: Codex <noreply@openai.com>
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-24 05:21:15 +01:00
Snider
e83c3d811d feat(pipeline): add MetaReader contract + Forgejo-backed implementation
Introduce a pipeline metadata surface that enforces "no body content
ever reaches pipeline decisions". MetaReader is an interface with four
methods (getPRMeta, getEpicMeta, getIssueState, getCommentReactions),
each returning a readonly DTO carrying only structural fields —
state, mergeability, SHAs, branches, reaction counts, child linkage.
ForgejoMetaReader projects raw Forgejo API payloads into these DTOs
and drops body/description/review text before the caller can see it.

Unit test mocks rich Forgejo payloads containing body, description,
review_text, and comment_body, then asserts the DTO toArray output
never exposes those keys — the regression fence for the RFC rule.

Downstream callers (ScanForWork, ManagePullRequest) still use the
raw ForgejoService today; that refactor lands under Mantis #90.

Closes tasks.lthn.sh/view.php?id=89

Co-authored-by: Codex <noreply@openai.com>
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-23 18:09:54 +01:00
e58986a3b4 revert fcb9c189e5
revert fix(agentic): harden TODO workspace write

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-23 12:32:57 +01:00
Codex
cbc262add4 fix(agentic): harden TODO workspace write
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-23 12:32:57 +01:00
Snider
ccedf536d6 fix(agent-tool-registry): harden rate limiting and api key identifiers
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:48:29 +01:00
Snider
1e8af462f2 fix(agentic): harden tool execution and template validation
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-17 20:25:19 +01:00
Snider
be1130f470 agent updates 2026-03-21 11:10:44 +00:00