diff --git a/scripts/build_runtime_package.py b/scripts/build_runtime_package.py index 366b86c2..910ea691 100755 --- a/scripts/build_runtime_package.py +++ b/scripts/build_runtime_package.py @@ -150,6 +150,13 @@ def rewrite_imports(text: str, regex: re.Pattern) -> str: `import X` → `import molecule_runtime.X as X` (preserve binding) `from X import Y` → `from molecule_runtime.X import Y` `from X.sub import Y` → `from molecule_runtime.X.sub import Y` + + Rejects `import X as Y` because the rewrite would produce + `import molecule_runtime.X as X as Y`, a syntax error. The PR #2433 + incident shipped this exact pattern past `Python Lint & Test` (which + runs against pre-rewrite source) but blew up the wheel-smoke gate. + Detecting it here turns the silent build failure into a build-time + error with a clear path: use `from X import …` or plain `import X`. """ def repl(m: re.Match) -> str: indent, kw, mod, rest = m.group("indent"), m.group("kw"), m.group("mod"), m.group("rest") @@ -163,6 +170,26 @@ def rewrite_imports(text: str, regex: re.Pattern) -> str: # `import X.sub` — rewrite as `import molecule_runtime.X.sub` and # leave the trailing dot pattern intact for the rest of the line. return f"{indent}import molecule_runtime.{mod}{rest}" + # Detect `import X as Y` — the regex's `rest` group captures only + # the immediate following char (whitespace, comma, or EOL), so we + # have to peek at the surrounding line context. The match start is + # at the line's `import` keyword; everything after the matched + # name on the same line is what the source author wrote. + line_start = text.rfind("\n", 0, m.start()) + 1 + line_end = text.find("\n", m.end()) + if line_end == -1: + line_end = len(text) + line_after = text[m.end() - len(rest):line_end] + # Strip comments from consideration so `import X # noqa` doesn't trip. + line_after_no_comment = line_after.split("#", 1)[0] + if re.search(r"^\s*as\s+\w+", line_after_no_comment): + raise ValueError( + f"rewrite_imports: cannot rewrite 'import {mod} as ' on a " + f"workspace module — the regex would produce " + f"'import molecule_runtime.{mod} as {mod} as ', invalid syntax. " + f"Use 'from {mod} import …' or plain 'import {mod}' instead. " + f"Offending line: {text[line_start:line_end]!r}" + ) # Plain `import X` — alias preserves the local name. return f"{indent}import molecule_runtime.{mod} as {mod}{rest}" return regex.sub(repl, text) diff --git a/workspace/a2a_mcp_server.py b/workspace/a2a_mcp_server.py index afffb956..09512f26 100644 --- a/workspace/a2a_mcp_server.py +++ b/workspace/a2a_mcp_server.py @@ -17,6 +17,10 @@ import json import logging import sys +import inbox # noqa: F401 — bridge wiring lives in main(); the rewriter +# produces `import molecule_runtime.inbox as inbox` +# which preserves this binding for set_notification_callback. + from a2a_tools import ( tool_check_task_status, tool_commit_memory, @@ -212,8 +216,7 @@ async def main(): # pragma: no cover # Loop closed during shutdown — best-effort, swallow. pass - import inbox as _inbox_module - _inbox_module.set_notification_callback(_on_inbox_message) + inbox.set_notification_callback(_on_inbox_message) buffer = "" while True: