Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

139 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lib_flow_engine

Experimental Convertigo project used by the Flow POC.

The Java kernel resolves lib_flow_engine.Engine to:

libs/flow/Engine.js

Engine.js exposes:

run(requestJson)           -> responseJson
analyze(requestJson)       -> analysisJson
context(requestJson)       -> contextJson
catalog(requestJson)       -> catalogJson
search(requestJson)        -> rg-like Flow search results
describeTree(requestJson)  -> virtual tree JSON
applyMutation(requestJson) -> mutated Flow model + tree
outputSchema(requestJson)  -> JSON schema
types(requestJson)         -> property type descriptors

The Java side passes flowSource as an opaque string. This project owns parsing, catalog, analysis and execution. For the POC, Engine.js is evaluated without the Rhino compiled-script cache so runtime engine changes can be picked up after the Java bootstrap is restarted.

For source control, a Convertigo Flow should not serialize its full source as an escaped bean property. On the spike-flowscript branch, FlowScript is the preferred canonical sidecar in the owning project:

libs/flows/<FlowName>.flow.js

The loader only accepts this .flow.js sidecar. Legacy libs/flows/<FlowName>.flow.yaml sidecars were used during the spike migration and must not be treated as a runtime fallback. The bean property remains an in-memory editor bridge. On save/export the property is removed from Convertigo serialization and the sidecar file is written instead.

The runtime core is intentionally small. Concrete behavior is implemented by block descriptors in:

libs/flow/blocks/*.block.js

Control flow is also implemented as blocks, for example if and forEach. The flow.call block calls another Flow sidecar inside the Flow engine. This is the preferred low-overhead composition path when a project wants subflows without going through Convertigo requestable/XML execution.

On spike-flowscript, blocks use *.block.js as the canonical source. The file contains a small _meta object for the visible contract and one implementation body:

const _meta = {
  description: "Decorates a message.",
  icon: "mdi:format-text",
  properties: {
    message: { kind: "template", type: "string" }
  },
  outputs: {
    out: { type: "string" }
  }
}

function decorate({ input, config, result }) {
  const text = `*** ${input.message} ***`
  return text
}

The block id is derived from the path, for example libs/flow/blocks/demo/decorate.block.js becomes demo.decorate.

Rhino-backed blocks use the same *.block.js file: _meta.runtime declares "rhino" and the body is an IIFE returning run. Legacy *.block.yaml descriptors are still accepted as read-only migration fallback. When a .block.js sibling exists, it wins and the YAML descriptor is ignored. Future implementation kinds (java, kotlin, etc.) should keep the same logical contract shape exposed in the tree and MCP APIs.

Flow-backed blocks are regular catalog blocks. At runtime the engine exposes evaluated instance properties through input and a private mutable local scope for the block implementation. JS hooks/raw implementations can still call ctx.props(node) to inspect the raw instance, but props.* is not an expression scope. An internal return stops only the composite block, then the parent Flow continues normally.

The fragment.use block expands a reusable graph inline from:

<current-project>/libs/flow/fragments/<FragmentName>.fragment.yaml

A fragment is not a requestable and does not create a new scope. It behaves like the fragment nodes were written at that exact position, so it can read and write input, config, local, result and current directly. The Flow tree and analysis expand fragment children logically while the disk representation keeps the fragment as a separate source file.

At runtime, the engine loads blocks and optional shared helper libraries from two places:

lib_flow_engine/libs/flow/blocks/**/*.block.js # core block definitions
<current-project>/libs/flow/blocks/**/*.block.js # project-local block definitions
<current-project>/libs/flow/fragments/*.fragment.yaml # project-local fragments
lib_flow_engine/libs/flow/lib/*.js         # core helper libraries
<current-project>/libs/flow/lib/*.js      # project-local helper libraries
<current-project>/libs/flow/resources/**/* # editable docs/data resources

Project-local blocks are meant for application-specific vocabulary. They cannot silently override core blocks; a name collision is reported as an error. Blocks can call ctx.lib("name") to load libs/flow/lib/name.js once per Flow execution context. Blocks should declare that dependency with uses: [name] so the library is visible in the catalog/treeview and patchable through MCP. When a reusable behavior has a clear input/output contract, prefer a block over a helper library: blocks can be used in Flow graphs and can also be called from JavaScript implementations with ctx.callBlock(name, props).

Project FlowEngine

The POC now has one project-level FlowEngine DatabaseObject, similar in spirit to MobileApplication or UrlMapper.

It is the project anchor for:

  • the selected runtime engine;
  • project-local blocks, additive with the runtime/core catalog;
  • global contract bindings;
  • configuration scopes;
  • shared fragments.

The Java object stays intentionally small. Its editable YAML body is stored in:

<current-project>/libs/flow/engine.yaml

Example:

version: 1
engineQName: lib_flow_engine.Engine
bindings:
  weather.currentTemperature@1: WeatherTemperatureOpenMeteo
config:
  openMeteo:
    baseUrl: https://api.open-meteo.com/v1/forecast

Flow remains the requestable/executable object. It always uses the selected engine from the project FlowEngine. It can override bindings, config values and fragments locally when needed, but the common project defaults belong to the project FlowEngine.

The current catalog includes low-level composable blocks for:

  • HTTP: http.get, http.request;
  • JSON/object data: json.select, json.push, json.parse, json.stringify, object.pick, object.merge;
  • lists: list.filter, list.map, list.sort;
  • control flow: if, forEach, use, return, throw;
  • output and text: set, email.mock;
  • Convertigo runtime: log, requestable.call, session.get, session.set, session.remove.

lib_flow_engine should stay the standard runtime vocabulary. A block belongs there only when it is generally useful in normal application Flows. Tooling blocks, MCP server plumbing, benchmark helpers or migration helpers belong in a separate library project such as lib_flow_mcp. The Flow MCP block lives there, not in the standard catalog.

In the FlowEngine virtual tree and Eclipse palette, blocks are grouped by origin: core engine, current project, then external libraries. The runtime still receives one flat block name, but the authoring UI should make it clear whether a block comes from lib_flow_engine, the application project, or a future library project.

Block names use a light namespace convention. Short names such as set, return and forEach are reserved for core primitives. Domain and integration blocks should use dotted names such as json.select, requestable.call, email.send or flow.node.add. Catalog metadata may also expose package, namespace and private.

A private block is visible inside the project that defines it, but must not be advertised to projects that reference that project as a library. This gives authors and agents a safe place for local implementation details, generated helpers and one-off glue without polluting the public palette. Catalog APIs hide private blocks by default; diagnostic callers can pass includePrivate: true when they intentionally inspect implementation details.

Direct Rhino code inside a Flow should remain an escape hatch, not the normal model. The preferred equivalent of a small function is a project-local custom block, often private: true, with a tiny catalog descriptor and optional analyze() method. A generic script block would be useful only for debugging or advanced migration cases because it hides intent, weakens schemas and encourages SequenceJS-style low-code bypasses.

Custom Rhino blocks should also stay primitive-sized. They must not hide a whole backend feature such as HTTP fetch, parsing, normalization, sorting and response mapping in one JavaScript file. Use FlowScript for orchestration and standard blocks such as http.get, requestable.call, list.* and json.*; keep Rhino for the one missing bridge or algorithm.

New project-local blocks should use the FlowScript *.block.js source format: static metadata lives in _meta, and behavior lives in one function. Rhino ES6 JavaScript is only the implementation runtime when a block needs JVM/Java integration or algorithmic code. It may use Java classes through Packages, for example for integration adapters, but it must not assume Node.js APIs such as require, npm modules or browser globals. A Rhino block is still one *.block.js: _meta carries the contract, the implementation is an IIFE returning run, and optional dynamic display/analysis code belongs in hooks.file. Legacy descriptor-backed Rhino remains readable only as a migration fallback. Rhino implementations defining catalog(), displayName() or analyze() directly are rejected.

