Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/agent-task-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ on:
- "tests/production-boundary-enforcement.test.ts"
- "tests/runtime-tool-policy.test.ts"
- "packages/runtime-playground/src/phpunit-command-handlers.ts"
- "packages/runtime-playground/src/playground-cli-runner.ts"
- "packages/runtime-playground/src/playground-wordpress-archive-cache.ts"
- "tests/playground-custom-archive-cache*.test.ts"
- "tests/fixtures/playground-cache-lease-child.ts"
- "tests/playground-phpunit-readonly-cache.integration.test.ts"
- "tests/fixtures/phpunit-playground-harness/**"
- "packages/cli/src/bounded-recipe-plan.ts"
Expand Down Expand Up @@ -53,6 +57,10 @@ on:
- "tests/production-boundary-enforcement.test.ts"
- "tests/runtime-tool-policy.test.ts"
- "packages/runtime-playground/src/phpunit-command-handlers.ts"
- "packages/runtime-playground/src/playground-cli-runner.ts"
- "packages/runtime-playground/src/playground-wordpress-archive-cache.ts"
- "tests/playground-custom-archive-cache*.test.ts"
- "tests/fixtures/playground-cache-lease-child.ts"
- "tests/playground-phpunit-readonly-cache.integration.test.ts"
- "tests/fixtures/phpunit-playground-harness/**"
- "packages/cli/src/bounded-recipe-plan.ts"
Expand Down Expand Up @@ -84,6 +92,7 @@ jobs:
- run: npm run test:disposable-mysql-mysqli-e2e
- run: npm run test:runtime-sources-playground-integration
- run: npm run test:playground-phpunit-readonly-cache-integration
- run: npm run test:playground-custom-archive-cache
- run: npm run test:native-agent-task-playground-e2e
- run: npm run test:native-agent-task-interruption
- run: npm run test:trusted-apply-artifact-channel
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ kimaki tunnel -- sh -c 'npm run wp-codebox -- run \

When a caller exposes the local Playground through a tunnel or proxy, pass `--preview-public-url <url>` to report that public URL in `artifacts.preview.url`, `metadata.json`, and `files/review.json`. WP Codebox also passes the same URL to Playground as `site-url` and defines `WP_HOME` / `WP_SITEURL` in the sandbox config, so WordPress-generated links and canonical redirects align with the public preview URL. The local proxy URL remains recorded as `preview.localUrl`. If the fixed port is already occupied, WP Codebox fails clearly with `EADDRINUSE` and the requested `--preview-port` value.

Playground's content-addressed `custom-*.zip` WordPress archives are retained separately from stable release archives and cleaned after runtime use. The custom cache defaults to a seven-day age limit, 1 GiB, and 20 archives. Configure those bounds with `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_MAX_AGE_MS`, `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_MAX_BYTES`, and `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_MAX_COUNT`; configure crashed legacy lock recovery with `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_STALE_LOCK_MS`. All maintenance uses `WP_CODEBOX_PLAYGROUND_WORDPRESS_CACHE_DIR` when set. The Playground runtime package exports `maintainPlaygroundCustomArchiveCache()` for deterministic `diagnose`, `dry-run`, and `apply` evidence.
Playground's content-addressed `custom-*.zip` WordPress archives are retained separately from stable release archives and cleaned after runtime use. The custom cache defaults to a seven-day age limit, 1 GiB, and 20 archives. Configure those bounds with `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_MAX_AGE_MS`, `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_MAX_BYTES`, and `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_MAX_COUNT`. Active locks and runtimes use token-owned renewable leases; configure their duration with `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_LEASE_MS` and crashed legacy lock recovery with `WP_CODEBOX_PLAYGROUND_CUSTOM_ARCHIVE_STALE_LOCK_MS`. All maintenance uses `WP_CODEBOX_PLAYGROUND_WORDPRESS_CACHE_DIR` when set. The Playground runtime package exports `maintainPlaygroundCustomArchiveCache()` for deterministic `diagnose`, `dry-run`, and `apply` evidence, including filesystem protections, orphan sidecars, logical bytes removed, estimated allocated inode bytes removed, and the concurrently observed filesystem free-space delta.

