Skip to content

Commit 134b7a1

Browse files
authored
Merge pull request #981 from Automattic/issue-842-decouple-agent-boot
Decouple agent sandbox boot from Data Machine
2 parents 6bfca42 + b5d8c07 commit 134b7a1

5 files changed

Lines changed: 48 additions & 148 deletions

File tree

packages/cli/src/agent-code.ts

Lines changed: 16 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@ function agentChatTaskCode(options: AgentSandboxCodeOptions): string {
9797
const timeoutLimit = Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 0
9898
const agentBundles = normalizeAgentBundleSpecs(options.agentBundles ?? [])
9999
const runtimeTask = normalizeRuntimeTask(options.runtimeTask, input)
100-
const sandboxWorkspaceJson = JSON.stringify(sandboxWorkspace ?? null)
101-
102100
return `
103101
if (function_exists('wp_set_current_user')) {
104102
wp_set_current_user(1);
@@ -108,70 +106,6 @@ if (${timeoutLimit} > 0 && function_exists('set_time_limit')) {
108106
set_time_limit(${timeoutLimit});
109107
}
110108
111-
if (!defined('DATAMACHINE_WORKSPACE_PATH')) {
112-
define('DATAMACHINE_WORKSPACE_PATH', ${JSON.stringify(SANDBOX_WORKSPACE_ROOT)});
113-
}
114-
if (!is_dir(DATAMACHINE_WORKSPACE_PATH)) {
115-
if (function_exists('wp_mkdir_p')) {
116-
wp_mkdir_p(DATAMACHINE_WORKSPACE_PATH);
117-
} else {
118-
mkdir(DATAMACHINE_WORKSPACE_PATH, 0775, true);
119-
}
120-
}
121-
122-
add_filter('datamachine_code_remote_workspace_backend_should_handle', '__return_false', 100);
123-
124-
$sandbox_workspace_adoptions = array();
125-
if (function_exists('wp_get_ability')) {
126-
$sandbox_workspace_contract = json_decode(${JSON.stringify(sandboxWorkspaceJson)}, true);
127-
$sandbox_repo_backed_mounts = array();
128-
if (is_array($sandbox_workspace_contract) && is_array($sandbox_workspace_contract['mounts'] ?? null)) {
129-
foreach ($sandbox_workspace_contract['mounts'] as $sandbox_mount) {
130-
if (!is_array($sandbox_mount)) {
131-
continue;
132-
}
133-
$sandbox_mount_target = rtrim((string) ($sandbox_mount['target'] ?? ''), '/');
134-
if ('' === $sandbox_mount_target) {
135-
continue;
136-
}
137-
if ('repo-backed' === (string) ($sandbox_mount['sourceMode'] ?? '')) {
138-
$sandbox_repo_backed_mounts[$sandbox_mount_target] = true;
139-
}
140-
}
141-
}
142-
$sandbox_adopt_callback = static function () use (&$sandbox_workspace_adoptions, $sandbox_repo_backed_mounts): void {
143-
$sandbox_adopt_ability = wp_get_ability('datamachine-code/workspace-adopt') ?: wp_get_ability('datamachine/workspace-adopt');
144-
if (!$sandbox_adopt_ability || !method_exists($sandbox_adopt_ability, 'execute')) {
145-
return;
146-
}
147-
foreach (glob(rtrim(DATAMACHINE_WORKSPACE_PATH, '/') . '/*', GLOB_ONLYDIR) ?: array() as $sandbox_workspace_dir) {
148-
$sandbox_workspace_name = basename($sandbox_workspace_dir);
149-
if (is_file($sandbox_workspace_dir . '/.git') || isset($sandbox_repo_backed_mounts[rtrim($sandbox_workspace_dir, '/')])) {
150-
$sandbox_workspace_adoptions[$sandbox_workspace_name] = array(
151-
'success' => true,
152-
'skipped' => true,
153-
'reason' => 'repo_backed_mount',
154-
'message' => 'Mounted repo-backed workspaces are treated as sandbox workspaces, not Data Machine primary checkouts.',
155-
);
156-
continue;
157-
}
158-
$sandbox_adopt_result = $sandbox_adopt_ability->execute(array(
159-
'path' => $sandbox_workspace_dir,
160-
'name' => $sandbox_workspace_name,
161-
));
162-
$sandbox_workspace_adoptions[$sandbox_workspace_name] = is_wp_error($sandbox_adopt_result)
163-
? array('success' => false, 'error' => $sandbox_adopt_result->get_error_message())
164-
: $sandbox_adopt_result;
165-
}
166-
};
167-
if (class_exists('DataMachine\\Abilities\\PermissionHelper')) {
168-
DataMachine\\Abilities\\PermissionHelper::run_as_authenticated($sandbox_adopt_callback);
169-
} else {
170-
$sandbox_adopt_callback();
171-
}
172-
}
173-
$sandbox_stack['workspace_adoptions'] = $sandbox_workspace_adoptions;
174-
175109
$sandbox_agent_bundles = json_decode(${JSON.stringify(JSON.stringify(agentBundles))}, true);
176110
$sandbox_agent_bundle_imports = wp_codebox_import_sandbox_agent_bundles(is_array($sandbox_agent_bundles) ? $sandbox_agent_bundles : array());
177111
$sandbox_stack['agent_bundle_imports'] = $sandbox_agent_bundle_imports;
@@ -210,7 +144,7 @@ function wp_codebox_ensure_sandbox_default_agent(string $agent_slug, array $agen
210144
return array('success' => true, 'agent' => $agent_slug, 'existing' => true);
211145
}
212146
213-
if (!class_exists('DataMachine\\Engine\\Agents\\AgentRegistry')) {
147+
if (!class_exists('WP_Agents_Registry')) {
214148
return array('success' => false, 'agent' => $agent_slug, 'reason' => 'agent_registry_unavailable');
215149
}
216150
@@ -224,7 +158,12 @@ function wp_codebox_ensure_sandbox_default_agent(string $agent_slug, array $agen
224158
'default_model' => (string) ($agent_input['model'] ?? ''),
225159
), static fn($value): bool => '' !== $value);
226160
227-
\\DataMachine\\Engine\\Agents\\AgentRegistry::register($agent_slug, array(
161+
$registry = WP_Agents_Registry::get_instance();
162+
if (!$registry || !method_exists($registry, 'register')) {
163+
return array('success' => false, 'agent' => $agent_slug, 'reason' => 'agent_registry_unavailable');
164+
}
165+
166+
$registered = $registry->register($agent_slug, array(
228167
'label' => 'WP Codebox Sandbox',
229168
'description' => 'Default sandbox agent for WP Codebox runtime tasks.',
230169
'owner_resolver' => static fn(): int => $owner_id,
@@ -236,16 +175,10 @@ function wp_codebox_ensure_sandbox_default_agent(string $agent_slug, array $agen
236175
),
237176
));
238177
239-
$summary = \\DataMachine\\Engine\\Agents\\AgentRegistry::reconcile();
240-
$created = is_array($summary['created'] ?? null) ? $summary['created'] : array();
241-
$existing = is_array($summary['existing'] ?? null) ? $summary['existing'] : array();
242-
243178
return array(
244-
'success' => in_array($agent_slug, $created, true) || in_array($agent_slug, $existing, true) || (function_exists('wp_get_agent') && (bool) wp_get_agent($agent_slug)),
179+
'success' => null !== $registered || (function_exists('wp_get_agent') && (bool) wp_get_agent($agent_slug)),
245180
'agent' => $agent_slug,
246-
'created' => in_array($agent_slug, $created, true),
247-
'existing' => in_array($agent_slug, $existing, true),
248-
'summary' => $summary,
181+
'created' => null !== $registered,
249182
);
250183
}
251184
@@ -426,12 +359,7 @@ if (!empty($sandbox_agent_bundle_import_failures)) {
426359
);
427360
} else {
428361
$runtime_task_input = $runtime_task_run && is_array($sandbox_runtime_task['input'] ?? null) ? $sandbox_runtime_task['input'] : array();
429-
$agent_execute_callback = static function () use ($ability, $runtime_task_run, $runtime_task_input, $decoded_agent_input) {
430-
return $ability->execute($runtime_task_run ? $runtime_task_input : $decoded_agent_input);
431-
};
432-
$agent_result = class_exists('DataMachine\\Abilities\\PermissionHelper')
433-
? DataMachine\\Abilities\\PermissionHelper::run_as_authenticated($agent_execute_callback)
434-
: $agent_execute_callback();
362+
$agent_result = $ability->execute($runtime_task_run ? $runtime_task_input : $decoded_agent_input);
435363
if (is_wp_error($agent_result)) {
436364
$sandbox_agent_runtime = array(
437365
'agent_runtime' => array(
@@ -605,25 +533,8 @@ export function agentSandboxRunCode(task: string, code: string, providerPlugins:
605533
return `<?php
606534
require_once ABSPATH . 'wp-admin/includes/plugin.php';
607535
608-
if (!defined('DATAMACHINE_WORKSPACE_PATH')) {
609-
define('DATAMACHINE_WORKSPACE_PATH', ${JSON.stringify(SANDBOX_WORKSPACE_ROOT)});
610-
}
611-
if (!is_dir(DATAMACHINE_WORKSPACE_PATH)) {
612-
if (function_exists('wp_mkdir_p')) {
613-
wp_mkdir_p(DATAMACHINE_WORKSPACE_PATH);
614-
} else {
615-
mkdir(DATAMACHINE_WORKSPACE_PATH, 0775, true);
616-
}
617-
}
618-
619-
add_filter('datamachine_code_remote_workspace_backend_should_handle', '__return_false', 100);
620-
621-
add_filter('datamachine_should_load_full_runtime', '__return_true', 1);
622-
623536
$plugins = array_merge(array(
624537
'agents-api/agents-api.php',
625-
'data-machine/data-machine.php',
626-
'data-machine-code/data-machine-code.php',
627538
), wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)));
628539
629540
function wp_codebox_plugin_entry_path(string $plugin): ?array {
@@ -773,16 +684,12 @@ do_action('wp_abilities_api_init');
773684
$sandbox_task = ${phpStringLiteral(task)};
774685
$sandbox_stack = array(
775686
'plugins' => $activation_results,
776-
'signals' => array(
777-
'agents_api_loaded' => defined('AGENTS_API_LOADED'),
778-
'agents_registry_class' => class_exists('WP_Agents_Registry'),
779-
'data_machine_version' => defined('DATAMACHINE_VERSION') ? DATAMACHINE_VERSION : null,
780-
'data_machine_permission_helper' => class_exists('DataMachine\\Abilities\\PermissionHelper'),
781-
'data_machine_code_version' => defined('DATAMACHINE_CODE_VERSION') ? DATAMACHINE_CODE_VERSION : null,
782-
'data_machine_code_workspace' => class_exists('DataMachineCode\\Workspace\\Workspace'),
783-
'provider_plugins' => wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
784-
'provider_plugin_files' => wp_codebox_provider_plugin_file_diagnostics(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
785-
),
687+
'signals' => array(
688+
'agents_api_loaded' => defined('AGENTS_API_LOADED'),
689+
'agents_registry_class' => class_exists('WP_Agents_Registry'),
690+
'provider_plugins' => wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
691+
'provider_plugin_files' => wp_codebox_provider_plugin_file_diagnostics(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
692+
),
786693
);
787694
788695
ob_start();
@@ -814,12 +721,8 @@ export function agentRuntimeProbeCode(providerPlugins: Array<{ slug: string }>):
814721
return `<?php
815722
require_once ABSPATH . 'wp-admin/includes/plugin.php';
816723
817-
add_filter('datamachine_should_load_full_runtime', '__return_true', 1);
818-
819724
$plugins = array_merge(array(
820725
'agents-api/agents-api.php',
821-
'data-machine/data-machine.php',
822-
'data-machine-code/data-machine-code.php',
823726
), wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)));
824727
825728
function wp_codebox_plugin_entry_path(string $plugin): ?array {
@@ -955,10 +858,6 @@ echo json_encode(
955858
'signals' => array(
956859
'agents_api_loaded' => defined('AGENTS_API_LOADED'),
957860
'agents_registry_class' => class_exists('WP_Agents_Registry'),
958-
'data_machine_version' => defined('DATAMACHINE_VERSION') ? DATAMACHINE_VERSION : null,
959-
'data_machine_permission_helper' => class_exists('DataMachine\\\\Abilities\\\\PermissionHelper'),
960-
'data_machine_code_version' => defined('DATAMACHINE_CODE_VERSION') ? DATAMACHINE_CODE_VERSION : null,
961-
'data_machine_code_workspace' => class_exists('DataMachineCode\\\\Workspace\\\\Workspace'),
962861
'provider_plugins' => wp_codebox_provider_plugin_entries(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
963862
'provider_plugin_files' => wp_codebox_provider_plugin_file_diagnostics(json_decode(${JSON.stringify(JSON.stringify(providerPlugins))}, true)),
964863
),

packages/cli/src/recipe-sources.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ export interface PreparedExtraPlugin {
6060

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

63-
function phpSingleQuotedLiteral(value: string): string {
64-
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`
65-
}
66-
6763
export interface RecipeStagedFileProvenance {
6864
kind: "local"
6965
original: string
@@ -1213,8 +1209,6 @@ export function installMuPluginsCode(extraPlugins: PreparedExtraPlugin[]): strin
12131209
return null
12141210
}
12151211

1216-
const workspaceRootLiteral = phpSingleQuotedLiteral(SANDBOX_WORKSPACE_ROOT)
1217-
12181212
return `$plugins = ${JSON.stringify(muPlugins)};
12191213
$runtime_dir = WPMU_PLUGIN_DIR . '/wp-codebox-runtime';
12201214
if (!is_dir(WPMU_PLUGIN_DIR) && !mkdir(WPMU_PLUGIN_DIR, 0777, true) && !is_dir(WPMU_PLUGIN_DIR)) {
@@ -1233,11 +1227,6 @@ $lines = array(
12331227
'',
12341228
"defined( 'ABSPATH' ) || exit;",
12351229
'',
1236-
"if ( ! defined( 'DATAMACHINE_WORKSPACE_PATH' ) ) {",
1237-
" define( 'DATAMACHINE_WORKSPACE_PATH', ${workspaceRootLiteral} );",
1238-
"}",
1239-
"add_filter( 'datamachine_should_load_full_runtime', '__return_true', 1 );",
1240-
'',
12411230
);
12421231
foreach ($plugins as $plugin) {
12431232
if ('' === $plugin || str_starts_with($plugin, '/') || str_contains($plugin, '..') || !str_ends_with($plugin, '.php')) {

scripts/agent-sandbox-code-smoke.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ async function main() {
8282
assert.match(code, /agents_chat_runtime_principal_permission/, "sandbox chat should authorize through Agents API runtime-principal permission")
8383
assert.match(code, /AgentsAPI\\AI\\WP_Agent_Execution_Principal/, "sandbox chat should emit a valid namespaced runtime principal class reference")
8484
assert.doesNotMatch(code, /AgentsAPIAIWP_Agent_Execution_Principal/, "sandbox chat should not collapse namespaced runtime principal references")
85-
assert.match(code, /PermissionHelper::run_as_authenticated/, "sandbox chat should execute runtime abilities in an authenticated Data Machine context")
86-
assert.match(code, /DataMachine\\Abilities\\PermissionHelper::run_as_authenticated/, "sandbox chat should emit a valid namespaced PermissionHelper class reference")
87-
assert.match(code, /datamachine-code\/workspace-adopt/, "sandbox setup should use the canonical Data Machine Code workspace adopt ability")
88-
assert.doesNotMatch(code, /PermissionHelper::run_as_authenticated\([^\)]*,\s*1\)/, "sandbox chat should not force a user-specific Data Machine capability check")
85+
assert.doesNotMatch(code, /DataMachine\\/, "sandbox chat should not emit Data Machine class references")
86+
assert.doesNotMatch(code, /PermissionHelper::run_as_authenticated/, "sandbox chat should leave runtime authentication to mounted components")
87+
assert.doesNotMatch(code, /datamachine-code\/workspace-adopt/, "sandbox setup should leave workspace adoption to mounted components")
88+
assert.doesNotMatch(code, /datamachine\/workspace-adopt/, "sandbox setup should not call legacy Data Machine workspace adoption")
8989
assert.doesNotMatch(code, /DataMachineAbilitiesPermissionHelper/, "sandbox chat should not collapse namespaced PermissionHelper references")
9090
assert.doesNotMatch(code, /datamachine_agent_mode_sandbox/, "sandbox chat should not depend on Data Machine agent mode filters")
9191
assert.doesNotMatch(code, /WP_Codebox_Sandbox_Perception_Directive/, "sandbox chat should not register Data Machine directives")
@@ -105,11 +105,11 @@ async function main() {
105105
assert.match(code, /\\"mode\\":\\"readwrite\\"/, "sandbox context should include mounted workspace mode")
106106
assert.doesNotMatch(code, /Mounted Workspaces/, "sandbox chat should leave mounted workspace rendering to the generic runtime")
107107
assert.doesNotMatch(code, /Bounded tree/, "sandbox chat should not inject Data Machine directive tree output")
108-
assert.match(code, /datamachine_code_remote_workspace_backend_should_handle/, "sandbox mode should use the mounted workspace backend")
109-
assert.match(code, /is_file\(\$sandbox_workspace_dir \. '\/.git'\)/, "sandbox setup should detect linked worktree mounts")
110-
assert.match(code, /\$sandbox_repo_backed_mounts/, "sandbox setup should track repo-backed mounts from the workspace contract")
111-
assert.match(code, /repo_backed_mount/, "sandbox setup should report repo-backed mounts as non-fatal diagnostics")
112-
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")
108+
assert.doesNotMatch(code, /datamachine_code_remote_workspace_backend_should_handle/, "sandbox mode should not hardcode Data Machine workspace backend filters")
109+
assert.doesNotMatch(code, /\$sandbox_workspace_adoptions/, "sandbox setup should not own runtime-specific workspace adoption")
110+
assert.doesNotMatch(code, /repo_backed_mount/, "sandbox setup should leave repo-backed mount handling to mounted components")
111+
assert.doesNotMatch(code, /data-machine\/data-machine\.php/, "sandbox setup should not hardcode Data Machine plugin activation")
112+
assert.doesNotMatch(code, /data-machine-code\/data-machine-code\.php/, "sandbox setup should not hardcode Data Machine Code plugin activation")
113113
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")
114114
assert.match(code, /wp_codebox_import_sandbox_agent_bundles/, "sandbox setup should import declared runtime agent bundles")
115115
assert.match(code, /wp_agent_import_runtime_bundles/, "sandbox setup should consume the generic runtime bundle helper")

scripts/agent-sandbox-workspace-root-smoke.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import { readFile } from "node:fs/promises"
33

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

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

15-
console.log("agent sandbox workspace root smoke ok")
16+
console.log("agent sandbox generic boot seam smoke ok")
1617
}
1718

1819
main().catch((error) => {

0 commit comments

Comments
 (0)