135 lines
5.7 KiB
Python
135 lines
5.7 KiB
Python
"""Custom/org runtime entrypoint.
|
|
|
|
Custom mode runs with company-mode semantics, but with a user-selected
|
|
organization config. The runner owns the custom isolation boundary:
|
|
|
|
- company mode's engine/config objects are not mutated;
|
|
- the custom runtime uses its own OPCEngine instance and company executor;
|
|
- the runtime is rebound to the caller's store so UI session/task/transcript
|
|
state remains in the same project context as the chat that started it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import uuid
|
|
from typing import Any, TYPE_CHECKING
|
|
|
|
from opc.core.config import OPCConfig
|
|
from opc.core.models import UserMessage
|
|
from opc.core.org_config import (
|
|
apply_org_config_payload_to_config,
|
|
load_org_config_payload,
|
|
validate_runnable_org_config,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from opc.engine import OPCEngine
|
|
|
|
|
|
class CustomRuntimeRunner:
|
|
"""Run custom/org turns through an isolated company-runtime engine."""
|
|
|
|
def __init__(self, parent: OPCEngine) -> None:
|
|
self.parent = parent
|
|
|
|
def _build_org_config(self, organization_id: str | None) -> tuple[OPCConfig, str | None]:
|
|
config_dir = self.parent.opc_home / "config"
|
|
payload, source_path = load_org_config_payload(config_dir, organization_id)
|
|
try:
|
|
base_config = OPCConfig.load(config_dir) if config_dir.exists() else copy.deepcopy(self.parent.config)
|
|
except Exception:
|
|
base_config = copy.deepcopy(self.parent.config)
|
|
loaded_config = apply_org_config_payload_to_config(
|
|
base_config,
|
|
payload,
|
|
source_path=source_path,
|
|
)
|
|
resolved_org_id = str(getattr(loaded_config.org, "organization_id", "") or organization_id or "").strip() or None
|
|
validate_runnable_org_config(loaded_config, organization_id=resolved_org_id or "")
|
|
return loaded_config, resolved_org_id
|
|
|
|
async def process_message(
|
|
self,
|
|
content: str,
|
|
*,
|
|
project_id: str | None,
|
|
session_id: str | None,
|
|
org_id: str | None,
|
|
preferred_agent: str | None,
|
|
domains: list[str] | None,
|
|
origin_task_id: str | None,
|
|
attachment_refs: list[dict[str, Any]] | None,
|
|
message_metadata: dict[str, Any] | None,
|
|
) -> str:
|
|
from opc.engine import OPCEngine
|
|
from opc.layer2_organization.phase_hooks import unregister_dispatcher_wake
|
|
|
|
org_config, resolved_org_id = self._build_org_config(org_id)
|
|
normalized_project_id = str(project_id or self.parent.project_id or "default").strip() or "default"
|
|
shared_store = getattr(self.parent, "store", None)
|
|
runtime = OPCEngine(
|
|
config=org_config,
|
|
opc_home=self.parent.opc_home,
|
|
project_id=normalized_project_id,
|
|
store=shared_store,
|
|
owns_store=shared_store is None,
|
|
run_startup_reconcile=shared_store is None,
|
|
active_task_run_registry=getattr(self.parent, "_active_task_run_registry", None),
|
|
owns_active_task_run_registry=False,
|
|
on_progress=self.parent.on_progress,
|
|
on_runtime_event=self.parent.on_runtime_event,
|
|
on_escalation=self.parent.on_escalation,
|
|
)
|
|
runtime.on_company_runtime_children = self.parent.on_company_runtime_children
|
|
await runtime.initialize()
|
|
company_executor = getattr(runtime, "company_executor", None)
|
|
if company_executor is not None:
|
|
callback_factory = getattr(self.parent, "on_company_kanban_callback_factory", None)
|
|
if callable(callback_factory):
|
|
company_executor.on_kanban_changed = callback_factory(runtime)
|
|
else:
|
|
parent_executor = getattr(self.parent, "company_executor", None)
|
|
company_executor.on_kanban_changed = getattr(parent_executor, "on_kanban_changed", None)
|
|
|
|
try:
|
|
normalized_attachment_refs = runtime._normalize_attachment_refs(attachment_refs)
|
|
metadata = {
|
|
"mode": "company",
|
|
"exec_mode": "org",
|
|
"org_id": resolved_org_id,
|
|
"organization_id": resolved_org_id,
|
|
"organization_name": str(getattr(org_config.org, "organization_name", "") or "").strip(),
|
|
"organization_config_file": str(getattr(org_config.org, "organization_config_file", "") or "").strip(),
|
|
"preferred_agent": preferred_agent,
|
|
"domains": domains or [],
|
|
"company_profile": "custom",
|
|
"origin_task_id": origin_task_id,
|
|
"attachment_refs": normalized_attachment_refs,
|
|
}
|
|
if message_metadata:
|
|
metadata.update(dict(message_metadata))
|
|
metadata["mode"] = "company"
|
|
metadata["exec_mode"] = "org"
|
|
metadata["company_profile"] = "custom"
|
|
metadata["org_id"] = resolved_org_id
|
|
metadata["organization_id"] = resolved_org_id
|
|
|
|
message = UserMessage(
|
|
channel="cli",
|
|
user_id="owner",
|
|
content=content,
|
|
attachments=normalized_attachment_refs,
|
|
session_id=session_id or str(uuid.uuid4()),
|
|
project_context=normalized_project_id,
|
|
metadata=metadata,
|
|
)
|
|
response = await runtime.message_bus.process_single(message)
|
|
return response.content if response else "No response generated."
|
|
finally:
|
|
company_executor = getattr(runtime, "company_executor", None)
|
|
wake = getattr(company_executor, "_signal_dispatcher_wake", None)
|
|
if wake is not None:
|
|
unregister_dispatcher_wake(wake)
|
|
await runtime.shutdown()
|