Skip to content

feat: publishCustomEvent, AgentCat::fake(), signal drain and end-to-end tests - #4

Merged
naji247 merged 17 commits into
feat/plan-a-foundationfrom
feat/plan-c2-public-api-and-e2e
Oct 22, 2026
Merged

naji247 merged 17 commits into
feat/plan-a-foundationfrom
feat/plan-c2-public-api-and-e2e

Conversation

@naji247

@naji247 naji247 commented Oct 17, 2026

Copy link
Copy Markdown
Member

Fourth part of the PHP AgentCat SDK: the rest of the public API and end-to-end tests. Stacked on #3; review #1, #2 and #3 first.

What is in it

  • AgentCat::publishCustomEvent($serverOrSessionId, 'proj_…', new CustomEvent(...)) with the TypeScript server and session-string forms. The server form accepts the Builder or the built Mcp\Server, uses the tracked project and server info, and applies redactEvent only. The two TypeScript validation errors throw \InvalidArgumentException; when AgentCat itself could not track the server (an mcp/sdk change, or track() ran while AgentCat was off), it logs one warning instead of throwing.
  • AgentCat::fake() for customer test suites: events(), logs(), assertTracked(), assertTrackedCount(), assertNothingTracked(), assertCustomEventPublished(). No network, hooks applied, each passing assertion counts as one PHPUnit assertion, and AgentCat's log lines stay in memory instead of ~/agentcat.log.
  • SIGTERM/SIGINT drain for long-running CLI servers (ext-pcntl): the first signal goes back to the earlier handler, AgentCat sends the queued events, then calls that handler or re-raises the signal. Never installed over a SIG_IGN set in PHP. A second signal acts at once.
  • One 5 s shutdown budget per queue, shared by the signal drain, shutdown() and the shutdown function.
  • An out-of-range duration is sent as no duration (no PHP 8.5 warning on STDOUT).
  • composer.json suggests ext-pcntl and ext-posix.

Testing

1735 tests pass (composer check; two expected skips). Full mutation score 98.4 %, covered MSI on changed lines 100 %. New E2E suite: a real mcp/sdk server under php -S and as a stdio subprocess, raw JSON-RPC, a local mock collector; it proves the response does not wait for the hooks, stdout stays pure JSON-RPC, EOF and SIGTERM delivery within the 5 s budget, and a fiber suspended inside AgentCat's interceptor. The mcp-sdk-main job now runs Contract, Integration and E2E against mcp/sdk main at 82e19d1. PHP 8.5 with the highest and the lowest dependency sets passed locally in Docker (PHPStan and PHPUnit).

…rver after a failed track

A track() that AgentCat could not complete (mcp/sdk drift, an unsupported
shape, an internal failure) or an unreadable built server now marks the
process. publishCustomEvent() then logs one warning per call for a server
it cannot find instead of throwing into the customer's code; without such
a failure it still throws like TypeScript. The drift cases that need
ReflectionClass move to the contract tests, and the PHPDoc says how a
coercive caller's scalar first argument is read.
PHP 8.5 warns when it casts such a float to int, and a CLI server prints
that warning on STDOUT. A NaN, an infinity or an out-of-range float now
leaves the duration out on PHP 8.3, 8.4 and 8.5 alike.
A later shutdown() (the shutdown function after a signal drain that used
the whole budget) still ran one pump: hooks for up to 5 waiting events and
up to 5 transfers that the process abandons at once. Once the deadline of
the first shutdown has passed, a later one only reports the events that
still wait.
An ignore inherited from the parent process reads as SIG_DFL in PHP, so
AgentCat installs its drain for that signal. No behavior change.
AC-69 is "a high-level server whose low-level server is tracked is
accepted". These tests cover D31, D44 and ruling R4, which have no
acceptance criterion; PublishCustomEventTest keeps the AC-69 tag.
Under AgentCat::fake(), track() and publishCustomEvent() wrote to
$HOME/agentcat.log on every developer machine and CI runner, and a
Builder's PSR-3 logger tracked under the fake took every later AgentCat
line of the suite. While a fake's client is the shared client, AgentCat now
logs to that fake only (AgentCatFake::logs()) and adopts no Builder logger.
AgentCat::setClient() that replaces it brings back the routing from before
the first fake().
In a test environment, a server that the application tracks in setUp(),
before the test calls AgentCat::fake(), is not tracked at all. Code under
test that then published a custom event for it got the TypeScript
"Server is not tracked" exception. track() now records, process wide,
that it ran while AgentCat was off; publishCustomEvent() then logs a
warning that says to call fake() first (the failed-track line keeps
precedence), and assertTracked()/assertTrackedCount() add the same hint
when no tool call was recorded. The fake() PHPDoc says to call it before
the application builds its servers.
Base automatically changed from feat/plan-c1-adapter to feat/plan-a-foundation October 22, 2026 22:55
@naji247
naji247 merged commit e8cdc7f into feat/plan-a-foundation Oct 22, 2026
11 of 12 checks passed
@naji247
naji247 deleted the feat/plan-c2-public-api-and-e2e branch October 22, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants