@floless/app 0.5.1
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/bin/floless.mjs +13 -0
- package/dist/floless-server.cjs +56801 -0
- package/dist/skills/floless-app-bridge/SKILL.md +80 -0
- package/dist/skills/floless-app-routines/SKILL.md +168 -0
- package/dist/skills/floless-app-routines/references/routines-api.md +130 -0
- package/dist/skills/floless-app-workflows/SKILL.md +352 -0
- package/dist/skills/floless-app-workflows/references/dev-server-and-run-trace.md +119 -0
- package/dist/skills/floless-app-workflows/references/exec-contract.md +104 -0
- package/dist/web/app.css +2129 -0
- package/dist/web/app.js +1334 -0
- package/dist/web/apple-touch-icon.png +0 -0
- package/dist/web/aware.js +3274 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +98 -0
- package/dist/web/index.html +484 -0
- package/launch.mjs +543 -0
- package/package.json +43 -0
- package/teardown.mjs +128 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: floless-app-workflows
|
|
3
|
+
description: This skill should be used when building, modifying, or reasoning about AWARE .flo workflows for floless.app — especially reusable apps that take inputs (e.g. a phase number) and run across different Tekla models, exec nodes that run Roslyn C# against the live model, the in-app HTML Viewer, the Code tab, or "Debug in VS". Covers the exec contract, the install→validate→compile→run loop, app inputs templating, and the server seams (aware-adapter, app-reader, index).
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Building reusable .flo workflows in floless.app
|
|
9
|
+
|
|
10
|
+
## What floless.app is (and is not)
|
|
11
|
+
|
|
12
|
+
floless.app is a **thin web UI over the `aware` CLI**. It owns **no engine and no LLM**.
|
|
13
|
+
Every capability is either `aware <verb>` (shelled through one adapter) or reading AWARE
|
|
14
|
+
state off disk. The browser is just the window. Never make the UI compose workflows or call
|
|
15
|
+
an LLM — it renders AWARE state, triggers `aware` verbs, and relays user intent.
|
|
16
|
+
|
|
17
|
+
The deliverable floless.app produces is a **reusable AWARE `.flo` app**: one that declares
|
|
18
|
+
`inputs:` with defaults and runs unchanged across different models. The canonical example
|
|
19
|
+
lives at `demos/tekla-bom-by-phase/` — study it before building a new one.
|
|
20
|
+
|
|
21
|
+
## The reusable-app shape
|
|
22
|
+
|
|
23
|
+
An app becomes reusable by declaring a top-level `inputs:` block and templating those inputs
|
|
24
|
+
into node config with `{{ inputs.<name> }}`. The canonical `tekla-bom-by-phase.flo` models the
|
|
25
|
+
**desktop-floless 3-node shape** — a Phase **input** node → the Tekla **core logic** node → an
|
|
26
|
+
HTML Report **viewer** node — as **three real `tekla/exec` nodes whose data flows along the
|
|
27
|
+
edges** (the canvas story and the data flow are the same thing):
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
app: tekla-bom-by-phase
|
|
31
|
+
version: 0.3.0
|
|
32
|
+
display-name: Tekla BOM by Phase
|
|
33
|
+
description: |
|
|
34
|
+
<one paragraph — required>
|
|
35
|
+
exposes-as-agent: false
|
|
36
|
+
inputs:
|
|
37
|
+
phase:
|
|
38
|
+
type: integer
|
|
39
|
+
default: 1 # keeps the app runnable with no user action
|
|
40
|
+
description: Construction phase number to report on (Tekla PHASE) — editable per run.
|
|
41
|
+
requires:
|
|
42
|
+
- tekla@0.1.x
|
|
43
|
+
layout: linear
|
|
44
|
+
nodes:
|
|
45
|
+
- id: phase-input # NODE 1 — Phase input: scope the run
|
|
46
|
+
agent: tekla
|
|
47
|
+
command: exec
|
|
48
|
+
mode: read # author intent (see exec-mode note below)
|
|
49
|
+
config:
|
|
50
|
+
version: "2025.0"
|
|
51
|
+
args: { phase: "{{ inputs.phase }}" } # from the app-input knob
|
|
52
|
+
code: |
|
|
53
|
+
<Roslyn C# — reads args["phase"], returns new { ok, phase, partsInPhase, modelName }>
|
|
54
|
+
- id: bom # NODE 2 — the Tekla core logic
|
|
55
|
+
agent: tekla
|
|
56
|
+
command: exec
|
|
57
|
+
mode: read
|
|
58
|
+
config:
|
|
59
|
+
version: "2025.0"
|
|
60
|
+
args: { phase: "{{ phase-input.result.phase }}" } # CARRIES node 1's output along the edge
|
|
61
|
+
code: |
|
|
62
|
+
<Roslyn C# — reads args["phase"], returns new { ok, phase, html, ... }>
|
|
63
|
+
- id: viewer # NODE 3 — HTML Report Viewer terminal
|
|
64
|
+
agent: tekla
|
|
65
|
+
command: exec
|
|
66
|
+
mode: read
|
|
67
|
+
config:
|
|
68
|
+
version: "2025.0"
|
|
69
|
+
args:
|
|
70
|
+
html: "{{ bom.result.html }}" # CARRIES the report along the edge
|
|
71
|
+
phase: "{{ bom.result.phase }}"
|
|
72
|
+
code: |
|
|
73
|
+
<Roslyn C# — returns new { ok, phase, html = args["html"] } (the terminal)>
|
|
74
|
+
connections:
|
|
75
|
+
- { from: phase-input, to: bom }
|
|
76
|
+
- { from: bom, to: viewer }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
At run time the user (or the UI) supplies `--input phase=2`; the runtime substitutes it into
|
|
80
|
+
node 1's `config.args.phase`; the bridge hands it to the C# as the `args` global. Node 1 emits
|
|
81
|
+
the phase, node 2 consumes it and emits the report HTML, node 3 carries that HTML as the
|
|
82
|
+
terminal — the edges carry real data.
|
|
83
|
+
|
|
84
|
+
### `layout:` must match the topology (`linear` vs `dag`) — VERIFIED
|
|
85
|
+
|
|
86
|
+
The top-level `layout:` field is **load-bearing for the UI**, not a cosmetic hint. The canvas
|
|
87
|
+
(`renderTopology` in `web/aware.js`) only honors the `connections:` block when `layout: dag`;
|
|
88
|
+
under `layout: linear` it **ignores `connections` and draws a node-order chain** (each node → the
|
|
89
|
+
next, in file order), pulling only edge *labels* positionally. So: a straight chain → keep
|
|
90
|
+
`layout: linear`; **any branch — fan-out, fan-in, a node feeding two — → use `layout: dag`**, or
|
|
91
|
+
the real edges won't render (a fan-out `message-input → A` + `message-input → B` shows as the
|
|
92
|
+
chain `message-input → A → B`). `dag` triggers `layoutDag` (longest-path layering over the real
|
|
93
|
+
`connections`). Verified on aware 0.51.0 against the live UI; `college-phase-exporter` ships `dag`.
|
|
94
|
+
|
|
95
|
+
### Cross-node templating (how data flows along an edge) — VERIFIED
|
|
96
|
+
|
|
97
|
+
A downstream node references an upstream node's returned object as **`{{ <node-id>.result.<field> }}`**
|
|
98
|
+
(empirically verified on aware 0.46.0). The `.result.` segment is required: the host bridge
|
|
99
|
+
wraps an exec script's `return` value under `result` (the node output is
|
|
100
|
+
`{ ok, result: {...}, host, host_version, ... }`), so `{{ bom.html }}` resolves to **empty** but
|
|
101
|
+
`{{ bom.result.html }}` resolves to the value. Large strings carry intact (an ~12 KB BOM HTML
|
|
102
|
+
travels node 2 → node 3 unchanged). exec output is dynamic, so no `output-schema` is needed for
|
|
103
|
+
the reference to resolve. (The `app-spec.md` example's `{{ runtime: <node>.<field> }}` prefix
|
|
104
|
+
form is stale — the no-prefix `{{ <node>.result.<field> }}` is what runs.)
|
|
105
|
+
|
|
106
|
+
**FOOTGUN — `{{ }}` is rendered across the WHOLE node config, including the exec `code`
|
|
107
|
+
(comments too) — VERIFIED on 0.51.0.** The template engine doesn't skip the `code` block, so any
|
|
108
|
+
`{{ … }}` in C# (even inside a `//` comment) is parsed/rendered at run start and aborts the run:
|
|
109
|
+
- a `{{ }}` literal (empty) → `template parse: syntax error: unexpected end of variable block`;
|
|
110
|
+
- a self-reference like `{{ <thisNode>.result.x }}` in the node's own comment → `template render:
|
|
111
|
+
undefined value` (a node's own `result` doesn't exist during its own execution).
|
|
112
|
+
The failure surfaces as `aware app run … failed (exit 3)` on the FIRST node, which reads like a
|
|
113
|
+
host-attach error but isn't. **Rule: never write `{{` or `}}` anywhere inside `code`** — describe
|
|
114
|
+
templates in prose without braces (e.g. "reads `node.result.x`"). The only legitimate `{{ }}` live
|
|
115
|
+
in `config.args` values. (`tekla-bom`'s comments dodge this only because they reference an
|
|
116
|
+
*upstream* node that has already run — fragile; prefer no braces in code at all.)
|
|
117
|
+
|
|
118
|
+
### Why all three nodes are `tekla/exec` (hard constraints — predicates can't carry data)
|
|
119
|
+
|
|
120
|
+
- **Inline nodes can't carry data.** Only `inline.kind: predicate` runs, and predicates return a
|
|
121
|
+
**boolean** — they cannot emit a value to a downstream node. `kind: shape`/`map` (which could
|
|
122
|
+
reshape/pass data) are **rejected at validate + compile** on aware 0.46 (`E_APP_INLINE_KIND`,
|
|
123
|
+
closed #160) and marked reserved/not-yet-runtime-supported. So a genuine input→logic→viewer
|
|
124
|
+
**data flow** can only be built from real host (`exec`) nodes.
|
|
125
|
+
- **The `html-report` agent ships no binary** and on aware 0.46 is marked `status: planned` and
|
|
126
|
+
**rejected** at validate/install/compile/run (`E_APP_AGENT_UNAVAILABLE`, closed #161) — never
|
|
127
|
+
use it as the viewer. The viewer is a `tekla/exec` node that **forwards `{{ bom.result.html }}`**
|
|
128
|
+
and returns it, so the report HTML is the terminal node's own output. `extractReportHtml`
|
|
129
|
+
scans the trace and takes the **last** node carrying `data.result.html`; floless's HTML Viewer
|
|
130
|
+
binds to the chain **terminal** (`reportNodeId()` = last node when any node has exec code), so
|
|
131
|
+
the forwarded HTML is what renders.
|
|
132
|
+
- **Cost of the 3-exec shape:** 3 bridge round-trips per run (~10–15 s) and the report HTML
|
|
133
|
+
crosses the bridge twice (built in node 2, echoed by node 3). Acceptable for the
|
|
134
|
+
data-carrying canvas; the alternative (a single exec + UI-only "input"/"viewer" chrome)
|
|
135
|
+
doesn't honor "3 visible nodes".
|
|
136
|
+
|
|
137
|
+
### exec mode label: declare `mode: read`, but AWARE overrides it (issue #165)
|
|
138
|
+
|
|
139
|
+
Declare `mode: read` on every read-only exec node (author intent). **AWARE ignores it for
|
|
140
|
+
`exec`:** the compiler can't resolve an exec command's mode, so the lock stamps `mode: write`
|
|
141
|
+
with the note *"…command exec not found; defaulting to write-mode for safety"* (filed as
|
|
142
|
+
aware-aeco/aware#165 — silent override of a documented field). It's cosmetic — read-only exec
|
|
143
|
+
runs fine with no `safety:` block. floless reconciles this in `app-reader.ts`: when the source
|
|
144
|
+
declares `mode: read` and the lock defaulted an exec node to write, the UI shows **read** and
|
|
145
|
+
rewrites the cryptic note into a plain-English one — surfacing AWARE's default, not hiding it.
|
|
146
|
+
|
|
147
|
+
## The exec contract (tekla `agent: tekla, command: exec`)
|
|
148
|
+
|
|
149
|
+
The exec node runs Roslyn C# in the host bridge (`aware-tekla.exe`) against the **live model**.
|
|
150
|
+
Globals injected: `dynamic model` (a `Tekla.Structures.Model.Model`) and
|
|
151
|
+
`IDictionary<string, object> args` (the `config.args` block, JSON-typed). The script ends with
|
|
152
|
+
`return <object>;`. Read `references/exec-contract.md` for the full contract, the available/
|
|
153
|
+
unavailable references (no `System.Net`, no `System.Diagnostics.Process`; manual HTML-escape),
|
|
154
|
+
the part-NAME categorization, and phase filtering.
|
|
155
|
+
|
|
156
|
+
Two rules that bite immediately:
|
|
157
|
+
- **Return the report HTML inline** as `return new { ok = true, html = sb.ToString(), ... };`.
|
|
158
|
+
Do **not** write a file. AWARE ships no html-report render binary, and inline HTML keeps the
|
|
159
|
+
app model-agnostic (no filesystem permission, no hardcoded path). The HTML Viewer renders
|
|
160
|
+
whatever the node returns.
|
|
161
|
+
- **No `System.Net`** (so no `WebUtility.HtmlEncode`) — escape with a manual lambda.
|
|
162
|
+
|
|
163
|
+
## The build loop (install → validate → compile → run)
|
|
164
|
+
|
|
165
|
+
Resolve the `aware` CLI the same way the adapter does: prefer the npm global
|
|
166
|
+
`@aware-aeco/cli/scripts/bin/aware.js` run via `node`. Then:
|
|
167
|
+
|
|
168
|
+
1. **Install from the app's *directory*** (not the `.flo` file — registry-hosted/path-to-file is
|
|
169
|
+
rejected): `aware app install ./demos/tekla-bom-by-phase`. **Reinstalling an updated app first
|
|
170
|
+
needs `aware app uninstall <id>`** — a same-id `app install` errors `conflict: app <id> already
|
|
171
|
+
installed` and silently leaves the OLD source in place (a stale compile/run will mislead you).
|
|
172
|
+
2. **Validate** — `aware app validate <PATH>` takes the app's **directory** (NOT an app id, and
|
|
173
|
+
NOT the `.flo` file): `aware app validate ./demos/tekla-bom-by-phase`. Verified on 0.51.0:
|
|
174
|
+
passing an id errors `io: cannot find the path` (os error 3); passing the `.flo` file errors
|
|
175
|
+
`directory name is invalid` (os error 267).
|
|
176
|
+
3. **Compile** — `aware app compile <PATH>` also takes the app **directory** and writes
|
|
177
|
+
`<app>.lock` **into that same directory**. To refresh what the UI reads, compile the installed
|
|
178
|
+
copy (`aware app compile ~/.aware/apps/<id>`); or compile the editable source
|
|
179
|
+
(`aware app compile ./demos/<id>`) and copy the `.lock` across. If the `.lock` is missing/stale
|
|
180
|
+
in `~/.aware/apps/<id>/`, app-reader reports `uncompiled` and the UI Run gate stays disabled.
|
|
181
|
+
(Older docs said `compile <id>` resolved relative to CWD — stale; it's a `<PATH>` arg now.)
|
|
182
|
+
4. **Run for real — MANDATORY before calling ANY `.flo` change done.** `aware app run <id> --input
|
|
183
|
+
k=v --json` (`run` takes the app **id**, unlike `validate`/`compile` which take a path). The
|
|
184
|
+
trace lands at `~/.aware/logs/<id>/<instance>/<run>.jsonl`; the exec result is at `data.result`
|
|
185
|
+
(the bridge wraps the script's return under `result`). **Read the trace** and confirm every node
|
|
186
|
+
emitted `node-output` with `ok:true` and the run ends `run-end · status:ok`. A clean compile and
|
|
187
|
+
a correctly-rendered canvas do **NOT** prove the `.flo` runs — a bad cross-node template or a
|
|
188
|
+
stray `{{ }}` in `code` only surfaces at run (it aborts the FIRST node with a misleading exit-3
|
|
189
|
+
"host" error). Then drive the same **▶ Run workflow** in the UI per the project's real-E2E rule.
|
|
190
|
+
|
|
191
|
+
**`--simulate` is NOT a substitute for exec apps.** Verified on 0.51.0: `--simulate` aborts at
|
|
192
|
+
the first template (`template render: undefined value (in t:1)`) for BOTH `hello-world` AND
|
|
193
|
+
`tekla-bom-by-phase` (the latter runs fine for real) — stubs carry no `result.<field>` and even
|
|
194
|
+
`{{ inputs.* }}` isn't seeded, so the first template renders undefined regardless of correctness.
|
|
195
|
+
A simulate "failure" on an exec-data-carrying app therefore proves nothing. Simulate only helps
|
|
196
|
+
for schema-backed (non-exec) compositions and for catching template *parse* errors; the real
|
|
197
|
+
gate is a live `aware app run` + trace check (above).
|
|
198
|
+
|
|
199
|
+
Keep both copies in sync: the editable source under `demos/<id>/` (committed) and the installed
|
|
200
|
+
copy under `~/.aware/apps/<id>/`. Copy the freshly compiled `.lock` back into `demos/<id>/`.
|
|
201
|
+
|
|
202
|
+
## Bake a workflow into a reusable agent (`exposes-as-agent`) — VERIFIED, AWARE 0.52.0+
|
|
203
|
+
|
|
204
|
+
A multi-node `.flo` can be **baked into a single callable agent** so another app drops it in as one
|
|
205
|
+
node and still sets its inputs. Declarative (no `aware bake` verb): add two manifest fields to the
|
|
206
|
+
`.flo`. Wired end-to-end by aware-aeco/aware#178 (shipped 0.52.0); inert/dead-code on ≤0.51 — gate on
|
|
207
|
+
the CLI version. Verified on 0.52.0 (see `docs/plans/2026-05-29-bake-workflow-into-agent-design.md`).
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
exposes-as-agent: true
|
|
211
|
+
exposed-commands:
|
|
212
|
+
run: # the command other apps call: agent: <app>, command: run
|
|
213
|
+
lifecycle: single # single = one-shot, returns the terminal node's output;
|
|
214
|
+
# start = long-running, streams terminal outputs (event source)
|
|
215
|
+
inputs:
|
|
216
|
+
phase: { type: integer } # ← becomes the agent's parameter; caller sets it, type-validated
|
|
217
|
+
outputs: { type: single, schema: { ok: bool, phase: integer } }
|
|
218
|
+
nodes: [ ... the normal chain ... ]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Mechanics (all confirmed against the substrate + a real run):
|
|
222
|
+
- **`aware app install`** of an exposes-as-agent app **synthesizes a callable agent** under
|
|
223
|
+
`~/.aware/agents/<app>/manifest.yaml` — it then shows in `aware agent list` and resolves as
|
|
224
|
+
`agent: <app>`. `aware app uninstall` removes it. (No new install flag; it's automatic.)
|
|
225
|
+
- **A consumer node** `{ agent: <app>, command: run, config: { phase: 7 } }` dispatches into the
|
|
226
|
+
nested chain; the caller's `config` inputs **route into the baked app's `{{ inputs.<name> }}`** and
|
|
227
|
+
are **type-validated** (wrong type → `validation failed: exposed command 'run' input 'phase':
|
|
228
|
+
expected integer, got string`). Observable proof: the nested run-start trace carries
|
|
229
|
+
`config:{phase:7}` (logs under `~/.aware/logs/<app>/nested/…jsonl`).
|
|
230
|
+
- **Guards:** an exposed app **cannot** compose another exposed app (`E_APP_EXPOSED_COMPOSES_EXPOSED`)
|
|
231
|
+
nor reference its own id; `exposes-as-agent: true` with no `exposed-commands` →
|
|
232
|
+
`E_APP_EXPOSES_NO_COMMANDS`. The caller inherits the backing app's `requires`/permissions union.
|
|
233
|
+
- **Test-without-a-host:** make the inner chain a single `inline.kind: predicate` node (runs with no
|
|
234
|
+
bridge) and assert the nested run-start `config`, or assert the type-rejection — both prove routing
|
|
235
|
+
without a live Tekla. (An `exec` inner node needs the real host.) Note: an inline predicate sees the
|
|
236
|
+
upstream *event* as `e.<field>`, NOT `inputs.<field>` — don't assert routing via the predicate body.
|
|
237
|
+
|
|
238
|
+
floless.app play (designed, now unblocked): a "Bake into agent" affordance that rewrites a working
|
|
239
|
+
`.flo` to add `exposes-as-agent` + `exposed-commands` mirroring the app's `inputs:`. Thin-UI: a
|
|
240
|
+
manifest edit + recompile, no engine. See the design doc for the Stage-1 plan.
|
|
241
|
+
|
|
242
|
+
## How the UI surfaces map to AWARE state
|
|
243
|
+
|
|
244
|
+
- **Code tab** shows the node's real source: `node.config.code` (the exec C#), not lockfile
|
|
245
|
+
YAML. Pure-agent / inline nodes (no inline code) fall back to the resolved-lock YAML. The C#
|
|
246
|
+
is syntax-highlighted by a small dependency-free scanner in `web/aware.js`
|
|
247
|
+
(`highlightCSharp`) that reuses the demo's token classes (`kw`/`ty`/`st`/`cm` + `nu` for
|
|
248
|
+
numbers). No highlighting library — extend the scanner, don't add a dependency.
|
|
249
|
+
- **Inputs live on the input node** (the old above-canvas strip was removed). The input node
|
|
250
|
+
(`inputNodeId()` — first node with `{{ inputs.x }}` in `config.args`) carries a **"Set inputs ▸"**
|
|
251
|
+
button (styled dialog, not `window.prompt`) and an at-a-glance **value chip** ("phase 5",
|
|
252
|
+
refreshed via `markSpecialNodes`). The report node carries a **"View report ▸"** button. Both
|
|
253
|
+
are first-class buttons injected by `markSpecialNodes` in `web/aware.js`; double-click the node
|
|
254
|
+
still works as a shortcut.
|
|
255
|
+
- **One Run, single model** (the approved baseline has ONE `▶ Run workflow`; never add a second
|
|
256
|
+
run control). The header **`▶ Run workflow`** does the **REAL** run: `POST /api/run
|
|
257
|
+
{simulate:false, inputs}`. If the app has a report node (`reportNodeId()` — the exec terminal),
|
|
258
|
+
it renders the returned `html` in the HTML Viewer and caches it; otherwise it fills the Execution
|
|
259
|
+
trace. The secondary **`Simulate`** header button is `simulate:true` (every node stubbed from its
|
|
260
|
+
output-schema, no host) — a composition check.
|
|
261
|
+
- **Per-node run status on the canvas + a Stop button.** During a run each node card paints
|
|
262
|
+
running → ✓ done / ✗ failed from the trace (`pushTrace`), and the report-run overlay shows a
|
|
263
|
+
**Stop** button. See `references/dev-server-and-run-trace.md` for the trace event kinds, the live
|
|
264
|
+
fs-watcher streaming, and the cancel mechanics.
|
|
265
|
+
- **HTML Viewer** (in-app modal): **load vs run is split** (mirrors the desktop floless HTML
|
|
266
|
+
Viewer — the node displays the last result; it does not recompute).
|
|
267
|
+
- **Double-click the viewer node (or "View report ▸") → LOADS the last report** from an in-memory
|
|
268
|
+
per-app cache (`lastReportByApp`), instantly, **no run**. If nothing has run yet, it shows a
|
|
269
|
+
"click ▶ Run workflow" prompt (never a spinner). Do NOT make this trigger a run — a live run is
|
|
270
|
+
~15s and showing a spinner over stale content reads as "stuck".
|
|
271
|
+
- Rendered in a **sandboxed iframe `srcdoc`** (`sandbox="allow-same-origin"`, scripts
|
|
272
|
+
disabled). The UI never builds the HTML; it relays exactly what the exec returned.
|
|
273
|
+
- **`Simulate` can't run an exec-data-carrying app**: stubs have no `result.<field>`, so the
|
|
274
|
+
cross-node templates (`{{ bom.result.html }}`) render undefined and the run aborts
|
|
275
|
+
(`template render: undefined value`, exit 3). `/api/run` returns that **in-band** (HTTP 200
|
|
276
|
+
`{ok:false}`) so the console stays clean and the UI explains it. Use `▶ Run app` for exec apps.
|
|
277
|
+
- **Debug in VS** (HTML Viewer header) reproduces the desktop floless action. There is no
|
|
278
|
+
`aware exec` verb for ad-hoc code, so `POST /api/debug-node` injects
|
|
279
|
+
`System.Diagnostics.Debugger.Launch(); Debugger.Break();` (after the last `using` directive)
|
|
280
|
+
and sends the code **straight to the bridge** (`~/.aware/bridges/aware-tekla.exe`). The .NET
|
|
281
|
+
JIT picker pops; the user attaches Visual Studio to `aware-tekla.exe`. It debugs the
|
|
282
|
+
**decompiled** Roslyn submission, not source.
|
|
283
|
+
|
|
284
|
+
## Server seams (where to make changes)
|
|
285
|
+
|
|
286
|
+
- `server/aware-adapter.ts` — the **only** place that shells `aware` *and* the host bridge.
|
|
287
|
+
`run(id, {dryRun, simulate, inputs})` passes `--input k=v`; `execTekla(code, {version, args,
|
|
288
|
+
debug})` talks to the bridge directly (resolves `~/.aware/bridges`, injects the debugger when
|
|
289
|
+
`debug`). Nothing else touches raw `aware`/bridge argv. **`run()` serializes through a single
|
|
290
|
+
promise-chain run lock** — manual (UI) runs and scheduled (routine) runs share it, since the host
|
|
291
|
+
bridge does one exec at a time and `cancelActiveRun()` assumes a single active run.
|
|
292
|
+
- `server/routines.ts` + `/api/routines` (in `index.ts`) — `.flo` runs on a trigger. Two `kind`s
|
|
293
|
+
share the store (`~/.floless/routines.json`) and the **⏱ Routines** panel:
|
|
294
|
+
- **`schedule`** — a time-trigger; the in-server scheduler fires due routines through
|
|
295
|
+
`aware.run()` (the shared run lock above). Carries a `schedule` + `nextFireAt`.
|
|
296
|
+
- **`trigger`** — an event-trigger of a streaming `lifecycle:start` source (e.g. `tekla.watch`).
|
|
297
|
+
A long-lived `aware app run` lives in `server/trigger-sessions.ts` **outside** the run lock; the
|
|
298
|
+
row renders a live `session` snapshot `{state:'listening'|'blocked'|'error'|'stopped',
|
|
299
|
+
firedCount, lastEvent, error}` pushed over the `trigger-session-changed` SSE event. The enabled
|
|
300
|
+
toggle = start/stop the session (not "arm a timer"); run-now is refused (the UI hides ▶). The
|
|
301
|
+
create body is `{kind:'trigger', name, workflow, inputs, enabled}` (no `schedule`) — eligibility
|
|
302
|
+
is `app.triggerSource != null` (computed by `app-reader.detectTriggerSource`); `createRoutine`
|
|
303
|
+
records the binding (`trigger:{nodeId,agent,command}`) from `app.triggerSource` so the row's
|
|
304
|
+
"On trigger · agent/command" descriptor survives a reload. v1 sets **no** source params (the
|
|
305
|
+
watch's `filter` etc. live in node config — see `references/dev-server-and-run-trace.md`).
|
|
306
|
+
Authoring path for the terminal AI = the `floless-app-routines` skill. Both kinds are still
|
|
307
|
+
thin-UI: an event-/time-trigger of `aware app run`, never composing a workflow or calling an LLM.
|
|
308
|
+
- `server/app-reader.ts` — reads `~/.aware/apps/<id>/` off disk (the CLI's `app show` is
|
|
309
|
+
text-only). Parses the top-level `inputs:` block into `app.inputs`, exposes `node.config`
|
|
310
|
+
(incl. `code`), and computes the Run gate from the `.lock` source-hash. **Reconciles the exec
|
|
311
|
+
write-mode default** (issue #165): when the source declares `mode: read` and the lock defaulted
|
|
312
|
+
an `exec` node to `write`, it reports `read` and rewrites the cryptic compile note — so the
|
|
313
|
+
canvas isn't littered with false write-mode badges.
|
|
314
|
+
- `server/index.ts` — routes. `/api/run` accepts `inputs` and returns `report` via
|
|
315
|
+
`extractReportHtml(events)` (scans the trace for `data.result.html`); it surfaces a user Stop
|
|
316
|
+
in-band as `{ cancelled:true }`. `/api/run/stop` → `cancelActiveRun()` (kills the in-flight
|
|
317
|
+
run's process tree). `/api/debug-node` resolves `{{ inputs.x }}` in `config.args` and dispatches
|
|
318
|
+
via `execTekla(..., {debug:true})`.
|
|
319
|
+
|
|
320
|
+
## Keeping this skill current (self-learning)
|
|
321
|
+
|
|
322
|
+
This skill is **self-learning**: it must improve every time a floless.app session surfaces a
|
|
323
|
+
new, non-obvious fact. Treat the skill as the living memory of how floless.app works.
|
|
324
|
+
|
|
325
|
+
At the end of any session that touched floless.app, before reporting done, ask: *did I learn
|
|
326
|
+
something a future session would waste time rediscovering?* If yes, fold it in **now**:
|
|
327
|
+
|
|
328
|
+
- A new AWARE constraint or footgun (e.g. "only inline predicate runs", "html-report has no
|
|
329
|
+
binary", a CLI arg quirk) → add it to the relevant section here or to `references/`.
|
|
330
|
+
- A new exec-contract detail / Tekla API gotcha → `references/exec-contract.md`.
|
|
331
|
+
- A new UI seam or pattern (a route, a render path, a token class) → the UI / server-seams
|
|
332
|
+
sections.
|
|
333
|
+
- A corrected assumption → fix the stale text so the wrong version never resurfaces.
|
|
334
|
+
|
|
335
|
+
How to update (do not hand-edit around the process):
|
|
336
|
+
1. Route the change through the `skill-creator` skill (the project's standing rule —
|
|
337
|
+
`feedback_skill_creator_required.md`).
|
|
338
|
+
2. Keep SKILL.md lean; push long detail into `references/`. Avoid duplicating a fact in both.
|
|
339
|
+
3. Re-validate: `python <skill-creator>/scripts/quick_validate.py .claude/skills/floless-app-workflows`.
|
|
340
|
+
4. Commit with the code it documents, or as a `docs(skill):` commit.
|
|
341
|
+
|
|
342
|
+
Keep entries terse and concrete (the constraint + why + where), not prose. If a section grows
|
|
343
|
+
past a few hard facts, split it into a `references/` file and link it.
|
|
344
|
+
|
|
345
|
+
## Guardrails (do not drift)
|
|
346
|
+
|
|
347
|
+
- Determinism is AWARE's: `<app>.lock` is the approved artifact; Run is gated on a fresh
|
|
348
|
+
source-hash. Never let the UI run on drift.
|
|
349
|
+
- Any change under `web/` requires a Playwright pass (visual + interaction), not just a check of
|
|
350
|
+
the request body — see the project's standing rule.
|
|
351
|
+
- When a real `aware`/bridge bug surfaces, file it on `aware-aeco/aware` (don't file your own
|
|
352
|
+
misuse).
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Dev server + run trace (verifying floless.app changes)
|
|
2
|
+
|
|
3
|
+
Concrete, easily-rediscovered facts for verifying UI/server changes against a live host.
|
|
4
|
+
|
|
5
|
+
## Running the dev server for verification
|
|
6
|
+
|
|
7
|
+
The repo's TypeScript host serves `web/` and shells `aware`. To verify changes you run it
|
|
8
|
+
on **port 4317** and drive it with Playwright (the standing real-E2E rule).
|
|
9
|
+
|
|
10
|
+
- **Command:** `tsx main.ts --serve` from `server/` (this is the `start` script). The `dev`
|
|
11
|
+
script is `tsx watch …` (auto-reload) but the server is usually launched with **`start`
|
|
12
|
+
(NO watch)** — so **server TS edits (`index.ts`, `aware-adapter.ts`, `app-reader.ts`) need a
|
|
13
|
+
manual restart**; a 404 on a route you just added means the old process is still running.
|
|
14
|
+
- **License env (the shared-dir trick) — REQUIRED:** the dev server's license dir
|
|
15
|
+
(`licensing.ts storeDir()`) defaults to `~/.aware`, which has **no token**, so without an
|
|
16
|
+
override the dev server is signed-out and the UI is gated. Start it with
|
|
17
|
+
`FLOLESS_LICENSE_DIR=%LOCALAPPDATA%\FlolessApp-data` (git-bash: `"$LOCALAPPDATA/FlolessApp-data"`)
|
|
18
|
+
so it shares the **installed SEA app's** token + `floless-install-id` (so no seat takeover —
|
|
19
|
+
single-session/newest-login-wins). Token lives at
|
|
20
|
+
`%LOCALAPPDATA%\FlolessApp-data\floless-license.json`.
|
|
21
|
+
- **`web/` is served fresh from disk per request** — edits to `web/*.js|css|html` need only a
|
|
22
|
+
browser **reload**, no server restart. Only server TS needs a restart.
|
|
23
|
+
- **Restart recipe (Windows):** find the listener (`netstat -ano | grep :4317`), get its tree
|
|
24
|
+
(`Get-CimInstance Win32_Process`), `taskkill //PID <tsx-pid> //T //F`, then relaunch
|
|
25
|
+
`FLOLESS_LICENSE_DIR="$LOCALAPPDATA/FlolessApp-data" npx tsx main.ts --serve` in the background.
|
|
26
|
+
|
|
27
|
+
## The run trace (what `aware app run` emits)
|
|
28
|
+
|
|
29
|
+
Verified event kinds in the JSONL trace (`~/.aware/logs/<id>/<instance>/<run>.jsonl`), in order:
|
|
30
|
+
`run-start` → per node `node-start` then **`node-output`** → `run-end` (`status:"ok"|…`).
|
|
31
|
+
|
|
32
|
+
- The completion kind is **`node-output`**, NOT `output`/`write` (those legacy kinds don't appear
|
|
33
|
+
from current aware — a `switch` that only cased `output`/`write` silently misclassifies them).
|
|
34
|
+
- Each per-node event carries **`ev.node`** (the node id) + `ev.agent`/`ev.command`. Keep the raw
|
|
35
|
+
`node` on trace rows if anything downstream (e.g. the Execution-tab node filter) needs it.
|
|
36
|
+
|
|
37
|
+
### The trace streams LIVE for report runs (non-obvious)
|
|
38
|
+
|
|
39
|
+
floless watches `~/.aware/logs` and broadcasts a `trace-file` SSE event as the runtime writes the
|
|
40
|
+
JSONL — replaying the file cumulatively. That branch is gated `!state.running`; a **report run
|
|
41
|
+
sets `reportRunning` (NOT `state.running`)**, so the branch fires and per-node canvas status
|
|
42
|
+
(running → done) lights up **in real time** behind the modal. Non-report/`simulate` runs set
|
|
43
|
+
`state.running`, so they paint from the **batched** trace that `/api/run` broadcasts after the CLI
|
|
44
|
+
returns. Either way, painting per-node status in `pushTrace` (in `web/aware.js`) covers both,
|
|
45
|
+
plus terminal-driven runs.
|
|
46
|
+
|
|
47
|
+
## Verification gotchas (driving the localhost API + Playwright on Windows)
|
|
48
|
+
|
|
49
|
+
Two traps that waste time when verifying server changes against `http://127.0.0.1:4317`:
|
|
50
|
+
|
|
51
|
+
- **Rapid-fire `curl` to the localhost API returns `000` (connection failed) for all but the last
|
|
52
|
+
call in a tight loop.** It's Windows ephemeral-port / TIME_WAIT pressure from many short-lived
|
|
53
|
+
connections, NOT a server bug (the route works — a single call returns 200). To drive the API in
|
|
54
|
+
bulk (seeding/cleanup), use ONE `python - <<'PY' … urllib.request …` process (keeps the work in
|
|
55
|
+
one process, no connection storm) or space `curl`s out. The browser's SSE/keepalive connection is
|
|
56
|
+
unaffected, so real UI verification is fine.
|
|
57
|
+
- **Playwright MCP "Browser is already in use for …mcp-chrome…".** After a crash/disconnect the
|
|
58
|
+
MCP can't relaunch because an orphan Chrome still holds the profile. Recover: find the orphan
|
|
59
|
+
chrome (`Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" | ? CommandLine -like
|
|
60
|
+
'*mcp-chrome*'`), `taskkill /PID <pid> /T /F` it (its parent is the active `@playwright/mcp`
|
|
61
|
+
node process), optionally delete a stale `%LOCALAPPDATA%\ms-playwright\<profile>\lockfile`, then
|
|
62
|
+
`browser_navigate` again — the MCP relaunches cleanly. The MCP browser can drop mid-session here;
|
|
63
|
+
re-attach with the same recipe and re-assert via a DOM probe (`browser_evaluate`).
|
|
64
|
+
|
|
65
|
+
## Verifying "On trigger" (event-driven) routines without live Tekla
|
|
66
|
+
|
|
67
|
+
The trigger-routine UI (the **⏱ Routines** panel's `trigger` kind) is real-E2E-verifiable on any
|
|
68
|
+
machine via a `self_test:true` watch fixture — no Tekla, no `--simulate`:
|
|
69
|
+
|
|
70
|
+
- **Fixture:** `demos/tekla-watch-smoke/` — one `tekla.watch` node with `config.self_test:true`
|
|
71
|
+
(+ `filter:welded` etc.) → the bridge emits one `listening` then **5 synthetic `fired` events**
|
|
72
|
+
through the real watch code path. Install/compile like any app (`aware app install ./demos/…`;
|
|
73
|
+
reinstall needs `aware app uninstall` first; compile from `~/.aware/apps`). The watch's `filter`
|
|
74
|
+
lives in **node config**, not top-level `inputs:`, so v1 trigger routines take no source params
|
|
75
|
+
(an eligible app with no `inputs:` renders zero input fields — fine).
|
|
76
|
+
- **Eligibility:** `GET /api/app/:id` → `app.triggerSource` is non-null when the source node runs a
|
|
77
|
+
`lifecycle:start` + `stream` command. The Add/Edit form keys the schedule-vs-trigger chooser off
|
|
78
|
+
this; `tekla-watch-smoke` is eligible.
|
|
79
|
+
- **Live row state to assert:** create `{kind:'trigger', workflow, inputs:{}, enabled:true}` → the
|
|
80
|
+
row's `.rtn-next` goes `listening` → (events) → ends `stopped` (self_test exits after 5; a real
|
|
81
|
+
never-ending watch stays `listening · N×`). `GET /api/routines` annotates each trigger routine
|
|
82
|
+
with `session:{state,firedCount,lastEvent,error}` and the persisted `trigger:{nodeId,agent,command}`
|
|
83
|
+
— assert `firedCount>=5` there (the label may have already settled to `stopped` by the time you
|
|
84
|
+
poll, so gate the pass on the **API** firedCount, not only the transient label).
|
|
85
|
+
|
|
86
|
+
### Driving the licensed dev server + Playwright (what actually works here)
|
|
87
|
+
|
|
88
|
+
- **The installed SEA server usually already holds :4317** (and it 404s `/api/status` — different
|
|
89
|
+
build). Don't fight it: `index.ts` honors `PORT`, so run the dev server on a free port —
|
|
90
|
+
`PORT=4318 FLOLESS_LICENSE_DIR="$LOCALAPPDATA/FlolessApp-data" npx tsx main.ts --serve` (from
|
|
91
|
+
`server/`, background). Without the license dir it serves the **sign-in gate** (no `#routines-btn`)
|
|
92
|
+
— assert `document.getElementById('routines-btn')` exists before driving the panel.
|
|
93
|
+
- **The Playwright MCP browser is flaky/locked here** ("Browser is already in use…"). A **standalone
|
|
94
|
+
Node script is more reliable**: `playwright-core` resolves at
|
|
95
|
+
`C:/Users/Pawel/AppData/Roaming/npm/node_modules/@playwright/cli/node_modules/playwright-core`;
|
|
96
|
+
the registry's default `chrome-headless-shell` is often missing, so pass an explicit
|
|
97
|
+
`executablePath` to an installed chromium build under
|
|
98
|
+
`%LOCALAPPDATA%/ms-playwright/chromium-<rev>/chrome-win64/chrome.exe` (1217/1208 seen). Have the
|
|
99
|
+
script write a `report.txt` + screenshots and `process.exit(pass?0:1)`; **run it as a background
|
|
100
|
+
Bash task** — the run-tool's output buffer here lags badly, but the background-task *completion
|
|
101
|
+
notification* reliably flushes the result. **Exercise real clicks** (e.g. click both
|
|
102
|
+
`.rtn-mode-btn`s and assert the layout toggles) — a default-path-only run misses dead-control bugs
|
|
103
|
+
(an unwired chooser shipped exactly because the first pass relied on the auto-default).
|
|
104
|
+
- **Footgun — a failing Bash command cancels its sibling tool calls in the same assistant turn.**
|
|
105
|
+
Don't batch an `Edit`/`Write` with a `grep -c`/`[ ]` test that can exit non-zero (it cancels the
|
|
106
|
+
edits). Append `; true` to verification one-liners, or isolate risky commands in their own turn.
|
|
107
|
+
|
|
108
|
+
## Stop / cancel an in-flight run
|
|
109
|
+
|
|
110
|
+
`aware app run` blocks; the escape hatch for a hung/unattached host:
|
|
111
|
+
- `aware-adapter.ts` tracks the active run's child; `cancelActiveRun()` kills it. On Windows kill
|
|
112
|
+
the **whole tree** with `taskkill /pid <pid> /T /F` — `aware` spawns `node` → the host bridge,
|
|
113
|
+
so a bare `child.kill()` orphans the bridge.
|
|
114
|
+
- `run()` throws a distinct `AwareError(..., { cancelled: true })` when killed; `/api/run` surfaces
|
|
115
|
+
it **in-band** (HTTP 200 `{ cancelled:true }`) so it reads as "cancelled", not a fault.
|
|
116
|
+
- `POST /api/run/stop` → `cancelActiveRun()`. `api()` drops the `cancelled` flag (throws a bare
|
|
117
|
+
Error), so the UI keeps a local `cancelRequested` flag to render "Run cancelled" vs "Run failed",
|
|
118
|
+
and `pushTrace` stops painting the canvas once it's set (late fs-watcher events would otherwise
|
|
119
|
+
re-pulse a node "running" after the wipe).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# The tekla `exec` contract (Roslyn C# against the live model)
|
|
2
|
+
|
|
3
|
+
The `tekla` agent's `exec` command runs a C# **script** (Roslyn scripting, not a full program)
|
|
4
|
+
inside the host bridge `aware-tekla.exe`, which is connected to a running Tekla Structures
|
|
5
|
+
instance with a model open. Reference impl: `demos/tekla-bom-by-phase/bom-by-phase.exec.cs`.
|
|
6
|
+
|
|
7
|
+
## Globals injected into the script
|
|
8
|
+
|
|
9
|
+
| Global | Type | Notes |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `model` | `dynamic` (`Tekla.Structures.Model.Model`) | `null` if no live model — guard it. |
|
|
12
|
+
| `args` | `IDictionary<string, object?>` | The node's `config.args` block, JSON-decoded. |
|
|
13
|
+
|
|
14
|
+
`args` values are JSON-typed by the bridge: a JSON number arrives as `int`/`long`/`double`, a
|
|
15
|
+
string as `string`, etc. **Read defensively** — a templated `{{ inputs.phase }}` may arrive as a
|
|
16
|
+
number or a string depending on how it was supplied:
|
|
17
|
+
|
|
18
|
+
```csharp
|
|
19
|
+
int phase = 1;
|
|
20
|
+
if (args != null && args.TryGetValue("phase", out var pv) && pv != null) {
|
|
21
|
+
if (pv is int pi) phase = pi;
|
|
22
|
+
else if (pv is long pl) phase = (int)pl;
|
|
23
|
+
else if (pv is double pd) phase = (int)pd;
|
|
24
|
+
else { int.TryParse(pv.ToString(), out var ps); if (ps != 0) phase = ps; }
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Return value
|
|
29
|
+
|
|
30
|
+
End the script with `return <object>;`. The bridge serializes it and wraps it under `result`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{ "ok": true, "result": { "ok": true, "phase": 2, "html": "<!doctype html>…" },
|
|
34
|
+
"host": "tekla", "host_version": "2025.0", "host_pid": 47016 }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For a report node, return the HTML **inline**:
|
|
38
|
+
`return new { ok = true, phase, html = sb.ToString(), modelName, totalParts, … };`
|
|
39
|
+
floless extracts `data.result.html` for the HTML Viewer.
|
|
40
|
+
|
|
41
|
+
## References available / NOT available
|
|
42
|
+
|
|
43
|
+
- **Available:** `System`, `System.Collections.Generic`, `System.Linq`, `System.Text`,
|
|
44
|
+
`System.IO`, `System.Diagnostics.Debugger`, and the Tekla Open API
|
|
45
|
+
(`Tekla.Structures.Model`, etc. — discovered/probed from the running install).
|
|
46
|
+
- **NOT referenced:** `System.Net` (so **no** `WebUtility.HtmlEncode` — escape manually),
|
|
47
|
+
`System.Diagnostics.Process`. Don't rely on them.
|
|
48
|
+
|
|
49
|
+
Manual HTML escape (since `System.Net` is unavailable):
|
|
50
|
+
|
|
51
|
+
```csharp
|
|
52
|
+
Func<string,string> E = s => (s ?? "")
|
|
53
|
+
.Replace("&","&").Replace("<","<").Replace(">",">").Replace("\"",""");
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Enumerating + filtering parts
|
|
57
|
+
|
|
58
|
+
Use the selector and filter with `as Part` (there is no `GetAllObjectsWithType(typeof(Part))`
|
|
59
|
+
overload that takes a `Type` — that's a CS1503; `GetAllObjects()` + `as Part` is the way):
|
|
60
|
+
|
|
61
|
+
```csharp
|
|
62
|
+
var en = ((Model)model).GetModelObjectSelector().GetAllObjects();
|
|
63
|
+
while (en.MoveNext()) {
|
|
64
|
+
var part = en.Current as Part; if (part == null) continue;
|
|
65
|
+
Phase ph = null; try { part.GetPhase(out ph); } catch { ph = null; }
|
|
66
|
+
if (ph == null || ph.PhaseNumber != phase) continue; // phase filter
|
|
67
|
+
string name = ""; part.GetReportProperty("NAME", ref name); // BEAM/COLUMN/PLATE/…
|
|
68
|
+
string profile = ""; part.GetReportProperty("PROFILE", ref profile);
|
|
69
|
+
string material = "";part.GetReportProperty("MATERIAL", ref material);
|
|
70
|
+
double w = 0; part.GetReportProperty("WEIGHT", ref w);
|
|
71
|
+
double l = 0; part.GetReportProperty("LENGTH", ref l);
|
|
72
|
+
// aggregate: category(NAME) → "profile|material" → {qty,weight,length}
|
|
73
|
+
}
|
|
74
|
+
string modelName; try { modelName = ((Model)model).GetInfo().ModelName; } catch { modelName = "Tekla model"; }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Categorize by `NAME`, not the .NET class.** The .NET class (`Beam`, `ContourPlate`,
|
|
78
|
+
`PolyBeam`) lumps beams + columns together; the report property `NAME` carries the detailer's
|
|
79
|
+
intent (`BEAM`, `COLUMN`, `CHANNEL`, `EMBED PLATE`, `ANGLE`, `PLATE`, `STIFF. PLATE`, …), which
|
|
80
|
+
is what users want grouped. Group by `NAME` (category) then by `profile|material` within each.
|
|
81
|
+
|
|
82
|
+
## Verifying against a live model (direct bridge call)
|
|
83
|
+
|
|
84
|
+
To test exec C# without the full app runtime, send it straight to the bridge over its
|
|
85
|
+
JSON-stdin contract:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
BIN="C:/Users/Pawel/.aware/bridges/aware-tekla.exe" # post-#148 persistent location
|
|
89
|
+
python -c "import json,io; print(json.dumps({'code': io.open('x.cs',encoding='utf-8').read(), 'args':{'phase':2}}))" \
|
|
90
|
+
| "$BIN" exec --version 2025.0 --json-stdin
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The bridge reads `version`, `code`, and `args` from the stdin JSON (or `--version` flag). One
|
|
94
|
+
instance serves one model; if the bridge is busy (e.g. a debug session holding it), wait for it
|
|
95
|
+
to free.
|
|
96
|
+
|
|
97
|
+
## Gotchas observed
|
|
98
|
+
|
|
99
|
+
- A model may have **no phase 1** (College Desert has phases 2 and 3001). A `default: 1` that
|
|
100
|
+
returns an empty report is correct behavior — the user changes the phase per model. Always
|
|
101
|
+
render an empty-state message rather than failing.
|
|
102
|
+
- `GetReportProperty` returns by `ref`; initialize the target first (`string x = "";`).
|
|
103
|
+
- The bridge commits changes only when the script wrote and the connection is live; pure-read
|
|
104
|
+
scripts (like BOM) need no `safety:` block. Write-mode nodes do (`*.create`, `*.update`, …).
|