The FlowEngine virtual tree also exposes Catalog / Types. Types are first-class engine descriptors stored as libs/flow/types/*.type.yaml: docs, validation/read/write hooks and web editor fragments belong there. Block property descriptors reference this vocabulary with kind, and the catalog can still keep usage counts as secondary information.

FlowScript spike

The spike-flowscript branch adds an experimental code-like authoring view for LLMs. For project Flows, .flow.js is now the canonical sidecar on this branch: the engine lists and loads it first, compiles it into the internal Flow definition, validates block names/properties, and executes that model. Legacy .flow.yaml Flow sidecars are obsolete and must be migrated or removed instead of being loaded at runtime.

Use tools/migrate-flow-js-canonical.sh during the spike to report existing pairs and, once a .flow.js sibling exists, remove obsolete YAML sidecars:

tools/migrate-flow-js-canonical.sh /path/to/Project/libs/flows
tools/migrate-flow-js-canonical.sh --remove-yaml /path/to/Project/libs/flows

Use tools/migrate-block-js-canonical.py to validate or migrate project-local Flow-backed blocks through the live Flow MCP endpoint. It returns a complete _meta + function FlowScript block source, validates it, and writes only when --write is passed:

tools/migrate-block-js-canonical.py --project lib_flow_mcp
tools/migrate-block-js-canonical.py --project lib_flow_mcp --name mcp.handle
tools/migrate-block-js-canonical.py --project lib_flow_mcp --write

For new Flows, prefer the natural code-like form:

function GetFeedSorted({ input, config, result }) {
  const feed = requestable.call(".RSSConnector.GetFeed");
  const sortedItems = list.sort(feed.rss.channel.item, {
    by: current.title,
    direction: "asc"
  });
  const news = list.map(sortedItems, {
    title: current.title,
    description: current.description,
    imageUrl: current.enclosure.attr.url
  });
  result.news = news;
  result.count = news.length;
  return result;
}

This is syntax sugar, not free-form JavaScript. const name = block(...) becomes out: local.name, paths like feed.rss.channel.item become local.feed.rss.channel.item, object-style list.map expands to forEach/json.object/json.push, and result.key = value writes the response scope. The lower-level canonical call form remains valid for precise edits:

list.sort({ id: "sort", items: "local.feed.rss.channel.item", by: "current.title", out: "local.sorted" })

Agent-facing core blocks:

  • flow.code.get
  • flow.code.set
  • flow.code.patch
  • flow.code.rg

Compiler/debug core blocks:

  • flow.source.get
  • flow.source.validate
  • flow.source.patch

This is meant to compare Flow MCP blocks versus FlowScript via MCP on the same benchmark, not to freeze a final DSL.

Type editors are standard web components loaded from libs/flow/types/editors. For a property kind: "path", the host looks for flow-path-editor; for kind: "requestable", it looks for flow-requestable-editor, and so on. The Java side only provides the JxBrowser host and a small bridge.

An editor component should implement:

element.setState(state)
element.value
element.dispatchEvent(new CustomEvent("flow-value", { detail: { value } }))

The host also assigns element.flowHost, with:

flowHost.request("context", {})
flowHost.request("requestables", {})
flowHost.setValue(value)

This keeps editor behavior hot-reloadable from the Flow engine project instead of baking each property type into Eclipse Java code.

Standard block selection

Core blocks should stay small, generic and predictable. The default move is to add JSON/scope/list/control blocks that compose well, then promote project-local blocks only when the vocabulary is clearly reusable.

Good first-class candidates from legacy Steps:

  • LogStep -> log;
  • SequenceStep / TransactionStep -> requestable.call for the KISS path;
  • GetRequestHeaderStep, SetResponseHeaderStep, SetResponseStatusStep -> request/response blocks, once the request and response scopes are finalized;
  • SessionGetStep, SessionSetStep, SessionRemoveStep -> session.get, session.set, session.remove;
  • SmtpStep -> not as a direct clone, but as an email.send contract backed by SMTP config scopes;
  • JSON file helpers (ReadJSONStep, WriteJSONStep) can become file blocks later, but need a clear project sandbox first.

Poor candidates for the initial core:

  • XML/XPath/XSD-heavy Steps, because they would bring the legacy data model into a JSON-first engine before the bridge is designed;
  • process and filesystem mutation blocks (ProcessExecStep, DeleteStep, MoveFileStep, etc.) before security and deployment boundaries are defined;
  • authentication and context-removal Steps before runtime ownership is clear.

Block descriptors expose one display icon through icon. Use mdi:* or another Iconify id for shared/core icons, a file path relative to the block file for custom project/library icons, or an HTTPS URL when the icon must be cached from a remote source. The Flow engine resolves the authoring value to concrete local files when they exist in the provider project's cache.

Generated icon caches are intentionally ignored by Git:

libs/flow/icons/iconify/<provider>/<name>.svg
libs/flow/icons/iconify/<provider>/<name>_16x16.png
libs/flow/icons/iconify/<provider>/<name>_32x32.png
libs/flow/icons/url/<sha256>.<ext>

For example, mdi:power resolves to libs/flow/icons/iconify/mdi/power_16x16.png when the cache has been populated. Run tools/generate-mdi-icon-cache.js to populate the local cache from an Iconify MDI icons.json pack. The tool reuses Convertigo's convertigo-svg-icons Batik converter, so generated PNGs follow the same path as NGX dynamic component icons.

If no return block is executed, the Flow returns the result scope implicitly. Use return only to stop early or return something other than result. Use throw inside error branches to stop with a structured error.

Runtime handles

Most Flow data should remain JSON-serializable, but some low-level blocks need to keep live runtime objects between nodes: Java DBO instances, file writers, OpenDocument handles, XLS workbooks, JDBC connections or transactions. Model those values as typed handles such as handle<dbo>, handle<file.writer>, handle<xls.workbook> or handle<jdbc.transaction>.

Handles may live in execution scopes such as local, current and request. They must not be returned in result, persisted in Flow YAML, learned as JSON schemas, or sent through MCP/SDK responses. When a trace or picker needs to show a handle, show a small serializable summary: handle type, label, state and optional QName/path. Never serialize the Java object itself.

Prefer scoped with* blocks for resources that must be closed, mirroring Java try-with-resources:

- block: file.withWriter
  path: local.outputPath
  as: local.writer
  nodes:
    - block: forEach
      items: local.lines
      nodes:
        - block: file.write
          writer: local.writer
          value: "{{ current }}"

The with* block opens the handle, writes it to the requested scope path, runs child nodes, then closes the handle in a finally-style cleanup. Explicit open / close blocks may exist for advanced cases, but standard libraries should prefer the scoped form so authors and agents do not need to manage cleanup manually.

The same pattern works for readers and other Java-backed resources. For example, file.withReader exposes a handle<file.reader> and file.forEachLine iterates over it while setting current to the current line:

- block: file.withReader
  path: local.inputPath
  as: local.reader
  nodes:
    - block: file.forEachLine
      reader: local.reader
      nodes:
        - block: json.push
          path: result.lines
          value: "{{ current }}"

Use requestable.call when the target must go through the regular Convertigo requestable path, like the SDK does. It accepts a sequence, Flow or transaction target and unwraps the historical XML-to-JSON document wrapper so Flow authors work with direct JSON data.

For requestable.call to be useful to pickers and agents, target Flows should expose a small static contract:

input:
  name: string
output:
  message: string
  source: string
nodes:
  ...

flow-analyze uses this output shape without running the child Flow. A node such as out: local.custom then advertises local.custom.message and local.custom.source as produced paths.

Search API

Engine.search() is the compact discovery API for agents. It searches Flow sidecars, nodes, catalog blocks/types and learned schemas, then returns references that can be passed to other tools. Multi-word queries match unordered tokens, so GetFeed requestable call can find a requestable.call node targeting AAAProject.GetFeed.

Node matches return:

flowQName + nodeId + JSON Pointer path

Use nodeId for semantic edit tools and the /nodes/... path for low-level mutations. context behaves like rg -C: it adds nearby parent, previous, children and next summaries without returning the whole Flow.

Engine.applyMutation() accepts both low-level JSON Pointer paths and semantic node targets:

{ "op": "replace", "nodeId": "setMessage", "property": "value", "value": "Done" }

Use beforeNodeId, afterNodeId, or parentNodeId + slot for insertions when the target is clear; an agent should not depend on array indexes. JSON Pointer paths remain the escape hatch for exact structural edits.

For broader model-debug edits, flow-get returns the parsed Flow definition, and flow-set, flow-run, flow-test, flow-tree, flow-apply and flow-output-schema accept that same definition object. Normal authoring should prefer flow-code-*, because FlowScript is the canonical source on this spike.

Block Authoring API

Blocks can be listed/read from core, shared libraries and the project. Creation and editing are project-local. For FlowScript blocks, prefer blockCodeSet({ name, code, properties, outputs }); it writes <name>.block.js and removes obsolete YAML fallbacks for that block. blockCreate({ name, descriptorSource|descriptor, implementationSource }) remains a compatibility facade and writes canonical *.block.js for new project-local blocks.

  • blockGet({ name }) reads any visible block as one logical unit. Canonical FlowScript blocks return code, codeRevision, descriptor and implementationRuntime. Legacy YAML-backed blocks may still return descriptorSource during migration.
  • blockCodeSet({ name, code, properties, outputs }) creates or replaces a project-local FlowScript block stored as <name>.block.js.
  • blockCodeGet({ name }) reads canonical block code with its revision for patch workflows.
  • blockCodeRg({ pattern, name?, namespace? }) searches FlowScript block code and returns small extracts with block revisions.
  • blockCodePatch({ name, revision, codepatch|code }) applies a revision-checked patch or replacement to a project-local FlowScript block.
  • blockCreate({ name, descriptorSource|descriptor|definition, implementationSource }) creates or migrates a project-local block through the canonical format. Prefer blockCodeSet for new work.
  • blockDuplicate({ fromName, toName }) copies a visible block into the project using the canonical format.
  • blockEdit({ name, descriptorSource|descriptor|definition, implementationSource }) edits a project-local block and rewrites the canonical source.

Core/shared blocks are intentionally not editable in place.

Resource Patch API

For code-like maintenance, the engine also exposes project-local text resource APIs:

  • resourceSearch({ query }) searches whitelisted resources, like a small rg.
  • resourceGet({ path }) reads content and returns a hash.
  • resourcePatch({ path, baseHash, patch }) applies a unified diff, then validates block/library JavaScript, Flow block/type descriptors and parses Flow/fragment YAML by default. Hunk line numbers may be approximate when the surrounding context is unique.

The writable surface is intentionally narrow:

libs/flow/blocks/**/*.js
libs/flow/blocks/**/*.block.js
libs/flow/blocks/**/*.flow.yaml
libs/flow/fragments/**/*.fragment.yaml
libs/flow/lib/**/*.js
libs/flow/resources/**/*.{md,txt,json,yaml,yml}
libs/flow/types/**/*.type.yaml
libs/flow/types/**/*.js
libs/flow/types/editors/**/*.{html,css,js}

