@loomcycle/n8n-nodes-loomcycle 1.0.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.
Files changed (99) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +190 -0
  3. package/dist/credentials/LoomCycleApi.credentials.d.ts +21 -0
  4. package/dist/credentials/LoomCycleApi.credentials.js +81 -0
  5. package/dist/credentials/LoomCycleApi.credentials.js.map +1 -0
  6. package/dist/nodes/LoomCycle/LoomCycle.node.d.ts +22 -0
  7. package/dist/nodes/LoomCycle/LoomCycle.node.js +403 -0
  8. package/dist/nodes/LoomCycle/LoomCycle.node.js.map +1 -0
  9. package/dist/nodes/LoomCycle/LoomCycle.node.json +13 -0
  10. package/dist/nodes/LoomCycle/LoomCycle.svg +13 -0
  11. package/dist/nodes/LoomCycle/descriptions/agentdef.d.ts +21 -0
  12. package/dist/nodes/LoomCycle/descriptions/agentdef.js +155 -0
  13. package/dist/nodes/LoomCycle/descriptions/agentdef.js.map +1 -0
  14. package/dist/nodes/LoomCycle/descriptions/channels.d.ts +19 -0
  15. package/dist/nodes/LoomCycle/descriptions/channels.js +164 -0
  16. package/dist/nodes/LoomCycle/descriptions/channels.js.map +1 -0
  17. package/dist/nodes/LoomCycle/descriptions/index.d.ts +6 -0
  18. package/dist/nodes/LoomCycle/descriptions/index.js +16 -0
  19. package/dist/nodes/LoomCycle/descriptions/index.js.map +1 -0
  20. package/dist/nodes/LoomCycle/descriptions/mcpserverdef.d.ts +29 -0
  21. package/dist/nodes/LoomCycle/descriptions/mcpserverdef.js +241 -0
  22. package/dist/nodes/LoomCycle/descriptions/mcpserverdef.js.map +1 -0
  23. package/dist/nodes/LoomCycle/descriptions/memory.d.ts +20 -0
  24. package/dist/nodes/LoomCycle/descriptions/memory.js +119 -0
  25. package/dist/nodes/LoomCycle/descriptions/memory.js.map +1 -0
  26. package/dist/nodes/LoomCycle/descriptions/runs.d.ts +17 -0
  27. package/dist/nodes/LoomCycle/descriptions/runs.js +226 -0
  28. package/dist/nodes/LoomCycle/descriptions/runs.js.map +1 -0
  29. package/dist/nodes/LoomCycle/descriptions/skilldef.d.ts +10 -0
  30. package/dist/nodes/LoomCycle/descriptions/skilldef.js +144 -0
  31. package/dist/nodes/LoomCycle/descriptions/skilldef.js.map +1 -0
  32. package/dist/nodes/LoomCycle/helpers/capability.d.ts +42 -0
  33. package/dist/nodes/LoomCycle/helpers/capability.js +54 -0
  34. package/dist/nodes/LoomCycle/helpers/capability.js.map +1 -0
  35. package/dist/nodes/LoomCycle/helpers/client.d.ts +15 -0
  36. package/dist/nodes/LoomCycle/helpers/client.js +30 -0
  37. package/dist/nodes/LoomCycle/helpers/client.js.map +1 -0
  38. package/dist/nodes/LoomCycle/helpers/envVarHints.d.ts +30 -0
  39. package/dist/nodes/LoomCycle/helpers/envVarHints.js +67 -0
  40. package/dist/nodes/LoomCycle/helpers/envVarHints.js.map +1 -0
  41. package/dist/nodes/LoomCycle/helpers/errors.d.ts +23 -0
  42. package/dist/nodes/LoomCycle/helpers/errors.js +189 -0
  43. package/dist/nodes/LoomCycle/helpers/errors.js.map +1 -0
  44. package/dist/nodes/LoomCycle/helpers/loadOptions.d.ts +24 -0
  45. package/dist/nodes/LoomCycle/helpers/loadOptions.js +89 -0
  46. package/dist/nodes/LoomCycle/helpers/loadOptions.js.map +1 -0
  47. package/dist/nodes/LoomCycle/helpers/segments.d.ts +13 -0
  48. package/dist/nodes/LoomCycle/helpers/segments.js +31 -0
  49. package/dist/nodes/LoomCycle/helpers/segments.js.map +1 -0
  50. package/dist/nodes/LoomCycle/helpers/staticData.d.ts +5 -0
  51. package/dist/nodes/LoomCycle/helpers/staticData.js +31 -0
  52. package/dist/nodes/LoomCycle/helpers/staticData.js.map +1 -0
  53. package/dist/nodes/LoomCycle/helpers/streaming.d.ts +31 -0
  54. package/dist/nodes/LoomCycle/helpers/streaming.js +48 -0
  55. package/dist/nodes/LoomCycle/helpers/streaming.js.map +1 -0
  56. package/dist/nodes/LoomCycleChannelMessage/LoomCycleChannelMessage.node.d.ts +24 -0
  57. package/dist/nodes/LoomCycleChannelMessage/LoomCycleChannelMessage.node.js +162 -0
  58. package/dist/nodes/LoomCycleChannelMessage/LoomCycleChannelMessage.node.js.map +1 -0
  59. package/dist/nodes/LoomCycleChannelMessage/LoomCycleChannelMessage.node.json +13 -0
  60. package/dist/nodes/LoomCycleChannelMessage/LoomCycleChannelMessage.svg +9 -0
  61. package/dist/nodes/LoomCycleChannelMessage/helpers/subscribe.d.ts +20 -0
  62. package/dist/nodes/LoomCycleChannelMessage/helpers/subscribe.js +106 -0
  63. package/dist/nodes/LoomCycleChannelMessage/helpers/subscribe.js.map +1 -0
  64. package/dist/nodes/LoomCycleChannelTool/LoomCycleChannelTool.node.d.ts +5 -0
  65. package/dist/nodes/LoomCycleChannelTool/LoomCycleChannelTool.node.js +114 -0
  66. package/dist/nodes/LoomCycleChannelTool/LoomCycleChannelTool.node.js.map +1 -0
  67. package/dist/nodes/LoomCycleChannelTool/LoomCycleChannelTool.node.json +13 -0
  68. package/dist/nodes/LoomCycleChannelTool/LoomCycleChannelTool.svg +13 -0
  69. package/dist/nodes/LoomCycleMcpServerTool/LoomCycleMcpServerTool.node.d.ts +5 -0
  70. package/dist/nodes/LoomCycleMcpServerTool/LoomCycleMcpServerTool.node.js +259 -0
  71. package/dist/nodes/LoomCycleMcpServerTool/LoomCycleMcpServerTool.node.js.map +1 -0
  72. package/dist/nodes/LoomCycleMcpServerTool/LoomCycleMcpServerTool.node.json +13 -0
  73. package/dist/nodes/LoomCycleMcpServerTool/LoomCycleMcpServerTool.svg +14 -0
  74. package/dist/nodes/LoomCycleMemoryTool/LoomCycleMemoryTool.node.d.ts +5 -0
  75. package/dist/nodes/LoomCycleMemoryTool/LoomCycleMemoryTool.node.js +108 -0
  76. package/dist/nodes/LoomCycleMemoryTool/LoomCycleMemoryTool.node.js.map +1 -0
  77. package/dist/nodes/LoomCycleMemoryTool/LoomCycleMemoryTool.node.json +13 -0
  78. package/dist/nodes/LoomCycleMemoryTool/LoomCycleMemoryTool.svg +11 -0
  79. package/dist/nodes/LoomCycleRunCompleted/LoomCycleRunCompleted.node.d.ts +18 -0
  80. package/dist/nodes/LoomCycleRunCompleted/LoomCycleRunCompleted.node.js +188 -0
  81. package/dist/nodes/LoomCycleRunCompleted/LoomCycleRunCompleted.node.js.map +1 -0
  82. package/dist/nodes/LoomCycleRunCompleted/LoomCycleRunCompleted.node.json +13 -0
  83. package/dist/nodes/LoomCycleRunCompleted/LoomCycleRunCompleted.svg +10 -0
  84. package/dist/nodes/LoomCycleRunCompleted/helpers/poll.d.ts +12 -0
  85. package/dist/nodes/LoomCycleRunCompleted/helpers/poll.js +69 -0
  86. package/dist/nodes/LoomCycleRunCompleted/helpers/poll.js.map +1 -0
  87. package/dist/nodes/LoomCycleRunCompleted/helpers/sse.d.ts +29 -0
  88. package/dist/nodes/LoomCycleRunCompleted/helpers/sse.js +61 -0
  89. package/dist/nodes/LoomCycleRunCompleted/helpers/sse.js.map +1 -0
  90. package/dist/nodes/LoomCycleSubAgentTool/LoomCycleSubAgentTool.node.d.ts +5 -0
  91. package/dist/nodes/LoomCycleSubAgentTool/LoomCycleSubAgentTool.node.js +131 -0
  92. package/dist/nodes/LoomCycleSubAgentTool/LoomCycleSubAgentTool.node.js.map +1 -0
  93. package/dist/nodes/LoomCycleSubAgentTool/LoomCycleSubAgentTool.node.json +13 -0
  94. package/dist/nodes/LoomCycleSubAgentTool/LoomCycleSubAgentTool.svg +12 -0
  95. package/dist/nodes/_shared/clusterTool.d.ts +19 -0
  96. package/dist/nodes/_shared/clusterTool.js +36 -0
  97. package/dist/nodes/_shared/clusterTool.js.map +1 -0
  98. package/dist/tsconfig.tsbuildinfo +1 -0
  99. package/package.json +86 -0
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoomCycleMcpServerTool = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const zod_1 = require("zod");
6
+ const client_1 = require("@loomcycle/client");
7
+ const client_2 = require("../LoomCycle/helpers/client");
8
+ const segments_1 = require("../LoomCycle/helpers/segments");
9
+ const streaming_1 = require("../LoomCycle/helpers/streaming");
10
+ const envVarHints_1 = require("../LoomCycle/helpers/envVarHints");
11
+ const clusterTool_1 = require("../_shared/clusterTool");
12
+ /**
13
+ * `LoomCycle MCP Server Tool` — **the strategic differentiator** for the
14
+ * n8n integration.
15
+ *
16
+ * Wires a single MCP server registration into loomcycle's substrate
17
+ * idempotently (get-then-create), then exposes a tool the parent AI
18
+ * Agent can call to delegate work to a loomcycle agent that has access
19
+ * to that MCP server's tool surface.
20
+ *
21
+ * Flow on `supplyData`:
22
+ * 1. Read tool config (name, transport, URL, headers, the loomcycle
23
+ * agent to spawn).
24
+ * 2. Refuse stdio transport with `NodeOperationError` (stdio MCP
25
+ * servers must live in loomcycle.yaml — the substrate enforces
26
+ * this; we surface it pre-wire for a cleaner error).
27
+ * 3. Idempotent ensure: `mcpServerDef({op:"get", name})` → on
28
+ * `NotFoundError`, `mcpServerDef({op:"create", ...})`. This means
29
+ * the second-and-Nth canvas runs are no-ops; the first run does
30
+ * the actual registration.
31
+ * 4. Return a tool that, when invoked, spawns the configured
32
+ * loomcycle agent with `allowed_tools: ["mcp__<name>__*"]` so
33
+ * the agent has access to the just-registered MCP server's tools.
34
+ *
35
+ * Lifecycle: **`cleanupOnEnd: false` default** — registrations persist
36
+ * across workflow executions so multiple agentic teams share stable
37
+ * MCP fleets without churn. Opt in to retire-on-workflow-end via the
38
+ * sub-node parameter when you want ephemeral / scoped registrations.
39
+ */
40
+ const McpServerInputSchema = zod_1.z.object({
41
+ prompt: zod_1.z.string().describe('Prompt sent to the loomcycle agent (which has access to the MCP server\'s tools)'),
42
+ });
43
+ class LoomCycleMcpServerTool {
44
+ constructor() {
45
+ this.description = {
46
+ displayName: 'LoomCycle MCP Server Tool',
47
+ name: 'loomCycleMcpServerTool',
48
+ icon: 'file:LoomCycleMcpServerTool.svg',
49
+ group: ['transform'],
50
+ version: 1,
51
+ description: 'Registers an MCP server in the loomcycle substrate (idempotent), then exposes a tool that delegates to a loomcycle agent using that MCP server',
52
+ defaults: { name: 'LoomCycle MCP Server Tool' },
53
+ codex: { categories: ['AI'], subcategories: { AI: ['Tools'] } },
54
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
55
+ inputs: [],
56
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
57
+ outputs: [n8n_workflow_1.NodeConnectionTypes.AiTool],
58
+ outputNames: ['Tool'],
59
+ credentials: [{ name: 'loomCycleApi', required: true }],
60
+ properties: [
61
+ {
62
+ displayName: 'Tool Name',
63
+ name: 'toolName',
64
+ type: 'string',
65
+ default: 'loomcycle_mcp',
66
+ required: true,
67
+ description: 'Name of the tool surfaced to the parent AI Agent',
68
+ },
69
+ {
70
+ displayName: 'Tool Description',
71
+ name: 'toolDescription',
72
+ type: 'string',
73
+ typeOptions: { rows: 3 },
74
+ default: 'Delegate a task to a loomcycle agent that has access to the configured MCP server. Pass the task as the prompt.',
75
+ description: 'Description the AI Agent sees when deciding whether to call the tool',
76
+ },
77
+ {
78
+ displayName: 'MCP Server Name',
79
+ name: 'mcpName',
80
+ type: 'string',
81
+ default: '',
82
+ required: true,
83
+ description: 'Registration name in loomcycle — referenced by agents as `mcp__&lt;name&gt;__&lt;tool&gt;`. Must be unique across the substrate; collisions with static yaml entries are refused.',
84
+ },
85
+ {
86
+ displayName: 'Transport',
87
+ name: 'transport',
88
+ type: 'options',
89
+ default: 'streamable-http',
90
+ required: true,
91
+ options: [
92
+ { name: 'HTTP', value: 'http', description: 'Classic JSON-RPC over HTTP POST' },
93
+ { name: 'Streamable HTTP', value: 'streamable-http', description: 'MCP Streamable HTTP transport (recommended)' },
94
+ ],
95
+ description: 'Transport for the MCP server. Stdio is intentionally not supported here — register stdio MCPs in loomcycle.yaml.',
96
+ },
97
+ {
98
+ displayName: 'URL',
99
+ name: 'mcpUrl',
100
+ type: 'string',
101
+ default: '',
102
+ required: true,
103
+ placeholder: 'https://mcp.example.com/v1',
104
+ description: 'MCP server endpoint URL. Hostname must be in loomcycle\'s HTTPHostAllowlist.',
105
+ },
106
+ {
107
+ displayName: 'Headers',
108
+ name: 'headers',
109
+ type: 'fixedCollection',
110
+ placeholder: 'Add Header',
111
+ default: {},
112
+ typeOptions: { multipleValues: true },
113
+ options: [
114
+ {
115
+ name: 'header',
116
+ displayName: 'Header',
117
+ values: [
118
+ {
119
+ displayName: 'Name',
120
+ name: 'name',
121
+ type: 'string',
122
+ default: '',
123
+ required: true,
124
+ },
125
+ {
126
+ displayName: 'Value',
127
+ name: 'value',
128
+ type: 'string',
129
+ default: '',
130
+ required: true,
131
+ description: 'Supports `${LOOMCYCLE_FOO}` env-var substitution + `${run.user_bearer:-FALLBACK}` per-run substitution',
132
+ },
133
+ ],
134
+ },
135
+ ],
136
+ description: 'Headers attached to every MCP call. Use template strings (`${LOOMCYCLE_FOO_TOKEN}`) — plaintext credentials never travel through this wire path.',
137
+ },
138
+ {
139
+ displayName: 'Loomcycle Agent',
140
+ name: 'agent',
141
+ type: 'string',
142
+ default: '',
143
+ required: true,
144
+ description: 'Loomcycle agent name to spawn when the parent AI Agent invokes this tool. The spawn will include `mcp__&lt;name&gt;__*` in allowed_tools so the agent can reach the MCP server.',
145
+ },
146
+ {
147
+ displayName: 'Cleanup On Workflow End',
148
+ name: 'cleanupOnEnd',
149
+ type: 'boolean',
150
+ default: false,
151
+ description: 'Whether to retire the MCP server registration when the workflow execution ends. Default false: registrations persist so multiple agentic teams share stable MCP fleets without churn.',
152
+ },
153
+ ],
154
+ };
155
+ }
156
+ async supplyData() {
157
+ const toolName = this.getNodeParameter('toolName', 0, 'loomcycle_mcp');
158
+ const toolDescription = this.getNodeParameter('toolDescription', 0, '');
159
+ const mcpName = this.getNodeParameter('mcpName', 0);
160
+ const transport = this.getNodeParameter('transport', 0);
161
+ const mcpUrl = this.getNodeParameter('mcpUrl', 0);
162
+ const headersParam = this.getNodeParameter('headers', 0, {});
163
+ const agent = this.getNodeParameter('agent', 0);
164
+ const cleanupOnEnd = this.getNodeParameter('cleanupOnEnd', 0, false);
165
+ if (transport !== 'http' && transport !== 'streamable-http') {
166
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Transport must be HTTP or Streamable-HTTP. Stdio MCP servers must be declared in loomcycle.yaml — dynamic registration does not support stdio.`);
167
+ }
168
+ const headers = collectHeaders(headersParam);
169
+ const client = await (0, client_2.getClient)(this);
170
+ const userIdDefault = await (0, client_2.getCredentialDefault)(this, 'userId');
171
+ const userTierDefault = await (0, client_2.getCredentialDefault)(this, 'userTier');
172
+ // Idempotent ensure: try get; on NotFoundError, create.
173
+ try {
174
+ await client.mcpServerDef({ op: 'get', name: mcpName });
175
+ }
176
+ catch (err) {
177
+ if (!(err instanceof client_1.NotFoundError)) {
178
+ throw err;
179
+ }
180
+ const createInput = {
181
+ op: 'create',
182
+ name: mcpName,
183
+ promote: true,
184
+ // transport / url / headers ride on the index-signature
185
+ // (SubstrateToolInput has `[extra: string]: unknown`).
186
+ transport,
187
+ url: mcpUrl,
188
+ };
189
+ if (Object.keys(headers).length > 0)
190
+ createInput.headers = headers;
191
+ await client.mcpServerDef(createInput);
192
+ }
193
+ // Env-var hints (defence-in-depth — surface in the node's log so
194
+ // operators see which env vars must exist on the loomcycle side).
195
+ const envVars = (0, envVarHints_1.extractEnvVarsFromHeaders)(headersParam);
196
+ if (envVars.length > 0) {
197
+ this.logger.info?.(`[LoomCycleMcpServerTool] MCP server ${mcpName} registered. Required env vars on loomcycle: ${envVars.join(', ')}`);
198
+ }
199
+ const allowedToolGlob = `mcp__${mcpName}__*`;
200
+ const tool = (0, clusterTool_1.buildTool)({
201
+ name: toolName,
202
+ description: toolDescription,
203
+ schema: McpServerInputSchema,
204
+ fn: async (args) => {
205
+ const runOpts = {
206
+ agent,
207
+ segments: (0, segments_1.buildSegments)(args.prompt, true), // model-supplied prompt → untrusted
208
+ allowedTools: [allowedToolGlob],
209
+ };
210
+ if (userIdDefault)
211
+ runOpts.userId = userIdDefault;
212
+ if (userTierDefault)
213
+ runOpts.userTier = userTierDefault;
214
+ const result = await (0, streaming_1.drainRunStream)(client.runStreaming(runOpts));
215
+ return result.finalText;
216
+ },
217
+ });
218
+ const supplyData = { response: tool };
219
+ if (cleanupOnEnd) {
220
+ supplyData.closeFunction = async () => {
221
+ try {
222
+ await client.mcpServerDef({ op: 'retire', name: mcpName });
223
+ }
224
+ catch {
225
+ // Best-effort cleanup; n8n's lifecycle calls closeFunction
226
+ // at workflow-end and we don't want a failed retire to
227
+ // taint the workflow's success state.
228
+ }
229
+ };
230
+ }
231
+ return supplyData;
232
+ }
233
+ }
234
+ exports.LoomCycleMcpServerTool = LoomCycleMcpServerTool;
235
+ /**
236
+ * Collect headers from the n8n fixedCollection shape into a map.
237
+ * Identical to the helper inside the umbrella node's executeMcpServerDef
238
+ * — kept local because the umbrella's lives in a closure and we want
239
+ * the cluster sub-node to be standalone.
240
+ */
241
+ function collectHeaders(raw) {
242
+ const out = {};
243
+ if (!raw || typeof raw !== 'object')
244
+ return out;
245
+ const headerCollection = raw.header;
246
+ if (!Array.isArray(headerCollection))
247
+ return out;
248
+ for (const entry of headerCollection) {
249
+ if (!entry || typeof entry !== 'object')
250
+ continue;
251
+ const name = entry.name;
252
+ const value = entry.value;
253
+ if (typeof name === 'string' && name && typeof value === 'string') {
254
+ out[name] = value;
255
+ }
256
+ }
257
+ return out;
258
+ }
259
+ //# sourceMappingURL=LoomCycleMcpServerTool.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoomCycleMcpServerTool.node.js","sourceRoot":"","sources":["../../../nodes/LoomCycleMcpServerTool/LoomCycleMcpServerTool.node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ {
2
+ "node": "@loomcycle/n8n-nodes-loomcycle.loomCycleMcpServerTool",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["AI"],
6
+ "resources": {
7
+ "primaryDocumentation": [
8
+ {
9
+ "url": "https://github.com/denn-gubsky/n8n-nodes-loomcycle#cluster-sub-nodes"
10
+ }
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="60" height="60">
3
+ <title>LoomCycle MCP Server Tool</title>
4
+ <circle cx="30" cy="30" r="28" fill="#0f172a"/>
5
+ <g fill="none" stroke="#22d3ee" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
6
+ <rect x="14" y="16" width="32" height="14" rx="2"/>
7
+ <path d="M14 22 L46 22"/>
8
+ <circle cx="18" cy="19" r="1" fill="#22d3ee"/>
9
+ <circle cx="22" cy="19" r="1" fill="#22d3ee"/>
10
+ <path d="M22 30 L22 38 L30 38"/>
11
+ <path d="M38 30 L38 38 L30 38"/>
12
+ <circle cx="30" cy="44" r="3" fill="#22d3ee"/>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,5 @@
1
+ import type { INodeType, INodeTypeDescription, ISupplyDataFunctions, SupplyData } from 'n8n-workflow';
2
+ export declare class LoomCycleMemoryTool implements INodeType {
3
+ description: INodeTypeDescription;
4
+ supplyData(this: ISupplyDataFunctions): Promise<SupplyData>;
5
+ }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoomCycleMemoryTool = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const zod_1 = require("zod");
6
+ const client_1 = require("../LoomCycle/helpers/client");
7
+ const clusterTool_1 = require("../_shared/clusterTool");
8
+ /**
9
+ * `LoomCycle Memory Tool` — cluster sub-node that plugs into n8n's AI
10
+ * Agent. Exposes loomcycle's Memory read surface as a single discriminated
11
+ * tool the agent can call.
12
+ *
13
+ * Read-only in this sub-phase (mirroring the action-node Memory resource;
14
+ * Set/Delete/Search land when `@loomcycle/client` exposes admin write
15
+ * endpoints).
16
+ */
17
+ const MemoryInputSchema = zod_1.z.object({
18
+ op: zod_1.z
19
+ .enum(['listScopes', 'listScopeIDs', 'listEntries', 'getEntry'])
20
+ .describe('Which Memory operation to invoke'),
21
+ scope: zod_1.z
22
+ .string()
23
+ .optional()
24
+ .describe('Memory scope (e.g. "agent", "user"). Required for listScopeIDs / listEntries / getEntry.'),
25
+ scopeID: zod_1.z
26
+ .string()
27
+ .optional()
28
+ .describe('Identifier within the scope. Required for listEntries / getEntry.'),
29
+ key: zod_1.z.string().optional().describe('Entry key. Required for getEntry.'),
30
+ prefix: zod_1.z.string().optional().describe('Key prefix filter for listEntries'),
31
+ limit: zod_1.z.number().int().positive().optional().describe('Max number of entries to return (listEntries)'),
32
+ });
33
+ class LoomCycleMemoryTool {
34
+ constructor() {
35
+ this.description = {
36
+ displayName: 'LoomCycle Memory Tool',
37
+ name: 'loomCycleMemoryTool',
38
+ icon: 'file:LoomCycleMemoryTool.svg',
39
+ group: ['transform'],
40
+ version: 1,
41
+ description: 'Loomcycle Memory ops (read-only) as a tool the AI Agent can call',
42
+ defaults: { name: 'LoomCycle Memory Tool' },
43
+ codex: { categories: ['AI'], subcategories: { AI: ['Tools'] } },
44
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
45
+ inputs: [],
46
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
47
+ outputs: [n8n_workflow_1.NodeConnectionTypes.AiTool],
48
+ outputNames: ['Tool'],
49
+ credentials: [{ name: 'loomCycleApi', required: true }],
50
+ properties: [
51
+ {
52
+ displayName: 'Tool Name',
53
+ name: 'toolName',
54
+ type: 'string',
55
+ default: 'loomcycle_memory',
56
+ required: true,
57
+ description: 'Name of the tool surfaced to the parent AI Agent — must be unique across sibling tools',
58
+ },
59
+ {
60
+ displayName: 'Tool Description',
61
+ name: 'toolDescription',
62
+ type: 'string',
63
+ typeOptions: { rows: 3 },
64
+ default: 'Read loomcycle Memory entries. Use op=listScopes to list scopes, op=listScopeIDs+scope to list ids, op=listEntries+scope+scopeID to list keys (optionally prefix/limit), op=getEntry+scope+scopeID+key to read a value.',
65
+ description: 'Description the AI Agent sees when deciding whether to call the tool',
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ async supplyData() {
71
+ const toolName = this.getNodeParameter('toolName', 0, 'loomcycle_memory');
72
+ const toolDescription = this.getNodeParameter('toolDescription', 0, '');
73
+ const client = await (0, client_1.getClient)(this);
74
+ const tool = (0, clusterTool_1.buildTool)({
75
+ name: toolName,
76
+ description: toolDescription,
77
+ schema: MemoryInputSchema,
78
+ fn: async (args) => {
79
+ switch (args.op) {
80
+ case 'listScopes':
81
+ return client.listMemoryScopes();
82
+ case 'listScopeIDs':
83
+ if (!args.scope)
84
+ throw new Error('scope is required for listScopeIDs');
85
+ return client.listMemoryScopeIDs(args.scope);
86
+ case 'listEntries': {
87
+ if (!args.scope || !args.scopeID)
88
+ throw new Error('scope + scopeID required for listEntries');
89
+ const opts = {};
90
+ if (args.prefix)
91
+ opts.prefix = args.prefix;
92
+ if (args.limit)
93
+ opts.limit = args.limit;
94
+ return client.listMemoryEntries(args.scope, args.scopeID, opts);
95
+ }
96
+ case 'getEntry':
97
+ if (!args.scope || !args.scopeID || !args.key) {
98
+ throw new Error('scope + scopeID + key required for getEntry');
99
+ }
100
+ return client.getMemoryEntry(args.scope, args.scopeID, args.key);
101
+ }
102
+ },
103
+ });
104
+ return { response: tool };
105
+ }
106
+ }
107
+ exports.LoomCycleMemoryTool = LoomCycleMemoryTool;
108
+ //# sourceMappingURL=LoomCycleMemoryTool.node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoomCycleMemoryTool.node.js","sourceRoot":"","sources":["../../../nodes/LoomCycleMemoryTool/LoomCycleMemoryTool.node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ {
2
+ "node": "@loomcycle/n8n-nodes-loomcycle.loomCycleMemoryTool",
3
+ "nodeVersion": "1.0",
4
+ "codexVersion": "1.0",
5
+ "categories": ["AI"],
6
+ "resources": {
7
+ "primaryDocumentation": [
8
+ {
9
+ "url": "https://github.com/denn-gubsky/n8n-nodes-loomcycle#cluster-sub-nodes"
10
+ }
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="60" height="60">
3
+ <title>LoomCycle Memory Tool</title>
4
+ <circle cx="30" cy="30" r="28" fill="#0f172a"/>
5
+ <g fill="none" stroke="#22d3ee" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
6
+ <rect x="14" y="18" width="32" height="24" rx="3"/>
7
+ <path d="M14 26 L46 26"/>
8
+ <path d="M14 34 L46 34"/>
9
+ <path d="M22 18 L22 42"/>
10
+ </g>
11
+ </svg>
@@ -0,0 +1,18 @@
1
+ import type { INodeType, INodeTypeDescription, ITriggerFunctions, ITriggerResponse } from 'n8n-workflow';
2
+ /**
3
+ * `LoomCycle: Run Completed` — trigger that fires when a loomcycle
4
+ * agent run reaches a terminal state (completed / failed / cancelled).
5
+ *
6
+ * Two transports, operator-selectable via the `mode` parameter:
7
+ * - `sse` (default): persistent stream via streamUserRunStates. The
8
+ * adapter handles the substrate's 30-min server-side cap by
9
+ * yielding cleanly; our loop transparently re-opens. parentAgentId
10
+ * filter and debug toggle are forwarded.
11
+ * - `poll`: periodic listUserAgents calls with workflow-static
12
+ * dedup. Use when the deployment can't sustain long-lived SSE
13
+ * (reverse-proxy issues, Cloudflare workers, etc.).
14
+ */
15
+ export declare class LoomCycleRunCompleted implements INodeType {
16
+ description: INodeTypeDescription;
17
+ trigger(this: ITriggerFunctions): Promise<ITriggerResponse>;
18
+ }
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoomCycleRunCompleted = void 0;
4
+ const client_1 = require("../LoomCycle/helpers/client");
5
+ const errors_1 = require("../LoomCycle/helpers/errors");
6
+ const sse_1 = require("./helpers/sse");
7
+ const poll_1 = require("./helpers/poll");
8
+ /**
9
+ * `LoomCycle: Run Completed` — trigger that fires when a loomcycle
10
+ * agent run reaches a terminal state (completed / failed / cancelled).
11
+ *
12
+ * Two transports, operator-selectable via the `mode` parameter:
13
+ * - `sse` (default): persistent stream via streamUserRunStates. The
14
+ * adapter handles the substrate's 30-min server-side cap by
15
+ * yielding cleanly; our loop transparently re-opens. parentAgentId
16
+ * filter and debug toggle are forwarded.
17
+ * - `poll`: periodic listUserAgents calls with workflow-static
18
+ * dedup. Use when the deployment can't sustain long-lived SSE
19
+ * (reverse-proxy issues, Cloudflare workers, etc.).
20
+ */
21
+ class LoomCycleRunCompleted {
22
+ constructor() {
23
+ this.description = {
24
+ displayName: 'LoomCycle: Run Completed',
25
+ name: 'loomCycleRunCompleted',
26
+ icon: 'file:LoomCycleRunCompleted.svg',
27
+ group: ['trigger'],
28
+ version: 1,
29
+ description: 'Fires when a loomcycle agent run reaches a terminal state (completed/failed/cancelled)',
30
+ defaults: { name: 'LoomCycle: Run Completed' },
31
+ // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
32
+ inputs: [],
33
+ outputs: ['main'],
34
+ credentials: [{ name: 'loomCycleApi', required: true }],
35
+ properties: [
36
+ {
37
+ displayName: 'User ID',
38
+ name: 'userId',
39
+ type: 'string',
40
+ default: '',
41
+ description: 'User_id to watch. Empty = use the credential\'s Default User ID.',
42
+ },
43
+ {
44
+ displayName: 'Statuses',
45
+ name: 'statuses',
46
+ type: 'multiOptions',
47
+ default: ['completed'],
48
+ options: [
49
+ { name: 'Cancelled', value: 'cancelled' },
50
+ { name: 'Completed', value: 'completed' },
51
+ { name: 'Failed', value: 'failed' },
52
+ ],
53
+ description: 'Which terminal statuses fire this trigger',
54
+ },
55
+ {
56
+ displayName: 'Transport',
57
+ name: 'mode',
58
+ type: 'options',
59
+ default: 'sse',
60
+ options: [
61
+ {
62
+ name: 'SSE (Push)',
63
+ value: 'sse',
64
+ description: 'Persistent stream; near-zero latency. Requires reverse-proxy SSE support.',
65
+ },
66
+ {
67
+ name: 'Polling',
68
+ value: 'poll',
69
+ description: 'Periodic list calls with dedup. Use when SSE is not viable.',
70
+ },
71
+ ],
72
+ },
73
+ {
74
+ displayName: 'Poll Interval (Seconds)',
75
+ name: 'intervalSec',
76
+ type: 'number',
77
+ default: 30,
78
+ typeOptions: { minValue: 5, maxValue: 3600 },
79
+ displayOptions: { show: { mode: ['poll'] } },
80
+ description: 'How frequently to poll for new terminal-state rows',
81
+ },
82
+ {
83
+ displayName: 'Additional Fields',
84
+ name: 'additionalFields',
85
+ type: 'collection',
86
+ placeholder: 'Add Field',
87
+ default: {},
88
+ options: [
89
+ {
90
+ displayName: 'Agent Name',
91
+ name: 'agent',
92
+ type: 'string',
93
+ default: '',
94
+ description: 'Narrow to runs of a specific agent name (SSE mode only)',
95
+ },
96
+ {
97
+ displayName: 'Parent Agent ID',
98
+ name: 'parentAgentId',
99
+ type: 'string',
100
+ default: '',
101
+ description: 'Narrow to sub-runs of a specific parent agent_id',
102
+ },
103
+ {
104
+ displayName: 'Surface Stream Open/Close Events',
105
+ name: 'emitClose',
106
+ type: 'boolean',
107
+ default: false,
108
+ description: 'Whether to emit synthetic `{__meta:"stream_close"}` rows when the SSE stream reconnects. SSE mode only.',
109
+ },
110
+ {
111
+ displayName: 'Reconnect Backoff (Ms)',
112
+ name: 'reconnectBackoffMs',
113
+ type: 'number',
114
+ default: 1000,
115
+ typeOptions: { minValue: 100, maxValue: 60000 },
116
+ description: 'Base backoff between SSE reconnects. Multiplied by attempt count (capped at 4×).',
117
+ },
118
+ ],
119
+ },
120
+ ],
121
+ };
122
+ }
123
+ async trigger() {
124
+ const userIdParam = this.getNodeParameter('userId', '') ?? '';
125
+ const statuses = this.getNodeParameter('statuses', ['completed']) ?? ['completed'];
126
+ const mode = this.getNodeParameter('mode', 'sse') ?? 'sse';
127
+ const intervalSec = this.getNodeParameter('intervalSec', 30) ?? 30;
128
+ const additionalFields = this.getNodeParameter('additionalFields', {}) ?? {};
129
+ const userId = userIdParam || (await (0, client_1.getCredentialDefault)(this, 'userId'));
130
+ if (!userId) {
131
+ throw (0, errors_1.wrapLoomcycleError)(new Error('User ID required — set per-node or as Default User ID on the credential'), this.getNode());
132
+ }
133
+ const client = await (0, client_1.getClient)(this);
134
+ const ac = new AbortController();
135
+ const agent = additionalFields.agent || undefined;
136
+ const parentAgentId = additionalFields.parentAgentId || undefined;
137
+ const emitClose = additionalFields.emitClose ?? false;
138
+ const reconnectBackoffMs = additionalFields.reconnectBackoffMs ?? 1000;
139
+ // Background loop — fire-and-forget. n8n's closeFunction aborts
140
+ // the controller; both loops respect it cooperatively.
141
+ if (mode === 'poll') {
142
+ void poll_1.runPollLoop.call(this, {
143
+ client,
144
+ userId,
145
+ statuses: statuses,
146
+ parentAgentId,
147
+ intervalMs: intervalSec * 1000,
148
+ signal: ac.signal,
149
+ });
150
+ }
151
+ else {
152
+ void sse_1.runSseLoop.call(this, {
153
+ client,
154
+ userId,
155
+ statuses,
156
+ parentAgentId,
157
+ agent,
158
+ debug: emitClose,
159
+ emitClose,
160
+ signal: ac.signal,
161
+ reconnectBackoffMs,
162
+ });
163
+ }
164
+ // Single-shot path used when the operator clicks "Listen for Test
165
+ // Event" in the n8n editor. We resolve as soon as the first event
166
+ // lands, then close.
167
+ async function manualTriggerFunction() {
168
+ const oneShotAc = new AbortController();
169
+ const userIdInner = userId;
170
+ await poll_1.pollOnce.call(this, {
171
+ client,
172
+ userId: userIdInner,
173
+ statuses: statuses,
174
+ parentAgentId,
175
+ intervalMs: 0,
176
+ signal: oneShotAc.signal,
177
+ });
178
+ }
179
+ return {
180
+ closeFunction: async () => {
181
+ ac.abort();
182
+ },
183
+ manualTriggerFunction: manualTriggerFunction.bind(this),
184
+ };
185
+ }
186
+ }
187
+ exports.LoomCycleRunCompleted = LoomCycleRunCompleted;
188
+ //# sourceMappingURL=LoomCycleRunCompleted.node.js.map