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
133 changes: 16 additions & 117 deletions packages/cli/src/agent-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ function agentChatTaskCode(options: AgentSandboxCodeOptions): string {
const timeoutLimit = Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 0
const agentBundles = normalizeAgentBundleSpecs(options.agentBundles ?? [])
const runtimeTask = normalizeRuntimeTask(options.runtimeTask, input)
const sandboxWorkspaceJson = JSON.stringify(sandboxWorkspace ?? null)

return `
if (function_exists('wp_set_current_user')) {
wp_set_current_user(1);
Expand All @@ -108,70 +106,6 @@ if (${timeoutLimit} > 0 && function_exists('set_time_limit')) {
set_time_limit(${timeoutLimit});
}

if (!defined('DATAMACHINE_WORKSPACE_PATH')) {
define('DATAMACHINE_WORKSPACE_PATH', ${JSON.stringify(SANDBOX_WORKSPACE_ROOT)});
}
if (!is_dir(DATAMACHINE_WORKSPACE_PATH)) {
if (function_exists('wp_mkdir_p')) {
wp_mkdir_p(DATAMACHINE_WORKSPACE_PATH);
} else {
mkdir(DATAMACHINE_WORKSPACE_PATH, 0775, true);
}
}

add_filter('datamachine_code_remote_workspace_backend_should_handle', '__return_false', 100);

$sandbox_workspace_adoptions = array();
if (function_exists('wp_get_ability')) {
$sandbox_workspace_contract = json_decode(${JSON.stringify(sandboxWorkspaceJson)}, true);
$sandbox_repo_backed_mounts = array();
if (is_array($sandbox_workspace_contract) && is_array($sandbox_workspace_contract['mounts'] ?? null)) {
foreach ($sandbox_workspace_contract['mounts'] as $sandbox_mount) {
if (!is_array($sandbox_mount)) {
continue;
}
$sandbox_mount_target = rtrim((string) ($sandbox_mount['target'] ?? ''), '/');
if ('' === $sandbox_mount_target) {
continue;
}
if ('repo-backed' === (string) ($sandbox_mount['sourceMode'] ?? '')) {
$sandbox_repo_backed_mounts[$sandbox_mount_target] = true;
}
}
}
$sandbox_adopt_callback = static function () use (&$sandbox_workspace_adoptions, $sandbox_repo_backed_mounts): void {
$sandbox_adopt_ability = wp_get_ability('datamachine-code/workspace-adopt') ?: wp_get_ability('datamachine/workspace-adopt');
if (!$sandbox_adopt_ability || !method_exists($sandbox_adopt_ability, 'execute')) {
return;
}
foreach (glob(rtrim(DATAMACHINE_WORKSPACE_PATH, '/') . '/*', GLOB_ONLYDIR) ?: array() as $sandbox_workspace_dir) {
$sandbox_workspace_name = basename($sandbox_workspace_dir);
if (is_file($sandbox_workspace_dir . '/.git') || isset($sandbox_repo_backed_mounts[rtrim($sandbox_workspace_dir, '/')])) {
$sandbox_workspace_adoptions[$sandbox_workspace_name] = array(
'success' => true,
'skipped' => true,
'reason' => 'repo_backed_mount',
'message' => 'Mounted repo-backed workspaces are treated as sandbox workspaces, not Data Machine primary checkouts.',
);
continue;
}
$sandbox_adopt_result = $sandbox_adopt_ability->execute(array(
'path' => $sandbox_workspace_dir,
'name' => $sandbox_workspace_name,
));
$sandbox_workspace_adoptions[$sandbox_workspace_name] = is_wp_error($sandbox_adopt_result)
? array('success' => false, 'error' => $sandbox_adopt_result->get_error_message())
: $sandbox_adopt_result;
}
};
if (class_exists('DataMachine\\Abilities\\PermissionHelper')) {
DataMachine\\Abilities\\PermissionHelper::run_as_authenticated($sandbox_adopt_callback);
} else {
$sandbox_adopt_callback();
}
}
$sandbox_stack['workspace_adoptions'] = $sandbox_workspace_adoptions;

$sandbox_agent_bundles = json_decode(${JSON.stringify(JSON.stringify(agentBundles))}, true);
$sandbox_agent_bundle_imports = wp_codebox_import_sandbox_agent_bundles(is_array($sandbox_agent_bundles) ? $sandbox_agent_bundles : array());
$sandbox_stack['agent_bundle_imports'] = $sandbox_agent_bundle_imports;
Expand Down Expand Up @@ -210,7 +144,7 @@ function wp_codebox_ensure_sandbox_default_agent(string $agent_slug, array $agen
return array('success' => true, 'agent' => $agent_slug, 'existing' => true);
}

if (!class_exists('DataMachine\\Engine\\Agents\\AgentRegistry')) {
if (!class_exists('WP_Agents_Registry')) {
return array('success' => false, 'agent' => $agent_slug, 'reason' => 'agent_registry_unavailable');
}

Expand All @@ -224,7 +158,12 @@ function wp_codebox_ensure_sandbox_default_agent(string $agent_slug, array $agen
'default_model' => (string) ($agent_input['model'] ?? ''),
), static fn($value): bool => '' !== $value);

\\DataMachine\\Engine\\Agents\\AgentRegistry::register($agent_slug, array(
$registry = WP_Agents_Registry::get_instance();
if (!$registry || !method_exists($registry, 'register')) {
return array('success' => false, 'agent' => $agent_slug, 'reason' => 'agent_registry_unavailable');
}

$registered = $registry->register($agent_slug, array(
'label' => 'WP Codebox Sandbox',
'description' => 'Default sandbox agent for WP Codebox runtime tasks.',
'owner_resolver' => static fn(): int => $owner_id,
Expand All @@ -236,16 +175,10 @@ function wp_codebox_ensure_sandbox_default_agent(string $agent_slug, array $agen
),
));

$summary = \\DataMachine\\Engine\\Agents\\AgentRegistry::reconcile();
$created = is_array($summary['created'] ?? null) ? $summary['created'] : array();
$existing = is_array($summary['existing'] ?? null) ? $summary['existing'] : array();

return array(
'success' => in_array($agent_slug, $created, true) || in_array($agent_slug, $existing, true) || (function_exists('wp_get_agent') && (bool) wp_get_agent($agent_slug)),
'success' => null !== $registered || (function_exists('wp_get_agent') && (bool) wp_get_agent($agent_slug)),
'agent' => $agent_slug,
'created' => in_array($agent_slug, $created, true),
'existing' => in_array($agent_slug, $existing, true),
'summary' => $summary,
'created' => null !== $registered,
);
}

Expand Down Expand Up @@ -426,12 +359,7 @@ if (!empty($sandbox_agent_bundle_import_failures)) {
);
} else {
$runtime_task_input = $runtime_task_run && is_array($sandbox_runtime_task['input'] ?? null) ? $sandbox_runtime_task['input'] : array();
$agent_execute_callback = static function () use ($ability, $runtime_task_run, $runtime_task_input, $decoded_agent_input) {
return $ability->execute($runtime_task_run ? $runtime_task_input : $decoded_agent_input);
};
$agent_result = class_exists('DataMachine\\Abilities\\PermissionHelper')
? DataMachine\\Abilities\\PermissionHelper::run_as_authenticated($agent_execute_callback)
: $agent_execute_callback();
$agent_result = $ability->execute($runtime_task_run ? $runtime_task_input : $decoded_agent_input);
if (is_wp_error($agent_result)) {
$sandbox_agent_runtime = array(
'agent_runtime' => array(
Expand Down Expand Up @@ -605,25 +533,8 @@ export function agentSandboxRunCode(task: string, code: string, providerPlugins:
return `<?php
require_once ABSPATH . 'wp-admin/includes/plugin.php';

if (!defined('DATAMACHINE_WORKSPACE_PATH')) {
define('DATAMACHINE_WORKSPACE_PATH', ${JSON.stringify(SANDBOX_WORKSPACE_ROOT)});
}
if (!is_dir(DATAMACHINE_WORKSPACE_PATH)) {
if (function_exists('wp_mkdir_p')) {
wp_mkdir_p(DATAMACHINE_WORKSPACE_PATH);
} else {
mkdir(DATAMACHINE_WORKSPACE_PATH, 0775, true);
}
}

add_filter('datamachine_code_remote_workspace_backend_should_handle', '__return_false', 100);

add_filter('datamachine_should_load_full_runtime', '__return_true', 1);

$plugins = array_merge(array(
'agents-api/agents-api.php',
'data-machine/data-machine.php',
'data-machine-code/data-machine-code.php',
), wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)));

function wp_codebox_plugin_entry_path(string $plugin): ?array {
Expand Down Expand Up @@ -773,16 +684,12 @@ do_action('wp_abilities_api_init');
$sandbox_task = ${phpStringLiteral(task)};
$sandbox_stack = array(
'plugins' => $activation_results,
'signals' => array(
'agents_api_loaded' => defined('AGENTS_API_LOADED'),
'agents_registry_class' => class_exists('WP_Agents_Registry'),
'data_machine_version' => defined('DATAMACHINE_VERSION') ? DATAMACHINE_VERSION : null,
'data_machine_permission_helper' => class_exists('DataMachine\\Abilities\\PermissionHelper'),
'data_machine_code_version' => defined('DATAMACHINE_CODE_VERSION') ? DATAMACHINE_CODE_VERSION : null,
'data_machine_code_workspace' => class_exists('DataMachineCode\\Workspace\\Workspace'),
'provider_plugins' => wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
'provider_plugin_files' => wp_codebox_provider_plugin_file_diagnostics(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
),
'signals' => array(
'agents_api_loaded' => defined('AGENTS_API_LOADED'),
'agents_registry_class' => class_exists('WP_Agents_Registry'),
'provider_plugins' => wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
'provider_plugin_files' => wp_codebox_provider_plugin_file_diagnostics(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
),
);

ob_start();
Expand Down Expand Up @@ -814,12 +721,8 @@ export function agentRuntimeProbeCode(providerPlugins: Array<{ slug: string }>):
return `<?php
require_once ABSPATH . 'wp-admin/includes/plugin.php';

add_filter('datamachine_should_load_full_runtime', '__return_true', 1);

$plugins = array_merge(array(
'agents-api/agents-api.php',
'data-machine/data-machine.php',
'data-machine-code/data-machine-code.php',
), wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)));

function wp_codebox_plugin_entry_path(string $plugin): ?array {
Expand Down Expand Up @@ -955,10 +858,6 @@ echo json_encode(
'signals' => array(
'agents_api_loaded' => defined('AGENTS_API_LOADED'),
'agents_registry_class' => class_exists('WP_Agents_Registry'),
'data_machine_version' => defined('DATAMACHINE_VERSION') ? DATAMACHINE_VERSION : null,
'data_machine_permission_helper' => class_exists('DataMachine\\\\Abilities\\\\PermissionHelper'),
'data_machine_code_version' => defined('DATAMACHINE_CODE_VERSION') ? DATAMACHINE_CODE_VERSION : null,
'data_machine_code_workspace' => class_exists('DataMachineCode\\\\Workspace\\\\Workspace'),
'provider_plugins' => wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
'provider_plugin_files' => wp_codebox_provider_plugin_file_diagnostics(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
),
Expand Down
11 changes: 0 additions & 11 deletions packages/cli/src/recipe-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ export interface PreparedExtraPlugin {

type BootActivePluginCandidate = Pick<PreparedExtraPlugin, "pluginFile" | "activate" | "loadAs">

function phpSingleQuotedLiteral(value: string): string {
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`
}

export interface RecipeStagedFileProvenance {
kind: "local"
original: string
Expand Down Expand Up @@ -1213,8 +1209,6 @@ export function installMuPluginsCode(extraPlugins: PreparedExtraPlugin[]): strin
return null
}

const workspaceRootLiteral = phpSingleQuotedLiteral(SANDBOX_WORKSPACE_ROOT)

return `$plugins = ${JSON.stringify(muPlugins)};
$runtime_dir = WPMU_PLUGIN_DIR . '/wp-codebox-runtime';
if (!is_dir(WPMU_PLUGIN_DIR) && !mkdir(WPMU_PLUGIN_DIR, 0777, true) && !is_dir(WPMU_PLUGIN_DIR)) {
Expand All @@ -1233,11 +1227,6 @@ $lines = array(
'',
"defined( 'ABSPATH' ) || exit;",
'',
"if ( ! defined( 'DATAMACHINE_WORKSPACE_PATH' ) ) {",
" define( 'DATAMACHINE_WORKSPACE_PATH', ${workspaceRootLiteral} );",
"}",
"add_filter( 'datamachine_should_load_full_runtime', '__return_true', 1 );",
'',
);
foreach ($plugins as $plugin) {
if ('' === $plugin || str_starts_with($plugin, '/') || str_contains($plugin, '..') || !str_ends_with($plugin, '.php')) {
Expand Down
18 changes: 9 additions & 9 deletions scripts/agent-sandbox-code-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ async function main() {
assert.match(code, /agents_chat_runtime_principal_permission/, "sandbox chat should authorize through Agents API runtime-principal permission")
assert.match(code, /AgentsAPI\\AI\\WP_Agent_Execution_Principal/, "sandbox chat should emit a valid namespaced runtime principal class reference")
assert.doesNotMatch(code, /AgentsAPIAIWP_Agent_Execution_Principal/, "sandbox chat should not collapse namespaced runtime principal references")
assert.match(code, /PermissionHelper::run_as_authenticated/, "sandbox chat should execute runtime abilities in an authenticated Data Machine context")
assert.match(code, /DataMachine\\Abilities\\PermissionHelper::run_as_authenticated/, "sandbox chat should emit a valid namespaced PermissionHelper class reference")
assert.match(code, /datamachine-code\/workspace-adopt/, "sandbox setup should use the canonical Data Machine Code workspace adopt ability")
assert.doesNotMatch(code, /PermissionHelper::run_as_authenticated\([^\)]*,\s*1\)/, "sandbox chat should not force a user-specific Data Machine capability check")
assert.doesNotMatch(code, /DataMachine\\/, "sandbox chat should not emit Data Machine class references")
assert.doesNotMatch(code, /PermissionHelper::run_as_authenticated/, "sandbox chat should leave runtime authentication to mounted components")
assert.doesNotMatch(code, /datamachine-code\/workspace-adopt/, "sandbox setup should leave workspace adoption to mounted components")
assert.doesNotMatch(code, /datamachine\/workspace-adopt/, "sandbox setup should not call legacy Data Machine workspace adoption")
assert.doesNotMatch(code, /DataMachineAbilitiesPermissionHelper/, "sandbox chat should not collapse namespaced PermissionHelper references")
assert.doesNotMatch(code, /datamachine_agent_mode_sandbox/, "sandbox chat should not depend on Data Machine agent mode filters")
assert.doesNotMatch(code, /WP_Codebox_Sandbox_Perception_Directive/, "sandbox chat should not register Data Machine directives")
Expand All @@ -105,11 +105,11 @@ async function main() {
assert.match(code, /\\"mode\\":\\"readwrite\\"/, "sandbox context should include mounted workspace mode")
assert.doesNotMatch(code, /Mounted Workspaces/, "sandbox chat should leave mounted workspace rendering to the generic runtime")
assert.doesNotMatch(code, /Bounded tree/, "sandbox chat should not inject Data Machine directive tree output")
assert.match(code, /datamachine_code_remote_workspace_backend_should_handle/, "sandbox mode should use the mounted workspace backend")
assert.match(code, /is_file\(\$sandbox_workspace_dir \. '\/.git'\)/, "sandbox setup should detect linked worktree mounts")
assert.match(code, /\$sandbox_repo_backed_mounts/, "sandbox setup should track repo-backed mounts from the workspace contract")
assert.match(code, /repo_backed_mount/, "sandbox setup should report repo-backed mounts as non-fatal diagnostics")
assert.ok(code.indexOf("repo_backed_mount") < code.indexOf("$sandbox_adopt_result = $sandbox_adopt_ability->execute"), "repo-backed mounts should be skipped before Data Machine workspace adoption")
assert.doesNotMatch(code, /datamachine_code_remote_workspace_backend_should_handle/, "sandbox mode should not hardcode Data Machine workspace backend filters")
assert.doesNotMatch(code, /\$sandbox_workspace_adoptions/, "sandbox setup should not own runtime-specific workspace adoption")
assert.doesNotMatch(code, /repo_backed_mount/, "sandbox setup should leave repo-backed mount handling to mounted components")
assert.doesNotMatch(code, /data-machine\/data-machine\.php/, "sandbox setup should not hardcode Data Machine plugin activation")
assert.doesNotMatch(code, /data-machine-code\/data-machine-code\.php/, "sandbox setup should not hardcode Data Machine Code plugin activation")
assert.match(code, /\\"tool_policy\\":\{\\"mode\\":\\"allow\\",\\"tools\\":\[\\"workspace_read\\",\\"workspace_write\\",\\"workspace_edit\\"\]/, "sandbox agent tool policy should include only sandbox-visible runtime tool ids")
assert.match(code, /wp_codebox_import_sandbox_agent_bundles/, "sandbox setup should import declared runtime agent bundles")
assert.match(code, /wp_agent_import_runtime_bundles/, "sandbox setup should consume the generic runtime bundle helper")
Expand Down
17 changes: 9 additions & 8 deletions scripts/agent-sandbox-workspace-root-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import { readFile } from "node:fs/promises"

async function main() {
const source = await readFile("packages/cli/src/agent-code.ts", "utf8")
const defineNeedle = "define('DATAMACHINE_WORKSPACE_PATH'"
const mkdirNeedle = "wp_mkdir_p(DATAMACHINE_WORKSPACE_PATH)"
const importNeedle = "wp_codebox_import_sandbox_agent_bundles"
const pluginsLoadedNeedle = "do_action('plugins_loaded')"
const initNeedle = "do_action('init')"
const abilitiesNeedle = "do_action('wp_abilities_api_init')"

assert.match(source, /define\('DATAMACHINE_WORKSPACE_PATH'/, "sandbox code should define the DMC workspace path")
assert.match(source, /wp_mkdir_p\(DATAMACHINE_WORKSPACE_PATH\)/, "sandbox code should create the DMC workspace path")
assert.ok(source.indexOf(defineNeedle) < source.indexOf(mkdirNeedle), "workspace root should be defined before creation")
assert.ok(source.indexOf(mkdirNeedle) < source.indexOf(importNeedle), "workspace root should exist before runtime bundle imports")
assert.doesNotMatch(source, /DATAMACHINE_WORKSPACE_PATH/, "sandbox boot should not define a Data Machine workspace path")
assert.doesNotMatch(source, /datamachine_code_remote_workspace_backend_should_handle/, "sandbox boot should not set Data Machine workspace backend filters")
assert.doesNotMatch(source, /datamachine-code\/workspace-adopt/, "sandbox boot should not adopt Data Machine workspaces")
assert.ok(source.indexOf(pluginsLoadedNeedle) < source.indexOf(initNeedle), "mounted components should see plugins_loaded before init")
assert.ok(source.indexOf(initNeedle) < source.indexOf(abilitiesNeedle), "mounted components should self-configure before abilities initialize")

console.log("agent sandbox workspace root smoke ok")
console.log("agent sandbox generic boot seam smoke ok")
}

main().catch((error) => {
Expand Down
Loading