From 150eda0799e777e263f6590ff0971f4b0ae3a6cf Mon Sep 17 00:00:00 2001 From: Endri Bezati Date: Sun, 8 Nov 2026 10:58:38 +0100 Subject: [PATCH 1/2] feat: a product whose CLI has no run says so; the diagram then offers no Run Phase 5 of the declutter plan, third step. The toolkit registered the CLI run driver for every sidecar product, and the palette header showed Run and Stop for every diagram. In ndf, whose CLI has no `run`, Run invoked `ndf run --out-dir wf-out ...`; in chisel-ide, the library-mode mlir-viewer and read-only profiles it invoked a command nothing registered. SidecarProfileInput.runDriver: false registers no driver and offers no resume. The host derives clientBehavior.runDriver from the profile, as it does chatBackend, and the header adds Run and Stop only when it is not false. sv and vhdl keep their driver: `hdl run` simulates through it. Claude-Session: https://claude.ai/code/session_01EToj6RJRMSSHQiqGNMU9wv --- CHANGELOG.md | 8 +++++ packages/diagram-client/src/profile.ts | 11 +++++++ .../src/tool-palette-header-controls.ts | 10 +++--- .../diagram-client/test/offers-run.test.ts | 33 +++++++++++++++++++ packages/extension-core/src/api.ts | 8 +++++ .../diagram/diagram-editor-provider.ts | 3 ++ ...gram-editor-provider-client-assets.test.ts | 2 +- ...gram-editor-provider-status-source.test.ts | 13 ++++++++ .../src/sidecar-diagram-profile.ts | 16 +++++++-- .../test/client-behavior-twin.test.ts | 4 ++- .../test/sidecar-diagram-profile.test.ts | 12 +++++++ 11 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 packages/diagram-client/test/offers-run.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d93ed18..90c5fc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,14 @@ platform API version. ### Added +- **A product whose CLI has no `run` says so: `runDriver: false`.** The + toolkit then registers no run driver, the chat offers no resume, and the + diagram shows no Run or Stop. The webview learns it from a derived + `clientBehavior.runDriver` (like `chatBackend`), so every consumer without + a run driver (ndf once it opts out, chisel-ide, the library-mode + mlir-viewer, read-only profiles) loses a Run button that invoked a command + nothing registered or a CLI subcommand that does not exist. sv and vhdl keep + theirs: `hdl run` simulates through it. - **Selection trace** (`clientBehavior.selectionTrace`, opt-in): selecting a node draws every path through it, what leads to it in green and what it leads to in blue, and fades the rest; after mlir-viewer's trace highlighter. diff --git a/packages/diagram-client/src/profile.ts b/packages/diagram-client/src/profile.ts index 7e242f4..8be7cce 100644 --- a/packages/diagram-client/src/profile.ts +++ b/packages/diagram-client/src/profile.ts @@ -24,6 +24,8 @@ type DiagramClientBehavior = { nodeFamilies?: NodeFamilySpec[]; /** Whether the host has a chat backend; derived by the platform. */ chatBackend?: boolean; + /** Whether a run driver is behind the diagram; derived by the platform. */ + runDriver?: boolean; /** The ACP connectors the runtime discovered or the user declared, for an * agent's `connector` (extension-core `AcpConnectorInfo`). */ acpConnectors?: AcpConnectorInfo[]; @@ -156,6 +158,15 @@ export function clientBehavior(): DiagramClientBehavior { return configured && typeof configured === 'object' ? configured : {}; } +/** + * Whether the diagram offers Run and Stop: unless the host says no run driver + * is behind it. A webview the host did not describe (dev, standalone) keeps + * them, as before. + */ +export function offersRun(): boolean { + return clientBehavior().runDriver !== false; +} + /** * The host resolves part of the behavior after the webview is up (what depends * on the machine or the workspace, e.g. the ACP connectors the runtime knows) diff --git a/packages/diagram-client/src/tool-palette-header-controls.ts b/packages/diagram-client/src/tool-palette-header-controls.ts index c2524ba..32fe4a5 100644 --- a/packages/diagram-client/src/tool-palette-header-controls.ts +++ b/packages/diagram-client/src/tool-palette-header-controls.ts @@ -1,7 +1,7 @@ import { codiconCSSClasses } from '@eclipse-glsp/sprotty'; import { VscodeUi } from './vscode-ui'; import { exportDiagramSvg } from './svg-export-command'; -import { commandId, feedbackEdgesVisibleStorageKey, queueTraceVisibleStorageKey } from './profile'; +import { commandId, feedbackEdgesVisibleStorageKey, queueTraceVisibleStorageKey, offersRun } from './profile'; type DiagramContext = { sourceUri?: string; @@ -182,8 +182,8 @@ function patchHeaderTools(headerTools: HTMLElement): void { }; } - // 2) Add Run button as first header tool. - if (!headerTools.querySelector('i.codicon.codicon-play.cal-run-workflow')) { + // 2) Add Run button as first header tool, when something runs the diagram. + if (offersRun() && !headerTools.querySelector('i.codicon.codicon-play.cal-run-workflow')) { const runIcon = createHeaderIcon('play'); runIcon.classList.add('cal-run-workflow'); runIcon.title = 'Run workflow'; @@ -209,8 +209,8 @@ function patchHeaderTools(headerTools: HTMLElement): void { headerTools.insertBefore(runIcon, headerTools.firstChild); } - // 3) Add Stop button to cancel an active workflow run. - if (!headerTools.querySelector('i.codicon.codicon-debug-stop.cal-stop-workflow')) { + // 3) Add Stop button to cancel an active workflow run, under the same condition. + if (offersRun() && !headerTools.querySelector('i.codicon.codicon-debug-stop.cal-stop-workflow')) { const stopIcon = createHeaderIcon('debug-stop'); stopIcon.classList.add('cal-stop-workflow'); stopIcon.title = 'Stop workflow run'; diff --git a/packages/diagram-client/test/offers-run.test.ts b/packages/diagram-client/test/offers-run.test.ts new file mode 100644 index 0000000..b71d416 --- /dev/null +++ b/packages/diagram-client/test/offers-run.test.ts @@ -0,0 +1,33 @@ +/** + * Run and Stop are offered unless the host says no run driver is behind the + * diagram. The palette header asks {@link offersRun} before adding either. + */ +import { afterEach, describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { offersRun } from '../src/profile'; + +afterEach(() => { + delete (globalThis as any).diagramIdentifier; +}); + +describe('offersRun', () => { + it('follows the host\'s derived flag', () => { + (globalThis as any).diagramIdentifier = { clientBehavior: { runDriver: false } }; + expect(offersRun()).toBe(false); + (globalThis as any).diagramIdentifier = { clientBehavior: { runDriver: true } }; + expect(offersRun()).toBe(true); + }); + + it('keeps them in a webview the host did not describe', () => { + expect(offersRun()).toBe(true); + }); + + it('is what the palette header asks before adding Run and Stop', () => { + const here = path.dirname(fileURLToPath(import.meta.url)); + const source = readFileSync(path.join(here, '../src/tool-palette-header-controls.ts'), 'utf8'); + expect(source).toMatch(/offersRun\(\) && !headerTools\.querySelector\('i\.codicon\.codicon-play\.cal-run-workflow'\)/); + expect(source).toMatch(/offersRun\(\) && !headerTools\.querySelector\('i\.codicon\.codicon-debug-stop\.cal-stop-workflow'\)/); + }); +}); diff --git a/packages/extension-core/src/api.ts b/packages/extension-core/src/api.ts index ccc29e5..1650b9b 100644 --- a/packages/extension-core/src/api.ts +++ b/packages/extension-core/src/api.ts @@ -409,6 +409,14 @@ export interface DiagramClientBehavior { /** What the hierarchy outline panel is called (its title and its button's * tooltip); "Hierarchy" by default. */ outlineTitle?: string; + /** + * Whether a run driver is behind this diagram: the Run and Stop buttons are + * offered exactly then. Derived by the platform from `DiagramProfile.runDriver`, + * like {@link chatBackend}; read it, do not write it. A button that invokes a + * command nothing registered, or a CLI subcommand that does not exist, is + * worse than no button. + */ + runDriver?: boolean; /** * Whether the host has a chat backend behind this diagram. * diff --git a/packages/extension-core/src/extension/diagram/diagram-editor-provider.ts b/packages/extension-core/src/extension/diagram/diagram-editor-provider.ts index e356387..440a702 100644 --- a/packages/extension-core/src/extension/diagram/diagram-editor-provider.ts +++ b/packages/extension-core/src/extension/diagram/diagram-editor-provider.ts @@ -990,6 +990,9 @@ export class WorkflowEditorProvider extends GlspEditorProvider { // absent is exactly what "the chat backend is not activated" // means, so it is the honest source. chatBackend: this.profile.chat !== undefined, + // The same for the run driver: Run and Stop are offered exactly + // when something is registered to run the diagram. + runDriver: this.profile.runDriver !== undefined, // The same for the status source: the menu offers exactly the // commands the host registered, or none. Data only; `collect` // stays on the host. diff --git a/packages/extension-core/test/diagram-editor-provider-client-assets.test.ts b/packages/extension-core/test/diagram-editor-provider-client-assets.test.ts index ea5467f..8f98a3f 100644 --- a/packages/extension-core/test/diagram-editor-provider-client-assets.test.ts +++ b/packages/extension-core/test/diagram-editor-provider-client-assets.test.ts @@ -168,7 +168,7 @@ describe('WorkflowEditorProvider default webview assets (no clientAssets)', () =