Skip to content

feat(extensions): add provider-less terminal connections - #419

Merged
feigeCode merged 1 commit into
feigeCode:mainfrom
keenser:workbench_terminal_ux
Oct 20, 2026
Merged

feigeCode merged 1 commit into
feigeCode:mainfrom
keenser:workbench_terminal_ux

Conversation

@keenser

@keenser keenser commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Description

Adds provider-less terminal connections to the extension system. A connection contribution can now declare a terminal block (command, args, env, title) instead of a provider runtime. When the user opens such a connection, the host starts a regular local terminal tab from the declared template, using the non-secret values saved in the connection form.

Problem: an extension whose only job is "run a local command with a few saved parameters" (e.g. tsh ssh <target> for Teleport) still had to ship and spawn a native provider process plus a resource workbench. Sessions are tied to a live provider client, so this could not be simplified without a large rework. With this change, such an extension is a single extension.json with no runtime, no bin/, and no Rust.

What changed:

  • Manifest / registration (extension-runtime): runtimeId and resourceType become optional. terminal is mutually exclusive with runtimeId, resourceType, shellViewId, and workbench bindings. command is fixed (no placeholders). Placeholders ({{fieldId}})
    in args, env values and title may only reference existing non-secret form fields. Secret fields are rejected for terminal connections.
  • Launch resolution (terminal_launch.rs): substitutes placeholders from the saved connection values. Optional args/env entries that resolve to an empty string are dropped. Control characters in values are rejected.
  • Option-injection guard: without a provider there is no per-extension input validation, and connection data may come from cloud sync / team sharing. If an argument template does not start with - but the substituted value does, the open is rejected (e.g. target = "-oProxyCommand=..." cannot become an ssh option).
  • Terminal tab (terminal, terminal_view): launched sessions get a fixed tab title, can be duplicated, and restart on Enter after the child process exits instead of leaving a dead tab.
  • Host open path (home_strategy.rs): new branch that opens a terminal tab for contributions with terminal, independent of the shell-plugins feature.
  • Connection form: the "Test" button is hidden for terminal connections (there is no provider to probe).
  • Docs: new section in the extension development guide.

The companion extension (tsh-ssh, static composite, engines.onetcli >= 0.20.3) lives in navop-extensions and is submitted separately. The submodule pointer is intentionally not part of this PR.

Break Changes

No behavior change for existing extensions. Two API-level notes:

  • ResourceConnectionContrib.runtime_id / resource_type now default to empty on deserialization. Registration still rejects a non-terminal connection that leaves them empty, with the same error as before.
  • RegisteredResourceConnectionContribution gains a public terminal: Option<ResourceConnectionTerminal> field, so Rust code constructing it by struct literal must add terminal: None.
 RegisteredResourceConnectionContribution {
     ...
     shell_view_id: None,
+    terminal: None,
     form: Default::default(),
 }

How to Test

Automated:

cargo test -p extension-runtime --lib -- terminal # template resolution, option-injection guard, registration rules
cargo test -p terminal_view --lib duplicate # duplicate source for launched terminals
cargo test -p terminal --lib restart # restart-after-exit for local sessions
cargo test -p main home
cargo check --workspace --tests

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes.
  • Tested macOS, Windows and Linux platforms performance (if the change is platform-specific)

A connection contribution can now declare `terminal` (command, args, env, title) instead of a provider runtime. The host resolves the templates against the saved non-secret form values and opens a regular local terminal tab, so extensions like tsh-ssh need nothing but an extension.json.

- Manifest: runtimeId/resourceType become optional; `terminal` is mutually exclusive with runtimeId, resourceType, shellViewId and workbench bindings. The command is fixed (no placeholders) and placeholders may only reference existing non-secret fields.
- Host-side guard: an argument whose template does not start with '-' is rejected if the substituted value does, so shared or synced connection data cannot inject command-line options. Empty optional args/env entries are dropped, control characters are rejected.
- Terminal: launched sessions get a fixed tab title, can be duplicated, and restart on Enter after the child process exits.
- Connection form hides the Test button for terminal connections.
- Docs: describe terminal connections in the extension development guide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@feigeCode
feigeCode merged commit dafb3b9 into feigeCode:main Oct 20, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants