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.
@@ -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) => property.name !== 'queryMode' && property.name !== 'capabilityQueryKey')
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', 'batchCreate', 'update', 'delete'].includes(operation))
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 (!spaceId || !snapshot)
261
+ if (!snapshot)
115
262
  return null;
116
- const operation = String((_b = context.getNodeParameter('operation', itemIndex, '')) !== null && _b !== void 0 ? _b : '');
117
- const actionKey = String((_c = context.getNodeParameter('actionKey', itemIndex, '')) !== null && _c !== void 0 ? _c : '').trim();
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((_d = input.recordId) !== null && _d !== void 0 ? _d : '').trim();
125
- const recordScoped = ['update', 'delete', 'action'].includes(operation)
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 enableUserAuthorization = Boolean(this.getNodeParameter('enableUserAuthorization', itemIndex, false));
186
- const principalUserId = enableUserAuthorization
187
- ? String((_a = this.getNodeParameter('authorizationPrincipalUserId', itemIndex, '')) !== null && _a !== void 0 ? _a : '').trim()
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 (!enableUserAuthorization)
686
+ if (!auth.enabled)
192
687
  return supplied;
193
688
  const baseTool = supplied.response;
194
- const schema = {
195
- ...baseTool.schema,
196
- properties: {
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
- const output = await runtimeTool.invoke(semantic);
232
- return withAuthorizationRequired(output, authorizationScope(this, itemIndex, semantic));
233
- },
694
+ return await runtimeTool.invoke(semantic);
695
+ }),
234
696
  };
235
- return { response: tool };
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;