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';