n8n-nodes-stacksona 0.1.0
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/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/credentials/StacksonaGateApi.credentials.d.ts +7 -0
- package/dist/credentials/StacksonaGateApi.credentials.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/nodes/StacksonaGate/StacksonaGate.node.d.ts +5 -0
- package/dist/nodes/StacksonaGate/StacksonaGate.node.js +293 -0
- package/dist/nodes/StacksonaGate/stacksona.svg +30 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stacksona
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# n8n-nodes-stacksona
|
|
2
|
+
|
|
3
|
+
n8n community node for Stacksona Gate, the approval and audit layer for AI agents and automated workflows.
|
|
4
|
+
|
|
5
|
+
`n8n-nodes-stacksona` lets n8n workflows log activity, request human approval before gated actions, check decision status, validate signed approval tokens, and wait for reviewer approval before continuing automation.
|
|
6
|
+
|
|
7
|
+
Use it to add human-in-the-loop approval, audit trails, and runtime AI governance to n8n workflows without building a custom integration.
|
|
8
|
+
|
|
9
|
+
## What it does
|
|
10
|
+
|
|
11
|
+
This n8n community node connects n8n to Stacksona Gate.
|
|
12
|
+
|
|
13
|
+
With this package, your n8n workflows can:
|
|
14
|
+
|
|
15
|
+
* Log workflow and agent events for audit history
|
|
16
|
+
* Request approval before sensitive or risky actions
|
|
17
|
+
* Wait for a human approve or reject decision
|
|
18
|
+
* Check decision status by task or thread
|
|
19
|
+
* Validate signed one-time approval tokens
|
|
20
|
+
* Add governance to AI agents and automated workflows
|
|
21
|
+
* Keep approval records in Stacksona Gate
|
|
22
|
+
|
|
23
|
+
## Install in n8n
|
|
24
|
+
|
|
25
|
+
Install this community package in n8n:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
n8n-nodes-stacksona
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
In n8n, go to:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Settings → Community Nodes → Install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then enter:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
n8n-nodes-stacksona
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
After installation, search for **Stacksona Gate** in the n8n node picker.
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
* n8n with community nodes enabled
|
|
48
|
+
* A Stacksona Gate workspace
|
|
49
|
+
* A Stacksona Gate URL
|
|
50
|
+
* A Stacksona Agent API key starting with `sg_`
|
|
51
|
+
|
|
52
|
+
## Credentials
|
|
53
|
+
|
|
54
|
+
Create a **Stacksona Gate API** credential in n8n.
|
|
55
|
+
|
|
56
|
+
Required fields:
|
|
57
|
+
|
|
58
|
+
| Field | Required | Description |
|
|
59
|
+
| ------------- | -------: | ------------------------------------------- |
|
|
60
|
+
| Gate URL | Yes | Your Stacksona Gate tenant URL |
|
|
61
|
+
| Agent API Key | Yes | Stacksona Agent API key starting with `sg_` |
|
|
62
|
+
|
|
63
|
+
Example Gate URL:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
https://subdomain.stacksona.cloud
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This is only an example. Your Stacksona Gate tenant subdomain may be different.
|
|
70
|
+
|
|
71
|
+
## Available operations
|
|
72
|
+
|
|
73
|
+
### Log Event
|
|
74
|
+
|
|
75
|
+
Logs task, workflow, or agent activity to Stacksona Gate.
|
|
76
|
+
|
|
77
|
+
Use this for audit trails, reviewer context, observability, and workflow history.
|
|
78
|
+
|
|
79
|
+
### Request Decision
|
|
80
|
+
|
|
81
|
+
Requests an approval decision before a gated action runs.
|
|
82
|
+
|
|
83
|
+
Use this before sensitive actions such as sending emails, issuing refunds, updating records, calling external APIs, or triggering operational workflows.
|
|
84
|
+
|
|
85
|
+
### Get Decision
|
|
86
|
+
|
|
87
|
+
Fetches the current decision status.
|
|
88
|
+
|
|
89
|
+
Use this to check whether a request is still pending, approved, rejected, or otherwise resolved.
|
|
90
|
+
|
|
91
|
+
### Validate Approval Token
|
|
92
|
+
|
|
93
|
+
Validates a signed one-time approval token.
|
|
94
|
+
|
|
95
|
+
Use this when signed approval tokens are enabled and your workflow needs proof that a gated action was approved before execution.
|
|
96
|
+
|
|
97
|
+
### Request Decision and Wait
|
|
98
|
+
|
|
99
|
+
Requests a decision and waits for reviewer approval or rejection.
|
|
100
|
+
|
|
101
|
+
Use this when the n8n workflow should pause until Stacksona Gate returns a final decision.
|
|
102
|
+
|
|
103
|
+
## Typical workflow
|
|
104
|
+
|
|
105
|
+
A common Stacksona Gate workflow in n8n looks like this:
|
|
106
|
+
|
|
107
|
+
1. Use **Log Event** to record task or agent activity.
|
|
108
|
+
2. Use **Request Decision** before the gated action.
|
|
109
|
+
3. If the decision status is `allow`, continue the workflow.
|
|
110
|
+
4. If the decision status is `pending_review`, use **Get Decision** in a loop until the status becomes `approved` or `rejected`.
|
|
111
|
+
5. If signed tokens are enabled, use **Validate Approval Token** before executing the approved action.
|
|
112
|
+
6. Continue only when the action is allowed or approved.
|
|
113
|
+
|
|
114
|
+
## Waiting for approval
|
|
115
|
+
|
|
116
|
+
Use **Request Decision and Wait** when the n8n workflow should wait for a human reviewer.
|
|
117
|
+
|
|
118
|
+
This operation polls Stacksona Gate for the final decision. It starts with Gate's `recommended_poll_after_seconds` when returned, then uses the configured fallback interval.
|
|
119
|
+
|
|
120
|
+
Webhook delivery is configured per agent in Stacksona Gate Admin, not inside the n8n node.
|
|
121
|
+
|
|
122
|
+
To handle approval webhooks in n8n, use an n8n **Webhook** trigger, then call **Get Decision** or continue your downstream workflow logic.
|
|
123
|
+
|
|
124
|
+
## Common use cases
|
|
125
|
+
|
|
126
|
+
Use `n8n-nodes-stacksona` when your workflow needs approval before actions like:
|
|
127
|
+
|
|
128
|
+
* Sending emails
|
|
129
|
+
* Issuing refunds
|
|
130
|
+
* Updating CRM records
|
|
131
|
+
* Calling external APIs
|
|
132
|
+
* Posting to Slack or Teams
|
|
133
|
+
* Creating support tickets
|
|
134
|
+
* Deploying code
|
|
135
|
+
* Updating databases
|
|
136
|
+
* Changing customer account settings
|
|
137
|
+
* Triggering financial or operational workflows
|
|
138
|
+
|
|
139
|
+
## Why use Stacksona Gate with n8n?
|
|
140
|
+
|
|
141
|
+
n8n is powerful for automation, but some workflow steps should not run without human review.
|
|
142
|
+
|
|
143
|
+
Stacksona Gate adds a governance layer between automation intent and execution. Your workflow can pause, ask for approval, log the decision, and continue only when the action is allowed or approved.
|
|
144
|
+
|
|
145
|
+
This is useful for AI agents, internal automations, compliance workflows, support operations, and any process where automation needs oversight.
|
|
146
|
+
|
|
147
|
+
## n8n package metadata
|
|
148
|
+
|
|
149
|
+
This package declares the n8n entry in `package.json` so n8n can find the compiled node and credential files:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"n8n": {
|
|
154
|
+
"n8nNodesApiVersion": 1,
|
|
155
|
+
"credentials": [
|
|
156
|
+
"dist/credentials/StacksonaGateApi.credentials.js"
|
|
157
|
+
],
|
|
158
|
+
"nodes": [
|
|
159
|
+
"dist/nodes/StacksonaGate/StacksonaGate.node.js"
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Keywords
|
|
166
|
+
|
|
167
|
+
n8n community node, n8n AI approval, Stacksona Gate, AI agent approval, human-in-the-loop automation, workflow approval, agent audit logs, AI observability, runtime AI governance, approval workflow, gated automation, n8n governance, approval token validation.
|
|
168
|
+
|
|
169
|
+
## Links
|
|
170
|
+
|
|
171
|
+
* Website: https://stacksona.com
|
|
172
|
+
* Community package: `n8n-nodes-stacksona`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class StacksonaGateApi {
|
|
2
|
+
name = 'stacksonaGateApi';
|
|
3
|
+
displayName = 'Stacksona Gate API';
|
|
4
|
+
documentationUrl = 'https://stacksona.com';
|
|
5
|
+
properties = [
|
|
6
|
+
{
|
|
7
|
+
displayName: 'Gate URL',
|
|
8
|
+
name: 'baseUrl',
|
|
9
|
+
type: 'string',
|
|
10
|
+
default: '',
|
|
11
|
+
placeholder: 'https://gate-1.stacksona.cloud',
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Required base URL for your Stacksona Gate instance. Example only: https://gate-1.stacksona.cloud',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
displayName: 'Agent API Key',
|
|
17
|
+
name: 'apiKey',
|
|
18
|
+
type: 'string',
|
|
19
|
+
typeOptions: { password: true },
|
|
20
|
+
default: '',
|
|
21
|
+
required: true,
|
|
22
|
+
description: 'Agent API key generated in Stacksona Gate Admin. It starts with sg_.',
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StacksonaGate } from './nodes/StacksonaGate/StacksonaGate.node.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StacksonaGate } from './nodes/StacksonaGate/StacksonaGate.node.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class StacksonaGate implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { NodeOperationError } from 'n8n-workflow';
|
|
2
|
+
export class StacksonaGate {
|
|
3
|
+
description = {
|
|
4
|
+
displayName: 'Stacksona Gate',
|
|
5
|
+
name: 'stacksonaGate',
|
|
6
|
+
icon: 'file:stacksona.svg',
|
|
7
|
+
group: ['transform'],
|
|
8
|
+
version: 1,
|
|
9
|
+
subtitle: '={{$parameter["operation"]}}',
|
|
10
|
+
description: 'Log events, request decisions, and poll approvals in Stacksona Gate',
|
|
11
|
+
defaults: { name: 'Stacksona Gate' },
|
|
12
|
+
inputs: ['main'],
|
|
13
|
+
outputs: ['main'],
|
|
14
|
+
credentials: [{ name: 'stacksonaGateApi', required: true }],
|
|
15
|
+
properties: [
|
|
16
|
+
{
|
|
17
|
+
displayName: 'Operation',
|
|
18
|
+
name: 'operation',
|
|
19
|
+
type: 'options',
|
|
20
|
+
noDataExpression: true,
|
|
21
|
+
default: 'requestDecision',
|
|
22
|
+
options: [
|
|
23
|
+
{ name: 'Log Event', value: 'logEvent', description: 'Log a task event', action: 'Log a task event' },
|
|
24
|
+
{ name: 'Request Decision', value: 'requestDecision', description: 'Ask Gate for a decision', action: 'Request a decision' },
|
|
25
|
+
{ name: 'Request Decision and Wait', value: 'requestDecisionAndWait', description: 'Ask Gate for a decision and poll until approved or rejected when review is required', action: 'Request a decision and wait' },
|
|
26
|
+
{ name: 'Get Decision', value: 'getDecision', description: 'Get a decision by thread or task', action: 'Get a decision' },
|
|
27
|
+
{ name: 'Validate Approval Token', value: 'validateApprovalToken', description: 'Validate a signed approval token', action: 'Validate an approval token' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
displayName: 'Task ID',
|
|
32
|
+
name: 'taskId',
|
|
33
|
+
type: 'string',
|
|
34
|
+
default: '',
|
|
35
|
+
required: true,
|
|
36
|
+
displayOptions: { show: { operation: ['logEvent', 'requestDecision', 'requestDecisionAndWait'] } },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
displayName: 'Event Type',
|
|
40
|
+
name: 'eventType',
|
|
41
|
+
type: 'string',
|
|
42
|
+
default: 'task.started',
|
|
43
|
+
required: true,
|
|
44
|
+
displayOptions: { show: { operation: ['logEvent'] } },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
displayName: 'Event Summary',
|
|
48
|
+
name: 'eventSummary',
|
|
49
|
+
type: 'string',
|
|
50
|
+
default: '',
|
|
51
|
+
required: true,
|
|
52
|
+
displayOptions: { show: { operation: ['logEvent'] } },
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
displayName: 'Workflow Name',
|
|
56
|
+
name: 'workflowName',
|
|
57
|
+
type: 'string',
|
|
58
|
+
default: '',
|
|
59
|
+
required: true,
|
|
60
|
+
displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
displayName: 'Task Label',
|
|
64
|
+
name: 'taskLabel',
|
|
65
|
+
type: 'string',
|
|
66
|
+
default: '',
|
|
67
|
+
required: true,
|
|
68
|
+
displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
displayName: 'Tool Name',
|
|
72
|
+
name: 'toolName',
|
|
73
|
+
type: 'string',
|
|
74
|
+
default: '',
|
|
75
|
+
required: true,
|
|
76
|
+
displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
displayName: 'Subject',
|
|
80
|
+
name: 'subject',
|
|
81
|
+
type: 'string',
|
|
82
|
+
default: '',
|
|
83
|
+
required: true,
|
|
84
|
+
displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
displayName: 'Preview',
|
|
88
|
+
name: 'preview',
|
|
89
|
+
type: 'string',
|
|
90
|
+
default: '',
|
|
91
|
+
displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
displayName: 'Risk Level',
|
|
95
|
+
name: 'riskLevel',
|
|
96
|
+
type: 'options',
|
|
97
|
+
default: 'medium',
|
|
98
|
+
options: [
|
|
99
|
+
{ name: 'Low', value: 'low' },
|
|
100
|
+
{ name: 'Medium', value: 'medium' },
|
|
101
|
+
{ name: 'High', value: 'high' },
|
|
102
|
+
{ name: 'Critical', value: 'critical' },
|
|
103
|
+
],
|
|
104
|
+
displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
displayName: 'Summary Items',
|
|
108
|
+
name: 'summaryItems',
|
|
109
|
+
type: 'fixedCollection',
|
|
110
|
+
default: {},
|
|
111
|
+
typeOptions: { multipleValues: true },
|
|
112
|
+
displayOptions: { show: { operation: ['requestDecision', 'requestDecisionAndWait'] } },
|
|
113
|
+
options: [{ name: 'item', displayName: 'Item', values: [{ displayName: 'Text', name: 'text', type: 'string', default: '' }] }],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
displayName: 'Thread ID',
|
|
117
|
+
name: 'threadId',
|
|
118
|
+
type: 'string',
|
|
119
|
+
default: '',
|
|
120
|
+
displayOptions: { show: { operation: ['getDecision'] } },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
displayName: 'Task ID',
|
|
124
|
+
name: 'decisionTaskId',
|
|
125
|
+
type: 'string',
|
|
126
|
+
default: '',
|
|
127
|
+
displayOptions: { show: { operation: ['getDecision', 'validateApprovalToken'] } },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
displayName: 'Approval Token / Signature',
|
|
131
|
+
name: 'signature',
|
|
132
|
+
type: 'string',
|
|
133
|
+
typeOptions: { password: true },
|
|
134
|
+
default: '',
|
|
135
|
+
required: true,
|
|
136
|
+
displayOptions: { show: { operation: ['validateApprovalToken'] } },
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
displayName: 'Fallback Poll Interval Seconds',
|
|
140
|
+
name: 'pollIntervalSeconds',
|
|
141
|
+
type: 'number',
|
|
142
|
+
default: 15,
|
|
143
|
+
description: 'Used when Gate does not return recommended_poll_after_seconds',
|
|
144
|
+
displayOptions: { show: { operation: ['requestDecisionAndWait'] } },
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
displayName: 'Max Wait Seconds',
|
|
148
|
+
name: 'maxWaitSeconds',
|
|
149
|
+
type: 'number',
|
|
150
|
+
default: 600,
|
|
151
|
+
description: 'Maximum time to wait for an approved or rejected decision',
|
|
152
|
+
displayOptions: { show: { operation: ['requestDecisionAndWait'] } },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
displayName: 'Payload JSON',
|
|
156
|
+
name: 'payloadJson',
|
|
157
|
+
type: 'json',
|
|
158
|
+
default: '{}',
|
|
159
|
+
description: 'Arbitrary JSON payload sent to Stacksona Gate',
|
|
160
|
+
displayOptions: { show: { operation: ['logEvent', 'requestDecision', 'requestDecisionAndWait'] } },
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
async execute() {
|
|
165
|
+
const items = this.getInputData();
|
|
166
|
+
const returnData = [];
|
|
167
|
+
const credentials = await this.getCredentials('stacksonaGateApi');
|
|
168
|
+
const baseUrl = String(credentials.baseUrl).replace(/\/+$/, '');
|
|
169
|
+
const apiKey = String(credentials.apiKey);
|
|
170
|
+
for (let i = 0; i < items.length; i++) {
|
|
171
|
+
try {
|
|
172
|
+
const operation = this.getNodeParameter('operation', i);
|
|
173
|
+
let response;
|
|
174
|
+
if (operation === 'logEvent') {
|
|
175
|
+
const taskId = this.getNodeParameter('taskId', i);
|
|
176
|
+
const payload = parseJsonParameter(this.getNodeParameter('payloadJson', i, '{}'), this, i);
|
|
177
|
+
await stacksonaRequest.call(this, baseUrl, apiKey, 'POST', `/api/agent/tasks/${encodeURIComponent(taskId)}/events`, {
|
|
178
|
+
event_type: this.getNodeParameter('eventType', i),
|
|
179
|
+
event_summary: this.getNodeParameter('eventSummary', i),
|
|
180
|
+
payload,
|
|
181
|
+
});
|
|
182
|
+
response = { ok: true };
|
|
183
|
+
}
|
|
184
|
+
if (operation === 'requestDecision' || operation === 'requestDecisionAndWait') {
|
|
185
|
+
const taskId = this.getNodeParameter('taskId', i);
|
|
186
|
+
const payload = parseJsonParameter(this.getNodeParameter('payloadJson', i, '{}'), this, i);
|
|
187
|
+
const summaryCollection = this.getNodeParameter('summaryItems', i, {});
|
|
188
|
+
response = await stacksonaRequest.call(this, baseUrl, apiKey, 'POST', `/api/agent/tasks/${encodeURIComponent(taskId)}/requests`, {
|
|
189
|
+
workflow_name: this.getNodeParameter('workflowName', i),
|
|
190
|
+
task_label: this.getNodeParameter('taskLabel', i),
|
|
191
|
+
tool_name: this.getNodeParameter('toolName', i),
|
|
192
|
+
subject: this.getNodeParameter('subject', i),
|
|
193
|
+
preview: this.getNodeParameter('preview', i, ''),
|
|
194
|
+
risk_level: this.getNodeParameter('riskLevel', i, 'medium'),
|
|
195
|
+
summary: (summaryCollection.item ?? []).map((entry) => entry.text).filter(Boolean),
|
|
196
|
+
payload,
|
|
197
|
+
});
|
|
198
|
+
if (operation === 'requestDecisionAndWait') {
|
|
199
|
+
const initial = response;
|
|
200
|
+
if (initial.status === 'pending_review') {
|
|
201
|
+
response = await pollDecision.call(this, baseUrl, apiKey, {
|
|
202
|
+
threadId: typeof initial.thread_id === 'string' ? initial.thread_id : undefined,
|
|
203
|
+
taskId: typeof initial.task_id === 'string' ? initial.task_id : taskId,
|
|
204
|
+
}, {
|
|
205
|
+
initialRecommendedSeconds: typeof initial.recommended_poll_after_seconds === 'number' ? initial.recommended_poll_after_seconds : undefined,
|
|
206
|
+
fallbackIntervalSeconds: this.getNodeParameter('pollIntervalSeconds', i, 15),
|
|
207
|
+
maxWaitSeconds: this.getNodeParameter('maxWaitSeconds', i, 600),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (operation === 'getDecision') {
|
|
213
|
+
const threadId = this.getNodeParameter('threadId', i, '');
|
|
214
|
+
const taskId = this.getNodeParameter('decisionTaskId', i, '');
|
|
215
|
+
const params = new URLSearchParams();
|
|
216
|
+
if (threadId)
|
|
217
|
+
params.set('thread_id', threadId);
|
|
218
|
+
if (taskId)
|
|
219
|
+
params.set('task_id', taskId);
|
|
220
|
+
if (!params.toString())
|
|
221
|
+
throw new NodeOperationError(this.getNode(), 'Thread ID or Task ID is required', { itemIndex: i });
|
|
222
|
+
response = await stacksonaRequest.call(this, baseUrl, apiKey, 'GET', `/api/agent/decisions?${params.toString()}`);
|
|
223
|
+
}
|
|
224
|
+
if (operation === 'validateApprovalToken') {
|
|
225
|
+
response = await stacksonaRequest.call(this, baseUrl, apiKey, 'POST', '/api/agent/approvals/validate', {
|
|
226
|
+
task_id: this.getNodeParameter('decisionTaskId', i),
|
|
227
|
+
signature: this.getNodeParameter('signature', i),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
returnData.push({ json: toN8nJson(response), pairedItem: { item: i } });
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (this.continueOnFail()) {
|
|
234
|
+
returnData.push({ json: { error: error instanceof Error ? error.message : String(error) }, pairedItem: { item: i } });
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return [returnData];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function parseJsonParameter(value, ctx, itemIndex) {
|
|
244
|
+
if (typeof value === 'object' && value !== null)
|
|
245
|
+
return value;
|
|
246
|
+
try {
|
|
247
|
+
return JSON.parse(String(value || '{}'));
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
throw new NodeOperationError(ctx.getNode(), 'Payload JSON must be valid JSON', { itemIndex });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function toN8nJson(value) {
|
|
254
|
+
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
255
|
+
return value;
|
|
256
|
+
return { value: value };
|
|
257
|
+
}
|
|
258
|
+
async function sleep(ms) {
|
|
259
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
260
|
+
}
|
|
261
|
+
async function pollDecision(baseUrl, apiKey, query, options) {
|
|
262
|
+
const started = Date.now();
|
|
263
|
+
let intervalSeconds = options.initialRecommendedSeconds ?? options.fallbackIntervalSeconds ?? 15;
|
|
264
|
+
while (true) {
|
|
265
|
+
const params = new URLSearchParams();
|
|
266
|
+
if (query.threadId)
|
|
267
|
+
params.set('thread_id', query.threadId);
|
|
268
|
+
if (query.taskId)
|
|
269
|
+
params.set('task_id', query.taskId);
|
|
270
|
+
const decision = await stacksonaRequest.call(this, baseUrl, apiKey, 'GET', `/api/agent/decisions?${params.toString()}`);
|
|
271
|
+
if (decision.status === 'approved' || decision.status === 'rejected')
|
|
272
|
+
return decision;
|
|
273
|
+
if (Date.now() - started > options.maxWaitSeconds * 1000) {
|
|
274
|
+
throw new NodeOperationError(this.getNode(), 'Timed out waiting for Stacksona Gate decision');
|
|
275
|
+
}
|
|
276
|
+
if (typeof decision.recommended_poll_after_seconds === 'number') {
|
|
277
|
+
intervalSeconds = decision.recommended_poll_after_seconds;
|
|
278
|
+
}
|
|
279
|
+
await sleep(intervalSeconds * 1000);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function stacksonaRequest(baseUrl, apiKey, method, path, body) {
|
|
283
|
+
return this.helpers.httpRequest({
|
|
284
|
+
method,
|
|
285
|
+
url: `${baseUrl}${path}`,
|
|
286
|
+
headers: {
|
|
287
|
+
Authorization: `Bearer ${apiKey}`,
|
|
288
|
+
'Content-Type': 'application/json',
|
|
289
|
+
},
|
|
290
|
+
body,
|
|
291
|
+
json: true,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="216" height="216">
|
|
3
|
+
<path d="M0,0 L90,0 L103,8 L115,14 L115,16 L121,18 L137,27 L146,33 L146,48 L145,55 L141,63 L142,69 L139,87 L137,92 L137,103 L135,114 L125,129 L119,140 L113,149 L106,162 L104,162 L102,167 L100,171 L-7,171 L-14,160 L-20,151 L-26,141 L-29,138 L-39,122 L-44,115 L-46,104 L-46,91 L-48,88 L-51,70 L-50,63 L-54,56 L-55,50 L-55,34 L-45,27 L-24,15 L-16,10 L-1,1 Z " fill="#5A56BD" transform="translate(63,23)"/>
|
|
4
|
+
<path d="M0,0 L90,0 L103,8 L115,14 L115,16 L121,18 L137,27 L146,33 L146,48 L145,55 L141,63 L142,69 L139,87 L137,92 L137,103 L135,114 L125,129 L119,140 L113,149 L106,162 L104,162 L102,167 L100,171 L-7,171 L-14,160 L-20,151 L-26,141 L-29,138 L-39,122 L-44,115 L-46,104 L-46,91 L-48,88 L-51,70 L-50,63 L-54,56 L-55,50 L-55,34 L-45,27 L-24,15 L-16,10 L-1,1 Z M4,28 L-8,37 L-24,48 L-36,56 L-37,60 L-28,70 L-23,75 L-24,78 L-30,82 L-29,87 L-16,104 L-21,99 L-31,85 L-34,85 L-33,90 L-28,98 L-23,103 L-21,108 L-13,119 L-11,129 L-10,140 L-8,142 L100,142 L103,139 L105,119 L108,113 L113,108 L116,102 L123,94 L126,90 L126,85 L122,86 L112,100 L110,102 L117,92 L122,85 L122,82 L116,78 L118,74 L120,69 L128,59 L128,56 L112,46 L102,39 L91,32 L86,28 Z " fill="#8492CD" transform="translate(63,23)"/>
|
|
5
|
+
<path d="M0,0 L90,0 L103,8 L115,14 L115,16 L121,18 L137,27 L146,33 L146,48 L145,55 L141,62 L136,63 L128,57 L112,47 L102,40 L91,33 L86,29 L4,29 L-8,38 L-24,49 L-36,57 L-44,63 L-46,63 L-42,69 L-32,82 L-33,84 L-40,76 L-47,67 L-48,62 L-51,62 L-54,56 L-55,50 L-55,34 L-45,27 L-24,15 L-16,10 L-1,1 Z " fill="#A292D4" transform="translate(63,23)"/>
|
|
6
|
+
<path d="M0,0 L82,0 L93,8 L104,15 L119,25 L124,29 L119,32 L110,42 L108,45 L103,44 L87,32 L80,27 L2,27 L-14,40 L-18,43 L-20,43 L-16,50 L-17,52 L-21,48 L-21,46 L-24,47 L-23,51 L-19,55 L-17,67 L-16,80 L-15,82 L100,83 L99,89 L-13,89 L-16,84 L-19,80 L-22,73 L-34,58 L-33,53 L-27,49 L-29,44 L-38,36 L-41,32 L-40,28 L-28,20 L-11,8 Z " fill="#7463C7" transform="translate(67,51)"/>
|
|
7
|
+
<path d="M0,0 L3,0 L7,5 L6,9 L127,9 L126,5 L129,1 L133,2 L132,7 L130,10 L128,10 L127,28 L125,35 L123,37 L9,37 L7,34 L5,12 L2,7 L-1,4 Z " fill="#4E44B6" transform="translate(43,97)"/>
|
|
8
|
+
<path d="M0,0 L4,2 L8,5 L13,6 L14,12 L11,30 L9,35 L8,39 L1,49 L-9,64 L-19,80 L-23,88 L-27,90 L-28,85 L-26,82 L-24,62 L-20,55 L-15,50 L-13,45 L-6,37 L-3,33 L-2,28 L-6,30 L-15,43 L-20,48 L-18,43 L-7,28 L-6,25 L-11,23 L-13,19 L-11,17 L-9,12 Z " fill="#7C86C0" transform="translate(191,80)"/>
|
|
9
|
+
<path d="M0,0 L2,0 L9,9 L16,15 L17,19 L10,24 L11,29 L24,46 L23,48 L10,32 L8,27 L5,27 L8,34 L11,39 L16,44 L19,50 L27,61 L29,71 L29,81 L24,76 L21,69 L9,52 L6,49 L2,45 L-3,37 L-5,41 L-5,46 L-7,46 L-7,33 L-9,30 L-12,12 L-11,4 L-9,4 L-7,9 L2,20 L5,23 L-5,10 L-7,5 Z " fill="#8080C3" transform="translate(24,81)"/>
|
|
10
|
+
<path d="M0,0 L2,1 L1,15 L-2,22 L-11,36 L-16,45 L-22,54 L-29,67 L-34,68 L-34,53 L-142,53 L-142,50 L-37,50 L-36,47 L-34,47 L-33,51 L-30,49 L-27,42 L-15,23 L-5,8 Z " fill="#9FA1BB" transform="translate(198,118)"/>
|
|
11
|
+
<path d="M0,0 L4,5 L13,18 L15,23 L19,27 L27,39 L31,44 L34,51 L38,55 L39,72 L33,68 L26,58 L19,46 L16,43 L6,27 L1,20 L0,16 Z " fill="#A2A6C6" transform="translate(18,118)"/>
|
|
12
|
+
<path d="M0,0 L4,2 L8,5 L13,6 L14,12 L9,13 L8,9 L-3,24 L-3,28 L-8,33 L-18,47 L-20,48 L-18,43 L-7,28 L-6,25 L-11,23 L-13,19 L-11,17 L-9,12 Z " fill="#7B77BA" transform="translate(191,80)"/>
|
|
13
|
+
<path d="M0,0 L2,0 L3,4 L3,7 L-106,6 L-106,3 L-1,3 Z " fill="#A9B4DF" transform="translate(162,165)"/>
|
|
14
|
+
<path d="M0,0 L112,0 L110,1 L110,3 L1,3 Z " fill="#5F64C3" transform="translate(54,134)"/>
|
|
15
|
+
<path d="M0,0 L2,4 L5,3 L5,11 L2,24 L1,26 L-4,26 L-12,19 L-11,15 Z " fill="#898AC9" transform="translate(199,88)"/>
|
|
16
|
+
<path d="M0,0 L2,0 L2,16 L-2,23 L-7,24 L-14,18 L-8,10 L-2,2 Z " fill="#A9A5C4" transform="translate(206,62)"/>
|
|
17
|
+
<path d="M0,0 L5,5 L13,16 L13,19 L8,23 L4,21 L1,19 L-1,8 L-1,1 Z " fill="#A5A4C6" transform="translate(14,92)"/>
|
|
18
|
+
<path d="M0,0 L3,0 L5,5 L4,12 L3,14 L-2,14 L-6,11 L-6,6 L-1,1 Z " fill="#A5A5C0" transform="translate(197,100)"/>
|
|
19
|
+
<path d="M0,0 L2,1 L2,16 L0,19 L0,10 L1,7 L-6,12 L-9,13 L-7,9 Z " fill="#887EAF" transform="translate(207,55)"/>
|
|
20
|
+
<path d="M0,0 L2,0 L6,6 L18,20 L18,22 L15,21 L6,10 L-1,2 Z " fill="#B2A3DE" transform="translate(12,56)"/>
|
|
21
|
+
<path d="M0,0 L5,5 L14,15 L16,20 L9,14 L0,3 L-2,1 Z " fill="#897ACE" transform="translate(30,77)"/>
|
|
22
|
+
<path d="M0,0 L2,0 L4,5 L7,8 L5,12 L1,8 L-1,8 Z " fill="#7272A7" transform="translate(13,85)"/>
|
|
23
|
+
<path d="M0,0 L4,2 L6,6 L3,8 L3,15 L1,15 L1,2 Z " fill="#747B97" transform="translate(16,112)"/>
|
|
24
|
+
<path d="M0,0 L2,0 L1,4 L-24,4 L-24,3 L-1,3 Z " fill="#9FABDD" transform="translate(162,165)"/>
|
|
25
|
+
<path d="M0,0 L3,1 L4,7 L-1,8 L-1,1 Z " fill="#70719F" transform="translate(201,85)"/>
|
|
26
|
+
<path d="M0,0 L2,0 L1,10 L-1,10 L-3,6 L-4,4 L-1,3 Z " fill="#89899B" transform="translate(202,95)"/>
|
|
27
|
+
<path d="M0,0 L2,0 L2,4 L4,4 L3,11 L1,9 Z " fill="#595665" transform="translate(8,70)"/>
|
|
28
|
+
<path d="M0,0 L3,1 L7,5 L8,9 L4,8 Z " fill="#6F707E" transform="translate(31,155)"/>
|
|
29
|
+
<path d="M0,0 L2,0 L2,4 L0,4 L-1,8 L-4,7 L-1,1 Z " fill="#A9A9C9" transform="translate(202,91)"/>
|
|
30
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-stacksona",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "n8n community nodes for Stacksona Gate approvals and audit events.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc && gulp build:icons",
|
|
15
|
+
"clean": "rimraf dist",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"n8n-community-node-package",
|
|
20
|
+
"stacksona",
|
|
21
|
+
"approval",
|
|
22
|
+
"ai-governance",
|
|
23
|
+
"agent"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"n8n": {
|
|
30
|
+
"n8nNodesApiVersion": 1,
|
|
31
|
+
"credentials": [
|
|
32
|
+
"dist/credentials/StacksonaGateApi.credentials.js"
|
|
33
|
+
],
|
|
34
|
+
"nodes": [
|
|
35
|
+
"dist/nodes/StacksonaGate/StacksonaGate.node.js"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.11.30",
|
|
40
|
+
"gulp": "^4.0.2",
|
|
41
|
+
"n8n-workflow": "^1.82.0",
|
|
42
|
+
"rimraf": "^5.0.5",
|
|
43
|
+
"typescript": "^5.4.5"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://Stacksonas.com",
|
|
46
|
+
"author": "Stacksona"
|
|
47
|
+
}
|