feat(a0-connector): lazy-load remote tool guidance
Move A0 CLI remote execution and file-editing guidance into skills, and gate compact remote tool stubs on subscribed CLI capabilities instead of always advertising unavailable tools. Retire verbose per-turn remote guidance extras while preserving connector protocol and tool schemas.
Alessandro committed
Apr 28, 2026 at 16:09 UTC
7c71185f1649b4acf05b9f419277a52eaa0fe009
17 files changed
+548
-492
plugins/_a0_connector/extensions/python/_functions/extensions/python/system_prompt/_11_tools_prompt/build_prompt/end/_70_include_remote_tool_stubs.py
new
+174
@@ -0,0 +1,174 @@
1
+from __future__ import annotations
2
+
3
+from dataclasses import dataclass
4
+from typing import Any
5
+
6
+from helpers.extension import Extension
7
+
8
+from plugins._a0_connector.helpers.ws_runtime import (
9
+ computer_use_metadata_for_sid,
10
+ remote_exec_metadata_for_sid,
11
+ remote_file_metadata_for_sid,
12
+ subscribed_sids_for_context,
13
+)
14
+
15
+
16
+@dataclass(frozen=True)
17
+class RemoteFileCapability:
18
+ available: bool
19
+ write_enabled: bool = False
20
+ access_mode: str = "Unknown"
21
+ advertised: bool = False
22
+
23
+
24
+class IncludeRemoteToolStubs(Extension):
25
+ def execute(self, data: dict[str, Any] = {}, **kwargs: Any) -> None:
26
+ if not self.agent:
27
+ return
28
+
29
+ result = data.get("result")
30
+ if not isinstance(result, str):
31
+ return
32
+
33
+ context_id = str(getattr(self.agent.context, "id", "") or "").strip()
34
+ if not context_id:
35
+ return
36
+
37
+ stubs: list[str] = []
38
+ file_capability = _remote_file_capability(context_id)
39
+
40
+ if file_capability.available:
41
+ stubs.append(
42
+ self.agent.read_prompt(
43
+ "agent.connector_tool.text_editor_remote.md",
44
+ access_mode=file_capability.access_mode,
45
+ write_guidance=_file_write_guidance(file_capability),
46
+ )
47
+ )
48
+
49
+ if _remote_exec_available(context_id):
50
+ stubs.append(
51
+ self.agent.read_prompt(
52
+ "agent.connector_tool.code_execution_remote.md",
53
+ access_mode=file_capability.access_mode,
54
+ write_runtime_note=_exec_write_runtime_note(file_capability),
55
+ )
56
+ )
57
+
58
+ computer_use = _computer_use_capability(context_id)
59
+ if computer_use:
60
+ stubs.append(
61
+ self.agent.read_prompt(
62
+ "agent.connector_tool.computer_use_remote.md",
63
+ backend=computer_use["backend"],
64
+ trust_mode=computer_use["trust_mode"],
65
+ features=computer_use["features"],
66
+ )
67
+ )
68
+
69
+ if not stubs:
70
+ return
71
+
72
+ data["result"] = (
73
+ result.rstrip()
74
+ + "\n\n"
75
+ + "\n\n".join(stub.strip() for stub in stubs if stub.strip())
76
+ )
77
+
78
+
79
+def _subscribed_sids(context_id: str) -> list[str]:
80
+ return sorted(subscribed_sids_for_context(context_id))
81
+
82
+
83
+def _remote_file_capability(context_id: str) -> RemoteFileCapability:
84
+ saw_advertised = False
85
+ saw_enabled = False
86
+ saw_write_enabled = False
87
+
88
+ for sid in _subscribed_sids(context_id):
89
+ metadata = remote_file_metadata_for_sid(sid)
90
+ if not metadata:
91
+ continue
92
+ saw_advertised = True
93
+ if not metadata.get("enabled", True):
94
+ continue
95
+ saw_enabled = True
96
+ if metadata.get("write_enabled"):
97
+ saw_write_enabled = True
98
+
99
+ if not saw_enabled:
100
+ return RemoteFileCapability(
101
+ available=False,
102
+ access_mode="Disabled" if saw_advertised else "Unknown",
103
+ advertised=saw_advertised,
104
+ )
105
+
106
+ return RemoteFileCapability(
107
+ available=True,
108
+ write_enabled=saw_write_enabled,
109
+ access_mode="Read&Write" if saw_write_enabled else "Read only",
110
+ advertised=True,
111
+ )
112
+
113
+
114
+def _remote_exec_available(context_id: str) -> bool:
115
+ for sid in _subscribed_sids(context_id):
116
+ metadata = remote_exec_metadata_for_sid(sid)
117
+ if metadata and metadata.get("enabled"):
118
+ return True
119
+ return False
120
+
121
+
122
+def _computer_use_capability(context_id: str) -> dict[str, str] | None:
123
+ for sid in _subscribed_sids(context_id):
124
+ metadata = computer_use_metadata_for_sid(sid)
125
+ if not metadata or not metadata.get("supported") or not metadata.get("enabled"):
126
+ continue
127
+
128
+ backend_id = str(metadata.get("backend_id") or "").strip() or "unknown"
129
+ backend_family = str(metadata.get("backend_family") or "").strip()
130
+ backend = backend_id if not backend_family else f"{backend_id}/{backend_family}"
131
+ trust_mode = str(metadata.get("trust_mode") or "").strip() or "unknown"
132
+ features_value = metadata.get("features")
133
+ if isinstance(features_value, (list, tuple)):
134
+ features = ", ".join(
135
+ str(item).strip() for item in features_value if str(item).strip()
136
+ )
137
+ else:
138
+ features = ""
139
+
140
+ return {
141
+ "backend": backend,
142
+ "trust_mode": trust_mode,
143
+ "features": features or "none advertised",
144
+ }
145
+
146
+ return None
147
+
148
+
149
+def _file_write_guidance(capability: RemoteFileCapability) -> str:
150
+ if capability.write_enabled:
151
+ return "Writes and patches are currently available."
152
+ return (
153
+ "Writes and patches are disabled until the user switches the CLI to "
154
+ "Read&Write with F3."
155
+ )
156
+
157
+
158
+def _exec_write_runtime_note(capability: RemoteFileCapability) -> str:
159
+ if capability.write_enabled:
160
+ return "Mutating runtimes are currently available because local access is Read&Write."
161
+ if capability.available:
162
+ return (
163
+ "Mutating runtimes are disabled until the user switches the CLI to "
164
+ "Read&Write with F3; use output/reset only for existing sessions."
165
+ )
166
+ if capability.advertised:
167
+ return (
168
+ "The CLI advertises remote file access as disabled; mutating runtimes "
169
+ "are unavailable until local file access is enabled."
170
+ )
171
+ return (
172
+ "The CLI did not advertise a file access mode; prefer non-mutating "
173
+ "inspection until access is clear."
174
+ )
plugins/_a0_connector/extensions/python/message_loop_prompts_after/_77_include_computer_use_remote.py
deleted
-50
@@ -1,50 +0,0 @@
1
-from __future__ import annotations
2
-
3
-from agent import LoopData
4
-from helpers.extension import Extension
5
-
6
-from plugins._a0_connector.helpers.ws_runtime import (
7
- computer_use_metadata_for_sid,
8
- select_computer_use_target_sid,
9
-)
10
-
11
-
12
-class IncludeComputerUseRemote(Extension):
13
- async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
14
- if not self.agent:
15
- return
16
-
17
- context_id = getattr(self.agent.context, "id", "")
18
- if not context_id:
19
- return
20
-
21
- sid = select_computer_use_target_sid(context_id)
22
- if not sid:
23
- return
24
-
25
- metadata = computer_use_metadata_for_sid(sid)
26
- if not metadata or not metadata.get("supported") or not metadata.get("enabled"):
27
- return
28
-
29
- backend_id = str(metadata.get("backend_id") or "").strip() or "unknown"
30
- backend_family = str(metadata.get("backend_family") or "").strip()
31
- backend = backend_id if not backend_family else f"{backend_id}/{backend_family}"
32
- trust_mode = str(metadata.get("trust_mode") or "").strip() or "unknown"
33
- support_reason = str(metadata.get("support_reason") or "").strip() or "No support details available."
34
-
35
- features_value = metadata.get("features")
36
- if isinstance(features_value, (list, tuple)):
37
- features = ", ".join(str(item).strip() for item in features_value if str(item).strip())
38
- else:
39
- features = ""
40
- if not features:
41
- features = "none advertised"
42
-
43
- prompt = self.agent.read_prompt(
44
- "agent.extras.computer_use_remote.md",
45
- backend=backend,
46
- trust_mode=trust_mode,
47
- features=features,
48
- support_reason=support_reason,
49
- )
50
- loop_data.extras_temporary["computer_use_remote"] = prompt
plugins/_a0_connector/extensions/python/message_loop_prompts_after/_78_include_code_execution_remote.py
deleted
-127
@@ -1,127 +0,0 @@
1
-from __future__ import annotations
2
-
3
-from agent import LoopData
4
-from helpers.extension import Extension
5
-
6
-from plugins._a0_connector.helpers.exec_config import build_exec_config
7
-from plugins._a0_connector.helpers.ws_runtime import (
8
- remote_file_metadata_for_sid,
9
- select_remote_exec_target_sid,
10
-)
11
-
12
-
13
-def _format_timeouts(payload: dict[str, int]) -> str:
14
- return ", ".join(f"{key}={value}" for key, value in payload.items()) or "none"
15
-
16
-
17
-def _format_patterns(value: object) -> str:
18
- if isinstance(value, (list, tuple)):
19
- items = [str(item).strip() for item in value if str(item).strip()]
20
- else:
21
- items = []
22
- return ", ".join(items) or "none"
23
-
24
-
25
-class IncludeCodeExecutionRemote(Extension):
26
- async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
27
- if not self.agent:
28
- return
29
-
30
- context_id = getattr(self.agent.context, "id", "")
31
- if not context_id:
32
- return
33
-
34
- sid = select_remote_exec_target_sid(context_id, require_writes=False)
35
- if not sid:
36
- return
37
-
38
- metadata = remote_file_metadata_for_sid(sid)
39
- if metadata is None:
40
- access_mode = "Read&Write (legacy/unknown)"
41
- write_runtime_guidance = (
42
- "- `runtime=terminal`, `python`, `nodejs`, and `input` are expected to be "
43
- "available, but this CLI did not advertise an explicit F3 access mode.\n"
44
- "- Use shell syntax that matches the remote host (for example, PowerShell on "
45
- "Windows)."
46
- )
47
- write_runtime_examples = """```json
48
-{
49
- "tool_name": "code_execution_remote",
50
- "tool_args": {
51
- "runtime": "terminal",
52
- "session": 0,
53
- "code": "pwd"
54
- }
55
-}
56
-```
57
-
58
-```json
59
-{
60
- "tool_name": "code_execution_remote",
61
- "tool_args": {
62
- "runtime": "python",
63
- "session": 0,
64
- "code": "import os\\nprint(os.getcwd())"
65
- }
66
-}
67
-```"""
68
- elif metadata.get("write_enabled"):
69
- access_mode = "Read&Write"
70
- write_runtime_guidance = (
71
- "- `runtime=terminal`, `python`, `nodejs`, and `input` may modify files on "
72
- "the remote CLI machine. Use them only when shell-backed execution is the "
73
- "right tool for the job.\n"
74
- "- Use shell syntax that matches the remote host (for example, PowerShell on "
75
- "Windows)."
76
- )
77
- write_runtime_examples = """```json
78
-{
79
- "tool_name": "code_execution_remote",
80
- "tool_args": {
81
- "runtime": "terminal",
82
- "session": 0,
83
- "code": "pwd"
84
- }
85
-}
86
-```
87
-
88
-```json
89
-{
90
- "tool_name": "code_execution_remote",
91
- "tool_args": {
92
- "runtime": "python",
93
- "session": 0,
94
- "code": "import os\\nprint(os.getcwd())"
95
- }
96
-}
97
-```"""
98
- else:
99
- access_mode = "Read only"
100
- write_runtime_guidance = (
101
- "- `runtime=terminal`, `python`, `nodejs`, and `input` are disabled while "
102
- "local access is Read only. Press F3 to switch the host machine to Read&Write "
103
- "before starting new shell-backed work that could modify files."
104
- )
105
- write_runtime_examples = ""
106
-
107
- exec_config = build_exec_config(agent=self.agent)
108
- code_exec_timeouts = exec_config.get("code_exec_timeouts")
109
- output_timeouts = exec_config.get("output_timeouts")
110
- prompt_patterns = exec_config.get("prompt_patterns")
111
- dialog_patterns = exec_config.get("dialog_patterns")
112
-
113
- prompt = self.agent.read_prompt(
114
- "agent.extras.code_execution_remote.md",
115
- access_mode=access_mode,
116
- write_runtime_guidance=write_runtime_guidance,
117
- write_runtime_examples=write_runtime_examples,
118
- code_exec_timeouts=_format_timeouts(
119
- code_exec_timeouts if isinstance(code_exec_timeouts, dict) else {}
120
- ),
121
- output_timeouts=_format_timeouts(
122
- output_timeouts if isinstance(output_timeouts, dict) else {}
123
- ),
124
- prompt_patterns=_format_patterns(prompt_patterns),
125
- dialog_patterns=_format_patterns(dialog_patterns),
126
- )
127
- loop_data.extras_temporary["code_execution_remote"] = prompt
plugins/_a0_connector/extensions/python/message_loop_prompts_after/_79_include_text_editor_remote.py
deleted
-120
@@ -1,120 +0,0 @@
1
-from __future__ import annotations
2
-
3
-from agent import LoopData
4
-from helpers.extension import Extension
5
-
6
-from plugins._a0_connector.helpers.ws_runtime import (
7
- remote_file_metadata_for_sid,
8
- select_remote_file_target_sid,
9
-)
10
-
11
-
12
-class IncludeTextEditorRemote(Extension):
13
- async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
14
- if not self.agent:
15
- return
16
-
17
- context_id = getattr(self.agent.context, "id", "")
18
- if not context_id:
19
- return
20
-
21
- sid = select_remote_file_target_sid(context_id)
22
- if not sid:
23
- return
24
-
25
- metadata = remote_file_metadata_for_sid(sid)
26
- if metadata is None:
27
- access_mode = "Read&Write (legacy/unknown)"
28
- write_guidance = (
29
- "- Writes and patches are expected to be available, but this CLI did not "
30
- "advertise an explicit F3 access mode.\n"
31
- "- Prefer `patch_text` for context-anchored edits when supported."
32
- )
33
- write_examples = """```json
34
-{
35
- "tool_name": "text_editor_remote",
36
- "tool_args": {
37
- "op": "write",
38
- "path": "/path/on/remote/machine/file.py",
39
- "content": "import os\\nprint('hello')\\n"
40
- }
41
-}
42
-```
43
-
44
-```json
45
-{
46
- "tool_name": "text_editor_remote",
47
- "tool_args": {
48
- "op": "patch",
49
- "path": "/path/on/remote/machine/file.py",
50
- "patch_text": "*** Begin Patch\\n*** Update File: /path/on/remote/machine/file.py\\n@@ def main():\\n+ setup()\\n*** End Patch"
51
- }
52
-}
53
-```"""
54
- elif metadata.get("write_enabled"):
55
- access_mode = "Read&Write"
56
- write_guidance = (
57
- "- Use `write` only when replacing or creating the full file is the right operation.\n"
58
- "- Use `patch` with `patch_text` for context-anchored edits, especially after inserts/deletes or when line numbers may have shifted.\n"
59
- "- Use `patch` with `edits` only for surgical line-range edits based on the latest remote read.\n"
60
- "- Freshness-aware line patching may reject stale edits. If a line patch requires a reread, read the file again and then retry with updated ranges."
61
- )
62
- write_examples = """```json
63
-{
64
- "tool_name": "text_editor_remote",
65
- "tool_args": {
66
- "op": "write",
67
- "path": "/path/on/remote/machine/file.py",
68
- "content": "import os\\nprint('hello')\\n"
69
- }
70
-}
71
-```
72
-
73
-```json
74
-{
75
- "tool_name": "text_editor_remote",
76
- "tool_args": {
77
- "op": "patch",
78
- "path": "/path/on/remote/machine/file.py",
79
- "patch_text": "*** Begin Patch\\n*** Update File: /path/on/remote/machine/file.py\\n@@ def main():\\n+ setup()\\n*** End Patch"
80
- }
81
-}
82
-```
83
-
84
-```json
85
-{
86
- "tool_name": "text_editor_remote",
87
- "tool_args": {
88
- "op": "patch",
89
- "path": "/path/on/remote/machine/file.py",
90
- "patch_text": "*** Begin Patch\\n*** Update File: /path/on/remote/machine/file.py\\n@@ def main():\\n- old_helper()\\n+ new_helper()\\n*** End Patch"
91
- }
92
-}
93
-```
94
-
95
-```json
96
-{
97
- "tool_name": "text_editor_remote",
98
- "tool_args": {
99
- "op": "patch",
100
- "path": "/path/on/remote/machine/file.py",
101
- "edits": [
102
- {"from": 5, "to": 5, "content": " if x == 2:\\n"}
103
- ]
104
- }
105
-}
106
-```"""
107
- else:
108
- access_mode = "Read only"
109
- write_guidance = (
110
- "- Writes and patches are disabled in this CLI session. Press F3 to switch the host machine to Read&Write before attempting `write` or `patch`."
111
- )
112
- write_examples = ""
113
-
114
- prompt = self.agent.read_prompt(
115
- "agent.extras.text_editor_remote.md",
116
- access_mode=access_mode,
117
- write_guidance=write_guidance,
118
- write_examples=write_examples,
119
- )
120
- loop_data.extras_temporary["text_editor_remote"] = prompt
plugins/_a0_connector/prompts/agent.connector_tool.code_execution_remote.md
new
+27
@@ -0,0 +1,27 @@
1
+# code_execution_remote tool
2
+
3
+Runs shell-backed execution on the machine where the subscribed A0 CLI is running.
4
+Load `a0-cli-remote-workflows` before using this tool for nontrivial local project work.
5
+
6
+Current local access mode: `{{access_mode}}`
7
+
8
+## Requirements
9
+- A CLI client is subscribed to this chat and advertises remote execution.
10
+- Paths and shell syntax are evaluated on the CLI host, not inside Agent Zero.
11
+- {{write_runtime_note}}
12
+
13
+## Arguments
14
+- `runtime`: one of `terminal`, `python`, `nodejs`, `output`, `reset`
15
+- `runtime=input` is a temporary deprecated compatibility alias for sending one line of
16
+ keyboard input into a running shell session
17
+- `session`: integer session id (default `0`)
18
+
19
+Runtime-specific fields:
20
+- `terminal`, `python`, `nodejs`: require `code`
21
+- `input`: requires `keyboard` (or `code` as fallback)
22
+- `reset`: optional `reason`
23
+
24
+## Notes
25
+- Reuse `session` when continuing a workflow.
26
+- Use `output` to poll a running session and `reset` for a stuck session.
27
+- If the CLI returns a disabled/no-client error, ask the user to enable or reconnect the CLI instead of falling back to server-side execution.
plugins/_a0_connector/prompts/agent.connector_tool.computer_use_remote.md
new
+27
@@ -0,0 +1,27 @@
1
+# computer_use_remote tool
2
+
3
+Controls the subscribed A0 CLI host machine as a local desktop target.
4
+Load `computer-use-remote` before using this tool.
5
+
6
+## Requirements
7
+- A CLI client is subscribed to this chat and advertises enabled local computer use.
8
+- Backend: `{{backend}}`
9
+- Trust mode: `{{trust_mode}}`
10
+- Features: `{{features}}`
11
+
12
+## Arguments
13
+- `action`: one of `start_session`, `status`, `capture`, `move`, `click`, `scroll`, `key`, `type`, `stop_session`
14
+- `session_id`: optional for actions after `start_session`
15
+
16
+Action-specific fields:
17
+- `move`: `x`, `y` normalized to `[0,1]`
18
+- `click`: optional `x`, `y`, plus optional `button` (`left`, `right`, `middle`) and `count`
19
+- `scroll`: `dx`, `dy`
20
+- `key`: `key` or `keys`
21
+- `type`: `text`, optional `submit` boolean
22
+
23
+## Runtime Notes
24
+- Use `start_session` before interactive actions. `status` only inspects state.
25
+- Successful interactive actions attach a fresh screenshot; base decisions on the latest capture.
26
+- Prefer keyboard/accessibility routes before pointer actions.
27
+- Coordinates are normalized global screen coordinates.
plugins/_a0_connector/prompts/agent.connector_tool.text_editor_remote.md
new
+21
@@ -0,0 +1,21 @@
1
+# text_editor_remote tool
2
+
3
+Reads, writes, and patches files on the machine where the subscribed A0 CLI is running.
4
+This is different from server-side file tools. Load `a0-cli-remote-workflows` before using it for edits.
5
+
6
+Current access mode: `{{access_mode}}`
7
+
8
+## Requirements
9
+- A CLI client is subscribed to this chat and advertises remote file access.
10
+- Paths are evaluated on the CLI host filesystem, not the Agent Zero server.
11
+- {{write_guidance}}
12
+
13
+## Operations
14
+- `read`: optional `line_from`, `line_to`
15
+- `write`: requires `content`
16
+- `patch`: requires either `patch_text` or `edits`
17
+
18
+## Notes
19
+- Prefer `read` before line-number edits.
20
+- Prefer `patch_text` for context-anchored changes and `edits` only for fresh, surgical line ranges.
21
+- If freshness checks reject a line patch, reread the file and retry with updated ranges.
plugins/_a0_connector/prompts/agent.extras.code_execution_remote.md
deleted
-45
@@ -1,45 +0,0 @@
1
-## code_execution_remote guidance
2
-
3
-Remote code execution is currently available in this context through the connected CLI.
4
-Current local access mode: `{{access_mode}}`
5
-
6
-Execution config:
7
-- code execution timeouts: `{{code_exec_timeouts}}`
8
-- output polling timeouts: `{{output_timeouts}}`
9
-- prompt patterns: `{{prompt_patterns}}`
10
-- dialog patterns: `{{dialog_patterns}}`
11
-
12
-- Use this tool for shell-backed execution on the remote CLI machine, not on the Agent Zero server.
13
-- Session ids are frontend-local and persistent across calls. Reuse the same `session` when continuing a workflow.
14
-- Use `runtime=terminal` for shell commands, `runtime=python` for Python snippets, and `runtime=nodejs` for Node.js snippets.
15
-- Use `runtime=output` to poll a running session after a prior call returned before the shell settled.
16
-- Use `runtime=reset` when a session is stuck or you need a clean shell.
17
-- `runtime=input` is only a deprecated compatibility alias for sending one line of keyboard input into a running shell session.
18
-- Frontend execution may still be locally disabled in the CLI session. If so, expect a structured `{ok: false}` error instead of a fallback runtime.
19
-- Prefer concise, self-checking commands. For multi-step work, inspect output and continue in the same session instead of restarting from scratch.
20
-{{write_runtime_guidance}}
21
-
22
-Examples:
23
-
24
-```json
25
-{
26
- "tool_name": "code_execution_remote",
27
- "tool_args": {
28
- "runtime": "output",
29
- "session": 0
30
- }
31
-}
32
-```
33
-
34
-```json
35
-{
36
- "tool_name": "code_execution_remote",
37
- "tool_args": {
38
- "runtime": "reset",
39
- "session": 0,
40
- "reason": "Start a clean shell for the next step."
41
- }
42
-}
43
-```
44
-
45
-{{write_runtime_examples}}
plugins/_a0_connector/prompts/agent.extras.computer_use_remote.md
deleted
-29
@@ -1,29 +0,0 @@
1
-## computer_use_remote guidance
2
-
3
-Computer use is currently available in this context.
4
-Backend: `{{backend}}`
5
-Trust mode: `{{trust_mode}}`
6
-Features: `{{features}}`
7
-Support note: `{{support_reason}}`
8
-
9
-- Use this for local desktop and native UI tasks on the connected machine.
10
-- If the task is browser-only and the user is flexible, prefer the direct `browser` tool because it is usually more reliable and token-efficient than screenshot-driven desktop control.
11
-- Use `start_session` before interactive desktop actions. `status` is for inspection; `stop_session` ends the session.
12
-- Base every decision on the latest screenshot or a definitive tool result, not memory.
13
-- The current action API uses normalized global screen coordinates. Do not assume window IDs, element indices, background-safe input, or semantic click targets unless the advertised features explicitly say they exist.
14
-- If features include `real-cursor-may-move` or `focus-risk`, expect pointer actions to affect the visible desktop state; prefer keyboard/accessibility routes even more strongly.
15
-- Successful `start_session`, `move`, `click`, `scroll`, `key`, and `type` calls already attach a fresh screenshot.
16
-- Each attached screenshot includes a `capture id`; treat the latest attached capture as authoritative and ignore superseded capture references.
17
-- If an attached screenshot looks unchanged after a state-changing action, use one explicit `capture` to verify before repeating the same action.
18
-- Use `capture` only when you need a screen refresh without taking another action.
19
-- Prefer accessibility and semantic UI paths first: application shortcuts, command palettes, menu accelerators, address/search bars, focus traversal, selection shortcuts, and other keyboard-accessible controls.
20
-- Prefer `key` and `type` over pointer actions whenever there is a plausible keyboard or accessibility path. Use `tab`, `shift+tab`, arrow keys, hotkeys, text search, and submit keys before reaching for the mouse.
21
-- For viewport movement, try keyboard scrolling first: `page_down`, `page_up`, `space`, `shift+space`, arrow keys, `home`, or `end`. Use `scroll` when the desired scrollable region is already active or a keyboard route cannot target it; prefer `scroll` over click-dragging or clicking scrollbars.
22
-- Treat `move` and `click` as last-resort actions for controls that cannot be reached or activated reliably through accessibility, hotkeys, keyboard navigation, or browser/app-native tooling.
23
-- Before clicking, make sure the latest screenshot makes the target unambiguous and that a keyboard/accessibility route has already been tried or ruled out. Use one deliberate click, then reassess from the fresh screenshot.
24
-- Treat menus and popups as transient UI. If a click dismisses one without visible progress, treat that attempt as failed and switch to a non-pointer strategy.
25
-- If the same approach has already failed twice without visible progress, stop repeating it and try a different strategy.
26
-- For browser work done through this tool, only claim success when the page content area visibly shows the expected destination or result.
27
-- Use `type(..., submit=true)` only for navigation-style entry such as an address bar or command box. For ordinary text fields, type first and send `enter` separately only if needed.
28
-- In `free_run`, do not expect a fresh approval prompt. If silent restore is no longer valid, expect `COMPUTER_USE_REARM_REQUIRED`.
29
-- Treat user interventions as high-priority control signals. If the user says `stop`, `pause`, `abort`, `hold`, `don't continue`, or equivalent, stop using computer-use tools until the user explicitly resumes.
plugins/_a0_connector/prompts/agent.extras.text_editor_remote.md
deleted
-29
@@ -1,29 +0,0 @@
1
-## text_editor_remote guidance
2
-
3
-Remote file editing is currently available in this context through the connected CLI.
4
-Current access mode: `{{access_mode}}`
5
-
6
-- Use `text_editor_remote` when the user asks you to edit files on their local machine while connected via the CLI.
7
-- Paths are evaluated on the remote CLI machine's filesystem, not on the Agent Zero server.
8
-- Prefer `patch_text` for edits that can be located by surrounding code context.
9
-- For `patch_text` inserts, use one `@@ existing line` anchor followed directly by `+new line`.
10
-- For `patch_text` replacements, use `@@ before target` then `-old`/`+new`, or `@@ old target` then the same `-old`/`+new`.
11
-- Do not repeat the same old line as both context and deletion in one replacement hunk.
12
-- Prefer `read` before line-number `edits` so you have current line numbers and freshness metadata.
13
-- `read` is always the safest first step for inspecting the local file.
14
-{{write_guidance}}
15
-
16
-Examples:
17
-
18
-```json
19
-{
20
- "tool_name": "text_editor_remote",
21
- "tool_args": {
22
- "op": "read",
23
- "path": "/path/on/remote/machine/file.py",
24
- "line_from": 1,
25
- "line_to": 50
26
- }
27
-}
28
-```
29
-{{write_examples}}
plugins/_a0_connector/prompts/agent.system.tool.code_execution_remote.md
deleted
-32
@@ -1,32 +0,0 @@
1
-# code_execution_remote tool
2
-
3
-This tool runs shell-backed execution on the **remote machine where the CLI is running**.
4
-Detailed usage guidance is injected separately only when the current context has a
5
-subscribed CLI, so the base system prompt stays small when remote execution is not in play.
6
-
7
-## Requirements
8
-- A CLI client must be connected to this context via the shared `/ws` namespace.
9
-- The CLI client must support `connector_exec_op`.
10
-- Frontend execution may be locally disabled in the CLI session; in that case the result is
11
- a structured `{ok: false}` error and no fallback runtime is used.
12
-- Mutating runtimes (`terminal`, `python`, `nodejs`, and `input`) also require the CLI
13
- session to advertise local access mode `Read&Write` via F3. `output` and `reset` can
14
- still be used for existing sessions while the CLI is in `Read only`.
15
-
16
-## Arguments
17
-- `runtime`: one of `terminal`, `python`, `nodejs`, `output`, `reset`
18
-- `runtime=input` is a temporary deprecated compatibility alias for sending one line of
19
- keyboard input into a running shell session
20
-- `session`: integer session id (default `0`)
21
-
22
-Runtime-specific fields:
23
-- `terminal`, `python`, `nodejs`: require `code`
24
-- `input`: requires `keyboard` (or `code` as fallback)
25
-- `reset`: optional `reason`
26
-
27
-## Notes
28
-- Session state is frontend-local and shell-backed.
29
-- `output` is for long-running operations where a prior call returned control before the
30
- shell reached a prompt.
31
-- Use shell syntax that matches the remote host (for example, PowerShell on Windows).
32
-- The transport uses `connector_exec_op` and `connector_exec_op_result` with shared `op_id`.
plugins/_a0_connector/prompts/agent.system.tool.computer_use_remote.md
deleted
-31
@@ -1,31 +0,0 @@
1
-# computer_use_remote tool
2
-
3
-Use the connected CLI host machine as a local desktop target.
4
-
5
-This tool is only usable when the current context has a subscribed CLI with enabled local computer use.
6
-Detailed operating guidance is injected separately only when that condition is true, so the base system prompt stays small when computer use is not in play.
7
-
8
-## Requirements
9
-- A CLI client must be connected to this context via the shared `/ws` namespace.
10
-- The CLI must advertise `computer_use_remote` support and local computer use must be enabled there.
11
-
12
-## Arguments
13
-- `action`: one of `start_session`, `status`, `capture`, `move`, `click`, `scroll`, `key`, `type`, `stop_session`
14
-- `session_id`: optional for actions after `start_session`
15
-
16
-Action-specific fields:
17
-- `move`: `x`, `y` normalized to `[0,1]`
18
-- `click`: optional `x`, `y`, plus optional `button` (`left`, `right`, `middle`) and `count`
19
-- `scroll`: `dx`, `dy`
20
-- `key`: `key` or `keys`
21
-- `type`: `text`, optional `submit` boolean
22
-
23
-## Runtime Notes
24
-- The current action API uses normalized global screen coordinates; do not assume window IDs, element indices, background-safe input, or semantic click targets unless runtime guidance explicitly advertises them.
25
-- Successful `start_session`, `move`, `click`, `scroll`, `key`, and `type` calls automatically attach a fresh screenshot.
26
-- Attached screenshots include a `capture id`; use the latest capture as the coordinate basis.
27
-- If the attached screenshot appears unchanged after a state-changing action, verify once with `capture` before repeating the same action.
28
-- `status` reports the current computer-use state without starting a session.
29
-- Prefer accessibility, semantic UI controls, hotkeys, focus traversal, and other keyboard paths before pointer actions.
30
-- For viewport movement, prefer keyboard scrolling first; use `scroll` when a wheel-style scroll is the most reliable way to move an already-focused viewport or pane.
31
-- Use `move` and `click` only as a last resort when no reliable accessibility or keyboard route is available and the latest screenshot makes the target unambiguous.
plugins/_a0_connector/prompts/agent.system.tool.text_editor_remote.md
deleted
-25
@@ -1,25 +0,0 @@
1
-# text_editor_remote tool
2
-
3
-This tool allows you to read, write, and patch files on the **remote machine where the CLI is running**.
4
-This is different from `text_editor` which operates on the Agent Zero server's filesystem.
5
-Detailed usage guidance is injected separately only when the current context has a
6
-subscribed CLI, so the base system prompt stays small when remote editing is not in play.
7
-
8
-## Requirements
9
-- A CLI client must be connected to this context via the shared `/ws` namespace.
10
-- The CLI client must have enabled remote file editing support.
11
-
12
-## Operations
13
-- `read`: optional `line_from`, `line_to`
14
-- `write`: requires `content`
15
-- `patch`: requires either `patch_text` or `edits`
16
-
17
-## Notes
18
-- Paths are evaluated on the **remote machine's filesystem**, not the Agent Zero server.
19
-- The transport uses `connector_file_op` and `connector_file_op_result` with a shared `op_id`.
20
-- `patch_text` uses context chunks and does not require fresh line numbers.
21
-- `patch_text` line rules: `@@ existing line` anchors the hunk; `+new` inserts after the anchor when there are no context or delete lines; `-old` then `+new` replaces the next matching old line after the anchor, or the anchor line itself when `@@` is the old target line.
22
-- For replacements, do not repeat the same old line as both a space-context line and a `-old` line.
23
-- Every non-header content line in `patch_text` must start with exactly one prefix: space for kept context, `+` for added content, or `-` for removed content. Do not emit raw unprefixed content lines.
24
-- Do not stack multiple `@@` lines for one insert. Use one anchor, then the `+` lines to insert.
25
-- `edits` uses 1-based line ranges and may require rereading after line-count changes.
plugins/_a0_connector/skills/a0-cli-remote-workflows/SKILL.md
new
+64
@@ -0,0 +1,64 @@
1
+---
2
+name: a0-cli-remote-workflows
3
+description: Guide safe use of A0 CLI remote shell execution and remote file editing on the connected host machine. Load before using code_execution_remote or text_editor_remote for local project work through the CLI connector.
4
+version: 1.0.0
5
+author: Agent Zero Team
6
+tags: ["agent-zero", "a0", "cli", "connector", "remote-execution", "remote-files"]
7
+trigger_patterns:
8
+ - "code_execution_remote"
9
+ - "text_editor_remote"
10
+ - "remote file editing"
11
+ - "remote shell execution"
12
+ - "edit my local files through a0 cli"
13
+ - "run commands on the cli host"
14
+allowed_tools:
15
+ - code_execution_remote
16
+ - text_editor_remote
17
+---
18
+
19
+# A0 CLI Remote Workflows
20
+
21
+## Boundary
22
+
23
+Use `code_execution_remote` and `text_editor_remote` only for work on the machine where A0 CLI is running. These paths, shells, runtimes, and files belong to the CLI host, not the Agent Zero server or Docker container.
24
+
25
+If the task belongs inside Agent Zero's own runtime, use the normal server-side tools instead.
26
+
27
+## Access Modes
28
+
29
+- Read only: inspect files and poll/reset existing execution sessions. Do not attempt writes or mutating shell work until the user switches the CLI to Read&Write with F3.
30
+- Read&Write: shell-backed execution, writes, and patches may modify the CLI host. Keep changes narrow and intentional.
31
+- Execution may also be disabled locally in the CLI. If a remote tool returns a structured disabled/no-client error, explain the required CLI toggle instead of falling back to the server filesystem.
32
+
33
+## Remote Execution
34
+
35
+- Use `runtime=terminal` for shell commands, `runtime=python` for Python snippets, and `runtime=nodejs` for Node.js snippets.
36
+- Reuse the same integer `session` while continuing a workflow; session state is local to the CLI frontend.
37
+- Use `runtime=output` when a previous command is still running or returned before the shell reached a prompt.
38
+- Use `runtime=reset` when a session is stuck or a clean shell is safer.
39
+- Treat `runtime=input` as deprecated compatibility for sending one line to a running shell.
40
+- Match the remote host shell syntax. A Windows CLI may need PowerShell syntax even when Agent Zero runs on Linux.
41
+
42
+## Remote File Editing
43
+
44
+- Start with `read` when inspecting a file or preparing line-based edits.
45
+- Use `write` only when replacing or creating the whole file is truly the right operation.
46
+- Prefer `patch` with `patch_text` for context-anchored edits, especially after inserts/deletes or when line numbers may have shifted.
47
+- Use `patch` with `edits` only for small line-range edits based on the latest remote read.
48
+- If freshness-aware line patching rejects an edit as stale, reread the file and retry with updated ranges.
49
+
50
+## Patch Text Rules
51
+
52
+- `patch_text` supports update hunks for one file.
53
+- Use one `@@ existing line` anchor, then `+new line` entries for insertion.
54
+- For replacement, use `@@ before target` followed by `-old` and `+new`, or use `@@ old target` followed by the same replacement pair.
55
+- Do not repeat the same old line as both context and deletion in one hunk.
56
+- Every non-header content line must begin with exactly one prefix: space for context, `+` for additions, or `-` for removals.
57
+- Do not stack multiple `@@` anchors for one insert.
58
+
59
+## Failure Handling
60
+
61
+- If no CLI is connected or subscribed, ask the user to connect A0 CLI to this chat.
62
+- If writes are blocked, tell the user to switch local access to Read&Write with F3.
63
+- If execution is disabled, tell the user to enable remote execution in the CLI.
64
+- If a request times out or the CLI disconnects, poll once if a session may still be running; otherwise summarize the failure and wait for reconnection.
skills/computer-use-remote/SKILL.md
+15
-4
@@ -1,7 +1,7 @@
1
---
2
name: computer-use-remote
3
description: Detailed operating guide for using computer_use_remote on the connected local machine. Load this skill before using computer_use_remote for desktop control, screenshots, menus, browser chrome, or other native UI tasks.
4
-version: 1.0.0
4
+version: 1.1.0
5
author: Agent Zero Team
6
tags: ["computer-use", "desktop", "local-ui", "screenshots", "native-ui"]
7
trigger_patterns:
@@ -23,27 +23,38 @@ allowed_tools:
23
24
Load this skill before using `computer_use_remote` for local desktop and native UI tasks on the connected machine.
25
26
-For ordinary website browsing, search, form filling, and web downloads, prefer the direct `browser` tool instead. If the user is flexible and the task is browser-only, guide them toward browser tools because they are usually more reliable and token-efficient than screenshot-driven computer use.
26
+If the task is browser-only and the user is flexible, prefer direct browser tooling because it is usually more reliable and token-efficient than screenshot-driven desktop control.
27
28
## Core Loop
29
30
1. Call `start_session` first.
31
2. Decide from the latest screenshot, not from memory.
32
3. Interactive actions (`move`, `click`, `scroll`, `key`, `type`) already attach a fresh screenshot after they run.
33
-4. Use `capture` only when you need another screenshot without taking an action.
33
+4. Use `status` for state without starting a session.
34
+5. Use `capture` only when you need another screenshot without taking an action.
35
36
## Operating Rules
37
38
- Only the latest screenshot or a definitive tool result counts as evidence.
38
-- Prefer keyboard actions over pointer actions whenever a reliable keyboard path exists.
39
+- The current API uses normalized global screen coordinates; do not assume window ids, element indexes, background-safe input, or semantic click targets unless the runtime explicitly advertises them.
40
+- Prefer accessibility and semantic UI paths first: shortcuts, command palettes, menu accelerators, address/search bars, focus traversal, and other keyboard-accessible controls.
41
+- Prefer `key` and `type` over pointer actions whenever a reliable keyboard path exists.
42
- When a menu or popup is open, treat it as the active UI and prefer keyboard navigation over clicking small transient rows by coordinate.
43
- If a click dismisses a menu or popup without producing the expected next UI, treat that attempt as failed.
44
- If the same approach has already failed twice without visible progress, switch strategy instead of repeating it.
45
- Do not infer focus or task completion from chat logs, sidebars, tool summaries, or status text.
46
- For browser-navigation tasks done through this tool, only claim success if the browser content area visibly shows the destination page or result.
47
+- If the attached screenshot appears unchanged after a state-changing action, use one explicit `capture` to verify before repeating the same action.
48
- Use `type(..., submit=true)` only for URL or navigation-style entry where Enter should fire immediately after typing.
49
- Do not use `submit=true` for ordinary text fields. Type first, then send `enter` separately if needed.
50
51
+## Pointer And Scrolling
52
+
53
+- Try keyboard scrolling first: `page_down`, `page_up`, `space`, `shift+space`, arrows, `home`, or `end`.
54
+- Use `scroll` when the desired pane is already active or keyboard scrolling cannot target it.
55
+- Treat `move` and `click` as last-resort actions for controls that cannot be reached through keyboard, accessibility, browser, or app-native tooling.
56
+- Before clicking, make sure the latest screenshot makes the target unambiguous. Use one deliberate click, then reassess from the fresh screenshot.
57
+
58
## Control Signals
59
60
- Treat user interventions as high-priority control signals.
tests/test_a0_connector_prompt_gating.py
new
+217
@@ -0,0 +1,217 @@
1
+import importlib.util
2
+import sys
3
+import uuid
4
+from pathlib import Path
5
+
6
+import yaml
7
+
8
+PROJECT_ROOT = Path(__file__).resolve().parents[1]
9
+if str(PROJECT_ROOT) not in sys.path:
10
+ sys.path.insert(0, str(PROJECT_ROOT))
11
+
12
+
13
+def _restore_real_helpers_package() -> None:
14
+ helpers_module = sys.modules.get("helpers")
15
+ if helpers_module is None or getattr(helpers_module, "__file__", ""):
16
+ return
17
+
18
+ for name in list(sys.modules):
19
+ if name == "helpers" or name.startswith("helpers."):
20
+ del sys.modules[name]
21
+
22
+
23
+_restore_real_helpers_package()
24
+
25
+from plugins._a0_connector.helpers import ws_runtime
26
+
27
+
28
+PROMPT_ROOT = PROJECT_ROOT / "plugins" / "_a0_connector" / "prompts"
29
+GATE_PATH = (
30
+ PROJECT_ROOT
31
+ / "plugins"
32
+ / "_a0_connector"
33
+ / "extensions"
34
+ / "python"
35
+ / "_functions"
36
+ / "extensions"
37
+ / "python"
38
+ / "system_prompt"
39
+ / "_11_tools_prompt"
40
+ / "build_prompt"
41
+ / "end"
42
+ / "_70_include_remote_tool_stubs.py"
43
+)
44
+
45
+
46
+def _load_gate_class():
47
+ spec = importlib.util.spec_from_file_location(
48
+ "test_a0_connector_remote_tool_gate",
49
+ GATE_PATH,
50
+ )
51
+ module = importlib.util.module_from_spec(spec)
52
+ assert spec and spec.loader
53
+ sys.modules[spec.name] = module
54
+ spec.loader.exec_module(module)
55
+ return module.IncludeRemoteToolStubs
56
+
57
+
58
+IncludeRemoteToolStubs = _load_gate_class()
59
+
60
+
61
+class FakeContext:
62
+ def __init__(self, context_id: str):
63
+ self.id = context_id
64
+
65
+
66
+class FakeAgent:
67
+ def __init__(self, context_id: str):
68
+ self.context = FakeContext(context_id)
69
+
70
+ def read_prompt(self, file: str, **kwargs) -> str:
71
+ text = (PROMPT_ROOT / file).read_text(encoding="utf-8")
72
+ for key, value in kwargs.items():
73
+ text = text.replace("{{" + key + "}}", str(value))
74
+ return text
75
+
76
+
77
+def _context_id() -> str:
78
+ return f"ctx-{uuid.uuid4()}"
79
+
80
+
81
+def _sid() -> str:
82
+ return f"sid-{uuid.uuid4()}"
83
+
84
+
85
+def _parse_skill_frontmatter(path: Path) -> dict:
86
+ text = path.read_text(encoding="utf-8")
87
+ assert text.startswith("---")
88
+ return yaml.safe_load(text.split("---", 2)[1]) or {}
89
+
90
+
91
+def _apply_gate(context_id: str) -> str:
92
+ data = {"result": "## available tools\nbase_tool"}
93
+ IncludeRemoteToolStubs(agent=FakeAgent(context_id)).execute(data=data)
94
+ return data["result"]
95
+
96
+
97
+def _subscribe(
98
+ context_id: str,
99
+ *,
100
+ remote_files: dict | None = None,
101
+ remote_exec: dict | None = None,
102
+ computer_use: dict | None = None,
103
+) -> str:
104
+ sid = _sid()
105
+ ws_runtime.register_sid(sid)
106
+ ws_runtime.subscribe_sid_to_context(sid, context_id)
107
+ if remote_files is not None:
108
+ ws_runtime.store_sid_remote_file_metadata(sid, remote_files)
109
+ if remote_exec is not None:
110
+ ws_runtime.store_sid_remote_exec_metadata(sid, remote_exec)
111
+ if computer_use is not None:
112
+ ws_runtime.store_sid_computer_use_metadata(sid, computer_use)
113
+ return sid
114
+
115
+
116
+def test_remote_tool_stubs_absent_without_subscribed_cli():
117
+ prompt = _apply_gate(_context_id())
118
+
119
+ assert "text_editor_remote tool" not in prompt
120
+ assert "code_execution_remote tool" not in prompt
121
+ assert "computer_use_remote tool" not in prompt
122
+
123
+
124
+def test_file_only_cli_adds_text_editor_stub():
125
+ context_id = _context_id()
126
+ sid = _subscribe(
127
+ context_id,
128
+ remote_files={"enabled": True, "write_enabled": True},
129
+ )
130
+ try:
131
+ prompt = _apply_gate(context_id)
132
+ finally:
133
+ ws_runtime.unregister_sid(sid)
134
+
135
+ assert "text_editor_remote tool" in prompt
136
+ assert "Current access mode: `Read&Write`" in prompt
137
+ assert "code_execution_remote tool" not in prompt
138
+ assert "computer_use_remote tool" not in prompt
139
+
140
+
141
+def test_exec_enabled_cli_adds_execution_stub():
142
+ context_id = _context_id()
143
+ sid = _subscribe(
144
+ context_id,
145
+ remote_exec={"enabled": True},
146
+ )
147
+ try:
148
+ prompt = _apply_gate(context_id)
149
+ finally:
150
+ ws_runtime.unregister_sid(sid)
151
+
152
+ assert "code_execution_remote tool" in prompt
153
+ assert "text_editor_remote tool" not in prompt
154
+ assert "computer_use_remote tool" not in prompt
155
+
156
+
157
+def test_read_only_mode_marks_mutating_operations_disabled():
158
+ context_id = _context_id()
159
+ sid = _subscribe(
160
+ context_id,
161
+ remote_files={"enabled": True, "write_enabled": False, "mode": "read_only"},
162
+ remote_exec={"enabled": True},
163
+ )
164
+ try:
165
+ prompt = _apply_gate(context_id)
166
+ finally:
167
+ ws_runtime.unregister_sid(sid)
168
+
169
+ assert "text_editor_remote tool" in prompt
170
+ assert "code_execution_remote tool" in prompt
171
+ assert "Current access mode: `Read only`" in prompt
172
+ assert "Writes and patches are disabled" in prompt
173
+ assert "Mutating runtimes are disabled" in prompt
174
+
175
+
176
+def test_computer_use_enabled_cli_adds_computer_stub():
177
+ context_id = _context_id()
178
+ sid = _subscribe(
179
+ context_id,
180
+ computer_use={
181
+ "supported": True,
182
+ "enabled": True,
183
+ "trust_mode": "ask",
184
+ "backend_id": "local",
185
+ "backend_family": "desktop",
186
+ "features": ["screenshots", "keyboard"],
187
+ },
188
+ )
189
+ try:
190
+ prompt = _apply_gate(context_id)
191
+ finally:
192
+ ws_runtime.unregister_sid(sid)
193
+
194
+ assert "computer_use_remote tool" in prompt
195
+ assert "Backend: `local/desktop`" in prompt
196
+ assert "Features: `screenshots, keyboard`" in prompt
197
+ assert "text_editor_remote tool" not in prompt
198
+ assert "code_execution_remote tool" not in prompt
199
+
200
+
201
+def test_remote_workflow_skills_parse():
202
+ connector_skill = _parse_skill_frontmatter(
203
+ PROJECT_ROOT
204
+ / "plugins"
205
+ / "_a0_connector"
206
+ / "skills"
207
+ / "a0-cli-remote-workflows"
208
+ / "SKILL.md"
209
+ )
210
+ computer_skill = _parse_skill_frontmatter(
211
+ PROJECT_ROOT / "skills" / "computer-use-remote" / "SKILL.md"
212
+ )
213
+
214
+ assert connector_skill["name"] == "a0-cli-remote-workflows"
215
+ assert connector_skill["description"]
216
+ assert computer_skill["name"] == "computer-use-remote"
217
+ assert computer_skill["description"]
tests/test_default_prompt_budget.py
+3
@@ -55,6 +55,9 @@ async def test_default_agent0_prompt_budget_and_guardrails():
55
assert '"tool_name": "code_execution_tool"' in system_text
56
assert '"tool_name": "memory_load"' in system_text
57
assert "informative but tight" in system_text
58
+ assert "# code_execution_remote tool" not in system_text
59
+ assert "# text_editor_remote tool" not in system_text
60
+ assert "# computer_use_remote tool" not in system_text
61
62
63
def test_a0_small_profile_removed_and_prompt_text_generic():