发布

  • [OPIK-6516] [SDK] feat(runner): add `opik connect/endpoint stop` to cleanly terminate local runners (#6730)

    frostbyte_neo 发布于 2026-05-18 13:21:41 +00:00

    • [NA] [SDK] refactor: group opik connect/endpoint into cli/local_runner with Rich error UX

    • Move connect.py, endpoint.py, _run.py, pairing.py, error_view.py under
      cli/local_runner/ so the pairing flow is one cohesive subpackage.

    • Banner now shows Workspace alongside Opik URL and Project, with values
      rendered bold so they read before the pairing link.

    • Replace the inline error string with a Rich-rendered labelled block
      (Reason / Workspace / URL / Config / Fix / Docs / Run) via a new
      RichClickError + build_config_error_block factory. Plain text stays on
      .message for Sentry/tests; Rich output is used in .show().

    • Detect "no config file at ~/.opik.config" and surface a clear
      "run opik configure" call-to-action on every failure path.

    • Drop default values on internal kwargs that production callers always
      supply (workspace, base_url, config_file_exists, create_if_missing, etc.)
      so signatures reflect actual usage; add test helpers to keep call sites
      readable.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • [NA] [SDK][FE] feat: auto-configure on connect/endpoint + pairing context UX

    • opik connect/endpoint auto-launch opik configure when no config file
      exists. Skipped on --non-interactive, --headless, no TTY, or when an
      API key is already supplied via --api-key / OPIK_API_KEY.

    • Extract preflight helpers (should_create_project, maybe_auto_configure)
      into cli/local_runner/preflight.py so tests can target a public surface.

    • Pair URL now carries &url=<ui-url> with the /api suffix stripped, so
      the pairing page can show the user-facing instance address.

    • Pairing error screens render a labelled context card under the subtitle:
      "Workspace: X / Pairing with Opik at: " — URL is a clickable link
      that opens the Opik UI in a new tab.

    • scripts/dev-runner.{sh,ps1} export TOGGLE_FORCE_WORKSPACE_VERSION=version_2
      by default so a fresh local backend doesn't trip the "Workspace upgrade
      required" pairing screen. Override via TOGGLE_FORCE_WORKSPACE_VERSION=disabled.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • [NA] [FE] feat(pairing): reword settings card and reorder Opik URL/Workspace

    • Add caption "The CLI tried to pair using these Opik settings:" above
      the card so the framing is unambiguous — these are the values the CLI
      used, not the user's current session.

    • Rename "Pairing with Opik at" → "Opik URL" so the labels match the CLI
      banner (Opik URL / Workspace) verbatim.

    • Reorder rows: URL first, then Workspace — same order as the banner.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • [NA] [SDK][FE] feat(pairing): include project name in pair link and context card

    • CLI build_pairing_link now accepts project_name and appends a URL-encoded
      &project=<name> query param. run_pairing forwards it through.

    • PairingPage reads project from the query; PairingStatusScreen renders
      a new Project row under Workspace in the error context card.

    • Project names with spaces / / round-trip safely via percent-encoding;
      added a regression test covering the encoding shape.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • fix lint

    • fix(pairing): scheme allow-list on Opik URL link + URL-encode workspace

    Addresses PR #6691 review feedback:

    • PairingStatusScreen: validate expectedBaseUrl with URL() and only
      render it as a clickable <a> when the scheme is http/https; otherwise
      render as plain text. Prevents a crafted pair URL like
      ?url=javascript:alert(1) from producing a clickable script link.
    • PairingPage: thread expected workspace/project/baseUrl through the
      invalid_link branch as well, so the CLI context card surfaces even
      when the link fragment is malformed.
    • PairingStatusScreen: loosen showWorkspaceContext to fire when any of
      workspace/project/url is present; gate the Workspace row individually.
    • pairing.py: URL-encode the workspace query param the same way as
      url and project, so workspace names containing &/= don't split
      the query and confuse the FE's URLSearchParams parsing. Added a
      regression test covering the encoding.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • fix(tui): use solid bullet for banner mark

    The combining-enclosing-circle glyph (⠀⃝) failed to render on
    terminals that lack the combining mark, falling back to two red
    placeholder boxes before "opik". Switch to U+25CF (●), which renders
    consistently as a solid colored dot.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • refactor(preflight): skip redundant project lookup when preflight already 404'd

    should_create_project now returns (create_if_missing, known_missing) so
    the interactive path — which already observed the 404 before prompting —
    can tell resolve_project_id to skip the duplicate lookup and go straight
    to _create_project. Headless still leaves known_missing=False so the
    resolver can find an already-existing project across re-runs.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • [OPIK-6497] [SDK][FE] feat(runner): notify backend on close for instant disconnect

    • Supervisor calls disconnect_runner on shutdown (Ctrl+C, SIGTERM, 410
      eviction, clean exit) so the FE flips the runner card off before the
      heartbeat TTL expires. Best-effort: server-side cleanup is idempotent
      and the heartbeat-TTL reaper picks up anything we miss.

    • activate.py restores default signal handlers and raises KeyboardInterrupt
      on first signal so a second Ctrl+C force-exits when the child wedges on
      its own SIGINT path. Bound supervisor's graceful timeout to 5s.

    • Tighten FE poll interval to 1s so the disconnect surfaces in the runner
      card immediately.

    • Cover supervisor disconnect on shutdown, 410 eviction, ApiError tolerance,
      and end-to-end SIGINT/SIGTERM delivery via a subprocess driver.

    Implements OPIK-6497.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • revert(activate): drop SIG_DFL + KeyboardInterrupt force-kill path

    The two-stage signal handling (raise KeyboardInterrupt on first signal,
    restore SIG_DFL so a second signal force-kills) was a safety net for
    wedged agents that swallow KeyboardInterrupt. Removing it: the supervisor's
    graceful timeout + SIGKILL escalation already covers wedged children, and
    keeping the handler purely cooperative avoids changing the SIGINT contract
    that frameworks (uvicorn, etc.) may rely on.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • [OPIK-6516] [SDK] feat(runner): add opik connect/endpoint stop to cleanly terminate local runners

    Adds first-class stop subcommands so Ollie (and humans) can tear down a
    headless runner without resorting to pkill on the process group, which often
    left orphaned children and stale "Endpoint open" state in the Agent Sandbox.

    • opik connect stop --project X | --all | --runner ID and the same for
      endpoint. Each Supervisor writes a small JSON lock file under
      ~/.opik/runners/ on startup (pid, runner_id, runner_type, project,
      workspace, started_at) and removes it in the shutdown finally; stale entries
      are purged opportunistically on the next write.
    • Stop sends SIGTERM to the supervisor pid, which routes through the
      OPIK-6497 handler that calls disconnect_runner before exit — the FE flips
      off on its next poll instead of waiting out the heartbeat TTL. SIGKILL is
      reserved for unresponsive supervisors and skips the backend notification
      (the reaper picks them up).
    • opik connect and opik endpoint become Click groups with a hidden _run
      subcommand and a shared RunnerGroup.resolve_command fallback, so the
      legacy opik endpoint --project X -- python script.py invocation form
      still works alongside the new stop subcommand. --project is now
      required at the Click layer instead of validated by hand.
    • Headless endpoint pairing now surfaces a "Paired ✓" panel with a direct
      Agent Playground link (connect keeps the generic project URL since Ollie
      is the user-facing surface there). Both browser and headless flows share
      pairing.post_pairing_url so the destination logic lives in one place.
    • Internal helpers (Supervisor, launch_supervisor) drop default values on
      parameters that aren't part of the user SDK API — every caller passes
      workspace/project_name/runner_type explicitly.
    • Intra-package imports in cli/local_runner/ switched to module-style
      (from . import pairing; pairing.RunnerType) for clearer call-site
      attribution and easier patch targets in tests.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • ci(guardrails): disable guardrails-ai integration tests

    guardrails-ai is on PyPI quarantine, so the install step in the lib
    integration runner fails before pytest even starts. Comment out the
    job and its references in workflow_dispatch and notify-slack needs;
    drop the SUITE_RESULTS entry (a YAML # would survive as literal text
    into the JSON payload). Re-enable when the package is back on PyPI.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • style(runner): collapse RunnerGroup fallback call onto one line

    ruff format wants the super().resolve_command(...) call on a single
    line; restore that to unblock CI lint.

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

    • fix(runner): address PR review on opik stop CLI
    • stop.py: only drop the pid lock file when the runner actually exited.
      On SIGTERM-denied / SIGKILL-failed / still-alive paths we leave the
      file in place so a subsequent opik <type> stop can rediscover the
      live supervisor instead of being blind to it.
    • pid_file.remove(): bump the OSError log line to LOGGER.error — a
      failed pid-file unlink is a real signal that the cleanup contract
      broke, not a debug-level event.
    • RunnerGroup: narrow the UsageError-catch fallback. Connect (no
      positional after _run) now surfaces click's native "No such command
      'stp'" for subcommand typos instead of routing them into _run and
      printing a confusing "Missing --project". Endpoint opts in via
      accepts_positional_after_run=True so the legacy
      opik endpoint -- python script.py form keeps surfacing the helpful
      "Missing --project" error when --project is omitted.
    • connect.py / endpoint.py: split --runner help text across lines and
      reword it as a usage hint ("Use when a project has more than one
      runner attached to it") instead of the opaque "disambiguates". Brings
      source lines back under the 88-char project limit.
    • test_pid_file.py and test_stop.py: rename every test method to the
      python-sdk testing convention
      (test_<WHAT>__<CASE>__<EXPECTED_RESULT> / __happyflow).

    Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com


    Co-authored-by: Claude Opus 4.7 (1M context) noreply@anthropic.com

    下载附件