org_node_trigger.py 1.9 KB
"""c_org_node_trigger (03-C §3.3): subprocess wrapper skeleton.

U-005 scope: shell out `claude --agent <org_node> -p ...` and return the text
response -- "骨架,不含完整状态机" per development-plan.md. No task-lifecycle
state machine yet: TASK_NOT_READY/CONCURRENCY_LIMIT_REACHED/USAGE_LIMIT_REACHED
handling is U-010's job, not this one.

Identity comes entirely from `.claude/agents/<org_node>.md` in the target
project (see src/agent_profile.py) -- this module only assembles the prompt
and parses the response, no role-isolation logic of its own (03-C §3.3).
"""

import json
import subprocess

from src.agent_profile import ORG_NODES


class OrgNodeTriggerError(Exception):
    """Raised when the claude subprocess exits non-zero or reports is_error."""


def trigger_org_node(org_node, prompt, target_folder_path, timeout=300):
    """Runs `claude --agent <org_node> -p <prompt>` with cwd=target_folder_path.

    `.claude/agents/<org_node>.md` must already be deployed in target_folder_path
    (agent_profile.deploy_agent_definitions) -- this function does not deploy it.
    Returns the response text (the JSON `result` field).
    """
    if org_node not in ORG_NODES:
        raise ValueError(org_node)

    result = subprocess.run(
        [
            "claude", "--agent", org_node,
            "-p", prompt,
            "--output-format", "json",
            "--permission-mode", "auto",
        ],
        cwd=target_folder_path,
        stdin=subprocess.DEVNULL,
        capture_output=True,
        text=True,
        timeout=timeout,
    )
    if result.returncode != 0:
        raise OrgNodeTriggerError(
            "claude --agent {} exited {}: {}".format(org_node, result.returncode, result.stderr)
        )

    payload = json.loads(result.stdout)
    if payload.get("is_error"):
        raise OrgNodeTriggerError("claude --agent {} reported is_error: {}".format(org_node, payload))
    return payload["result"]