org_node_trigger.py 2.42 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:
        # 2026-07-31 fix: a real Coder call once failed with returncode=1 and
        # empty stderr, and the exception message (stderr-only, as it used to
        # be) gave zero diagnostic signal -- whatever claude actually said
        # about the failure was in stdout, which this branch was discarding.
        # Both are now included (truncated -- stdout in JSON mode can be
        # large) so a future real failure is actually debuggable.
        raise OrgNodeTriggerError(
            "claude --agent {} exited {}\nstderr: {}\nstdout (truncated): {}".format(
                org_node, result.returncode, result.stderr, result.stdout[:4000]
            )
        )

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