@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.
@@ -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;CAoqC1C,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' / 'set up' / 'find a time for' a meeting with a person or team without a specific time — that is SCHEDULING (subaction start). 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.",
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;CAoN1C,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,MAmO1B,CAAC"}
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: ["FITNESS", "HEALTHKIT", "GOOGLE_FIT", "WELLNESS"],
182
- description: "Query health and fitness data from HealthKit or Google Fit. Subactions: today, trend, by_metric, status.",
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.210",
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.210",
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.210",
490
- "@elizaos/shared": "^2.0.0-alpha.210",
491
- "@elizaos/ui": "^2.0.0-alpha.210",
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
- * Wait for the current child to exit. Returns early if the child is null.
204
- * Capped at CHILD_EXIT_WAIT_TIMEOUT_MS beyond that we assume the child
205
- * is hung, send SIGKILL, and treat it as exited so the supervisor can
206
- * make forward progress instead of blocking forever.
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 listing keys through n8n's public API.
235
- * A 2xx means the key is still live; 401/403 means it was revoked.
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":"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"}
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;AAoOjD,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,KAAK,CAQX;IAKF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IAChD,sFAAsF;IACtF,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,KAAK,CAA6B;IAC1C,8EAA8E;IAC9E,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,iBAAiB,CAAS;IAClC;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAiB;gBAE5B,MAAM,GAAE,gBAAqB,EAAE,IAAI,GAAE,cAAmB;IA2BpE,QAAQ,IAAI,eAAe;IAI3B;;;OAGG;IACH,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B;;;;;;;;;OASG;IACH,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAiC1C;;;OAGG;IACH,OAAO,CAAC,cAAc;IAiBtB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,IAAI;IAanC,OAAO,CAAC,IAAI;IAgBZ,OAAO,CAAC,QAAQ;IAKhB;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0C5B;;;OAGG;YACW,aAAa;YA2Fb,UAAU;IAmHxB,qEAAqE;IACrE,OAAO,CAAC,YAAY;IAapB;;;;;;;OAOG;IACH,OAAO,CAAC,2BAA2B;IAoBnC,OAAO,CAAC,SAAS;IAwBjB;;;;;OAKG;YACW,cAAc;IAsC5B;;;;;;;;;;;OAWG;YACW,YAAY;IAc1B,OAAO,CAAC,UAAU;YAIJ,mBAAmB;YAOnB,aAAa;IAc3B;;;;;;;;;;OAUG;YACW,cAAc;IAa5B;;;;;;;;;;;;;;;;;;;OAmBG;YACW,eAAe;IAkF7B;;;;OAIG;YACW,sBAAsB;IAuDpC;;;;OAIG;YACW,kBAAkB;IAsEhC,uEAAuE;YACzD,iBAAiB;IAkB/B;;;;;;OAMG;YACW,oBAAoB;IAuClC,6EAA6E;IAC7E,OAAO,CAAC,sBAAsB;IAQ9B,oCAAoC;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,6EAA6E;IAC7E,SAAS,IAAI,OAAO;IAMpB,OAAO,CAAC,WAAW;YAIL,WAAW;YAOX,YAAY;YAYZ,aAAa;IAI3B;;;;;;;;;OASG;YACW,UAAU;IAmCxB,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,qBAAqB;CAM9B;AAmBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,gBAAqB,GAAG,UAAU,CAYvE;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,GAAE,gBAAqB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAKrB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,IAAI,CAElD;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAgBvD"}
@@ -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
- await this.runSupervisor();
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
- child.on("exit", (code, signal) => {
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
- * Wait for the current child to exit. Returns early if the child is null.
610
- * Capped at CHILD_EXIT_WAIT_TIMEOUT_MS beyond that we assume the child
611
- * is hung, send SIGKILL, and treat it as exited so the supervisor can
612
- * make forward progress instead of blocking forever.
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
- let settled = false;
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, keep polling is pointless the port
683
- // will never open. Fail fast so the supervisor surfaces the error and
684
- // kicks off the next retry (with captured stderr in recentOutput).
685
- // Uses `typeof === "number"` rather than `!== null` so this tolerates
686
- // test fakes whose exitCode is undefined while still running.
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 && typeof child.exitCode === "number") {
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 listing keys through n8n's public API.
758
- * A 2xx means the key is still live; 401/403 means it was revoked.
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}/rest/api-keys`, {
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 res = await this.deps.fetch(`${host}/rest/api-keys`, {
858
+ const label = "milady-sidecar";
859
+ const createKey = async () => this.deps.fetch(`${host}/rest/api-keys`, {
815
860
  method: "POST",
816
- headers: {
817
- "content-type": "application/json",
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
- log(`api-key create failed: ${res.status} ${res.statusText}${bodyText ? ` — ${bodyText.slice(0, 200)}` : ""}`);
830
- return null;
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