From 1cdcff9502d7f55fbcf34c4d5b2a09c537a7dcc3 Mon Sep 17 00:00:00 2001 From: yyjeqhc <1772413353@qq.com> Date: Mon, 9 Nov 2026 11:09:18 +0800 Subject: [PATCH 1/2] refactor(tools): replace positional definitions with named declarations --- .../src/tool_definition.rs | 102 +-- .../src/tool_definition/agent_tasks.rs | 72 +- .../src/tool_definition/artifacts.rs | 396 +++++----- .../src/tool_definition/browser.rs | 21 +- .../src/tool_definition/checkpoints.rs | 162 ++-- .../src/tool_definition/code_mode.rs | 94 +-- .../src/tool_definition/coding_agents.rs | 162 ++-- .../src/tool_definition/computer.rs | 52 +- .../src/tool_definition/diagnostics.rs | 12 +- .../src/tool_definition/documents.rs | 27 +- .../src/tool_definition/docx.rs | 27 +- .../src/tool_definition/edits.rs | 82 +- .../src/tool_definition/git.rs | 34 +- .../src/tool_definition/hygiene.rs | 125 +-- .../src/tool_definition/jobs.rs | 728 +++++++++--------- .../src/tool_definition/memory.rs | 143 ++-- .../src/tool_definition/patches.rs | 73 +- .../src/tool_definition/plugins.rs | 15 +- .../tool_definition/resource_references.rs | 10 +- .../src/tool_definition/runner_config.rs | 63 +- .../src/tool_definition/sessions.rs | 568 +++++++------- .../src/tool_definition/skills.rs | 214 ++--- .../src/tool_definition/ssh_resources.rs | 14 +- .../src/tool_definition/testing.rs | 178 ++--- 24 files changed, 1689 insertions(+), 1685 deletions(-) diff --git a/crates/webcodex-tool-contracts/src/tool_definition.rs b/crates/webcodex-tool-contracts/src/tool_definition.rs index 2bd98731f..766ef6257 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition.rs @@ -516,7 +516,7 @@ pub enum ToolAuditSemanticResultPolicy { impl ToolAuditPolicy { /// Existing audit-safe behavior for tools without a narrower result - /// projection. Every ToolDefinition must opt in explicitly through `def(...)`; + /// projection. Every ToolDefinition must declare its audit policy explicitly; /// there is intentionally no implicit/missing-policy default. pub const TYPED_CANONICAL: Self = Self { request: ToolAuditRequestPolicy::Typed, @@ -1179,7 +1179,10 @@ pub struct ToolManifestIntent { pub tools: &'static [&'static str], } -const fn def( +// Required inputs to the canonical definition. Keep these named and without a +// Default: visibility, audit, authority, capability and effect flags must be +// deliberate at each declaration (or supplied by a narrow shared constructor). +struct ToolDeclaration { name: &'static str, audit: ToolAuditPolicy, visibility: ToolVisibility, @@ -1193,39 +1196,42 @@ const fn def( destructive: bool, shell_like: bool, session_evidence: ToolSessionEvidencePolicy, -) -> ToolDefinition { - ToolDefinition { - name, - audit, - model_spec: None, - adaptive_runtime_direct: None, +} - operator_extension_family: None, - execution: None, - composition: ToolCompositionPolicy::Denied, - host_orchestration: ToolHostOrchestrationHint::UNSPECIFIED, - visibility, - category, - metadata: make_tool_metadata( - name, - provider_id, - semantic, - required_scope, - requires_project, - path_hint, - destructive, - shell_like, - ), - policy: ToolDefinitionPolicy::DEFAULT, - activity: ToolActivityPolicy::DEFAULT, - session_evidence, - runner_capability, +impl ToolDeclaration { + const fn define(self) -> ToolDefinition { + ToolDefinition { + name: self.name, + audit: self.audit, + model_spec: None, + adaptive_runtime_direct: None, + operator_extension_family: None, + execution: None, + composition: ToolCompositionPolicy::Denied, + host_orchestration: ToolHostOrchestrationHint::UNSPECIFIED, + visibility: self.visibility, + category: self.category, + metadata: make_tool_metadata( + self.name, + self.provider_id, + self.semantic, + self.required_scope, + self.requires_project, + self.path_hint, + self.destructive, + self.shell_like, + ), + policy: ToolDefinitionPolicy::DEFAULT, + activity: ToolActivityPolicy::DEFAULT, + session_evidence: self.session_evidence, + runner_capability: self.runner_capability, + } } } // Runner-backed Project reads share this authority/effect contract. Audit // redaction, capability, path selection, and Session evidence stay explicit -// at each declaration; mutation and execution tools continue to use `def`. +// at each declaration; mutation and execution tools use a full ToolDeclaration. const fn runner_project_read_def( name: &'static str, audit: ToolAuditPolicy, @@ -1234,26 +1240,27 @@ const fn runner_project_read_def( path_hint: ToolPathHint, session_evidence: ToolSessionEvidencePolicy, ) -> ToolDefinition { - def( + ToolDeclaration { name, audit, - ToolVisibility::ModelVisible, + visibility: ToolVisibility::ModelVisible, category, - Some(runner_capability), - TOOL_PROVIDER_RUNNER, - ToolSemanticContract { + runner_capability: Some(runner_capability), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: ToolSemanticContract { effect: ToolEffect::Observe, risk: ToolRisk::Read, approval: ToolApprovalPolicy::None, idempotency: ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, + required_scope: Some(PROJECT_READ), + requires_project: true, path_hint, - false, - false, + destructive: false, + shell_like: false, session_evidence, - ) + } + .define() } // Server-owned Control operations without a Project, path, Runner dispatch, or @@ -1268,21 +1275,22 @@ const fn control_def( required_scope: Option<&'static str>, destructive: bool, ) -> ToolDefinition { - def( + ToolDeclaration { name, audit, visibility, category, - None, - TOOL_PROVIDER_CONTROL, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, semantic, required_scope, - false, - NoPath, + requires_project: false, + path_hint: NoPath, destructive, - false, - ToolSessionEvidencePolicy::NONE, - ) + shell_like: false, + session_evidence: ToolSessionEvidencePolicy::NONE, + } + .define() } const fn model_spec(definition: ToolDefinition, description: &'static str) -> ToolDefinition { diff --git a/crates/webcodex-tool-contracts/src/tool_definition/agent_tasks.rs b/crates/webcodex-tool-contracts/src/tool_definition/agent_tasks.rs index e12373bc3..393fec76e 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/agent_tasks.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/agent_tasks.rs @@ -1,7 +1,7 @@ use super::RunnerCapabilityRequirement::CodingAgentRuns; use super::ToolVisibility::ModelVisible; use super::{ - control_def, def, model_spec, permission_risk, require_all_scopes, ToolDefinition, + control_def, model_spec, permission_risk, require_all_scopes, ToolDeclaration, ToolDefinition, PERMISSION_RISK_JOB, PERMISSION_RISK_WRITE, TOOL_CATEGORY_AGENT_TASK, }; use crate::metadata::{ @@ -210,26 +210,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ permission_risk( model_spec( require_all_scopes( - def( - "start_agent_task_coding_run", - CODING_RUN_AUDIT, - ModelVisible, - TOOL_CATEGORY_AGENT_TASK, - Some(CodingAgentRuns), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "start_agent_task_coding_run", + audit: CODING_RUN_AUDIT, + visibility: ModelVisible, + category: TOOL_CATEGORY_AGENT_TASK, + runner_capability: Some(CodingAgentRuns), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::FencedReplay, }, - Some(CODING_AGENT_RUN), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(CODING_AGENT_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), &[ COMMUNICATION_READ, COMMUNICATION_MANAGE, @@ -244,26 +244,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ permission_risk( model_spec( require_all_scopes( - def( - "reconcile_agent_task_coding_run", - CODING_RUN_AUDIT, - ModelVisible, - TOOL_CATEGORY_AGENT_TASK, - None, - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: WorkflowManage, - approval: super::ToolApprovalPolicy::None, - idempotency: super::ToolIdempotency::DesiredState, - }, - Some(COMMUNICATION_MANAGE), - false, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + ToolDeclaration { + name: "reconcile_agent_task_coding_run", + audit: CODING_RUN_AUDIT, + visibility: ModelVisible, + category: TOOL_CATEGORY_AGENT_TASK, + runner_capability: None, + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: WorkflowManage, + approval: super::ToolApprovalPolicy::None, + idempotency: super::ToolIdempotency::DesiredState, + }, + required_scope: Some(COMMUNICATION_MANAGE), + requires_project: false, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), &[COMMUNICATION_READ, COMMUNICATION_MANAGE, CODING_AGENT_RUN], ), "Reconcile only the exact CodingAgentRun already durably bound to one owned AgentTaskAttempt. It never starts execution, chooses a provider, changes intent, or requires the old browser's Attempt fence. Authoritative Completed/Failed/Cancelled truth can terminalize the exact latest bound Attempt even after its ordinary lease expires; Lost remains outcome_unknown.", diff --git a/crates/webcodex-tool-contracts/src/tool_definition/artifacts.rs b/crates/webcodex-tool-contracts/src/tool_definition/artifacts.rs index 80ff5f6cc..c1d0c2d4a 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/artifacts.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/artifacts.rs @@ -1,9 +1,9 @@ use super::RunnerCapabilityRequirement::{FileRead, FileWrite}; use super::ToolVisibility::ModelVisible; use super::{ - adaptive_runtime_direct, def, model_spec, permission_risk, require_all_scopes, - requires_artifact_upload_path_binding, ToolDefinition, PERMISSION_RISK_ARTIFACT_WRITE, - TOOL_CATEGORY_ARTIFACT, + adaptive_runtime_direct, model_spec, permission_risk, require_all_scopes, + requires_artifact_upload_path_binding, ToolDeclaration, ToolDefinition, + PERMISSION_RISK_ARTIFACT_WRITE, TOOL_CATEGORY_ARTIFACT, }; use crate::metadata::{ ToolPathHint::Artifact, @@ -14,26 +14,26 @@ use crate::metadata::{ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ adaptive_runtime_direct( model_spec( - def( - "present_spreadsheet", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileRead), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "present_spreadsheet", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileRead), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, @@ -45,26 +45,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ), permission_risk( model_spec( - def( - "save_project_artifact", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileWrite), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: ProjectWrite, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - Artifact, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + ToolDeclaration { + name: "save_project_artifact", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: ProjectWrite, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Artifact, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Write a bounded binary project artifact when the caller already holds the bounded binary/base64 content. Do not read a current ChatGPT/host attachment and base64-encode it through the model; use import_host_files for host-native attachment import. Suitable for generated images, PDFs, ZIP archives, and DOCX/PPTX/XLSX Office artifacts already present as bounded caller data; not for UTF-8 source edits.", ), PERMISSION_RISK_ARTIFACT_WRITE, @@ -72,26 +72,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ adaptive_runtime_direct( permission_risk( model_spec( - def( - "import_host_files", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileWrite), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "import_host_files", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - Artifact, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Artifact, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Import 1..10 current ChatGPT/host attachments into a Runner project using openaiFileIdRefs from the host file-reference mechanism, up to 256 MiB per file. This is the preferred host-native attachment-to-project transfer path: do not base64-transfer files, construct download URLs, or use local /mnt/data paths; Control streams downloads into project artifacts. A multi-file batch is not atomic: if a later item fails, structured output preserves imported/succeeded items, identifies the failed item and reason, and sets partial_success=true. MCP trust has two tiers: an active authenticated OAuth client may import only from OpenAI file hosts; an exact WEBCODEX_OAUTH2_TRUSTED_MCP_FILE_CLIENT_IDS match may import from arbitrary public HTTPS. Both retain DNS/public-IP, pinning, redirect, bounded-download, and Project write protections.", ), PERMISSION_RISK_ARTIFACT_WRITE, @@ -102,26 +102,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ permission_risk( model_spec( require_all_scopes( - def( - "transfer_project_artifact", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "transfer_project_artifact", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - None, - false, - Artifact, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: None, + requires_project: false, + path_hint: Artifact, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), &[PROJECT_READ, PROJECT_WRITE], ), "Transfer one regular artifact directly from a source Project to a destination Project through Control. Requires source project:read and destination project:write; each Project is independently resolved and authorized. Control fences the exact source bytes/SHA-256/MIME snapshot, streams bounded internal chunks into the existing destination artifact upload protocol, and verifies the committed destination bytes/SHA-256. Binary payloads do not pass through Host attachments or model text. overwrite defaults to false.", @@ -131,26 +131,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ permission_risk( model_spec( require_all_scopes( - def( - "import_artifact_handoff", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "import_artifact_handoff", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::Keyed, }, - None, - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: None, + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), &[PROJECT_WRITE], ), "Accept one exact artifact handoff grant as its bound destination principal and import the frozen source snapshot into the destination Project. Requires only destination project:write; the grant authorizes the exact source read. Control revalidates destination authority and grant state, re-reads the source snapshot through the existing snapshot-fenced Runner export path, fails stale on changed bytes, and streams bounded internal chunks into the existing destination artifact upload protocol. Same idempotency_key + complete request replays the original result; conflicting or one-shot replay fails closed.", @@ -159,171 +159,171 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( model_spec( - def( - "inspect_project_artifact", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileRead), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "inspect_project_artifact", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileRead), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Project artifact read: metadata=facts; inspect=fenced segment; image=MCP image; export=MCP ResourceLink. Use export for whole files, not repeated inspect calls; use import_host_files for host-to-Project attachments.", ), 56, super::ToolDirectReason::CoreWorkflow, ), model_spec( - def( - "read_project_artifact_metadata", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileRead), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "read_project_artifact_metadata", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileRead), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Read artifact size/SHA/MIME up to 256 MiB; no contents are returned. Files up to 10 MiB also retain image dimensions and ZIP entry counts when recognized; larger files use bounded streaming hashing. ZIPs are never extracted. allow_missing=true returns exists=false for an absent file.", ), model_spec( - def( - "read_project_artifact_chunk", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileRead), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "read_project_artifact_chunk", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileRead), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Bounded chunk inspection API for a project artifact. Returns base64 for one small segment plus full-file sha256/MIME metadata. A truncated ranged read emits one parser-ready suggested_call that carries the observed full-file sha256 as expected_sha256, so continuation either reads the same exact content incarnation or fails closed with snapshot_changed before returning changed bytes; do not manually translate next_offset or sha256 bookkeeping. If the goal is to deliver the complete file to ChatGPT/host/user, do not loop over base64 chunks; use inspect_project_artifact(action=export) instead.", ), model_spec( - def( - "begin_artifact_upload", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileWrite), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "begin_artifact_upload", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Begin a bounded low-level chunked binary artifact upload up to 256 MiB. Creates a project-local temporary upload session; finish commits atomically to the target path. This is not the preferred path for a current ChatGPT/host attachment; use import_host_files for host-native import. MIME is presentation metadata: unknown regular artifacts may use application/octet-stream; Project authorization, sensitive-path checks, root containment, symlink protection, fencing, and byte/SHA validation remain the safety boundary.", ), requires_artifact_upload_path_binding(model_spec( - def( - "upload_artifact_chunk", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileWrite), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "upload_artifact_chunk", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Append one base64 chunk up to 1 MiB decoded to an active artifact upload. path is required and must exactly match begin_artifact_upload; this binds upload_id to the target path.", )), requires_artifact_upload_path_binding(permission_risk( model_spec( - def( - "finish_artifact_upload", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileWrite), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: ProjectWrite, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - Artifact, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + ToolDeclaration { + name: "finish_artifact_upload", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: ProjectWrite, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Artifact, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Finish an active artifact upload. path is required and must exactly match begin_artifact_upload; this binds upload_id before atomic commit.", ), PERMISSION_RISK_ARTIFACT_WRITE, )), requires_artifact_upload_path_binding(permission_risk( model_spec( - def( - "abort_artifact_upload", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_ARTIFACT, - Some(FileWrite), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: ProjectWrite, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - Artifact, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + ToolDeclaration { + name: "abort_artifact_upload", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: ProjectWrite, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Artifact, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Abort an active artifact upload. path is required and must exactly match begin_artifact_upload; this binds upload_id before cleanup and reports final_file_exists without touching the final target.", ), PERMISSION_RISK_ARTIFACT_WRITE, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/browser.rs b/crates/webcodex-tool-contracts/src/tool_definition/browser.rs index b8c9f0df8..b9e702b19 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/browser.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/browser.rs @@ -1,12 +1,11 @@ use super::ToolVisibility::ModelVisible; use super::{ - def, model_spec, permission_risk, require_any_scopes, ToolDefinition, + control_def, model_spec, permission_risk, require_any_scopes, ToolDefinition, PERMISSION_RISK_BROWSER_CONTROL, TOOL_CATEGORY_BROWSER, }; use crate::metadata::{ - ToolPathHint::None as NoPath, ToolRisk::{BrowserControl as BrowserControlRisk, Read}, - BROWSER_CONTROL, BROWSER_LAUNCH, BROWSER_READ, TOOL_PROVIDER_CONTROL, + BROWSER_CONTROL, BROWSER_LAUNCH, BROWSER_READ, }; const BROWSER_ACT_GATEWAY_SCOPES: &[&str] = &[BROWSER_CONTROL, BROWSER_LAUNCH]; @@ -57,15 +56,13 @@ mod tests { pub(super) const DEFINITIONS: &[ToolDefinition] = &[ model_spec( - def( + control_def( "observe_browser", super::ToolAuditPolicy::typed_semantic( super::ToolAuditSemanticResultPolicy::BrowserObservation, ), ModelVisible, TOOL_CATEGORY_BROWSER, - None, - TOOL_PROVIDER_CONTROL, super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, @@ -74,25 +71,19 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ }, Some(BROWSER_READ), false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, ), "Read-only Browser; needs browser:read and capabilities. discover lists explicit Share offers; browsers/pages lists attached runtimes. New tabs need Share then attach. stale_element: snapshot; stale_attachment: restore Share. surface maps exact native window, also needs computer:read; ambiguity fails closed. IDs are process-local. browsers/user_handoff recovers live hold/fence. Query AND-filters semantic fields over <=4352 nodes before paging. Bounds/truncation make absence inconclusive. New snapshot stales old ids; element_id/actions admit effects. since_cursor returns later diagnostics. No scripts/selectors/CDP.", ), require_any_scopes( permission_risk( model_spec( - def( + control_def( "control_browser", super::ToolAuditPolicy::typed_semantic( super::ToolAuditSemanticResultPolicy::BrowserControl, ), ModelVisible, TOOL_CATEGORY_BROWSER, - None, - TOOL_PROVIDER_CONTROL, super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: BrowserControlRisk, @@ -100,11 +91,7 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ idempotency: super::ToolIdempotency::NonIdempotent, }, None, - false, - NoPath, true, - false, - super::ToolSessionEvidencePolicy::NONE, ) .with_host_orchestration_hint( super::ToolHostOrchestrationHint::sequential() diff --git a/crates/webcodex-tool-contracts/src/tool_definition/checkpoints.rs b/crates/webcodex-tool-contracts/src/tool_definition/checkpoints.rs index 20e40cf8c..c20dd878b 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/checkpoints.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/checkpoints.rs @@ -1,7 +1,7 @@ use super::RunnerCapabilityRequirement::{FileRead, FileWrite, OwnerOnly}; use super::ToolVisibility::ModelVisible; use super::{ - def, git_like, model_spec, permission_risk, ToolDefinition, PERMISSION_RISK_PATCH, + git_like, model_spec, permission_risk, ToolDeclaration, ToolDefinition, PERMISSION_RISK_PATCH, TOOL_CATEGORY_CHECKPOINT, }; use crate::metadata::{ @@ -12,9 +12,9 @@ use crate::metadata::{ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ git_like(model_spec( - def( - "create_workspace_checkpoint", - super::ToolAuditPolicy::TYPED_CANONICAL.context( + ToolDeclaration { + name: "create_workspace_checkpoint", + audit: super::ToolAuditPolicy::TYPED_CANONICAL.context( super::ToolAuditContextPolicy::Fields(&[ super::ToolAuditResultField::value("checkpoint_id"), super::ToolAuditResultField::value("head"), @@ -27,120 +27,120 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("kind"), ]), ), - ModelVisible, - TOOL_CATEGORY_CHECKPOINT, - Some(FileRead), - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_CHECKPOINT, + runner_capability: Some(FileRead), + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: super::ToolRisk::CheckpointManage, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE .failure(super::ToolFailureEvidence::ProvenNoStateChangeNonActionable) .lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ), + }.define(), "Create a bounded workspace checkpoint outside the project worktree. Captures HEAD, status, text diffs, and optional small untracked text files.", )), model_spec( - def( - "list_workspace_checkpoints", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CHECKPOINT, - Some(OwnerOnly), - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + ToolDeclaration { + name: "list_workspace_checkpoints", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CHECKPOINT, + runner_capability: Some(OwnerOnly), + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "List checkpoint metadata for a project without returning full diffs or saved file content.", ), model_spec( - def( - "read_workspace_checkpoint", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CHECKPOINT, - Some(OwnerOnly), - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + ToolDeclaration { + name: "read_workspace_checkpoint", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CHECKPOINT, + runner_capability: Some(OwnerOnly), + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Show bounded checkpoint metadata, file list, skipped files, and optional diff stat. Does not return full diff/content by default.", ), git_like(permission_risk( model_spec( - def( - "restore_workspace_checkpoint", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CHECKPOINT, - Some(FileWrite), - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: ProjectWrite, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - Patch, - true, - false, - super::ToolSessionEvidencePolicy::NONE.changed_paths(super::ToolChangedPathEvidence::ResultField("changed_paths")).lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ), + ToolDeclaration { + name: "restore_workspace_checkpoint", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CHECKPOINT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: ProjectWrite, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Patch, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.changed_paths(super::ToolChangedPathEvidence::ResultField("changed_paths")).lifecycle(super::ToolSessionLifecycleEffect::Mutation), + }.define(), "Restore a checkpoint after confirm=true. Requires matching HEAD and refuses unsafe current state rather than half-restoring.", ), PERMISSION_RISK_PATCH, )), model_spec( - def( - "delete_workspace_checkpoint", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CHECKPOINT, - Some(OwnerOnly), - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + ToolDeclaration { + name: "delete_workspace_checkpoint", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CHECKPOINT, + runner_capability: Some(OwnerOnly), + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), + }.define(), "Delete one checkpoint JSON file after confirm=true. Does not touch the project worktree.", ), ]; diff --git a/crates/webcodex-tool-contracts/src/tool_definition/code_mode.rs b/crates/webcodex-tool-contracts/src/tool_definition/code_mode.rs index 869a7fb3a..842a8548c 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/code_mode.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/code_mode.rs @@ -1,7 +1,7 @@ use super::ToolVisibility::ModelVisible; use super::{ - adaptive_runtime_direct, def, model_spec, permission_risk, requires_explicit_business_session, - ToolDefinition, PERMISSION_RISK_WRITE, TOOL_CATEGORY_RUNTIME, + adaptive_runtime_direct, model_spec, permission_risk, requires_explicit_business_session, + ToolDeclaration, ToolDefinition, PERMISSION_RISK_WRITE, TOOL_CATEGORY_RUNTIME, }; use crate::metadata::{ ToolPathHint::None as NoPath, @@ -31,27 +31,27 @@ const EFFECTFUL_RESULT_AUDIT_FIELDS: &[super::ToolAuditResultField] = &[ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ adaptive_runtime_direct( requires_explicit_business_session(model_spec( - def( - "execute_code_mode", - super::ToolAuditPolicy::typed_fields(RESULT_AUDIT_FIELDS), - ModelVisible, - TOOL_CATEGORY_RUNTIME, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "execute_code_mode", + audit: super::ToolAuditPolicy::typed_fields(RESULT_AUDIT_FIELDS), + visibility: ModelVisible, + category: TOOL_CATEGORY_RUNTIME, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE .review(super::ToolReviewEvidence::ReadOnlyInspection), - ), + }.define(), "Read-only Code Mode for related inspections. Prefer a direct tool for one simple observation. Use Promise.all only for independent calls; keep dependent follow-ups sequential inside one cell. Filter child results before text(value); never a raw-result dump. Project before the outer-output limit. Children keep canonical authority; no shell/fs/network/mutation/validation/Jobs.", )), 45, @@ -59,26 +59,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( requires_explicit_business_session(model_spec( - def( - "execute_effectful_code_mode", - super::ToolAuditPolicy::typed_fields(EFFECTFUL_RESULT_AUDIT_FIELDS), - ModelVisible, - TOOL_CATEGORY_RUNTIME, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "execute_effectful_code_mode", + audit: super::ToolAuditPolicy::typed_fields(EFFECTFUL_RESULT_AUDIT_FIELDS), + visibility: ModelVisible, + category: TOOL_CATEGORY_RUNTIME, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Validation Code Mode for E1 reads plus project_validate, cargo_check and cargo_test. Prefer project_validate for portable validation; use Cargo validator specialists when their advanced contract matters. Distill results before text(value). Children retain canonical Project/Session, permission, validation and Job semantics. Plugin/MCP gateways, shell/process tools, nested Job observation and recursive Code Mode remain unavailable.", )), 105, @@ -87,26 +87,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ adaptive_runtime_direct( permission_risk( requires_explicit_business_session(model_spec( - super::require_all_scopes(def( - "execute_mutating_code_mode", - super::ToolAuditPolicy::typed_fields(EFFECTFUL_RESULT_AUDIT_FIELDS), - ModelVisible, - TOOL_CATEGORY_RUNTIME, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + super::require_all_scopes(ToolDeclaration { + name: "execute_mutating_code_mode", + audit: super::ToolAuditPolicy::typed_fields(EFFECTFUL_RESULT_AUDIT_FIELDS), + visibility: ModelVisible, + category: TOOL_CATEGORY_RUNTIME, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE, - ), &[PROJECT_WRITE, JOB_RUN]), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), &[PROJECT_WRITE, JOB_RUN]), "Bounded coding Code Mode: adaptive E1 reads, at most one canonical edit_project_files attempt, then project_validate, cargo_check or cargo_test only after a successful known edit; a successful no-op may validate. Requires project:write and job:run; child authority remains canonical. Execution pass is not current-source proof: inspect source_state. Continue Jobs outside the cell using exact effect_receipt children. Plugin/MCP gateways, shell/process tools, nested Job observation, alternate writes, recursion and whole-program retry remain unavailable.", )), PERMISSION_RISK_WRITE, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/coding_agents.rs b/crates/webcodex-tool-contracts/src/tool_definition/coding_agents.rs index 387643a77..01fbfea47 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/coding_agents.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/coding_agents.rs @@ -1,8 +1,8 @@ use super::RunnerCapabilityRequirement::CodingAgentRuns; use super::ToolVisibility::ModelVisible; use super::{ - def, model_spec, permission_risk, require_all_scopes, ToolDefinition, PERMISSION_RISK_JOB, - PERMISSION_RISK_WRITE, TOOL_CATEGORY_CODING_AGENT, + model_spec, permission_risk, require_all_scopes, ToolDeclaration, ToolDefinition, + PERMISSION_RISK_JOB, PERMISSION_RISK_WRITE, TOOL_CATEGORY_CODING_AGENT, }; use crate::metadata::{ ToolPathHint::None as NoPath, @@ -14,9 +14,9 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ permission_risk( model_spec( require_all_scopes( - def( - "start_coding_agent", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "start_coding_agent", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("run_id"), super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("provider_id"), @@ -29,23 +29,23 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("recovery_kind"), ]), - ModelVisible, - TOOL_CATEGORY_CODING_AGENT, - Some(CodingAgentRuns), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_CODING_AGENT, + runner_capability: Some(CodingAgentRuns), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::Keyed, }, - Some(CODING_AGENT_RUN), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(CODING_AGENT_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), &[CODING_AGENT_RUN, webcodex_core::authority::SCOPE_PROJECT_WRITE], ), "Start one idempotent delegated ACP Run on an exact Project and logical Runner provider; configured model API adapters can perform text reasoning/review. Optional context_session_id quotes an independently authorized bounded handoff and explicit Goal context; no source resume, recorder inference, or authority transfer. Current files/Git are not fetched. The context snapshot is part of the intent: a changed snapshot under the same key conflicts. Autonomous execution may outlive this request; after any uncertain start observe the same Run rather than dispatching a replacement. Choose providers only from the exact Runner inventory; no automatic quota/model switching.", @@ -53,100 +53,100 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ PERMISSION_RISK_JOB, ), model_spec( - def( - "observe_coding_agent", - super::ToolAuditPolicy::typed_semantic( + ToolDeclaration { + name: "observe_coding_agent", + audit: super::ToolAuditPolicy::typed_semantic( super::ToolAuditSemanticResultPolicy::CodingAgentObservation, ), - ModelVisible, - TOOL_CATEGORY_CODING_AGENT, - None, - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_CODING_AGENT, + runner_capability: None, + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(CODING_AGENT_RUN), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(CODING_AGENT_RUN), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Observe bounded normalized events and lifecycle for one existing CodingAgentRun. Return the opaque token for only-new follow-ups; history loss/reset is explicit. Observation never starts, retries, or resumes ACP work.", ), permission_risk( model_spec( - def( - "cancel_coding_agent", - super::ToolAuditPolicy::typed_fields(&[ - super::ToolAuditResultField::value("run_id"), - super::ToolAuditResultField::value("project"), - super::ToolAuditResultField::value("provider_id"), - super::ToolAuditResultField::value("state"), - super::ToolAuditResultField::value("execution_state"), - super::ToolAuditResultField::value("cancel_requested"), - super::ToolAuditResultField::pointer("terminal_stop_reason", "/terminal/stop_reason"), - super::ToolAuditResultField::pointer("terminal_error_code", "/terminal/error_code"), - super::ToolAuditResultField::pointer("terminal_completed_at", "/terminal/completed_at"), - super::ToolAuditResultField::value("error_kind"), - super::ToolAuditResultField::value("recovery_kind"), - ]), - ModelVisible, - TOOL_CATEGORY_CODING_AGENT, - None, - TOOL_PROVIDER_RUNNER, - // Cancel is Run lifecycle control but deliberately not a second - // WebCodex PermissionEvaluator decision after start admission. - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: super::ToolRisk::RunControl, - approval: super::ToolApprovalPolicy::InheritFromStart, - idempotency: super::ToolIdempotency::DesiredState, - }, - Some(CODING_AGENT_RUN), - false, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + ToolDeclaration { + name: "cancel_coding_agent", + audit: super::ToolAuditPolicy::typed_fields(&[ + super::ToolAuditResultField::value("run_id"), + super::ToolAuditResultField::value("project"), + super::ToolAuditResultField::value("provider_id"), + super::ToolAuditResultField::value("state"), + super::ToolAuditResultField::value("execution_state"), + super::ToolAuditResultField::value("cancel_requested"), + super::ToolAuditResultField::pointer("terminal_stop_reason", "/terminal/stop_reason"), + super::ToolAuditResultField::pointer("terminal_error_code", "/terminal/error_code"), + super::ToolAuditResultField::pointer("terminal_completed_at", "/terminal/completed_at"), + super::ToolAuditResultField::value("error_kind"), + super::ToolAuditResultField::value("recovery_kind"), + ]), + visibility: ModelVisible, + category: TOOL_CATEGORY_CODING_AGENT, + runner_capability: None, + provider_id: TOOL_PROVIDER_RUNNER, + // Cancel is Run lifecycle control but deliberately not a second + // WebCodex PermissionEvaluator decision after start admission. + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: super::ToolRisk::RunControl, + approval: super::ToolApprovalPolicy::InheritFromStart, + idempotency: super::ToolIdempotency::DesiredState, + }, + required_scope: Some(CODING_AGENT_RUN), + requires_project: false, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Request cancellation of one existing CodingAgentRun. This does not grant permission, retry a prompt, or create a replacement Run; observe the same run_id for authoritative terminal state.", ), PERMISSION_RISK_WRITE, ), model_spec( - def( - "diagnose_coding_agent", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "diagnose_coding_agent", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("provider_id"), super::ToolAuditResultField::value("env_presence"), super::ToolAuditResultField::value("live_acp"), ]), - ModelVisible, - TOOL_CATEGORY_CODING_AGENT, - Some(CodingAgentRuns), - TOOL_PROVIDER_RUNNER, + visibility: ModelVisible, + category: TOOL_CATEGORY_CODING_AGENT, + runner_capability: Some(CodingAgentRuns), + provider_id: TOOL_PROVIDER_RUNNER, // Presence-only is pure observation. The optional live ACP probe // launches one ephemeral provider process, so the runtime gates // include_live_acp=true on the same explicit authority as // start_coding_agent; this static contract never grants it. - super::ToolSemanticContract { + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(CODING_AGENT_RUN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(CODING_AGENT_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Diagnose one explicitly selected ACP provider for one exact authorized Project through its configured Runner. Default (include_live_acp=false): report only the provider's configured env_from_env destination keys as present/absent booleans for their mapped source variables in the running Runner process - not the source names or values, unrelated names, paths, or process launches. include_live_acp=true additionally requires the same scope authority as start_coding_agent (including OAuth coding_agent:run and project:write) and a fresh per-Project permission decision, then launches one bounded ephemeral provider process exactly like a Run start, performing only initialize then session/new to project the live config-option identities (bounded ids, labels, kinds, and allowlisted choices); it never sends session/prompt, runs provider tools, or writes configuration, and the whole process tree is terminated on every exit path. One probe runs at a time per Runner within a fixed budget; a concurrent probe returns a bounded busy outcome. Results come from the live Runner, never from static configuration; stale Runner, provider-generation, or Project identities fail closed with bounded reason codes. No automatic retry or fallback: re-observe the Runner inventory and fix inputs instead.", ), ]; diff --git a/crates/webcodex-tool-contracts/src/tool_definition/computer.rs b/crates/webcodex-tool-contracts/src/tool_definition/computer.rs index d8efad828..77148910e 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/computer.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/computer.rs @@ -1,11 +1,11 @@ use super::RunnerCapabilityRequirement::FileWrite; use super::ToolVisibility::ModelVisible; use super::{ - def, model_spec, permission_risk, require_all_scopes, require_any_scopes, ToolDefinition, - PERMISSION_RISK_WRITE, TOOL_CATEGORY_COMPUTER, + control_def, model_spec, permission_risk, require_all_scopes, require_any_scopes, + ToolDeclaration, ToolDefinition, PERMISSION_RISK_WRITE, TOOL_CATEGORY_COMPUTER, }; use crate::metadata::{ - ToolPathHint::{Artifact, None as NoPath}, + ToolPathHint::Artifact, ToolRisk::{ComputerControl as ComputerControlRisk, ProjectWrite, Read}, COMPUTER_CONTROL, COMPUTER_LAUNCH, COMPUTER_READ, PROJECT_WRITE, TOOL_PROVIDER_CONTROL, }; @@ -14,28 +14,32 @@ const COMPUTER_CONTROL_GATEWAY_SCOPES: &[&str] = &[COMPUTER_CONTROL, COMPUTER_LA pub(super) const DEFINITIONS: &[ToolDefinition] = &[ model_spec( - def( + control_def( "observe_computer", super::ToolAuditPolicy::typed_semantic( super::ToolAuditSemanticResultPolicy::ComputerObservation, ), - ModelVisible, TOOL_CATEGORY_COMPUTER, None, TOOL_PROVIDER_CONTROL, + ModelVisible, + TOOL_CATEGORY_COMPUTER, super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead }, - Some(COMPUTER_READ), false, NoPath, false, false, super::ToolSessionEvidencePolicy::NONE, + Some(COMPUTER_READ), + false, ), "Read-only Computer gateway: targets/windows/displays/applications, accessibility_status/accessibility_tree/find_elements/element_state, snapshot_window/snapshot_display, read_clipboard. Use exact client_id/surface_id/element_id with element_state: can_press/can_focus/can_input_text/can_select guide control_computer actions, never authority. Role/value alone is insufficient; control revalidates permissions/capabilities. Recheck only when changed state affects the next decision. State preserves observation_generation; stale IDs need fresh tree/search. Tree/search replaces all surface IDs including root_element_id. For truncated search, choose a fresh tree branch and its latest root_element_id. Scoped reads require updated Runner/verified scope receipt. Bounds: depth 8, nodes 256, absolute lineage 32; native deadlines. Truncated zero results are inconclusive; capped siblings are not paginated. Windows element_state scroll fields horizontal_percent/vertical_percent require computer_scroll: null axis=non-scrollable, null object=unsupported/protected, missing=unknown. Positions grant no authority. ComboBox value grants no focus/input. Exact scopes, Runner capabilities, protected-content, image/clipboard bounds and snapshot-generation guards apply. No activation/launch/control input, pointer/clipboard writes, project artifact save, shell fallback or uncertain-effect retry.", ).with_discovery_keywords(&["desktop", "gui", "mac", "macos", "windows", "screen", "screenshot", "observe", "inspect", "view", "桌面", "屏幕"]), require_any_scopes( permission_risk( model_spec( - def( + control_def( "control_computer", super::ToolAuditPolicy::typed_semantic( super::ToolAuditSemanticResultPolicy::ComputerControl, ), - ModelVisible, TOOL_CATEGORY_COMPUTER, None, TOOL_PROVIDER_CONTROL, + ModelVisible, + TOOL_CATEGORY_COMPUTER, super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: ComputerControlRisk, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent }, - None, false, NoPath, true, false, super::ToolSessionEvidencePolicy::NONE, + None, + true, ), "Effectful Computer gateway; choose exact schema action/target. element_state can_press/can_focus/can_input_text/can_select guide actions, never authority; avoid unchanged false retries. Windows press needs InvokePattern; focus/input need foreground AXTextField; input needs exact focus, writable empty value. select replaces selection (computer_selection+computer_control); missing selected is unknown. scroll targets one exact Windows ScrollPattern container (computer_scroll+computer_control): one small/large increment; omitted/null=small. Distances vary. Re-observe; position_before/position_after do not prove UI stability/completion. key uses schema names: Windows computer_key_input, extra keys/chords computer_key_chords, legacy computer_letter_shortcuts only Ctrl+J/Ctrl+P; macOS navigation only. Keys are not text/app commands. Shift+Tab: key=tab,modifiers=[shift]. Windows option=Alt; command, option+Tab/option+Escape/control+Escape/control+option+Delete denied. Keys need foreground/focused surface, unprotected focus; release held keys and re-observe. Only press/input_text/scroll accept observe_after={action:accessibility_tree,max_depth?,max_nodes?}; computer:read and preflight pin Runner/window. post_observation read failure preserves action success; uncertain action skips read. Tree replaces IDs; read is not UI-settled. Scopes/permissions/capabilities/identity/snapshot guards apply. Local pause: resume locally; observe allowed. No implicit focus/activation, shell bypass, scripts or old/uncertain-effect replay.", ).with_discovery_keywords(&["desktop", "gui", "mac", "macos", "windows", "operate", "control", "click", "mouse", "keyboard", "桌面", "鼠标", "键盘"]), @@ -45,9 +49,9 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ), require_all_scopes( model_spec( - def( - "save_computer_snapshot", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "save_computer_snapshot", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("path"), super::ToolAuditResultField::value("client_id"), @@ -61,23 +65,23 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("file_bytes"), super::ToolAuditResultField::value("saved"), ]), - ModelVisible, - TOOL_CATEGORY_COMPUTER, - Some(FileWrite), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_COMPUTER, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - Artifact, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Save one exact window snapshot as a create-only project artifact without returning image bytes. Reuses observe_computer(action=snapshot_window) region/downscale semantics and requires computer:read plus project:write. No overwrite or encoding control. Unknown writes require artifact-metadata reconciliation before retry.", ), &[PROJECT_WRITE, COMPUTER_READ], diff --git a/crates/webcodex-tool-contracts/src/tool_definition/diagnostics.rs b/crates/webcodex-tool-contracts/src/tool_definition/diagnostics.rs index aa4998e4c..a66a16ff1 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/diagnostics.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/diagnostics.rs @@ -1,11 +1,11 @@ use super::ToolVisibility::ModelHidden; -use super::{def, ToolDefinition, ToolOperatorExtensionFamily, TOOL_CATEGORY_RUNTIME}; -use crate::metadata::{ToolPathHint::None as NoPath, ToolRisk::Read, ADMIN, TOOL_PROVIDER_CONTROL}; +use super::{control_def, ToolDefinition, ToolOperatorExtensionFamily, TOOL_CATEGORY_RUNTIME}; +use crate::metadata::{ToolRisk::Read, ADMIN}; /// Operator-only forensic diagnostics. The tool is kernel-known so the shared /// typed dispatcher can enforce its contract, but it is projected only by a /// capable Stateless MCP 2026 operator surface. -pub(super) const DEFINITIONS: &[ToolDefinition] = &[def( +pub(super) const DEFINITIONS: &[ToolDefinition] = &[control_def( "read_tool_trace", super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("trace_ref"), @@ -24,8 +24,6 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[def( ]), ModelHidden, TOOL_CATEGORY_RUNTIME, - None, - TOOL_PROVIDER_CONTROL, super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, @@ -34,10 +32,6 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[def( }, Some(ADMIN), false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, ) .with_activity( super::ToolActivityPresentation::Support, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/documents.rs b/crates/webcodex-tool-contracts/src/tool_definition/documents.rs index 42f0a01b8..a7deded44 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/documents.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/documents.rs @@ -20,26 +20,27 @@ const fn document_read_definition( visibility: ToolVisibility, idempotency: ToolIdempotency, ) -> ToolDefinition { - def( + ToolDeclaration { name, - ToolAuditPolicy::typed_fields(DOCUMENT_AUDIT_FIELDS), + audit: ToolAuditPolicy::typed_fields(DOCUMENT_AUDIT_FIELDS), visibility, - TOOL_CATEGORY_ARTIFACT, - Some(RunnerCapabilityRequirement::FileRead), - TOOL_PROVIDER_CONTROL, - ToolSemanticContract { + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(RunnerCapabilityRequirement::FileRead), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: ToolSemanticContract { effect: ToolEffect::Observe, risk: Read, approval: ToolApprovalPolicy::None, idempotency, }, - Some(PROJECT_READ), - true, - Artifact, - false, - false, - ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: ToolSessionEvidencePolicy::NONE, + } + .define() .with_activity( ToolActivityPresentation::Transport, ToolActivityInteraction::NonMeaningful, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/docx.rs b/crates/webcodex-tool-contracts/src/tool_definition/docx.rs index daa2ef51a..536703436 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/docx.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/docx.rs @@ -19,26 +19,27 @@ const fn docx_read_definition( visibility: ToolVisibility, idempotency: ToolIdempotency, ) -> ToolDefinition { - def( + ToolDeclaration { name, - ToolAuditPolicy::typed_fields(DOCX_AUDIT_FIELDS), + audit: ToolAuditPolicy::typed_fields(DOCX_AUDIT_FIELDS), visibility, - TOOL_CATEGORY_ARTIFACT, - Some(RunnerCapabilityRequirement::FileRead), - TOOL_PROVIDER_CONTROL, - ToolSemanticContract { + category: TOOL_CATEGORY_ARTIFACT, + runner_capability: Some(RunnerCapabilityRequirement::FileRead), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: ToolSemanticContract { effect: ToolEffect::Observe, risk: Read, approval: ToolApprovalPolicy::None, idempotency, }, - Some(PROJECT_READ), - true, - Artifact, - false, - false, - ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: Artifact, + destructive: false, + shell_like: false, + session_evidence: ToolSessionEvidencePolicy::NONE, + } + .define() .with_activity( ToolActivityPresentation::Transport, ToolActivityInteraction::NonMeaningful, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/edits.rs b/crates/webcodex-tool-contracts/src/tool_definition/edits.rs index fc0a8e558..b1abe02d7 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/edits.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/edits.rs @@ -1,7 +1,7 @@ use super::RunnerCapabilityRequirement::FileWrite; use super::ToolVisibility::{ModelHidden, ModelVisible}; use super::{ - adaptive_runtime_direct, def, model_spec, permission_risk, ToolDefinition, + adaptive_runtime_direct, model_spec, permission_risk, ToolDeclaration, ToolDefinition, PERMISSION_RISK_WRITE, TOOL_CATEGORY_EDIT, }; use crate::metadata::{ @@ -13,26 +13,26 @@ use crate::metadata::{ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ permission_risk( model_spec( - def( - "write_project_file", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelHidden, - TOOL_CATEGORY_EDIT, - Some(FileWrite), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: ProjectWrite, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - SinglePath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + ToolDeclaration { + name: "write_project_file", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelHidden, + category: TOOL_CATEGORY_EDIT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: ProjectWrite, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: SinglePath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Create a new file or perform an intentional whole-file replacement. Existing-file replacement requires expected_read_revision from read_files; ToolRuntime resolves that model-facing snapshot handle to the Runner guard, so the model does not copy a digest. Failures expose minimal error facts and at most one parser-ready read_files recovery call when a fresh read is required. Choose this path when whole-file replacement is genuinely the clearest reliable mutation, then inspect the resulting diff and validate the final source.", ).with_discovery_keywords(&["create", "write", "overwrite", "file", "contents", "创建", "写入", "覆盖", "文件", "内容"]), PERMISSION_RISK_WRITE, @@ -40,26 +40,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ adaptive_runtime_direct( permission_risk( model_spec( - def( - "edit_project_files", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_EDIT, - Some(FileWrite), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: ProjectWrite, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - PathList, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + ToolDeclaration { + name: "edit_project_files", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_EDIT, + runner_capability: Some(FileWrite), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: ProjectWrite, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: PathList, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_composition_policy(super::ToolCompositionPolicy::Sequential) .with_host_orchestration_hint( super::ToolHostOrchestrationHint::sequential() diff --git a/crates/webcodex-tool-contracts/src/tool_definition/git.rs b/crates/webcodex-tool-contracts/src/tool_definition/git.rs index 6b4763d19..3ffba611a 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/git.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/git.rs @@ -1,8 +1,8 @@ use super::RunnerCapabilityRequirement::GitOrShell; use super::ToolVisibility::ModelVisible; use super::{ - adaptive_runtime_direct, change_summary_like, def, git_like, model_spec, require_all_scopes, - runner_project_read_def, ToolDefinition, TOOL_CATEGORY_GIT, + adaptive_runtime_direct, change_summary_like, git_like, model_spec, require_all_scopes, + runner_project_read_def, ToolDeclaration, ToolDefinition, TOOL_CATEGORY_GIT, }; use crate::metadata::{ ToolPathHint::{None as NoPath, PathList}, @@ -96,26 +96,26 @@ pub(super) const SUMMARY_DEFINITIONS: &[ToolDefinition] = &[ ]; pub(super) const DETAIL_DEFINITIONS: &[ToolDefinition] = &[ require_all_scopes(git_like(model_spec( - def( - "commit_git_paths", - super::ToolAuditPolicy::TYPED_CANONICAL.drop_null_request_values(), - ModelVisible, - TOOL_CATEGORY_GIT, - Some(GitOrShell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "commit_git_paths", + audit: super::ToolAuditPolicy::TYPED_CANONICAL.drop_null_request_values(), + visibility: ModelVisible, + category: TOOL_CATEGORY_GIT, + runner_capability: Some(GitOrShell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - PathList, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: PathList, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Commit exactly requested changed file paths with an atomic expected_head fence and isolated temporary index; normal Git clean filters may run under job:run authority, ordinary commit hooks are bypassed so they cannot add unrelated paths, and the tool never pushes.", )), &[PROJECT_WRITE, JOB_RUN]), git_like(model_spec( diff --git a/crates/webcodex-tool-contracts/src/tool_definition/hygiene.rs b/crates/webcodex-tool-contracts/src/tool_definition/hygiene.rs index 64d8dfe34..42da6b535 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/hygiene.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/hygiene.rs @@ -1,6 +1,6 @@ use super::RunnerCapabilityRequirement::{GitOrShell, Shell, StructuredProcess}; use super::ToolVisibility::ModelVisible; -use super::{def, git_like, model_spec, ToolDefinition, TOOL_CATEGORY_CLEANUP}; +use super::{git_like, model_spec, ToolDeclaration, ToolDefinition, TOOL_CATEGORY_CLEANUP}; use crate::metadata::{ ToolPathHint::{None as NoPath, PathList}, ToolRisk::{ProjectWrite, Read}, @@ -8,97 +8,100 @@ use crate::metadata::{ }; pub(super) const DEFINITIONS: &[ToolDefinition] = &[model_spec( - def( - "check_workspace_hygiene", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CLEANUP, - Some(GitOrShell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "check_workspace_hygiene", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CLEANUP, + runner_capability: Some(GitOrShell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE.review(super::ToolReviewEvidence::HygieneReview), - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.review(super::ToolReviewEvidence::HygieneReview), + }.define(), "Default pre-final workspace hygiene review; read-only. Detects dirty worktree, untracked temp/smoke files, cache dirs, secret-like names, and large untracked files before validation or handoff. Never reads file contents.", )]; pub(super) const CLEANUP_DEFINITIONS: &[ToolDefinition] = &[ model_spec( - def( - "delete_project_files", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CLEANUP, - Some(Shell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "delete_project_files", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CLEANUP, + runner_capability: Some(Shell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - PathList, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: PathList, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + } + .define(), "Delete selected project-relative files only; safer than arbitrary rm for cleanup.", ), git_like(model_spec( - def( - "restore_git_paths", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CLEANUP, - Some(StructuredProcess), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "restore_git_paths", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CLEANUP, + runner_capability: Some(StructuredProcess), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - PathList, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: PathList, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + } + .define(), "Restore selected tracked paths with git restore; does not remove untracked files.", )), git_like(model_spec( - def( - "discard_untracked", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_CLEANUP, - Some(StructuredProcess), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "discard_untracked", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_CLEANUP, + runner_capability: Some(StructuredProcess), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - PathList, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: PathList, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + } + .define(), "Discard selected untracked files with git clean -f -- .", )), ]; diff --git a/crates/webcodex-tool-contracts/src/tool_definition/jobs.rs b/crates/webcodex-tool-contracts/src/tool_definition/jobs.rs index 465b70f98..5e7785caf 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/jobs.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/jobs.rs @@ -4,8 +4,8 @@ use super::RunnerCapabilityRequirement::{ }; use super::ToolVisibility::{ModelHidden, ModelVisible}; use super::{ - adaptive_runtime_direct, def, model_spec, permission_risk, require_all_scopes, - requires_explicit_business_session, ToolDefinition, PERMISSION_RISK_JOB, + adaptive_runtime_direct, model_spec, permission_risk, require_all_scopes, + requires_explicit_business_session, ToolDeclaration, ToolDefinition, PERMISSION_RISK_JOB, TOOL_CATEGORY_EXECUTION, TOOL_CATEGORY_JOB, }; use crate::metadata::{ @@ -20,15 +20,24 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ permission_risk( adaptive_runtime_direct( model_spec( - def("project_format", super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, TOOL_CATEGORY_EXECUTION, Some(super::RunnerCapabilityRequirement::ProjectFormat), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { effect: super::ToolEffect::Mutate, + ToolDeclaration { + name: "project_format", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(super::RunnerCapabilityRequirement::ProjectFormat), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: crate::metadata::ToolRisk::ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent }, - Some(crate::metadata::PROJECT_WRITE), true, NoPath, true, true, - super::ToolSessionEvidencePolicy::NONE), + required_scope: Some(crate::metadata::PROJECT_WRITE), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Intentionally format 1..8 ordered Project-relative source files under one Runner-resolved recipe root. Requires project:write, job:run and project_format_v1. Rust uses bounded Cargo edition and installed rustfmt stdin/stdout with skip_children; custom configuration and unsupported topology fail closed. Python uses bounded pyproject target-version and isolated Ruff stdin/stdout; extend configuration and unsupported topology fail closed. Go requires project_format_go_v1 and a local go.mod recipe marker; installed gofmt reads stdin with no flags, module loading or dependency validation. gofmt accepts full programs or native stdin fragments; formatting is not compilation. No workspace traversal, installs, package scripts, arbitrary flags or implied network permission. Candidate bytes stay private; Runner compares source and file identity before each guarded write. A multi-file write is not atomic: failure, cancellation, timeout, conflicting edits or a lost receipt can leave mutation unknown. Known state_changed comes only from the verified executor report, never exit status. One admitted Job owns every phase; retain and observe its exact continuation when pending or uncertain. Never automatically retry or redispatch. Review source before any new mutation. Formatting does not supply the edit_project_files receipt required by Code Mode dependent validation.", ).with_discovery_keywords(&["format", "source", "code", "formatting", "格式化", "源码", "代码"]).with_execution(super::ToolExecutionContract::new(super::ToolExecutionForm::ProjectFormat, super::ToolExecutionLifetime::Runner, super::ToolExecutionStart::SyncFirst, @@ -38,11 +47,22 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ &[crate::metadata::PROJECT_WRITE, JOB_RUN]), adaptive_runtime_direct( model_spec( - def("write_job_input", super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, TOOL_CATEGORY_JOB, Some(StructuredProcess), TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { effect:super::ToolEffect::Execute, risk:JobRun, + ToolDeclaration { + name: "write_job_input", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_JOB, + runner_capability: Some(StructuredProcess), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect:super::ToolEffect::Execute, risk:JobRun, approval:super::ToolApprovalPolicy::Standard, idempotency:super::ToolIdempotency::Keyed }, - Some(JOB_RUN), true, NoPath, false, true, super::ToolSessionEvidencePolicy::NONE), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Write bounded UTF-8 data or EOF to an existing run_process(interactive=true) Job. No new process, PTY, signal, stdin polling or automatic restart. Use one stable input_id per payload; exact same-id/same-data/close replay reconciles the retained receipt without resending, changed payload conflicts. Pending or uncertain delivery requires same-input reconciliation or observation, never a new id to retry. Written means OS pipe acceptance, not program consumption. At most 128 data writes per Job plus one empty EOF. New input is rejected while a write is pending or after closure; receipts are process-local and do not survive Runner replacement. Rechecks Job visibility, Project, Runner incarnation and capability; observe_jobs reads output and stop_job controls the process.", ), 71, @@ -50,27 +70,27 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( model_spec( - def( - "project_build", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "project_build", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .execution(super::ToolAuditExecutionPolicy::DIRECT_ARGV_TEXT), - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(ProjectBuild), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(ProjectBuild), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Runner-resolved Rust/Go/Python/Node build; fixed argv. Rust profile, targets.library/targets.binaries (<=8), artifact_policy=observe require project_build_rust_options_v1; others reject. Omission keeps defaults. Observe verifies selected Project-local Cargo outputs (path/bytes/SHA/MIME); cached files prove neither creation, correctness nor freshness. Missing/truncated/unsafe evidence fails observation while preserving native exit. No output-path/target-triple selection or toolchain installation. Rust/Go scope: <=8 packages or all_packages; Rust workspace root must equal Project root. Rust exclude_packages needs all_packages=true, 1–8 exact members and project_workspace_exclusions_v1; unknown/all-member exclusions fail before execution. Go fixes GO111MODULE=on/GOWORK=off. dependency_policy.mode=locked prevents resolution updates. Rust dependency_resolution_policy=offline adds Cargo --offline independently; others reject, missing cache never retries online. Neither policy is an OS sandbox; project code may write/network. Python: pyproject.toml, configured Python 3, installed build deps; --no-isolation builds sdist then wheel, rejects scope, requires project_build_python_v1. Node runs declared npm/pnpm/yarn/bun build with no installs, rejects scope/policies, requires project_build_node_v1. project_build_v1 gates planning/typed admission; manifests, scope and invocation are rechecked before execution. Pending: observe_jobs for the same durable Job, never redispatch. Unsupported builds: native execution.", ).with_discovery_keywords(&["compile", "build", "project", "编译", "构建", "项目"]) .with_execution(super::ToolExecutionContract::new( @@ -84,9 +104,9 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( model_spec( - def( - "run_process", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "run_process", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .session_input(super::ToolAuditSessionInputPolicy::OmitTopLevel(&[ "executable", "args", @@ -94,23 +114,23 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ "process_summary", ])) .execution(super::ToolAuditExecutionPolicy::DIRECT_ARGV_TEST_COUNTS), - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(StructuredProcess), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(StructuredProcess), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Run native executable literal argv under Runner ownership; run_shell takes shell grammar, run_script program text. interactive=true: stdin pipe via keyed write_job_input, no PTY/SSH/initial stdin/structured validation evidence. Pending: retain returned Job/continuation; observe_jobs for Job recovery. Never redispatch pending or outcome_unknown; inspect existing execution. Runner replacement cannot inherit it.", ).with_discovery_keywords(&["start", "run", "execute", "executable", "literal", "arguments", "argv", "program", "command", "启动", "运行", "执行", "程序", "字面量", "参数", "命令"]) .with_execution(super::ToolExecutionContract::new( super::ToolExecutionForm::NativeArgv, @@ -123,9 +143,9 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ ), require_all_scopes( model_spec( - def( - "run_detached_process", - super::ToolAuditPolicy::TYPED_CANONICAL.session_input( + ToolDeclaration { + name: "run_detached_process", + audit: super::ToolAuditPolicy::TYPED_CANONICAL.session_input( super::ToolAuditSessionInputPolicy::OmitTopLevel(&[ "executable", "args", @@ -134,23 +154,23 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ "process_summary", ]), ), - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(DetachedProcess), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(DetachedProcess), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::Keyed, }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Start a supervisor-owned native Job when its child must survive Runner replacement; duration alone is insufficient. Ownership transfers before payload start; recovery requires matching supervisor identity and lifetime fence. Retained replay keys prevent duplicate dispatch; expired keys never authorize retry. Observe/stop the same Job; outcome_unknown never authorizes redispatch. No shell, script or SSH fallback.", ).with_discovery_keywords(&["background", "detached", "daemon", "service", "process", "后台", "脱离", "守护", "服务", "程序"]) .with_execution(super::ToolExecutionContract::new( @@ -163,33 +183,33 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( model_spec( - def( - "run_script", - super::ToolAuditPolicy::TYPED_CANONICAL - .session_input(super::ToolAuditSessionInputPolicy::OmitTopLevel(&[ - "script", - "args", - "stdin", - "script_summary", - ])) - .execution(super::ToolAuditExecutionPolicy::SCRIPT_TEST_COUNTS), - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(StructuredScript), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Execute, - risk: JobRun, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE, - ), + ToolDeclaration { + name: "run_script", + audit: super::ToolAuditPolicy::TYPED_CANONICAL + .session_input(super::ToolAuditSessionInputPolicy::OmitTopLevel(&[ + "script", + "args", + "stdin", + "script_summary", + ])) + .execution(super::ToolAuditExecutionPolicy::SCRIPT_TEST_COUNTS), + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(StructuredScript), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Execute, + risk: JobRun, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Run typed sh/bash/PowerShell/Python/JavaScript/TypeScript script data, never interpolated into shell command text. run_process takes literal argv; run_shell takes shell grammar. Prefer structured source editors. Pending: retain returned Job/continuation; observe_jobs for Job recovery. Never redispatch pending or outcome_unknown; inspect existing execution. Runner-owned; replacement cannot inherit it.", ).with_discovery_keywords(&["run", "execute", "script", "code", "interpreter", "python", "javascript", "运行", "执行", "脚本", "解释器"]) .with_execution(super::ToolExecutionContract::new( @@ -203,31 +223,31 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( model_spec( - def( - "run_shell", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "run_shell", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .session_input(super::ToolAuditSessionInputPolicy::OmitTopLevel(&[ "command", "command_summary", ])) .execution(super::ToolAuditExecutionPolicy::TEST_COUNTS), - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(Shell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(Shell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Run shell grammar or a short related command chain; run_process takes literal argv, run_script program text. Local Jobs are Runner-owned; named Session SSH has no durable Job handoff. Pending: retain returned Job/continuation; observe_jobs for Job recovery. Never redispatch pending or outcome_unknown; inspect existing execution. Prefer structured source editors. Runner replacement cannot inherit local Jobs.", ).with_discovery_keywords(&["run", "execute", "shell", "command", "pipeline", "pipes", "terminal", "bash", "powershell", "运行", "执行", "命令", "管道", "终端"]) .with_execution(super::ToolExecutionContract::new( @@ -240,54 +260,54 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ super::ToolDirectReason::CoreWorkflow, ), requires_explicit_business_session(model_spec( - def( - "open_session_shell", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(PersistentShell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "open_session_shell", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(PersistentShell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Open), - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Open), + }.define(), "Open one bounded long-lived shell for an explicit Workflow Session. Primary use: one shell for repeated commands on the active named SSH resource in execution_context.resource, preserving remote cwd/env/exports/functions/umask. Local sh/bash or Windows PowerShell remains supported only when same local shell-process state is actually required, not merely for several commands. New SSH targets use manage_ssh_resource list/register, Runner restart, list again, then update_session_context; no per-shell host/resource parameter. The SSH target does not need WebCodex Runner.", )), requires_explicit_business_session(model_spec( - def( - "execute_session_shell", - super::ToolAuditPolicy::TYPED_CANONICAL.session_input( + ToolDeclaration { + name: "execute_session_shell", + audit: super::ToolAuditPolicy::TYPED_CANONICAL.session_input( super::ToolAuditSessionInputPolicy::OmitTopLevel(&[ "command", "command_summary", ]), ), - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(PersistentShell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(PersistentShell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Exec), - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Exec), + }.define(), "Execute one framed command in an existing Session persistent shell. Primary route is repeated commands on the same named SSH resource while retaining remote cwd/env/exports/functions/umask. Local persistent execution remains supported only when the same local shell process must retain state; ordinary one-shot work should use run_process, run_shell for shell semantics or short tightly related chains, and run_script for program-like shell content. Several commands alone are not a reason to open persistent shell. Commands are serialized in the same shell process.", ) .with_execution(super::ToolExecutionContract::new( @@ -297,81 +317,81 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ super::ToolExecutionContinuation::SessionShell, ))), requires_explicit_business_session(model_spec( - def( - "get_session_shell_status", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(PersistentShell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "get_session_shell_status", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(PersistentShell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Status), - ), + required_scope: Some(RUNTIME_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Status), + }.define(), "Read Runner-authoritative state for an explicit Session persistent shell. This never sends input to the process.", )), requires_explicit_business_session(permission_risk( model_spec( - def( - "close_session_shell", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(PersistentShell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "close_session_shell", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(PersistentShell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::DesiredState, }, - Some(JOB_RUN), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Close), - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Close), + }.define(), "Idempotently close an explicit Session persistent shell and terminate its complete process group.", ), PERMISSION_RISK_JOB, )), permission_risk( model_spec( - def( - "run_job", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "run_job", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .session_input(super::ToolAuditSessionInputPolicy::OmitTopLevel(&[ "command", "command_summary", ])) .execution(super::ToolAuditExecutionPolicy::TEST_COUNTS), - ModelVisible, - TOOL_CATEGORY_EXECUTION, - Some(AsyncJobs), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_EXECUTION, + runner_capability: Some(AsyncJobs), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Start an asynchronous Runner-owned shell Job with stable job_id, including while queued. Ordinary synchronous tools can hand off the same execution when pending. Server reconnects reconcile this Job; Runner replacement does not inherit it. Pending or outcome_unknown: observe_jobs, never redispatch. Use run_detached_process from the start when a native child must outlive the Runner.", ) .with_execution(super::ToolExecutionContract::new( @@ -384,53 +404,53 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ ), permission_risk( model_spec( - def( - "stop_job", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + ToolDeclaration { + name: "stop_job", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::DesiredState, }, - Some(JOB_RUN), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Stop one existing WebCodex Job by job_id. Requires confirm=true and preserves project/session ownership; log bodies are not returned.", ).with_discovery_keywords(&["cancel", "stop", "terminate", "job", "process", "取消", "停止", "终止", "作业", "进程"]), PERMISSION_RISK_JOB, ), adaptive_runtime_direct( model_spec( - def( - "observe_jobs", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "observe_jobs", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .session_input(super::ToolAuditSessionInputPolicy::ObserveJobs), - ModelVisible, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::Meaningful, @@ -445,26 +465,26 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( model_spec( - def( - "wait_for_job_readiness", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + ToolDeclaration { + name: "wait_for_job_readiness", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::Meaningful, @@ -477,26 +497,26 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ super::ToolDirectReason::CoreWorkflow, ), model_spec( - def( - "wait_for_job_terminal", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + ToolDeclaration { + name: "wait_for_job_terminal", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::Keyed, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::Meaningful, @@ -508,28 +528,28 @@ pub(super) const EXECUTION_DEFINITIONS: &[ToolDefinition] = &[ pub(super) const LISTING_DEFINITIONS: &[ToolDefinition] = &[ adaptive_runtime_direct(model_spec( - def( - "list_jobs", - super::ToolAuditPolicy::TYPED_CANONICAL.session_input( + ToolDeclaration { + name: "list_jobs", + audit: super::ToolAuditPolicy::TYPED_CANONICAL.session_input( super::ToolAuditSessionInputPolicy::OmitTopLevel(&["project", "session_id"]), ), - ModelVisible, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Support, super::ToolActivityInteraction::Meaningful, @@ -537,9 +557,9 @@ pub(super) const LISTING_DEFINITIONS: &[ToolDefinition] = &[ "Recovery and inventory primitive for caller-visible Jobs, not the normal continuation step. Exact project/session_id/status filters combine with AND before limit; no stdout/stderr bodies. Known Job: retain its identity/continuation and use observe_jobs for logs/details/recovery; do not list first or poll inventory.", ).with_discovery_keywords(&["list", "background", "jobs", "tasks", "running", "列出", "后台", "作业", "任务", "运行"]), 79, super::ToolDirectReason::CoreWorkflow), adaptive_runtime_direct(model_spec( - def( - "present_job_terminal_continuation", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "present_job_terminal_continuation", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer("wait_id", "/job_terminal_continuation/wait_id"), super::ToolAuditResultField::pointer("job_id", "/job_terminal_continuation/job_id"), super::ToolAuditResultField::pointer("state", "/job_terminal_continuation/state"), @@ -547,89 +567,89 @@ pub(super) const LISTING_DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::pointer("automatic_resume_available", "/job_terminal_continuation/automatic_resume_available"), super::ToolAuditResultField::value("error_kind"), ]), - ModelVisible, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Present one exact caller-owned still-waiting Job terminal wait as a bounded MCP App continuation card through this dedicated callable, not call_runtime_tool. Use this only when no independent work remains and progress is blocked on that terminal transition. After successful presentation, produce a normal final assistant response and let the current turn finalize promptly; the card/tool result alone does not end the assistant turn. Do not keep polling or wait for the later follow-up in that presenting turn. An already-triggered wait should be handled in the current turn instead. Requires explicit wait_id, independently re-authorizes the wait and underlying Job visibility, never infers identity from Project, Session, ClientWindow, peer identity, credential, or recent activity, and never changes Job execution or terminal truth. Automatic readiness requires a separately verified unattended Host and an exact live authorized binding; dispatch acceptance is not a fresh turn or completed consume. Uncertain delivery is not permission to resend or retry execution.", ), 78, super::ToolDirectReason::Continuation), - def( - "bind_job_terminal_continuation", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "bind_job_terminal_continuation", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer("wait_id", "/job_terminal_continuation/wait_id"), super::ToolAuditResultField::pointer("job_id", "/job_terminal_continuation/job_id"), super::ToolAuditResultField::pointer("delivery_state", "/job_terminal_continuation/delivery_state"), super::ToolAuditResultField::value("state_changed"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::DesiredState, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "get_job_terminal_continuation_state", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "get_job_terminal_continuation_state", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer("wait_id", "/job_terminal_continuation/wait_id"), super::ToolAuditResultField::pointer("job_id", "/job_terminal_continuation/job_id"), super::ToolAuditResultField::pointer("state", "/job_terminal_continuation/state"), super::ToolAuditResultField::pointer("delivery_state", "/job_terminal_continuation/delivery_state"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "prepare_job_terminal_continuation", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "prepare_job_terminal_continuation", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("wait_id"), super::ToolAuditResultField::value("job_id"), super::ToolAuditResultField::value("attempt_id"), @@ -638,30 +658,30 @@ pub(super) const LISTING_DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("state_changed"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::FencedReplay, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "finish_job_terminal_continuation", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "finish_job_terminal_continuation", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("wait_id"), super::ToolAuditResultField::value("job_id"), super::ToolAuditResultField::value("attempt_id"), @@ -670,75 +690,75 @@ pub(super) const LISTING_DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("state_changed"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::FencedReplay, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "unbind_job_terminal_continuation", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "unbind_job_terminal_continuation", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer("wait_id", "/job_terminal_continuation/wait_id"), super::ToolAuditResultField::pointer("job_id", "/job_terminal_continuation/job_id"), super::ToolAuditResultField::pointer("delivery_state", "/job_terminal_continuation/delivery_state"), super::ToolAuditResultField::value("state_changed"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::FencedReplay, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "read_job_tail", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelHidden, - TOOL_CATEGORY_JOB, - None, - TOOL_PROVIDER_NATIVE, - super::ToolSemanticContract { + ToolDeclaration { + name: "read_job_tail", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelHidden, + category: TOOL_CATEGORY_JOB, + runner_capability: None, + provider_id: TOOL_PROVIDER_NATIVE, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), ]; diff --git a/crates/webcodex-tool-contracts/src/tool_definition/memory.rs b/crates/webcodex-tool-contracts/src/tool_definition/memory.rs index f74447802..097e7de73 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/memory.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/memory.rs @@ -1,6 +1,7 @@ use super::ToolVisibility::ModelHidden; use super::{ - def, require_all_scopes, ToolDefinition, ToolOperatorExtensionFamily, TOOL_CATEGORY_MEMORY, + control_def, require_all_scopes, ToolDeclaration, ToolDefinition, ToolOperatorExtensionFamily, + TOOL_CATEGORY_MEMORY, }; use crate::metadata::{ ToolPathHint::None as NoPath, @@ -14,9 +15,9 @@ use webcodex_core::authority::{MEMORY_MANAGE_SCOPES, MEMORY_READ_SCOPES}; /// Operator surface; the kernel capability gate is authoritative. pub(super) const DEFINITIONS: &[ToolDefinition] = &[ require_all_scopes( - def( - "search_memory", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "search_memory", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("catalog_revision"), super::ToolAuditResultField::value("total_count"), @@ -35,30 +36,31 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ])), - ModelHidden, - TOOL_CATEGORY_MEMORY, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_MEMORY, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + } + .define(), MEMORY_READ_SCOPES, ) .with_operator_extension_family(ToolOperatorExtensionFamily::MemoryRuntime), require_all_scopes( - def( - "read_memory", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "read_memory", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("memory_id"), super::ToolAuditResultField::value("memory_key"), @@ -79,30 +81,31 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ])), - ModelHidden, - TOOL_CATEGORY_MEMORY, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_MEMORY, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + } + .define(), MEMORY_READ_SCOPES, ) .with_operator_extension_family(ToolOperatorExtensionFamily::MemoryRuntime), require_all_scopes( - def( - "set_memory", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "set_memory", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("memory_id"), super::ToolAuditResultField::value("memory_key"), @@ -121,30 +124,31 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ])), - ModelHidden, - TOOL_CATEGORY_MEMORY, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_MEMORY, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: MemoryManage, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + } + .define(), MEMORY_MANAGE_SCOPES, ) .with_operator_extension_family(ToolOperatorExtensionFamily::MemoryManagement), require_all_scopes( - def( - "delete_memory", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "delete_memory", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("memory_id"), super::ToolAuditResultField::value("memory_key"), @@ -161,27 +165,28 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ])), - ModelHidden, - TOOL_CATEGORY_MEMORY, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_MEMORY, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: MemoryManage, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + } + .define(), MEMORY_MANAGE_SCOPES, ) .with_operator_extension_family(ToolOperatorExtensionFamily::MemoryManagement), - def( + control_def( "list_memory_scopes", super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("total_count"), @@ -193,8 +198,6 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ .context_from_result(), ModelHidden, TOOL_CATEGORY_MEMORY, - None, - TOOL_PROVIDER_CONTROL, super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, @@ -203,13 +206,9 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ }, Some(ADMIN), false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, ) .with_operator_extension_family(ToolOperatorExtensionFamily::MemoryManagement), - def( + control_def( "purge_memory_scope", super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("memory_scope_id"), @@ -222,8 +221,6 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ .context_from_result(), ModelHidden, TOOL_CATEGORY_MEMORY, - None, - TOOL_PROVIDER_CONTROL, super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: MemoryManage, @@ -231,11 +228,7 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ idempotency: super::ToolIdempotency::NonIdempotent, }, Some(ADMIN), - false, - NoPath, true, - false, - super::ToolSessionEvidencePolicy::NONE, ) .with_operator_extension_family(ToolOperatorExtensionFamily::MemoryManagement), ]; diff --git a/crates/webcodex-tool-contracts/src/tool_definition/patches.rs b/crates/webcodex-tool-contracts/src/tool_definition/patches.rs index f06ff4216..9232e5a4e 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/patches.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/patches.rs @@ -1,7 +1,8 @@ use super::RunnerCapabilityRequirement::{ApplyPatch, Shell}; use super::ToolVisibility::ModelHidden; use super::{ - def, model_spec, permission_risk, ToolDefinition, PERMISSION_RISK_PATCH, TOOL_CATEGORY_PATCH, + model_spec, permission_risk, ToolDeclaration, ToolDefinition, PERMISSION_RISK_PATCH, + TOOL_CATEGORY_PATCH, }; use crate::metadata::{ ToolPathHint::Patch, ToolRisk::ProjectWrite, PROJECT_WRITE, TOOL_PROVIDER_RUNNER, @@ -10,52 +11,52 @@ use crate::metadata::{ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ permission_risk( model_spec( - def( - "apply_patch", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelHidden, - TOOL_CATEGORY_PATCH, - Some(ApplyPatch), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: ProjectWrite, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - Patch, - true, - false, - super::ToolSessionEvidencePolicy::NONE.changed_paths(super::ToolChangedPathEvidence::ResultField("changed_paths")), - ), + ToolDeclaration { + name: "apply_patch", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelHidden, + category: TOOL_CATEGORY_PATCH, + runner_capability: Some(ApplyPatch), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: ProjectWrite, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Patch, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.changed_paths(super::ToolChangedPathEvidence::ResultField("changed_paths")), + }.define(), "Available for naturally patch-shaped contextual edits or externally supplied Codex patch input when patch form is genuinely the clearest reliable representation; it is not the default recovery for other edit failures. Repetitive targets need stable unique context (function/impl/type/test/module), not repeated lines or short fragments. Transactional: source rechecks, rollback, dry_run. matching_mode=unique is the default; matching_mode_rejected never justifies weakening the guard or switching to first_match. Reread/refine current source as needed, preserve unique/exact_unique on retry, and inspect the workspace on outcome_unknown.", ).with_discovery_keywords(&["apply", "patch", "add", "delete", "update", "files", "应用", "补丁", "新增", "删除", "修改", "文件"]), PERMISSION_RISK_PATCH, ), permission_risk( model_spec( - def( - "apply_unified_diff", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelHidden, - TOOL_CATEGORY_PATCH, - Some(Shell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "apply_unified_diff", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelHidden, + category: TOOL_CATEGORY_PATCH, + runner_capability: Some(Shell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: ProjectWrite, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_WRITE), - true, - Patch, - true, - false, - super::ToolSessionEvidencePolicy::NONE.changed_paths(super::ToolChangedPathEvidence::ResultField("affected_files")), - ), + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: Patch, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.changed_paths(super::ToolChangedPathEvidence::ResultField("affected_files")), + }.define(), "External raw unified-diff mutation path. Use only when the input is already a standard unified diff and that representation is the clearest reliable mutation. Performs bounded preflight before applying and never needs a separate validation call. Shell heredocs and Codex *** Begin Patch wrappers are rejected with recovery metadata; choose other mutation paths according to the task rather than converting model-generated work into unified diff by default.", ).with_discovery_keywords(&["apply", "unified", "diff", "patch", "应用", "统一", "补丁", "差异"]), PERMISSION_RISK_PATCH, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/plugins.rs b/crates/webcodex-tool-contracts/src/tool_definition/plugins.rs index ffea58e8b..bb7070ce7 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/plugins.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/plugins.rs @@ -1,25 +1,20 @@ use super::ToolVisibility::ModelVisible; use super::{ - adaptive_runtime_direct, def, model_spec, require_any_scopes, ToolDefinition, + adaptive_runtime_direct, control_def, model_spec, require_any_scopes, ToolDefinition, TOOL_CATEGORY_PLUGIN, }; -use crate::metadata::{ - ToolPathHint::None as NoPath, ToolRisk::RunControl, PLUGIN_INSPECT, PLUGIN_INVOKE, - PLUGIN_MANAGE, TOOL_PROVIDER_CONTROL, -}; +use crate::metadata::{ToolRisk::RunControl, PLUGIN_INSPECT, PLUGIN_INVOKE, PLUGIN_MANAGE}; const PLUGIN_GATEWAY_SCOPES: &[&str] = &[PLUGIN_INSPECT, PLUGIN_INVOKE, PLUGIN_MANAGE]; pub(super) const DEFINITIONS: &[ToolDefinition] = &[adaptive_runtime_direct( require_any_scopes( model_spec( - def( + control_def( "plugin_tool", super::ToolAuditPolicy::TYPED_CANONICAL, ModelVisible, TOOL_CATEGORY_PLUGIN, - None, - TOOL_PROVIDER_CONTROL, super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: RunControl, @@ -28,10 +23,6 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[adaptive_runtime_direct( }, None, false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, ), "Stable gateway for Runner-owned native Tool Plugins. Provider tools are never outer WebCodex MCP tools. Discovery starts at an exact visible Runner. Startup catalog is selection metadata, not an input schema or binding. Describe the selected tool when missing, then reuse both while retained for the exact Runner/provider/tool and bound Project; another call/task/Session alone needs no describe. projectBound requires project on describe and write authority at call. Never reuse a projectBound binding for another Project. For a different intended Project, describe with that project and use the new binding; do not just drop a call-time project. Re-describe after context loss or explicit stale/replaced/schema-change recovery; this is observation, not permission to replay an effect. Unknown outcomes still require reconciliation, never blind retry. Provider call accepts only binding + arguments and never retargets. Each action enforces plugin:inspect, plugin:invoke, or plugin:manage before dispatch.", ), diff --git a/crates/webcodex-tool-contracts/src/tool_definition/resource_references.rs b/crates/webcodex-tool-contracts/src/tool_definition/resource_references.rs index b6763fe43..086d71a5c 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/resource_references.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/resource_references.rs @@ -1,5 +1,5 @@ use super::*; -use crate::metadata::{ToolPathHint::None as NoPath, ToolRisk::Read}; +use crate::metadata::ToolRisk::Read; const fn resource_tool( name: &'static str, @@ -10,15 +10,13 @@ const fn resource_tool( adaptive_runtime_direct( model_spec( require_any_scopes( - def( + control_def( name, ToolAuditPolicy::TYPED_CANONICAL.session_input( ToolAuditSessionInputPolicy::OmitTopLevel(&["uri", "query"]), ), ToolVisibility::ModelVisible, TOOL_CATEGORY_PROJECT, - None, - TOOL_PROVIDER_CONTROL, ToolSemanticContract { effect: ToolEffect::Observe, risk: Read, @@ -27,10 +25,6 @@ const fn resource_tool( }, None, false, - NoPath, - false, - false, - ToolSessionEvidencePolicy::NONE, ), &[ crate::metadata::PROJECT_READ, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/runner_config.rs b/crates/webcodex-tool-contracts/src/tool_definition/runner_config.rs index aab7adbca..682a17e58 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/runner_config.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/runner_config.rs @@ -1,7 +1,8 @@ use super::RunnerCapabilityRequirement::RunnerConfigControl; use super::ToolVisibility::ModelVisible; use super::{ - def, model_spec, permission_risk, ToolDefinition, PERMISSION_RISK_WRITE, TOOL_CATEGORY_RUNTIME, + model_spec, permission_risk, ToolDeclaration, ToolDefinition, PERMISSION_RISK_WRITE, + TOOL_CATEGORY_RUNTIME, }; use crate::metadata::{ ToolPathHint::None as NoPath, @@ -11,50 +12,50 @@ use crate::metadata::{ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ model_spec( - def( - "check_runner_config", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_RUNTIME, - Some(RunnerConfigControl), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "check_runner_config", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_RUNTIME, + runner_capability: Some(RunnerConfigControl), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(RUNTIME_READ), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Read, parse, validate, and classify the candidate at one exact caller-visible Runner's startup-bound runner.toml path. This never mutates active config, accepts no filesystem path, and returns only bounded sanitized validation metadata.", ), permission_risk( model_spec( - def( - "reload_runner_config", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_RUNTIME, - Some(RunnerConfigControl), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + ToolDeclaration { + name: "reload_runner_config", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_RUNTIME, + runner_capability: Some(RunnerConfigControl), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: RunControl, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::FencedReplay, }, - Some(RUNNER_MANAGE), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(RUNNER_MANAGE), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Activate the candidate already present at one exact caller-visible Runner's startup-bound runner.toml path. Requires expected_generation, never writes the file, and reports partial activation/restart-required fields without pretending restart-only fields are live.", ), PERMISSION_RISK_WRITE, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/sessions.rs b/crates/webcodex-tool-contracts/src/tool_definition/sessions.rs index ac8a8ec9e..c58259f19 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/sessions.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/sessions.rs @@ -1,8 +1,8 @@ use super::RunnerCapabilityRequirement::{GitOrShell, InternalPosixScript, OwnerOnly}; use super::ToolVisibility::{ModelHidden, ModelVisible}; use super::{ - adaptive_runtime_direct, control_def, def, model_spec, permission_risk, require_all_scopes, - requires_explicit_business_session, ToolDefinition, PERMISSION_RISK_WRITE, + adaptive_runtime_direct, control_def, model_spec, permission_risk, require_all_scopes, + requires_explicit_business_session, ToolDeclaration, ToolDefinition, PERMISSION_RISK_WRITE, TOOL_CATEGORY_SESSION, TOOL_CATEGORY_VALIDATION, }; use crate::metadata::{ @@ -12,30 +12,30 @@ use crate::metadata::{ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ requires_explicit_business_session( - def( - "record_external_observation", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "record_external_observation", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("session_id"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - TOOL_CATEGORY_SESSION, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: super::ToolRisk::SessionCollaborate, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::FencedReplay, }, - Some(SESSION_COLLABORATE), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE + required_scope: Some(SESSION_COLLABORATE), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE .lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ) + }.define() .with_activity( super::ToolActivityPresentation::Support, super::ToolActivityInteraction::NonMeaningful, @@ -43,44 +43,58 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ), require_all_scopes(model_spec( - def( - "list_sessions", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "list_sessions", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("total"), super::ToolAuditResultField::array_len("returned", "sessions"), super::ToolAuditResultField::value("error_kind"), ]), - ModelVisible, TOOL_CATEGORY_SESSION, None, TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), true, NoPath, false, false, - super::ToolSessionEvidencePolicy::NONE, - ).with_activity(super::ToolActivityPresentation::Support, super::ToolActivityInteraction::NonMeaningful), + required_scope: Some(RUNTIME_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define().with_activity(super::ToolActivityPresentation::Support, super::ToolActivityInteraction::NonMeaningful), "Read-only recovery discovery when the exact Session id is missing, including a new Host window or account using the same WebCodex authority. Requires an authorized exact Project and lists only that caller's creation-time Session authority group, including retained Closed history. Lifecycle filter is optional; offset defaults to 0, limit to 10 and is normalized to 1..20. Titles are redacted/bounded and output is capped at 32 KiB. Counts exclude foreign authority groups. Inventory pagination is not a frozen snapshot. Explicitly choose a returned session_id/session_ref, then read read_session_handoff. Active work needs explicit bootstrap with the selected Session; Closed is historical only. Discovery never infers current/recent work, resumes a Session, grants authority, or restores hidden chat context.", ), &[RUNTIME_READ, PROJECT_READ]), requires_explicit_business_session(model_spec( - def( - "list_external_observations", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "list_external_observations", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("session_id"), super::ToolAuditResultField::value("error_kind"), ]), - ModelVisible, TOOL_CATEGORY_SESSION, None, TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), true, NoPath, false, false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Support, super::ToolActivityInteraction::NonMeaningful, @@ -104,26 +118,26 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ), adaptive_runtime_direct( model_spec( - def( - "work_on_project", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - "workflow", - Some(GitOrShell), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "work_on_project", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: "workflow", + runner_capability: Some(GitOrShell), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: super::ToolRisk::WorkflowManage, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(RUNTIME_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Support, super::ToolActivityInteraction::Meaningful, @@ -134,58 +148,58 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolDirectReason::CoreWorkflow, ), requires_explicit_business_session(model_spec( - def( - "finish_coding_task", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "finish_coding_task", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .context(super::ToolAuditContextPolicy::TaskOutputs), - ModelVisible, - "workflow", - Some(GitOrShell), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelVisible, + category: "workflow", + runner_capability: Some(GitOrShell), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity_kind(super::ToolActivityKind::Review), "Review task closeout evidence for code or ordinary file work. Optional outputs name up to 16 project-relative files; the Runner independently observes size/SHA/MIME, and missing or unavailable files block closeout. Presence does not prove content, counts or format: verify these for the task. Git and tests remain relevant to code work. This advisory snapshot supports model judgment; it does not decide task completion or generate the user-facing final report.", )), adaptive_runtime_direct( model_spec( - def( - "present_work_result", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "present_work_result", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer("project", "/work_result/project"), super::ToolAuditResultField::pointer("session_id", "/work_result/session_id"), super::ToolAuditResultField::pointer("state_version", "/work_result/state_version"), super::ToolAuditResultField::pointer("snapshot_id", "/work_result/final_changes/snapshot_id"), super::ToolAuditResultField::value("error_kind"), ]), - ModelVisible, - "workflow", - Some(InternalPosixScript), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelVisible, + category: "workflow", + runner_capability: Some(InternalPosixScript), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, @@ -195,68 +209,68 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ 155, super::ToolDirectReason::Presentation, ), - def( - "get_work_result_state", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "get_work_result_state", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer("project", "/work_result/project"), super::ToolAuditResultField::pointer("session_id", "/work_result/session_id"), super::ToolAuditResultField::pointer("state_version", "/work_result/state_version"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - "workflow", - Some(GitOrShell), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: "workflow", + runner_capability: Some(GitOrShell), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "read_work_result_activity_detail", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "read_work_result_activity_detail", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer( "server_trace_id", "/activity_detail/server_trace_id", ), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - "workflow", - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: "workflow", + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "send_work_result_message", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "send_work_result_message", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("success"), super::ToolAuditResultField::value("session_id"), super::ToolAuditResultField::value("message_id"), @@ -264,53 +278,53 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("state_changed"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - "workflow", - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: "workflow", + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: super::ToolRisk::SessionCollaborate, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::Keyed, }, - Some(SESSION_COLLABORATE), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(SESSION_COLLABORATE), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, ), - def( - "read_changed_file_diff", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "read_changed_file_diff", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::pointer("project", "/changes_file_diff/project"), super::ToolAuditResultField::pointer("session_id", "/changes_file_diff/session_id"), super::ToolAuditResultField::pointer("snapshot_id", "/changes_file_diff/snapshot_id"), super::ToolAuditResultField::pointer("path", "/changes_file_diff/path"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - "workflow", - Some(InternalPosixScript), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: "workflow", + runner_capability: Some(InternalPosixScript), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Transport, super::ToolActivityInteraction::NonMeaningful, @@ -334,77 +348,77 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ )), requires_explicit_business_session(permission_risk( model_spec( - def( - "update_session_context", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_SESSION, - Some(OwnerOnly), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: super::ToolRisk::SessionCollaborate, - approval: super::ToolApprovalPolicy::None, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(PROJECT_WRITE), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ), + ToolDeclaration { + name: "update_session_context", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_SESSION, + runner_capability: Some(OwnerOnly), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: super::ToolRisk::SessionCollaborate, + approval: super::ToolApprovalPolicy::None, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(PROJECT_WRITE), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), + }.define(), "Update Session defaults. Binding execution_context.resource selects an already active Runner-local named SSH resource; for a new explicit target use manage_ssh_resource list/register and restart the Runner first, then list and bind the active name. open_session_shell uses that Session binding. Requires an authorized project matching the exact Session project; cross-project escape is not supported. Context and event commit under the store lock; the background writer persists, so success does not mean disk flush. Never falls back and never creates unknown Sessions.", ), PERMISSION_RISK_WRITE, )), requires_explicit_business_session(permission_risk( model_spec( - def( - "close_session", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_SESSION, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: super::ToolRisk::SessionCollaborate, - approval: super::ToolApprovalPolicy::None, - idempotency: super::ToolIdempotency::DesiredState, - }, - Some(SESSION_COLLABORATE), - false, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Close).lifecycle(super::ToolSessionLifecycleEffect::IdempotentClose), - ), + ToolDeclaration { + name: "close_session", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: super::ToolRisk::SessionCollaborate, + approval: super::ToolApprovalPolicy::None, + idempotency: super::ToolIdempotency::DesiredState, + }, + required_scope: Some(SESSION_COLLABORATE), + requires_project: false, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.persistent_shell(super::PersistentShellEvidenceAction::Close).lifecycle(super::ToolSessionLifecycleEffect::IdempotentClose), + }.define(), "Explicitly close a workflow session (Active to Closed) for a required session_id. Query remains available; write/shell/mutation tools are denied. Idempotent when already closed; unknown ids fail without create. finish_coding_task does not close.", ), PERMISSION_RISK_WRITE, )), requires_explicit_business_session(model_spec( - def( - "read_validation_summary", - super::ToolAuditPolicy::TYPED_CANONICAL, - ModelVisible, - TOOL_CATEGORY_VALIDATION, - Some(OwnerOnly), - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + ToolDeclaration { + name: "read_validation_summary", + audit: super::ToolAuditPolicy::TYPED_CANONICAL, + visibility: ModelVisible, + category: TOOL_CATEGORY_VALIDATION, + runner_capability: Some(OwnerOnly), + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Support, super::ToolActivityInteraction::Meaningful, @@ -412,9 +426,9 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ "Read bounded structured validation evidence already recorded in an explicit project-scoped session ledger. Does not run Cargo or shell commands, enqueue a Runner request, read project files, mutate the workspace, or replace finish_coding_task.", )), requires_explicit_business_session(model_spec( - def( - "post_session_message", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "post_session_message", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("success"), super::ToolAuditResultField::value("session_id"), super::ToolAuditResultField::value("message_id"), @@ -423,23 +437,23 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::pointer("requires_ack", "/message/requires_ack"), super::ToolAuditResultField::pointer("author_session_id", "/message/author_session_id"), ]), - ModelVisible, - TOOL_CATEGORY_SESSION, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: super::ToolRisk::SessionCollaborate, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(SESSION_COLLABORATE), - false, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ), + required_scope: Some(SESSION_COLLABORATE), + requires_project: false, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), + }.define(), "Post a bounded collaboration message (todo, question, progress, guidance, risk, or decision). Optional delivery_key provides bounded restart-safe sender-scoped replay while the keyed message metadata remains retained; exact retries return the original message and conflicting retained key reuse fails closed. Any message may request request-scoped ACK; ACK proves only current model-context retention and neither resolves nor gates work. Use complete_session_message to atomically answer and resolve a finished todo.", )), @@ -515,66 +529,66 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ )), requires_explicit_business_session(permission_risk( model_spec( - def( - "resolve_session_message", - super::ToolAuditPolicy::typed_fields(&[ - super::ToolAuditResultField::value("success"), - super::ToolAuditResultField::value("session_id"), - super::ToolAuditResultField::value("message_id"), - super::ToolAuditResultField::pointer("status", "/message/status"), - super::ToolAuditResultField::pointer("resolved_by_message_id", "/message/resolved_by_message_id"), - ]), - ModelVisible, - TOOL_CATEGORY_SESSION, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: super::ToolRisk::SessionCollaborate, - approval: super::ToolApprovalPolicy::None, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(SESSION_COLLABORATE), - false, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ), + ToolDeclaration { + name: "resolve_session_message", + audit: super::ToolAuditPolicy::typed_fields(&[ + super::ToolAuditResultField::value("success"), + super::ToolAuditResultField::value("session_id"), + super::ToolAuditResultField::value("message_id"), + super::ToolAuditResultField::pointer("status", "/message/status"), + super::ToolAuditResultField::pointer("resolved_by_message_id", "/message/resolved_by_message_id"), + ]), + visibility: ModelVisible, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: super::ToolRisk::SessionCollaborate, + approval: super::ToolApprovalPolicy::None, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(SESSION_COLLABORATE), + requires_project: false, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), + }.define(), "Mark a session-local ledger message resolved. Idempotent when the message is already resolved; metadata-only and never modifies project files.", ), PERMISSION_RISK_WRITE, )), requires_explicit_business_session(permission_risk( model_spec( - def( - "complete_session_message", - super::ToolAuditPolicy::typed_fields(&[ - super::ToolAuditResultField::value("success"), - super::ToolAuditResultField::value("session_id"), - super::ToolAuditResultField::value("message_id"), - super::ToolAuditResultField::value("answer_message_id"), - super::ToolAuditResultField::value("completion_id"), - super::ToolAuditResultField::value("replayed"), - super::ToolAuditResultField::pointer("author_session_id", "/answer/author_session_id"), - ]), - ModelVisible, - TOOL_CATEGORY_SESSION, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { - effect: super::ToolEffect::Mutate, - risk: super::ToolRisk::SessionCollaborate, - approval: super::ToolApprovalPolicy::None, - idempotency: super::ToolIdempotency::FencedReplay, - }, - Some(SESSION_COLLABORATE), - false, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), - ), + ToolDeclaration { + name: "complete_session_message", + audit: super::ToolAuditPolicy::typed_fields(&[ + super::ToolAuditResultField::value("success"), + super::ToolAuditResultField::value("session_id"), + super::ToolAuditResultField::value("message_id"), + super::ToolAuditResultField::value("answer_message_id"), + super::ToolAuditResultField::value("completion_id"), + super::ToolAuditResultField::value("replayed"), + super::ToolAuditResultField::pointer("author_session_id", "/answer/author_session_id"), + ]), + visibility: ModelVisible, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Mutate, + risk: super::ToolRisk::SessionCollaborate, + approval: super::ToolApprovalPolicy::None, + idempotency: super::ToolIdempotency::FencedReplay, + }, + required_scope: Some(SESSION_COLLABORATE), + requires_project: false, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.lifecycle(super::ToolSessionLifecycleEffect::Mutation), + }.define(), "Atomically answer and resolve one exact open todo. expected_assignment_fence is required and must be the exact opaque fence from get_session_assignment; completion_key separately keeps uncertain-result retries idempotent.", ), PERMISSION_RISK_WRITE, @@ -639,30 +653,30 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ "Explicit read-only recovery for missing task context or an explicit handoff; requires exact session_id/session_ref and is not routine status polling. Returns deterministic 8 KiB handoff_brief with task, workspace, progress, validation, Jobs, collaboration/external-report evidence, next actions and basis completeness. If the same owner has active Goals explicitly correlated to the Session, optional bounded goal_context is returned separately: zero omit it and multiple remain selection_required. Goal context is read-only: no inference, Goal mutation, liveness refresh, scheduling or authority grant. External reports retain unknown outcomes/incomplete coverage and are not native execution/validation. Omitted project uses the authorized Session Project; diagnostic=true adds ledger/closeout evidence. Concurrent Session change makes basis incomplete; re-observe before dependent work.", )), requires_explicit_business_session( - def( - "get_session_handoff_state", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "get_session_handoff_state", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("session_id"), super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("error_kind"), ]), - ModelHidden, - TOOL_CATEGORY_SESSION, - None, - TOOL_PROVIDER_CONTROL, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SESSION, + runner_capability: None, + provider_id: TOOL_PROVIDER_CONTROL, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(RUNTIME_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(RUNTIME_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_activity( super::ToolActivityPresentation::Support, super::ToolActivityInteraction::NonMeaningful, diff --git a/crates/webcodex-tool-contracts/src/tool_definition/skills.rs b/crates/webcodex-tool-contracts/src/tool_definition/skills.rs index 5fc95e8c5..9f215a5b5 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/skills.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/skills.rs @@ -1,8 +1,8 @@ use super::RunnerCapabilityRequirement::{FileRead, SkillManagement}; use super::ToolVisibility::{ModelHidden, ModelVisible}; use super::{ - adaptive_runtime_direct, def, model_spec, require_all_scopes, runner_project_read_def, - ToolDefinition, ToolOperatorExtensionFamily, TOOL_CATEGORY_SKILL, + adaptive_runtime_direct, model_spec, require_all_scopes, runner_project_read_def, + ToolDeclaration, ToolDefinition, ToolOperatorExtensionFamily, TOOL_CATEGORY_SKILL, }; use crate::metadata::{ ToolPathHint::None as NoPath, @@ -66,9 +66,9 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ), require_all_scopes( model_spec( - def( - "run_skill_resource", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "run_skill_resource", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("skill_id"), super::ToolAuditResultField::value("skill_path"), super::ToolAuditResultField::value("skill_sha256"), @@ -84,23 +84,23 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ "args", "process_summary", ])), - ModelVisible, - TOOL_CATEGORY_SKILL, - Some(super::RunnerCapabilityRequirement::SkillResourceExecution), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_SKILL, + runner_capability: Some(super::RunnerCapabilityRequirement::SkillResourceExecution), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - true, - true, - super::ToolSessionEvidencePolicy::NONE, - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: true, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define(), "Execute one supported scripts/*.py or scripts/*.sh resource from a trusted Runner-configured or managed Skill without retransmitting source. Configured Skills are live resources: expected_definition_revision fences selection; resource bytes are read at execution and package-relative helpers remain live; skill_sha256 identifies the executed main script. Managed Skills additionally require expected_package_revision and use an immutable package. Runner preserves Skill identity for __file__, sibling imports, and $0-relative helpers while keeping the requested Project cwd; project-content Skills are rejected. If execution_state=pending, keep the exact continuation and continue independent work; later same-scope results may surface terminal attention. Observe only for details/recovery.", ) .with_execution(super::ToolExecutionContract::new( @@ -111,9 +111,9 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ )), &[PROJECT_READ, JOB_RUN], ), - def( - "list_skills", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "list_skills", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("catalog_revision"), super::ToolAuditResultField::value("total_count"), @@ -136,27 +136,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ])), - ModelHidden, - TOOL_CATEGORY_SKILL, - Some(FileRead), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SKILL, + runner_capability: Some(FileRead), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_operator_extension_family(ToolOperatorExtensionFamily::SkillRuntime), - def( - "read_skill_file", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "read_skill_file", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("skill_id"), super::ToolAuditResultField::value("source_scope"), @@ -189,27 +189,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ])), - ModelHidden, - TOOL_CATEGORY_SKILL, - Some(FileRead), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SKILL, + runner_capability: Some(FileRead), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(PROJECT_READ), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(PROJECT_READ), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_operator_extension_family(ToolOperatorExtensionFamily::SkillRuntime), - def( - "list_skill_versions", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "list_skill_versions", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("skill_id"), super::ToolAuditResultField::value("skill_key"), @@ -221,27 +221,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ]), - ModelHidden, - TOOL_CATEGORY_SKILL, - Some(SkillManagement), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SKILL, + runner_capability: Some(SkillManagement), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Observe, risk: Read, approval: super::ToolApprovalPolicy::None, idempotency: super::ToolIdempotency::PureRead, }, - Some(ADMIN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(ADMIN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_operator_extension_family(ToolOperatorExtensionFamily::SkillManagement), - def( - "install_skill", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "install_skill", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("skill_id"), super::ToolAuditResultField::value("skill_key"), @@ -259,27 +259,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ]), - ModelHidden, - TOOL_CATEGORY_SKILL, - Some(SkillManagement), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SKILL, + runner_capability: Some(SkillManagement), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: SkillManage, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(ADMIN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(ADMIN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_operator_extension_family(ToolOperatorExtensionFamily::SkillManagement), - def( - "activate_skill", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "activate_skill", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("skill_id"), super::ToolAuditResultField::value("skill_key"), @@ -292,27 +292,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ]), - ModelHidden, - TOOL_CATEGORY_SKILL, - Some(SkillManagement), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SKILL, + runner_capability: Some(SkillManagement), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: SkillManage, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(ADMIN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(ADMIN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_operator_extension_family(ToolOperatorExtensionFamily::SkillManagement), - def( - "remove_skill_revision", - super::ToolAuditPolicy::typed_fields(&[ + ToolDeclaration { + name: "remove_skill_revision", + audit: super::ToolAuditPolicy::typed_fields(&[ super::ToolAuditResultField::value("project"), super::ToolAuditResultField::value("skill_id"), super::ToolAuditResultField::value("skill_key"), @@ -324,22 +324,22 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolAuditResultField::value("error_kind"), super::ToolAuditResultField::value("state_changed"), ]), - ModelHidden, - TOOL_CATEGORY_SKILL, - Some(SkillManagement), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelHidden, + category: TOOL_CATEGORY_SKILL, + runner_capability: Some(SkillManagement), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: SkillManage, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(ADMIN), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE, - ) + required_scope: Some(ADMIN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE, + }.define() .with_operator_extension_family(ToolOperatorExtensionFamily::SkillManagement), ]; diff --git a/crates/webcodex-tool-contracts/src/tool_definition/ssh_resources.rs b/crates/webcodex-tool-contracts/src/tool_definition/ssh_resources.rs index 2a7ef7537..11382864e 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/ssh_resources.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/ssh_resources.rs @@ -1,17 +1,13 @@ use super::ToolVisibility::ModelVisible; -use super::{def, model_spec, ToolDefinition, TOOL_CATEGORY_RUNTIME}; -use crate::metadata::{ - ToolPathHint::None as NoPath, ToolRisk::RunControl, SSH_LOCAL, TOOL_PROVIDER_CONTROL, -}; +use super::{control_def, model_spec, ToolDefinition, TOOL_CATEGORY_RUNTIME}; +use crate::metadata::{ToolRisk::RunControl, SSH_LOCAL}; pub(super) const DEFINITIONS: &[ToolDefinition] = &[model_spec( - def( + control_def( "manage_ssh_resource", super::ToolAuditPolicy::TYPED_CANONICAL, ModelVisible, TOOL_CATEGORY_RUNTIME, - None, - TOOL_PROVIDER_CONTROL, super::ToolSemanticContract { effect: super::ToolEffect::Mutate, risk: RunControl, @@ -19,11 +15,7 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[model_spec( idempotency: super::ToolIdempotency::FencedReplay, }, Some(SSH_LOCAL), - false, - NoPath, true, - false, - super::ToolSessionEvidencePolicy::NONE, ), "Discover and manage Runner-local named SSH resources only when PersistentShell needs a durable remote target. Start with action=list on one exact Runner; register/remove require its opaque revision binding. After a registration that requires restart, restart the Runner, list again, bind the active name with update_session_context, then discover open_session_shell/execute_session_shell. For one-shot SSH without persistent state, keep run_process. Targets and authentication details are never returned.", )]; diff --git a/crates/webcodex-tool-contracts/src/tool_definition/testing.rs b/crates/webcodex-tool-contracts/src/tool_definition/testing.rs index 16ccad0c5..570b56de8 100644 --- a/crates/webcodex-tool-contracts/src/tool_definition/testing.rs +++ b/crates/webcodex-tool-contracts/src/tool_definition/testing.rs @@ -1,8 +1,8 @@ use super::RunnerCapabilityRequirement::{OwnerOnly, Shell}; use super::ToolVisibility::ModelVisible; use super::{ - adaptive_runtime_direct, captures_validation_output, def, model_spec, ToolDefinition, - TOOL_CATEGORY_VALIDATION, + adaptive_runtime_direct, captures_validation_output, model_spec, ToolDeclaration, + ToolDefinition, TOOL_CATEGORY_VALIDATION, }; use crate::metadata::{ ToolPathHint::None as NoPath, ToolRisk::JobRun, JOB_RUN, TOOL_PROVIDER_RUNNER, @@ -10,27 +10,27 @@ use crate::metadata::{ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ captures_validation_output(model_spec( - def( - "cargo_fmt", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "cargo_fmt", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .execution(super::ToolAuditExecutionPolicy::TEXT), - ModelVisible, - TOOL_CATEGORY_VALIDATION, - Some(Shell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_VALIDATION, + runner_capability: Some(Shell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - true, - false, - super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::CargoFmt), - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: true, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::CargoFmt), + }.define(), "Use check=false (default) for intentional final formatting after relevant Rust source stabilizes: precheck first, mutate only for a proven rustfmt diff, and use changed/state_changed instead of reproducing rustfmt diffs with edit tools. Do not use cargo_fmt as a per-edit ritual. Use check=true for read-only formatting validation; only check mode may hand off the same execution as a Job. If check mode returns execution_state=pending, keep its continuation as fallback, continue independent work, and let ordinary same-Window/Project/Session results surface sparse terminal attention; do not poll. Observe only for details/recovery. If covered Rust source changes, check=true evidence is stale. For final formatting proof, freeze covered source or rerun after any required source edit. Server timing policy controls same-execution Job handoff grace.", ) .with_execution(super::ToolExecutionContract::new( @@ -40,27 +40,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolExecutionContinuation::ObserveJobs, ))), captures_validation_output(model_spec( - def( - "cargo_check", - super::ToolAuditPolicy::TYPED_CANONICAL - .execution(super::ToolAuditExecutionPolicy::TEXT), - ModelVisible, - TOOL_CATEGORY_VALIDATION, - Some(Shell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Execute, - risk: JobRun, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(JOB_RUN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::CargoCheck), - ) + ToolDeclaration { + name: "cargo_check", + audit: super::ToolAuditPolicy::TYPED_CANONICAL + .execution(super::ToolAuditExecutionPolicy::TEXT), + visibility: ModelVisible, + category: TOOL_CATEGORY_VALIDATION, + runner_capability: Some(Shell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Execute, + risk: JobRun, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::CargoCheck), + }.define() .with_composition_policy(super::ToolCompositionPolicy::Sequential) .with_host_orchestration_hint( super::ToolHostOrchestrationHint::sequential() @@ -75,27 +75,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolExecutionContinuation::ObserveJobs, ))), captures_validation_output(model_spec( - def( - "cargo_test", - super::ToolAuditPolicy::TYPED_CANONICAL - .execution(super::ToolAuditExecutionPolicy::TEST_ASSERTIONS), - ModelVisible, - TOOL_CATEGORY_VALIDATION, - Some(Shell), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { - effect: super::ToolEffect::Execute, - risk: JobRun, - approval: super::ToolApprovalPolicy::Standard, - idempotency: super::ToolIdempotency::NonIdempotent, - }, - Some(JOB_RUN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::CargoTest), - ) + ToolDeclaration { + name: "cargo_test", + audit: super::ToolAuditPolicy::TYPED_CANONICAL + .execution(super::ToolAuditExecutionPolicy::TEST_ASSERTIONS), + visibility: ModelVisible, + category: TOOL_CATEGORY_VALIDATION, + runner_capability: Some(Shell), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { + effect: super::ToolEffect::Execute, + risk: JobRun, + approval: super::ToolApprovalPolicy::Standard, + idempotency: super::ToolIdempotency::NonIdempotent, + }, + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::CargoTest), + }.define() .with_composition_policy(super::ToolCompositionPolicy::Sequential) .with_host_orchestration_hint(super::ToolHostOrchestrationHint::sequential()), "Run Cargo tests. Structured cargo test for common supported validation with bounded output, executed-test evidence, min_tests/require_tests, validation identity, and same execution Job handoff. lib=true selects Cargo --lib; filter is one Rust substring, not flags such as --exact or --nocapture. Exit 0 alone is not test proof: default requires positive counts; require_tests=false accepts zero only without min_tests; require_tests=true/min_tests enforce a proven minimum. no_run=true is compile-only. Sparse synchronous proof retains tests_run_count and explicit minimum_tests, require_tests=false for accepted zero, or no_run=true for compile-only. Rich success may lack test proof. source_state never certifies current workspace source. If pending, keep its continuation and continue independent work; same-scope results may surface terminal attention. Observe only for logs/details/recovery; do not poll. Edits to covered source stale evidence. For final evidence freeze covered source; if it changes, rerun the appropriate test.", @@ -108,27 +108,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ ))), adaptive_runtime_direct( captures_validation_output(model_spec( - def( - "project_validate", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "project_validate", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .execution(super::ToolAuditExecutionPolicy::TEST_ASSERTIONS), - ModelVisible, - TOOL_CATEGORY_VALIDATION, - Some(OwnerOnly), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_VALIDATION, + runner_capability: Some(OwnerOnly), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::Project), - ) + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::Project), + }.define() .with_composition_policy(super::ToolCompositionPolicy::Sequential) .with_host_orchestration_hint(super::ToolHostOrchestrationHint::sequential()), "Project validation. Project scripts may modify files or access network. Rust: format_check/check/lint/test; Go: check/test. Rust lint: cargo clippy --all-targets -- -D clippy::correctness; cargo_clippy evidence, project_validation_rust_clippy_v1. Clippy missing: no install/fallback. Node >=22.3: check>typecheck>lint; lint selects only scripts.lint (check identity); node --run without pre/post. Node TAP test only for exact scripts.test=node --test or node --test --test-reporter=tap. Vitest/Jest: fixed installed CLI, project_validation_node_json_v1 JSON counts; other scripts unproven. Scope: packages 1..8/all_packages=true; Rust Cargo workspace proven, Go single module. Rust exclude_packages (all_packages): 1..8 exact Cargo members; unknown/all excluded reject; project_workspace_exclusions_v1 planning/admission; not Go/Node/Python/format. test.filter: Rust substring, Go -run, pytest -k; not for Node. require_tests/min_tests need proven counts. Python/Ruff preinstalled; Ruff needs pyproject.toml [tool.ruff] target-version, no extend/fixes/cache/install. locked: Cargo --locked/Go -mod=readonly; not Python/Node/format. dependency_resolution_policy=offline: Rust check/lint/test Cargo --offline; Clippy requires project_validation_rust_clippy_offline_v1; cache miss fails, no online fallback. build scripts/tests may use network; not OS sandbox. Go GO111MODULE=on/GOWORK=off. No arbitrary executable, argv or shell. Same Runner Job; recheck provenance at queue; observe pending, never retry.", ).with_discovery_keywords(&["project", "validation", "build", "tests", "lint", "verify", "项目", "验证", "构建", "测试", "检查"]) @@ -142,27 +142,27 @@ pub(super) const DEFINITIONS: &[ToolDefinition] = &[ super::ToolDirectReason::CoreWorkflow, ), captures_validation_output(model_spec( - def( - "go_test", - super::ToolAuditPolicy::TYPED_CANONICAL + ToolDeclaration { + name: "go_test", + audit: super::ToolAuditPolicy::TYPED_CANONICAL .execution(super::ToolAuditExecutionPolicy::TEST_COUNTS), - ModelVisible, - TOOL_CATEGORY_VALIDATION, - Some(OwnerOnly), - TOOL_PROVIDER_RUNNER, - super::ToolSemanticContract { + visibility: ModelVisible, + category: TOOL_CATEGORY_VALIDATION, + runner_capability: Some(OwnerOnly), + provider_id: TOOL_PROVIDER_RUNNER, + semantic: super::ToolSemanticContract { effect: super::ToolEffect::Execute, risk: JobRun, approval: super::ToolApprovalPolicy::Standard, idempotency: super::ToolIdempotency::NonIdempotent, }, - Some(JOB_RUN), - true, - NoPath, - false, - false, - super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::GoTest), - ), + required_scope: Some(JOB_RUN), + requires_project: true, + path_hint: NoPath, + destructive: false, + shell_like: false, + session_evidence: super::ToolSessionEvidencePolicy::NONE.validation_identity(super::ToolValidationIdentityKind::GoTest), + }.define(), "Structured option for common supported go test -json (default ./...) when bounded package scopes, Go JSON test-count evidence, validation identity, or same-execution pending handoff are useful. If execution_state=pending, keep its continuation as fallback and continue independent work; ordinary same-Window/Project/Session results may surface sparse terminal validation truth. Observe only when logs/details/recovery are needed. Validation intent is intrinsic to this validator and Runtime-derived; do not pass generic execution purpose. Requires Runner Go JSON validation support; Server timing policy controls only synchronous grace before the same execution is returned pending and never changes total timeout or retry semantics. Sparse synchronous success retains tests_run_count only when complete evidence proves all counted tests passed; skipped or incomplete evidence stays rich. source_state independently describes source coverage.", ).with_discovery_keywords(&["golang", "unit", "integration", "tests", "test", "suite", "测试", "单元", "集成", "用例"]) .with_execution(super::ToolExecutionContract::new( From cdf6024790700aca0d31b00163c08a0b5ec3f0da Mon Sep 17 00:00:00 2001 From: yyjeqhc <1772413353@qq.com> Date: Mon, 9 Nov 2026 15:35:23 +0800 Subject: [PATCH 2/2] test(discovery): parse named tool declarations in offline tooling --- scripts/agent_tool_discovery.py | 10 ++- .../tests/test_agent_tool_discovery_cases.py | 63 +++++++++++++------ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/scripts/agent_tool_discovery.py b/scripts/agent_tool_discovery.py index e0e9a95ef..39b3c2327 100644 --- a/scripts/agent_tool_discovery.py +++ b/scripts/agent_tool_discovery.py @@ -104,7 +104,15 @@ def _catalog(): sources[path] = raw.decode("utf-8") names = set() for path in paths: - names.update(re.findall(r'\b(?:def|control_def|runner_project_read_def|resource_tool|docx_read_definition|document_read_definition)\(\s*"([a-z_]+)"', sources[path])) + source = sources[path] + names.update(re.findall( + r'\b(?:control_def|runner_project_read_def|resource_tool|docx_read_definition|document_read_definition)\(\s*"([a-z_]+)"', + source, + )) + names.update(re.findall( + r'\bToolDeclaration\s*\{\s*name:\s*"([a-z_]+)"', + source, + )) scopes = set(re.findall(r'pub const SCOPE_[A-Z_]+: &str = "([a-z_:]+)";', sources[authority_path])) categories = set(re.findall(r'pub const TOOL_CATEGORY_[A-Z_]+: &str = "([a-z_]+)";', sources[paths[0]])) return names, scopes, categories diff --git a/scripts/tests/test_agent_tool_discovery_cases.py b/scripts/tests/test_agent_tool_discovery_cases.py index 3f876822b..eda397910 100644 --- a/scripts/tests/test_agent_tool_discovery_cases.py +++ b/scripts/tests/test_agent_tool_discovery_cases.py @@ -40,30 +40,57 @@ def canonical_definitions(): if read_constructor is None: raise ValueError("runner_project_read_def constructor is missing") definitions = {} + # Parse the source-level contract declarations, not an inferred runtime + # manifest. The canonical constructor now uses named ToolDeclaration fields; + # the two narrow helper constructors still take positional parameters. + pattern = re.compile( + r'\b(?Pcontrol_def|runner_project_read_def)\(\s*"(?P[a-z_]+)"' + r'|\bToolDeclaration\s*\{\s*name:\s*"(?P[a-z_]+)"' + ) for path in sorted((CONTRACTS / "tool_definition").glob("*.rs")): source = path.read_text(encoding="utf-8") - matches = list(re.finditer( - r'\b(?Pdef|control_def|runner_project_read_def)\(\s*"(?P[a-z_]+)"', - source, - )) + matches = list(pattern.finditer(source)) for index, match in enumerate(matches): - end = matches[index + 1].start() if index + 1 < len(matches) else len(source) - body = source[match.end():end] - category = re.search(r'\bTOOL_CATEGORY_([A-Z_]+)\b', body) - policy_body = ( - read_constructor.group(0) - if match.group("constructor") == "runner_project_read_def" - else body - ) - scope = re.search( - r'ToolIdempotency::\w+\s*,?\s*},\s*Some\((?:crate::metadata::)?([A-Z_]+)\)', - policy_body, - ) + next_start = matches[index + 1].start() if index + 1 < len(matches) else len(source) + constructor = match.group("constructor") + if constructor is None: + # Limit extraction to this declaration, not later definitions. + define_end = source.find(".define()", match.end(), next_start) + if define_end < 0: + raise ValueError(f"ToolDeclaration lacks .define(): {path}:{match.start()}") + body = source[match.end():define_end] + category = re.search(r'\bcategory:\s*TOOL_CATEGORY_([A-Z_]+)\b', body) + scope = re.search( + r'\brequired_scope:\s*Some\((?:crate::metadata::)?([A-Z_]+)\)', + body, + ) + visible = bool(re.search(r'\bvisibility:\s*(?:ToolVisibility::)?ModelVisible\b', body)) + name = match.group("named_name") + else: + body = source[match.end():next_start] + category = re.search(r'\bTOOL_CATEGORY_([A-Z_]+)\b', body) + policy_body = ( + read_constructor.group(0) + if constructor == "runner_project_read_def" + else body + ) + scope = re.search( + r'\brequired_scope:\s*Some\((?:crate::metadata::)?([A-Z_]+)\)', + policy_body, + ) or re.search( + r'ToolIdempotency::\w+\s*,?\s*},\s*Some\((?:crate::metadata::)?([A-Z_]+)\)', + policy_body, + ) + visible = ( + True if constructor == "runner_project_read_def" + else bool(re.search(r'\bModelVisible\s*,', policy_body)) + ) + name = match.group("legacy_name") if category and scope: - definitions[match.group("name")] = { + definitions[name] = { "category": category.group(1).lower(), "scope_constant": scope.group(1), - "model_visible": bool(re.search(r'\bModelVisible\s*,', policy_body)), + "model_visible": visible, } authority = (ROOT / "crates/webcodex-core/src/authority.rs").read_text(encoding="utf-8") scopes = dict(re.findall(r'pub const SCOPE_([A-Z_]+): &str = "([a-z_:]+)";', authority))