Flow graph changes should still use flow-edit/flow-set mutations. Resource patching is for block implementations, composite blocks, helper libraries, custom property editors and project-local documentation/data resources.

Context picker API

Engine.context() returns the scope paths visible at a specific point in a Flow. It is meant to feed Studio pickers and compact MCP/LLM guidance from the same runtime analysis.

Request shape:

{
  "flowSource": "version: 1\nnodes:\n...",
  "node": "notify",
  "property": "body",
  "include": ["local", "result"],
  "detail": "normal"
}

node can be a node id, uid, name or tree path such as nodes[3].nodes[0]. include is optional; when absent, all visible scope roots are returned. Keep include to root scope names only, for example local, current, input or config. detail is normal by default; use compact when an LLM only needs the paths.

Normal response:

{
  "ok": true,
  "target": {
    "id": "notify",
    "block": "email.mock",
    "property": "body",
    "propertyDefinition": { "kind": "template", "type": "string" }
  },
  "scopes": {
    "local": {
      "paths": [
        { "path": "local.weather", "type": "unknown", "confidence": "inferred" }
      ]
    }
  }
}

Compact response:

{
  "ok": true,
  "scopes": {
    "local": ["local", "local.weather", "local.metropoles"]
  }
}

The analysis is positional. For the default position: "before", paths written after the target node are not returned. Inside a forEach, the current scope keeps a producer reference to the iterated source path.

Learned schemas

Flows learn their final result structure on the first successful named run when no declared output contract and no schema file exist yet. http.request and http.get also learn their node output structure on the first successful run when no node schema file exists yet. Stored files contain only types and object keys, never response values:

libs/flow/schemas/<flowName>/result.out.schema.json
libs/flow/schemas/<flowName>/<nodeId>.out.schema.json

If a file exists, it is reused and never overwritten by runtime execution. To relearn, delete it through the schemaReset() API or the MCP flow-schema-reset tool; the next successful run will create it again.

Flow output schema resolution is static-first. Engine.outputSchema() starts by analyzing the Flow graph, propagates known block output schemas through values, merges result.* writes, and honors explicit return blocks when their value schema is known. Learned result schemas are only a fallback when static analysis does not know enough.

Engine.context() and Engine.analyze() read these files and expose deeper paths such as local.weather.body.metropoles.city to Studio pickers and LLM guidance. When a forEach iterates over an array with a known schema, the picker context exposes the iterated item under current.*; for example current.city and current.temperature.

Call blocks also expose known output shapes during analysis when their target is static. requestable.call reads Flow output contracts directly. For legacy sequences and transactions, it asks Convertigo's schemaManager, converts the generated DOM sample to JSON, then infers a Flow schema and unwraps the historical document container.

Property kinds

Block descriptors must say how each property is interpreted. A string is not automatically a scope path or a template anymore.

Current kinds:

text        literal string
template    string or object with {{ expression }} slots
expression  pure JS-like value expression
path        scope path to read or write
literal     raw JSON value, no resolution
schema      data shape metadata

The property type may be a runtime handle type, for example handle<file.writer>. A consumer block should only accept compatible handles, and a producer block should expose handle outputs through its out or as property documentation.

Examples:

- block: http.request
  method: GET
  url: "{{ config.weatherUrl }}"
  headers:
    X-Api-Key: "{{ config.apiKey }}"
  out: local.weather

- block: if
  condition: current.temperature >= config.threshold

- block: json.push
  path: result.hotCities
  value: "{{ current.city }}"

- block: set
  path: result.message
  value: Weather alert computed

Use value for literals and dynamic values. A string containing only {{ expression }} returns the expression value with its native type; mixed text uses the same syntax as a string template.

Contracts and bindings

A Flow can call an intention instead of a technical implementation through use.

The smallest contract shape is:

contracts:
  weather.currentTemperature@1:
    description: Current temperature for one city.
    input:
      city: string
      latitude: number
      longitude: number
      unit: C|F
    output:
      city: string
      temperature: number
      unit: C|F
      provider: string
    defaultImplementation: WeatherTemperatureOpenMeteo

defaultImplementation keeps the contract executable even when the project has not declared a binding yet. It can point to a real provider or to an explicit mock. A contract without a default is allowed for design work, but the runtime will fail with NO_IMPLEMENTATION_FOR_CONTRACT if no binding is provided.

Resolution order is intentionally small:

node implementation
flow bindings
request config.bindings
project FlowEngine bindings
contract defaultImplementation

Example call site:

- id: getTemperature
  block: use
  contract: weather.currentTemperature@1
  input:
    city: current.city
    latitude: current.latitude
    longitude: current.longitude
    unit: local.unit
  out: local.temperature

The chosen implementation is a named Flow sidecar. It receives the call input in input and inherits the caller config scope, so provider URLs, API keys and environment choices stay injectable.

