algo-cli-runtime 0.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- ollama_cli/__init__.py +67 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Corrective feedback for malformed tool calls.
|
|
2
|
+
|
|
3
|
+
Weak local models routinely fumble tool invocations: bad JSON arguments,
|
|
4
|
+
wrong/missing parameter names, a JSON string where a dict was expected, or a
|
|
5
|
+
Unix command piped into a Windows shell. The default behavior — returning a
|
|
6
|
+
bare ``TypeError`` string — gives the model nothing to correct against, so it
|
|
7
|
+
loops or gives up.
|
|
8
|
+
|
|
9
|
+
These helpers turn a failed call into an actionable correction: the tool's real
|
|
10
|
+
signature (required vs optional params, runtime-injected params hidden) plus a
|
|
11
|
+
diagnosis of what the model sent. The agent loop feeds this back as the tool
|
|
12
|
+
result so the model can retry with the right shape.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import inspect
|
|
18
|
+
from typing import Any, Callable
|
|
19
|
+
|
|
20
|
+
# Params the runtime injects; the model must never supply them.
|
|
21
|
+
_RUNTIME_PARAMS = frozenset({"cwd", "cfg", "safe_mode"})
|
|
22
|
+
|
|
23
|
+
# Tokens that mean "you used a Unix tool in the Windows cmd.exe shell".
|
|
24
|
+
_WINDOWS_SHELL_MISS = (
|
|
25
|
+
"is not recognized as an internal or external command",
|
|
26
|
+
"'head'", "'tail'", "'grep'", "'sed'", "'awk'", "'cat'", "'less'",
|
|
27
|
+
)
|
|
28
|
+
_UNIX_TO_WINDOWS = {
|
|
29
|
+
"head": "use `more` or a command flag (e.g. pytest -q), not | head",
|
|
30
|
+
"tail": "use PowerShell `Get-Content -Tail N` or read_file with offset",
|
|
31
|
+
"grep": "use `findstr` or the search_files tool",
|
|
32
|
+
"cat": "use the read_file tool or `type`",
|
|
33
|
+
"sed": "use the edit_file tool",
|
|
34
|
+
"awk": "use read_file + parse, or findstr",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def signature_hint(fn: Callable[..., Any]) -> str:
|
|
39
|
+
"""Render a model-facing signature: required params first, then optionals."""
|
|
40
|
+
try:
|
|
41
|
+
sig = inspect.signature(fn)
|
|
42
|
+
except (TypeError, ValueError):
|
|
43
|
+
return ""
|
|
44
|
+
required: list[str] = []
|
|
45
|
+
optional: list[str] = []
|
|
46
|
+
for name, param in sig.parameters.items():
|
|
47
|
+
if name in _RUNTIME_PARAMS:
|
|
48
|
+
continue
|
|
49
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
50
|
+
continue
|
|
51
|
+
ann = ""
|
|
52
|
+
if param.annotation is not inspect.Parameter.empty:
|
|
53
|
+
ann = getattr(param.annotation, "__name__", None) or str(param.annotation).replace("typing.", "")
|
|
54
|
+
if param.default is inspect.Parameter.empty:
|
|
55
|
+
required.append(f"{name}: {ann}" if ann else name)
|
|
56
|
+
else:
|
|
57
|
+
optional.append(f"{name}={param.default!r}")
|
|
58
|
+
parts = []
|
|
59
|
+
if required:
|
|
60
|
+
parts.append("required: " + ", ".join(required))
|
|
61
|
+
if optional:
|
|
62
|
+
parts.append("optional: " + ", ".join(optional))
|
|
63
|
+
return "; ".join(parts)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def correct_tool_error(name: str, args: dict[str, Any], exc: Exception, fn: Callable[..., Any] | None) -> str:
|
|
67
|
+
"""Build a corrective tool-result message for a failed call."""
|
|
68
|
+
sent = sorted(k for k in (args or {}).keys() if k not in _RUNTIME_PARAMS)
|
|
69
|
+
lines = [f"Tool argument error for {name}: {exc}"]
|
|
70
|
+
|
|
71
|
+
# Malformed-JSON signal: chat_protocol wraps unparseable arguments as {"raw": ...}.
|
|
72
|
+
if list(args.keys()) == ["raw"] or "raw" in sent and len(sent) == 1:
|
|
73
|
+
lines.append(
|
|
74
|
+
"Your arguments were not valid JSON, so they could not be parsed into "
|
|
75
|
+
"fields. Re-send the call with a proper JSON object for `arguments`."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
hint = signature_hint(fn) if fn is not None else ""
|
|
79
|
+
if hint:
|
|
80
|
+
lines.append(f"Correct signature — {name}({hint})")
|
|
81
|
+
if sent:
|
|
82
|
+
lines.append(f"You sent: {', '.join(sent)}")
|
|
83
|
+
lines.append("Re-issue the call with exactly the required parameters and valid JSON values.")
|
|
84
|
+
return "\n".join(lines)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def shell_mistake_hint(command: str, output: str) -> str:
|
|
88
|
+
"""Return a one-line correction when shell output shows a cmd.exe miss, else ""."""
|
|
89
|
+
lowered = (output or "").lower()
|
|
90
|
+
if not any(marker.lower() in lowered for marker in _WINDOWS_SHELL_MISS):
|
|
91
|
+
return ""
|
|
92
|
+
cmd_lower = (command or "").lower()
|
|
93
|
+
for unix_tool, advice in _UNIX_TO_WINDOWS.items():
|
|
94
|
+
if unix_tool in cmd_lower:
|
|
95
|
+
return f"[hint] This is Windows cmd.exe: {advice}."
|
|
96
|
+
return (
|
|
97
|
+
"[hint] This is Windows cmd.exe — Unix tools are unavailable. "
|
|
98
|
+
"Use findstr/more, command flags, or read_file/search_files."
|
|
99
|
+
)
|
algo_cli/tool_policy.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Advisory tool policy selection for routed Agent Block runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from . import tools as tools_module
|
|
9
|
+
from ._internal.policy_chain import CheckResult, Control, evaluate_chain
|
|
10
|
+
from .capability_mask import Capability, CapabilityMask, tier_mask
|
|
11
|
+
from .task_router import TaskRoute
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
MUTATING_TOOLS = frozenset({"write_file", "edit_file", "batch_edit", "run_shell"})
|
|
15
|
+
WRITE_TOOLS = frozenset({"write_file", "edit_file", "batch_edit"})
|
|
16
|
+
SHELL_TOOLS = frozenset({"run_shell"})
|
|
17
|
+
|
|
18
|
+
TOOL_GROUP_MAP: dict[str, frozenset[str]] = {
|
|
19
|
+
"read": frozenset(
|
|
20
|
+
{
|
|
21
|
+
"read_file",
|
|
22
|
+
"read_pdf",
|
|
23
|
+
"render_pdf_pages",
|
|
24
|
+
"list_directory",
|
|
25
|
+
"search_files",
|
|
26
|
+
"git_status",
|
|
27
|
+
"git_diff",
|
|
28
|
+
"available_actions",
|
|
29
|
+
"session_slash",
|
|
30
|
+
"harness_stats",
|
|
31
|
+
"harness_search",
|
|
32
|
+
"harness_read",
|
|
33
|
+
"query_knowledge_graph",
|
|
34
|
+
"model_show",
|
|
35
|
+
}
|
|
36
|
+
),
|
|
37
|
+
"web": frozenset({"web_search", "web_fetch", "x_search"}),
|
|
38
|
+
"write": WRITE_TOOLS,
|
|
39
|
+
"shell": SHELL_TOOLS,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def expand_tool_groups(groups: list[str] | tuple[str, ...]) -> frozenset[str]:
|
|
44
|
+
tools: set[str] = set()
|
|
45
|
+
for group in groups:
|
|
46
|
+
tools.update(TOOL_GROUP_MAP[group])
|
|
47
|
+
return frozenset(tools)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ToolPolicyDecision:
|
|
52
|
+
"""Prospective policy decision; advisory until enforcement is enabled."""
|
|
53
|
+
|
|
54
|
+
allowed_tools: frozenset[str]
|
|
55
|
+
denied_tools: frozenset[str]
|
|
56
|
+
approval_required: frozenset[str]
|
|
57
|
+
reasons: tuple[str, ...]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class ShellMutationDecision:
|
|
62
|
+
"""Mutation-policy decision for a shell command attempted by an Agent Block."""
|
|
63
|
+
|
|
64
|
+
is_mutation: bool
|
|
65
|
+
blocked: bool
|
|
66
|
+
force_approval: bool
|
|
67
|
+
reason: str | None = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class RuntimeToolPolicyDecision:
|
|
72
|
+
"""Composable preflight policy for every model-invoked runtime tool."""
|
|
73
|
+
|
|
74
|
+
tool_name: str
|
|
75
|
+
tier: str
|
|
76
|
+
capability_mask: int
|
|
77
|
+
capability_names: tuple[str, ...]
|
|
78
|
+
allowed: bool
|
|
79
|
+
reasons: tuple[str, ...]
|
|
80
|
+
fired_rules: tuple[str, ...]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
_NETWORK_TOOLS = frozenset({
|
|
84
|
+
"web_search",
|
|
85
|
+
"web_fetch",
|
|
86
|
+
"x_search",
|
|
87
|
+
"x_account_status",
|
|
88
|
+
"x_account_draft_post",
|
|
89
|
+
"x_account_draft_reply",
|
|
90
|
+
"x_account_post",
|
|
91
|
+
"x_account_reply",
|
|
92
|
+
"x_account_post_action",
|
|
93
|
+
"model_pull",
|
|
94
|
+
"model_create",
|
|
95
|
+
})
|
|
96
|
+
_MODEL_TOOLS = frozenset({
|
|
97
|
+
"model_show",
|
|
98
|
+
"model_pull",
|
|
99
|
+
"model_copy",
|
|
100
|
+
"model_create",
|
|
101
|
+
"model_delete",
|
|
102
|
+
"embed_text",
|
|
103
|
+
"vision_describe",
|
|
104
|
+
})
|
|
105
|
+
_CREDENTIAL_TOOLS = frozenset({"credential_helpers_get", "credential_helpers_store"})
|
|
106
|
+
_MEMORY_TOOLS = frozenset({"remember", "append_lesson", "write_knowledge_graph_note"})
|
|
107
|
+
_EXTERNAL_PUBLISH_TOOLS = frozenset({"x_account_post", "x_account_reply", "x_account_post_action"})
|
|
108
|
+
_DESTRUCTIVE_TOOLS = frozenset({"model_delete"})
|
|
109
|
+
_STATE_WRITE_TOOLS = WRITE_TOOLS | frozenset({
|
|
110
|
+
"update_user_profile",
|
|
111
|
+
"plugins_load",
|
|
112
|
+
"credential_helpers_store",
|
|
113
|
+
"session_command",
|
|
114
|
+
"model_pull",
|
|
115
|
+
"model_copy",
|
|
116
|
+
"model_create",
|
|
117
|
+
"model_delete",
|
|
118
|
+
"harness_refresh",
|
|
119
|
+
"reindex_knowledge_graph",
|
|
120
|
+
"write_knowledge_graph_note",
|
|
121
|
+
"remember",
|
|
122
|
+
"append_lesson",
|
|
123
|
+
"x_account_post",
|
|
124
|
+
"x_account_reply",
|
|
125
|
+
"x_account_post_action",
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def tool_capability_mask(tool_name: str) -> CapabilityMask:
|
|
130
|
+
"""Return the stable structural capabilities required by a runtime tool."""
|
|
131
|
+
|
|
132
|
+
mask = CapabilityMask().add(Capability.READ)
|
|
133
|
+
if tool_name in _STATE_WRITE_TOOLS:
|
|
134
|
+
mask = mask.add(Capability.WRITE)
|
|
135
|
+
if tool_name == "run_shell":
|
|
136
|
+
mask = mask.add(Capability.SHELL)
|
|
137
|
+
if tool_name in _NETWORK_TOOLS:
|
|
138
|
+
mask = mask.add(Capability.NETWORK)
|
|
139
|
+
if tool_name in _MODEL_TOOLS:
|
|
140
|
+
mask = mask.add(Capability.MODEL)
|
|
141
|
+
if tool_name in _CREDENTIAL_TOOLS:
|
|
142
|
+
mask = mask.add(Capability.CREDENTIAL)
|
|
143
|
+
if tool_name in _MEMORY_TOOLS:
|
|
144
|
+
mask = mask.add(Capability.MEMORY)
|
|
145
|
+
if tool_name in _EXTERNAL_PUBLISH_TOOLS:
|
|
146
|
+
mask = mask.add(Capability.EXTERNAL_PUBLISH)
|
|
147
|
+
if tool_name in _DESTRUCTIVE_TOOLS:
|
|
148
|
+
mask = mask.add(Capability.DESTRUCTIVE)
|
|
149
|
+
return mask
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def tool_capability_tier(mask: CapabilityMask) -> str:
|
|
153
|
+
"""Choose the least-privileged tier that contains every required capability."""
|
|
154
|
+
|
|
155
|
+
for tier in ("tier0", "tier1", "tier2", "tier3"):
|
|
156
|
+
if mask.value & ~tier_mask(tier) == 0:
|
|
157
|
+
return tier
|
|
158
|
+
return "tier3"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def evaluate_runtime_tool_policy(
|
|
162
|
+
tool_name: str,
|
|
163
|
+
args: dict[str, Any],
|
|
164
|
+
*,
|
|
165
|
+
safe_mode: bool,
|
|
166
|
+
) -> RuntimeToolPolicyDecision:
|
|
167
|
+
"""Run the policy-chain and capability-mask preflight for a tool call."""
|
|
168
|
+
|
|
169
|
+
mask = tool_capability_mask(tool_name)
|
|
170
|
+
tier = tool_capability_tier(mask)
|
|
171
|
+
|
|
172
|
+
def registered_check(call: dict[str, Any], _context: dict[str, Any]) -> CheckResult:
|
|
173
|
+
registered = call["tool"] in tools_module.TOOL_MAP
|
|
174
|
+
return CheckResult(
|
|
175
|
+
name="tool_registered",
|
|
176
|
+
passed=registered,
|
|
177
|
+
reason="" if registered else f"unknown runtime tool: {call['tool']}",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def capability_check(call: dict[str, Any], _context: dict[str, Any]) -> CheckResult:
|
|
181
|
+
required = int(call["capability_mask"])
|
|
182
|
+
ceiling = tier_mask(str(call["tier"]))
|
|
183
|
+
passed = required & ~ceiling == 0
|
|
184
|
+
return CheckResult(
|
|
185
|
+
name="capability_tier",
|
|
186
|
+
passed=passed,
|
|
187
|
+
reason="" if passed else f"capability mask {required} exceeds {call['tier']}",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def safe_shell_check(call: dict[str, Any], context: dict[str, Any]) -> CheckResult:
|
|
191
|
+
blocked = (
|
|
192
|
+
call["tool"] == "run_shell"
|
|
193
|
+
and bool(context.get("safe_mode"))
|
|
194
|
+
and tools_module.shell_is_dangerous(str(call.get("args", {}).get("command", "")))
|
|
195
|
+
)
|
|
196
|
+
return CheckResult(
|
|
197
|
+
name="safe_shell",
|
|
198
|
+
passed=not blocked,
|
|
199
|
+
reason="safe mode blocks destructive commands and shell mutations" if blocked else "",
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
chain = (
|
|
203
|
+
(Control.REQUISITE, registered_check),
|
|
204
|
+
(Control.REQUIRED, capability_check),
|
|
205
|
+
(Control.REQUISITE, safe_shell_check),
|
|
206
|
+
)
|
|
207
|
+
chain_decision = evaluate_chain(
|
|
208
|
+
"runtime-tool",
|
|
209
|
+
chain,
|
|
210
|
+
{
|
|
211
|
+
"tool": tool_name,
|
|
212
|
+
"args": dict(args),
|
|
213
|
+
"tier": tier,
|
|
214
|
+
"capability_mask": mask.value,
|
|
215
|
+
},
|
|
216
|
+
{"safe_mode": safe_mode},
|
|
217
|
+
)
|
|
218
|
+
return RuntimeToolPolicyDecision(
|
|
219
|
+
tool_name=tool_name,
|
|
220
|
+
tier=tier,
|
|
221
|
+
capability_mask=mask.value,
|
|
222
|
+
capability_names=mask.names(),
|
|
223
|
+
allowed=chain_decision.passed,
|
|
224
|
+
reasons=chain_decision.reasons,
|
|
225
|
+
fired_rules=tuple(chain_decision.fired_rules()),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def evaluate_shell_command(
|
|
230
|
+
command: str,
|
|
231
|
+
*,
|
|
232
|
+
requires_change: bool,
|
|
233
|
+
safe_mode: bool,
|
|
234
|
+
) -> ShellMutationDecision:
|
|
235
|
+
"""Apply required-change shell mutation policy to one command."""
|
|
236
|
+
|
|
237
|
+
if not requires_change:
|
|
238
|
+
return ShellMutationDecision(False, False, False)
|
|
239
|
+
if not tools_module.shell_mutates_workspace(command):
|
|
240
|
+
return ShellMutationDecision(False, False, False)
|
|
241
|
+
reason = (
|
|
242
|
+
"file edits must use edit_file or write_file; direct file or Git mutations through "
|
|
243
|
+
"run_shell require explicit approval"
|
|
244
|
+
)
|
|
245
|
+
return ShellMutationDecision(
|
|
246
|
+
is_mutation=True,
|
|
247
|
+
blocked=safe_mode,
|
|
248
|
+
force_approval=not safe_mode,
|
|
249
|
+
reason=reason,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def requires_explicit_approval(
|
|
254
|
+
tool_name: str,
|
|
255
|
+
*,
|
|
256
|
+
block_policy: ToolPolicyDecision,
|
|
257
|
+
shell_decision: ShellMutationDecision,
|
|
258
|
+
policy_enforced: bool,
|
|
259
|
+
) -> bool:
|
|
260
|
+
"""Resolve all Agent Block policy sources that require an approval prompt."""
|
|
261
|
+
|
|
262
|
+
return shell_decision.force_approval or (
|
|
263
|
+
policy_enforced and tool_name in block_policy.approval_required
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def supports_mutation_audit(block_tools: frozenset[str]) -> bool:
|
|
268
|
+
"""Return whether a block can execute a workspace mutation tool."""
|
|
269
|
+
|
|
270
|
+
return bool(block_tools & MUTATING_TOOLS)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def describes_mutation_action(tool_name: str, args: dict[str, object]) -> str | None:
|
|
274
|
+
"""Describe a model-invoked action whose effects should be audited."""
|
|
275
|
+
|
|
276
|
+
if tool_name == "write_file":
|
|
277
|
+
path = str(args.get("path", "")).strip() or "(unspecified path)"
|
|
278
|
+
return f"write_file: {path}"
|
|
279
|
+
if tool_name == "edit_file":
|
|
280
|
+
path = str(args.get("path", "")).strip() or "(unspecified path)"
|
|
281
|
+
return f"edit_file: {path}"
|
|
282
|
+
if tool_name == "batch_edit":
|
|
283
|
+
path = str(args.get("path", "")).strip() or "(unspecified path)"
|
|
284
|
+
return f"batch_edit: {path}"
|
|
285
|
+
if tool_name == "run_shell":
|
|
286
|
+
command = str(args.get("command", "")).strip()
|
|
287
|
+
if tools_module.shell_mutates_workspace(command):
|
|
288
|
+
compact = " ".join(command.split())
|
|
289
|
+
return f"run_shell: {compact[:160]}"
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _route_ceiling(route: TaskRoute, block_tools: frozenset[str]) -> frozenset[str]:
|
|
294
|
+
if route.recommended_mode != "agent" or not route.allowed_tool_groups:
|
|
295
|
+
return block_tools
|
|
296
|
+
return expand_tool_groups(route.allowed_tool_groups)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def compute_policy(
|
|
300
|
+
route: TaskRoute,
|
|
301
|
+
block_role: str,
|
|
302
|
+
block_tools: frozenset[str],
|
|
303
|
+
safe_mode: bool,
|
|
304
|
+
auto_mode: bool,
|
|
305
|
+
) -> ToolPolicyDecision:
|
|
306
|
+
"""Return the advisory effective tool policy for one block."""
|
|
307
|
+
allowed = block_tools & _route_ceiling(route, block_tools)
|
|
308
|
+
reasons: list[str] = []
|
|
309
|
+
|
|
310
|
+
denied_by_route = block_tools - allowed
|
|
311
|
+
if denied_by_route:
|
|
312
|
+
reasons.append("route tool groups restrict this block")
|
|
313
|
+
|
|
314
|
+
if route.risk == "high":
|
|
315
|
+
denied = allowed & MUTATING_TOOLS
|
|
316
|
+
allowed -= denied
|
|
317
|
+
if denied:
|
|
318
|
+
reasons.append("high risk denies write and shell tools")
|
|
319
|
+
|
|
320
|
+
if route.task_type == "review":
|
|
321
|
+
denied = allowed & MUTATING_TOOLS
|
|
322
|
+
allowed -= denied
|
|
323
|
+
if denied:
|
|
324
|
+
reasons.append("review tasks are read-only")
|
|
325
|
+
elif route.task_type == "research":
|
|
326
|
+
denied = allowed & WRITE_TOOLS
|
|
327
|
+
allowed -= denied
|
|
328
|
+
if denied:
|
|
329
|
+
reasons.append("research tasks deny writes")
|
|
330
|
+
|
|
331
|
+
approval_required = frozenset()
|
|
332
|
+
if route.risk == "medium" and not auto_mode:
|
|
333
|
+
approval_required = allowed & MUTATING_TOOLS
|
|
334
|
+
if approval_required:
|
|
335
|
+
reasons.append("medium risk requires approval for mutating tools")
|
|
336
|
+
if safe_mode and "run_shell" in allowed:
|
|
337
|
+
reasons.append("safe mode remains active inside shell execution")
|
|
338
|
+
|
|
339
|
+
denied_tools = block_tools - allowed
|
|
340
|
+
if not reasons:
|
|
341
|
+
reasons.append(f"{block_role} block policy unchanged")
|
|
342
|
+
return ToolPolicyDecision(
|
|
343
|
+
allowed_tools=allowed,
|
|
344
|
+
denied_tools=denied_tools,
|
|
345
|
+
approval_required=approval_required,
|
|
346
|
+
reasons=tuple(reasons),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def format_policy_summary(decision: ToolPolicyDecision) -> str:
|
|
351
|
+
allowed = ", ".join(sorted(decision.allowed_tools)) or "none"
|
|
352
|
+
text = f"tools: {allowed}"
|
|
353
|
+
if decision.approval_required:
|
|
354
|
+
text += f" (approval: {', '.join(sorted(decision.approval_required))})"
|
|
355
|
+
if decision.denied_tools:
|
|
356
|
+
text += f"; denied: {', '.join(sorted(decision.denied_tools))}"
|
|
357
|
+
return text
|