Complete name-first 1C adapter saved-state support
This commit is contained in:
@@ -104,6 +104,18 @@ OBJECT_SELECTOR_SCHEMA_PROPERTIES = {
|
||||
}
|
||||
CACHE_POLICIES = {"none", "ttl", "snapshot", "stale_while_revalidate"}
|
||||
SOURCE_STATES = {"applied", "working", "all"}
|
||||
CONFIGURATION_VIEW_TO_SOURCE_STATE = {
|
||||
"effective": "working",
|
||||
"effective_working": "working",
|
||||
"designer": "working",
|
||||
"working": "working",
|
||||
"runtime": "applied",
|
||||
"runtime_applied": "applied",
|
||||
"applied": "applied",
|
||||
"compare": "all",
|
||||
"comparison": "all",
|
||||
"both": "all",
|
||||
}
|
||||
REST_STATE_BY_SOURCE_STATE = {
|
||||
"applied": "active",
|
||||
"working": "working",
|
||||
@@ -176,14 +188,24 @@ TOOLS = [
|
||||
"payload.base_id is required; get it from user/project context or check a concrete base with onec_health first. "
|
||||
"If you already have module_ref/read_selector, prefer direct read methods before global search. "
|
||||
"Search results include read_selector.method; reuse that selector directly for the next read call. "
|
||||
"For a name search across metadata objects, forms, attributes, commands, templates, routines, and extension definitions, use metadata.definition.find; "
|
||||
"use code.search only when the query is BSL text. Scope extension objects with extension.objects.find or metadata.definition.find areas=extensions, never by SQL table names. "
|
||||
"For unresolved module owners, inspect diagnostics.owner_resolution and adjust the object selector "
|
||||
"(ref, kind/name/guid, or object_type/object_name/object_guid) or owner_scan_limit. "
|
||||
"For programming in Configurator/designer, use source_state=working to read saved-but-not-applied metadata; "
|
||||
"use source_state=applied for active metadata or source_state=all to compare both. "
|
||||
"The default agent view is configuration_view=effective_working with source_state=working: the logical Designer snapshot, with working changes and extension layers preferred; "
|
||||
"it becomes executable after configuration update, not necessarily now. Use configuration_view=runtime_applied for code executable now, or compare to inspect both. "
|
||||
"Do not select Config/ConfigSave tables in ordinary programming calls. "
|
||||
"For saved-state methods, select base_saved_state or extension_saved_state with layer and identify objects by ref or kind/name; "
|
||||
"use table/file_name/module_ref only when continuing an explicit include_storage diagnostic result. "
|
||||
"Before writes, call metadata.write.preflight when you need a read-only route/freshness check; it reports "
|
||||
"ready, needs_prepare, needs_resolution, or blocked and never applies SQL writes. "
|
||||
"Repository manual-capture protocol: when repository.lock.request or repository.lock.request.status returns "
|
||||
"status=pending_user_lock, do not invent confirmation fields. After the user confirms the exact object is "
|
||||
"captured in Configurator, call the returned next_call.method using next_call.params as this tool's payload. "
|
||||
"The successful confirmation returns write_context; forward it unchanged as payload.repository_lock (or copy its fields to the payload top level) for write preflight and write calls. "
|
||||
"For BSL edits, prefer high-level metadata.write: pass a 1C canonical path, routine_text, and routine_operation; "
|
||||
"the adapter prepares saved-state when needed, saves into working/saved-state metadata, and never activates it. "
|
||||
"For a scheduled-job schedule, use metadata.write with target.kind=schedule and target.ref such as РегламентныеЗадания.ОбменДанными; pass named schedule fields instead of GUIDs or tree paths. "
|
||||
"For adding a form command with a visible button and handler routine, use metadata.form.command_button.write. "
|
||||
"Use code.write only as a compatibility shortcut for simple module edits. "
|
||||
"Long metadata calls return a job_id quickly; poll it with method mcp.job.get."
|
||||
@@ -362,6 +384,16 @@ TOOLS = [
|
||||
"routine_text": "<full-procedure-or-function-text>",
|
||||
},
|
||||
},
|
||||
{
|
||||
"method": "metadata.write",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"target": {"kind": "schedule", "ref": "РегламентныеЗадания.<scheduled-job-name>"},
|
||||
"schedule": {"begin_time": "09:00:00", "week_days": [1, 2, 3, 4, 5]},
|
||||
"allow_saved_state_write": True,
|
||||
"mode": "plan",
|
||||
},
|
||||
},
|
||||
{
|
||||
"method": "metadata.form.command_button.write",
|
||||
"payload": {
|
||||
@@ -417,9 +449,9 @@ TOOLS = [
|
||||
"method": "metadata.saved_state.prepare",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"target_table": "ConfigCASSave",
|
||||
"object_type": "<metadata-kind>",
|
||||
"object_name": "<metadata-object-name>",
|
||||
"layer": "extension_saved_state",
|
||||
"ref": "<metadata-kind>.<metadata-object-name>",
|
||||
"extension": "<extension-name>",
|
||||
"mode": "plan",
|
||||
},
|
||||
},
|
||||
@@ -427,8 +459,9 @@ TOOLS = [
|
||||
"method": "metadata.saved_state.diff",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"table": "ConfigCASSave",
|
||||
"file_name": "<saved-state-file-name>",
|
||||
"ref": "<metadata-kind>.<metadata-object-name>",
|
||||
"module_ordinal": 1,
|
||||
"extension": "<extension-name-if-needed>",
|
||||
"max_text_diff_lines": 80,
|
||||
},
|
||||
},
|
||||
@@ -436,7 +469,7 @@ TOOLS = [
|
||||
"method": "metadata.saved_state.status",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"table": "ConfigCASSave",
|
||||
"layer": "extension_saved_state",
|
||||
"limit": 200,
|
||||
},
|
||||
},
|
||||
@@ -444,6 +477,7 @@ TOOLS = [
|
||||
"method": "metadata.saved_state.changes.list",
|
||||
"payload": {
|
||||
"base_id": "<base_id-from-project-context>",
|
||||
"layer": "extension_saved_state",
|
||||
"limit": 200,
|
||||
"include_context": True,
|
||||
"group_by_context": True,
|
||||
@@ -990,15 +1024,17 @@ TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "access_object_explain",
|
||||
"description": "Explain who can see a BSP-protected object/record by resolving object access keys to groups, user sets, and users.",
|
||||
"description": "Explain who can see a BSP-protected data record. Prefer public object_ref plus record_ref; legacy raw BSP fields remain available.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["base_id"],
|
||||
"properties": {
|
||||
"base_id": {"type": "string", "description": "Concrete 1C base id."},
|
||||
"object": {"type": "string", "description": "Normalized object ref from access_object_keys.object."},
|
||||
"object_id": {"type": "string", "description": "Data record id from access_object_keys.object_id."},
|
||||
"object_sql_number": {"type": "integer"},
|
||||
"object_ref": {"type": "string", "description": "Public metadata ref, for example Справочники.Номенклатура."},
|
||||
"record_ref": {"type": "string", "description": "Concrete 1C application-data record reference."},
|
||||
"object": {"type": "string", "description": "Legacy raw BSP object value."},
|
||||
"object_id": {"type": "string", "description": "Legacy raw BSP object_id; prefer record_ref."},
|
||||
"object_sql_number": {"type": "integer", "description": "Legacy physical selector; prefer object_ref."},
|
||||
"access_key": {"type": "string"},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 20000, "default": 1000},
|
||||
"subject_limit": {"type": "integer", "minimum": 1, "maximum": 20000, "default": 1000},
|
||||
@@ -1010,20 +1046,25 @@ TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "access_keys_query",
|
||||
"description": "Page through BSP access key registers by group, user set, object, access set, or all. Use for data restriction diagnostics, not for metadata-object role rights.",
|
||||
"description": "Page through BSP access key registers. kind selects the query area, not a metadata kind; in object mode prefer object_ref plus optional record_ref.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["base_id"],
|
||||
"properties": {
|
||||
"base_id": {"type": "string", "description": "Concrete 1C base id."},
|
||||
"kind": {"type": "string", "description": "group, user, object, access_set, or all."},
|
||||
"kind": {"type": "string", "description": "Query area: group, user_set, object, set, or all. This is not a 1C metadata kind."},
|
||||
"area": {"type": "string", "description": "Alias of kind."},
|
||||
"group": {"type": "string"},
|
||||
"user": {"type": "string"},
|
||||
"user_set": {"type": "string"},
|
||||
"object": {"type": "string"},
|
||||
"object_id": {"type": "string"},
|
||||
"object_sql_number": {"type": "integer"},
|
||||
"object_ref": {"type": "string", "description": "Public metadata ref, for example Справочники.Номенклатура."},
|
||||
"record_ref": {"type": "string", "description": "Concrete 1C application-data record reference."},
|
||||
"object": {"type": "string", "description": "Legacy raw BSP object value."},
|
||||
"object_id": {"type": "string", "description": "Legacy raw BSP object_id; prefer record_ref."},
|
||||
"object_sql_number": {"type": "integer", "description": "Legacy physical selector; prefer object_ref."},
|
||||
"access_key": {"type": "string"},
|
||||
"resolve_records": {"type": "boolean", "default": False},
|
||||
"max_resolved_records": {"type": "integer", "minimum": 0, "maximum": 5000, "default": 200},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 20000, "default": 1000},
|
||||
"offset": {"type": "integer", "minimum": 0, "default": 0},
|
||||
"timeout_seconds": {"type": "integer", "minimum": 1, "maximum": 120, "default": 120},
|
||||
@@ -1033,15 +1074,17 @@ TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "access_object_keys_resolve",
|
||||
"description": "Return BSP object access-key rows and resolve object records when possible. Accepts object names/public refs and internal ids.",
|
||||
"description": "Return BSP object access-key rows and resolve record presentations. Prefer public object_ref plus optional record_ref.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"required": ["base_id"],
|
||||
"properties": {
|
||||
"base_id": {"type": "string", "description": "Concrete 1C base id."},
|
||||
"object": {"type": "string"},
|
||||
"object_id": {"type": "string"},
|
||||
"object_sql_number": {"type": "integer"},
|
||||
"object_ref": {"type": "string", "description": "Public metadata ref, for example Справочники.Номенклатура."},
|
||||
"record_ref": {"type": "string", "description": "Concrete 1C application-data record reference."},
|
||||
"object": {"type": "string", "description": "Legacy raw BSP object value."},
|
||||
"object_id": {"type": "string", "description": "Legacy raw BSP object_id; prefer record_ref."},
|
||||
"object_sql_number": {"type": "integer", "description": "Legacy physical selector; prefer object_ref."},
|
||||
"access_key": {"type": "string"},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 20000, "default": 1000},
|
||||
"offset": {"type": "integer", "minimum": 0, "default": 0},
|
||||
@@ -1211,7 +1254,22 @@ def normalize_source_state(value: Any, source_mode: str) -> str:
|
||||
return "working"
|
||||
if value_text in SOURCE_STATES:
|
||||
return value_text
|
||||
return "working" if source_mode == "designer" else "applied"
|
||||
# Agents program against the logical Designer configuration by default.
|
||||
# Runtime remains explicit through source_mode=runtime or source_state=applied.
|
||||
return "applied" if source_mode == "runtime" else "working"
|
||||
|
||||
|
||||
def normalize_configuration_view(value: Any, source_state: str) -> tuple[str, str]:
|
||||
view = str(value or "").strip().casefold()
|
||||
if not view:
|
||||
return ("effective_working" if source_state == "working" else "runtime_applied" if source_state == "applied" else "compare", source_state)
|
||||
mapped = CONFIGURATION_VIEW_TO_SOURCE_STATE.get(view)
|
||||
if not mapped:
|
||||
return "", ""
|
||||
if source_state and source_state != mapped:
|
||||
return "", ""
|
||||
canonical = "effective_working" if mapped == "working" else "runtime_applied" if mapped == "applied" else "compare"
|
||||
return canonical, mapped
|
||||
|
||||
|
||||
def normalize_cache_policy(value: Any, source_mode: str) -> str:
|
||||
@@ -2213,7 +2271,15 @@ def apply_freshness_request_policy(payload: dict[str, Any], method: str) -> dict
|
||||
if method == "code.write":
|
||||
source_mode = "designer"
|
||||
cache_policy = normalize_cache_policy(payload.get("cache_policy"), source_mode)
|
||||
source_state = normalize_source_state(payload.get("source_state"), source_mode)
|
||||
explicit_source_state = normalize_source_state(payload.get("source_state"), source_mode) if payload.get("source_state") is not None else ""
|
||||
configuration_view, source_state = normalize_configuration_view(payload.get("configuration_view"), explicit_source_state)
|
||||
if not configuration_view:
|
||||
transformed = dict(payload)
|
||||
transformed["_configuration_view_error"] = "configuration_view conflicts with source_state or is unsupported."
|
||||
return transformed
|
||||
if not source_state:
|
||||
source_state = normalize_source_state(None, source_mode)
|
||||
configuration_view, source_state = normalize_configuration_view(payload.get("configuration_view"), source_state)
|
||||
if method == "code.write":
|
||||
source_state = "working"
|
||||
cache_policy = "none"
|
||||
@@ -2221,6 +2287,7 @@ def apply_freshness_request_policy(payload: dict[str, Any], method: str) -> dict
|
||||
transformed = dict(payload)
|
||||
transformed["source_mode"] = source_mode
|
||||
transformed["source_state"] = source_state
|
||||
transformed["configuration_view"] = configuration_view
|
||||
transformed["cache_policy"] = cache_policy
|
||||
if method in REST_STATE_METHODS and "state" not in transformed:
|
||||
transformed["state"] = REST_STATE_BY_SOURCE_STATE.get(source_state, "working")
|
||||
@@ -2878,7 +2945,19 @@ def metadata_write_code_guardrail(method: str, payload: dict[str, Any]) -> dict[
|
||||
def run_or_enqueue_adapter_method(method: str, payload: dict[str, Any]) -> Any:
|
||||
request_start = now_ts()
|
||||
request_id = uuid.uuid4().hex
|
||||
if method == "code.search" and payload.get("extension_guid") in {None, ""}:
|
||||
payload = dict(payload)
|
||||
payload.pop("extension_guid", None)
|
||||
request_payload = apply_freshness_request_policy(payload, method)
|
||||
configuration_view_error = request_payload.pop("_configuration_view_error", None)
|
||||
if configuration_view_error:
|
||||
return {
|
||||
"schema": "adapter_1c_mcp_policy.v1",
|
||||
"status": "invalid_argument",
|
||||
"method": method,
|
||||
"error": "invalid_configuration_view",
|
||||
"diagnostics": {"message": str(configuration_view_error)},
|
||||
}
|
||||
request_payload["_mcp_request_id"] = request_id
|
||||
payload = request_payload
|
||||
code_guardrail = metadata_write_code_guardrail(method, payload)
|
||||
|
||||
Reference in New Issue
Block a user