WeatherAlertContract demonstrates this style: the main alert flow manipulates weather.currentTemperature@1, while WeatherTemperatureOpenMeteo owns the Open-Meteo request and response mapping.

WeatherContractBinding demonstrates a Flow-level binding override: the same contract resolves to WeatherTemperatureMock without changing the use call site.

WeatherProjectBinding demonstrates a project-level binding override coming from libs/flow/engine.yaml.

Current smoke scenario

The project contains a weather-alert fixture:

fixtures/weather-alert.json

It is used to validate a realistic Flow shape:

  • HTTP GET with headers;
  • JSON selection;
  • iteration over a list;
  • comparison;
  • conditional push into result.hotCities;
  • templated email fields;
  • mock email notification;
  • direct returned JSON payload.

The smoke test also validates a compact variant using the richer catalog:

  • generic http.request;
  • list.filter on current.temperature;
  • list.sort on current.city;
  • list.map to produce the returned city names;
  • implicit result return.

Run the standalone Rhino smoke test from the Convertigo source tree:

java -cp /Users/nicolas/git/convertigo/engine/build/libs/dependencies-8.5.0-beta.jar \
  org.mozilla.javascript.tools.shell.Main \
  -version 200 \
  /Users/nicolas/git/lib_flow_engine/tests/smoke.js \
  /Users/nicolas/git/lib_flow_engine/libs/flow

When the project is loaded in Convertigo, the inline runtime validation can be called through the regular requestable URL:

curl -sS --get \
  --data-urlencode '__sequence=WeatherAlertInline' \
  'http://localhost:18080/convertigo/projects/AAAProject/.json'

Expected payload:

{
  "hotCities": ["Paris", "Marseille"],
  "notification": {
    "sent": true,
    "to": "ops@example.com",
    "subject": "Weather alert",
    "body": "Hot cities over 35C: [\"Paris\",\"Marseille\"]"
  },
  "message": "Weather alert computed"
}

WeatherAlert is the same scenario using config.weatherUrl, config.apiKey and config.threshold. The Flow engine detects those config.* references and, when no explicit request config is provided, reads matching values from the Rhino scope already populated by Convertigo sequence variables. This keeps the Java bridge unaware of Flow variable semantics.

The request scope also exposes runtime metadata useful for portable examples:

request.convertigoUrl
request.projectUrl
request.engineDir
request.engineProjectDir
request.projectDir

WeatherAlertInline reads the fixture through a local file:// URL derived from request.engineProjectDir. This keeps the deterministic smoke test portable in Studio and Docker, where the external HTTP host/port may not be reachable from inside the JVM.

WeatherAlertOpenMeteo is the same type of alert against the public Open-Meteo forecast API. It calls https://api.open-meteo.com/v1/forecast for a small hardcoded list of French cities, reads body.current.temperature_2m, and returns the cities above the threshold. It is useful for a real-network demo, while WeatherAlertInline remains the deterministic no-network smoke test.

lib_flow_engine also carries its own requestable qualification Flows:

  • QualifCoreData exercises the core JSON/object blocks;
  • QualifWeatherInline exercises HTTP, JSON selection, list filtering, sorting and mapping against the local fixture.

Analysis output

Engine.analyze() is intentionally owned by the JS runtime. Java does not parse the Flow source.

The current analysis returns:

  • all known scope paths;
  • global reads;
  • global writes;
  • property-kind based reads and writes instead of guessing every string;
  • one entry per node with id, block, props, reads, writes and child groups;
  • learned JSON schemas, when available, attached to the scope path produced by the node;
  • legacy requestable schemas for static requestable.call targets when a live Convertigo engine is available;
  • a first errors array for future static diagnostics.

This is the first shape needed by a future Flow picker or MCP tool. Deep JSON paths are exposed when a node has a declared or learned schema.

Flow MCP library

The MCP-facing experiment lives in lib_flow_mcp. It uses the same runtime APIs, catalog and context analysis as a normal Flow project, but keeps protocol plumbing out of the standard engine vocabulary.

The agent authoring cycle is documented by lib_flow_mcp, which owns the MCP tool names and protocol surface.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages