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
+256 -232
View File
@@ -2118,33 +2118,6 @@ def runtime_run(task_id: str = typer.Argument(...), project: Optional[str] = typ
asyncio.run(_run_service_command(project, lambda svc: svc.runtime.run_task(project_id=project or "default", task_id=task_id), json_output=json_output))
recovery_app = typer.Typer(help="Recover interrupted company runtimes")
app.add_typer(recovery_app, name="recovery")
@recovery_app.command("scan")
def recovery_scan(project: Optional[str] = typer.Option(None, "--project", "-p"), json_output: bool = typer.Option(False, "--json")):
asyncio.run(_run_service_command(project, lambda svc: svc.runtime.recovery_scan(project_id=project or "default"), json_output=json_output))
@recovery_app.command("resume")
def recovery_resume(parent_task_id: str = typer.Argument(...), project: Optional[str] = typer.Option(None, "--project", "-p"), json_output: bool = typer.Option(False, "--json")):
asyncio.run(_run_service_command(project, lambda svc: svc.runtime.recovery_action(project_id=project or "default", action="resume", parent_task_id=parent_task_id), json_output=json_output))
@recovery_app.command("cancel")
def recovery_cancel(parent_task_id: str = typer.Argument(...), yes: bool = typer.Option(False, "--yes", "-y"), project: Optional[str] = typer.Option(None, "--project", "-p"), json_output: bool = typer.Option(False, "--json")):
if not yes:
console.print("[warning]Destructive command requires --yes.[/warning]")
raise typer.Exit(code=1)
asyncio.run(_run_service_command(project, lambda svc: svc.runtime.recovery_action(project_id=project or "default", action="cancel", parent_task_id=parent_task_id), json_output=json_output))
@recovery_app.command("retry")
def recovery_retry(parent_task_id: str = typer.Argument(...), project: Optional[str] = typer.Option(None, "--project", "-p"), json_output: bool = typer.Option(False, "--json")):
asyncio.run(_run_service_command(project, lambda svc: svc.runtime.recovery_action(project_id=project or "default", action="retry", parent_task_id=parent_task_id), json_output=json_output))
comms_app = typer.Typer(help="Inspect company-mode comms")
app.add_typer(comms_app, name="comms")
@@ -2834,6 +2807,17 @@ class ChatTurnController:
self._closing = True
self.queue.clear()
await self.stop_kanban_watch(silent=True)
prepare = getattr(
self.state.engine,
"prepare_active_company_runtimes_for_shutdown",
None,
)
if callable(prepare):
# Persist company checkpoints and put the engine into
# infrastructure-shutdown mode before cancelling the active turn.
# Engine.shutdown() repeats this idempotently when it closes the
# remaining stores and subsystems.
await prepare()
task = self.active_task
if task is None or task.done():
return
@@ -2994,8 +2978,6 @@ _SLASH_COMMANDS: tuple[_SlashCommandSpec, ...] = (
_SlashCommandSpec("Tasks", "/task rename <task_id> <title>", "Rename a task and its session title."),
_SlashCommandSpec("Tasks", "/task delete <task_id> --yes", "Hard-delete a task and its persisted lifecycle data."),
_SlashCommandSpec("Runtime", "/runtime [--limit N] [--full]", "Show live runtime, active tasks, external sessions, and checkpoints."),
_SlashCommandSpec("Runtime", "/recover [--limit N] [--full]", "Show interrupted runtime and resumable checkpoints.", ("scan", "resume", "cancel", "retry")),
_SlashCommandSpec("Runtime", "/recover resume|cancel|retry <parent_task_id>", "Act on an interrupted company runtime."),
_SlashCommandSpec("Runtime", "/logs <task_id|session_id> [--limit N] [--full]", "Show execution logs, runtime events, tools, and transcript."),
_SlashCommandSpec("Runtime", "/comms <task_id> [--limit N] [--full]", "Show company-mode messages, handoffs, and review notes."),
_SlashCommandSpec("Runtime", "/attachments [--limit N] [--full]", "List current session attachment references."),
@@ -3027,7 +3009,7 @@ _SLASH_COMMANDS: tuple[_SlashCommandSpec, ...] = (
_SlashCommandSpec("Diagnostics", "/checkpoints [--limit N] [--full]", "List pending execution checkpoints."),
)
CommandSpec = _SlashCommandSpec
_SLASH_ALIASES = {"p": "project", "s": "session", "t": "task", "checkpoint": "checkpoints", "recovery": "recover", "work-item": "work-items", "workitems": "work-items"}
_SLASH_ALIASES = {"p": "project", "s": "session", "t": "task", "checkpoint": "checkpoints", "work-item": "work-items", "workitems": "work-items"}
def _initial_company_profile(config: OPCConfig) -> str:
@@ -3862,6 +3844,8 @@ async def _resolve_session_or_task(state: _InteractiveChatState, token: str) ->
async def _resolve_runtime_control_target(state: _InteractiveChatState, target: str = "") -> tuple[str, str]:
from opc.layer2_organization.company_runtime_identity import load_company_runtime_identity_index
store = _require_chat_store(state, label="Session store")
if store is None:
return "", ""
@@ -3869,24 +3853,38 @@ async def _resolve_runtime_control_target(state: _InteractiveChatState, target:
if not raw_target:
console.print("[warning]No current session. Use /session list or /session create first.[/warning]")
return "", ""
project_id = _current_project_id(state.engine)
identity_index = await load_company_runtime_identity_index(store, project_id)
task = await store.get_task(raw_target) if hasattr(store, "get_task") else None
if task is not None:
project_id = str(getattr(task, "project_id", "") or "default")
if project_id != _current_project_id(state.engine):
console.print(f"[warning]Target belongs to project '{project_id}'. Switch project first.[/warning]")
task_project_id = str(getattr(task, "project_id", "") or "default")
if task_project_id != project_id:
console.print(f"[warning]Target belongs to project '{task_project_id}'. Switch project first.[/warning]")
return "", ""
runtime_identity = identity_index.resolve(task_id=raw_target)
if runtime_identity is not None:
return raw_target, runtime_identity.runtime_session_id
return str(getattr(task, "id", "") or ""), str(getattr(task, "session_id", "") or getattr(task, "parent_session_id", "") or "")
runtime_identity = (
identity_index.resolve(runtime_session_id=raw_target)
or identity_index.resolve(task_session_id=raw_target)
)
if runtime_identity is not None:
control_task_id = (
runtime_identity.ui_anchor_task_id
or runtime_identity.config_source_task_id
)
if control_task_id:
return control_task_id, runtime_identity.runtime_session_id
session = await store.get_session(raw_target) if hasattr(store, "get_session") else None
if session is None:
console.print(f"[warning]Task or session not found: {raw_target}[/warning]")
return "", ""
project_id = str(getattr(session, "project_id", "") or "default")
if project_id != _current_project_id(state.engine):
console.print(f"[warning]Target belongs to project '{project_id}'. Switch project first.[/warning]")
session_project_id = str(getattr(session, "project_id", "") or "default")
if session_project_id != project_id:
console.print(f"[warning]Target belongs to project '{session_project_id}'. Switch project first.[/warning]")
return "", ""
if raw_target in state.session_to_task:
return state.session_to_task[raw_target], raw_target
tasks = await store.get_tasks(project_id=project_id) if hasattr(store, "get_tasks") else []
tasks = list(identity_index.tasks)
candidates = [
item for item in tasks
if str(getattr(item, "session_id", "") or "") == raw_target
@@ -3894,8 +3892,15 @@ async def _resolve_runtime_control_target(state: _InteractiveChatState, target:
if not candidates:
console.print(f"[warning]Session is not task-backed: {raw_target}[/warning]")
return "", raw_target
candidates.sort(key=lambda item: bool(str(getattr(item, "parent_session_id", "") or "")))
return str(getattr(candidates[0], "id", "") or ""), raw_target
task_mode_anchor = min(
candidates,
key=lambda item: (
bool(str(getattr(item, "parent_session_id", "") or "")),
str(getattr(item, "created_at", "") or ""),
str(getattr(item, "id", "") or ""),
),
)
return str(getattr(task_mode_anchor, "id", "") or ""), raw_target
def _make_cli_runtime_control_context(state: _InteractiveChatState, controller: ChatTurnController | None = None) -> Any:
@@ -3923,35 +3928,66 @@ def _make_cli_runtime_control_context(state: _InteractiveChatState, controller:
return context
async def _latest_company_suspend_checkpoint(state: _InteractiveChatState) -> Any | None:
session_id = str(state.session_id or "").strip()
if not session_id:
return None
for name in ("get_active_company_runtime_suspend_checkpoint", "get_pending_company_runtime_suspend_checkpoint"):
getter = getattr(state.engine, name, None)
if callable(getter):
try:
checkpoint = await getter(session_id)
except Exception:
checkpoint = None
if checkpoint is not None and str(getattr(checkpoint, "status", "") or "pending") == "pending":
return checkpoint
async def _company_runtime_identity_for_session(
state: _InteractiveChatState,
session_id: str | None = None,
) -> Any | None:
from opc.layer2_organization.company_runtime_identity import (
load_company_runtime_identity_index,
)
runtime_session_id = str(session_id or state.session_id or "").strip()
store = getattr(state.engine, "store", None)
if store is not None and hasattr(store, "get_pending_checkpoints"):
try:
checkpoints = await store.get_pending_checkpoints(
project_id=_current_project_id(state.engine),
session_id=session_id,
checkpoint_types=["company_runtime_suspended", "company_runtime_interrupted"],
)
except TypeError:
checkpoints = await store.get_pending_checkpoints(project_id=_current_project_id(state.engine), session_id=session_id)
except Exception:
checkpoints = []
for checkpoint in list(checkpoints or []):
if str(getattr(checkpoint, "checkpoint_type", "") or "") in {"company_runtime_suspended", "company_runtime_interrupted"}:
return checkpoint
return None
if not runtime_session_id or store is None:
return None
try:
identity_index = await load_company_runtime_identity_index(
store,
_current_project_id(state.engine),
)
# The interactive session may be a role/work-item session. Resolve it
# as a task session after trying the canonical root session so both UI
# roots and child channels converge on the same company runtime.
identity = (
identity_index.resolve(runtime_session_id=runtime_session_id)
or identity_index.resolve(task_session_id=runtime_session_id)
)
except Exception:
return None
return identity
async def _latest_company_suspend_checkpoint(
state: _InteractiveChatState,
session_id: str | None = None,
) -> Any | None:
identity = await _company_runtime_identity_for_session(state, session_id)
return identity.checkpoint if identity is not None and identity.resumable else None
async def _company_runtime_execution_identity(
state: _InteractiveChatState,
runtime_identity: Any,
) -> Any:
"""Read execution configuration from the durable runtime config source."""
from opc.plugins.office_ui.execution_identity import execution_identity_from_task
config_source_task_id = str(
getattr(runtime_identity, "config_source_task_id", "") or ""
).strip()
store = getattr(state.engine, "store", None)
if not config_source_task_id or store is None or not hasattr(store, "get_task"):
raise RuntimeError("Company runtime has no durable configuration source.")
config_task = await store.get_task(config_source_task_id)
if config_task is None:
raise RuntimeError("Company runtime configuration source no longer exists.")
return execution_identity_from_task(
config_task,
default_exec_mode=state.mode,
default_company_profile=state.company_profile,
default_preferred_agent=state.preferred_agent or "native",
default_org_id=state.org_id,
)
async def _handle_stop_slash(state: _InteractiveChatState, args: list[str], controller: ChatTurnController | None = None) -> None:
@@ -3972,22 +4008,35 @@ async def _handle_stop_slash(state: _InteractiveChatState, args: list[str], cont
return
payload = dict(result.payload)
state.runtime_control_state = str(payload.get("runtime_control_state") or payload.get("status") or "stopped")
state.runtime_control_task_id = str(payload.get("resume_parent_task_id") or payload.get("task_id") or task_id)
state.runtime_control_task_id = str(payload.get("task_id") or task_id)
state.runtime_control_session_id = str(payload.get("resume_parent_session_id") or payload.get("session_id") or session_id)
state.runtime_control_checkpoint_id = str(payload.get("checkpoint_id") or "")
console.print("[success]Stopped.[/success] [dim]Send a message to revise, or /continue to resume.[/dim]")
async def _runtime_control_identity_for_task(state: _InteractiveChatState, task_id: str) -> Any:
async def _runtime_control_execution_identity(state: _InteractiveChatState, task_id: str) -> Any:
from opc.layer2_organization.company_runtime_identity import load_company_runtime_identity_index
from opc.plugins.office_ui.execution_identity import execution_identity_from_task
store = getattr(state.engine, "store", None)
task = None
if store is not None and hasattr(store, "get_task") and task_id:
if store is not None and task_id:
try:
task = await store.get_task(task_id)
identity_index = await load_company_runtime_identity_index(
store,
_current_project_id(state.engine),
)
runtime_identity = identity_index.resolve(task_id=task_id)
task = identity_index.task(
runtime_identity.config_source_task_id if runtime_identity is not None else task_id
)
except Exception:
task = None
if task is None and hasattr(store, "get_task"):
try:
task = await store.get_task(task_id)
except Exception:
task = None
return execution_identity_from_task(
task,
default_exec_mode=state.mode,
@@ -4014,13 +4063,24 @@ async def _handle_continue_slash(state: _InteractiveChatState, args: list[str],
task_id, session_id = await _resolve_runtime_control_target(state, target)
if not task_id:
return
identity = await _runtime_control_identity_for_task(state, task_id)
identity = await _runtime_control_execution_identity(state, task_id)
content = " ".join(message_parts).strip() or "Resume the existing runtime."
checkpoint = await _latest_company_suspend_checkpoint(state, session_id)
if identity.exec_mode in {"company", "org", "custom"} and checkpoint is None:
console.print(
"[warning]No suspended or interrupted company runtime is available to continue.[/warning]"
)
return
if controller is not None and controller.is_busy:
checkpoint = await _latest_company_suspend_checkpoint(state)
if checkpoint is None and state.runtime_control_state not in {"suspended", "stopped"}:
console.print("[warning]Busy: wait for the current turn or /stop it before /continue.[/warning]")
return
metadata: dict[str, Any] = {"ui_force_resume": True}
if checkpoint is not None:
metadata["response_to_checkpoint_id"] = str(getattr(checkpoint, "checkpoint_id", "") or "")
metadata["response_to_checkpoint_type"] = str(
getattr(checkpoint, "checkpoint_type", "") or "company_runtime_suspended"
)
item = QueuedChatInput(
text=content,
project_id=_current_project_id(state.engine),
@@ -4030,26 +4090,29 @@ async def _handle_continue_slash(state: _InteractiveChatState, args: list[str],
org_id=identity.org_id,
preferred_agent=identity.preferred_agent,
domains=list(state.domains),
message_metadata={"ui_force_resume": True},
message_metadata=metadata,
)
state.runtime_control_state = "resuming"
state.runtime_control_task_id = task_id
state.runtime_control_session_id = session_id or state.session_id
state.runtime_control_checkpoint_id = ""
state.runtime_control_checkpoint_id = str(getattr(checkpoint, "checkpoint_id", "") or "")
if controller is not None:
await controller.submit_item(item)
else:
previous_session_id = state.session_id
previous_mode = state.mode
previous_profile = state.company_profile
previous_org_id = state.org_id
previous_agent = state.preferred_agent
state.session_id = session_id or state.session_id
state.mode = identity.exec_mode
state.company_profile = identity.company_profile
state.org_id = identity.org_id
state.preferred_agent = identity.preferred_agent
try:
await _process_interactive_chat_message(state, content, message_metadata={"ui_force_resume": True})
await _process_interactive_chat_message(state, content, message_metadata=metadata)
finally:
state.session_id = previous_session_id
state.mode = previous_mode
state.company_profile = previous_profile
state.org_id = previous_org_id
@@ -5630,140 +5693,6 @@ async def _handle_runtime_slash(state: _InteractiveChatState, args: list[str]) -
_render_checkpoint_table(checkpoints[:limit], title="Pending Checkpoints", full=full)
class _ChatRecoveryFacade:
def __init__(self, state: _InteractiveChatState) -> None:
self._state = state
self.project_id = _current_project_id(state.engine)
@property
def store(self): # noqa: ANN201 - preserve recovery facade shape
return getattr(self._state.engine, "store", None)
@property
def opc_home(self) -> Path:
return Path(getattr(self._state.engine, "opc_home", get_opc_home()))
async def ensure_ready(self):
return self._state.engine
def _get_chat_recovery_manager(state: _InteractiveChatState) -> Any:
cached = getattr(state, "_cli_chat_recovery_manager", None)
engine_id = id(state.engine)
if cached and cached[0] == engine_id:
return cached[1]
from opc.plugins.cli_board.services.recovery import CliRecoveryManager
manager = CliRecoveryManager(_ChatRecoveryFacade(state))
setattr(state, "_cli_chat_recovery_manager", (engine_id, manager))
return manager
def _render_recovery_status(status: Any, *, limit: int, full: bool = False) -> None:
workflows = list(getattr(status, "interrupted", []) or [])[:limit]
if workflows:
table = Table(title=f"Interrupted Company Runtimes ({len(workflows)})")
table.add_column("Parent Task")
table.add_column("Parent Session")
table.add_column("Title")
table.add_column("Profile")
table.add_column("Interrupted")
table.add_column("Work Items")
for workflow in workflows:
interrupted_items = [
item for item in list(getattr(workflow, "work_items", []) or [])
if bool(getattr(item, "interrupted", False))
]
table.add_row(
str(getattr(workflow, "parent_task_id", "") or ""),
str(getattr(workflow, "parent_session_id", "") or ""),
_clip_text(getattr(workflow, "title", "") or "", 70, full=full),
str(getattr(workflow, "profile", "") or ""),
str(getattr(workflow, "interrupted_at", "") or ""),
f"{len(interrupted_items)}/{len(list(getattr(workflow, 'work_items', []) or []))}",
)
console.print(table)
else:
console.print("[info]No interrupted company runtimes found.[/info]")
active = list(getattr(status, "active_recoveries", []) or [])
if active:
console.print(f"[info]Active recoveries: {', '.join(active)}[/info]")
async def _find_checkpoint_by_id(store: Any, project_id: str, checkpoint_id: str) -> Any | None:
if hasattr(store, "get_execution_checkpoints"):
checkpoints = await store.get_execution_checkpoints(project_id=project_id)
else:
checkpoints = await store.get_pending_checkpoints(project_id=project_id)
return next((item for item in checkpoints if str(getattr(item, "checkpoint_id", "") or "") == checkpoint_id), None)
async def _handle_recover_slash(state: _InteractiveChatState, args: list[str]) -> None:
store = _require_chat_store(state, label="Recovery store")
if store is None:
return
if args and args[0].lower() == "resume":
if len(args) != 2:
console.print("[warning]Usage: /recover resume <parent_task_id>. Try /recover.[/warning]")
return
manager = _get_chat_recovery_manager(state)
result = await manager.resume(args[1])
if result.get("ok"):
resumed = result.get("resumed_work_item_projection_ids", []) or []
console.print(f"[success]Recovery started for {args[1]}: {', '.join(resumed) or 'runtime queued'}.[/success]")
return
if result.get("error") == "not_found":
checkpoint = await _find_checkpoint_by_id(store, _current_project_id(state.engine), args[1])
if checkpoint:
session_id = str(getattr(checkpoint, "session_id", "") or "")
suffix = f" Try /session resume {session_id}." if session_id else " Try /checkpoints."
console.print(f"[warning]Checkpoint {args[1]} is not resumed directly.{suffix}[/warning]")
return
console.print(f"[warning]Recovery could not start: {result.get('error', 'unknown_error')}[/warning]")
return
if args and args[0].lower() in {"cancel", "retry"}:
action = args[0].lower()
if len(args) < 2:
console.print(f"[warning]Usage: /recover {action} <parent_task_id>{' --yes' if action == 'cancel' else ''}. Try /recover.[/warning]")
return
if action == "cancel":
remaining, ok = _require_yes_arg(args[2:], usage="/recover cancel <parent_task_id> --yes")
if not ok or remaining:
return
elif len(args) > 2:
console.print(f"[warning]Usage: /recover {action} <parent_task_id>[/warning]")
return
payload = await _run_chat_office_service(
state,
lambda svc: svc.runtime.recovery_action(project_id=_current_project_id(state.engine), action=action, parent_task_id=args[1]),
)
if payload:
_emit_payload(payload)
return
try:
if args and args[0].lower() == "scan":
args = args[1:]
args, limit, full = _parse_view_args(args)
except ValueError as exc:
console.print(f"[warning]{exc}. Try /recover --limit 20.[/warning]")
return
if args:
console.print("[warning]Usage: /recover [--limit N] [--full] or /recover resume <parent_task_id>.[/warning]")
return
manager = _get_chat_recovery_manager(state)
status = await manager.get_status()
_render_recovery_status(status, limit=limit, full=full)
if hasattr(store, "get_execution_checkpoints"):
checkpoints = await store.get_execution_checkpoints(
project_id=_current_project_id(state.engine),
statuses=["pending", "resuming"],
)
else:
checkpoints = await store.get_pending_checkpoints(project_id=_current_project_id(state.engine))
_render_checkpoint_table(checkpoints[:limit], title="Recovery Checkpoints", full=full)
async def _resolve_logs_target(
state: _InteractiveChatState,
target: str,
@@ -7487,10 +7416,6 @@ def _busy_slash_policy(command: str, args: list[str]) -> BusyCommandPolicy:
}
if command in readonly_roots:
return BusyCommandPolicy.IMMEDIATE_READONLY
if command == "recover":
if not args or args[0].lower() in {"scan", "status", "list"}:
return BusyCommandPolicy.IMMEDIATE_READONLY
return BusyCommandPolicy.BLOCKED_WHEN_BUSY
if command == "task":
if args and args[0].lower() == "show":
return BusyCommandPolicy.IMMEDIATE_READONLY
@@ -7810,8 +7735,6 @@ async def _handle_chat_slash_command(state: _InteractiveChatState, user_input: s
await _handle_reorg_slash(state, args)
elif command == "runtime":
await _handle_runtime_slash(state, args)
elif command == "recover":
await _handle_recover_slash(state, args)
elif command in {"work-items", "work-item"}:
await _handle_work_items_slash(state, args)
elif command == "logs":
@@ -7835,6 +7758,13 @@ async def _sync_runtime_checkpoint_hint(state: _InteractiveChatState) -> None:
display = state.runtime_display
if not hasattr(display, "set_checkpoint_hint"):
return
runtime_identity = await _company_runtime_identity_for_session(state)
if runtime_identity is not None and runtime_identity.checkpoint is not None:
checkpoint = runtime_identity.checkpoint
checkpoint_type = str(getattr(checkpoint, "checkpoint_type", "") or "pending")
checkpoint_id = str(getattr(checkpoint, "checkpoint_id", "") or "")
display.set_checkpoint_hint(checkpoint_type or checkpoint_id)
return
getter = getattr(state.engine, "get_latest_pending_checkpoint_for_session", None)
if not callable(getter) or not state.session_id:
display.set_checkpoint_hint("")
@@ -7862,24 +7792,110 @@ async def _process_interactive_chat_message(
try:
state.runtime_display.begin_turn()
effective_metadata = message_metadata
if effective_metadata is None:
suspend_checkpoint = await _latest_company_suspend_checkpoint(state)
if suspend_checkpoint is not None:
effective_metadata = {
"response_to_checkpoint_id": str(getattr(suspend_checkpoint, "checkpoint_id", "") or ""),
"response_to_checkpoint_type": str(getattr(suspend_checkpoint, "checkpoint_type", "") or "company_runtime_suspended"),
}
elif state.mode == "company":
execution_session_id = state.session_id
execution_origin_task_id: str | None = None
execution_mode = state.mode
execution_company_profile = state.company_profile
execution_org_id = state.org_id
execution_preferred_agent = state.preferred_agent
handoff_identity = None
runtime_identity = await _company_runtime_identity_for_session(state)
runtime_checkpoint = (
runtime_identity.checkpoint
if runtime_identity is not None
else None
)
explicit_runtime_type = str(
(effective_metadata or {}).get("response_to_checkpoint_type", "") or ""
).strip()
explicit_runtime_id = str(
(effective_metadata or {}).get("response_to_checkpoint_id", "") or ""
).strip()
is_explicit_runtime_handoff = explicit_runtime_type in {
"company_runtime_suspended",
"company_runtime_interrupted",
}
if is_explicit_runtime_handoff:
if runtime_identity is None or runtime_checkpoint is None:
raise RuntimeError(
"Company runtime checkpoint does not match the current session."
)
checkpoint_id = str(
getattr(runtime_checkpoint, "checkpoint_id", "") or ""
).strip()
checkpoint_type = str(
getattr(runtime_checkpoint, "checkpoint_type", "") or ""
).strip()
checkpoint_status = str(
getattr(runtime_checkpoint, "status", "") or ""
).strip().lower()
if (
not explicit_runtime_id
or explicit_runtime_id != checkpoint_id
or explicit_runtime_type != checkpoint_type
):
raise RuntimeError(
"Company runtime checkpoint identity mismatch; refresh before continuing."
)
if checkpoint_status != "pending":
raise RuntimeError(
f"Company runtime checkpoint is {checkpoint_status or 'not pending'}."
)
execution_session_id = runtime_identity.runtime_session_id
execution_origin_task_id = runtime_identity.ui_anchor_task_id or None
handoff_identity = runtime_identity
elif effective_metadata is None and runtime_checkpoint is not None:
checkpoint_status = str(
getattr(runtime_checkpoint, "status", "") or ""
).strip().lower()
if checkpoint_status != "pending":
raise RuntimeError(
f"Company runtime checkpoint is {checkpoint_status or 'not pending'}."
)
if runtime_identity is None:
raise RuntimeError(
"Company runtime checkpoint does not match the current session."
)
effective_metadata = {
"response_to_checkpoint_id": str(getattr(runtime_checkpoint, "checkpoint_id", "") or ""),
"response_to_checkpoint_type": str(getattr(runtime_checkpoint, "checkpoint_type", "") or "company_runtime_suspended"),
}
execution_session_id = runtime_identity.runtime_session_id
execution_origin_task_id = runtime_identity.ui_anchor_task_id or None
handoff_identity = runtime_identity
elif effective_metadata is None:
if runtime_identity is not None and runtime_identity.pending_checkpoint_id:
# An active checkpoint may only be pending or resuming. Never
# let a malformed identity/status fall through as a new turn.
raise RuntimeError(
"Company runtime checkpoint is not available for a new turn."
)
if state.mode == "company":
effective_metadata = {"company_preflight": "manual"}
if handoff_identity is not None:
runtime_execution_identity = await _company_runtime_execution_identity(
state,
handoff_identity,
)
execution_mode = runtime_execution_identity.exec_mode
execution_company_profile = runtime_execution_identity.company_profile
execution_org_id = runtime_execution_identity.org_id
execution_preferred_agent = runtime_execution_identity.preferred_agent
response = await state.engine.process_message(
user_input,
project_id=getattr(state.engine, "project_id", None),
session_id=state.session_id,
mode=state.mode,
org_id=state.org_id or None,
company_profile=state.company_profile if state.mode == "company" else None,
preferred_agent=state.preferred_agent,
session_id=execution_session_id,
mode=execution_mode,
org_id=execution_org_id or None,
company_profile=(
execution_company_profile
if execution_mode == "company"
else None
),
preferred_agent=execution_preferred_agent,
domains=list(state.domains),
origin_task_id=execution_origin_task_id,
message_metadata=effective_metadata,
)
await state.runtime_display.flush()
@@ -7910,7 +7926,15 @@ async def _process_interactive_chat_message(
"company_runtime_interrupted",
}
):
state.runtime_control_state = "running"
remaining_checkpoint = await _latest_company_suspend_checkpoint(state)
if remaining_checkpoint is not None:
state.runtime_control_state = "suspended"
state.runtime_control_checkpoint_id = str(
getattr(remaining_checkpoint, "checkpoint_id", "") or ""
)
else:
state.runtime_control_state = "idle"
state.runtime_control_checkpoint_id = ""
except KeyboardInterrupt:
console.print("\n[warning]Interrupted. Type /quit to exit.[/warning]")
except Exception as e: