Running a registered step through the shared Rust CLI delegates work to an existing runner; its returned job ID is used to inspect status and logs. The browser's MCP connection also reused its existing commands and execution host. These cases keep input checks and execution records with the original program as another entry point is added.
The browser MCP and shared CLI use different engines, but neither needs a second owner for execution. The comparison follows where each program keeps its checks and records. The full CLI integration Goal was still in progress when these sources were read.
In the video-editor connector, numeric selection skipped a check already used by name-based selection. The repair made both routes validate the resolved target. The sections below follow that call sequence and the dated evidence for the correction.
Two entry points must not quietly become two products
If the screen refuses a disabled plugin but MCP executes it by ID, they do not offer the same capability. Likewise, one route checking the result while another celebrates process start creates inconsistent behavior. Users experience different rules depending on how they ask.
I connected external control to existing program, notebook and controller services. Transport shape can differ while ownership, enablement and busy-state responsibilities remain. The important commonality is consistent execution protection, rather than identical interface wording.
Plugin execution continues through the existing command
The external handler checks context, refreshes the catalog and verifies that the selected plugin is enabled. It passes project, profile, tab, inputs and mode to the existing pluginRun command. It does not create an MCP-only shortcut that launches Python independently.
Simple publication and delivery inputs likewise continue through pluginPublish and pluginDeliver. That preserves checks involving saved defaults, selected files and pre-execution conditions. A new function with a familiar name would not establish a shared engine; I followed the injected command and actual call route.
Manual operations still pass through the Broker
northstar_call verifies the current task ID and uses programCall. The controller passes that call and the current execution epoch to broker.execute. Being requested by an agent does not bypass native-input or element-reference checks.
This also explains why MCP does not accept arbitrary page JavaScript or raw CDP commands. Fixed tools retain current target and outcome checks. A host using browser-control technology internally differs from exposing every underlying command to an external caller.
A shared service does not remove entry-specific validation
Using common services does not remove checks on external input. Schema, context, current task ID, private-profile boundaries and enabled plugins are checked at the entry. Conflicting requests while a program or notebook is busy are rejected too.
Shared execution still needs checks at each entry. A screen action and an external message arrive differently, so their preconditions can differ before they reach the service. Keep execution responsibility together while retaining the checks each entry needs.
Execution success and automatic retention are separate results
finishProgram can retain a reusable procedure from successful work. If retention fails, it records an event separately from the execution outcome. Finishing work and creating a reusable asset do not necessarily succeed together.
The integration test rejects disabled plugins and changed contexts and prevents completion while an outcome remains unproven. The original development note compared the call route and tests from that time. This update does not operate the installed app or freshly test exhaustive equivalence between every screen button and MCP command.
A Rust entry point can retain an existing execution engine
The shared CLI reads program definitions and existing execution records in Rust. Registered steps, however, are executed through a fixed Python compatibility bridge that delegates to the existing tower.cli_run. A command-line program written in Rust does not establish that the connected programs' internal engines have also been rewritten in Rust.
The bridge finds registered programs and steps and checks accepted input names. It preserves confirmation requirements rather than treating installation of the CLI as approval. Its plan route shows the execution specification and argument count without exposing argument values, and returns executed as false. Checking a specification is not executing the business operation.
There is no separate manager duplicating execution records and logs. Tower work is queried with the existing runner's run_id, while API work uses its returned job_id. One wait path explicitly avoids turning a timeout into cancellation of the original job. A shared CLI still needs to retain the owning engine and the kind of ID being queried.
Keep registration distinct from verification in the coverage map
The coverage source does not turn its program list into a count of completed connections. It distinguishes missing launchers, missing target paths, registration without verification, actions that open a file or interface, and actions with a historical success record. Cached connection-check times are retained separately.
The historical-success label looks for a completed execution with a successful exit record for the matching program and step. It is not a test that every feature works now. The inspected function also sets overall connection-and-verification completion to false. Finding a definition, opening an interface and finishing the requested work remain separate observations.
Isolated test source covers retained confirmation requirements, unknown-input rejection, a preview that sends no change request, asynchronous job-ID preservation and wait timeout. For this update I read those declarations and code; I did not execute the original tests, CLI or API. Alongside the browser example, they show where existing checks, records and job ownership belong when another entry point is added.
Resolve a name or number, then apply the same selection check
After connecting the editor through a CLI, the user asked to include character and speaker selection. A choice can be specified by name or by an element number in an observed screen. Treating an existing number as sufficient can admit action buttons such as confirmation or synthesis into a choice command.
The repair record says the numeric route skipped the action-label check used by the name route. The inspected choose function resolves a name to an element number or accepts the supplied number, then calls the shared validate_choice_target function. Only a passing target reaches the UI action. Name and number locate the element; the common check determines whether it belongs in this command.
Both routes first verify the snapshot and selection panel. The UI action also compares the observation again before input. These numbers belong to that snapshot. They are not documented as fixed catalog identifiers that can be reused across an app update or a changed screen.
Explore the implementation
Explanatory call sequence · not an executable module
verify snapshot + selection panel
--name -> resolve node in that snapshot
--id -> use node from that snapshot
both -> validate target + actionable ancestors
-> UI action with snapshot revalidation
-> new observation; selection_applied_verified = falseCheck the parent action behind an unnamed image
An image inside a confirmation button may have no name. A check limited to the selected node’s label could accept the empty string while the click still reaches that button. The inspected function checks the target and actionable ancestors. It also rejects editable and password elements, out-of-range numbers and invalid parent relationships.
The inspected rule excludes labels involving confirmation, synthesis, generation and deletion, together with selected apply expressions. It does not scan arbitrary project titles on every ancestor. This is a label rule for the target and particular actionable ancestors, not a universal interpreter of every language and future app version.
The in-source regression function accepts ordinary speaker-name elements. It rejects a confirmation button, its unnamed image child, other action labels, a missing number and an editable element. I kept the initial record of 5 tests and a build distinct from the later total of 8 tests. I read those dated records and the function definition; they do not establish live UI verification of both complete CLI routes.
The choice result leaves application verification false and returns a fresh observation. Selecting a character tile may immediately insert it into a clip; selecting a speaker and synthesizing speech remain separate stages. The later record adds live checks for some selection panels. It does not establish every conditional item or synthesis and export outcomes, and independent review of the common code remains unconfirmed.
Ask the same question of other work tools
A business tool with owner and employee screens, an admin API and an automation CLI faces the same issue. Before multiplying interfaces, decide where storage, verification and permissions are enforced. One fast route does not establish readiness of the whole product.
Reusing execution rules can reduce differences between entry points. A common-code defect can also affect several routes, so regression tests remain necessary. Keep evidence that the routes share code separate from evidence that a particular job completed through it.