- Delete Models/AgentWorkspaceState.php (legacy port, no backing table)
- Rewrite Models/WorkspaceState.php as the single canonical state model
backed by agent_workspace_states table with array value cast,
type helpers, scopeForPlan/scopeOfType, static getValue/setValue,
and toMcpContext() for MCP tool output
- Update AgentPlan::states() relation and setState() return type
- Update StateSet MCP tool import
- Update SecurityTest to use WorkspaceState
- Add WorkspaceStateTest covering table, casts, type helpers, scopes,
static helpers, toMcpContext, and AgentPlan integration
- Mark CQ-001 done in TODO.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Snapshots YAML template content in a new `plan_template_versions` table
whenever a plan is created from a template. Plans reference their version
via `template_version_id` so existing plans are unaffected by future
template file edits.
Key changes:
- Migration 0006: create `plan_template_versions` table (slug, version,
name, content JSON, content_hash SHA-256); add nullable FK
`template_version_id` to `agent_plans`
- Model `PlanTemplateVersion`: `findOrCreateFromTemplate()` deduplicates
identical content by hash; `historyFor()` returns versions newest-first
- `AgentPlan`: add `template_version_id` fillable and `templateVersion()`
relationship
- `PlanTemplateService::createPlan()`: snapshot raw template before
variable substitution; store version id and version number in metadata;
add `getVersionHistory()` and `getVersion()` public methods
- Tests: `TemplateVersionManagementTest` covering model behaviour, plan
creation snapshotting, deduplication, history ordering, and service
methods
Closes#35
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audited all PHP files for US English spellings per CLAUDE.md convention.
Fixed "Organize" → "Organise" in Mcp/Servers/Marketing.php docstring.
CSS/JS identifiers (borderColor, backgroundColor, transition-colors) and
array keys that form interface contracts with the host-uk/core package are
unchanged as they are not prose.
Closes#36
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `agentic.plan_retention_days` config (default 90 days via AGENTIC_PLAN_RETENTION_DAYS env)
- Add SoftDeletes and `archived_at` timestamp to AgentPlan model
- Add migration for `deleted_at` and `archived_at` columns on agent_plans
- Create `agentic:plan-cleanup` command with --dry-run and --days options
- Schedule retention cleanup to run daily via service provider
- Register PlanRetentionCommand in ConsoleBooting handler
- Add PlanRetentionTest feature test suite covering all retention scenarios
- Fix archive() to store archived_at as dedicated column (not metadata string)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace setup-php action with pre-built container.
Eliminates ~50s setup overhead per matrix job.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace anonymous class extending ApiKey with Mockery mock to avoid
requiring php-api package at load time
- Replace with() named args with withSomeOfArgs() for Mockery compat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- interpolateVariables: use string concatenation for triple-brace
placeholders instead of PHP string interpolation which only
produces single braces
- AgentToolRegistryTest: replace Cache::fake() (not available) with
Cache::flush() since array driver is already in-memory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add README to Mcp/Tools/Agent/ explaining:
- How ToolDependency works (contextExists, sessionState, entityExists)
- Context requirements (workspace_id, session_id) and multi-tenant safety
- Step-by-step guide for creating new tools
- AgentTool base class property and method reference
- Dependency resolution order and recommended declaration sequence
- Troubleshooting guide for common dependency errors
Closes#32
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document each PROVIDER_PATTERNS entry with real User-Agent examples,
add inline comments to BROWSER_INDICATORS and NON_AGENT_BOTS with
categorised UA examples, document MCP_TOKEN_HEADER with token format
details, and add class-level usage examples and detection priority
ordering.
Closes#31
Refs: DOC-001
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProcessContentTaskTest: set mock properties directly instead of
shouldReceive('__get') which doesn't reliably intercept property
access on Mockery mocks of non-existent classes.
HasStreamParsing: fix parseJSONStream chunked read bug where the
inner parse loop restarted at position 0 with stale state from
a previous partial parse. Track scan position across chunks.
AgentDetection: fix Postman regex \bPostman\b → \bPostman/ so it
matches PostmanRuntime (no word boundary between n and R).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enhance `validateVariables()` in `PlanTemplateService` to produce
actionable errors instead of the generic "Required variable '...' is missing".
Changes:
- Extracted `buildVariableError()` helper that composes the message from
the variable's `description`, `format`, `example`, and `examples` fields
- Added `naming_convention` key to the returned array so callers have
a constant reminder that variable names use snake_case
- Added a `NAMING_CONVENTION` private const to avoid string duplication
Tests (6 new cases in `PlanTemplateServiceTest`):
- description included in error message
- single `example` value included
- `examples` list (first two) included
- `format` hint included alongside example
- `naming_convention` present in both valid and invalid results
- bare variable (no description) still produces useful "missing" message
Closes#30
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log a warning for each AI provider registered without an API key so
that misconfiguration is surfaced at boot time (not silently on the
first API call). Each message names the environment variable to set:
ANTHROPIC_API_KEY – Claude
GOOGLE_AI_API_KEY – Gemini
OPENAI_API_KEY – OpenAI
Providers without a key remain registered but are marked unavailable
via isAvailable(), preserving backward compatibility.
- Add Log::warning() calls in registerProviders() for empty keys
- Extend AgenticManagerTest with a dedicated 'API key validation
warnings' describe block (7 new test cases)
- Update DX-002 in TODO.md as resolved
Closes#29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated workspace_id error messages in all MCP tools to include
actionable guidance and a documentation link. Affected tools:
PlanCreate, PlanGet, PlanList, StateSet, StateGet, StateList,
SessionStart.
Resolves DX-001 from TODO.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Track progress in a per-batch JSON state file after each article so
a mid-run crash leaves a recoverable checkpoint
- Add `maxRetries` parameter to generateBatch() with per-article retry
loop (default: 1 extra attempt)
- Add `resumeBatch()` to re-process only failed/pending articles,
skipping those already successfully generated in a prior run
- Add `loadBatchProgress()` public method for inspecting state
- State stores per-article status, attempt counts, error messages,
and timestamps for full observability
Tests: 6 new scenarios covering state persistence, resume capability,
retry logic, and the no-state error case
Closes#27
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap stream() in try/catch to prevent silent failures.
On exception, log the error and yield a structured error event:
['type' => 'error', 'message' => string]
Adds tests for connection errors, runtime exceptions, error event
format, and Log::error invocation. Closes ERR-001 in TODO.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cache the list of permitted tool names in `AgentToolRegistry::forApiKey()`
using a 1-hour TTL to avoid O(n) filtering on every request (PERF-002).
- Add `Cache::remember()` in `forApiKey()` storing tool names keyed by API
key ID (`agent_tool_registry:api_key:{id}`)
- Add `flushCacheForApiKey(int|string $id)` for explicit invalidation
- Add `CACHE_TTL` constant (3600 s) for easy tuning
- Invalidate cache in `AgentApiKeyService::updatePermissions()` and `revoke()`
so permission changes take effect immediately
- Add `tests/Unit/AgentToolRegistryTest.php` covering cache hit/miss,
per-key isolation, scope filtering, TTL constant, and flush behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves#23
- Replace per-dependency `AgentPhase::find()` loop with a single
`AgentPhase::whereIn('id', $dependencies)->get()` call, reducing
query count from N to 1 for any number of dependencies
- Short-circuit early when dependencies list is empty to avoid
unnecessary query at all
- Add tests: empty deps, skipped-dep passthrough, single-query
assertion, blocker shape validation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace individual __get expectations with a single closure that handles
all property access. Fixes ErrorException on undefined property access
with Mockery mocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Verify agent_sessions.session_id: unique() constraint creates an
implicit unique index (agent_sessions_session_id_unique) which is
sufficient for string lookups; no additional index required
- Drop redundant agent_plans_slug_index: the unique() constraint on
slug already provides agent_plans_slug_unique covering all lookups
- Add compound (workspace_id, slug) index on agent_plans for the
common routing pattern WHERE workspace_id = ? AND slug = ?
- Verify agent_workspace_states.key: already indexed via ->index('key')
in migration 000003; no additional index required
- Mark DB-002 as resolved in TODO.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hardcoded cache key in ForAgentsController with a config-based
key (`mcp.cache.for_agents_key`) and configurable TTL
(`mcp.cache.for_agents_ttl`). This prevents collisions with other modules
or packages that might use the same flat cache key.
- Add `cacheKey()` method on ForAgentsController, reads from config
- Add `cache` section to config.php with default key and TTL
- Dynamic Cache-Control max-age now follows the configured TTL
- Add ForAgentsControllerTest covering key customisation,
cache storage, invalidation, TTL, and response structure
Refs: TODO.md CQ-003
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch View/Modal/Admin/ApiKeyManager.php from Core\Api\Models\ApiKey
to Core\Mod\Agentic\Models\AgentApiKey and AgentApiKeyService, bringing
the workspace-owner admin UI into consistency with all other services.
Changes:
- Replace Core\Api\Models\ApiKey import with AgentApiKey + AgentApiKeyService
- Use AgentApiKeyService::create() for key generation
- Use AgentApiKey::forWorkspace() scoping in revokeKey() and render()
- Rename newKeyScopes → newKeyPermissions, toggleScope → togglePermission
- Expose availablePermissions() from AgentApiKey for the create form
- Update blade template: permissions field, getMaskedKey(), togglePermission,
dynamic permission checkboxes from AgentApiKey::availablePermissions()
- Add tests/Feature/ApiKeyManagerTest.php with integration coverage
- Mark CQ-002 resolved in TODO.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>