Skip to content

feat: Split get-actor-run into data + -widget toolsΒ #719

@jirispilka

Description

@jirispilka

πŸ”— PR chain β€” step 6 of 6 in #577

# Issue PR Status
1 #714 β€” Rename openai β†’ apps #720 in review
2 #715 β€” Capability auto-detect + gating #721 in review
3 #716 β€” fetch-actor-details split (pilot) #722 in review
4 #717 β€” search-actors split #723 in review
5 #718 β€” call-actor split #724 in review
6 #719 (this issue) #734 in review

Parent: #577. Final child. Completes the umbrella.


Context and motivation

Final child of #577. Mirrors #716, #717, #718. Depends on #714, #715.

After this PR:

  • get-actor-run = data-only, mode-independent (same ToolEntry returned in both modes)
  • get-actor-run-widget = widget-only, additive in apps mode
  • No -internal equivalent existed, so nothing to delete

Why the widget variant now (not later)

Under today's call-actor-widget auto-polling model, WORKFLOW_RULES forbids calling get-actor-run after a widget render β€” so the apps-mode widget branch was effectively dead. Forward direction: call-actor becomes always-async (returns runId immediately, no UI), and the LLM picks get-actor-run (silent status) or get-actor-run-widget (UI progress) based on whether the user wants a visual. Shipping the widget variant now keeps the six-tool split symmetric with #722 / #723 / #724 and prepares the infrastructure before the async-execution policy change (tracked separately per #582).

Scope

Shipped (Approach A β€” same as peers)

  • get-actor-run across modes = data-only (current default behavior). Dropped tool-level widget _meta; trimmed stale "NEVER call in UI mode" block from base description (that rule lives in WORKFLOW_RULES now).
  • Created src/tools/apps/get_actor_run_widget.ts:
    • Input: { runId }.strict()
    • Output: widget-shaped run status (reuses shared buildGetActorRunSuccessResponse({ widget: true }))
    • _meta.ui.resourceUri = ui://widget/actor-run.html
    • _meta.ui.visibility = ["model","app"], non-empty _meta.ui.csp
    • Response-level openai/widgetDescription
  • Added ACTOR_RUNS_GET_WIDGET to HelperTools
  • src/tools/categories.ts: runs entry is now plain defaultGetActorRun; ui[] gets { apps: getActorRunWidgetTool }
  • src/utils/server-instructions/apps.ts: fourth disambiguation bullet pairing the base tool with the widget variant. WORKFLOW_RULES untouched β€” the "NEVER poll after call-actor-widget" rule is orthogonal.

Out of scope

  • Widget UI code
  • #582 (waitSecs / always-async policy) β€” tracked separately
  • Structured output schema changes (existing getActorRunOutputSchema is shared between variants; no new schema needed)

Design notes

  • Base tool is the same frozen ToolEntry in both modes (defaultResult.runs === appsResult.runs for get-actor-run). The widget sibling lives only in ui category.
  • get_actor_run_common.ts (per refactor: Deduplicate get-actor-run handler logicΒ #699) already exposes shared builders. The widget tool calls fetchActorRunData() + buildGetActorRunSuccessResponse({ widget: true }); the base tool calls the same with widget: false.

Files changed (PR #734)

File Change
src/tools/apps/get_actor_run_widget.ts created
src/tools/apps/get_actor_run.ts deleted (base is mode-independent)
src/tools/categories.ts get-actor-run plain entry; register widget in ui
src/const.ts Add ACTOR_RUNS_GET_WIDGET = 'get-actor-run-widget'
src/tools/core/get_actor_run_common.ts drop widget _meta from base metadata; add openai/widgetDescription to widget response; trim description
src/utils/server-instructions/apps.ts fourth disambiguation bullet
tests/unit/tools.get_actor_run.widget.response.test.ts created
tests/unit/tools.mode_contract.test.ts widget in UI category, base on no-widget-meta list (runs category)
tests/unit/tools.categories.test.ts flip not.toBe β†’ toBe for get-actor-run (mode-independent now)

Internal repo impact

Purely additive on the widget side. Base get-actor-run keeps the same name and byte-identical input/output schemas across modes. The shared buildGetActorRunSuccessResponse widget branch now emits openai/widgetDescription on the response β€” harmless for consumers that ignore extra _meta keys. Internal repo should not need edits; called out for reviewer confirmation in PR #734.

Testing strategy

Unit

  • get-actor-run identical output in both modes (shared ToolEntry)
  • get-actor-run-widget returns widget shape + widget _meta + openai/widgetDescription
  • Strict input schema rejects stray keys
  • Updated tests/unit/tools.mode_contract.test.ts and tests/unit/tools.categories.test.ts

Integration

  • Human-run (npm run test:integration) β€” not covered by this PR's agent verification

mcpc probe (raw JSON-RPC over stdin)

Acceptance criteria:

  • Default mode: exactly one tool matches get-actor-run; no -widget
  • Apps mode: exactly two tools β€” base get-actor-run AND get-actor-run-widget
  • Base get-actor-run inputSchema + outputSchema byte-identical between modes
  • Base get-actor-run _meta has no ui.* keys in either mode
  • get-actor-run-widget._meta.ui.resourceUri == ui://widget/actor-run.html
  • get-actor-run-widget._meta.ui.visibility == ["model","app"]
  • get-actor-run-widget._meta.ui.csp is a non-empty object

Manual

  • MCPJam widget parity; ChatGPT end-to-end β€” deferred to human reviewer

Verification checklist

  • npm run type-check passes
  • npm run lint passes
  • npm run test:unit passes (615 tests)
  • npm run build passes
  • mcpc probe passes (all acceptance criteria above)
  • Integration tests pass (human-run)
  • MCPJam widget parity
  • Internal-repo audit complete

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request.t-aiIssues owned by the AI team.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions