@elizaos/app-core 2.0.0-alpha.210 → 2.0.0-alpha.211
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/apps/app-lifeops/src/actions/calendar.d.ts.map +1 -1
- package/apps/app-lifeops/src/actions/calendar.js +1 -3
- package/apps/app-lifeops/src/actions/cross-channel-send.d.ts.map +1 -1
- package/apps/app-lifeops/src/actions/cross-channel-send.js +3 -0
- package/apps/app-lifeops/src/actions/health.d.ts.map +1 -1
- package/apps/app-lifeops/src/actions/health.js +23 -2
- package/package.json +5 -5
- package/packages/app-core/src/services/n8n-sidecar.d.ts +23 -6
- package/packages/app-core/src/services/n8n-sidecar.d.ts.map +1 -1
- package/packages/app-core/src/services/n8n-sidecar.js +136 -46
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/actions/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAKN,aAAa,EACb,MAAM,EACN,KAAK,EACN,MAAM,eAAe,CAAC;AAyCvB,KAAK,iBAAiB,GAClB,MAAM,GACN,YAAY,GACZ,eAAe,GACf,cAAc,GACd,cAAc,GACd,cAAc,GACd,aAAa,CAAC;AAuBlB,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAkjDF,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,SAA2B,GAClC,OAAO,CAAC,eAAe,CAAC,CA6J1B;AA2gCD,eAAO,MAAM,cAAc,EAAE,MAAM,GAAG;IACpC,8BAA8B,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/actions/calendar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAKN,aAAa,EACb,MAAM,EACN,KAAK,EACN,MAAM,eAAe,CAAC;AAyCvB,KAAK,iBAAiB,GAClB,MAAM,GACN,YAAY,GACZ,eAAe,GACf,cAAc,GACd,cAAc,GACd,cAAc,GACd,aAAa,CAAC;AAuBlB,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAkjDF,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,KAAK,GAAG,SAAS,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,SAA2B,GAClC,OAAO,CAAC,eAAe,CAAC,CA6J1B;AA2gCD,eAAO,MAAM,cAAc,EAAE,MAAM,GAAG;IACpC,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAkqC1C,CAAC"}
|
|
@@ -2110,8 +2110,6 @@ export const calendarAction = {
|
|
|
2110
2110
|
tags: [
|
|
2111
2111
|
"always-include",
|
|
2112
2112
|
"calendar",
|
|
2113
|
-
"schedule",
|
|
2114
|
-
"meeting",
|
|
2115
2113
|
"event",
|
|
2116
2114
|
"recurring block",
|
|
2117
2115
|
"time block",
|
|
@@ -2127,7 +2125,7 @@ export const calendarAction = {
|
|
|
2127
2125
|
"DO NOT use this action for email inbox work, drafting or sending emails — use GMAIL_ACTION instead. " +
|
|
2128
2126
|
"DO NOT use this action for personal habits, goals, routines, or reminders — use LIFE instead. " +
|
|
2129
2127
|
"This action provides the final grounded reply; do not pair it with a speculative REPLY action." +
|
|
2130
|
-
" DO NOT use this action when the user asks to 'help schedule'
|
|
2128
|
+
" DO NOT use this action when the user asks to 'help schedule', 'help me schedule', 'set up a meeting with', 'find a time with', 'find us a time', 'find a slot with', or otherwise wants to negotiate a meeting with a person or team WITHOUT naming a concrete date or time — that is SCHEDULING (subaction: start). Any time the request mentions a person/team AND no specific time, route it to SCHEDULING, not here. DO NOT use this action to 'propose', 'suggest', or 'offer' meeting time slots — that is PROPOSE_MEETING_TIMES. Use CALENDAR_ACTION only when the user specifies (or intends to specify) a concrete date/time for the event.",
|
|
2131
2129
|
descriptionCompressed: "Google Calendar via LifeOps: view schedule, search events, create events, query travel. Not for email or habits.",
|
|
2132
2130
|
suppressPostActionContinuation: true,
|
|
2133
2131
|
validate: async (runtime, message) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-channel-send.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/actions/cross-channel-send.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,MAAM,EAMP,MAAM,eAAe,CAAC;AAyBvB,eAAO,MAAM,2BAA2B,yIAY9B,CAAC;AACX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAwZnF,eAAO,MAAM,sBAAsB,EAAE,MAAM,GAAG;IAC5C,8BAA8B,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"cross-channel-send.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/actions/cross-channel-send.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,MAAM,EAMP,MAAM,eAAe,CAAC;AAyBvB,eAAO,MAAM,2BAA2B,yIAY9B,CAAC;AACX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAwZnF,eAAO,MAAM,sBAAsB,EAAE,MAAM,GAAG;IAC5C,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAuN1C,CAAC"}
|
|
@@ -405,6 +405,9 @@ export const crossChannelSendAction = {
|
|
|
405
405
|
"Use this for any 'post <msg> to <channel>', 'send <msg> on <platform>', " +
|
|
406
406
|
"or 'dm <person> on <platform>' request — the channel name in the sentence " +
|
|
407
407
|
"(discord, telegram, signal, etc.) is the strongest signal. " +
|
|
408
|
+
"Do NOT use this for 'broadcast/push/send <X> to all my devices' or " +
|
|
409
|
+
"'broadcast a reminder to my phone/desktop/watch' — device-targeted " +
|
|
410
|
+
"reminders belong to INTENT_SYNC, not CROSS_CHANNEL_SEND. " +
|
|
408
411
|
"Do NOT use SCHEDULING for channel-send requests even if the message " +
|
|
409
412
|
"mentions a meeting-like word (e.g. 'standup', 'sync'); SCHEDULING is for " +
|
|
410
413
|
"negotiating calendar proposals, not relaying chat messages.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/actions/health.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,MAAM,EAOP,MAAM,eAAe,CAAC;AAqOvB,eAAO,MAAM,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/actions/health.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,MAAM,EAOP,MAAM,eAAe,CAAC;AAqOvB,eAAO,MAAM,YAAY,EAAE,MAwP1B,CAAC"}
|
|
@@ -178,8 +178,29 @@ function formatSummary(summary) {
|
|
|
178
178
|
}
|
|
179
179
|
export const healthAction = {
|
|
180
180
|
name: "HEALTH",
|
|
181
|
-
similes: [
|
|
182
|
-
|
|
181
|
+
similes: [
|
|
182
|
+
"FITNESS",
|
|
183
|
+
"HEALTHKIT",
|
|
184
|
+
"GOOGLE_FIT",
|
|
185
|
+
"WELLNESS",
|
|
186
|
+
"SLEEP",
|
|
187
|
+
"SLEEP_DATA",
|
|
188
|
+
"SLEEP_STATS",
|
|
189
|
+
"STEPS",
|
|
190
|
+
"STEP_COUNT",
|
|
191
|
+
"HEART_RATE",
|
|
192
|
+
"WORKOUT",
|
|
193
|
+
"EXERCISE",
|
|
194
|
+
"CALORIES",
|
|
195
|
+
"ACTIVITY_METRICS",
|
|
196
|
+
],
|
|
197
|
+
description: "Query health and fitness telemetry from HealthKit or Google Fit — sleep " +
|
|
198
|
+
"(duration, quality, stages), steps, heart rate, workouts, calories, and " +
|
|
199
|
+
"other body/activity metrics. Subactions: today, trend, by_metric, status. " +
|
|
200
|
+
"Use this for questions like 'how did I sleep last night', 'how many steps " +
|
|
201
|
+
"today', 'what was my resting heart rate', 'show my sleep trend this week'. " +
|
|
202
|
+
"Do NOT route health-metric questions through LIFE (LIFE is for tasks/goals/" +
|
|
203
|
+
"habits lifecycle, not wearable/quantified-self data).",
|
|
183
204
|
validate: async (runtime, message) => hasLifeOpsAccess(runtime, message),
|
|
184
205
|
handler: async (runtime, message, state, options, callback) => {
|
|
185
206
|
if (!(await hasLifeOpsAccess(runtime, message))) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/app-core",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.211",
|
|
4
4
|
"description": "Shared application core for elizaOS white-label agent apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -477,7 +477,7 @@
|
|
|
477
477
|
"@capacitor/keyboard": "8.0.3",
|
|
478
478
|
"@capacitor/preferences": "^8.0.1",
|
|
479
479
|
"@clack/prompts": "^1.0.0",
|
|
480
|
-
"@elizaos/agent": "^2.0.0-alpha.
|
|
480
|
+
"@elizaos/agent": "^2.0.0-alpha.211",
|
|
481
481
|
"@elizaos/app-companion": "^0.0.0",
|
|
482
482
|
"@elizaos/app-elizamaker": "^0.0.0",
|
|
483
483
|
"@elizaos/app-lifeops": "^0.0.0",
|
|
@@ -486,9 +486,9 @@
|
|
|
486
486
|
"@elizaos/app-task-coordinator": "^0.0.0",
|
|
487
487
|
"@elizaos/app-training": "^0.0.1",
|
|
488
488
|
"@elizaos/app-vincent": "^0.0.0",
|
|
489
|
-
"@elizaos/core": "^2.0.0-alpha.
|
|
490
|
-
"@elizaos/shared": "^2.0.0-alpha.
|
|
491
|
-
"@elizaos/ui": "^2.0.0-alpha.
|
|
489
|
+
"@elizaos/core": "^2.0.0-alpha.211",
|
|
490
|
+
"@elizaos/shared": "^2.0.0-alpha.211",
|
|
491
|
+
"@elizaos/ui": "^2.0.0-alpha.211",
|
|
492
492
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
493
493
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
494
494
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
@@ -200,10 +200,12 @@ export declare class N8nSidecar {
|
|
|
200
200
|
/** Push a line into the bounded recent-output buffer and publish. */
|
|
201
201
|
private recordOutput;
|
|
202
202
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
203
|
+
* Block until the current child exits. Returns early if the child is null
|
|
204
|
+
* or if `stop()` has flipped `stopping`. No timeout — n8n is a long-running
|
|
205
|
+
* service, so timing out here would SIGKILL a healthy child and bounce the
|
|
206
|
+
* supervisor into a "child exited unexpectedly" → retry loop that ends in
|
|
207
|
+
* the `max retries exceeded` error state. Shutdown-side timeouts live in
|
|
208
|
+
* `killChild()` (SIGTERM with a 5s SIGKILL fallback).
|
|
207
209
|
*/
|
|
208
210
|
private waitForChildExitWithTimeout;
|
|
209
211
|
private killChild;
|
|
@@ -231,8 +233,15 @@ export declare class N8nSidecar {
|
|
|
231
233
|
private loadPersistedApiKey;
|
|
232
234
|
private persistApiKey;
|
|
233
235
|
/**
|
|
234
|
-
* Validate a cached API key by
|
|
235
|
-
* A 2xx means the key is still live; 401/403
|
|
236
|
+
* Validate a cached API key by calling the public REST API that accepts
|
|
237
|
+
* the X-N8N-API-KEY header. A 2xx means the key is still live; 401/403
|
|
238
|
+
* means it was revoked.
|
|
239
|
+
*
|
|
240
|
+
* Important: /rest/api-keys is the internal endpoint that requires the
|
|
241
|
+
* JWT cookie and will always 401 for an X-N8N-API-KEY regardless of
|
|
242
|
+
* whether the key itself is valid. Using /api/v1/workflows instead —
|
|
243
|
+
* the same endpoint the proxy hits, so "valid for probe" = "valid for
|
|
244
|
+
* real traffic".
|
|
236
245
|
*/
|
|
237
246
|
private validateApiKey;
|
|
238
247
|
/**
|
|
@@ -270,6 +279,14 @@ export declare class N8nSidecar {
|
|
|
270
279
|
private acquireOwnerCookie;
|
|
271
280
|
/** List scopes the current role may grant when creating an API key. */
|
|
272
281
|
private fetchApiKeyScopes;
|
|
282
|
+
/**
|
|
283
|
+
* Delete every api-key row with a matching label. Used to recover from the
|
|
284
|
+
* "already exists" case when a previous provisioning run created the label
|
|
285
|
+
* but lost the `rawApiKey` (n8n only returns the raw key at creation time,
|
|
286
|
+
* so a partially-persisted state wedges the next boot unless we can delete
|
|
287
|
+
* and re-create). Returns the number of rows deleted.
|
|
288
|
+
*/
|
|
289
|
+
private deleteApiKeysByLabel;
|
|
273
290
|
/** 48 bytes of base64url entropy — ~64 chars, far above n8n's min length. */
|
|
274
291
|
private generateRandomPassword;
|
|
275
292
|
/** Stop the sidecar. Idempotent. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"n8n-sidecar.d.ts","sourceRoot":"","sources":["../../../../../src/services/n8n-sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAqB,KAAK,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAY3E,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,+DAA+D;IAC/D,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,KAAK,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,OAAO,SAAS,CAAC;IACzB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,sDAAsD;IACtD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7D;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;IACxD;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACnD,oDAAoD;IACpD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAED,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"n8n-sidecar.d.ts","sourceRoot":"","sources":["../../../../../src/services/n8n-sidecar.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,8 +75,6 @@ const DEFAULT_MAX_RETRIES = 3;
|
|
|
75
75
|
const DEFAULT_BACKOFF_BASE_MS = 2_000;
|
|
76
76
|
/** Uptime after which a ready sidecar is considered healthy and retries reset. */
|
|
77
77
|
const RETRY_RESET_AFTER_MS = 5 * 60 * 1_000;
|
|
78
|
-
/** Hard cap on how long we wait for a child to exit during supervision. */
|
|
79
|
-
const CHILD_EXIT_WAIT_TIMEOUT_MS = 2 * 60 * 1_000;
|
|
80
78
|
/** Grace period between SIGTERM and SIGKILL when reaping an orphan. */
|
|
81
79
|
const ORPHAN_SIGTERM_GRACE_MS = 5_000;
|
|
82
80
|
/** Terminal statuses that mean "not running right now". */
|
|
@@ -431,7 +429,27 @@ export class N8nSidecar {
|
|
|
431
429
|
errorMessage: null,
|
|
432
430
|
retries: 0,
|
|
433
431
|
});
|
|
434
|
-
|
|
432
|
+
// Resolve when the supervisor first transitions to a terminal steady
|
|
433
|
+
// state ("ready" or "error") — not when the supervisor loop itself
|
|
434
|
+
// exits. The loop keeps running for the full lifetime of the child,
|
|
435
|
+
// so awaiting it here would block callers forever on a healthy sidecar.
|
|
436
|
+
// The supervisor continues to run in the background after this resolves;
|
|
437
|
+
// stop() is the canonical way to terminate it.
|
|
438
|
+
const supervisorPromise = this.runSupervisor();
|
|
439
|
+
await new Promise((resolve) => {
|
|
440
|
+
if (this.state.status === "ready" || this.state.status === "error") {
|
|
441
|
+
resolve();
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const unsubscribe = this.subscribe((state) => {
|
|
445
|
+
if (state.status === "ready" || state.status === "error") {
|
|
446
|
+
unsubscribe();
|
|
447
|
+
resolve();
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
// Don't leave an unhandled rejection if the supervisor later errors.
|
|
452
|
+
supervisorPromise.catch(() => undefined);
|
|
435
453
|
}
|
|
436
454
|
/**
|
|
437
455
|
* Supervisor loop: spawn → probe readiness → (on crash) exponential
|
|
@@ -522,6 +540,18 @@ export class N8nSidecar {
|
|
|
522
540
|
// tsyringe decorator handling for 1.100+ — see `binary?` docs above).
|
|
523
541
|
const env = {
|
|
524
542
|
...process.env,
|
|
543
|
+
// Force NODE_ENV=production for the child.
|
|
544
|
+
//
|
|
545
|
+
// Reason: when the parent process has NODE_ENV=test (vitest sets this
|
|
546
|
+
// by default, and CI pipelines set it for similar reasons), `npx`
|
|
547
|
+
// spawned under Bun silently exits with code 1 before producing any
|
|
548
|
+
// stdout/stderr. Setting NODE_ENV=test also causes `npm install` to
|
|
549
|
+
// skip devDependencies, which can leave n8n's dep graph incomplete
|
|
550
|
+
// when npx runs it through the npm cache. Overriding to `production`
|
|
551
|
+
// gives the child the environment n8n was actually tested against
|
|
552
|
+
// and unblocks the sidecar inside vitest's live-e2e suite. The parent
|
|
553
|
+
// process's NODE_ENV is untouched — this only shapes the child env.
|
|
554
|
+
NODE_ENV: "production",
|
|
525
555
|
N8N_PORT: String(port),
|
|
526
556
|
N8N_HOST: this.config.host,
|
|
527
557
|
N8N_PROTOCOL: "http",
|
|
@@ -533,6 +563,14 @@ export class N8nSidecar {
|
|
|
533
563
|
N8N_USER_FOLDER: this.config.stateDir,
|
|
534
564
|
DB_TYPE: "sqlite",
|
|
535
565
|
DB_SQLITE_DATABASE: path.join(this.config.stateDir, "database.sqlite"),
|
|
566
|
+
// Opt out of the Enterprise-Edition modules. n8n's module-registry
|
|
567
|
+
// tries to `require()` an `.ee` variant of each enabled module at
|
|
568
|
+
// boot; both `insights.ee` and `external-secrets.ee` only ship with
|
|
569
|
+
// the EE build and are missing from the public npm/bunx install, so
|
|
570
|
+
// the child crashes with "Failed to load module 'insights'" before
|
|
571
|
+
// the HTTP server even binds. Disabling both here means the sidecar
|
|
572
|
+
// boots cleanly on the OSS build.
|
|
573
|
+
N8N_DISABLED_MODULES: "insights,external-secrets",
|
|
536
574
|
};
|
|
537
575
|
const versioned = `n8n@${this.config.version}`;
|
|
538
576
|
// Arg style depends on the launcher:
|
|
@@ -545,6 +583,7 @@ export class N8nSidecar {
|
|
|
545
583
|
: binaryBase === "bunx"
|
|
546
584
|
? ["--", versioned, "start"]
|
|
547
585
|
: [versioned, "start"];
|
|
586
|
+
this.recordOutput(`[spawn] ${this.config.binary} ${launcherArgs.join(" ")} (port ${port}, stateDir ${this.config.stateDir}, NODE_ENV=${env.NODE_ENV ?? "(unset)"}, PATH len=${(env.PATH ?? "").length})`);
|
|
548
587
|
const child = this.deps.spawn(this.config.binary, launcherArgs, {
|
|
549
588
|
env,
|
|
550
589
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -582,7 +621,14 @@ export class N8nSidecar {
|
|
|
582
621
|
// can linger as <defunct> because Node only waitpid()'s when something
|
|
583
622
|
// is listening. This handler is unconditional — waitForChildExit attaches
|
|
584
623
|
// its own once('exit') for supervisor-level signalling.
|
|
585
|
-
|
|
624
|
+
//
|
|
625
|
+
// Use `close` instead of `exit` so the final chunks from stdout/stderr
|
|
626
|
+
// are flushed into recentOutput before we log the exit summary. Node
|
|
627
|
+
// emits `exit` as soon as the process terminates but `close` only fires
|
|
628
|
+
// after all stdio streams have drained — which is the ordering we want
|
|
629
|
+
// so the supervisor / UI see the *reason* for the exit, not just the
|
|
630
|
+
// bare exit code line.
|
|
631
|
+
child.on("close", (code, signal) => {
|
|
586
632
|
const summary = code !== null
|
|
587
633
|
? `exit code ${code}`
|
|
588
634
|
: signal !== null
|
|
@@ -606,10 +652,12 @@ export class N8nSidecar {
|
|
|
606
652
|
// is snapshotted on every setState() call and served by getState().
|
|
607
653
|
}
|
|
608
654
|
/**
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
655
|
+
* Block until the current child exits. Returns early if the child is null
|
|
656
|
+
* or if `stop()` has flipped `stopping`. No timeout — n8n is a long-running
|
|
657
|
+
* service, so timing out here would SIGKILL a healthy child and bounce the
|
|
658
|
+
* supervisor into a "child exited unexpectedly" → retry loop that ends in
|
|
659
|
+
* the `max retries exceeded` error state. Shutdown-side timeouts live in
|
|
660
|
+
* `killChild()` (SIGTERM with a 5s SIGKILL fallback).
|
|
613
661
|
*/
|
|
614
662
|
waitForChildExitWithTimeout() {
|
|
615
663
|
return new Promise((resolve) => {
|
|
@@ -618,29 +666,16 @@ export class N8nSidecar {
|
|
|
618
666
|
resolve();
|
|
619
667
|
return;
|
|
620
668
|
}
|
|
621
|
-
|
|
669
|
+
if (this.stopping) {
|
|
670
|
+
resolve();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
622
673
|
const settle = () => {
|
|
623
|
-
if (settled)
|
|
624
|
-
return;
|
|
625
|
-
settled = true;
|
|
626
674
|
child.removeListener("exit", onExit);
|
|
627
|
-
this.deps.clearTimer(timer);
|
|
628
675
|
resolve();
|
|
629
676
|
};
|
|
630
677
|
const onExit = () => settle();
|
|
631
678
|
child.once("exit", onExit);
|
|
632
|
-
const timer = this.deps.setTimer(() => {
|
|
633
|
-
if (settled)
|
|
634
|
-
return;
|
|
635
|
-
logger.warn(`[n8n-sidecar] child did not exit within ${CHILD_EXIT_WAIT_TIMEOUT_MS}ms; forcing kill`);
|
|
636
|
-
try {
|
|
637
|
-
child.kill("SIGKILL");
|
|
638
|
-
}
|
|
639
|
-
catch {
|
|
640
|
-
/* no-op */
|
|
641
|
-
}
|
|
642
|
-
settle();
|
|
643
|
-
}, CHILD_EXIT_WAIT_TIMEOUT_MS);
|
|
644
679
|
});
|
|
645
680
|
}
|
|
646
681
|
killChild() {
|
|
@@ -679,13 +714,15 @@ export class N8nSidecar {
|
|
|
679
714
|
while (Date.now() < deadline) {
|
|
680
715
|
if (this.stopping)
|
|
681
716
|
return false;
|
|
682
|
-
// If the child died mid-probe
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
//
|
|
686
|
-
//
|
|
717
|
+
// If the child died mid-probe AND left no zombie descendants, keep
|
|
718
|
+
// polling is pointless. We only fail fast on a NON-ZERO exit code —
|
|
719
|
+
// npx itself cleanly handoffs to n8n and its own exit code is 0 in
|
|
720
|
+
// some spawn topologies (bun's child_process.spawn in particular),
|
|
721
|
+
// so a 0 here is not a real failure.
|
|
687
722
|
const child = this.child;
|
|
688
|
-
if (child &&
|
|
723
|
+
if (child &&
|
|
724
|
+
typeof child.exitCode === "number" &&
|
|
725
|
+
child.exitCode !== 0) {
|
|
689
726
|
throw new Error(`n8n child exited with code ${child.exitCode} before readiness probe succeeded`);
|
|
690
727
|
}
|
|
691
728
|
try {
|
|
@@ -754,12 +791,19 @@ export class N8nSidecar {
|
|
|
754
791
|
}
|
|
755
792
|
}
|
|
756
793
|
/**
|
|
757
|
-
* Validate a cached API key by
|
|
758
|
-
* A 2xx means the key is still live; 401/403
|
|
794
|
+
* Validate a cached API key by calling the public REST API that accepts
|
|
795
|
+
* the X-N8N-API-KEY header. A 2xx means the key is still live; 401/403
|
|
796
|
+
* means it was revoked.
|
|
797
|
+
*
|
|
798
|
+
* Important: /rest/api-keys is the internal endpoint that requires the
|
|
799
|
+
* JWT cookie and will always 401 for an X-N8N-API-KEY regardless of
|
|
800
|
+
* whether the key itself is valid. Using /api/v1/workflows instead —
|
|
801
|
+
* the same endpoint the proxy hits, so "valid for probe" = "valid for
|
|
802
|
+
* real traffic".
|
|
759
803
|
*/
|
|
760
804
|
async validateApiKey(host, key) {
|
|
761
805
|
try {
|
|
762
|
-
const res = await this.deps.fetch(`${host}/
|
|
806
|
+
const res = await this.deps.fetch(`${host}/api/v1/workflows?limit=1`, {
|
|
763
807
|
method: "GET",
|
|
764
808
|
headers: { "X-N8N-API-KEY": key },
|
|
765
809
|
signal: AbortSignal.timeout(5_000),
|
|
@@ -811,23 +855,33 @@ export class N8nSidecar {
|
|
|
811
855
|
log("/rest/api-keys/scopes returned no scopes");
|
|
812
856
|
return null;
|
|
813
857
|
}
|
|
814
|
-
const
|
|
858
|
+
const label = "milady-sidecar";
|
|
859
|
+
const createKey = async () => this.deps.fetch(`${host}/rest/api-keys`, {
|
|
815
860
|
method: "POST",
|
|
816
|
-
headers: {
|
|
817
|
-
|
|
818
|
-
cookie,
|
|
819
|
-
},
|
|
820
|
-
body: JSON.stringify({
|
|
821
|
-
label: "milady-sidecar",
|
|
822
|
-
scopes,
|
|
823
|
-
expiresAt: null,
|
|
824
|
-
}),
|
|
861
|
+
headers: { "content-type": "application/json", cookie },
|
|
862
|
+
body: JSON.stringify({ label, scopes, expiresAt: null }),
|
|
825
863
|
signal: AbortSignal.timeout(5_000),
|
|
826
864
|
});
|
|
865
|
+
let res = await createKey();
|
|
827
866
|
if (!res.ok) {
|
|
867
|
+
// 500 "There is already an entry with this name" means a prior
|
|
868
|
+
// provisioning run created the label but we lost the rawApiKey
|
|
869
|
+
// (n8n only returns it at creation time). Drop the stale row and
|
|
870
|
+
// re-create so the caller gets a usable key instead of null.
|
|
828
871
|
const bodyText = await res.text().catch(() => "");
|
|
829
|
-
|
|
830
|
-
|
|
872
|
+
if (res.status === 500 &&
|
|
873
|
+
/already\s+an?\s+entry\s+with\s+this\s+name/i.test(bodyText)) {
|
|
874
|
+
log("api-key label already exists in n8n — deleting and re-creating");
|
|
875
|
+
const deleted = await this.deleteApiKeysByLabel(host, cookie, label);
|
|
876
|
+
if (deleted > 0) {
|
|
877
|
+
res = await createKey();
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (!res.ok) {
|
|
881
|
+
const finalBody = bodyText || (await res.text().catch(() => ""));
|
|
882
|
+
log(`api-key create failed: ${res.status} ${res.statusText}${finalBody ? ` — ${finalBody.slice(0, 200)}` : ""}`);
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
831
885
|
}
|
|
832
886
|
const body = (await res.json());
|
|
833
887
|
const key = body.data?.rawApiKey ??
|
|
@@ -967,6 +1021,42 @@ export class N8nSidecar {
|
|
|
967
1021
|
return null;
|
|
968
1022
|
}
|
|
969
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Delete every api-key row with a matching label. Used to recover from the
|
|
1026
|
+
* "already exists" case when a previous provisioning run created the label
|
|
1027
|
+
* but lost the `rawApiKey` (n8n only returns the raw key at creation time,
|
|
1028
|
+
* so a partially-persisted state wedges the next boot unless we can delete
|
|
1029
|
+
* and re-create). Returns the number of rows deleted.
|
|
1030
|
+
*/
|
|
1031
|
+
async deleteApiKeysByLabel(host, cookie, label) {
|
|
1032
|
+
try {
|
|
1033
|
+
const listRes = await this.deps.fetch(`${host}/rest/api-keys`, {
|
|
1034
|
+
method: "GET",
|
|
1035
|
+
headers: { cookie },
|
|
1036
|
+
signal: AbortSignal.timeout(5_000),
|
|
1037
|
+
});
|
|
1038
|
+
if (!listRes.ok)
|
|
1039
|
+
return 0;
|
|
1040
|
+
const body = (await listRes.json());
|
|
1041
|
+
const matches = (body.data ?? []).filter((row) => typeof row.id === "string" &&
|
|
1042
|
+
typeof row.label === "string" &&
|
|
1043
|
+
row.label === label);
|
|
1044
|
+
let deleted = 0;
|
|
1045
|
+
for (const row of matches) {
|
|
1046
|
+
const delRes = await this.deps.fetch(`${host}/rest/api-keys/${encodeURIComponent(row.id)}`, {
|
|
1047
|
+
method: "DELETE",
|
|
1048
|
+
headers: { cookie },
|
|
1049
|
+
signal: AbortSignal.timeout(5_000),
|
|
1050
|
+
});
|
|
1051
|
+
if (delRes.ok)
|
|
1052
|
+
deleted += 1;
|
|
1053
|
+
}
|
|
1054
|
+
return deleted;
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
return 0;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
970
1060
|
/** 48 bytes of base64url entropy — ~64 chars, far above n8n's min length. */
|
|
971
1061
|
generateRandomPassword() {
|
|
972
1062
|
// crypto is Node 20+ global; fallback to Math.random if unavailable is
|