@angflow/angular 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/base.css +18 -0
- package/dist/esm/lib/agent/agent-bridge.service.d.ts +6 -2
- package/dist/esm/lib/agent/agent-bridge.service.d.ts.map +1 -1
- package/dist/esm/lib/agent/agent-bridge.service.js +228 -5
- package/dist/esm/lib/agent/agent-bridge.service.js.map +1 -1
- package/dist/esm/lib/agent/chat/agent-chat.component.d.ts +20 -0
- package/dist/esm/lib/agent/chat/agent-chat.component.d.ts.map +1 -0
- package/dist/esm/lib/agent/chat/agent-chat.component.js +174 -0
- package/dist/esm/lib/agent/chat/agent-chat.component.js.map +1 -0
- package/dist/esm/lib/agent/chat/agent-chat.service.d.ts +43 -0
- package/dist/esm/lib/agent/chat/agent-chat.service.d.ts.map +1 -0
- package/dist/esm/lib/agent/chat/agent-chat.service.js +226 -0
- package/dist/esm/lib/agent/chat/agent-chat.service.js.map +1 -0
- package/dist/esm/lib/agent/chat/default-system-prompt.d.ts +6 -0
- package/dist/esm/lib/agent/chat/default-system-prompt.d.ts.map +1 -0
- package/dist/esm/lib/agent/chat/default-system-prompt.js +14 -0
- package/dist/esm/lib/agent/chat/default-system-prompt.js.map +1 -0
- package/dist/esm/lib/agent/chat/index.d.ts +7 -0
- package/dist/esm/lib/agent/chat/index.d.ts.map +1 -0
- package/dist/esm/lib/agent/chat/index.js +5 -0
- package/dist/esm/lib/agent/chat/index.js.map +1 -0
- package/dist/esm/lib/agent/chat/provide-agent-chat.d.ts +29 -0
- package/dist/esm/lib/agent/chat/provide-agent-chat.d.ts.map +1 -0
- package/dist/esm/lib/agent/chat/provide-agent-chat.js +40 -0
- package/dist/esm/lib/agent/chat/provide-agent-chat.js.map +1 -0
- package/dist/esm/lib/agent/chat/types.d.ts +77 -0
- package/dist/esm/lib/agent/chat/types.d.ts.map +1 -0
- package/dist/esm/lib/agent/chat/types.js +9 -0
- package/dist/esm/lib/agent/chat/types.js.map +1 -0
- package/dist/esm/lib/agent/index.d.ts +1 -0
- package/dist/esm/lib/agent/index.d.ts.map +1 -1
- package/dist/esm/lib/agent/index.js +1 -0
- package/dist/esm/lib/agent/index.js.map +1 -1
- package/dist/esm/lib/agent/provide-agent-bridge.d.ts +8 -0
- package/dist/esm/lib/agent/provide-agent-bridge.d.ts.map +1 -1
- package/dist/esm/lib/agent/provide-agent-bridge.js +2 -1
- package/dist/esm/lib/agent/provide-agent-bridge.js.map +1 -1
- package/dist/esm/lib/agent/tool-schemas.d.ts.map +1 -1
- package/dist/esm/lib/agent/tool-schemas.js +138 -0
- package/dist/esm/lib/agent/tool-schemas.js.map +1 -1
- package/dist/esm/lib/components/a11y-descriptions/a11y-descriptions.component.js +3 -3
- package/dist/esm/lib/components/a11y-descriptions/a11y-descriptions.component.js.map +1 -1
- package/dist/esm/lib/components/attribution/attribution.component.js +3 -3
- package/dist/esm/lib/components/attribution/attribution.component.js.map +1 -1
- package/dist/esm/lib/components/background/background.component.js +3 -3
- package/dist/esm/lib/components/background/background.component.js.map +1 -1
- package/dist/esm/lib/components/connection-line/connection-line.component.js +3 -3
- package/dist/esm/lib/components/connection-line/connection-line.component.js.map +1 -1
- package/dist/esm/lib/components/controls/controls.component.js +3 -3
- package/dist/esm/lib/components/controls/controls.component.js.map +1 -1
- package/dist/esm/lib/components/edge-label-renderer/edge-label-renderer.component.js +3 -3
- package/dist/esm/lib/components/edge-label-renderer/edge-label-renderer.component.js.map +1 -1
- package/dist/esm/lib/components/edge-toolbar/edge-toolbar.component.d.ts +2 -2
- package/dist/esm/lib/components/edge-toolbar/edge-toolbar.component.js +3 -3
- package/dist/esm/lib/components/edge-toolbar/edge-toolbar.component.js.map +1 -1
- package/dist/esm/lib/components/edges/base-edge.component.js +3 -3
- package/dist/esm/lib/components/edges/base-edge.component.js.map +1 -1
- package/dist/esm/lib/components/edges/bezier-edge.component.js +3 -3
- package/dist/esm/lib/components/edges/bezier-edge.component.js.map +1 -1
- package/dist/esm/lib/components/edges/edge-text.component.js +3 -3
- package/dist/esm/lib/components/edges/edge-text.component.js.map +1 -1
- package/dist/esm/lib/components/edges/simple-bezier-edge.component.js +3 -3
- package/dist/esm/lib/components/edges/simple-bezier-edge.component.js.map +1 -1
- package/dist/esm/lib/components/edges/smooth-step-edge.component.js +3 -3
- package/dist/esm/lib/components/edges/smooth-step-edge.component.js.map +1 -1
- package/dist/esm/lib/components/edges/step-edge.component.js +3 -3
- package/dist/esm/lib/components/edges/step-edge.component.js.map +1 -1
- package/dist/esm/lib/components/edges/straight-edge.component.js +3 -3
- package/dist/esm/lib/components/edges/straight-edge.component.js.map +1 -1
- package/dist/esm/lib/components/handle/handle.component.js +3 -3
- package/dist/esm/lib/components/handle/handle.component.js.map +1 -1
- package/dist/esm/lib/components/handle-group/handle-group.component.d.ts +1 -1
- package/dist/esm/lib/components/handle-group/handle-group.component.js +3 -3
- package/dist/esm/lib/components/handle-group/handle-group.component.js.map +1 -1
- package/dist/esm/lib/components/handle-group/handle-row.component.js +3 -3
- package/dist/esm/lib/components/handle-group/handle-row.component.js.map +1 -1
- package/dist/esm/lib/components/minimap/minimap.component.d.ts +17 -8
- package/dist/esm/lib/components/minimap/minimap.component.d.ts.map +1 -1
- package/dist/esm/lib/components/minimap/minimap.component.js +63 -39
- package/dist/esm/lib/components/minimap/minimap.component.js.map +1 -1
- package/dist/esm/lib/components/ng-flow-provider/ng-flow-provider.component.js +3 -3
- package/dist/esm/lib/components/ng-flow-provider/ng-flow-provider.component.js.map +1 -1
- package/dist/esm/lib/components/node-resizer/node-resizer.component.js +3 -3
- package/dist/esm/lib/components/node-resizer/node-resizer.component.js.map +1 -1
- package/dist/esm/lib/components/node-toolbar/node-toolbar.component.js +3 -3
- package/dist/esm/lib/components/node-toolbar/node-toolbar.component.js.map +1 -1
- package/dist/esm/lib/components/nodes/default-node.component.js +3 -3
- package/dist/esm/lib/components/nodes/default-node.component.js.map +1 -1
- package/dist/esm/lib/components/nodes/group-node.component.js +3 -3
- package/dist/esm/lib/components/nodes/group-node.component.js.map +1 -1
- package/dist/esm/lib/components/nodes/input-node.component.js +3 -3
- package/dist/esm/lib/components/nodes/input-node.component.js.map +1 -1
- package/dist/esm/lib/components/nodes/output-node.component.js +3 -3
- package/dist/esm/lib/components/nodes/output-node.component.js.map +1 -1
- package/dist/esm/lib/components/nodes/template-node.component.d.ts +48 -0
- package/dist/esm/lib/components/nodes/template-node.component.d.ts.map +1 -0
- package/dist/esm/lib/components/nodes/template-node.component.js +209 -0
- package/dist/esm/lib/components/nodes/template-node.component.js.map +1 -0
- package/dist/esm/lib/components/panel/panel.component.js +3 -3
- package/dist/esm/lib/components/panel/panel.component.js.map +1 -1
- package/dist/esm/lib/components/selection-box/selection-box.component.js +3 -3
- package/dist/esm/lib/components/selection-box/selection-box.component.js.map +1 -1
- package/dist/esm/lib/components/viewport-portal/viewport-portal.component.js +3 -3
- package/dist/esm/lib/components/viewport-portal/viewport-portal.component.js.map +1 -1
- package/dist/esm/lib/container/edge-renderer/edge-renderer.component.d.ts.map +1 -1
- package/dist/esm/lib/container/edge-renderer/edge-renderer.component.js +9 -5
- package/dist/esm/lib/container/edge-renderer/edge-renderer.component.js.map +1 -1
- package/dist/esm/lib/container/ng-flow/ng-flow.component.d.ts +19 -1
- package/dist/esm/lib/container/ng-flow/ng-flow.component.d.ts.map +1 -1
- package/dist/esm/lib/container/ng-flow/ng-flow.component.js +28 -4
- package/dist/esm/lib/container/ng-flow/ng-flow.component.js.map +1 -1
- package/dist/esm/lib/container/node-renderer/node-renderer.component.d.ts +5 -0
- package/dist/esm/lib/container/node-renderer/node-renderer.component.d.ts.map +1 -1
- package/dist/esm/lib/container/node-renderer/node-renderer.component.js +71 -7
- package/dist/esm/lib/container/node-renderer/node-renderer.component.js.map +1 -1
- package/dist/esm/lib/container/pane/pane.component.js +3 -3
- package/dist/esm/lib/container/pane/pane.component.js.map +1 -1
- package/dist/esm/lib/container/viewport/viewport.component.js +3 -3
- package/dist/esm/lib/container/viewport/viewport.component.js.map +1 -1
- package/dist/esm/lib/directives/drag.directive.js +3 -3
- package/dist/esm/lib/directives/drag.directive.js.map +1 -1
- package/dist/esm/lib/directives/drop-zone.directive.js +3 -3
- package/dist/esm/lib/directives/drop-zone.directive.js.map +1 -1
- package/dist/esm/lib/directives/key-handler.directive.js +3 -3
- package/dist/esm/lib/directives/key-handler.directive.js.map +1 -1
- package/dist/esm/lib/directives/node-type.directive.js +3 -3
- package/dist/esm/lib/directives/node-type.directive.js.map +1 -1
- package/dist/esm/lib/layout/dagre-layout.d.ts +12 -0
- package/dist/esm/lib/layout/dagre-layout.d.ts.map +1 -0
- package/dist/esm/lib/layout/dagre-layout.js +13 -0
- package/dist/esm/lib/layout/dagre-layout.js.map +1 -0
- package/dist/esm/lib/layout/index.d.ts +4 -0
- package/dist/esm/lib/layout/index.d.ts.map +1 -0
- package/dist/esm/lib/layout/index.js +3 -0
- package/dist/esm/lib/layout/index.js.map +1 -0
- package/dist/esm/lib/layout/layout-nodes.d.ts +47 -0
- package/dist/esm/lib/layout/layout-nodes.d.ts.map +1 -0
- package/dist/esm/lib/layout/layout-nodes.js +49 -0
- package/dist/esm/lib/layout/layout-nodes.js.map +1 -0
- package/dist/esm/lib/public-api.d.ts +2 -1
- package/dist/esm/lib/public-api.d.ts.map +1 -1
- package/dist/esm/lib/public-api.js +4 -1
- package/dist/esm/lib/public-api.js.map +1 -1
- package/dist/esm/lib/services/flow-store.service.d.ts +52 -2
- package/dist/esm/lib/services/flow-store.service.d.ts.map +1 -1
- package/dist/esm/lib/services/flow-store.service.js +145 -3
- package/dist/esm/lib/services/flow-store.service.js.map +1 -1
- package/dist/esm/lib/services/ng-flow.service.d.ts +81 -0
- package/dist/esm/lib/services/ng-flow.service.d.ts.map +1 -1
- package/dist/esm/lib/services/ng-flow.service.js +127 -3
- package/dist/esm/lib/services/ng-flow.service.js.map +1 -1
- package/dist/esm/lib/types/index.d.ts +1 -0
- package/dist/esm/lib/types/index.d.ts.map +1 -1
- package/dist/esm/lib/types/index.js +1 -0
- package/dist/esm/lib/types/index.js.map +1 -1
- package/dist/esm/lib/types/node-template.d.ts +77 -0
- package/dist/esm/lib/types/node-template.d.ts.map +1 -0
- package/dist/esm/lib/types/node-template.js +10 -0
- package/dist/esm/lib/types/node-template.js.map +1 -0
- package/dist/esm/lib/utils/position-tween.d.ts +19 -0
- package/dist/esm/lib/utils/position-tween.d.ts.map +1 -0
- package/dist/esm/lib/utils/position-tween.js +25 -0
- package/dist/esm/lib/utils/position-tween.js.map +1 -0
- package/dist/esm/lib/utils/template-interpolation.d.ts +16 -0
- package/dist/esm/lib/utils/template-interpolation.d.ts.map +1 -0
- package/dist/esm/lib/utils/template-interpolation.js +51 -0
- package/dist/esm/lib/utils/template-interpolation.js.map +1 -0
- package/dist/style.css +18 -0
- package/package.json +78 -66
package/README.md
CHANGED
|
@@ -180,6 +180,59 @@ flowService.screenToFlowPosition({ x: event.clientX, y: event.clientY });
|
|
|
180
180
|
flowService.toObject(); // { nodes, edges, viewport }
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
## Floating Edges
|
|
184
|
+
|
|
185
|
+
Set `edgeMode="floating"` and edges attach wherever the line to the peer node's
|
|
186
|
+
center crosses each node's border — no handle declarations needed:
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<ng-flow [nodes]="nodes" [edges]="edges" edgeMode="floating" />
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Works with nodes that declare zero handles, which makes it ideal for
|
|
193
|
+
programmatic / agent-driven graphs. Note: a node with no handles cannot
|
|
194
|
+
originate an interactive drag-connection — declared handles still work for
|
|
195
|
+
starting connections while rendering stays floating. For per-edge control in
|
|
196
|
+
the default mode, set `floating` on individual handles instead:
|
|
197
|
+
`<ng-flow-handle type="source" [floating]="true" />`.
|
|
198
|
+
|
|
199
|
+
## Auto-Layout
|
|
200
|
+
|
|
201
|
+
`layoutNodes` is a pure dagre wrapper — feed it your nodes and edges, get back
|
|
202
|
+
a map of top-left positions:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { layoutNodes } from '@angflow/angular/layout'; // needs @dagrejs/dagre installed
|
|
206
|
+
|
|
207
|
+
const positions = layoutNodes(flow.getNodes(), flow.getEdges(), { direction: 'LR' });
|
|
208
|
+
flow.setNodePositions(positions);
|
|
209
|
+
|
|
210
|
+
// or in one call:
|
|
211
|
+
flow.applyLayout(layoutNodes, { direction: 'LR' });
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Options: `direction` (`'TB' | 'LR' | 'BT' | 'RL'`, default `'TB'`), `nodeSep`
|
|
215
|
+
(default 50), `rankSep` (default 80). Node dimensions resolve from
|
|
216
|
+
`measured` → `width`/`height` → `initialWidth`/`initialHeight` → 150×40. Any
|
|
217
|
+
function with the same shape plugs
|
|
218
|
+
into `applyLayout` (elk, custom grids, …).
|
|
219
|
+
|
|
220
|
+
## Animations
|
|
221
|
+
|
|
222
|
+
Turn on `[animate]` and the flow animates node entries (fade + scale) and
|
|
223
|
+
programmatic position changes (smooth tween, edges tracking mid-flight):
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<ng-flow [nodes]="nodes" [edges]="edges" [animate]="true" />
|
|
227
|
+
<!-- or tune the duration: -->
|
|
228
|
+
<ng-flow [animate]="{ duration: 200 }" />
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Animated paths: `setNodePositions`, `applyLayout`, and the agent bridge's
|
|
232
|
+
`layout_nodes` tool. Dragging is never animated, a drag cancels any in-flight
|
|
233
|
+
tween on that node, and everything is disabled under `prefers-reduced-motion`.
|
|
234
|
+
Per-call override: `flow.setNodePositions(positions, { animate: false })`.
|
|
235
|
+
|
|
183
236
|
## Architecture
|
|
184
237
|
|
|
185
238
|
- **Signal-based state** — Angular 17+ signals for fine-grained reactivity (no RxJS in the store)
|
package/dist/base.css
CHANGED
|
@@ -955,6 +955,24 @@ svg.xy-flow__connectionline {
|
|
|
955
955
|
}
|
|
956
956
|
|
|
957
957
|
|
|
958
|
+
/* Entry animation — applied by the node renderer when [animate] is enabled.
|
|
959
|
+
Uses the standalone `scale` property so it composes with the inline
|
|
960
|
+
translate() transform instead of overriding it. */
|
|
961
|
+
.xy-flow__node-enter {
|
|
962
|
+
animation: xy-flow-node-enter 300ms ease both;
|
|
963
|
+
transform-origin: 50% 50%;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
@keyframes xy-flow-node-enter {
|
|
967
|
+
from { opacity: 0; scale: 0.92; }
|
|
968
|
+
to { opacity: 1; scale: 1; }
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
@media (prefers-reduced-motion: reduce) {
|
|
972
|
+
.xy-flow__node-enter { animation: none; }
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
|
|
958
976
|
.xy-flow__node-input,
|
|
959
977
|
.xy-flow__node-default,
|
|
960
978
|
.xy-flow__node-output,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InjectionToken } from '@angular/core';
|
|
2
2
|
import type { NgFlowService } from '../services/ng-flow.service';
|
|
3
|
+
import type { AgentLayoutFn } from '../types/node-template';
|
|
3
4
|
import type { AgentHistoryOptions } from './history';
|
|
4
5
|
import type { AgentTransport } from './types';
|
|
5
6
|
import * as i0 from "@angular/core";
|
|
@@ -13,6 +14,8 @@ export declare const AGENT_ON_ERROR: InjectionToken<(err: unknown, ctx: {
|
|
|
13
14
|
transport?: AgentTransport;
|
|
14
15
|
method?: string;
|
|
15
16
|
}) => void>;
|
|
17
|
+
/** Optional host-provided layout function backing the `layout_nodes` tool. */
|
|
18
|
+
export declare const AGENT_LAYOUT: InjectionToken<AgentLayoutFn>;
|
|
16
19
|
/**
|
|
17
20
|
* Routes JSON-RPC requests from one or more transports to registered
|
|
18
21
|
* `NgFlowService` instances, and pushes change events back to the agent.
|
|
@@ -45,6 +48,7 @@ export declare class AngflowAgentBridge {
|
|
|
45
48
|
private readonly history;
|
|
46
49
|
private readonly handlers;
|
|
47
50
|
private readonly injector;
|
|
51
|
+
private readonly layoutFn;
|
|
48
52
|
private started;
|
|
49
53
|
private nextInProcessId;
|
|
50
54
|
private warnedOnBeforeDeleteBypass;
|
|
@@ -55,7 +59,7 @@ export declare class AngflowAgentBridge {
|
|
|
55
59
|
kind: 'transport-start' | 'transport-send' | 'dispatch';
|
|
56
60
|
transport?: AgentTransport;
|
|
57
61
|
method?: string;
|
|
58
|
-
}) => void) | null);
|
|
62
|
+
}) => void) | null, layoutFn: AgentLayoutFn | null);
|
|
59
63
|
private reportError;
|
|
60
64
|
/**
|
|
61
65
|
* Register a flow under `id`. The bridge subscribes to its `nodes`,
|
|
@@ -85,7 +89,7 @@ export declare class AngflowAgentBridge {
|
|
|
85
89
|
private resolveFlow;
|
|
86
90
|
private watchFlow;
|
|
87
91
|
private installHandlers;
|
|
88
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<AngflowAgentBridge, [{ optional: true; }, { optional: true; }, { optional: true; }]>;
|
|
92
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AngflowAgentBridge, [{ optional: true; }, { optional: true; }, { optional: true; }, { optional: true; }]>;
|
|
89
93
|
static ɵprov: i0.ɵɵInjectableDeclaration<AngflowAgentBridge>;
|
|
90
94
|
}
|
|
91
95
|
//# sourceMappingURL=agent-bridge.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-bridge.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/agent-bridge.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,cAAc,EAOf,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-bridge.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/agent-bridge.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,cAAc,EAOf,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAoB,MAAM,wBAAwB,CAAC;AAE9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAErD,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,SAAS,CAAC;;AAEjB,6DAA6D;AAC7D,eAAO,MAAM,gBAAgB,kCAAiE,CAAC;AAE/F,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,6CAEjC,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,cAAc,uBACnB,OAAO,OAAO;IAAE,IAAI,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAC/G,CAAC;AAEzB,8EAA8E;AAC9E,eAAO,MAAM,YAAY,+BAA0D,CAAC;AAiCpF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBACa,kBAAkB;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,WAAW,sCAAsB;IAE1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,0BAA0B,CAAS;IAE3C,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,mDAAwB;IAEhD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAEf;gBAG+B,UAAU,EAAE,cAAc,EAAE,GAAG,IAAI,EAC9B,cAAc,EAAE,mBAAmB,GAAG,KAAK,GAAG,IAAI,EACzD,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE;QAAE,IAAI,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,UAAU,CAAC;QAAC,SAAS,CAAC,EAAE,cAAc,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,EACzJ,QAAQ,EAAE,aAAa,GAAG,IAAI;IAWlE,OAAO,CAAC,WAAW;IASnB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,IAAI;IAuBrD,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAU5B,iCAAiC;IACjC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI9C;;;;;OAKG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBtF,OAAO,CAAC,KAAK;YAiBC,QAAQ;IAkGtB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,IAAI;IAYZ,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,SAAS;IAgDjB,OAAO,CAAC,eAAe;yCAhUZ,kBAAkB;6CAAlB,kBAAkB;CA20B9B"}
|
|
@@ -8,6 +8,8 @@ export const AGENT_TRANSPORTS = new InjectionToken('AngflowAgentTransports');
|
|
|
8
8
|
export const AGENT_HISTORY_OPTIONS = new InjectionToken('AngflowAgentHistoryOptions');
|
|
9
9
|
/** Optional error sink. Receives transport/dispatch failures that the bridge swallows. */
|
|
10
10
|
export const AGENT_ON_ERROR = new InjectionToken('AngflowAgentOnError');
|
|
11
|
+
/** Optional host-provided layout function backing the `layout_nodes` tool. */
|
|
12
|
+
export const AGENT_LAYOUT = new InjectionToken('AngflowAgentLayout');
|
|
11
13
|
const ERROR_INVALID_PARAMS = -32602;
|
|
12
14
|
const ERROR_METHOD_NOT_FOUND = -32601;
|
|
13
15
|
const ERROR_FLOW_NOT_FOUND = -32000;
|
|
@@ -50,7 +52,7 @@ const MUTATING_TOOLS = new Set([
|
|
|
50
52
|
* ```
|
|
51
53
|
*/
|
|
52
54
|
export class AngflowAgentBridge {
|
|
53
|
-
constructor(transports, historyOptions, onError) {
|
|
55
|
+
constructor(transports, historyOptions, onError, layoutFn) {
|
|
54
56
|
/** Schemas for every exposed tool — feed straight to a Claude / OpenAI tools array. */
|
|
55
57
|
this.toolSchemas = AGENT_TOOL_SCHEMAS;
|
|
56
58
|
this.flows = new Map();
|
|
@@ -65,6 +67,7 @@ export class AngflowAgentBridge {
|
|
|
65
67
|
this.history =
|
|
66
68
|
historyOptions === false ? null : new AgentHistory(historyOptions ?? undefined);
|
|
67
69
|
this.onError = onError ?? null;
|
|
70
|
+
this.layoutFn = layoutFn ?? null;
|
|
68
71
|
this.installHandlers();
|
|
69
72
|
this.start();
|
|
70
73
|
}
|
|
@@ -175,12 +178,13 @@ export class AngflowAgentBridge {
|
|
|
175
178
|
const flow = this.resolveFlow(params['flowId']);
|
|
176
179
|
const flowId = this.findFlowId(flow);
|
|
177
180
|
const isApplyChanges = req.method === 'apply_changes';
|
|
181
|
+
const isLayout = req.method === 'layout_nodes';
|
|
178
182
|
// Pre-mutation snapshot for history capture. Skipped for non-mutating tools.
|
|
179
183
|
// Shallow-clone each element so subsequent in-place mutations (notably
|
|
180
184
|
// the drag fast-path in FlowStore) can't retroactively corrupt the
|
|
181
185
|
// snapshot we already captured.
|
|
182
186
|
let snapshot = null;
|
|
183
|
-
if (this.history && (MUTATING_TOOLS.has(req.method) || isApplyChanges)) {
|
|
187
|
+
if (this.history && (MUTATING_TOOLS.has(req.method) || isApplyChanges || isLayout)) {
|
|
184
188
|
snapshot = {
|
|
185
189
|
nodes: flow.getNodes().map((n) => ({ ...n })),
|
|
186
190
|
edges: flow.getEdges().map((e) => ({ ...e })),
|
|
@@ -200,6 +204,15 @@ export class AngflowAgentBridge {
|
|
|
200
204
|
this.emitHistory(flowId);
|
|
201
205
|
}
|
|
202
206
|
}
|
|
207
|
+
else if (isLayout) {
|
|
208
|
+
// Capture only when at least one position was applied — an empty
|
|
209
|
+
// layout pass must not pollute the undo stack.
|
|
210
|
+
const positions = result?.positions ?? {};
|
|
211
|
+
if (Object.keys(positions).length > 0) {
|
|
212
|
+
this.history.capture(flowId, snapshot);
|
|
213
|
+
this.emitHistory(flowId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
203
216
|
else {
|
|
204
217
|
this.history.capture(flowId, snapshot);
|
|
205
218
|
this.emitHistory(flowId);
|
|
@@ -211,6 +224,9 @@ export class AngflowAgentBridge {
|
|
|
211
224
|
if (err instanceof FlowNotFoundError) {
|
|
212
225
|
return { id: req.id, error: { code: ERROR_FLOW_NOT_FOUND, message: err.message } };
|
|
213
226
|
}
|
|
227
|
+
if (err instanceof MethodUnavailableError) {
|
|
228
|
+
return { id: req.id, error: { code: ERROR_METHOD_NOT_FOUND, message: err.message } };
|
|
229
|
+
}
|
|
214
230
|
if (err instanceof InvalidParamsError) {
|
|
215
231
|
return { id: req.id, error: { code: ERROR_INVALID_PARAMS, message: err.message } };
|
|
216
232
|
}
|
|
@@ -669,11 +685,129 @@ export class AngflowAgentBridge {
|
|
|
669
685
|
this.history.clear(flowId);
|
|
670
686
|
this.emitHistory(flowId);
|
|
671
687
|
});
|
|
688
|
+
this.handlers.set('list_node_types', (flow) => ({ types: flow.getNodeTypeNames() }));
|
|
689
|
+
this.handlers.set('list_edge_types', (flow) => ({ types: flow.getEdgeTypeNames() }));
|
|
690
|
+
this.handlers.set('register_node_template', (flow, params) => {
|
|
691
|
+
const name = requireString(params, 'name');
|
|
692
|
+
if (name.length === 0) {
|
|
693
|
+
throw new InvalidParamsError('Param "name" must be a non-empty string.');
|
|
694
|
+
}
|
|
695
|
+
const spec = validateTemplateSpec(requireObject(params, 'spec'), 'register_node_template');
|
|
696
|
+
const claimed = flow
|
|
697
|
+
.getNodeTypeNames()
|
|
698
|
+
.find((t) => t.name === name && t.source !== 'template');
|
|
699
|
+
if (claimed) {
|
|
700
|
+
throw new InvalidParamsError(`register_node_template: "${name}" is already registered by the ` +
|
|
701
|
+
`${claimed.source === 'builtin' ? 'library' : 'host application'} and cannot be overridden.`);
|
|
702
|
+
}
|
|
703
|
+
flow.registerNodeTemplate(name, spec);
|
|
704
|
+
return { name };
|
|
705
|
+
});
|
|
706
|
+
this.handlers.set('unregister_node_template', (flow, params) => {
|
|
707
|
+
const name = requireString(params, 'name');
|
|
708
|
+
return { removed: flow.unregisterNodeTemplate(name) };
|
|
709
|
+
});
|
|
710
|
+
this.handlers.set('list_node_templates', (flow) => ({
|
|
711
|
+
templates: flow.getNodeTemplates(),
|
|
712
|
+
}));
|
|
713
|
+
this.handlers.set('layout_nodes', async (flow, params) => {
|
|
714
|
+
if (!this.layoutFn) {
|
|
715
|
+
throw new MethodUnavailableError('layout_nodes unavailable: no layout function configured. ' +
|
|
716
|
+
'Pass `layout` to provideAgentBridge (e.g. dagreLayout from @angflow/angular/layout).');
|
|
717
|
+
}
|
|
718
|
+
const direction = params['direction'] ?? 'TB';
|
|
719
|
+
if (typeof direction !== 'string' || !['TB', 'LR', 'BT', 'RL'].includes(direction)) {
|
|
720
|
+
throw new InvalidParamsError('Param "direction" must be one of: TB, LR, BT, RL.');
|
|
721
|
+
}
|
|
722
|
+
const nodeSep = typeof params['nodeSep'] === 'number' ? params['nodeSep'] : undefined;
|
|
723
|
+
const rankSep = typeof params['rankSep'] === 'number' ? params['rankSep'] : undefined;
|
|
724
|
+
const nodeIds = optionalStringArray(params, 'nodeIds');
|
|
725
|
+
if (nodeIds) {
|
|
726
|
+
for (const id of nodeIds) {
|
|
727
|
+
if (!flow.getNode(id)) {
|
|
728
|
+
throw new InvalidParamsError(`Param "nodeIds" contains unknown node id "${id}".`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const targetNodes = nodeIds
|
|
733
|
+
? nodeIds.map((id) => flow.getNode(id))
|
|
734
|
+
: flow.getNodes();
|
|
735
|
+
const idSet = new Set(targetNodes.map((n) => n.id));
|
|
736
|
+
const layoutNodes = targetNodes.map((n) => {
|
|
737
|
+
const internal = flow.getInternalNode(n.id);
|
|
738
|
+
return {
|
|
739
|
+
id: n.id,
|
|
740
|
+
width: internal?.measured?.width ?? n.width ?? 150,
|
|
741
|
+
height: internal?.measured?.height ?? n.height ?? 40,
|
|
742
|
+
position: { x: n.position.x, y: n.position.y },
|
|
743
|
+
};
|
|
744
|
+
});
|
|
745
|
+
// Induced subgraph: only edges with BOTH endpoints in the target set.
|
|
746
|
+
const layoutEdges = flow
|
|
747
|
+
.getEdges()
|
|
748
|
+
.filter((e) => idSet.has(e.source) && idSet.has(e.target))
|
|
749
|
+
.map((e) => ({ source: e.source, target: e.target }));
|
|
750
|
+
const raw = await this.layoutFn(layoutNodes, layoutEdges, {
|
|
751
|
+
direction: direction,
|
|
752
|
+
nodeSep,
|
|
753
|
+
rankSep,
|
|
754
|
+
});
|
|
755
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
756
|
+
throw new Error('layout function must return an object map of nodeId -> {x, y}.');
|
|
757
|
+
}
|
|
758
|
+
// Validate the full result BEFORE applying anything so a bad position
|
|
759
|
+
// rolls back cleanly (nothing applied, no history entry).
|
|
760
|
+
const applied = {};
|
|
761
|
+
const unknownIds = [];
|
|
762
|
+
for (const [id, pos] of Object.entries(raw)) {
|
|
763
|
+
if (!idSet.has(id)) {
|
|
764
|
+
unknownIds.push(id);
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (!pos ||
|
|
768
|
+
typeof pos.x !== 'number' ||
|
|
769
|
+
typeof pos.y !== 'number' ||
|
|
770
|
+
!Number.isFinite(pos.x) ||
|
|
771
|
+
!Number.isFinite(pos.y)) {
|
|
772
|
+
throw new Error(`layout function returned an invalid position for node "${id}"`);
|
|
773
|
+
}
|
|
774
|
+
applied[id] = { x: pos.x, y: pos.y };
|
|
775
|
+
}
|
|
776
|
+
if (unknownIds.length > 0) {
|
|
777
|
+
// eslint-disable-next-line no-console
|
|
778
|
+
console.warn(`[angflow] layout_nodes: layout function returned positions for unknown node ids ` +
|
|
779
|
+
`(ignored): ${unknownIds.join(', ')}`);
|
|
780
|
+
}
|
|
781
|
+
// Re-check existence at apply time: the graph may have changed while the
|
|
782
|
+
// (possibly async) layout fn ran — e.g. a human deleted a node. Only
|
|
783
|
+
// nodes that still exist are moved, reported, and counted for history.
|
|
784
|
+
const actuallyApplied = {};
|
|
785
|
+
for (const [id, position] of Object.entries(applied)) {
|
|
786
|
+
if (!flow.getNode(id))
|
|
787
|
+
continue;
|
|
788
|
+
actuallyApplied[id] = position;
|
|
789
|
+
}
|
|
790
|
+
// Honors the host's [animate] input: positions tween when it's on, and
|
|
791
|
+
// the await keeps the subsequent fitView measuring settled positions.
|
|
792
|
+
await flow.setNodePositions(actuallyApplied);
|
|
793
|
+
const shouldFit = params['fitView'] !== false;
|
|
794
|
+
if (shouldFit && Object.keys(actuallyApplied).length > 0) {
|
|
795
|
+
try {
|
|
796
|
+
await flow.fitView({});
|
|
797
|
+
}
|
|
798
|
+
catch (err) {
|
|
799
|
+
// Best-effort viewport fit: never fail the tool over a cosmetic step,
|
|
800
|
+
// but surface the error to hosts observing onError.
|
|
801
|
+
this.reportError(err, { kind: 'dispatch', method: 'layout_nodes' });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return { positions: actuallyApplied };
|
|
805
|
+
});
|
|
672
806
|
}
|
|
673
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
674
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
807
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AngflowAgentBridge, deps: [{ token: AGENT_TRANSPORTS, optional: true }, { token: AGENT_HISTORY_OPTIONS, optional: true }, { token: AGENT_ON_ERROR, optional: true }, { token: AGENT_LAYOUT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
808
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AngflowAgentBridge, providedIn: 'root' }); }
|
|
675
809
|
}
|
|
676
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
810
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: AngflowAgentBridge, decorators: [{
|
|
677
811
|
type: Injectable,
|
|
678
812
|
args: [{ providedIn: 'root' }]
|
|
679
813
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
@@ -691,11 +825,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
691
825
|
}, {
|
|
692
826
|
type: Inject,
|
|
693
827
|
args: [AGENT_ON_ERROR]
|
|
828
|
+
}] }, { type: undefined, decorators: [{
|
|
829
|
+
type: Optional
|
|
830
|
+
}, {
|
|
831
|
+
type: Inject,
|
|
832
|
+
args: [AGENT_LAYOUT]
|
|
694
833
|
}] }] });
|
|
695
834
|
class FlowNotFoundError extends Error {
|
|
696
835
|
}
|
|
697
836
|
class InvalidParamsError extends Error {
|
|
698
837
|
}
|
|
838
|
+
/** Tool exists in the catalog but the deployment lacks a required capability. Maps to -32601. */
|
|
839
|
+
class MethodUnavailableError extends Error {
|
|
840
|
+
}
|
|
699
841
|
class ApplyChangesError extends Error {
|
|
700
842
|
constructor(failedIndex, message) {
|
|
701
843
|
super(message);
|
|
@@ -893,6 +1035,87 @@ function optionalStringArray(params, key) {
|
|
|
893
1035
|
}
|
|
894
1036
|
return value;
|
|
895
1037
|
}
|
|
1038
|
+
const BADGE_COLOR_SET = new Set(['slate', 'indigo', 'emerald', 'amber', 'rose']);
|
|
1039
|
+
const HANDLE_POSITION_SET = new Set(['top', 'right', 'bottom', 'left']);
|
|
1040
|
+
const KNOWN_SPEC_KEYS = new Set(['title', 'icon', 'accent', 'variant', 'badges', 'fields', 'body', 'handles']);
|
|
1041
|
+
/** Validate a NodeTemplateSpec payload. Throws InvalidParamsError naming the offending field. */
|
|
1042
|
+
function validateTemplateSpec(value, ctx) {
|
|
1043
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1044
|
+
throw new InvalidParamsError(`${ctx}: spec must be an object.`);
|
|
1045
|
+
}
|
|
1046
|
+
const s = value;
|
|
1047
|
+
for (const key of Object.keys(s)) {
|
|
1048
|
+
if (!KNOWN_SPEC_KEYS.has(key)) {
|
|
1049
|
+
throw new InvalidParamsError(`${ctx}: unknown spec key "${key}".`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
for (const key of ['title', 'icon', 'accent', 'body']) {
|
|
1053
|
+
if (s[key] !== undefined && typeof s[key] !== 'string') {
|
|
1054
|
+
throw new InvalidParamsError(`${ctx}: spec.${key} must be a string.`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
if (s['variant'] !== undefined && s['variant'] !== 'compact' && s['variant'] !== 'detailed') {
|
|
1058
|
+
throw new InvalidParamsError(`${ctx}: spec.variant must be "compact" or "detailed".`);
|
|
1059
|
+
}
|
|
1060
|
+
if (s['badges'] !== undefined) {
|
|
1061
|
+
if (!Array.isArray(s['badges']))
|
|
1062
|
+
throw new InvalidParamsError(`${ctx}: spec.badges must be an array.`);
|
|
1063
|
+
s['badges'].forEach((raw, i) => {
|
|
1064
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1065
|
+
throw new InvalidParamsError(`${ctx}: spec.badges[${i}] must be an object.`);
|
|
1066
|
+
}
|
|
1067
|
+
const b = raw;
|
|
1068
|
+
if (typeof b['text'] !== 'string') {
|
|
1069
|
+
throw new InvalidParamsError(`${ctx}: spec.badges[${i}].text must be a string.`);
|
|
1070
|
+
}
|
|
1071
|
+
if (b['color'] !== undefined && !BADGE_COLOR_SET.has(b['color'])) {
|
|
1072
|
+
throw new InvalidParamsError(`${ctx}: spec.badges[${i}].color must be one of: ${Array.from(BADGE_COLOR_SET).join(', ')}.`);
|
|
1073
|
+
}
|
|
1074
|
+
if (b['showIf'] !== undefined && typeof b['showIf'] !== 'string') {
|
|
1075
|
+
throw new InvalidParamsError(`${ctx}: spec.badges[${i}].showIf must be a string.`);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
if (s['fields'] !== undefined) {
|
|
1080
|
+
if (!Array.isArray(s['fields']))
|
|
1081
|
+
throw new InvalidParamsError(`${ctx}: spec.fields must be an array.`);
|
|
1082
|
+
s['fields'].forEach((raw, i) => {
|
|
1083
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1084
|
+
throw new InvalidParamsError(`${ctx}: spec.fields[${i}] must be an object.`);
|
|
1085
|
+
}
|
|
1086
|
+
const f = raw;
|
|
1087
|
+
if (typeof f['label'] !== 'string') {
|
|
1088
|
+
throw new InvalidParamsError(`${ctx}: spec.fields[${i}].label must be a string.`);
|
|
1089
|
+
}
|
|
1090
|
+
if (typeof f['value'] !== 'string') {
|
|
1091
|
+
throw new InvalidParamsError(`${ctx}: spec.fields[${i}].value must be a string.`);
|
|
1092
|
+
}
|
|
1093
|
+
if (f['showIf'] !== undefined && typeof f['showIf'] !== 'string') {
|
|
1094
|
+
throw new InvalidParamsError(`${ctx}: spec.fields[${i}].showIf must be a string.`);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
if (s['handles'] !== undefined) {
|
|
1099
|
+
if (!Array.isArray(s['handles']))
|
|
1100
|
+
throw new InvalidParamsError(`${ctx}: spec.handles must be an array.`);
|
|
1101
|
+
s['handles'].forEach((raw, i) => {
|
|
1102
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1103
|
+
throw new InvalidParamsError(`${ctx}: spec.handles[${i}] must be an object.`);
|
|
1104
|
+
}
|
|
1105
|
+
const h = raw;
|
|
1106
|
+
if (h['type'] !== 'source' && h['type'] !== 'target') {
|
|
1107
|
+
throw new InvalidParamsError(`${ctx}: spec.handles[${i}].type must be "source" or "target".`);
|
|
1108
|
+
}
|
|
1109
|
+
if (h['position'] !== undefined && !HANDLE_POSITION_SET.has(h['position'])) {
|
|
1110
|
+
throw new InvalidParamsError(`${ctx}: spec.handles[${i}].position must be one of: top, right, bottom, left.`);
|
|
1111
|
+
}
|
|
1112
|
+
if (h['id'] !== undefined && typeof h['id'] !== 'string') {
|
|
1113
|
+
throw new InvalidParamsError(`${ctx}: spec.handles[${i}].id must be a string.`);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
return value;
|
|
1118
|
+
}
|
|
896
1119
|
/** Validate that `value` is a structurally valid Node payload for add_*. */
|
|
897
1120
|
function validateNodeShape(value, ctx) {
|
|
898
1121
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|