diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1821c1..e1787a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,18 @@ platform API version.
### Removed
+- **The `@path` annotation editor and its wizard template.** No producer has
+ a `@path` decorator: wfpy sets a workflow's tool search paths with
+ `@config(path=[...])`. The property panel's editor never rendered, and the
+ wizard's "external tool search paths" template wrote `@path(...)`, which the
+ Python source cannot import. A `path` annotation a project defines itself
+ still opens in the generic editor.
+- **Agent and tool keys nothing reads**: `wf:toolSpec` on tool nodes (the
+ external-actor flag stays), `cal:agentStateful` and its constant
+ `WorkflowDiagramMetadata.AGENT_STATEFUL` (a removal from the shared API, for
+ the next minor version), the never-called agent-metadata reset in the model
+ storage, and the property panel's unused tab builder with its stylesheet
+ rules, and its inline-Markdown renderer with the helper only it called.
- **Element types the model never emits**, with their client classes and
views, server mappings, shape hints and layout branches: `node:proxy`, the
two collapsed nodes, the if-branch, if-join, foreach-join and inline-if
diff --git a/packages/diagram-client/src/editing-action-handlers.ts b/packages/diagram-client/src/editing-action-handlers.ts
index 6fe08f5..735cf31 100644
--- a/packages/diagram-client/src/editing-action-handlers.ts
+++ b/packages/diagram-client/src/editing-action-handlers.ts
@@ -585,7 +585,6 @@ export class WorkflowEditAnnotationsActionHandler implements IActionHandler {
items: [
{ id: 'tool', label: '@tool (external execution)', description: 'cmd, args, inheritStdio, ...' },
{ id: 'viewer', label: '@viewer (open outputs in VS Code)', description: 'open, diff, openWith (viewType)' },
- { id: 'path', label: '@path (external tool search paths)', description: 'paths=["./bin", "./tools"]' },
{ id: 'custom', label: 'Custom annotation…', description: 'Enter any name and arguments' }
],
placeHolder: 'Choose an annotation template'
@@ -720,41 +719,6 @@ export class WorkflowEditAnnotationsActionHandler implements IActionHandler {
if (title === undefined) return;
annotationText = `@viewer(\n action="diff",\n inputs=${inputsExpr.trim()},\n token="last",\n title=${toWfStringLiteral(title)}\n)`;
}
- } else if (template === 'path') {
- annotationName = 'path';
-
- const paths: string[] = [];
-
- for (;;) {
- const next = await VscodeUi.instance.inputBox({
- prompt: paths.length === 0 ? 'Add path (e.g. ./bin)' : 'Add another path (optional)'
- });
- if (next === undefined) return;
- const trimmed = next.trim();
- if (trimmed) {
- paths.push(trimmed);
- }
-
- if (paths.length === 0) {
- continue;
- }
-
- const addMore = await VscodeUi.instance.quickPick({
- items: [
- { id: 'yes', label: 'Add another path' },
- { id: 'no', label: 'Done' }
- ],
- placeHolder: 'Add another path?'
- });
- if (addMore !== 'yes') {
- break;
- }
- }
-
- if (paths.length === 0) return;
-
- const listExpr = `[${paths.map(p => toWfStringLiteral(p)).join(', ')}]`;
- annotationText = `@path(\n paths=${listExpr}\n)`;
} else {
const name = await VscodeUi.instance.inputBox({
prompt: 'Annotation name (without @)',
diff --git a/packages/diagram-client/src/markdown.ts b/packages/diagram-client/src/markdown.ts
index 2267d1b..4332697 100644
--- a/packages/diagram-client/src/markdown.ts
+++ b/packages/diagram-client/src/markdown.ts
@@ -24,11 +24,6 @@ export function renderDocumentMarkdownSafe(src: string): string {
return DOMPurify.sanitize(documentMd.render(src));
}
-/** Render inline Markdown (no block wrapping) to sanitized HTML. */
-export function renderInlineMarkdownSafe(src: string): string {
- return DOMPurify.sanitize(md.renderInline(src));
-}
-
/**
* Cheap heuristic for "is this worth rendering as Markdown?" — used to keep
* plain text in pre-wrap form instead of paying markdown layout for it.
diff --git a/packages/diagram-client/src/property-panel.css b/packages/diagram-client/src/property-panel.css
index 490297f..265f336 100644
--- a/packages/diagram-client/src/property-panel.css
+++ b/packages/diagram-client/src/property-panel.css
@@ -47,64 +47,6 @@
container-name: pp-panel;
}
-/* ============================================
- Property Panel Tabs
- ============================================ */
-
-.property-tabs-header {
- position: sticky;
- top: 0;
- z-index: 10;
- display: flex;
- flex-direction: row;
- overflow-x: auto;
- border-bottom: 1px solid var(--vscode-panel-border);
- background: var(--vscode-editorGroupHeader-tabsBackground, #2d2d2d);
- -ms-overflow-style: none;
- scrollbar-width: none;
- flex-shrink: 0;
-}
-
-.property-tabs-header::-webkit-scrollbar {
- display: none;
-}
-
-.property-tab-btn {
- flex: 0 0 auto;
- padding: 8px 12px;
- background: transparent;
- border: none;
- border-bottom: 1px solid transparent;
- color: var(--vscode-tab-inactiveForeground, #8b8b8b);
- cursor: pointer;
- font-size: 11px;
- font-weight: 600;
- letter-spacing: 0.3px;
- text-transform: uppercase;
- transition: color 0.15s ease, border-color 0.15s ease;
- outline: none;
-}
-
-.property-tab-btn:hover {
- color: var(--vscode-tab-hoverForeground, #cccccc);
- background: var(--vscode-list-hoverBackground, rgba(90, 93, 94, 0.31));
-}
-
-.property-tab-btn.active {
- color: var(--vscode-tab-activeForeground, #ffffff);
- border-bottom: 1px solid var(--vscode-focusBorder, #007fd4);
-}
-
-.property-tab-content {
- display: none;
- flex-direction: column;
- height: 100%;
-}
-
-.property-tab-content.active {
- display: flex;
-}
-
.property-panel.collapsed {
transform: translateX(-100%);
}
@@ -588,24 +530,6 @@
letter-spacing: 0.6px;
}
-/* Tabs: minimal, accent underline. */
-.property-tabs-header {
- background: transparent;
- border-bottom: 1px solid var(--pp-border);
-}
-.property-tab-btn {
- color: var(--pp-muted);
- border-bottom-color: transparent;
-}
-.property-tab-btn:hover {
- color: var(--vscode-foreground);
- background: transparent;
-}
-.property-tab-btn.active {
- color: var(--vscode-foreground);
- border-bottom-color: var(--pp-accent);
-}
-
/* Sections: plain header (no fill), hairline separators between sections. */
.property-section {
border-bottom: 1px solid var(--pp-border);
diff --git a/packages/diagram-client/src/property-panel.ts b/packages/diagram-client/src/property-panel.ts
index 947a353..df46318 100644
--- a/packages/diagram-client/src/property-panel.ts
+++ b/packages/diagram-client/src/property-panel.ts
@@ -5,7 +5,6 @@ import { ISelectionListener, IActionDispatcher, TYPES } from '@eclipse-glsp/clie
import {
renderMarkdownSafe,
renderDocumentMarkdownSafe,
- renderInlineMarkdownSafe,
looksLikeMarkdown as looksLikeMarkdownShared,
} from './markdown';
import { html, render, nothing, type TemplateResult } from 'lit';
@@ -341,8 +340,7 @@ export class PropertyPanel implements ISelectionListener, IGModelRootListener {
const section = this.buildAgentContextSection(args);
if (!section) return;
- const agentTab = content.querySelector('.property-tab-content[data-tab-id="agent"]') as HTMLElement | null;
- (agentTab ?? content).appendChild(section);
+ content.appendChild(section);
this.scrollAgentToLatest(section);
}
@@ -482,42 +480,6 @@ export class PropertyPanel implements ISelectionListener, IGModelRootListener {
container?.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
- protected createTabs(container: HTMLElement, tabs: Array<{ id: string, label: string, buildContent: (content: HTMLElement) => void }>): void {
- const header = document.createElement('div');
- header.className = 'property-tabs-header';
-
- const bodyWrap = document.createElement('div');
- bodyWrap.style.flex = '1';
- bodyWrap.style.overflowY = 'auto';
-
- let first = true;
- for (const tab of tabs) {
- const btn = document.createElement('button');
- btn.className = 'property-tab-btn' + (first ? ' active' : '');
- btn.textContent = tab.label;
-
- const tabContent = document.createElement('div');
- tabContent.className = 'property-tab-content' + (first ? ' active' : '');
- tabContent.dataset.tabId = tab.id;
-
- btn.addEventListener('click', () => {
- Array.from(header.querySelectorAll('.property-tab-btn')).forEach(b => b.classList.remove('active'));
- Array.from(bodyWrap.querySelectorAll('.property-tab-content')).forEach(c => c.classList.remove('active'));
- btn.classList.add('active');
- tabContent.classList.add('active');
- });
-
- tab.buildContent(tabContent);
-
- header.appendChild(btn);
- bodyWrap.appendChild(tabContent);
- first = false;
- }
-
- container.appendChild(header);
- container.appendChild(bodyWrap);
- }
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected updateContentForNode(node: any, content: HTMLElement, requestAgentDiscovery = true): void {
const args = node.args || {};
@@ -926,13 +888,6 @@ export class PropertyPanel implements ISelectionListener, IGModelRootListener {
return renderMarkdownSafe(md);
}
- /**
- * Convert inline markdown (bold, italic, code, links) to sanitized HTML.
- */
- protected inlineMarkdown(text: string): string {
- return renderInlineMarkdownSafe(text);
- }
-
/**
* Shorten a file path to just the last 2 path segments for display.
*/
@@ -3545,186 +3500,26 @@ export class PropertyPanel implements ISelectionListener, IGModelRootListener {
body.appendChild(backendGrid);
}
- if (name === 'path') {
- if (argsMap.has('path') && !argsMap.has('paths')) {
- argsMap.set('paths', argsMap.get('path') ?? '');
- argsMap.delete('path');
- }
-
- const pathsContainer = document.createElement('div');
- pathsContainer.className = 'annotation-args-container';
-
- const pathsHeader = document.createElement('div');
- pathsHeader.className = 'annotation-args-header';
-
- const pathsTitle = document.createElement('div');
- pathsTitle.className = 'annotation-args-title';
- pathsTitle.textContent = 'paths (list)';
-
- const pathsMode = document.createElement('div');
- pathsMode.className = 'annotation-args-mode';
-
- const toRawBtn = document.createElement('button');
- toRawBtn.type = 'button';
- toRawBtn.className = 'mini-btn';
- toRawBtn.textContent = 'Raw…';
-
- const toListBtn = document.createElement('button');
- toListBtn.type = 'button';
- toListBtn.className = 'mini-btn';
- toListBtn.textContent = 'List…';
-
- pathsMode.appendChild(toListBtn);
- pathsMode.appendChild(toRawBtn);
- pathsHeader.appendChild(pathsTitle);
- pathsHeader.appendChild(pathsMode);
-
- const pathsHelp = document.createElement('div');
- pathsHelp.className = 'annotation-args-help';
- pathsHelp.textContent = 'Each row is a directory path (string literal).';
-
- const pathsList = document.createElement('div');
- pathsList.className = 'annotation-args-list';
-
- const addPathBtn = document.createElement('button');
- addPathBtn.type = 'button';
- addPathBtn.className = 'mini-btn';
- addPathBtn.textContent = 'Add path';
-
- const rawRow = document.createElement('div');
- rawRow.className = 'annotation-field';
- const rawLabel = document.createElement('div');
- rawLabel.className = 'annotation-label';
- rawLabel.textContent = 'paths (expression)';
- const rawTextarea = document.createElement('textarea');
- rawTextarea.className = 'annotation-input annotation-textarea';
- rawTextarea.rows = 3;
- rawTextarea.placeholder = 'e.g. ["./bin", "./tools"]';
- rawRow.appendChild(rawLabel);
- rawRow.appendChild(rawTextarea);
-
- let pathItems: string[] = [];
- let mode: 'list' | 'raw' = 'list';
-
- const renderPathList = () => {
- pathsList.innerHTML = '';
- pathItems.forEach((val, idx) => {
- const row = document.createElement('div');
- row.className = 'annotation-args-item';
-
- const idxEl = document.createElement('div');
- idxEl.className = 'annotation-args-index';
- idxEl.textContent = String(idx);
-
- const inp = document.createElement('input');
- inp.className = 'annotation-input annotation-args-input';
- inp.value = val;
- inp.placeholder = 'path';
- inp.addEventListener('input', () => {
- pathItems[idx] = inp.value;
- argsMap.set('paths', this.serializeWfStringListExpr(pathItems));
- });
-
- const rm = document.createElement('button');
- rm.type = 'button';
- rm.className = 'mini-btn danger icon-only';
- rm.title = 'Remove path';
- rm.setAttribute('aria-label', 'Remove path');
- const rmIcon = document.createElement('span');
- rmIcon.className = 'codicon codicon-remove';
- rm.appendChild(rmIcon);
- rm.addEventListener('click', () => {
- pathItems.splice(idx, 1);
- argsMap.set('paths', this.serializeWfStringListExpr(pathItems));
- renderPathList();
- });
-
- row.appendChild(idxEl);
- row.appendChild(inp);
- row.appendChild(rm);
- pathsList.appendChild(row);
- });
- };
-
- const setMode = (next: 'list' | 'raw') => {
- mode = next;
- const showList = mode === 'list';
- pathsList.style.display = showList ? '' : 'none';
- addPathBtn.style.display = showList ? '' : 'none';
- pathsHelp.style.display = showList ? '' : 'none';
- rawRow.style.display = showList ? 'none' : '';
- };
-
- addPathBtn.addEventListener('click', () => {
- pathItems.push('');
- argsMap.set('paths', this.serializeWfStringListExpr(pathItems));
- renderPathList();
- });
-
- rawTextarea.addEventListener('input', () => {
- argsMap.set('paths', rawTextarea.value);
- });
-
- toRawBtn.addEventListener('click', () => {
- rawTextarea.value = argsMap.get('paths') ?? '';
- setMode('raw');
- });
-
- toListBtn.addEventListener('click', () => {
- const parsed = this.tryParseWfStringListExpr(argsMap.get('paths') ?? '');
- if (!parsed) {
- VscodeUi.instance.infoMessage('Cannot switch to list mode: paths is not a list of string literals.');
- setMode('raw');
- return;
- }
- pathItems = parsed;
- argsMap.set('paths', this.serializeWfStringListExpr(pathItems));
- renderPathList();
- setMode('list');
- });
-
- const initialPathsExpr = argsMap.get('paths') ?? '';
- const parsed = this.tryParseWfStringListExpr(initialPathsExpr);
- if (parsed) {
- pathItems = parsed;
- argsMap.set('paths', this.serializeWfStringListExpr(pathItems));
- renderPathList();
- setMode('list');
- } else {
- rawTextarea.value = initialPathsExpr;
- setMode('raw');
- }
-
- pathsContainer.appendChild(pathsHeader);
- pathsContainer.appendChild(pathsHelp);
- pathsContainer.appendChild(pathsList);
- pathsContainer.appendChild(addPathBtn);
- pathsContainer.appendChild(rawRow);
- body.appendChild(pathsContainer);
- }
-
// Show remaining args (custom annotation args, or tool extras)
const reserved = new Set(
name === 'tool'
? ['cmd', 'args', 'inheritStdio']
- : (name === 'path'
- ? ['paths', 'path']
- : (name === 'agent' ? [
- // Identity / model
- 'prompt', 'claudeAgent', 'skill', 'model', 'provider', 'endpoint',
- 'timeoutMs', 'contextBudget', 'stateful',
- // Backend / runtime controls (dropdowns above)
- 'truncationStrategy', 'fireableWithoutInput', 'transport', 'connector', 'mode', 'cliToolsMode', 'reasoningEffort',
- // Sub-block toggles
- 'useClaudeAgent', 'useSkill', 'usePrompt', 'useSkillHooks',
- // MCP block
- 'useMcp', 'mcpServers', 'mcpServerConfigs', 'mcpTransport',
- // LSP block (+ shorthand scalars)
- 'useLsp', 'lspServers', 'lspServerConfigs', 'lspCommand', 'lspArgs',
- 'lspLanguageId', 'lspExtraFlags', 'lspPort', 'lspSeverityThreshold', 'lspMaxRepairAttempts',
- // Output validators editor
- 'outputValidators'
- ] : []))
+ : (name === 'agent' ? [
+ // Identity / model
+ 'prompt', 'claudeAgent', 'skill', 'model', 'provider', 'endpoint',
+ 'timeoutMs', 'contextBudget', 'stateful',
+ // Backend / runtime controls (dropdowns above)
+ 'truncationStrategy', 'fireableWithoutInput', 'transport', 'connector', 'mode', 'cliToolsMode', 'reasoningEffort',
+ // Sub-block toggles
+ 'useClaudeAgent', 'useSkill', 'usePrompt', 'useSkillHooks',
+ // MCP block
+ 'useMcp', 'mcpServers', 'mcpServerConfigs', 'mcpTransport',
+ // LSP block (+ shorthand scalars)
+ 'useLsp', 'lspServers', 'lspServerConfigs', 'lspCommand', 'lspArgs',
+ 'lspLanguageId', 'lspExtraFlags', 'lspPort', 'lspSeverityThreshold', 'lspMaxRepairAttempts',
+ // Output validators editor
+ 'outputValidators'
+ ] : [])
);
// Remaining args = genuinely custom annotation args (no dedicated control
// above). Each renders as an editable key/value row with its own remove button;
diff --git a/packages/diagram-client/test/fixtures/property-selectors-baseline.json b/packages/diagram-client/test/fixtures/property-selectors-baseline.json
index 5eb3979..1682907 100644
--- a/packages/diagram-client/test/fixtures/property-selectors-baseline.json
+++ b/packages/diagram-client/test/fixtures/property-selectors-baseline.json
@@ -135,13 +135,6 @@
".property-section-header:hover": 3,
".property-section.collapsed .property-section-content": 1,
".property-section.collapsed .property-section-header .section-icon": 1,
-".property-tab-btn": 2,
-".property-tab-btn.active": 2,
-".property-tab-btn:hover": 2,
-".property-tab-content": 1,
-".property-tab-content.active": 1,
-".property-tabs-header": 2,
-".property-tabs-header::-webkit-scrollbar": 1,
".property-value": 1,
".property-value.read-only": 1,
".property-value.read-only .mini-btn": 1
diff --git a/packages/diagram-client/test/run-agent-outputs-render.test.ts b/packages/diagram-client/test/run-agent-outputs-render.test.ts
index d07d4d1..4a24237 100644
--- a/packages/diagram-client/test/run-agent-outputs-render.test.ts
+++ b/packages/diagram-client/test/run-agent-outputs-render.test.ts
@@ -11,7 +11,6 @@ import { describe, expect, it, vi } from 'vitest';
vi.mock('../src/markdown', () => ({
renderMarkdownSafe: (src: string) => `${src}`,
- renderInlineMarkdownSafe: (src: string) => src,
looksLikeMarkdown: () => true
}));
diff --git a/packages/diagram-server/src/model/graph-gmodel-source.ts b/packages/diagram-server/src/model/graph-gmodel-source.ts
index 62a6ed4..b0be471 100644
--- a/packages/diagram-server/src/model/graph-gmodel-source.ts
+++ b/packages/diagram-server/src/model/graph-gmodel-source.ts
@@ -552,7 +552,6 @@ export class GraphGModelSource {
if (node.meta['tool']) {
gnode.args = {
...(gnode.args ?? {}),
- 'wf:toolSpec': node.meta['tool'],
[WorkflowDiagramMetadata.IS_EXTERNAL_ACTOR]: true
};
}
diff --git a/packages/diagram-server/src/server/source-model-storage.ts b/packages/diagram-server/src/server/source-model-storage.ts
index 0bb2564..f069e13 100644
--- a/packages/diagram-server/src/server/source-model-storage.ts
+++ b/packages/diagram-server/src/server/source-model-storage.ts
@@ -1523,46 +1523,6 @@ export class WorkflowSourceModelStorage implements SourceModelStorage {
visit(root);
}
- /**
- * Remove all agent context metadata from GNodes so it can be cleanly re-applied
- * without leftover data from a previous cycle.
- *
- * Note: execution-glow state (`IS_EXECUTING`, `cal-node-active` CSS) is NOT cleared
- * here. It is cleared synchronously inside `applyViewerOverlayToEdges()` right before
- * the new overlay is applied. This avoids a race condition where a stale
- * `ComputedBoundsAction` arriving between the `await` gap (clear ↔ overlay read)
- * could submit the model without the active-glow CSS class, causing the glow to
- * flicker/disappear.
- */
- private clearAgentContextMetadata(root: GModelRoot): void {
- const agentKeys: string[] = [
- WorkflowDiagramMetadata.AGENT_CHAT_HISTORY,
- WorkflowDiagramMetadata.AGENT_FIRE_COUNT,
- WorkflowDiagramMetadata.AGENT_CONTEXT_BUDGET,
- WorkflowDiagramMetadata.AGENT_TRUNCATION_STRATEGY,
- WorkflowDiagramMetadata.AGENT_MODEL,
- WorkflowDiagramMetadata.AGENT_STATEFUL,
- WorkflowDiagramMetadata.AGENT_SKILL_STATUS,
- ];
- const visit = (element: any): void => {
- if (element instanceof GNode) {
- const args = element.args as Record | undefined;
- if (args) {
- for (const key of agentKeys) {
- delete args[key];
- }
- }
- }
- const children = element.children as any[] | undefined;
- if (Array.isArray(children)) {
- for (const child of children) {
- visit(child);
- }
- }
- };
- visit(root);
- }
-
/**
* Tracks the in-flight deferred agent-enrichment discovery so tests can await it. Not used for
* correctness in production (fire-and-forget); convergence relies on the warm cache, not this
@@ -1935,7 +1895,6 @@ export class WorkflowSourceModelStorage implements SourceModelStorage {
const byInstanceName = new Map;
@@ -1943,7 +1902,6 @@ export class WorkflowSourceModelStorage implements SourceModelStorage {
const byEntityName = new Map;
@@ -1985,7 +1943,6 @@ export class WorkflowSourceModelStorage implements SourceModelStorage {
if (entry.model) {
(element.args as any)[WorkflowDiagramMetadata.AGENT_MODEL] = entry.model;
}
- (element.args as any)[WorkflowDiagramMetadata.AGENT_STATEFUL] = entry.stateful;
}
}
const children = element.children as any[] | undefined;
@@ -2017,7 +1974,6 @@ export class WorkflowSourceModelStorage implements SourceModelStorage {
entityName: string;
fireCount: number;
model?: string;
- stateful: boolean;
contextBudget: number;
truncationStrategy: string;
chatHistory: Array<{ role: string; content: string }>;
@@ -2045,7 +2001,6 @@ export class WorkflowSourceModelStorage implements SourceModelStorage {
entityName: string;
fireCount: number;
model?: string;
- stateful: boolean;
contextBudget: number;
truncationStrategy: string;
chatHistory: Array<{ role: string; content: string }>;
diff --git a/packages/shared/src/diagram-types.ts b/packages/shared/src/diagram-types.ts
index abdcb2d..f0ed19b 100644
--- a/packages/shared/src/diagram-types.ts
+++ b/packages/shared/src/diagram-types.ts
@@ -317,8 +317,6 @@ export namespace WorkflowDiagramMetadata {
export const AGENT_TRUNCATION_STRATEGY = 'cal:agentTruncationStrategy';
/** Model identifier (e.g. "openai/gpt-5-mini"). */
export const AGENT_MODEL = 'cal:agentModel';
- /** Whether the agent is stateful. */
- export const AGENT_STATEFUL = 'cal:agentStateful';
/** Skill status summary for agent node. */
export const AGENT_SKILL_STATUS = 'cal:agentSkillStatus';