openfox 2.0.112 → 2.0.114

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +1 -0
  3. package/dist/CHANGELOG.md +49 -0
  4. package/dist/agent-defaults/builder.agent.md +1 -0
  5. package/dist/agent-defaults/planner.agent.md +1 -0
  6. package/dist/{backend-P3GHHLHJ.js → backend-LVYHLGQJ.js} +2 -2
  7. package/dist/{chat-handler-BYXZIIV3.js → chat-handler-KWXRSWXZ.js} +32 -26
  8. package/dist/{chunk-XWNCKIPO.js → chunk-2XD65L7M.js} +2 -2
  9. package/dist/{chunk-C5NQF2K4.js → chunk-3O77NT4T.js} +29 -10
  10. package/dist/{chunk-XCK5IBO4.js → chunk-7GWARYQC.js} +60 -1042
  11. package/dist/{chunk-YHEQTVFV.js → chunk-AZHWLLPP.js} +1 -1
  12. package/dist/{chunk-FFPCVN5M.js → chunk-BZVW77PI.js} +4 -4
  13. package/dist/{chunk-SDGNV45R.js → chunk-FW4NDUBL.js} +15 -15
  14. package/dist/{chunk-Z56SEG4Z.js → chunk-GUHHUDNL.js} +6 -6
  15. package/dist/{chunk-RT2A2RRL.js → chunk-HP5CIB66.js} +2 -2
  16. package/dist/{chunk-UAF2FOI5.js → chunk-HV6IALXF.js} +7 -7
  17. package/dist/{chunk-SYJMUIRC.js → chunk-I7FY2LTR.js} +2 -2
  18. package/dist/{chunk-RISQXU32.js → chunk-JP7NM545.js} +2 -2
  19. package/dist/{chunk-NB2D6I64.js → chunk-JXLRL5JR.js} +5 -5
  20. package/dist/{chunk-RAKPI4OE.js → chunk-K733B5T2.js} +264 -27
  21. package/dist/{chunk-C372UH7N.js → chunk-KSXCOHHW.js} +18 -20
  22. package/dist/{chunk-JDT63SSP.js → chunk-L63Q4JQJ.js} +3 -3
  23. package/dist/{chunk-ERYUSG5C.js → chunk-LXYPMFTH.js} +3 -3
  24. package/dist/{chunk-FSYRANYR.js → chunk-MUSPWEBB.js} +4 -5
  25. package/dist/chunk-NCZRWCJN.js +7 -0
  26. package/dist/{chunk-JR54MDZI.js → chunk-NLO64UE3.js} +2 -2
  27. package/dist/{chunk-3UILDGOO.js → chunk-NWJLWLKF.js} +8 -8
  28. package/dist/chunk-OSXPQSLW.js +128 -0
  29. package/dist/chunk-OTYAEEX3.js +1081 -0
  30. package/dist/{chunk-PCC4DBPF.js → chunk-OXSTNSCF.js} +2 -2
  31. package/dist/{chunk-33Q7LCVD.js → chunk-P5UANBBX.js} +6 -4
  32. package/dist/{chunk-LCQ5AN6Y.js → chunk-PBW3YC3G.js} +89 -1
  33. package/dist/{chunk-IYTDCPEQ.js → chunk-QD7MRMCH.js} +352 -333
  34. package/dist/{chunk-N4RTAGFY.js → chunk-SA34STZ6.js} +2 -2
  35. package/dist/{chunk-FR5LUTLQ.js → chunk-SBSUDHL4.js} +10 -6
  36. package/dist/{chunk-6FPGJR7O.js → chunk-SFZGMB2G.js} +2 -2
  37. package/dist/{chunk-SVJFMZ2Q.js → chunk-TKKP56QK.js} +11 -4
  38. package/dist/{chunk-JNHKKUGJ.js → chunk-UPBWEHIL.js} +30 -148
  39. package/dist/{chunk-QGXLZZBS.js → chunk-UTIQ7OO5.js} +2 -2
  40. package/dist/{chunk-RKMP74BB.js → chunk-VPVLO436.js} +4 -4
  41. package/dist/chunk-W77NBJAZ.js +957 -0
  42. package/dist/{chunk-P45RJGOT.js → chunk-X25Q6RAV.js} +1 -1
  43. package/dist/{chunk-BOSKQI62.js → chunk-YGJIK3CG.js} +4 -4
  44. package/dist/cli/dev.js +1 -1
  45. package/dist/cli/index.js +1 -1
  46. package/dist/{client-QNQTX5EA.js → client-EM7ISGUP.js} +6 -6
  47. package/dist/{compactor-X2KZ3XFU.js → compactor-V3LR7APW.js} +13 -12
  48. package/dist/{config-TW5FHQEM.js → config-EAB3V4QK.js} +2 -2
  49. package/dist/{dynamic-context-ZRQO2FJN.js → dynamic-context-MVGOF5V2.js} +10 -9
  50. package/dist/{events-Y6XXQYFG.js → events-PI7IMPHT.js} +8 -7
  51. package/dist/{folding-Z4GNWRC3.js → folding-FO57K4JM.js} +6 -5
  52. package/dist/http-client-33EZ3BLR.js +11 -0
  53. package/dist/{inspect-proxy-N42Z5TOG.js → inspect-proxy-NHKQQ2CD.js} +4 -4
  54. package/dist/launch-L5ROVNI3.js +52 -0
  55. package/dist/{manager-HCMDFEQH.js → manager-66YAEO7K.js} +5 -5
  56. package/dist/{model-overrides-O3ZYKFP7.js → model-overrides-L53PZNZA.js} +4 -4
  57. package/dist/orchestrator-XK5K3RPL.js +63 -0
  58. package/dist/package.json +7 -6
  59. package/dist/{path-security-525YLND3.js → path-security-RSEI3YKB.js} +12 -11
  60. package/dist/{platform-MEQUWG6U.js → platform-WA4GK5BO.js} +4 -4
  61. package/dist/{processor-UN4H4V7F.js → processor-WDZSFBVO.js} +31 -25
  62. package/dist/{project-creator-BVA55HY6.js → project-creator-676WQXVI.js} +3 -3
  63. package/dist/{projects-PQXME5S4.js → projects-SSBWIYL4.js} +3 -3
  64. package/dist/{protocol-D6MISQyz.d.ts → protocol-BzHs024-.d.ts} +23 -3
  65. package/dist/{protocol-FTHAR2YL.js → protocol-RKSWKNXI.js} +3 -3
  66. package/dist/provider/index.d.ts +4 -4
  67. package/dist/{provider-3R4X62LW.js → provider-PLIWRBCF.js} +9 -9
  68. package/dist/{provider-manager-W5N4XRE5.js → provider-manager-2QSJTPWS.js} +7 -7
  69. package/dist/{registry-74KPZ5NV.js → registry-ZP4TN2IN.js} +5 -4
  70. package/dist/{serve-WHFUNQ6Z.js → serve-MZ3BCCWC.js} +36 -33
  71. package/dist/server/index.d.ts +8 -5
  72. package/dist/server/index.js +35 -32
  73. package/dist/server-EPXODFMB.js +55 -0
  74. package/dist/service-54PGIPDQ.js +25 -0
  75. package/dist/{session-overrides-XH6I3COP.js → session-overrides-LXN6W7SA.js} +7 -6
  76. package/dist/{sessions-D772YTWL.js → sessions-CHPVUG6H.js} +8 -5
  77. package/dist/{settings-KO6DSJLD.js → settings-NEBM3GQX.js} +3 -3
  78. package/dist/shared/index.d.ts +3 -3
  79. package/dist/shared/index.js +1 -1
  80. package/dist/skill-defaults/workflows/SKILL.md +517 -0
  81. package/dist/tasks-TJXG2V3U.js +217 -0
  82. package/dist/{tools-HTUTYMKI.js → tools-CIM26IFB.js} +31 -23
  83. package/dist/{types-BMEOlLT0.d.ts → types-CMtHpDWT.d.ts} +81 -1
  84. package/dist/{types-D0TgGL3t.d.ts → types-CbggXFvR.d.ts} +1 -1
  85. package/dist/{update-BRUFGGTW.js → update-UZBBO42X.js} +2 -2
  86. package/dist/web/assets/{index-nS1p0g8O.css → index-BmILAZrT.css} +1 -1
  87. package/dist/web/assets/index-rZHXkfMq.js +324 -0
  88. package/dist/web/index.html +2 -2
  89. package/dist/web/sw.js +1 -1
  90. package/dist/workflow-defaults/default.workflow.json +45 -5
  91. package/dist/{workspace-YDUQZ3HC.js → workspace-2XODU4YJ.js} +4 -4
  92. package/package.json +7 -6
  93. package/dist/chunk-P5AA7OYJ.js +0 -7
  94. package/dist/http-client-2GDKHZIG.js +0 -11
  95. package/dist/orchestrator-P5EUMC7E.js +0 -57
  96. package/dist/server-7MOOJR2N.js +0 -48
  97. package/dist/web/assets/index-Dukvfqm-.js +0 -323
@@ -0,0 +1,517 @@
1
+ ---
2
+ name: workflows
3
+ description: 'Author and manage OpenFox workflow files (.workflow.json): step types, transition conditions, template variables, and storage locations.'
4
+ metadata:
5
+ version: 1.0.0
6
+ openfox:
7
+ displayName: Workflows
8
+ ---
9
+
10
+ # OpenFox Workflows — Authoring Reference
11
+
12
+ Authoritative reference for creating and editing OpenFox workflow files (`.workflow.json`).
13
+ Executor implementation: `src/server/workflows/` (`types.ts`, `executor.ts`, `registry.ts`)
14
+ and `src/server/routes/workflows.ts`.
15
+
16
+ A workflow is a declarative **state machine**: a sequence of steps (agent turns, sub-agent
17
+ calls, shell commands, user pauses) wired together by **transitions** with **conditions**.
18
+ The executor walks the graph until it reaches a terminal state (`$done` or `$blocked`).
19
+
20
+ When a user asks you to create or edit a workflow, follow this document.
21
+
22
+ ---
23
+
24
+ ## 1. Storage Locations & Precedence
25
+
26
+ Workflows are plain JSON files with extension `.workflow.json` (never markdown). Three
27
+ tiers, merged **by `metadata.id`** with later tiers overriding earlier ones:
28
+
29
+ | Tier | Location | Notes |
30
+ | ----------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
31
+ | **Default** | `src/server/workflows/defaults/{id}.workflow.json` (bundled → `dist/workflow-defaults/`) | Ships with the product. Not editable, not deletable. |
32
+ | **User** | `{configDir}/workflows/{id}.workflow.json` | Per-user, machine-local. |
33
+ | **Project** | `{projectDir}/.openfox/workflows/{id}.workflow.json` | Committed to the repo, shared with the team. **Recommended for agent-authored workflows.** |
34
+
35
+ **Precedence (highest wins):** `project > user > default`. A project workflow with the same
36
+ `metadata.id` as a bundled default replaces it everywhere.
37
+
38
+ **Config dirs:** production `~/.config/openfox/`, development `~/.config/openfox-dev/`
39
+ (other platforms: `XDG_CONFIG_HOME`/`~/.config` on Linux, `~/Library/Application Support`
40
+ on macOS, `%APPDATA%` on Windows).
41
+
42
+ **Filename convention:** `{id}.workflow.json` — the loader keys on the embedded
43
+ `metadata.id` (any `*.workflow.json` is read), but every writer uses the ID as the
44
+ filename, so keep them in sync: one workflow per file. `id` is a lowercase slug
45
+ `[a-z0-9-]` (e.g. `build-test-fix`).
46
+
47
+ **Minimum validity** (files failing this are **silently skipped** with a warning — no hard
48
+ error at startup): `metadata.id` is set **and** `steps` is a non-empty array. Malformed
49
+ JSON is also skipped.
50
+
51
+ **API surface:** CRUD routes at `/api/workflows` (`src/server/routes/workflows.ts`) — list,
52
+ get/create/update/delete, duplicate, template-variables. Creating via file is preferred for
53
+ agent-authored workflows because the file is reviewable and committable.
54
+
55
+ ---
56
+
57
+ ## 2. Overall Shape
58
+
59
+ ```jsonc
60
+ {
61
+ "metadata": {
62
+ "id": "build-test-fix",
63
+ "name": "Build → Test → Fix",
64
+ "description": "Loop: build, run tests, fix failures.",
65
+ "version": "1.0.0",
66
+ "color": "#3b82f6", // optional, UI accent
67
+ "parameters": [
68
+ // optional, prompted at launch
69
+ {
70
+ "id": "feature",
71
+ "label": "Feature name",
72
+ "description": "What to implement",
73
+ "position": 0, // optional, ordering
74
+ "required": true, // optional, default false
75
+ },
76
+ ],
77
+ },
78
+ "entryStep": "build", // ID of the first step to execute
79
+ "settings": {
80
+ "maxIterations": 50, // safety cap on state-machine iterations
81
+ },
82
+ "steps": [/* see §3 */],
83
+ "startCondition": { "type": "always" }, // optional, gates workflow start
84
+ }
85
+ ```
86
+
87
+ ### Field reference
88
+
89
+ | Field | Type | Required | Description |
90
+ | ------------------------ | --------------------- | -------- | -------------------------------------------------------------------------------------------- |
91
+ | `metadata.id` | string | yes | Unique slug; filename stem. Lowercase `[a-z0-9-]`. |
92
+ | `metadata.name` | string | yes | Human-readable display name. |
93
+ | `metadata.description` | string | yes | What the workflow does. |
94
+ | `metadata.version` | string | yes | Semver-ish version string. |
95
+ | `metadata.color` | string | no | Hex color for UI badges. |
96
+ | `metadata.parameters` | `WorkflowParameter[]` | no | User inputs requested at launch (see §6). |
97
+ | `entryStep` | string | yes | ID of the step the workflow starts at. |
98
+ | `settings.maxIterations` | number | yes | Hard cap on executor loop iterations. Exceeding it ⇒ `BLOCKED`. Default in the editor is 50. |
99
+ | `steps` | `WorkflowStep[]` | yes | Non-empty. Order is display-only — execution follows transitions. |
100
+ | `startCondition` | `TransitionCondition` | no | Gates workflow start on session metadata (default: `always`). |
101
+
102
+ ---
103
+
104
+ ## 3. Steps
105
+
106
+ Every step shares these base fields:
107
+
108
+ | Field | Type | Required | Description |
109
+ | ------------- | -------------- | -------- | -------------------------------------------------------------------------------------------------- |
110
+ | `id` | string | yes | Unique within the workflow. Referenced by `entryStep`, `goto`, `{{stepOutput.id}}`. |
111
+ | `name` | string | yes | Display name. |
112
+ | `phase` | string | yes | Maps to the session phase for UI: `"build"`, `"verification"`, `"waiting"`, `"blocked"`, `"done"`. |
113
+ | `transitions` | `Transition[]` | yes | Evaluated **in order, first match wins**. See §4. |
114
+ | `subGroup` | string | no | Groups steps for running a subset in isolation. See §7. |
115
+
116
+ ### 3.1 `agent` — full LLM turn with tools
117
+
118
+ ```jsonc
119
+ {
120
+ "id": "implement",
121
+ "name": "Implement",
122
+ "type": "agent",
123
+ "phase": "build",
124
+ "agentId": "builder", // optional, default: resolved default agent (usually "planner")
125
+ "prompt": "Implement {{criteriaCount}} criteria…",
126
+ "nudgePrompt": "Keep going. {{reason}} …", // optional, injected on re-entry
127
+ "transitions": [/* … */],
128
+ }
129
+ ```
130
+
131
+ - Runs a full agent turn (LLM + tool loop) with the agent's tool registry.
132
+ - `agentId` defaults to the resolved default agent: DB setting → global config →
133
+ `OPENFOX_DEFAULT_AGENT` env → `"planner"`. Common values: `"builder"`, `"planner"`.
134
+ - `prompt` is injected as a user message **on first entry**, with
135
+ `"\n\nOnce you're done, call step_done()"` appended. Supports template variables (§6).
136
+ - **Advance rule:** the step only advances after the agent calls **`step_done()`**
137
+ successfully. If it finishes without `step_done()`, the executor **loops back to the
138
+ same step** and injects a nudge (`nudgePrompt` if present, plus a `step_done()` reminder).
139
+ If no `prompt` is set and it's the first entry, a generic kickoff
140
+ ("Proceed with the current step.") is injected.
141
+ - **Result:** if the agent uses `return_value` (with `result` and/or `content`), those
142
+ become the step's `result` and `stepOutput`; otherwise the result defaults to
143
+ `"completed"`. `stepOutput.stepDoneCalled` is `"true"`/`"false"`.
144
+
145
+ ### 3.2 `sub_agent` — isolated sub-agent with fresh context
146
+
147
+ ```jsonc
148
+ {
149
+ "id": "verify",
150
+ "name": "Verifier",
151
+ "type": "sub_agent",
152
+ "phase": "verification",
153
+ "subAgentType": "verifier", // required — any configured sub-agent type
154
+ "prompt": "## Criteria\n{{criteriaList}} …",
155
+ "nudgePrompt": "…", // declared in the schema
156
+ "transitions": [/* … */],
157
+ }
158
+ ```
159
+
160
+ - Runs one isolated sub-agent turn (fresh context). The `step_done` tool is **removed**
161
+ from sub-agents.
162
+ - `prompt` defaults to `"Perform your task."` if omitted.
163
+ - Unknown `subAgentType` ⇒ the step resolves with `result: "error"`.
164
+ - **Result:** the sub-agent's `return_value` `result`, or `"success"` if none. Content
165
+ lands in `{{stepOutput.content}}`.
166
+
167
+ ### 3.3 `shell` — run a command, branch on exit code
168
+
169
+ ```jsonc
170
+ {
171
+ "id": "lint",
172
+ "name": "Lint",
173
+ "type": "shell",
174
+ "phase": "verification",
175
+ "command": "npm run lint",
176
+ "timeout": 90000, // optional, ms, default 60000
177
+ "successExitCodes": [0], // optional, default [0]
178
+ "transitions": [/* … */],
179
+ }
180
+ ```
181
+
182
+ - `command` runs in the session workdir and supports template variables (§6).
183
+ - **Result:** `"success"` if the exit code is in `successExitCodes`, else `"failure"`.
184
+ - `stepOutput`: `stdout`, `stderr`, `exitCode` (string).
185
+ - The command and its output (truncated to 10k chars) are echoed into the chat as system
186
+ messages.
187
+
188
+ ### 3.4 `user` — pause for a human decision
189
+
190
+ ```jsonc
191
+ {
192
+ "id": "approve",
193
+ "name": "Approve Fix Plan",
194
+ "type": "user",
195
+ "phase": "verification",
196
+ "transitions": [
197
+ { "when": { "type": "step_result", "result": "apply" }, "goto": "apply_fixes" },
198
+ { "when": { "type": "step_result", "result": "skip" }, "goto": "start_dev_server" },
199
+ { "when": { "type": "always" }, "goto": "apply_fixes" },
200
+ ],
201
+ }
202
+ ```
203
+
204
+ - Pauses the workflow and presents **buttons derived from the transitions**:
205
+ - each `step_result` transition ⇒ one choice button (its `result` string is both the id
206
+ and the label);
207
+ - an `always` transition ⇒ a `"Continue"` button (`id: "continue"`).
208
+ - On resume, the picked choice becomes the step's `result`; the chosen button's `goto`
209
+ drives the next transition. Selecting "Continue" (or resuming without an explicit choice)
210
+ yields the reserved result `"continue"`, which matches an `always` transition.
211
+ - Use this for approvals, plan sign-off, manual QA gates, etc. Only `step_result` and
212
+ `always` transitions matter for choices; other conditions are ignored when deriving
213
+ buttons.
214
+
215
+ ---
216
+
217
+ ## 4. Transitions & Conditions
218
+
219
+ Each step carries an ordered `transitions` array. The executor evaluates them **in order
220
+ and takes the first whose `when` matches**. If none matches, the workflow goes `$blocked`
221
+ ("Runner blocked: No matching transition").
222
+
223
+ ```jsonc
224
+ { "when": { /* condition */ }, "goto": "next_step_id" | "$done" | "$blocked", "subGroup": "optional" }
225
+ ```
226
+
227
+ - `goto` is a step `id` or one of the terminal states:
228
+ - `"$done"` — workflow completes (session phase `done`, stats recorded).
229
+ - `"$blocked"` — workflow stops blocked.
230
+ - `subGroup` on a transition is only meaningful when running that sub-group (§7).
231
+
232
+ ### Conditions (all four)
233
+
234
+ | Condition | Matches when |
235
+ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
236
+ | `{ "type": "always" }` | Always. Use as the final fallback to avoid dead ends. |
237
+ | `{ "type": "step_result", "result": "x" }` | The current step returned result exactly `"x"` (from `return_value`, shell exit classification, or a user choice). |
238
+ | `{ "type": "metadata_all_match", "key": "criteria", "field": "status", "value": "passed" }` | **Every** session-metadata entry under `key` has `entry[field] === value`. |
239
+ | `{ "type": "metadata_all_in", "key": "criteria", "field": "status", "values": ["completed","passed"] }` | Every entry's `field` is **one of** `values`. |
240
+
241
+ Notes on metadata conditions:
242
+
243
+ - Operate on the session's metadata entries (managed via the `session_metadata` tool),
244
+ keyed by name — common keys: `criteria` (fields `status`, `description`, …) and
245
+ `review_findings` (field `status`: `open`/`resolved`/`dismissed`).
246
+ - **Empty entry list ⇒ condition is `true`** (vacuous truth). If there are no criteria at
247
+ all, a `metadata_all_match` on `criteria` passes.
248
+ - Evaluated with the **latest** session state after the step executes.
249
+
250
+ Example — build → test → fix loop:
251
+
252
+ ```
253
+ build ──(metadata_all_in status completed|passed)──▶ test
254
+ ▲ │
255
+ └───────────────(always fallback)─────────────────────┘
256
+ test ──(step_result "passed")────────────────────────▶ $done
257
+ test ──(step_result "failed")────────────────────────▶ fix
258
+ fix ──(always)──────────────────────────────────────▶ test
259
+ ```
260
+
261
+ ---
262
+
263
+ ## 5. Runtime Semantics (authoring-critical)
264
+
265
+ - **`step_done()` is mandatory for agent steps.** Without it, the step loops back on itself
266
+ with nudges. Write prompts that explicitly end with "call `step_done()`".
267
+ - **Results come from `return_value`** (`result`, `content`). Branch on `step_result`
268
+ conditions. Shell steps produce `success`/`failure` from exit codes; sub-agents default
269
+ to `success`; agent steps default to `completed`.
270
+ - **`maxIterations` caps the whole workflow**, not individual steps. Tight loops +
271
+ `always` self-transitions can burn it fast. Escape loops with `step_result` /
272
+ `metadata_*` conditions.
273
+ - **Blocking:** no matching transition ⇒ `$blocked`. `startCondition` unmet ⇒ blocked
274
+ before the first step. Hitting `maxIterations` ⇒ blocked.
275
+ - **Abort/resume:** aborting mid-workflow keeps the execution record alive; sending a new
276
+ message resumes from the current step.
277
+ - **Phases:** `step.phase` drives the session-phase UI (`build`, `verification`, …) and is
278
+ set as each step runs.
279
+ - **Session mode:** agent steps set the session mode to their `agentId`.
280
+
281
+ ---
282
+
283
+ ## 6. Template Variables (prompts, nudges, shell commands)
284
+
285
+ The named variables below are the canonical list (the API exposes them via
286
+ `GET /api/workflows/template-variables`); `{{stepOutput.<key>}}` resolves generically
287
+ against the previous step's output map:
288
+
289
+ | Variable | Meaning |
290
+ | ------------------------------- | -------------------------------------------------------------------------------------------------------------- |
291
+ | `{{workdir}}` | Session working directory |
292
+ | `{{reason}}` | Human-readable reason (e.g. "N criteria remaining") |
293
+ | `{{criteriaCount}}` | Total number of criteria |
294
+ | `{{pendingCount}}` | Number of pending/failed criteria |
295
+ | `{{criteriaList}}` | Formatted list of all criteria with status (`[PASSED]`, `[NEEDS VERIFICATION]`, `[FAILED]`, `[NOT COMPLETED]`) |
296
+ | `{{modifiedFiles}}` | Git-diff list of files modified this session |
297
+ | `{{stepOutput.content}}` | Text output of the previous step (agent/sub-agent `return_value` content) |
298
+ | `{{stepOutput.result}}` | Result string of the previous step |
299
+ | `{{stepOutput.stdout}}` | Previous **shell** step stdout |
300
+ | `{{stepOutput.stderr}}` | Previous **shell** step stderr |
301
+ | `{{stepOutput.exitCode}}` | Previous **shell** step exit code |
302
+ | `{{stepOutput.stepDoneCalled}}` | Whether the previous agent step called `step_done()` |
303
+ | `{{params}}` / `{{someParam}}` | User-supplied launch parameters (see below) |
304
+
305
+ - `{{stepOutput.<anything>}}` is resolved generically from the previous step's output map;
306
+ unknown keys render empty.
307
+ - `{{stepOutput.*}}` refers to the **immediately preceding** executed step in the run (not
308
+ the step that transitioned to the current one via a loop).
309
+ - **Parameters:** any `metadata.parameters` entry is collected at launch and injected as
310
+ `{{paramId}}` in prompts/nudges/commands. Parameters resolve last and **cannot override**
311
+ the built-in variables above. Example: the `review` workflow prompts the user for
312
+ `pr_number` and uses `{{pr_number}}` throughout.
313
+ - Deprecated aliases: `{{verifierFindings}}` → `{{stepOutput.content}}`,
314
+ `{{previousStepOutput}}` → `{{stepOutput.stdout}}`.
315
+
316
+ ---
317
+
318
+ ## 7. Sub-Groups
319
+
320
+ A `subGroup` string on a step groups related steps so the workflow can be **run in
321
+ isolation as a slice** (e.g. the UI runs just the "code review" group of a larger
322
+ workflow):
323
+
324
+ - Running a sub-group executes **only** steps whose `subGroup` matches, starting at the
325
+ group's first step.
326
+ - Only untagged transitions and transitions tagged with the running sub-group (or a
327
+ sub-group already entered via escape) are candidates, evaluated first-match-wins.
328
+ - A candidate transition pointing to a step **outside** the active group is treated as
329
+ `$done` (the slice completes) — **unless** it is tagged with an entered sub-group.
330
+ Such a transition **escapes**: its target step is pulled into the slice and executes,
331
+ and the target's own sub-group tag becomes eligible too, so a slice can loop into
332
+ another sub-group and back (e.g. `verify` failure → `build` → `verify` → … →
333
+ all passed → `$done`).
334
+
335
+ Escaping example from the bundled "Build & Verify" workflow:
336
+
337
+ - `verify` step: `always → build` tagged `subGroup: "verify"` — lets the "verify" slice
338
+ pull the builder back in on failure.
339
+
340
+ Untagged transitions keep slices closed: in the "build" slice, the `build` step's
341
+ untagged `metadata_all_in → verify` edge is clamped to `$done`, so implementing alone
342
+ finishes without escalating into the verifier (verification is the "verify" slice's job).
343
+
344
+ Because only tagged transitions may leave the slice, a foreign-group-tagged transition
345
+ cannot preempt an in-slice one: transitions tagged with a sub-group that was never
346
+ entered are not evaluated at all in a slice run.
347
+
348
+ On a full run, `subGroup` (on steps and transitions) is purely organizational; transition
349
+ tags are ignored and every step's transitions apply as written.
350
+
351
+ ---
352
+
353
+ ## 8. Authoring Checklist (do this every time)
354
+
355
+ 1. **Choose scope.** Project workflows go in `.openfox/workflows/` (commit them — they're
356
+ part of the repo contract). User-global workflows go in `{configDir}/workflows/`.
357
+ 2. **Slug the ID** — lowercase `[a-z0-9-]`, used as filename `{id}.workflow.json` and as
358
+ the override key.
359
+ 3. **Fill `metadata`** completely: `id`, `name`, `description`, `version`; add `color` and
360
+ `parameters` when useful.
361
+ 4. **Design the graph:** pick `entryStep`, size `settings.maxIterations` generously but
362
+ sanely, and sketch steps + transitions on paper first.
363
+ 5. **Give every step** a unique `id`, a readable `name`, and a `phase`.
364
+ 6. **Agent steps:** write a concrete `prompt`, end it with "call `step_done()`", and use
365
+ `return_value` (with distinct `result` strings) wherever downstream steps branch on
366
+ outcome. Add `nudgePrompt` for retries when useful.
367
+ 7. **Transitions:** order them so specific conditions come first, and **always end with an
368
+ `always` fallback** to prevent `$blocked` dead ends.
369
+ 8. **Insert `user` steps** for anything needing a human gate (approvals, test sign-off).
370
+ 9. **Validate:** the file must be valid JSON with `metadata.id` + non-empty `steps`, or the
371
+ loader silently skips it. IDs must match `goto`/`entryStep` exactly.
372
+ 10. **Test:** launch the workflow (UI "Workflows ›" dropdown or `/api/workflows`) and watch
373
+ for `$blocked`/`Max iterations` outcomes; iterate.
374
+
375
+ ---
376
+
377
+ ## 9. Worked Examples
378
+
379
+ ### 9.1 Minimal skeleton — linear chain
380
+
381
+ ```json
382
+ {
383
+ "metadata": {
384
+ "id": "hello-check",
385
+ "name": "Hello Check",
386
+ "description": "Greet, run tests, report.",
387
+ "version": "1.0.0",
388
+ "color": "#22c55e"
389
+ },
390
+ "entryStep": "greet",
391
+ "settings": { "maxIterations": 10 },
392
+ "steps": [
393
+ {
394
+ "id": "greet",
395
+ "name": "Greet",
396
+ "type": "agent",
397
+ "phase": "build",
398
+ "agentId": "builder",
399
+ "prompt": "Say hi in one line. Then call step_done().",
400
+ "transitions": [{ "when": { "type": "always" }, "goto": "run_tests" }]
401
+ },
402
+ {
403
+ "id": "run_tests",
404
+ "name": "Run Tests",
405
+ "type": "shell",
406
+ "phase": "verification",
407
+ "command": "npm run test",
408
+ "transitions": [
409
+ { "when": { "type": "step_result", "result": "success" }, "goto": "report" },
410
+ { "when": { "type": "always" }, "goto": "$blocked" }
411
+ ]
412
+ },
413
+ {
414
+ "id": "report",
415
+ "name": "Report",
416
+ "type": "agent",
417
+ "phase": "verification",
418
+ "agentId": "builder",
419
+ "prompt": "Tests passed. Summarize in two lines, then call step_done().",
420
+ "transitions": [{ "when": { "type": "always" }, "goto": "$done" }]
421
+ }
422
+ ],
423
+ "startCondition": { "type": "always" }
424
+ }
425
+ ```
426
+
427
+ ### 9.2 Rich — loop with metadata gating and a human gate
428
+
429
+ Uses `session_metadata` statuses: builder marks criteria `completed`; verifier flips them
430
+ to `passed`/`failed`; the workflow advances only when all are `passed`, with an `always`
431
+ fallback that loops back to the builder.
432
+
433
+ ```json
434
+ {
435
+ "metadata": {
436
+ "id": "review-loop",
437
+ "name": "Review Loop",
438
+ "description": "Implement, verify, human-approve, finalize.",
439
+ "version": "1.0.0",
440
+ "color": "#a371f7"
441
+ },
442
+ "entryStep": "build",
443
+ "settings": { "maxIterations": 50 },
444
+ "steps": [
445
+ {
446
+ "id": "build",
447
+ "name": "Implement",
448
+ "type": "agent",
449
+ "phase": "build",
450
+ "agentId": "builder",
451
+ "prompt": "Fulfil the {{criteriaCount}} criteria. Update each with session_metadata (status completed). Then call step_done().",
452
+ "transitions": [
453
+ {
454
+ "when": {
455
+ "type": "metadata_all_in",
456
+ "key": "criteria",
457
+ "field": "status",
458
+ "values": ["completed", "passed"]
459
+ },
460
+ "goto": "verify"
461
+ },
462
+ { "when": { "type": "always" }, "goto": "build" }
463
+ ]
464
+ },
465
+ {
466
+ "id": "verify",
467
+ "name": "Verify",
468
+ "type": "sub_agent",
469
+ "phase": "verification",
470
+ "subAgentType": "verifier",
471
+ "prompt": "## Criteria\n{{criteriaList}}\n\nMark each as passed or failed via session_metadata.",
472
+ "transitions": [
473
+ {
474
+ "when": { "type": "metadata_all_match", "key": "criteria", "field": "status", "value": "passed" },
475
+ "goto": "approve"
476
+ },
477
+ { "when": { "type": "always" }, "goto": "build" }
478
+ ]
479
+ },
480
+ {
481
+ "id": "approve",
482
+ "name": "Approve",
483
+ "type": "user",
484
+ "phase": "verification",
485
+ "transitions": [
486
+ { "when": { "type": "step_result", "result": "go" }, "goto": "finalize" },
487
+ { "when": { "type": "step_result", "result": "rework" }, "goto": "build" },
488
+ { "when": { "type": "always" }, "goto": "finalize" }
489
+ ]
490
+ },
491
+ {
492
+ "id": "finalize",
493
+ "name": "Finalize",
494
+ "type": "agent",
495
+ "phase": "verification",
496
+ "agentId": "builder",
497
+ "prompt": "Wrap up with a summary of what changed, then call step_done().",
498
+ "transitions": [{ "when": { "type": "always" }, "goto": "$done" }]
499
+ }
500
+ ],
501
+ "startCondition": { "type": "always" }
502
+ }
503
+ ```
504
+
505
+ ---
506
+
507
+ ## 10. Troubleshooting (authoring mistakes)
508
+
509
+ | Symptom | Likely cause |
510
+ | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
511
+ | Workflow doesn't appear at all | File invalid (missing `metadata.id` or empty `steps`) or malformed JSON — loader skips it. Wrong filename (must be `{id}.workflow.json`). |
512
+ | Stuck looping on one agent step | Agent never calls `step_done()`. Add the instruction to the prompt. |
513
+ | "Runner blocked: No matching transition" | No condition matched. Add an `always` fallback, or fix `step_result` strings / metadata field names to match exactly. |
514
+ | Never leaves a step despite `step_result` | Result string mismatch (case/whitespace) or wrong step's output is being inspected (`stepOutput` is the immediately-preceding step). |
515
+ | Blocked immediately at start | `startCondition` (non-`always`) evaluated false against current session metadata. |
516
+ | "Max iterations (N) reached" | Loop lacks a terminating condition. Widen the escape conditions, not just `maxIterations`. |
517
+ | User step shows unexpected/missing buttons | Choices are derived only from `step_result` and `always` transitions of that step. |