fix: unify company runtime recovery lifecycle

This commit is contained in:
LZH-YS1998
2026-07-14 14:35:43 +08:00
parent 5e02364eb4
commit b8202bbe9e
56 changed files with 8753 additions and 3542 deletions
+10 -1
View File
@@ -32,4 +32,13 @@ class ServiceError(Exception):
self.payload = dict(payload or {})
def to_payload(self) -> dict[str, Any]:
return {"error": self.message, "code": self.code, **self.payload}
# Transport envelope fields are authoritative. Business details may
# add context, but must never turn an error acknowledgement into a
# success or replace the exception's code/message.
payload = {
key: value
for key, value in self.payload.items()
if key not in {"ok", "error", "code"}
}
payload.update({"error": self.message, "code": self.code})
return payload
-45
View File
@@ -200,48 +200,3 @@ class RuntimeService:
payload["display_text"] = " | ".join(display_parts)
payload["event_type"] = event_type
return payload
async def recovery_scan(self, *, project_id: str) -> ServiceResult:
manager = await self._recovery_manager(project_id)
from opc.plugins.office_ui.recovery_manager import _serialize_status
status = await manager.get_recovery_status()
return ServiceResult(_serialize_status(status, project_id=project_id))
async def recovery_action(self, *, project_id: str, action: str, parent_task_id: str) -> ServiceResult:
manager = await self._recovery_manager(project_id)
normalized = str(action or "").strip().lower()
if normalized == "scan":
return await self.recovery_scan(project_id=project_id)
if not str(parent_task_id or "").strip():
raise ServiceError("parent_task_id_required", "parent_task_id required")
if normalized in {"resume", "retry"}:
payload = await manager.resume(parent_task_id)
elif normalized == "cancel":
payload = await manager.cancel(parent_task_id)
else:
raise ServiceError("unknown_recovery_action", f"unknown action: {action}", {"action": action})
payload = {**dict(payload), "project_id": project_id, "parent_task_id": parent_task_id, "action": normalized}
if not payload.get("ok", False):
raise ServiceError(str(payload.get("error") or "recovery_failed"), str(payload.get("error") or "recovery_failed"), payload)
return ServiceResult(payload)
async def _recovery_manager(self, project_id: str) -> Any:
engine = await self.context.engine_for_project(project_id)
async def _noop_broadcast(_event: dict[str, Any]) -> None:
return None
managers = getattr(self.context, "recovery_managers", None)
if managers is None:
managers = {}
setattr(self.context, "recovery_managers", managers)
key = self.context.normalize_project_id(project_id)
existing = managers.get(key)
if existing is not None and getattr(existing, "_engine", None) is engine:
return existing
from opc.plugins.office_ui.recovery_manager import RuntimeRecoveryManager
manager = RuntimeRecoveryManager(engine, _noop_broadcast)
managers[key] = manager
return manager
+305 -182
View File
@@ -12,6 +12,12 @@ from typing import Any
from loguru import logger
from opc.core.models import Task, TaskStatus
from opc.layer2_organization.company_runtime_identity import (
ACTIVE_COMPANY_RUNTIME_CHECKPOINT_STATUSES,
COMPANY_RUNTIME_CHECKPOINT_TYPES,
is_company_runtime_task,
load_company_runtime_identity_index,
)
from opc.plugins.office_ui.execution_identity import (
ExecutionIdentity,
canonicalize_execution_identity,
@@ -156,6 +162,32 @@ class SessionService:
task_project = self.context.normalize_project_id(getattr(task, "project_id", None))
if task_project != pid:
raise ServiceError("target_wrong_project", "Target belongs to a different project", {"project_id": task_project})
# Company control is resolved from durable session/checkpoint scope
# before any generic Task/session fallback. This prevents a shared
# final-decider row from winning merely because it was loaded first.
identity_index = await load_company_runtime_identity_index(store, pid)
if task is not None:
identity = identity_index.resolve(task_id=raw_target)
else:
identity = (
identity_index.resolve(runtime_session_id=raw_target)
or identity_index.resolve(task_session_id=raw_target)
)
if identity is not None:
# A concrete Task id is the caller's UI/chat channel, never the
# company execution identity. Keep that channel stable while the
# shared identity supplies the durable runtime session/checkpoint.
if task is not None:
return task, identity.runtime_session_id
resolved_task = (
identity_index.task(identity.ui_anchor_task_id)
or identity_index.task(identity.config_source_task_id)
)
if resolved_task is not None:
return resolved_task, identity.runtime_session_id
if task is not None:
return task, str(getattr(task, "session_id", "") or getattr(task, "parent_session_id", "") or "")
session = await store.get_session(raw_target) if hasattr(store, "get_session") else None
@@ -165,139 +197,64 @@ class SessionService:
if session_project != pid:
raise ServiceError("target_wrong_project", "Target belongs to a different project", {"project_id": session_project})
tasks = await store.get_tasks(project_id=pid) if hasattr(store, "get_tasks") else []
tasks = list(identity_index.tasks)
session_tasks = [
candidate for candidate in tasks
if str(getattr(candidate, "session_id", "") or "") == raw_target
]
if not session_tasks:
raise ServiceError("session_not_task_backed", "Session is not linked to a task-backed runtime", {"session_id": raw_target})
session_tasks.sort(key=lambda item: bool(str(getattr(item, "parent_session_id", "") or "")))
return session_tasks[0], raw_target
task_mode_anchor = min(
session_tasks,
key=lambda item: (
bool(str(getattr(item, "parent_session_id", "") or "")),
str(getattr(item, "created_at", "") or ""),
str(getattr(item, "id", "") or ""),
),
)
return task_mode_anchor, raw_target
async def _resolve_company_runtime_target(self, engine: Any, task: Any) -> dict[str, Any]:
store = getattr(engine, "store", None)
parent_session_id = str(
getattr(task, "parent_session_id", "")
or getattr(task, "session_id", "")
or ""
).strip()
parent_task_id = str(self.context.session_to_task.get(parent_session_id) or "").strip()
project_id = self.context.normalize_project_id(getattr(task, "project_id", None) or getattr(engine, "project_id", None))
try:
project_tasks = await store.get_tasks(project_id=project_id) if hasattr(store, "get_tasks") else [task]
except Exception:
project_tasks = [task]
for candidate in project_tasks:
candidate_id = str(getattr(candidate, "id", "") or "").strip()
candidate_session_id = str(getattr(candidate, "session_id", "") or "").strip()
candidate_parent_session_id = str(getattr(candidate, "parent_session_id", "") or "").strip()
if candidate_session_id == parent_session_id and not candidate_parent_session_id:
parent_task_id = candidate_id
break
if not parent_task_id:
parent_task_id = str(
self.context.active_runtime_children.get(str(getattr(task, "id", "") or ""))
or getattr(task, "id", "")
or ""
).strip()
affected_task_ids: list[str] = []
for candidate in project_tasks:
candidate_id = str(getattr(candidate, "id", "") or "").strip()
if not candidate_id:
continue
candidate_session_id = str(getattr(candidate, "session_id", "") or "").strip()
candidate_parent_session_id = str(getattr(candidate, "parent_session_id", "") or "").strip()
if (
candidate_id == str(getattr(task, "id", "") or "")
or candidate_id == parent_task_id
or candidate_session_id == parent_session_id
or candidate_parent_session_id == parent_session_id
):
if candidate_id not in affected_task_ids:
affected_task_ids.append(candidate_id)
for child_id, origin_id in list(self.context.active_runtime_children.items()):
if origin_id == parent_task_id or child_id == str(getattr(task, "id", "") or ""):
if child_id not in affected_task_ids:
affected_task_ids.append(child_id)
if parent_task_id and parent_task_id not in affected_task_ids:
affected_task_ids.insert(0, parent_task_id)
return {
"parent_session_id": parent_session_id,
"parent_task_id": parent_task_id or str(getattr(task, "id", "") or ""),
"origin_task_id": parent_task_id or str(getattr(task, "id", "") or ""),
"affected_task_ids": affected_task_ids or [str(getattr(task, "id", "") or "")],
}
async def _mark_company_runtime_stop_state(
async def _resolve_company_runtime_target(
self,
*,
engine: Any,
task_ids: list[str],
state: str,
stop_intent_id: str,
checkpoint_type: str = "",
) -> None:
task: Any,
*,
runtime_session_id: str = "",
checkpoint_id: str = "",
) -> dict[str, Any]:
store = getattr(engine, "store", None)
project_id = self.context.normalize_project_id(getattr(task, "project_id", None) or getattr(engine, "project_id", None))
if not self.context.store_is_ready(store):
return
for task_id in task_ids:
try:
task = await store.get_task(str(task_id))
except Exception:
task = None
if not task or self._is_terminal_status(task):
continue
metadata = dict(getattr(task, "metadata", {}) or {})
metadata["company_runtime_stop_state"] = state
metadata["company_runtime_stop_intent_id"] = stop_intent_id
metadata["company_runtime_stop_marked_at"] = datetime.now().isoformat()
metadata["dispatch_hold"] = "company_runtime_suspended"
metadata["company_runtime_suspended_at"] = datetime.now().isoformat()
if checkpoint_type:
metadata["company_runtime_suspend_checkpoint_type"] = checkpoint_type
metadata.setdefault("suspended_task_status", self._task_status_value(task))
task.metadata = metadata
task.status = TaskStatus.BLOCKED
if hasattr(task, "execution_lock"):
task.execution_lock = False
if hasattr(task, "execution_locked_at"):
task.execution_locked_at = None
try:
await store.save_task(task)
except Exception:
logger.opt(exception=True).debug("failed to mark company runtime stop state")
async def _clear_company_runtime_stop_state(self, *, engine: Any, task_ids: list[str]) -> None:
store = getattr(engine, "store", None)
if not self.context.store_is_ready(store):
return
for task_id in task_ids:
try:
task = await store.get_task(str(task_id))
except Exception:
task = None
if not task:
continue
metadata = dict(getattr(task, "metadata", {}) or {})
for key in (
"dispatch_hold",
"company_runtime_stop_state",
"company_runtime_stop_intent_id",
"company_runtime_stop_marked_at",
"company_runtime_suspend_checkpoint_type",
"company_runtime_suspended_at",
"suspended_task_status",
):
metadata.pop(key, None)
task.metadata = metadata
if self._task_status_value(task) == "blocked":
task.status = TaskStatus.IDLE
try:
await store.save_task(task)
except Exception:
logger.opt(exception=True).debug("failed to clear company runtime stop state")
raise ServiceError("store_not_ready", "store_not_ready", {"project_id": project_id})
index = await load_company_runtime_identity_index(store, project_id)
identity = index.resolve(
task_id=str(getattr(task, "id", "") or ""),
runtime_session_id=runtime_session_id,
checkpoint_id=checkpoint_id,
)
if identity is None:
raise ServiceError(
"company_runtime_identity_mismatch",
"Company runtime identity does not match the requested task/session/checkpoint",
{
"task_id": str(getattr(task, "id", "") or ""),
"runtime_session_id": str(runtime_session_id or ""),
"checkpoint_id": str(checkpoint_id or ""),
},
)
config_task = index.task(identity.config_source_task_id) or task
ui_anchor_task_id = identity.ui_anchor_task_id
return {
"identity": identity,
"runtime_session_id": identity.runtime_session_id,
"ui_channel_task_id": str(getattr(task, "id", "") or ""),
"ui_anchor_task_id": ui_anchor_task_id,
"config_source_task_id": identity.config_source_task_id,
"config_task": config_task,
"origin_task_id": ui_anchor_task_id,
"affected_task_ids": list(identity.runtime_task_ids),
"checkpoint": identity.checkpoint,
}
def _normalize_requested_config(
self,
@@ -743,8 +700,54 @@ class SessionService:
if getattr(engine, "memory", None):
await engine.memory.ensure_session(task.session_id, project_id=project_id, title=task.title, mode="primary", metadata={"source": "service"})
await store.save_task(task)
# A Task selected by the UI/CLI is only the chat channel for company
# mode. Resolve the durable runtime scope before choosing execution
# configuration, session, origin, or checkpoint. In particular, a
# role/work-item Task must never become the parent execution identity.
company_target: dict[str, Any] | None = None
task_is_company_runtime = is_company_runtime_task(task)
try:
company_target = await self._resolve_company_runtime_target(engine, task)
except ServiceError as exc:
if exc.code != "company_runtime_identity_mismatch" or task_is_company_runtime:
raise
if task.status == TaskStatus.CANCELLED:
company_identity = (
company_target.get("identity")
if company_target is not None
else None
)
checkpoint = (
company_target.get("checkpoint")
if company_target is not None
else None
)
active_cancelled_anchor = bool(
company_identity is not None
and str(getattr(company_identity, "ui_anchor_task_id", "") or "").strip()
== str(getattr(task, "id", "") or "").strip()
and checkpoint is not None
and str(getattr(checkpoint, "checkpoint_type", "") or "").strip()
in COMPANY_RUNTIME_CHECKPOINT_TYPES
and str(getattr(checkpoint, "status", "") or "").strip().lower()
in ACTIVE_COMPANY_RUNTIME_CHECKPOINT_STATUSES
)
if not active_cancelled_anchor:
raise ServiceError(
"session_ended",
"session_ended",
{"project_id": project_id, "task_id": task.id},
)
config_task = (
company_target.get("config_task")
if company_target is not None
else task
) or task
identity = self.resolve_task_identity(
task,
config_task,
default_exec_mode=mode,
default_company_profile=company_profile if company_profile is not None else "corporate",
default_preferred_agent=preferred_agent if preferred_agent is not None else "native",
@@ -752,26 +755,85 @@ class SessionService:
)
if identity.is_custom_org and not identity.org_id:
raise ServiceError("org_id_required", "org_id_required", {"project_id": project_id, "task_id": task.id})
await self.persist_session_config(
task,
exec_mode=identity.exec_mode,
company_profile=identity.company_profile,
preferred_agent=identity.preferred_agent,
org_id=identity.org_id,
engine=engine,
)
# Existing company scopes read configuration from the resolver's
# config source. Only persist when that source is the selected Task;
# this preserves normal session configuration while avoiding writes to
# an internal work item merely because it was used as the UI channel.
if (
company_target is None
or str(getattr(config_task, "id", "") or "").strip()
== str(getattr(task, "id", "") or "").strip()
):
await self.persist_session_config(
task,
exec_mode=identity.exec_mode,
company_profile=identity.company_profile,
preferred_agent=identity.preferred_agent,
org_id=identity.org_id,
engine=engine,
)
execution_session_id = str(getattr(task, "session_id", "") or "").strip()
origin_task_id = str(getattr(task, "id", "") or "").strip() or None
message_metadata: dict[str, Any] | None = None
if company_target is not None:
execution_session_id = str(
company_target.get("runtime_session_id", "") or ""
).strip()
origin_task_id = str(
company_target.get("ui_anchor_task_id", "") or ""
).strip() or None
if not execution_session_id:
raise ServiceError(
"company_runtime_identity_mismatch",
"Company runtime has no canonical runtime session",
{"project_id": project_id, "task_id": task.id},
)
checkpoint = company_target.get("checkpoint")
if checkpoint is not None:
checkpoint_id = str(
getattr(checkpoint, "checkpoint_id", "") or ""
).strip()
checkpoint_status = str(
getattr(checkpoint, "status", "") or ""
).strip().lower()
if checkpoint_status != "pending":
raise ServiceError(
"company_runtime_checkpoint_not_pending",
"Company runtime checkpoint is not pending",
{
"project_id": project_id,
"task_id": task.id,
"checkpoint_id": checkpoint_id,
"checkpoint_status": checkpoint_status,
},
)
message_metadata = {
"response_to_checkpoint_id": checkpoint_id,
"response_to_checkpoint_type": str(
getattr(checkpoint, "checkpoint_type", "") or ""
).strip(),
}
response = await engine.process_message(
str(content or "").strip(),
project_id=project_id,
session_id=task.session_id,
session_id=execution_session_id,
mode=identity.exec_mode,
org_id=identity.org_id or None,
company_profile=identity.company_profile if identity.is_company_runtime else None,
preferred_agent=identity.preferred_agent if identity.is_task else None,
domains=list(domains or []),
origin_task_id=task.id,
origin_task_id=origin_task_id,
message_metadata=message_metadata,
)
return ServiceResult({"project_id": project_id, "task_id": task.id, "session_id": task.session_id, "response": response})
return ServiceResult({
"project_id": project_id,
"task_id": task.id,
"session_id": execution_session_id,
"response": response,
})
async def rename(self, *, project_id: str, task_id: str = "", session_id: str = "", title: str) -> ServiceResult:
pid = self.context.normalize_project_id(project_id)
@@ -935,50 +997,44 @@ class SessionService:
default_payload=default_payload,
)
engine = await self.context.engine_for_project(project_id)
exec_mode, _company_profile = self.resolve_task_session_config(task)
if exec_mode in {"company", "org", "custom"}:
try:
target_info = await self._resolve_company_runtime_target(engine, task)
except ServiceError as exc:
if exc.code != "company_runtime_identity_mismatch":
raise
target_info = None
if target_info is None and is_company_runtime_task(task):
raise ServiceError(
"company_runtime_identity_mismatch",
"Company runtime identity could not be resolved; refusing task-mode cancellation",
{"task_id": resolved_task_id, "session_id": resolved_session_id},
)
if target_info is not None:
stop_intent_id = str(uuid.uuid4())
affected_task_ids = list(target_info.get("affected_task_ids", []) or [resolved_task_id])
suspended: dict[str, Any] | None = None
suspend = getattr(engine, "suspend_company_runtime", None)
await self._mark_company_runtime_stop_state(
engine=engine,
task_ids=affected_task_ids,
state="suspending",
stop_intent_id=stop_intent_id,
)
if callable(suspend):
try:
suspended = await suspend(
origin_task_id=str(target_info.get("origin_task_id", "") or resolved_task_id),
session_id=(str(target_info.get("parent_session_id", "") or resolved_session_id).strip() or None),
session_id=(str(target_info.get("runtime_session_id", "") or resolved_session_id).strip() or None),
reason="user_stop",
checkpoint_type="company_runtime_suspended",
stop_intent_id=stop_intent_id,
)
except Exception:
logger.opt(exception=True).warning("suspend_company_runtime failed during service stop")
if suspended is not None:
for candidate in list(suspended.get("task_ids", []) or []):
candidate_id = str(candidate or "").strip()
if candidate_id and candidate_id not in affected_task_ids:
affected_task_ids.append(candidate_id)
await self._mark_company_runtime_stop_state(
engine=engine,
task_ids=affected_task_ids,
state="suspended",
stop_intent_id=stop_intent_id,
checkpoint_type=str(suspended.get("checkpoint_type", "") or "company_runtime_suspended"),
)
else:
await self._mark_company_runtime_stop_state(
engine=engine,
task_ids=affected_task_ids,
state="suspended",
stop_intent_id=stop_intent_id,
checkpoint_type="company_runtime_suspended",
if suspended is None:
raise ServiceError(
"company_runtime_suspend_failed",
"Company runtime could not be suspended",
{"runtime_session_id": target_info.get("runtime_session_id", "")},
)
for candidate in list(suspended.get("task_ids", []) or []):
candidate_id = str(candidate or "").strip()
if candidate_id and candidate_id not in affected_task_ids:
affected_task_ids.append(candidate_id)
self.context.stop_requested_task_ids.update(affected_task_ids)
if self.context.cancel_session_tasks is not None:
for tid in affected_task_ids:
@@ -1009,8 +1065,7 @@ class SessionService:
"stop_intent_id": stop_intent_id,
"checkpoint_id": str((suspended or {}).get("checkpoint_id", "") or ""),
"task_ids": affected_task_ids,
"resume_parent_task_id": str(target_info.get("parent_task_id", "") or resolved_task_id),
"resume_parent_session_id": str(target_info.get("parent_session_id", "") or resolved_session_id),
"resume_parent_session_id": str(target_info.get("runtime_session_id", "") or resolved_session_id),
}
return ServiceResult(payload, [ServiceEvent("session_runtime_control", payload), ServiceEvent("session_updated", payload)])
@@ -1039,6 +1094,8 @@ class SessionService:
project_id: str,
task_id: str = "",
session_id: str = "",
runtime_session_id: str = "",
checkpoint_id: str = "",
target: str = "",
content: str = "",
) -> ServiceResult:
@@ -1066,45 +1123,111 @@ class SessionService:
default_payload=default_payload,
)
engine = await self.context.engine_for_project(project_id)
exec_mode, company_profile = self.resolve_task_session_config(task)
target_info = await self._resolve_company_runtime_target(engine, task) if exec_mode in {"company", "org", "custom"} else {
try:
company_target = await self._resolve_company_runtime_target(
engine,
task,
runtime_session_id=runtime_session_id,
checkpoint_id=checkpoint_id,
)
except ServiceError as exc:
if exc.code != "company_runtime_identity_mismatch" or runtime_session_id or checkpoint_id:
raise
if is_company_runtime_task(task):
raise
company_target = None
target_info = company_target or {
"affected_task_ids": [resolved_task_id],
"parent_task_id": resolved_task_id,
"parent_session_id": resolved_session_id,
"ui_anchor_task_id": resolved_task_id,
"runtime_session_id": resolved_session_id,
"config_task": task,
"checkpoint": None,
}
affected_task_ids = list(target_info.get("affected_task_ids", []) or [resolved_task_id])
await self._clear_company_runtime_stop_state(engine=engine, task_ids=affected_task_ids)
message = str(content or "").strip() or "Resume the existing runtime."
engine_mode = "company" if exec_mode == "company" else ("org" if exec_mode in {"org", "custom"} else "task")
org_id = self.resolve_task_org_id(task) if engine_mode == "org" else ""
config_task = target_info.get("config_task") or task
config_exec_mode, config_company_profile = self.resolve_task_session_config(config_task)
engine_mode = "company" if config_exec_mode == "company" else (
"org" if config_exec_mode in {"org", "custom"} else "task"
)
if engine_mode in {"company", "org"}:
checkpoint = target_info.get("checkpoint")
if checkpoint is None:
raise ServiceError("company_runtime_checkpoint_not_found", "No active company runtime checkpoint")
if str(getattr(checkpoint, "status", "") or "").strip().lower() != "pending":
raise ServiceError(
"company_runtime_checkpoint_not_pending",
"Company runtime checkpoint is not pending",
{"checkpoint_id": str(getattr(checkpoint, "checkpoint_id", "") or "")},
)
else:
checkpoint = None
org_id = self.resolve_task_org_id(config_task) if engine_mode == "org" else ""
message_metadata: dict[str, Any] = {"ui_force_resume": True}
if checkpoint is not None:
message_metadata.update({
"response_to_checkpoint_id": str(getattr(checkpoint, "checkpoint_id", "") or ""),
"response_to_checkpoint_type": str(getattr(checkpoint, "checkpoint_type", "") or ""),
})
response = await engine.process_message(
message,
project_id=self.context.normalize_project_id(project_id),
session_id=str(target_info.get("parent_session_id", "") or resolved_session_id),
session_id=str(target_info.get("runtime_session_id", "") or resolved_session_id),
mode=engine_mode,
org_id=org_id or None,
company_profile=company_profile if engine_mode == "company" else None,
preferred_agent=self.resolve_task_preferred_agent(task) if engine_mode == "task" else None,
origin_task_id=str(target_info.get("parent_task_id", "") or resolved_task_id),
message_metadata={"ui_force_resume": True},
company_profile=config_company_profile if engine_mode == "company" else None,
preferred_agent=self.resolve_task_preferred_agent(config_task) if engine_mode == "task" else None,
origin_task_id=(str(target_info.get("ui_anchor_task_id", "") or "").strip() or None),
message_metadata=message_metadata,
)
runtime_control_state = "idle"
pending_runtime_checkpoint_id = ""
if engine_mode in {"company", "org"}:
refreshed_index = await load_company_runtime_identity_index(
engine.store,
self.context.normalize_project_id(project_id),
)
refreshed_identity = refreshed_index.resolve(
runtime_session_id=str(
target_info.get("runtime_session_id", "") or resolved_session_id
),
)
if refreshed_identity is not None and refreshed_identity.pending_checkpoint_id:
pending_runtime_checkpoint_id = refreshed_identity.pending_checkpoint_id
runtime_control_state = (
"suspended"
if refreshed_identity.pending_checkpoint_status == "pending"
else "resuming"
)
payload = {
**default_payload,
"status": "resuming",
"runtime_control_state": "resuming",
"can_resume": False,
"status": runtime_control_state,
"runtime_control_state": runtime_control_state,
"can_resume": runtime_control_state == "suspended",
"response": response,
"task_ids": affected_task_ids,
"resume_parent_task_id": str(target_info.get("parent_task_id", "") or resolved_task_id),
"resume_parent_session_id": str(target_info.get("parent_session_id", "") or resolved_session_id),
"resume_parent_session_id": str(target_info.get("runtime_session_id", "") or resolved_session_id),
"pending_runtime_checkpoint_id": pending_runtime_checkpoint_id,
}
return ServiceResult(payload, [ServiceEvent("session_runtime_control", payload), ServiceEvent("session_updated", payload)])
async def resume(self, *, project_id: str, task_id: str = "", session_id: str = "", target: str = "", content: str = "") -> ServiceResult:
async def resume(
self,
*,
project_id: str,
task_id: str = "",
session_id: str = "",
runtime_session_id: str = "",
checkpoint_id: str = "",
target: str = "",
content: str = "",
) -> ServiceResult:
return await self.continue_run(
project_id=project_id,
task_id=task_id,
session_id=session_id,
runtime_session_id=runtime_session_id,
checkpoint_id=checkpoint_id,
target=target,
content=content,
)