n8n-nodes-lifespace 0.2.0 → 0.2.2
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/README.md +1 -1
- package/dist/nodes/LifeSpace/LifeSpace.node.js +38 -32
- package/dist/nodes/LifeSpace/LifeSpace.node.js.map +1 -1
- package/dist/nodes/LifeSpaceAgentTool/LifeSpaceAgentTool.node.d.ts +2 -1
- package/dist/nodes/LifeSpaceAgentTool/LifeSpaceAgentTool.node.js +563 -58
- package/dist/nodes/LifeSpaceAgentTool/LifeSpaceAgentTool.node.js.map +1 -1
- package/dist/nodes/LifeSpaceWorkflow/LifeSpaceWorkflow.node.js +361 -2
- package/dist/nodes/LifeSpaceWorkflow/LifeSpaceWorkflow.node.js.map +1 -1
- package/dist/nodes/agent/lifeSpaceMultiMutationTool.d.ts +23 -0
- package/dist/nodes/agent/lifeSpaceMultiMutationTool.js +181 -0
- package/dist/nodes/agent/lifeSpaceMultiMutationTool.js.map +1 -0
- package/dist/nodes/agent/lifeSpacePersonTool.d.ts +12 -0
- package/dist/nodes/agent/lifeSpacePersonTool.js +261 -0
- package/dist/nodes/agent/lifeSpacePersonTool.js.map +1 -0
- package/dist/nodes/shared/lifeSpaceExecutionAuthority.js +6 -2
- package/dist/nodes/shared/lifeSpaceExecutionAuthority.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LifeSpaceAgentTool = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const lifeSpaceMultiMutationTool_1 = require("../agent/lifeSpaceMultiMutationTool");
|
|
6
|
+
const lifeSpacePersonTool_1 = require("../agent/lifeSpacePersonTool");
|
|
4
7
|
const lifeSpaceToolSnapshot_1 = require("../agent/lifeSpaceToolSnapshot");
|
|
5
8
|
const LifeSpaceTool_node_1 = require("../LifeSpaceTool/LifeSpaceTool.node");
|
|
9
|
+
const lifeSpaceExecutionAuthority_1 = require("../shared/lifeSpaceExecutionAuthority");
|
|
6
10
|
const LEGACY_AUTH_PARAMETERS = new Set([
|
|
7
11
|
'authorityMode',
|
|
8
12
|
'principalUserId',
|
|
9
13
|
'delegationId',
|
|
10
14
|
'readDelegationId',
|
|
11
|
-
'batchDelegationIds',
|
|
12
15
|
]);
|
|
13
16
|
const USER_AUTH_RECOVERABLE_CODES = new Set([
|
|
14
17
|
'DELEGATION_REQUIRED',
|
|
@@ -16,6 +19,7 @@ const USER_AUTH_RECOVERABLE_CODES = new Set([
|
|
|
16
19
|
'DELEGATION_SCOPE_INSUFFICIENT',
|
|
17
20
|
'PRINCIPAL_AUTHORITY_INSUFFICIENT',
|
|
18
21
|
]);
|
|
22
|
+
const FROM_AI_KEY = /\$fromAI\s*\(\s*(['"])([A-Za-z0-9_-]{1,64})\1/u;
|
|
19
23
|
function internalOnly(property) {
|
|
20
24
|
if (!LEGACY_AUTH_PARAMETERS.has(property.name))
|
|
21
25
|
return property;
|
|
@@ -47,13 +51,107 @@ function authorizationProperties() {
|
|
|
47
51
|
},
|
|
48
52
|
];
|
|
49
53
|
}
|
|
54
|
+
function toolResourceProperty() {
|
|
55
|
+
return {
|
|
56
|
+
displayName: 'Resource',
|
|
57
|
+
name: 'toolResource',
|
|
58
|
+
type: 'options',
|
|
59
|
+
noDataExpression: true,
|
|
60
|
+
options: [
|
|
61
|
+
{
|
|
62
|
+
name: 'Record',
|
|
63
|
+
value: 'record',
|
|
64
|
+
description: 'Use one ordinary metadata-driven LifeSpace model through Runtime Discovery',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Person',
|
|
68
|
+
value: 'person',
|
|
69
|
+
description: 'Use the Kernel first-class Space Person Directory',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
default: 'record',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function personOperationProperty() {
|
|
76
|
+
return {
|
|
77
|
+
displayName: 'Operation',
|
|
78
|
+
name: 'personOperation',
|
|
79
|
+
type: 'options',
|
|
80
|
+
noDataExpression: true,
|
|
81
|
+
displayOptions: { show: { toolResource: ['person'] } },
|
|
82
|
+
options: [
|
|
83
|
+
{ name: 'Create Person', value: 'create', action: 'Create a person', description: 'Create an unlinked Space Person' },
|
|
84
|
+
{ name: 'Delete Person', value: 'delete', action: 'Delete a person', description: 'Delete an unlinked Space Person using current optimistic concurrency' },
|
|
85
|
+
{ name: 'Get Person', value: 'get', action: 'Get a person', description: 'Get one Space Person by stable per_* ID' },
|
|
86
|
+
{ name: 'List / Search People', value: 'list', action: 'List or search people', description: 'Search canonical and alternate Person names with bounded cursor pagination' },
|
|
87
|
+
{ name: 'Update Person', value: 'update', action: 'Update a person', description: 'Update canonical or alternate Person names using current optimistic concurrency' },
|
|
88
|
+
],
|
|
89
|
+
default: 'list',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function recordOptionsProperty() {
|
|
93
|
+
return {
|
|
94
|
+
displayName: 'Options',
|
|
95
|
+
name: 'recordOptions',
|
|
96
|
+
type: 'collection',
|
|
97
|
+
placeholder: 'Add Option',
|
|
98
|
+
default: { batchProcessing: true },
|
|
99
|
+
displayOptions: {
|
|
100
|
+
show: {
|
|
101
|
+
toolResource: ['record'],
|
|
102
|
+
operation: ['create', 'update', 'delete'],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
options: [
|
|
106
|
+
{
|
|
107
|
+
displayName: 'Batch Processing',
|
|
108
|
+
name: 'batchProcessing',
|
|
109
|
+
type: 'boolean',
|
|
110
|
+
default: true,
|
|
111
|
+
noDataExpression: true,
|
|
112
|
+
description: 'Whether to group 1-20 Tool items into one LifeSpace request. Defaults on. When enabled without Atomic Consistency, items succeed or fail independently and partial success is preserved.',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
displayName: 'Atomic Consistency',
|
|
116
|
+
name: 'atomicConsistency',
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
default: false,
|
|
119
|
+
description: 'Whether all items must commit together or roll back together. Leave off for normal independent Bulk processing. In an AI Agent workflow, use n8n\'s “Let the model define this parameter” control only when the Agent should decide per Tool call whether the user\'s intent requires all-or-none semantics.',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function withRecordOnly(property) {
|
|
125
|
+
var _a;
|
|
126
|
+
if (property.name === 'operation') {
|
|
127
|
+
return {
|
|
128
|
+
...property,
|
|
129
|
+
options: ((_a = property.options) !== null && _a !== void 0 ? _a : []).filter((option) => !('value' in option) || option.value !== 'batchCreate'),
|
|
130
|
+
displayOptions: { show: { toolResource: ['record'] } },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (property.name === 'recordType') {
|
|
134
|
+
return { ...property, displayOptions: { show: { toolResource: ['record'] } } };
|
|
135
|
+
}
|
|
136
|
+
if (property.name === 'actionKey') {
|
|
137
|
+
return {
|
|
138
|
+
...property,
|
|
139
|
+
displayOptions: { show: { toolResource: ['record'], operation: ['action'] } },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return property;
|
|
143
|
+
}
|
|
50
144
|
function agentProperties(properties) {
|
|
51
145
|
const projected = properties
|
|
52
|
-
.filter((property) =>
|
|
53
|
-
.map(internalOnly)
|
|
146
|
+
.filter((property) => !['queryMode', 'capabilityQueryKey', 'batchDelegationIds'].includes(property.name))
|
|
147
|
+
.map(internalOnly)
|
|
148
|
+
.map(withRecordOnly);
|
|
54
149
|
const spaceIndex = projected.findIndex((property) => property.name === 'spaceId');
|
|
55
150
|
const insertAt = spaceIndex >= 0 ? spaceIndex : 0;
|
|
56
|
-
projected.splice(insertAt, 0, ...authorizationProperties());
|
|
151
|
+
projected.splice(insertAt, 0, ...authorizationProperties(), toolResourceProperty());
|
|
152
|
+
const actualSpaceIndex = projected.findIndex((property) => property.name === 'spaceId');
|
|
153
|
+
projected.splice(actualSpaceIndex + 1, 0, personOperationProperty());
|
|
154
|
+
projected.push(recordOptionsProperty());
|
|
57
155
|
return projected;
|
|
58
156
|
}
|
|
59
157
|
function agentExecutionContext(context, itemIndex, principalUserId, delegationId) {
|
|
@@ -68,8 +166,6 @@ function agentExecutionContext(context, itemIndex, principalUserId, delegationId
|
|
|
68
166
|
return delegationId ? principalUserId : '';
|
|
69
167
|
if (name === 'delegationId' || name === 'readDelegationId')
|
|
70
168
|
return delegationId;
|
|
71
|
-
if (name === 'batchDelegationIds')
|
|
72
|
-
return '[]';
|
|
73
169
|
return target.getNodeParameter(name, requestedItemIndex !== null && requestedItemIndex !== void 0 ? requestedItemIndex : itemIndex, fallback, options);
|
|
74
170
|
};
|
|
75
171
|
},
|
|
@@ -100,29 +196,84 @@ function requiredAccess(operation, actionKey, snapshot) {
|
|
|
100
196
|
var _a;
|
|
101
197
|
if (operation === 'query')
|
|
102
198
|
return 'read';
|
|
103
|
-
if (['create', '
|
|
199
|
+
if (['create', 'update', 'delete'].includes(operation))
|
|
104
200
|
return 'write';
|
|
105
201
|
if (operation !== 'action' || !snapshot)
|
|
106
202
|
return null;
|
|
107
203
|
const access = (_a = snapshot.model.actions.find((action) => action.key === actionKey)) === null || _a === void 0 ? void 0 : _a.access;
|
|
108
204
|
return access === 'read' || access === 'write' || access === 'manage' ? access : null;
|
|
109
205
|
}
|
|
206
|
+
function currentRecordOperation(context, itemIndex) {
|
|
207
|
+
var _a;
|
|
208
|
+
const operation = String((_a = context.getNodeParameter('operation', itemIndex, 'query')) !== null && _a !== void 0 ? _a : 'query');
|
|
209
|
+
if (operation === 'batchCreate') {
|
|
210
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Legacy Agent Tool operation batchCreate is no longer supported. Select Create and configure Batch Processing / Atomic Consistency under Options.');
|
|
211
|
+
}
|
|
212
|
+
return operation;
|
|
213
|
+
}
|
|
214
|
+
function rawRecordOptions(context) {
|
|
215
|
+
var _a;
|
|
216
|
+
const node = context.getNode();
|
|
217
|
+
const raw = (_a = node.parameters) === null || _a === void 0 ? void 0 : _a.recordOptions;
|
|
218
|
+
return raw && typeof raw === 'object' && !Array.isArray(raw)
|
|
219
|
+
? raw
|
|
220
|
+
: {};
|
|
221
|
+
}
|
|
222
|
+
function fromAiKey(value) {
|
|
223
|
+
var _a, _b;
|
|
224
|
+
if (typeof value !== 'string')
|
|
225
|
+
return null;
|
|
226
|
+
return (_b = (_a = FROM_AI_KEY.exec(value)) === null || _a === void 0 ? void 0 : _a[2]) !== null && _b !== void 0 ? _b : null;
|
|
227
|
+
}
|
|
228
|
+
function recordBatchSettings(context, itemIndex) {
|
|
229
|
+
var _a;
|
|
230
|
+
const raw = rawRecordOptions(context);
|
|
231
|
+
const atomicFromAiKey = fromAiKey(raw.atomicConsistency);
|
|
232
|
+
const evaluated = atomicFromAiKey
|
|
233
|
+
? {}
|
|
234
|
+
: context.getNodeParameter('recordOptions', itemIndex, {});
|
|
235
|
+
const batchValue = (_a = raw.batchProcessing) !== null && _a !== void 0 ? _a : evaluated.batchProcessing;
|
|
236
|
+
return {
|
|
237
|
+
batchProcessing: batchValue === undefined ? true : batchValue === true,
|
|
238
|
+
atomicConsistency: atomicFromAiKey
|
|
239
|
+
? false
|
|
240
|
+
: raw.atomicConsistency === true || evaluated.atomicConsistency === true,
|
|
241
|
+
atomicFromAiKey,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
110
244
|
function authorizationScope(context, itemIndex, semantic) {
|
|
111
|
-
var _a, _b, _c, _d;
|
|
245
|
+
var _a, _b, _c, _d, _e;
|
|
112
246
|
const spaceId = String((_a = context.getNodeParameter('spaceId', itemIndex, '')) !== null && _a !== void 0 ? _a : '').trim();
|
|
247
|
+
if (!spaceId)
|
|
248
|
+
return null;
|
|
249
|
+
const resource = String((_b = context.getNodeParameter('toolResource', itemIndex, 'record')) !== null && _b !== void 0 ? _b : 'record');
|
|
250
|
+
if (resource === 'person') {
|
|
251
|
+
const operation = String((_c = context.getNodeParameter('personOperation', itemIndex, 'list')) !== null && _c !== void 0 ? _c : 'list');
|
|
252
|
+
return {
|
|
253
|
+
spaceId,
|
|
254
|
+
scopes: [{
|
|
255
|
+
target: { type: 'space', id: spaceId },
|
|
256
|
+
maxAccess: (0, lifeSpacePersonTool_1.personToolRequiredAccess)(operation),
|
|
257
|
+
}],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
113
260
|
const snapshot = (0, lifeSpaceToolSnapshot_1.decodeAgentToolSemanticSnapshot)(context.getNodeParameter('recordType', itemIndex, ''));
|
|
114
|
-
if (!
|
|
261
|
+
if (!snapshot)
|
|
115
262
|
return null;
|
|
116
|
-
const operation =
|
|
117
|
-
const actionKey = String((
|
|
263
|
+
const operation = currentRecordOperation(context, itemIndex);
|
|
264
|
+
const actionKey = String((_d = context.getNodeParameter('actionKey', itemIndex, '')) !== null && _d !== void 0 ? _d : '').trim();
|
|
118
265
|
const access = requiredAccess(operation, actionKey, snapshot);
|
|
119
266
|
if (!access)
|
|
120
267
|
return null;
|
|
268
|
+
const batchProcessing = ['create', 'update', 'delete'].includes(operation)
|
|
269
|
+
? recordBatchSettings(context, itemIndex).batchProcessing
|
|
270
|
+
: false;
|
|
121
271
|
const input = semantic && typeof semantic === 'object' && !Array.isArray(semantic)
|
|
122
272
|
? semantic
|
|
123
273
|
: {};
|
|
124
|
-
const recordId = String((
|
|
125
|
-
const recordScoped =
|
|
274
|
+
const recordId = String((_e = input.recordId) !== null && _e !== void 0 ? _e : '').trim();
|
|
275
|
+
const recordScoped = !batchProcessing
|
|
276
|
+
&& ['update', 'delete', 'action'].includes(operation)
|
|
126
277
|
&& /^rec_[A-Za-z0-9_-]+$/u.test(recordId);
|
|
127
278
|
const scope = recordScoped
|
|
128
279
|
? { target: { type: 'record', id: recordId }, maxAccess: access }
|
|
@@ -161,6 +312,330 @@ function withAuthorizationRequired(output, required) {
|
|
|
161
312
|
authorizationRequired: required,
|
|
162
313
|
});
|
|
163
314
|
}
|
|
315
|
+
function parseJsonCandidate(value) {
|
|
316
|
+
if (typeof value !== 'string')
|
|
317
|
+
return value;
|
|
318
|
+
try {
|
|
319
|
+
return JSON.parse(value);
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
return value;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function structuredFailure(error) {
|
|
326
|
+
var _a;
|
|
327
|
+
const queue = [error];
|
|
328
|
+
const seen = new Set();
|
|
329
|
+
while (queue.length) {
|
|
330
|
+
const current = parseJsonCandidate(queue.shift());
|
|
331
|
+
if (!current || typeof current !== 'object' || Array.isArray(current) || seen.has(current))
|
|
332
|
+
continue;
|
|
333
|
+
seen.add(current);
|
|
334
|
+
const object = current;
|
|
335
|
+
const nested = object.error;
|
|
336
|
+
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
337
|
+
const apiError = nested;
|
|
338
|
+
const code = String((_a = apiError.code) !== null && _a !== void 0 ? _a : '');
|
|
339
|
+
if (code) {
|
|
340
|
+
return JSON.stringify({
|
|
341
|
+
ok: false,
|
|
342
|
+
error: {
|
|
343
|
+
code,
|
|
344
|
+
message: typeof apiError.message === 'string' ? apiError.message : 'LifeSpace Tool call failed',
|
|
345
|
+
retryable: false,
|
|
346
|
+
nextAction: USER_AUTH_RECOVERABLE_CODES.has(code) ? 'request_authorization' : 'report_failure',
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
for (const key of ['body', 'data', 'response', 'cause', 'description', 'message']) {
|
|
352
|
+
if (object[key] !== undefined)
|
|
353
|
+
queue.push(object[key]);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return JSON.stringify({
|
|
357
|
+
ok: false,
|
|
358
|
+
error: {
|
|
359
|
+
code: 'LIFESPACE_TOOL_CALL_FAILED',
|
|
360
|
+
message: error instanceof Error ? error.message : 'LifeSpace Tool call failed',
|
|
361
|
+
retryable: false,
|
|
362
|
+
nextAction: 'report_failure',
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function stringifyToolOutput(value) {
|
|
367
|
+
if (typeof value === 'string')
|
|
368
|
+
return value;
|
|
369
|
+
if (value === undefined)
|
|
370
|
+
return JSON.stringify({ success: true });
|
|
371
|
+
try {
|
|
372
|
+
return JSON.stringify(value);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return String(value);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function requestOptions(baseUrl, request) {
|
|
379
|
+
const options = {
|
|
380
|
+
method: request.method,
|
|
381
|
+
url: `${baseUrl}${request.path}`,
|
|
382
|
+
json: true,
|
|
383
|
+
};
|
|
384
|
+
if (request.qs && Object.keys(request.qs).length)
|
|
385
|
+
options.qs = request.qs;
|
|
386
|
+
if (request.body)
|
|
387
|
+
options.body = request.body;
|
|
388
|
+
if (request.repeatQueryArrays)
|
|
389
|
+
options.arrayFormat = 'repeat';
|
|
390
|
+
return options;
|
|
391
|
+
}
|
|
392
|
+
function currentVersion(context, response) {
|
|
393
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
|
394
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'LifeSpace current-resource lookup returned an invalid response');
|
|
395
|
+
}
|
|
396
|
+
const data = response.data;
|
|
397
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
398
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'LifeSpace current-resource lookup returned an invalid data envelope');
|
|
399
|
+
}
|
|
400
|
+
const version = data.version;
|
|
401
|
+
if (typeof version !== 'number' || !Number.isInteger(version) || version < 1) {
|
|
402
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'LifeSpace current-resource lookup did not expose a usable version');
|
|
403
|
+
}
|
|
404
|
+
return version;
|
|
405
|
+
}
|
|
406
|
+
async function agentBaseUrl(context, itemIndex) {
|
|
407
|
+
return (0, lifeSpaceExecutionAuthority_1.delegatedAgentCoreBaseUrl)(context, await context.getCredentials('lifeSpaceAgentExecutionApi', itemIndex));
|
|
408
|
+
}
|
|
409
|
+
function inputForLog(value) {
|
|
410
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
411
|
+
? value
|
|
412
|
+
: { value: String(value !== null && value !== void 0 ? value : '') };
|
|
413
|
+
}
|
|
414
|
+
function semanticToolInput(schema, value) {
|
|
415
|
+
if (typeof value.toolCallId !== 'string')
|
|
416
|
+
return value;
|
|
417
|
+
const input = {};
|
|
418
|
+
for (const key of Object.keys(schema.properties)) {
|
|
419
|
+
if (Object.prototype.hasOwnProperty.call(value, key))
|
|
420
|
+
input[key] = value[key];
|
|
421
|
+
}
|
|
422
|
+
if (Object.prototype.hasOwnProperty.call(value, 'delegationId'))
|
|
423
|
+
input.delegationId = value.delegationId;
|
|
424
|
+
return input;
|
|
425
|
+
}
|
|
426
|
+
async function invokePerson(context, itemIndex, semantic, delegationId) {
|
|
427
|
+
var _a, _b;
|
|
428
|
+
const operation = String((_a = context.getNodeParameter('personOperation', itemIndex, 'list')) !== null && _a !== void 0 ? _a : 'list');
|
|
429
|
+
const spaceId = String((_b = context.getNodeParameter('spaceId', itemIndex, '')) !== null && _b !== void 0 ? _b : '').trim();
|
|
430
|
+
const executionContext = agentExecutionContext(context, itemIndex, '', delegationId);
|
|
431
|
+
const authority = await (0, lifeSpaceExecutionAuthority_1.executionAuthority)(executionContext, itemIndex, (0, lifeSpacePersonTool_1.personToolRequiredAccess)(operation), { requireDelegation: false });
|
|
432
|
+
const baseUrl = await agentBaseUrl(executionContext, itemIndex);
|
|
433
|
+
let request = (0, lifeSpacePersonTool_1.buildPersonToolRequest)(spaceId, operation, semantic);
|
|
434
|
+
if (request.needsCurrentVersion) {
|
|
435
|
+
const current = await (0, lifeSpaceExecutionAuthority_1.lifeSpaceRequest)(executionContext, authority, {
|
|
436
|
+
method: 'GET',
|
|
437
|
+
url: `${baseUrl}${request.path}`,
|
|
438
|
+
json: true,
|
|
439
|
+
});
|
|
440
|
+
request = (0, lifeSpacePersonTool_1.buildPersonToolRequest)(spaceId, operation, semantic, currentVersion(context, current));
|
|
441
|
+
}
|
|
442
|
+
const response = await (0, lifeSpaceExecutionAuthority_1.lifeSpaceRequest)(executionContext, authority, requestOptions(baseUrl, request));
|
|
443
|
+
return stringifyToolOutput(response);
|
|
444
|
+
}
|
|
445
|
+
async function personRuntime(context, itemIndex) {
|
|
446
|
+
var _a, _b, _c;
|
|
447
|
+
const spaceId = String((_a = context.getNodeParameter('spaceId', itemIndex, '')) !== null && _a !== void 0 ? _a : '').trim();
|
|
448
|
+
const operation = String((_b = context.getNodeParameter('personOperation', itemIndex, 'list')) !== null && _b !== void 0 ? _b : 'list');
|
|
449
|
+
const descriptionOverride = String((_c = context.getNodeParameter('descriptionOverride', itemIndex, '')) !== null && _c !== void 0 ? _c : '').trim();
|
|
450
|
+
const definition = (0, lifeSpacePersonTool_1.buildPersonToolDefinition)(spaceId, null, operation, descriptionOverride);
|
|
451
|
+
return {
|
|
452
|
+
baseUrl: await agentBaseUrl(context, itemIndex),
|
|
453
|
+
spaceId,
|
|
454
|
+
schema: definition.schema,
|
|
455
|
+
name: definition.name,
|
|
456
|
+
description: definition.description,
|
|
457
|
+
metadata: { lifeSpaceResource: 'person', lifeSpacePersonOperation: operation },
|
|
458
|
+
requiredAccess: definition.requiredAccess,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function dynamicAtomicDescription(model, config, operation, key) {
|
|
462
|
+
var _a, _b, _c, _d;
|
|
463
|
+
const plural = ((_a = model.display.plural) === null || _a === void 0 ? void 0 : _a.trim()) || `${((_b = model.display.singular) === null || _b === void 0 ? void 0 : _b.trim()) || model.key}s`;
|
|
464
|
+
const verb = operation === 'create' ? 'Create' : operation === 'update' ? 'Update' : 'Delete';
|
|
465
|
+
const base = ((_c = config.descriptionOverride) === null || _c === void 0 ? void 0 : _c.trim())
|
|
466
|
+
|| `${verb} 1-20 ${plural} in Space "${((_d = config.spaceName) === null || _d === void 0 ? void 0 : _d.trim()) || config.spaceId}".`;
|
|
467
|
+
const recordGuidance = operation === 'create'
|
|
468
|
+
? ''
|
|
469
|
+
: ' Versions are resolved when omitted. Use stable record IDs; never guess them.';
|
|
470
|
+
return `${base} "${key}" defaults false: non-atomic Bulk; items commit independently and partial success is possible. Set true only when the user requires all items in this Tool call to commit or roll back together. Item count alone is not a reason to choose atomic. Atomicity never spans Tool calls, operations, models, or Spaces. After Bulk partial failure, retry only failed items; never resend succeeded items.${recordGuidance}`;
|
|
471
|
+
}
|
|
472
|
+
async function multiRuntime(context, itemIndex) {
|
|
473
|
+
var _a, _b;
|
|
474
|
+
const spaceId = String((_a = context.getNodeParameter('spaceId', itemIndex, '')) !== null && _a !== void 0 ? _a : '').trim();
|
|
475
|
+
const snapshot = (0, lifeSpaceToolSnapshot_1.decodeAgentToolSemanticSnapshot)(context.getNodeParameter('recordType', itemIndex, ''));
|
|
476
|
+
if (!snapshot || snapshot.spaceId !== spaceId) {
|
|
477
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'This LifeSpace Agent Tool has no valid saved semantic contract. Reselect Record Type and save the workflow.');
|
|
478
|
+
}
|
|
479
|
+
const operation = currentRecordOperation(context, itemIndex);
|
|
480
|
+
if (!['create', 'update', 'delete'].includes(operation)) {
|
|
481
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Batch Processing is supported only for create/update/delete');
|
|
482
|
+
}
|
|
483
|
+
const batchSettings = recordBatchSettings(context, itemIndex);
|
|
484
|
+
if (!batchSettings.batchProcessing) {
|
|
485
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'LifeSpace multi-mutation requires Batch Processing to be enabled');
|
|
486
|
+
}
|
|
487
|
+
const mode = batchSettings.atomicConsistency ? 'atomic' : 'bulk';
|
|
488
|
+
const config = {
|
|
489
|
+
spaceId,
|
|
490
|
+
spaceName: snapshot.spaceName,
|
|
491
|
+
operation,
|
|
492
|
+
queryMode: 'generic',
|
|
493
|
+
actionKey: '',
|
|
494
|
+
capabilityQueryKey: '',
|
|
495
|
+
descriptionOverride: String((_b = context.getNodeParameter('descriptionOverride', itemIndex, '')) !== null && _b !== void 0 ? _b : '').trim(),
|
|
496
|
+
viewingTimezone: context.getTimezone(),
|
|
497
|
+
};
|
|
498
|
+
const definition = (0, lifeSpaceMultiMutationTool_1.buildMultiMutationDefinition)(snapshot.model, config, operation, mode);
|
|
499
|
+
const atomicFromAiKey = batchSettings.atomicFromAiKey;
|
|
500
|
+
const schema = atomicFromAiKey
|
|
501
|
+
? {
|
|
502
|
+
...definition.schema,
|
|
503
|
+
properties: {
|
|
504
|
+
...definition.schema.properties,
|
|
505
|
+
[atomicFromAiKey]: {
|
|
506
|
+
type: 'boolean',
|
|
507
|
+
default: false,
|
|
508
|
+
description: 'All-or-none for this Tool call. Defaults false. Set true only when the user requires every item to commit together; item count alone does not imply atomicity.',
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
}
|
|
512
|
+
: definition.schema;
|
|
513
|
+
return {
|
|
514
|
+
baseUrl: await agentBaseUrl(context, itemIndex),
|
|
515
|
+
spaceId,
|
|
516
|
+
schema,
|
|
517
|
+
name: definition.name,
|
|
518
|
+
description: atomicFromAiKey
|
|
519
|
+
? dynamicAtomicDescription(snapshot.model, config, operation, atomicFromAiKey)
|
|
520
|
+
: definition.description,
|
|
521
|
+
metadata: {
|
|
522
|
+
lifeSpaceModelVersion: snapshot.model.version,
|
|
523
|
+
lifeSpaceSchemaHash: snapshot.model.schemaHash,
|
|
524
|
+
lifeSpaceMutationMode: atomicFromAiKey ? 'agent-controlled' : mode,
|
|
525
|
+
...(atomicFromAiKey ? { lifeSpaceAtomicConsistencyArgument: atomicFromAiKey } : {}),
|
|
526
|
+
},
|
|
527
|
+
requiredAccess: 'write',
|
|
528
|
+
model: snapshot.model,
|
|
529
|
+
config,
|
|
530
|
+
operation,
|
|
531
|
+
mode,
|
|
532
|
+
atomicFromAiKey,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
function selectedMultiMode(context, runtime, semantic) {
|
|
536
|
+
if (!runtime.atomicFromAiKey)
|
|
537
|
+
return { mode: runtime.mode, semantic };
|
|
538
|
+
if (!semantic || typeof semantic !== 'object' || Array.isArray(semantic)) {
|
|
539
|
+
return { mode: 'bulk', semantic };
|
|
540
|
+
}
|
|
541
|
+
const input = { ...semantic };
|
|
542
|
+
const rawAtomic = input[runtime.atomicFromAiKey];
|
|
543
|
+
delete input[runtime.atomicFromAiKey];
|
|
544
|
+
if (rawAtomic !== undefined && typeof rawAtomic !== 'boolean') {
|
|
545
|
+
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `LifeSpace Atomic Consistency Tool argument ${runtime.atomicFromAiKey} must be boolean`);
|
|
546
|
+
}
|
|
547
|
+
return { mode: rawAtomic === true ? 'atomic' : 'bulk', semantic: input };
|
|
548
|
+
}
|
|
549
|
+
async function invokeMulti(context, itemIndex, semantic, delegationId) {
|
|
550
|
+
var _a;
|
|
551
|
+
const runtime = await multiRuntime(context, itemIndex);
|
|
552
|
+
const selected = selectedMultiMode(context, runtime, semantic);
|
|
553
|
+
const executionContext = agentExecutionContext(context, itemIndex, '', delegationId);
|
|
554
|
+
const authority = await (0, lifeSpaceExecutionAuthority_1.executionAuthority)(executionContext, itemIndex, 'write', { requireDelegation: false });
|
|
555
|
+
const requester = async (options) => (0, lifeSpaceExecutionAuthority_1.lifeSpaceRequest)(executionContext, authority, options);
|
|
556
|
+
const prepared = await (0, lifeSpaceMultiMutationTool_1.prepareMultiMutationInput)(executionContext, runtime.baseUrl, runtime.model, runtime.config, runtime.operation, runtime.schema, selected.semantic, requester);
|
|
557
|
+
const request = (0, lifeSpaceMultiMutationTool_1.buildMultiMutationRequest)(runtime.model, runtime.config, runtime.operation, selected.mode, prepared, authority.mode === 'delegatedAgent' ? (_a = authority.delegationId) !== null && _a !== void 0 ? _a : '' : '');
|
|
558
|
+
const outerAuthority = authority.mode === 'delegatedAgent'
|
|
559
|
+
? { ...authority, delegationId: null }
|
|
560
|
+
: authority;
|
|
561
|
+
const response = await (0, lifeSpaceExecutionAuthority_1.lifeSpaceRequest)(executionContext, outerAuthority, requestOptions(runtime.baseUrl, request));
|
|
562
|
+
return stringifyToolOutput(response);
|
|
563
|
+
}
|
|
564
|
+
function customTool(context, itemIndex, runtime, invoke) {
|
|
565
|
+
return {
|
|
566
|
+
name: (0, n8n_workflow_1.nodeNameToToolName)(context.getNode()),
|
|
567
|
+
description: runtime.description,
|
|
568
|
+
schema: runtime.schema,
|
|
569
|
+
metadata: {
|
|
570
|
+
lifeSpaceSemanticToolName: runtime.name,
|
|
571
|
+
...runtime.metadata,
|
|
572
|
+
},
|
|
573
|
+
invoke: async (query) => {
|
|
574
|
+
const { index } = context.addInputData(n8n_workflow_1.NodeConnectionTypes.AiTool, [[{ json: { query: inputForLog(query) } }]]);
|
|
575
|
+
let output;
|
|
576
|
+
try {
|
|
577
|
+
output = await invoke(query);
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
output = structuredFailure(error);
|
|
581
|
+
}
|
|
582
|
+
void context.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, index, [[{ json: { response: output } }]]);
|
|
583
|
+
return output;
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function authorizationSettings(context, itemIndex) {
|
|
588
|
+
var _a;
|
|
589
|
+
const enabled = Boolean(context.getNodeParameter('enableUserAuthorization', itemIndex, false));
|
|
590
|
+
return {
|
|
591
|
+
enabled,
|
|
592
|
+
principalUserId: enabled
|
|
593
|
+
? String((_a = context.getNodeParameter('authorizationPrincipalUserId', itemIndex, '')) !== null && _a !== void 0 ? _a : '').trim()
|
|
594
|
+
: '',
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function wrapOptionalAuthorization(context, itemIndex, baseTool, principalUserId, invokeWithDelegation) {
|
|
598
|
+
const schema = {
|
|
599
|
+
...baseTool.schema,
|
|
600
|
+
properties: {
|
|
601
|
+
...baseTool.schema.properties,
|
|
602
|
+
delegationId: {
|
|
603
|
+
type: 'string',
|
|
604
|
+
description: 'Optional LifeSpace dlg_* returned by a successful authorization confirmation. Omit it to execute with the Agent\'s own Authority.',
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
};
|
|
608
|
+
return {
|
|
609
|
+
...baseTool,
|
|
610
|
+
description: `${baseTool.description} When user authorization is required, pass the dlg_* returned by LifeSpace Confirm Authorization as delegationId. Omit delegationId to use the Agent's own Authority.`,
|
|
611
|
+
schema,
|
|
612
|
+
metadata: { ...baseTool.metadata, lifeSpaceUserAuthorization: 'optional' },
|
|
613
|
+
invoke: async (query) => {
|
|
614
|
+
const { semantic, delegationId } = toolInput(query);
|
|
615
|
+
if (delegationId && !/^dlg_[A-Za-z0-9_-]+$/u.test(delegationId))
|
|
616
|
+
return invalidDelegationOutput();
|
|
617
|
+
if (delegationId && !/^usr_[A-Za-z0-9_-]+$/u.test(principalUserId)) {
|
|
618
|
+
return JSON.stringify({
|
|
619
|
+
ok: false,
|
|
620
|
+
error: {
|
|
621
|
+
code: 'USER_CONTEXT_REQUIRED',
|
|
622
|
+
message: 'A trusted Principal User ID is required to use delegationId',
|
|
623
|
+
retryable: false,
|
|
624
|
+
nextAction: 'report_failure',
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
let output;
|
|
629
|
+
try {
|
|
630
|
+
output = await invokeWithDelegation(semantic, delegationId);
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
output = structuredFailure(error);
|
|
634
|
+
}
|
|
635
|
+
return withAuthorizationRequired(output, authorizationScope(context, itemIndex, semantic));
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
}
|
|
164
639
|
class LifeSpaceAgentTool extends LifeSpaceTool_node_1.LifeSpaceTool {
|
|
165
640
|
constructor() {
|
|
166
641
|
super();
|
|
@@ -170,7 +645,7 @@ class LifeSpaceAgentTool extends LifeSpaceTool_node_1.LifeSpaceTool {
|
|
|
170
645
|
light: 'file:lifespace.svg',
|
|
171
646
|
dark: 'file:lifespace.dark.svg',
|
|
172
647
|
},
|
|
173
|
-
description: 'Expose one scoped LifeSpace operation to an AI Agent',
|
|
648
|
+
description: 'Expose one scoped LifeSpace Record or Person operation to an AI Agent',
|
|
174
649
|
credentials: [
|
|
175
650
|
{
|
|
176
651
|
name: 'lifeSpaceAgentExecutionApi',
|
|
@@ -182,57 +657,87 @@ class LifeSpaceAgentTool extends LifeSpaceTool_node_1.LifeSpaceTool {
|
|
|
182
657
|
}
|
|
183
658
|
async supplyData(itemIndex) {
|
|
184
659
|
var _a;
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
660
|
+
const resource = String((_a = this.getNodeParameter('toolResource', itemIndex, 'record')) !== null && _a !== void 0 ? _a : 'record');
|
|
661
|
+
const auth = authorizationSettings(this, itemIndex);
|
|
662
|
+
if (resource === 'person') {
|
|
663
|
+
const runtime = await personRuntime(this, itemIndex);
|
|
664
|
+
const direct = customTool(this, itemIndex, runtime, async (query) => invokePerson(this, itemIndex, query, ''));
|
|
665
|
+
if (!auth.enabled)
|
|
666
|
+
return { response: direct };
|
|
667
|
+
return {
|
|
668
|
+
response: wrapOptionalAuthorization(this, itemIndex, direct, auth.principalUserId, async (semantic, delegationId) => invokePerson(this, itemIndex, semantic, delegationId)),
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const operation = currentRecordOperation(this, itemIndex);
|
|
672
|
+
const batchSettings = ['create', 'update', 'delete'].includes(operation)
|
|
673
|
+
? recordBatchSettings(this, itemIndex)
|
|
674
|
+
: null;
|
|
675
|
+
if (batchSettings === null || batchSettings === void 0 ? void 0 : batchSettings.batchProcessing) {
|
|
676
|
+
const runtime = await multiRuntime(this, itemIndex);
|
|
677
|
+
const direct = customTool(this, itemIndex, runtime, async (query) => invokeMulti(this, itemIndex, query, ''));
|
|
678
|
+
if (!auth.enabled)
|
|
679
|
+
return { response: direct };
|
|
680
|
+
return {
|
|
681
|
+
response: wrapOptionalAuthorization(this, itemIndex, direct, auth.principalUserId, async (semantic, delegationId) => invokeMulti(this, itemIndex, semantic, delegationId)),
|
|
682
|
+
};
|
|
683
|
+
}
|
|
189
684
|
const directContext = agentExecutionContext(this, itemIndex, '', '');
|
|
190
685
|
const supplied = await LifeSpaceTool_node_1.LifeSpaceTool.prototype.supplyData.call(directContext, itemIndex);
|
|
191
|
-
if (!
|
|
686
|
+
if (!auth.enabled)
|
|
192
687
|
return supplied;
|
|
193
688
|
const baseTool = supplied.response;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
...baseTool.schema.properties,
|
|
198
|
-
delegationId: {
|
|
199
|
-
type: 'string',
|
|
200
|
-
description: 'Optional LifeSpace dlg_* returned by a successful authorization confirmation. Omit it to execute with the Agent\'s own Authority.',
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
const tool = {
|
|
205
|
-
...baseTool,
|
|
206
|
-
description: `${baseTool.description} When user authorization is required, pass the dlg_* returned by LifeSpace Confirm Authorization as delegationId. Omit delegationId to use the Agent's own Authority.`,
|
|
207
|
-
schema,
|
|
208
|
-
metadata: {
|
|
209
|
-
...baseTool.metadata,
|
|
210
|
-
lifeSpaceUserAuthorization: 'optional',
|
|
211
|
-
},
|
|
212
|
-
invoke: async (query) => {
|
|
213
|
-
const { semantic, delegationId } = toolInput(query);
|
|
214
|
-
if (delegationId && !/^dlg_[A-Za-z0-9_-]+$/u.test(delegationId)) {
|
|
215
|
-
return invalidDelegationOutput();
|
|
216
|
-
}
|
|
217
|
-
if (delegationId && !/^usr_[A-Za-z0-9_-]+$/u.test(principalUserId)) {
|
|
218
|
-
return JSON.stringify({
|
|
219
|
-
ok: false,
|
|
220
|
-
error: {
|
|
221
|
-
code: 'USER_CONTEXT_REQUIRED',
|
|
222
|
-
message: 'A trusted Principal User ID is required to use delegationId',
|
|
223
|
-
retryable: false,
|
|
224
|
-
nextAction: 'report_failure',
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
const executionContext = agentExecutionContext(this, itemIndex, principalUserId, delegationId);
|
|
689
|
+
return {
|
|
690
|
+
response: wrapOptionalAuthorization(this, itemIndex, baseTool, auth.principalUserId, async (semantic, delegationId) => {
|
|
691
|
+
const executionContext = agentExecutionContext(this, itemIndex, auth.principalUserId, delegationId);
|
|
229
692
|
const runtimeSupply = await LifeSpaceTool_node_1.LifeSpaceTool.prototype.supplyData.call(executionContext, itemIndex);
|
|
230
693
|
const runtimeTool = runtimeSupply.response;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
},
|
|
694
|
+
return await runtimeTool.invoke(semantic);
|
|
695
|
+
}),
|
|
234
696
|
};
|
|
235
|
-
|
|
697
|
+
}
|
|
698
|
+
async execute() {
|
|
699
|
+
var _a;
|
|
700
|
+
const items = this.getInputData();
|
|
701
|
+
if (!items.length)
|
|
702
|
+
return [[]];
|
|
703
|
+
const resource = String((_a = this.getNodeParameter('toolResource', 0, 'record')) !== null && _a !== void 0 ? _a : 'record');
|
|
704
|
+
const operation = resource === 'person' ? 'query' : currentRecordOperation(this, 0);
|
|
705
|
+
const batchSettings = resource === 'record' && ['create', 'update', 'delete'].includes(operation)
|
|
706
|
+
? recordBatchSettings(this, 0)
|
|
707
|
+
: null;
|
|
708
|
+
const custom = resource === 'person' || (batchSettings === null || batchSettings === void 0 ? void 0 : batchSettings.batchProcessing) === true;
|
|
709
|
+
if (!custom)
|
|
710
|
+
return await LifeSpaceTool_node_1.LifeSpaceTool.prototype.execute.call(this);
|
|
711
|
+
const output = [];
|
|
712
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
|
|
713
|
+
const item = items[itemIndex];
|
|
714
|
+
if (!item)
|
|
715
|
+
continue;
|
|
716
|
+
const runtime = resource === 'person'
|
|
717
|
+
? await personRuntime(this, itemIndex)
|
|
718
|
+
: await multiRuntime(this, itemIndex);
|
|
719
|
+
const projected = semanticToolInput(runtime.schema, item.json);
|
|
720
|
+
const { semantic, delegationId } = toolInput(projected);
|
|
721
|
+
let response;
|
|
722
|
+
if (delegationId && !/^dlg_[A-Za-z0-9_-]+$/u.test(delegationId)) {
|
|
723
|
+
response = invalidDelegationOutput();
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
try {
|
|
727
|
+
response = resource === 'person'
|
|
728
|
+
? await invokePerson(this, itemIndex, semantic, delegationId)
|
|
729
|
+
: await invokeMulti(this, itemIndex, semantic, delegationId);
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
response = structuredFailure(error);
|
|
733
|
+
}
|
|
734
|
+
if (authorizationSettings(this, itemIndex).enabled) {
|
|
735
|
+
response = withAuthorizationRequired(response, authorizationScope(this, itemIndex, semantic));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
output.push({ json: { response }, pairedItem: { item: itemIndex } });
|
|
739
|
+
}
|
|
740
|
+
return [output];
|
|
236
741
|
}
|
|
237
742
|
}
|
|
238
743
|
exports.LifeSpaceAgentTool = LifeSpaceAgentTool;
|