Ver código fonte

docs: pausing for an answer, answering a webhook, and fs.readdir

fszontagh 1 mês atrás
pai
commit
dcddfb2e3d
2 arquivos alterados com 107 adições e 7 exclusões
  1. 16 7
      docs/node-roadmap.md
  2. 91 0
      docs/nodes.md

+ 16 - 7
docs/node-roadmap.md

@@ -10,7 +10,7 @@ The runner already exposes `http`, `storage` (including the file store),
 
 ## What exists today
 
-51 node definitions across: triggers (click, get/post/put, imap, schedule with
+56 node definitions across: triggers (click, get/post/put, imap, schedule with
 cron and overlap control, error), flow control (if-condition, loop, wait), data
 (rss-reader, the six storage nodes), email (four imap nodes plus smtp-send), AI
 (ollama-chat, comfyui-prompt), integration (telegram-send, nextcloud-talk),
@@ -29,12 +29,21 @@ now parsed, which is how `merge` gets two input handles.
 
 ## Tier 2 - triggers
 
-| Node | Notes |
-| --- | --- |
-| **Respond to Webhook** | **Needs C++.** `webhook_controller` fires and forgets, so there is no way to return a computed body. Without it the GET/POST/PUT triggers cannot back a real API |
-| **Database Change** | Poll a collection for new or changed documents. Pure JS over `storage.query` plus a stored cursor |
-| **File Watch** | Poll a directory through `fs.stat`. Pure JS when paired with a schedule |
-| **Queue / Manual Approval** | **Large.** Needs resumable executions; the engine currently runs a workflow to completion in one pass |
+Built. `respond-to-webhook`, `database-change`, `file-watch` and
+`wait-for-approval` all live in `nodes/`, with fixtures under `tests/nodes/`.
+
+Three platform changes came with them: `fs.readdir`, so a node can see what is
+in a directory; a `_webhookResponse` marker any node can return to set the
+status, headers and body a webhook replies with; and resumable executions - an
+execution can pause on a `_pause` marker and be continued later through
+`POST /api/v1/executions/{id}/resume`, rebuilt from the workflow snapshot stored
+with it.
+
+Two things the original entries claimed turned out not to hold. The webhook
+controller never fired and forgot: it already waited for completion and returned
+a body, and what was missing was control over the status code, the headers, and
+which node decides the response. And File Watch could not be pure JavaScript,
+because the filesystem API had no way to list a directory.
 
 Cron and interval scheduling are already covered by `schedule-trigger`,
 including its overlap policy.

+ 91 - 0
docs/nodes.md

@@ -199,6 +199,65 @@ does - the branch name is just computed:
 return { _activeBranch: 'case' + i, ['case' + i]: data };
 ```
 
+## Pausing for an Answer
+
+A node pauses its execution by returning a `_pause` marker, the way a branching
+node returns `_activeBranch`:
+
+```javascript
+return {
+    token: token,
+    reason: 'Approve the refund',
+    expiresAt: Date.now() + 86400000,
+    _pause: { token: token, reason: 'Approve the refund', expiresAt: expiresAt }
+};
+```
+
+The engine stops the walk, stores the execution as `waiting` with everything
+computed so far, and returns. Nothing downstream runs. The marker is stripped
+from the stored output, so a reader sees the request rather than the mechanism.
+
+Answering it continues the run:
+
+```
+POST /api/v1/executions/{id}/resume
+{ "token": "...", "approved": true, "data": { "note": "looks fine" } }
+```
+
+The paused node's output becomes that payload, and the walk continues from
+there. Nodes that already ran are not run again. The workflow is rebuilt from
+the snapshot stored with the execution, not from the workflow as it stands now,
+because it may have been edited while the approval waited.
+
+`GET /api/v1/executions/pending` lists executions waiting for an answer. It
+deliberately omits the token: listing is a weaker permission than approving.
+
+Two limits are deliberate. A pause inside a Loop body cannot be resumed, because
+loop iteration state is not part of the stored execution, so a node must refuse
+to pause there rather than record something unanswerable. And a webhook cannot
+wait for an approval - the HTTP request is still open and its deadline is
+35 seconds - so a webhook-triggered workflow that pauses returns immediately
+with the execution id.
+
+## Answering a Webhook
+
+A webhook-triggered workflow returns the last node's output as JSON by default.
+To control the response, return a `_webhookResponse` marker from any node:
+
+```javascript
+return {
+    _webhookResponse: {
+        status: 201,
+        headers: { 'Content-Type': 'application/json' },
+        body: { id: created.id }
+    }
+};
+```
+
+Any node may set it, not only the last one to run, so adding a node to the end
+of a workflow cannot silently change what its API returns. If several set it,
+the last one wins and the runner logs that it happened.
+
 ## Multiple Inputs
 
 A node that joins two branches names its inputs:
@@ -354,6 +413,38 @@ smartbotic.storage.update('collection', 'document-id', { key: 'new-value' });
 smartbotic.storage.delete('collection', 'document-id');
 ```
 
+### Filesystem
+
+```javascript
+// Read a file
+const read = smartbotic.fs.readFile('/tmp/example.txt', 'base64');
+// { success, data } - data is base64-encoded when the encoding argument is 'base64'
+
+// Write a file
+smartbotic.fs.writeFile('/tmp/example.txt', base64Data, 'base64');
+
+// Check existence
+const there = smartbotic.fs.exists('/tmp/example.txt');
+
+// Stat a file
+const info = smartbotic.fs.stat('/tmp/example.txt');
+// { success, size, mtime, isDirectory } - mtime is milliseconds since the epoch
+
+// Create a directory
+smartbotic.fs.mkdir('/tmp/example-dir');
+
+// Delete a file
+smartbotic.fs.unlink('/tmp/example.txt');
+
+// List a directory, one level
+const listing = smartbotic.fs.readdir('/var/spool/incoming');
+// { success: true, entries: [{ name, path, size, modifiedAt, isDirectory }] }
+```
+
+The `fs` API applies no path restrictions. Every call takes an arbitrary
+absolute path and acts on it with the runner process's own permissions, the
+same trust level a `code` node or `process.exec` already has.
+
 ### Credentials
 
 ```javascript