Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ platform API version.

### Changed

- **One declaration of the webview's behaviour flags, the command ids and
the in-process chat tool.** `DiagramClientBehavior` (with
`AcpConnectorInfo`), `DiagramCommandIds` and `InProcessChatTool` live in
`@dialogram/shared`; the API re-exports them, the toolkit's
`SidecarClientBehavior`, `SidecarProfileCommandIds` and `RegistryChatTool`
are the shared types (the product-facing behaviour is
`ProductClientBehavior`, the shared type minus the four fields the platform
derives), and the webview reads them instead of its own copy. The test that
kept two hand-written twins in step is gone with the second twin.
`scriptInterpreterCommands` is deprecated: nothing reads it.
- **One sidecar transport.** Six places each spawned the sidecar, wrote the
request and parsed the answer with their own wording and deadlines: the
invoker behind every diagram edit, the chat's `invokeSidecarOp` and
Expand Down
90 changes: 6 additions & 84 deletions packages/diagram-client/src/profile.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,16 @@
import type { NodeFamilySpec } from '@dialogram/shared';
import type { AcpConnectorInfo, DiagramClientBehavior, DiagramCommandIds, DiagramOperationKinds } from '@dialogram/shared';

type DiagramCommandIds = {
fitToScreen?: string;
runWorkflow?: string;
stopWorkflow?: string;
setQueueTraceVisible?: string;
setAgentToolConfig?: string;
getAgentToolConfig?: string;
};

type DiagramOperationKinds = {
createEntityPort?: string;
deleteEntityPort?: string;
};

type DiagramClientBehavior = {
graphSourceNavigation?: boolean;
networkPropertySections?: boolean;
networkNavigationLabels?: boolean;
noneSentinel?: string;
scriptInterpreterCommands?: string[];
paletteIcons?: Record<string, { dark: string; light?: string }>;
nodeFamilies?: NodeFamilySpec[];
/** Whether the host has a chat backend; derived by the platform. */
chatBackend?: boolean;
/** The ACP connectors the runtime discovered or the user declared, for an
* agent's `connector` (extension-core `AcpConnectorInfo`). */
acpConnectors?: AcpConnectorInfo[];
/** Whether a run can be resumed at a step of its queue trace. */
resumeAtStep?: boolean;
/** Where a nested workflow defined in another file opens: `in-place` shows it in
* this editor, as a view of the editor's root (one editor per hierarchy);
* `new-editor` (the default) opens the file in its own editor. */
nestedNavigation?: 'in-place' | 'new-editor';
/** Whether a double-click on a node that is not a network instance opens the
* source it references in the text editor. Off (the default), such a node
* does nothing on double-click. For a product whose leaves have a body of
* their own that no diagram shows yet: an HDL module or entity of processes
* and assignments lands at its definition. */
doubleClickOpensSource?: boolean;
/** Whether anything is written on a wire: the carried label (a width, a
* payload type), the queue badge and last-token button of a run, the
* index labels of a list connection. Default true. Off, an edge is its
* line, and the tooltip on hover still says what it carries. 'once': as
* true, but of the edges that leave or reach one port together with the
* same carried label, only the first writes it; the others say it in
* their tooltip (a node with many edges of one type, all on one trunk). */
edgeCaptions?: boolean | 'once';
/** Property-panel sections a product does without, each `false` to hide
* it (all shown by default): `ports`, the input and output port lists, for
* a product whose pins carry no meaning; `annotations`, the raw
* definition annotations, for one whose facts already say it all;
* `parameters`, the definition's parameters, for one whose nodes have
* none (its own editable fields still show); `entityType`, Basic Info's
* Type row (task, workflow) and its change-type button, for one whose
* node kinds are its own. */
propertySections?: { ports?: boolean; annotations?: boolean; parameters?: boolean; entityType?: boolean };
/** The profile's status source, as the context menu offers it: "Show
* <label>" runs `showCommand`, "Clear <label>" runs `clearCommand`.
* Derived by the platform from the profile. */
statusSource?: { label: string; showCommand: string; clearCommand: string };
/** Whether selecting an element draws its path: what leads to it in one
* colour, what it leads to in another, the rest faded. A node traces
* every path through it, an edge itself and its two ends, a container
* what feeds it and what it feeds across its frame; a path crosses group
* frames as its edge does. Off by default. See selection-trace.ts. */
selectionTrace?: boolean;
/** What the hierarchy outline panel is called (its title, its button's
* tooltip): plain words, "Hierarchy" by default. */
outlineTitle?: string;
};

export type AcpConnectorInfo = {
name: string;
available: boolean;
source: string;
command: string;
httpApi?: boolean;
model?: string | null;
mode?: string | null;
};
export type { AcpConnectorInfo, DiagramClientBehavior };

type DiagramIdentifier = {
commandIds?: DiagramCommandIds;
operationKinds?: DiagramOperationKinds;
commandIds?: Partial<DiagramCommandIds>;
operationKinds?: Partial<DiagramOperationKinds>;
settingsNamespace?: string;
clientBehavior?: DiagramClientBehavior;
};

type DiagramCommandKey = keyof Required<DiagramCommandIds>;
/** The commands the webview itself runs; the rest the host registers for its own menus. */
type DiagramCommandKey = 'fitToScreen' | 'runWorkflow' | 'stopWorkflow' | 'setQueueTraceVisible' | 'setAgentToolConfig' | 'getAgentToolConfig';
type DiagramOperationKey = keyof Required<DiagramOperationKinds>;

const DEFAULT_COMMAND_IDS: Record<DiagramCommandKey, string> = {
Expand Down
13 changes: 2 additions & 11 deletions packages/diagram-client/test/client-behavior-flags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@ const WFPY_BEHAVIOR = {
graphSourceNavigation: false,
networkPropertySections: false,
networkNavigationLabels: false,
noneSentinel: 'wfpy-none',
scriptInterpreterCommands: ['python', 'python3']
noneSentinel: 'wfpy-none'
};

const CALPY_BEHAVIOR = {
graphSourceNavigation: true,
networkPropertySections: true,
networkNavigationLabels: true,
noneSentinel: 'wfpy-none',
scriptInterpreterCommands: ['python', 'python3']
noneSentinel: 'wfpy-none'
};

function inject(behavior: unknown): void {
Expand All @@ -48,10 +46,6 @@ describe('clientBehavior flag consumption', () => {
expect(cb.networkNavigationLabels === true).toBe(false);
// property-panel CLI-tools "none" sentinel
expect(cb.noneSentinel).toBe('wfpy-none');
// views.ts icon detection
expect((cb.scriptInterpreterCommands ?? []).includes('python')).toBe(true);
expect((cb.scriptInterpreterCommands ?? []).includes('python3')).toBe(true);
expect((cb.scriptInterpreterCommands ?? []).includes('bash')).toBe(false);
});

it('resolves the calpy truth table (network behaviors on)', () => {
Expand All @@ -61,8 +55,6 @@ describe('clientBehavior flag consumption', () => {
expect(cb.networkPropertySections === true).toBe(true);
expect(cb.networkNavigationLabels === true).toBe(true);
expect(cb.noneSentinel).toBe('wfpy-none');
expect((cb.scriptInterpreterCommands ?? []).includes('python')).toBe(true);
expect((cb.scriptInterpreterCommands ?? []).includes('python3')).toBe(true);
});

it('reads as all-off with no sentinel when nothing is injected (dev/standalone)', () => {
Expand All @@ -71,7 +63,6 @@ describe('clientBehavior flag consumption', () => {
expect(cb.networkPropertySections === true).toBe(false);
expect(cb.networkNavigationLabels === true).toBe(false);
expect(cb.noneSentinel).toBeUndefined();
expect((cb.scriptInterpreterCommands ?? []).includes('python')).toBe(false);
});

it('ignores a non-object clientBehavior payload', () => {
Expand Down
22 changes: 5 additions & 17 deletions packages/diagram-server/src/server/mcp-platform-tool-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,14 @@ import {
type McpToolResult
} from '@eclipse-glsp/server-mcp';
import { jsonSchemaToZodShape } from './json-schema-to-zod';
import type { InProcessChatTool } from '@dialogram/shared';

/**
* Structural shape of an in-process, read-only chat tool bridged into MCP. Mirrors the
* platform's `InProcessChatTool` without importing it, keeping diagram-server free of an
* extension-core dependency. `handler` receives the session's scoped file and the tool args.
* An in-process chat tool as the bridge sees it: the platform's `InProcessChatTool`, read-only.
* A tool whose `mutates` is true is never bridged here (the generated handler inherits
* `readOnlyHint = true`); {@link bridgeableChatTools} filters it out (see `mcp-diagram-module.ts`).
*/
export interface PlatformMcpTool {
readonly name: string;
readonly description: string;
readonly inputSchema: Record<string, unknown>;
handler(file: string, args: Record<string, unknown>): string | Promise<string>;
/**
* When `true`, this tool WRITES source and MUST NOT be bridged here: the generated
* handler inherits `readOnlyHint = true`, so an auto-approving MCP client could invoke
* it and mutate files unconfirmed. Locked design (approach B): mutation-capable tools
* ride the GLSP-MCP built-in operation tools only. {@link bridgeableChatTools} filters
* these out (see `mcp-diagram-module.ts`).
*/
readonly mutates?: boolean;
}
export type PlatformMcpTool = Readonly<InProcessChatTool>;

/** Diagram-scope input = the base `{ sessionId }` plus whatever the tool's schema declares. */
type PlatformToolInput = { sessionId: string } & Record<string, unknown>;
Expand Down
Loading
Loading