Remote-host previews can opt into `--preview-bind <host>` with `--preview-port`. The flag changes the WP Codebox preview proxy bind address only; the upstream runtime server remains loopback-bound until backend bind-host control is available. The default stays `127.0.0.1`. Use `--preview-bind 0.0.0.0` only behind trusted firewall, tunnel, or reverse-proxy controls because the sandbox preview is reachable for the hold duration.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"test:playground-readonly-mounts-integration": "tsx tests/playground-readonly-mounts-integration.test.ts",
"test:playground-phpunit-readonly-cache-integration": "tsx tests/playground-phpunit-readonly-cache.integration.test.ts",
"test:playground-phpunit-bootstrap-failure-integration": "tsx tests/playground-phpunit-bootstrap-failure.integration.test.ts",
"test:playground-custom-archive-cache": "tsx tests/playground-custom-archive-cache.test.ts",
"test:playground-custom-archive-cache": "tsx tests/playground-custom-archive-cache.test.ts && tsx tests/playground-custom-archive-cache-process.test.ts && tsx tests/playground-custom-archive-cache.integration.test.ts",
"test:phpunit-runtime-failure-diagnostics": "tsx tests/phpunit-runtime-failure-diagnostics.test.ts",
"test:php-wasm-extension-manifests": "tsx tests/php-wasm-extension-manifests.test.ts",
"test:browser-task-builder": "tsx tests/browser-task-builder.test.ts",
Expand Down
42 changes: 36 additions & 6 deletions packages/runtime-playground/src/playground-cli-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as PlaygroundStorage from "@wp-playground/storage"
import { resolveWordPressRelease } from "@wp-playground/wordpress"
import { phpEnvAssignments, phpLiteral, phpWpConfigDefineAssignments } from "./php-snippets.js"
import { stageReadonlyPlaygroundMounts, type ReadonlyMountStaging } from "./mount-materialization.js"
import { acquirePlaygroundArchiveReference, isCustomPlaygroundWordPressArchive, maintainPlaygroundCustomArchiveCache, playgroundWordPressArchiveCacheDirectory, withPlaygroundArchiveCacheLock, type PlaygroundArchiveReference, type PlaygroundCustomArchiveCacheMaintenance } from "./playground-wordpress-archive-cache.js"
import { acquirePlaygroundArchiveReference, isCustomPlaygroundWordPressArchive, maintainPlaygroundCustomArchiveCache, playgroundWordPressArchiveCacheDirectory, withPlaygroundArchiveCacheLock, type PlaygroundArchiveReference, type PlaygroundCustomArchiveCacheDiagnostic, type PlaygroundCustomArchiveCacheMaintenance } from "./playground-wordpress-archive-cache.js"

const DEFAULT_RUNTIME_PHP_INI_ENTRIES = { memory_limit: "512M" }

Expand Down Expand Up @@ -66,6 +66,7 @@ export async function startPlaygroundCliServer(spec: RuntimeCreateSpec, mounts:
let readonlyMountStaging: ReadonlyMountStaging | undefined
let archiveReference: PlaygroundArchiveReference | undefined
let cacheMaintenance: PlaygroundCustomArchiveCacheMaintenance | undefined
let cacheMaintenanceDiagnostics: PlaygroundCustomArchiveCacheDiagnostic[] = []
let usesArchiveCache = false
try {
if (spec.preview?.port) {
Expand All @@ -89,11 +90,13 @@ export async function startPlaygroundCliServer(spec: RuntimeCreateSpec, mounts:
const stagedMounts = readonlyMountStaging.mounts
const preinstallMounts = stagedMounts.filter((mount) => mount.target === "/wordpress/wp-config.php")
const postinstallMounts = stagedMounts.filter((mount) => mount.target !== "/wordpress/wp-config.php")
const wordpressStartupAsset = wordpressDirectory ? undefined : await resolvePlaygroundWordPressStartupAsset(spec.environment.version, spec.environment.assets?.wordpressZip)
archiveReference = wordpressStartupAsset?.archiveReference
if (usesArchiveCache) {
cacheMaintenance = await maintainPlaygroundCustomArchiveCache().catch(() => undefined)
const maintenance = await automaticPlaygroundCustomArchiveCacheMaintenance()
cacheMaintenance = maintenance.result
cacheMaintenanceDiagnostics = maintenance.diagnostics
}
const wordpressStartupAsset = wordpressDirectory ? undefined : await resolvePlaygroundWordPressStartupAsset(spec.environment.version, spec.environment.assets?.wordpressZip)
archiveReference = wordpressStartupAsset?.archiveReference
const cacheValidation = wordpressStartupAsset?.cacheValidation ?? {
version: spec.environment.version ?? "mounted-wordpress-source",
sourceUrl: wordpressDirectory ?? "",
Expand All @@ -103,6 +106,9 @@ export async function startPlaygroundCliServer(spec: RuntimeCreateSpec, mounts:
if (cacheMaintenance) {
cacheValidation.retention = cacheMaintenance
}
if (cacheMaintenanceDiagnostics.length > 0) {
cacheValidation.retentionDiagnostics = cacheMaintenanceDiagnostics
}
const blueprintSummary = summarizeBlueprint(spec.environment.blueprint)
if (blueprintSummary.steps > 0) {
emitProgress("preview:applying-blueprint", "running", "Applying site setup", blueprintSummary)
Expand Down Expand Up @@ -187,7 +193,7 @@ export async function startPlaygroundCliServer(spec: RuntimeCreateSpec, mounts:
} finally {
await archiveReference?.release().catch(() => undefined)
if (usesArchiveCache) {
await maintainPlaygroundCustomArchiveCache().catch(() => undefined)
await automaticPlaygroundCustomArchiveCacheMaintenance(true)
}
await readonlyMountStaging?.[Symbol.asyncDispose]()
}
Expand All @@ -200,7 +206,7 @@ export async function startPlaygroundCliServer(spec: RuntimeCreateSpec, mounts:

await archiveReference?.release().catch(() => undefined)
if (usesArchiveCache) {
await maintainPlaygroundCustomArchiveCache().catch(() => undefined)
await automaticPlaygroundCustomArchiveCacheMaintenance(true)
}
await readonlyMountStaging?.[Symbol.asyncDispose]()
if (spec.preview?.port && errorHasCode(error, "EADDRINUSE")) {
Expand Down Expand Up @@ -623,6 +629,29 @@ function errorDetail(error: unknown): Record<string, unknown> {
return { message: String(error) }
}

async function automaticPlaygroundCustomArchiveCacheMaintenance(warnOnFailure = false): Promise<{ result?: PlaygroundCustomArchiveCacheMaintenance; diagnostics: PlaygroundCustomArchiveCacheDiagnostic[] }> {
try {
const result = await maintainPlaygroundCustomArchiveCache()
if (warnOnFailure) {
for (const diagnostic of result.diagnostics) {
console.warn(`[wp-codebox] ${diagnostic.code}: ${diagnostic.message}`)
}
}
return { result, diagnostics: result.diagnostics }
} catch (error) {
const diagnostic = {
code: "playground-custom-archive-cache-maintenance-failed",
message: error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500),
severity: "warning" as const,
path: playgroundWordPressArchiveCacheDirectory(),
}
if (warnOnFailure) {
console.warn(`[wp-codebox] ${diagnostic.code}: ${diagnostic.message}`)
}
return { diagnostics: [diagnostic] }
}
}

export interface PlaygroundWordPressArchiveCacheValidation {
version: string
sourceUrl: string
Expand All @@ -640,6 +669,7 @@ export interface PlaygroundWordPressArchiveCacheValidation {
deleted: boolean
}>
retention?: PlaygroundCustomArchiveCacheMaintenance
retentionDiagnostics?: PlaygroundCustomArchiveCacheDiagnostic[]
}

export interface PlaygroundWordPressArchiveCacheValidationOptions {
Expand Down
Loading
Loading