tide-commander 1.83.0 → 1.84.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/assets/{BossLogsModal-Bgg8MqNZ.js → BossLogsModal-C8zRzuFy.js} +1 -1
  2. package/dist/assets/{BossSpawnModal-SQNEPxH1.js → BossSpawnModal-CeA6mhN7.js} +1 -1
  3. package/dist/assets/{ControlsModal-ImEPi76l.js → ControlsModal-CP6NB4EV.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-Cs3jjehG.js → DockerLogsModal-DEgQJ1ZA.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-Cg3lq_Cv.js → EmbeddedEditor-CKvksEzA.js} +1 -1
  6. package/dist/assets/{GmailOAuthSetup-1Gh5-yZt.js → GmailOAuthSetup-DdRol00s.js} +1 -1
  7. package/dist/assets/{GoogleOAuthSetup-DmXPQyOR.js → GoogleOAuthSetup-CGV5agAn.js} +1 -1
  8. package/dist/assets/{IframeModal-BEItm7bf.js → IframeModal-hQ9wpT90.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-epI8eaV_.js → IntegrationsPanel-0DPPoL2y.js} +2 -2
  10. package/dist/assets/{LogViewerModal-B4ryb52t.js → LogViewerModal-DGVR_WfJ.js} +1 -1
  11. package/dist/assets/{MonitoringModal-CzUjqD5Y.js → MonitoringModal-FzeLMdzo.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-Ex9G2Mly.js → PM2LogsModal-UqeZ-Z-C.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-DIsamY35.js → RestoreArchivedAreaModal-Dk3oNX5d.js} +1 -1
  14. package/dist/assets/{Scene2DCanvas-B5VdT-R2.js → Scene2DCanvas-BLYWui_s.js} +1 -1
  15. package/dist/assets/{SceneManager-CgkTa3Uf.js → SceneManager-Bo_ImHGE.js} +1 -1
  16. package/dist/assets/{SkillsPanel-DXxAlZbz.js → SkillsPanel-D448Eb0f.js} +1 -1
  17. package/dist/assets/{SpawnModal-_1Af5rTQ.js → SpawnModal-BMlp3ZUn.js} +1 -1
  18. package/dist/assets/{SubordinateAssignmentModal-CZ1p4KxH.js → SubordinateAssignmentModal-WdKcjuF8.js} +1 -1
  19. package/dist/assets/{TriggerManagerPanel-DnOvAQq6.js → TriggerManagerPanel-DAQz-77a.js} +1 -1
  20. package/dist/assets/{WorkflowEditorPanel-Dr896thl.js → WorkflowEditorPanel-j4Ios7kX.js} +1 -1
  21. package/dist/assets/index-BR9_mEVJ.css +1 -0
  22. package/dist/assets/index-BdGz_GAe.css +1 -0
  23. package/dist/assets/index-C8qENpYZ.js +1 -0
  24. package/dist/assets/{index-CkhtIUBY.js → index-CDxPahrR.js} +1 -1
  25. package/dist/assets/index-CQ-mkqhz.js +1 -0
  26. package/dist/assets/{index-DVqDWfDB.js → index-Cbf25fjE.js} +2 -2
  27. package/dist/assets/{index-DvRrAd9Y.js → index-Ce_6e5Xj.js} +1 -1
  28. package/dist/assets/index-CkoqkFEg.js +2 -0
  29. package/dist/assets/{index-C70mj1bd.js → index-RyuR5SvS.js} +1 -1
  30. package/dist/assets/{index-CgPa_6TH.js → index-blYiJWLh.js} +1 -1
  31. package/dist/assets/{index-OYmdvKdM.js → index-voJf2Goi.js} +3 -3
  32. package/dist/assets/main-CFu_SP_0.css +1 -0
  33. package/dist/assets/{main-CHfGSVnw.js → main-CPwivjwQ.js} +87 -87
  34. package/dist/assets/{web-rv0WAVTk.js → web-DrIhSNal.js} +1 -1
  35. package/dist/assets/{web-D1aYcbd6.js → web-DsEKGJbn.js} +1 -1
  36. package/dist/index.html +3 -3
  37. package/dist/locales/en/common.json +5 -1
  38. package/dist/locales/en/config.json +61 -0
  39. package/dist/src/packages/server/integrations/whatsapp/whatsapp-notification-publisher.js +48 -0
  40. package/dist/src/packages/server/integrations/whatsapp/whatsapp-routes.js +43 -0
  41. package/dist/src/packages/server/routes/notifications.js +12 -0
  42. package/dist/src/packages/server/services/agent-service.js +10 -0
  43. package/dist/src/packages/server/services/index.js +1 -0
  44. package/dist/src/packages/server/services/whatsapp-notification-config-service.js +120 -0
  45. package/dist/src/packages/server/websocket/handlers/notification-handler.js +12 -0
  46. package/package.json +1 -1
  47. package/dist/assets/index-B0Q9DUzT.css +0 -1
  48. package/dist/assets/index-CFb36m7S.css +0 -1
  49. package/dist/assets/index-CoUtzDXD.js +0 -2
  50. package/dist/assets/index-DCHHa29_.js +0 -1
  51. package/dist/assets/index-ave_XgRW.js +0 -1
  52. package/dist/assets/main-DmmvYoEs.css +0 -1
@@ -1 +1 @@
1
- import{cg as s}from"./main-CHfGSVnw.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
1
+ import{cg as s}from"./main-CPwivjwQ.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
@@ -1 +1 @@
1
- import{cg as a}from"./main-CHfGSVnw.js";import{ImpactStyle as i,NotificationType as r}from"./index-DVqDWfDB.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
1
+ import{cg as a}from"./main-CPwivjwQ.js";import{ImpactStyle as i,NotificationType as r}from"./index-Cbf25fjE.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
package/dist/index.html CHANGED
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
6
6
  <!-- Disable bfcache to prevent memory leaks on page reload (especially in Brave) -->
7
7
  <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
8
8
  <meta http-equiv="Pragma" content="no-cache" />
@@ -22,10 +22,10 @@
22
22
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
23
23
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
24
24
  <title>Tide Commander</title>
25
- <script type="module" crossorigin src="/assets/main-CHfGSVnw.js"></script>
25
+ <script type="module" crossorigin src="/assets/main-CPwivjwQ.js"></script>
26
26
  <link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
28
- <link rel="stylesheet" crossorigin href="/assets/main-DmmvYoEs.css">
28
+ <link rel="stylesheet" crossorigin href="/assets/main-CFu_SP_0.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="app"></div>
@@ -338,6 +338,10 @@
338
338
  "tracking": "Tracking",
339
339
  "spawn": "Spawn",
340
340
  "commander": "Commander",
341
- "settings": "Settings"
341
+ "settings": "Settings",
342
+ "agents": "Agents",
343
+ "inspector": "Inspector",
344
+ "close": "Close",
345
+ "closeAgent": "Close agent"
342
346
  }
343
347
  }
@@ -14,6 +14,7 @@
14
14
  "secrets": "Secrets",
15
15
  "systemPrompt": "System Prompt",
16
16
  "whatsapp": "WhatsApp Integration",
17
+ "whatsappNotifications": "WhatsApp Notifications",
17
18
  "data": "Data",
18
19
  "experimental": "Experimental",
19
20
  "about": "About",
@@ -391,6 +392,66 @@
391
392
  "testSendFailed": "Failed to send the test message."
392
393
  }
393
394
  },
395
+ "whatsappNotifications": {
396
+ "title": "WhatsApp Notifications",
397
+ "description": "Choose which agent events get forwarded to WhatsApp. The recipient JID receives a message whenever an enabled event fires. Requires the WhatsApp integration to be enabled and a default session paired.",
398
+ "editButton": "Edit Notification Filters",
399
+ "loading": "Loading WhatsApp notification settings...",
400
+ "save": "Save",
401
+ "reset": "Reset Changes",
402
+ "resetToDefaults": "Reset to defaults",
403
+ "resetDone": "Notification filters reset to defaults.",
404
+ "saved": "Notification filters saved.",
405
+ "close": "Close",
406
+ "unsavedTitle": "Unsaved Changes",
407
+ "unsavedMessage": "You have unsaved notification filter changes. Close anyway?",
408
+ "closeAnyway": "Close anyway",
409
+ "keepEditing": "Keep editing",
410
+ "confirmReset": "Reset all toggles to ON and clear the recipient?",
411
+ "sections": {
412
+ "recipient": "Recipient",
413
+ "events": "Event Types"
414
+ },
415
+ "recipientLabel": "Recipient JID or phone number",
416
+ "recipientPlaceholder": "e.g. 5215512345678 or 5215512345678@s.whatsapp.net",
417
+ "recipientHint": "Where notification messages are sent. Leave empty to disable all WhatsApp notifications.",
418
+ "eventsHint": "Each toggle controls one event type. When OFF, that event is skipped silently.",
419
+ "events": {
420
+ "messages": {
421
+ "title": "Agent messages",
422
+ "help": "Generic agent notifications sent via /api/notify or the in-app notification skill."
423
+ },
424
+ "statusChanges": {
425
+ "title": "Status changes",
426
+ "help": "Whenever an agent transitions between idle, working, or other states."
427
+ },
428
+ "taskComplete": {
429
+ "title": "Task complete",
430
+ "help": "Notifications whose title indicates a task finished or completed."
431
+ },
432
+ "errors": {
433
+ "title": "Errors / blocked",
434
+ "help": "Notifications whose title flags an error, failure, or blocker."
435
+ },
436
+ "planReady": {
437
+ "title": "Plan ready",
438
+ "help": "Notifications emitted when an agent's plan is ready for review."
439
+ },
440
+ "agentSpawned": {
441
+ "title": "Agent spawned",
442
+ "help": "When a new agent is created."
443
+ },
444
+ "agentStopped": {
445
+ "title": "Agent stopped",
446
+ "help": "When an agent is deleted/stopped."
447
+ }
448
+ },
449
+ "errors": {
450
+ "loadFailed": "Failed to load WhatsApp notification settings.",
451
+ "saveFailed": "Failed to save WhatsApp notification settings.",
452
+ "resetFailed": "Failed to reset WhatsApp notification settings."
453
+ }
454
+ },
394
455
  "data": {
395
456
  "title": "Data Management",
396
457
  "exportData": "Export",
@@ -0,0 +1,48 @@
1
+ /**
2
+ * WhatsApp Notification Publisher
3
+ * Forwards selected agent events to WhatsApp via the local Baileys server.
4
+ *
5
+ * Each call site passes an event type; the publisher consults the per-type
6
+ * filter config (see whatsapp-notification-config-service) and skips silently
7
+ * when the toggle is off, when the WhatsApp integration is disabled, when the
8
+ * API key is missing, or when no recipient JID is configured.
9
+ */
10
+ import { WhatsAppClient } from './whatsapp-client.js';
11
+ import { loadConfig as loadWhatsAppConfig, WHATSAPP_API_KEY_SECRET } from './whatsapp-config.js';
12
+ import { getConfig as getNotificationConfig, } from '../../services/whatsapp-notification-config-service.js';
13
+ import { secretsService } from '../../services/secrets-service.js';
14
+ import { createLogger } from '../../utils/logger.js';
15
+ const log = createLogger('WhatsAppNotificationPublisher');
16
+ export async function publishNotification(event, title, message) {
17
+ const notifConfig = getNotificationConfig();
18
+ if (notifConfig.filter[event] === false) {
19
+ return { sent: false, reason: `event "${event}" disabled by filter` };
20
+ }
21
+ const recipient = notifConfig.recipient.trim();
22
+ if (!recipient) {
23
+ return { sent: false, reason: 'no recipient configured' };
24
+ }
25
+ const waConfig = loadWhatsAppConfig();
26
+ if (!waConfig.enabled) {
27
+ return { sent: false, reason: 'WhatsApp integration disabled' };
28
+ }
29
+ const apiKey = secretsService.getSecretByKey(WHATSAPP_API_KEY_SECRET)?.value;
30
+ if (!apiKey) {
31
+ return { sent: false, reason: 'WhatsApp API key not configured' };
32
+ }
33
+ const sessionId = waConfig.defaultSessionId;
34
+ if (!sessionId) {
35
+ return { sent: false, reason: 'no defaultSessionId configured' };
36
+ }
37
+ const body = title ? `*${title}*\n${message}` : message;
38
+ try {
39
+ const client = new WhatsAppClient(waConfig.baseUrl, apiKey);
40
+ await client.sendMessage(sessionId, recipient, body);
41
+ return { sent: true };
42
+ }
43
+ catch (err) {
44
+ const detail = err instanceof Error ? err.message : String(err);
45
+ log.warn(`WhatsApp publish failed (${event}): ${detail}`);
46
+ return { sent: false, reason: detail };
47
+ }
48
+ }
@@ -10,6 +10,7 @@ import { createLogger } from '../../utils/logger.js';
10
10
  import { WhatsAppClient } from './whatsapp-client.js';
11
11
  import { loadConfig, updateConfig, WHATSAPP_API_KEY_SECRET, } from './whatsapp-config.js';
12
12
  import { syncBridge } from './index.js';
13
+ import { getConfig as getNotificationConfig, updateConfig as updateNotificationConfig, clearConfig as clearNotificationConfig, getDefaultConfig as getDefaultNotificationConfig, WHATSAPP_NOTIFICATION_EVENT_TYPES, } from '../../services/whatsapp-notification-config-service.js';
13
14
  const log = createLogger('WhatsAppRoutes');
14
15
  /** Build the router. Closes over the integration context for secret access. */
15
16
  export function createWhatsAppRoutes(ctx) {
@@ -268,6 +269,48 @@ export function createWhatsAppRoutes(ctx) {
268
269
  res.status(502).json({ error: err instanceof Error ? err.message : String(err) });
269
270
  }
270
271
  });
272
+ // ─── GET /notification-config — Read per-event-type WhatsApp notification toggles ───
273
+ router.get('/notification-config', (_req, res) => {
274
+ res.json(getNotificationConfig());
275
+ });
276
+ // ─── PATCH /notification-config — Update toggles and/or recipient JID ───
277
+ router.patch('/notification-config', (req, res) => {
278
+ const body = (req.body ?? {});
279
+ const update = {};
280
+ if (body.filter && typeof body.filter === 'object') {
281
+ const inputFilter = body.filter;
282
+ const cleaned = {};
283
+ for (const key of WHATSAPP_NOTIFICATION_EVENT_TYPES) {
284
+ const v = inputFilter[key];
285
+ if (typeof v === 'boolean') {
286
+ cleaned[key] = v;
287
+ }
288
+ }
289
+ update.filter = cleaned;
290
+ }
291
+ if (typeof body.recipient === 'string') {
292
+ update.recipient = body.recipient;
293
+ }
294
+ try {
295
+ const next = updateNotificationConfig(update);
296
+ res.json(next);
297
+ }
298
+ catch (err) {
299
+ log.error(`WhatsApp notification-config update error: ${err}`);
300
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
301
+ }
302
+ });
303
+ // ─── DELETE /notification-config — Reset to defaults (all toggles ON, recipient cleared) ───
304
+ router.delete('/notification-config', (_req, res) => {
305
+ try {
306
+ clearNotificationConfig();
307
+ res.json(getDefaultNotificationConfig());
308
+ }
309
+ catch (err) {
310
+ log.error(`WhatsApp notification-config clear error: ${err}`);
311
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
312
+ }
313
+ });
271
314
  return router;
272
315
  }
273
316
  export default createWhatsAppRoutes;
@@ -9,6 +9,7 @@
9
9
  import { Router } from 'express';
10
10
  import { agentService } from '../services/index.js';
11
11
  import { createLogger, generateId } from '../utils/index.js';
12
+ import { publishNotification } from '../integrations/whatsapp/whatsapp-notification-publisher.js';
12
13
  const log = createLogger('Notifications');
13
14
  const router = Router();
14
15
  // Store for broadcasting notifications via WebSocket
@@ -74,6 +75,7 @@ router.post('/', (req, res) => {
74
75
  else {
75
76
  log.warn('Broadcast function not set - notification not sent to clients');
76
77
  }
78
+ void publishNotification(classifyNotificationEventType(title), `${agent.name}: ${title}`, message).catch((err) => log.warn(`WhatsApp publish skipped: ${err}`));
77
79
  res.status(200).json({
78
80
  success: true,
79
81
  notification
@@ -84,4 +86,14 @@ router.post('/', (req, res) => {
84
86
  res.status(500).json({ error: err.message });
85
87
  }
86
88
  });
89
+ function classifyNotificationEventType(title) {
90
+ const t = title.toLowerCase();
91
+ if (t.includes('plan ready') || t.includes('plan'))
92
+ return 'planReady';
93
+ if (t.includes('error') || t.includes('failed') || t.includes('blocked'))
94
+ return 'errors';
95
+ if (t.includes('task complete') || t.includes('completed') || t.includes('done'))
96
+ return 'taskComplete';
97
+ return 'messages';
98
+ }
87
99
  export default router;
@@ -10,6 +10,7 @@ import { loadAgents, saveAgents, saveAgentsAsync, getDataDir, loadAreas, saveAre
10
10
  import { listSessions, getSessionSummary, getProjectDir, loadSession, loadToolHistory, searchSession, } from '../claude/session-loader.js';
11
11
  import { loadSubagentHistory } from '../claude/subagent-history-loader.js';
12
12
  import { logger, generateId } from '../utils/index.js';
13
+ import { publishNotification } from '../integrations/whatsapp/whatsapp-notification-publisher.js';
13
14
  const log = logger.agent;
14
15
  const VALID_CLAUDE_MODELS = new Set(Object.keys(CLAUDE_MODEL_METADATA));
15
16
  const DEFAULT_CLAUDE_CONTEXT_LIMIT = 200000;
@@ -342,6 +343,7 @@ export async function createAgent(name, agentClass, cwd, position, sessionId, us
342
343
  reconcileAgentAreaAssignment(id, { x: agent.position.x, z: agent.position.z });
343
344
  emit('created', agent);
344
345
  log.log(' Event emitted: created');
346
+ void publishNotification('agentSpawned', `${agent.name}: spawned`, `Class: ${agent.class ?? 'unknown'}, cwd: ${agent.cwd}`).catch(() => { });
345
347
  return agent;
346
348
  }
347
349
  /**
@@ -413,6 +415,8 @@ export function updateAgent(id, updates, updateActivity = true) {
413
415
  return null;
414
416
  const normalizedUpdates = { ...updates };
415
417
  const sessionIdBefore = agent.sessionId;
418
+ const statusBefore = agent.status;
419
+ const agentNameBefore = agent.name;
416
420
  const hasSessionIdInUpdates = 'sessionId' in normalizedUpdates;
417
421
  // Track pending property updates for notification on next command
418
422
  // (these are changes that affect behavior but don't require restart)
@@ -475,12 +479,17 @@ export function updateAgent(id, updates, updateActivity = true) {
475
479
  log.warn(`🔑 [SESSION CHANGE] Agent ${agent.name} (${id}): sessionId changed from "${sessionIdBefore}" to "${agent.sessionId}". Updates had sessionId: ${hasSessionIdInUpdates}, updates keys: ${Object.keys(normalizedUpdates).join(', ')}`);
476
480
  }
477
481
  emit('updated', agent);
482
+ if (statusBefore !== agent.status) {
483
+ void publishNotification('statusChanges', `${agentNameBefore}: status changed`, `${statusBefore ?? 'unknown'} → ${agent.status ?? 'unknown'}`).catch(() => { });
484
+ }
478
485
  return agent;
479
486
  }
480
487
  export function deleteAgent(id) {
481
488
  const agent = agents.get(id);
482
489
  if (!agent)
483
490
  return false;
491
+ const deletedAgentName = agent.name;
492
+ const deletedAgentClass = agent.class;
484
493
  agents.delete(id);
485
494
  persistAgents();
486
495
  // Clean up area assignments for this agent
@@ -511,6 +520,7 @@ export function deleteAgent(id) {
511
520
  }
512
521
  });
513
522
  emit('deleted', id);
523
+ void publishNotification('agentStopped', `${deletedAgentName}: stopped`, `Class: ${deletedAgentClass ?? 'unknown'}`).catch(() => { });
514
524
  return true;
515
525
  }
516
526
  // ============================================================================
@@ -20,3 +20,4 @@ export * as eventRetentionService from './event-retention-service.js';
20
20
  export * as triggerService from './trigger-service.js';
21
21
  export * as workflowService from './workflow-service.js';
22
22
  export * as workflowChatService from './workflow-chat-service.js';
23
+ export * as whatsappNotificationConfigService from './whatsapp-notification-config-service.js';
@@ -0,0 +1,120 @@
1
+ /**
2
+ * WhatsApp Notification Config Service
3
+ * Manages per-event-type filters for the WhatsApp notification publisher.
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import * as os from 'os';
8
+ import { createLogger } from '../utils/logger.js';
9
+ const log = createLogger('WhatsAppNotificationConfig');
10
+ const DATA_DIR = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'tide-commander');
11
+ const CONFIG_FILE = path.join(DATA_DIR, 'whatsapp-notifications.json');
12
+ export const WHATSAPP_NOTIFICATION_EVENT_TYPES = [
13
+ 'messages',
14
+ 'statusChanges',
15
+ 'taskComplete',
16
+ 'errors',
17
+ 'planReady',
18
+ 'agentSpawned',
19
+ 'agentStopped',
20
+ ];
21
+ const DEFAULT_FILTER = {
22
+ messages: true,
23
+ statusChanges: true,
24
+ taskComplete: true,
25
+ errors: true,
26
+ planReady: true,
27
+ agentSpawned: true,
28
+ agentStopped: true,
29
+ };
30
+ export function getDefaultConfig() {
31
+ return {
32
+ filter: { ...DEFAULT_FILTER },
33
+ recipient: '',
34
+ updatedAt: 0,
35
+ version: '1.0',
36
+ };
37
+ }
38
+ function ensureDataDir() {
39
+ if (!fs.existsSync(DATA_DIR)) {
40
+ fs.mkdirSync(DATA_DIR, { recursive: true });
41
+ }
42
+ }
43
+ function normalizeFilter(input) {
44
+ const next = { ...DEFAULT_FILTER };
45
+ if (!input || typeof input !== 'object')
46
+ return next;
47
+ const obj = input;
48
+ for (const key of WHATSAPP_NOTIFICATION_EVENT_TYPES) {
49
+ const v = obj[key];
50
+ if (typeof v === 'boolean')
51
+ next[key] = v;
52
+ }
53
+ return next;
54
+ }
55
+ export function getConfig() {
56
+ ensureDataDir();
57
+ try {
58
+ if (fs.existsSync(CONFIG_FILE)) {
59
+ const raw = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
60
+ return {
61
+ filter: normalizeFilter(raw?.filter),
62
+ recipient: typeof raw?.recipient === 'string' ? raw.recipient : '',
63
+ updatedAt: typeof raw?.updatedAt === 'number' ? raw.updatedAt : 0,
64
+ version: typeof raw?.version === 'string' ? raw.version : '1.0',
65
+ };
66
+ }
67
+ }
68
+ catch (error) {
69
+ const msg = error instanceof Error ? error.message : String(error);
70
+ log.error(`Failed to load WhatsApp notification config: ${msg}`);
71
+ }
72
+ return getDefaultConfig();
73
+ }
74
+ export function updateConfig(input) {
75
+ ensureDataDir();
76
+ const current = getConfig();
77
+ const nextFilter = { ...current.filter };
78
+ if (input.filter && typeof input.filter === 'object') {
79
+ for (const key of WHATSAPP_NOTIFICATION_EVENT_TYPES) {
80
+ const v = input.filter[key];
81
+ if (typeof v === 'boolean')
82
+ nextFilter[key] = v;
83
+ }
84
+ }
85
+ const next = {
86
+ filter: nextFilter,
87
+ recipient: typeof input.recipient === 'string' ? input.recipient.trim() : current.recipient,
88
+ updatedAt: Date.now(),
89
+ version: '1.0',
90
+ };
91
+ try {
92
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), 'utf-8');
93
+ }
94
+ catch (error) {
95
+ const msg = error instanceof Error ? error.message : String(error);
96
+ log.error(`Failed to save WhatsApp notification config: ${msg}`);
97
+ throw error;
98
+ }
99
+ return next;
100
+ }
101
+ export function clearConfig() {
102
+ ensureDataDir();
103
+ try {
104
+ if (fs.existsSync(CONFIG_FILE)) {
105
+ fs.unlinkSync(CONFIG_FILE);
106
+ }
107
+ }
108
+ catch (error) {
109
+ const msg = error instanceof Error ? error.message : String(error);
110
+ log.error(`Failed to clear WhatsApp notification config: ${msg}`);
111
+ throw error;
112
+ }
113
+ }
114
+ export function isEventEnabled(event) {
115
+ const cfg = getConfig();
116
+ return cfg.filter[event] !== false;
117
+ }
118
+ export function getRecipient() {
119
+ return getConfig().recipient;
120
+ }
@@ -1,5 +1,6 @@
1
1
  import { agentService } from '../../services/index.js';
2
2
  import { logger } from '../../utils/index.js';
3
+ import { publishNotification } from '../../integrations/whatsapp/whatsapp-notification-publisher.js';
3
4
  const log = logger.ws;
4
5
  export function handleSendNotification(ctx, payload) {
5
6
  const { agentId, title, message, iconUrl, imageUrl } = payload;
@@ -24,4 +25,15 @@ export function handleSendNotification(ctx, payload) {
24
25
  type: 'agent_notification',
25
26
  payload: notification,
26
27
  });
28
+ void publishNotification(classifyNotificationEventType(title), `${agent.name}: ${title}`, message).catch((err) => log.warn(`WhatsApp publish skipped: ${err}`));
29
+ }
30
+ function classifyNotificationEventType(title) {
31
+ const t = title.toLowerCase();
32
+ if (t.includes('plan ready') || t.includes('plan'))
33
+ return 'planReady';
34
+ if (t.includes('error') || t.includes('failed') || t.includes('blocked'))
35
+ return 'errors';
36
+ if (t.includes('task complete') || t.includes('completed') || t.includes('done'))
37
+ return 'taskComplete';
38
+ return 'messages';
27
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.83.0",
3
+ "version": "1.84.1",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- .app.view-mode-flat .guake-terminal{display:none}.flat-view{--flat-middle-width: 1fr;--flat-inspector-width: 320px;display:grid;grid-template-columns:var(--flat-middle-width) 3px 1fr;height:100%;width:100%;background:#282a36;overflow:hidden;padding-left:64px;box-sizing:border-box;transition:grid-template-columns .25s ease}.flat-view--with-inspector{grid-template-columns:var(--flat-middle-width) 3px 1fr 3px var(--flat-inspector-width)}.flat-splitter{position:relative;cursor:col-resize;background:#ffffff0a;touch-action:none;-webkit-user-select:none;user-select:none;transition:background .15s ease;z-index:2}.flat-splitter:before{content:"";position:absolute;top:0;bottom:0;left:-5px;right:-5px}.flat-splitter:hover{background:#8be9fd73}body.flat-splitter-dragging{cursor:col-resize!important;-webkit-user-select:none!important;user-select:none!important}body.flat-splitter-dragging .flat-splitter{background:#8be9fd99}body.flat-splitter-dragging .flat-view{transition:none!important}.flat-middle{display:flex;flex-direction:column;background:#282a36;border-right:1px solid rgba(255,255,255,.06);overflow:hidden;min-width:280px}.flat-middle__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid rgba(255,255,255,.06);flex-shrink:0;gap:12px}.flat-middle__title{font-size:14px;font-weight:600;color:#f8f8f2;margin:0}.flat-middle__actions{display:flex;gap:6px;flex-shrink:0}.flat-middle__content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:8px}.flat-middle__content::-webkit-scrollbar{width:6px}.flat-middle__content::-webkit-scrollbar-track{background:transparent}.flat-middle__content::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:3px}.flat-middle__content::-webkit-scrollbar-thumb:hover{background:#fff3}.flat-cta-btn{padding:4px 10px;font-size:11px;font-weight:500;border:none;border-radius:4px;cursor:pointer;transition:all .15s ease;white-space:nowrap}.flat-cta-btn--agent{background:#50fa7b26;color:#50fa7b}.flat-cta-btn--agent:hover{background:#50fa7b40}.flat-cta-btn--boss{background:#ffd70026;color:gold}.flat-cta-btn--boss:hover{background:#ffd70040}.flat-cta-btn--building{background:#8be9fd26;color:#8be9fd}.flat-cta-btn--building:hover{background:#8be9fd40}.flat-cta-btn--area{background:#bd93f926;color:#bd93f9}.flat-cta-btn--area:hover{background:#bd93f940}.flat-middle .agent-overview-panel{position:relative;inset:auto;width:100%;height:100%;border-right:none;flex:1;min-height:0;z-index:auto}.flat-middle .agent-overview-panel .aop-row-controls .close-btn{display:none}.flat-middle__content:has(>.agent-overview-panel){padding:0;overflow:hidden}.flat-right{display:flex;flex-direction:column;background:#21222c;overflow:hidden}.flat-chat{display:flex;flex-direction:column;height:100%}.flat-chat--empty{align-items:center;justify-content:center}.flat-chat__placeholder{display:flex;flex-direction:column;align-items:center;gap:12px;color:#6272a4}.flat-chat__placeholder-icon{font-size:48px;opacity:.5}.flat-chat__placeholder-text{font-size:14px}.flat-map{display:flex;flex-direction:column;height:100%;padding:12px 16px;gap:10px;overflow-y:auto}.flat-map::-webkit-scrollbar{width:6px}.flat-map::-webkit-scrollbar-track{background:transparent}.flat-map::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:3px}.flat-map::-webkit-scrollbar-thumb:hover{background:#fff3}.flat-map__header{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-shrink:0}.flat-map__title{font-size:13px;font-weight:600;color:#f8f8f2}.flat-map__hint{font-size:11px;color:#6272a4}.flat-map__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;align-content:start}@media(max-width:768px){.flat-map__grid{grid-template-columns:1fr}}.flat-map__empty{grid-column:1/-1;display:flex;align-items:center;justify-content:center;padding:40px;color:#6272a4;font-size:13px}.flat-map-area-card{display:flex;flex-direction:column;background:#363848;border:1px solid rgba(255,255,255,.06);border-radius:8px;overflow:hidden;transition:border-color .15s ease,box-shadow .15s ease}.flat-map-area-card:hover{border-color:#ffffff1a;box-shadow:0 2px 8px #00000040}.flat-map-area-card__header{display:flex;align-items:center;gap:6px;padding:6px 8px;background:transparent;border:none;cursor:pointer;width:100%;text-align:left;color:#f8f8f2;font-size:12px;font-weight:600;transition:background .15s ease}.flat-map-area-card__header:hover{background:#ffffff0a}.flat-map-area-card__color{width:8px;height:8px;border-radius:50%;flex-shrink:0}.flat-map-area-card__name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.flat-map-area-card__count{font-size:10px;font-weight:500;color:#6272a4;background:#ffffff0f;padding:1px 5px;border-radius:8px;flex-shrink:0}.flat-map-area-card__caret{font-size:10px;color:#6272a4;flex-shrink:0;width:12px;text-align:center}.flat-map-area-card__agents{display:flex;flex-wrap:wrap;gap:4px;padding:0 8px 8px}.flat-map-agent-chip{position:relative;display:inline-flex;align-items:center;gap:5px;padding:3px 7px 6px;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px;cursor:pointer;color:#9a9caa;font-size:11px;text-align:left;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;min-width:0;overflow:hidden}.flat-map-agent-chip:hover{background:#ffffff14;border-color:#ffffff1f;color:#f8f8f2}.flat-map-agent-chip.working{overflow:visible;isolation:isolate;background-color:#4a9eff1f;background-image:linear-gradient(100deg,transparent 30%,rgba(180,230,255,.45) 49%,rgba(139,220,240,.3) 51%,transparent 70%),linear-gradient(100deg,transparent 32%,rgba(139,233,253,.3) 50%,transparent 68%);background-size:220% 100%,260% 100%;background-repeat:no-repeat,no-repeat;background-position:150% 0,150% 0;border-color:#8be9fdb3;color:#f8f8f2;box-shadow:0 0 8px #4a9eff59,inset 0 0 0 1px #8be9fd00;transform-origin:center;will-change:transform,background-position,box-shadow,border-color;animation:flat-map-agent-working-shimmer .75s linear infinite,flat-map-agent-working-shimmer-2 1.15s linear infinite,flat-map-agent-working-pulse 1.1s ease-in-out infinite,flat-map-agent-working-beat .85s ease-in-out infinite,flat-map-agent-working-iris 3.2s linear infinite}.flat-map-agent-chip.working:before{content:"";position:absolute;top:-2px;right:-2px;bottom:-2px;left:-2px;border-radius:inherit;pointer-events:none;z-index:-1;background:conic-gradient(from 0deg,#8be9fd00,#8bc8fd8c,#a082e680,#dc82c873,#dcb48266,#8bc8fd8c,#8be9fd00);filter:blur(8px);transform-origin:center;will-change:transform,opacity;animation:flat-map-agent-working-spin 3.8s linear infinite,flat-map-agent-working-breath 2.6s ease-in-out infinite}.flat-map-agent-chip.working:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:inherit;pointer-events:none;z-index:1;background-image:radial-gradient(1px 1px at 18% 32%,rgba(255,255,255,.7) 0%,transparent 60%),radial-gradient(1px 1px at 72% 58%,rgba(200,240,255,.65) 0%,transparent 60%),radial-gradient(1.5px 1.5px at 42% 80%,rgba(220,200,255,.55) 0%,transparent 60%),radial-gradient(1px 1px at 88% 22%,rgba(255,255,255,.6) 0%,transparent 60%),radial-gradient(1px 1px at 12% 70%,rgba(210,190,255,.55) 0%,transparent 60%),radial-gradient(1px 1px at 58% 18%,rgba(255,255,255,.55) 0%,transparent 60%);mix-blend-mode:screen;opacity:.5;animation:flat-map-agent-working-twinkle 1.9s ease-in-out infinite}.flat-map-agent-chip.working .flat-map-agent-chip__dot{position:relative;z-index:2;box-shadow:0 0 6px #4a9effb3;animation:flat-map-agent-working-dot .65s ease-in-out infinite}.flat-map-agent-chip.working .flat-map-agent-chip__name,.flat-map-agent-chip.working .flat-map-agent-chip__provider-icon,.flat-map-agent-chip.working .flat-map-agent-chip__crown{position:relative;z-index:2}@keyframes flat-map-agent-working-shimmer{0%{background-position:150% 0,150% 0}to{background-position:-70% 0,150% 0}}@keyframes flat-map-agent-working-shimmer-2{0%{background-position:150% 0,150% 0}to{background-position:150% 0,-70% 0}}@keyframes flat-map-agent-working-pulse{0%,to{border-color:#4a9eff8c;box-shadow:0 0 6px #4a9eff59,inset 0 0 0 1px #8be9fd00}50%{border-color:#b4faff;box-shadow:0 0 18px #4a9effcc,0 0 30px #8be9fd59,inset 0 0 0 1px #b4faff8c}}@keyframes flat-map-agent-working-beat{0%,to{transform:scale(1)}35%{transform:scale(1.035)}55%{transform:scale(.99)}75%{transform:scale(1.015)}}@keyframes flat-map-agent-working-dot{0%,to{transform:scale(1);box-shadow:0 0 6px #4a9effb3}50%{transform:scale(1.7);box-shadow:0 0 12px #b4faff,0 0 22px #4a9effb3}}@keyframes flat-map-agent-working-iris{0%{border-color:#8be9fdb3}25%{border-color:#b482ffd9}50%{border-color:#ff78dccc}75%{border-color:#ffc878bf}to{border-color:#8be9fdb3}}@keyframes flat-map-agent-working-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes flat-map-agent-working-breath{0%,to{opacity:.3;filter:blur(7px)}40%{opacity:.7;filter:blur(11px)}70%{opacity:.45;filter:blur(8px)}}@keyframes flat-map-agent-working-twinkle{0%,to{opacity:.25;transform:translate(0) scale(.9)}25%{opacity:1;transform:translate(1px,-1px) scale(1.1)}50%{opacity:.55;transform:translate(-1px,1px) scale(.95)}75%{opacity:1;transform:translate(1px,1px) scale(1.15)}}@media(prefers-reduced-motion:reduce){.flat-map-agent-chip.working{animation:flat-map-agent-working-shimmer 5s linear infinite;background-image:linear-gradient(110deg,transparent 40%,rgba(139,233,253,.3) 50%,transparent 60%),linear-gradient(110deg,transparent 40%,rgba(139,233,253,0) 50%,transparent 60%);transform:none}.flat-map-agent-chip.working:before,.flat-map-agent-chip.working:after{animation:none;opacity:.3}.flat-map-agent-chip.working .flat-map-agent-chip__dot{animation:none}}.flat-map-agent-chip__name{min-width:0;overflow:hidden;text-overflow:ellipsis}.flat-map-agent-chip__dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.flat-map-agent-chip__provider-icon{width:12px;height:12px;object-fit:contain;flex-shrink:0;filter:drop-shadow(0 1px 2px rgba(0,0,0,.25))}.flat-map-agent-chip__crown{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.flat-map-agent-chip__context-bar{position:absolute;left:0;right:0;bottom:0;height:2px;background:#ffffff0f;pointer-events:none}.flat-map-agent-chip__context-bar-fill{display:block;height:100%;transition:width .2s ease,background-color .2s ease}.flat-map-area-card__buildings{display:flex;flex-wrap:wrap;gap:4px;padding:6px 8px 8px;border-top:1px dashed rgba(255,255,255,.05);margin-top:2px}.flat-map-building-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 7px;background:#ffffff06;border:1px dashed rgba(255,255,255,.08);border-radius:8px;cursor:pointer;color:#9a9caa;font-size:11px;text-align:left;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap;min-width:0;overflow:hidden}.flat-map-building-chip:hover{background:#ffffff12;border-color:#ffffff2e;color:#f8f8f2}.flat-map-building-chip__name{min-width:0;overflow:hidden;text-overflow:ellipsis}.flat-map-building-chip__dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.flat-map-building-chip--error{border-color:#ff4a4a66}.flat-map-building-chip--running{border-style:solid;border-color:#4aff9e40}.flat-terminal-wrapper{display:flex;flex-direction:column;height:100%;min-height:0;position:relative;background:#21222c;overflow:hidden;--guake-side-panel-width: 380px}.flat-terminal-wrapper--with-side-panel>*:not(.guake-git-panel):not(.guake-buildings-panel):not(.guake-workflow-panel):not(.agent-debug-panel){margin-right:var(--guake-side-panel-width);transition:margin-right .2s ease}.flat-terminal-wrapper__header{display:flex;flex-direction:row;align-items:center;gap:12px;padding:6px 12px;border-bottom:1px solid rgba(255,255,255,.06);background:#1e1f29;flex-shrink:0;min-height:40px}.flat-terminal-wrapper__header-main{display:flex;align-items:center;gap:10px;min-width:0;flex:1 1 auto;overflow:hidden;padding:4px 6px;margin:0;background:transparent;border:1px solid transparent;border-radius:8px;color:inherit;font:inherit;text-align:left;cursor:pointer;transition:background .15s ease,border-color .15s ease}.flat-terminal-wrapper__header-main:hover{background:#ffffff0a;border-color:#ffffff0f}.flat-terminal-wrapper__header-main--active{background:#bd93f92e;border-color:#bd93f973}.flat-terminal-wrapper__header-main--active:hover{background:#bd93f942}.flat-terminal-wrapper__header-meta{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex:0 0 auto;flex-wrap:nowrap}.flat-terminal-wrapper__statusbar{display:flex;align-items:center;gap:10px;padding:6px 12px;flex-shrink:0;background:#1e1f29;border-top:1px solid rgba(255,255,255,.06);min-height:32px;box-sizing:border-box}.flat-terminal-wrapper__statusbar .flat-terminal-wrapper__cwd,.flat-terminal-wrapper__statusbar .flat-terminal-wrapper__context{background:transparent;border-color:#ffffff0a}.flat-terminal-wrapper__statusbar-spacer{flex:1 1 auto}.flat-terminal-wrapper__buildings{display:inline-flex;align-items:center;gap:4px;padding:2px;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px}.flat-terminal-wrapper__building-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:22px;padding:0;color:#6272a4;background:transparent;border:none;border-radius:7px;cursor:pointer;transition:background .15s ease,color .15s ease}.flat-terminal-wrapper__building-btn:hover{color:#f8f8f2;background:#ffffff14}.flat-terminal-wrapper__building-btn--offline{color:#ffb86cbf}.flat-terminal-wrapper__building-btn--offline:hover{color:#ffb86c}.flat-terminal-wrapper__building-btn--active{color:#f8f8f2;background:#bd93f947;box-shadow:0 0 0 1px #bd93f973}.flat-terminal-wrapper__building-btn--active:hover{background:#bd93f95c}.flat-terminal-wrapper__detached{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;font-size:11px;font-weight:500;color:#ffb86c;background:#ffb86c1f;border:1px solid rgba(255,184,108,.35);border-radius:10px;animation:blink 1.6s ease-in-out infinite}.flat-terminal-wrapper__area-dir{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;font-size:11px;font-family:SF Mono,Menlo,Consolas,monospace;color:#9a9caa;background:#ffffff08;border:1px solid rgba(255,255,255,.05);border-radius:10px;cursor:pointer;max-width:240px;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:background .15s ease,color .15s ease,border-color .15s ease}.flat-terminal-wrapper__area-dir:hover{background:#ffffff14;color:#f8f8f2;border-color:#ffffff24}.flat-terminal-wrapper__area-dir-name{overflow:hidden;text-overflow:ellipsis}.flat-terminal-wrapper__area-dir-branch{display:inline-flex;align-items:center;gap:3px;color:#8be9fd;opacity:.85}.flat-terminal-wrapper__branch-ahead{display:inline-flex;align-items:center;color:#50fa7b}.flat-terminal-wrapper__branch-behind{display:inline-flex;align-items:center;color:#ffb86c}.flat-terminal-wrapper__area-fetch{display:inline-flex;align-items:center;padding:1px 3px;border-radius:4px;color:#6272a4;cursor:pointer;transition:background .15s ease,color .15s ease}.flat-terminal-wrapper__area-fetch:hover{background:#ffffff14;color:#f8f8f2}.flat-terminal-wrapper__area-fetch--fetching{animation:blink 1s ease-in-out infinite}.flat-terminal-wrapper__context-label{color:#6272a4;margin-right:2px}.flat-terminal-wrapper__context-free{color:#6272a4;font-size:10px}.flat-terminal-wrapper__actions{display:inline-flex;align-items:center;gap:4px;padding:2px;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px}.flat-terminal-wrapper__action-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:24px;padding:0;font-size:12px;line-height:1;color:#6272a4;background:transparent;border:none;border-radius:7px;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}.flat-terminal-wrapper__action-btn:hover:not(:disabled){color:#f8f8f2;background:#ffffff0f}.flat-terminal-wrapper__action-btn:disabled{opacity:.35;cursor:not-allowed}.flat-terminal-wrapper__action-btn--active{color:#f8f8f2;background:#bd93f947;box-shadow:0 0 0 1px #bd93f973}.flat-terminal-wrapper__action-btn--active:hover{background:#bd93f95c}.flat-terminal-wrapper__action-btn--danger{color:#ff5555d9}.flat-terminal-wrapper__action-btn--danger:hover{color:#f55;background:#ff55551f}.flat-terminal-wrapper__action-btn--confirm{color:#f55;background:#ff55552e;box-shadow:0 0 0 1px #ff555580;animation:blink 1s ease-in-out infinite}.flat-terminal-wrapper__more{position:relative;display:inline-flex}.flat-terminal-wrapper__more-menu{position:absolute;top:calc(100% + 4px);right:0;min-width:220px;padding:4px;background:#363848;border:1px solid rgba(255,255,255,.08);border-radius:8px;box-shadow:0 8px 28px #00000073;z-index:100}.flat-terminal-wrapper__more-item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;font-size:12px;color:#f8f8f2;background:transparent;border:none;border-radius:6px;cursor:pointer;text-align:left;transition:background .15s ease,color .15s ease}.flat-terminal-wrapper__more-item:hover:not(:disabled){background:#ffffff0f}.flat-terminal-wrapper__more-item:disabled{opacity:.45;cursor:not-allowed}.flat-terminal-wrapper__more-item--danger{color:#ff5555e6}.flat-terminal-wrapper__more-item--danger:hover:not(:disabled){color:#f55;background:#ff55551f}.flat-terminal-wrapper__more-divider{height:1px;margin:4px 2px;background:#ffffff0f}.flat-terminal-wrapper__header-info{display:flex;flex-direction:row;align-items:baseline;gap:8px;min-width:0;overflow:hidden}.flat-terminal-wrapper__header-name{font-size:13px;font-weight:600;color:#f8f8f2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.flat-terminal-wrapper__header-status{font-size:10px;text-transform:capitalize;flex-shrink:0}.flat-terminal-wrapper__header-task{font-size:11px;color:#6272a4;max-width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:2px 8px;border-radius:10px;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);flex-shrink:1}.flat-terminal-wrapper__header-model{display:inline-flex;align-items:center;gap:6px;flex-shrink:0;min-width:0}.flat-terminal-wrapper__header-provider-icon{width:16px;height:16px;object-fit:contain;flex-shrink:0;filter:drop-shadow(0 1px 2px rgba(0,0,0,.25))}.flat-terminal-wrapper__header-model-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;font-size:10px;font-family:SF Mono,Menlo,Consolas,monospace;font-weight:600;color:#9a9caa;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:999px;white-space:nowrap;max-width:180px;overflow:hidden;text-overflow:ellipsis;line-height:1.4}.flat-terminal-wrapper__header-model-name{color:#f8f8f2;text-transform:lowercase;overflow:hidden;text-overflow:ellipsis}.flat-terminal-wrapper__header-model-sep{opacity:.6}.flat-terminal-wrapper__header-model-effort{color:#bd93f9;text-transform:lowercase}.flat-terminal-wrapper__cwd{display:inline-flex;align-items:center;gap:6px;padding:4px 9px;font-size:11px;color:#9a9caa;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px;cursor:pointer;max-width:240px;transition:background .15s ease,border-color .15s ease,color .15s ease}.flat-terminal-wrapper__cwd:hover{background:#ffffff14;border-color:#ffffff24;color:#f8f8f2}.flat-terminal-wrapper__cwd-icon{font-size:12px;line-height:1}.flat-terminal-wrapper__cwd-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:SF Mono,Menlo,Consolas,monospace}.flat-terminal-wrapper__context{display:inline-flex;align-items:center;gap:6px;padding:4px 9px;font-size:11px;color:#9a9caa;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px;cursor:pointer;transition:background .15s ease,border-color .15s ease}.flat-terminal-wrapper__context:hover{background:#ffffff14;border-color:#ffffff24}.flat-terminal-wrapper__context-icon{font-size:12px;line-height:1}.flat-terminal-wrapper__context-bar{position:relative;width:56px;height:5px;border-radius:3px;background:#ffffff14;overflow:hidden}.flat-terminal-wrapper__context-bar-fill{position:absolute;left:0;top:0;height:100%;border-radius:3px;transition:width .2s ease,background-color .2s ease}.flat-terminal-wrapper__context-tokens{font-family:SF Mono,Menlo,Consolas,monospace;font-size:11px;font-weight:600;font-variant-numeric:tabular-nums}.flat-terminal-wrapper__context-warning{font-size:11px;opacity:.8}.flat-terminal-wrapper__view-mode{display:inline-flex;align-items:center;padding:2px;gap:2px;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px}.flat-terminal-wrapper__view-mode-btn{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;font-size:11px;font-weight:500;color:#6272a4;background:transparent;border:none;border-radius:8px;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}.flat-terminal-wrapper__view-mode-btn:hover{color:#f8f8f2;background:#ffffff0a}.flat-terminal-wrapper__view-mode-btn--active{color:#f8f8f2;background:#bd93f947;box-shadow:0 0 0 1px #bd93f980,0 1px 3px #00000040}.flat-terminal-wrapper__view-mode-btn--active:hover{background:#bd93f95c}.flat-terminal-wrapper__view-mode-icon{font-size:13px;line-height:1}.flat-terminal-wrapper__view-mode-label{line-height:1}.flat-terminal-wrapper__theme{display:inline-flex;align-items:center}.flat-terminal-wrapper__theme .theme-selector-trigger{padding:4px 9px;font-size:11px;border-radius:10px;background:#ffffff0a;border:1px solid rgba(255,255,255,.06)}.flat-terminal-wrapper__theme .theme-selector-trigger:hover{background:#ffffff14;border-color:#ffffff24}.flat-terminal-wrapper__inspector-toggle{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;font-size:11px;font-weight:500;color:#9a9caa;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px;cursor:pointer;transition:background .15s ease,border-color .15s ease,color .15s ease,box-shadow .15s ease}.flat-terminal-wrapper__inspector-toggle:hover{color:#f8f8f2;background:#ffffff14;border-color:#ffffff24}.flat-terminal-wrapper__inspector-toggle--active{color:#f8f8f2;background:#bd93f938;border-color:#bd93f98c;box-shadow:0 0 0 1px #bd93f959,0 1px 3px #00000040}.flat-terminal-wrapper__inspector-toggle--active:hover{background:#bd93f952}.flat-terminal-wrapper__inspector-icon{display:inline-flex;align-items:center;justify-content:center;line-height:1}.flat-terminal-wrapper__inspector-label{line-height:1}.flat-terminal-wrapper__close{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;color:#6272a4;background:#ffffff0a;border:1px solid rgba(255,255,255,.06);border-radius:10px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.flat-terminal-wrapper__close:hover{color:#f55;background:#ff55551f;border-color:#f556}@media(max-width:1100px){.flat-terminal-wrapper__view-mode-label,.flat-terminal-wrapper__inspector-label{display:none}.flat-terminal-wrapper__cwd{max-width:160px}}.flat-terminal-wrapper .guake-output{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px 18px}.flat-terminal-wrapper .guake-output::-webkit-scrollbar{width:6px}.flat-terminal-wrapper .guake-output::-webkit-scrollbar-track{background:transparent}.flat-terminal-wrapper .guake-output::-webkit-scrollbar-thumb{background:#ffffff1a;border-radius:3px}.flat-terminal-wrapper .guake-input-container{position:relative;flex-shrink:0;border-top:1px solid rgba(255,255,255,.06);background:#1e1f29}.flat-terminal-wrapper .guake-search-bar,.flat-terminal-wrapper .atp-search-bar{position:sticky;top:0;z-index:10;background:#1e1f29}.flat-terminal-wrapper .guake-handle,.flat-terminal-wrapper .guake-side-panel-resize,.flat-terminal-wrapper .atp-resize-handle{display:none}.flat-terminal-wrapper .compacting-indicator{padding:12px;margin:8px}@keyframes blink{0%,to{opacity:1}50%{opacity:.3}}.flat-inspector{display:flex;flex-direction:column;min-width:0;min-height:0;height:100%;background:#1e1f29;border-left:1px solid rgba(255,255,255,.06);overflow:hidden;animation:flat-inspector-slide-in .22s ease}.flat-inspector__header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid rgba(255,255,255,.06);background:#21222c;flex-shrink:0}.flat-inspector__tabs{display:inline-flex;align-items:center;gap:4px;padding:2px;background:#00000040;border:1px solid rgba(255,255,255,.06);border-radius:6px}.flat-inspector__tab{padding:4px 10px;font-size:12px;font-weight:500;color:#6272a4;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background .15s ease,color .15s ease}.flat-inspector__tab:hover{color:#f8f8f2}.flat-inspector__tab--active{color:#f8f8f2;background:#ffffff14}.flat-inspector__close{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;font-size:13px;color:#6272a4;background:transparent;border:1px solid transparent;border-radius:6px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.flat-inspector__close:hover{color:#f8f8f2;background:#ffffff0f;border-color:#ffffff1a}.flat-inspector__body{flex:1 1 auto;min-height:0;min-width:0;overflow-y:auto;overflow-x:hidden;display:flex;flex-direction:column}.flat-inspector__body .unit-panel{flex:1 1 auto;min-height:0;min-width:0;padding:10px}.flat-inspector__empty{display:flex;align-items:center;justify-content:center;flex:1;color:#6272a4;font-size:13px}@keyframes flat-inspector-slide-in{0%{opacity:0;transform:translate(12px)}to{opacity:1;transform:translate(0)}}.flat-mobile-sidebar-toggle,.flat-mobile-sidebar-backdrop{display:none}@media(max-width:1024px){.flat-view,.flat-view--with-inspector{grid-template-columns:1fr}.flat-splitter{display:none}.flat-right{grid-column:1/-1}.flat-right>.flat-terminal-wrapper,.flat-right>.flat-chat{flex:1 1 auto;min-height:0;height:auto}.flat-middle{position:fixed;top:0;left:0;bottom:0;width:min(380px,92vw);z-index:185;transform:translate(-110%);transition:transform .22s ease;border-right:1px solid rgba(255,255,255,.08);box-shadow:6px 0 32px #0009}.flat-view--mobile-sidebar-open .flat-middle{transform:translate(0)}.flat-mobile-sidebar-toggle{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;margin:10px 12px 0;padding:8px 12px;font-size:12px;font-weight:500;color:#f8f8f2;background:#363848f2;border:1px solid rgba(255,255,255,.08);border-radius:10px;cursor:pointer;min-height:36px;flex-shrink:0;transition:background .15s ease,border-color .15s ease}.flat-mobile-sidebar-toggle:hover,.flat-mobile-sidebar-toggle:active{background:#bd93f947;border-color:#bd93f980}.flat-mobile-sidebar-toggle__label{line-height:1}.flat-mobile-sidebar-backdrop{display:block;position:fixed;top:0;right:0;bottom:0;left:0;z-index:184;background:#000000ad;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:flat-inspector-fade-in .18s ease}.flat-inspector{position:fixed;top:0;right:0;bottom:0;left:0;z-index:186;border-left:none;animation:flat-inspector-fade-in .18s ease}.flat-inspector__header{padding-left:64px}}@media(max-width:768px){.flat-view{padding-left:56px}.flat-middle__header{flex-wrap:wrap;padding:10px 12px;gap:8px}.flat-middle__content{padding:8px}.flat-cta-btn{padding:8px 10px;font-size:11px;min-height:34px}.flat-map{padding:10px 12px;gap:8px}.flat-map-area-card__agents{gap:6px}.flat-map-agent-chip{padding:7px 10px 9px;font-size:12px;min-height:34px}.flat-terminal-wrapper__header{flex-wrap:wrap;padding:6px 10px;gap:8px;min-height:44px}.flat-terminal-wrapper__header-main{flex:1 1 100%}.flat-terminal-wrapper__header-meta{flex:1 1 100%;justify-content:flex-start;flex-wrap:wrap}.flat-terminal-wrapper__header-task{display:none}.flat-terminal-wrapper__statusbar{flex-wrap:wrap;padding:6px 10px;gap:6px;row-gap:6px}.flat-terminal-wrapper__action-btn,.flat-terminal-wrapper__building-btn,.flat-terminal-wrapper__close{width:32px;height:32px}.flat-terminal-wrapper{--guake-side-panel-width: 100%}.flat-terminal-wrapper--with-side-panel>*:not(.guake-git-panel):not(.guake-buildings-panel):not(.guake-workflow-panel):not(.agent-debug-panel){margin-right:0}.flat-terminal-wrapper .guake-git-panel,.flat-terminal-wrapper .guake-buildings-panel,.flat-terminal-wrapper .guake-workflow-panel,.flat-terminal-wrapper .agent-debug-panel{width:100%}.flat-bottom-panel{height:180px}}@media(max-width:480px){.flat-view{padding-left:52px}.flat-middle{width:100vw;max-width:100vw;box-shadow:none}.flat-middle__header{padding:8px 10px}.flat-middle__actions{gap:4px}.flat-cta-btn{padding:6px 8px;font-size:10px;min-height:32px}.flat-map-area-card__header{padding:8px 10px;font-size:13px}.flat-map-agent-chip{font-size:12px;padding:6px 8px 8px}.flat-terminal-wrapper__header-name{font-size:12px}.flat-terminal-wrapper__header-model-chip{font-size:9px;max-width:130px}.flat-inspector__body{padding-bottom:env(safe-area-inset-bottom,0)}}@media(min-width:1400px){.flat-view{--flat-middle-width: minmax(280px, 400px);--flat-inspector-width: 360px}}@keyframes flat-inspector-fade-in{0%{opacity:0}to{opacity:1}}.flat-bottom-panel{display:flex;flex-direction:column;flex-shrink:0;height:250px;min-height:0;background:#1e1f29;border-top:1px solid rgba(255,255,255,.08);overflow:hidden}.flat-bottom-panel__header{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 10px;background:#21222c;border-bottom:1px solid rgba(255,255,255,.06);flex-shrink:0;min-height:28px}.flat-bottom-panel__title{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:500;color:#f8f8f2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.flat-bottom-panel__muted{color:#6272a4;font-weight:400;font-size:10px}.flat-bottom-panel__close{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;padding:0;color:#6272a4;background:transparent;border:none;border-radius:6px;cursor:pointer;transition:background .15s ease,color .15s ease}.flat-bottom-panel__close:hover{color:#f8f8f2;background:#ffffff14}.flat-bottom-panel__body{flex:1 1 auto;min-height:0;display:flex;background:#282a36}.flat-bottom-panel__body>*{flex:1 1 auto;min-height:0}.flat-bottom-panel__placeholder{flex:1 1 auto;display:flex;align-items:center;justify-content:center;color:#6272a4;font-size:12px}
@@ -1 +0,0 @@
1
- .system-prompt-modal{width:720px;max-width:95vw;max-height:85vh;display:flex;flex-direction:column;padding:0;background:#14141e;border:1px solid #2a2a3a;border-radius:12px;overflow:hidden}.system-prompt-modal .modal-header{padding:14px 20px;margin-bottom:0;border-bottom:1px solid #2a2a3a;font-size:16px;font-weight:600;display:flex;justify-content:space-between;align-items:center;gap:12px}.system-prompt-modal .modal-header h2{margin:0;font-size:16px}.system-prompt-modal .modal-footer{padding:12px 20px;border-top:1px solid #2a2a3a;display:flex;justify-content:space-between;align-items:center;gap:12px}.modal-close{background:transparent;border:none;color:#8a8a98;font-size:24px;line-height:1;cursor:pointer;padding:4px 8px;border-radius:4px;transition:all .15s;flex-shrink:0}.modal-close:hover{background:#d45a5a26;color:#d45a5a}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0;display:flex;flex-direction:column;gap:12px}.modal-description{margin:0;font-size:12px;color:#8a8a98;line-height:1.5}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px}.loading-state .spinner{width:32px;height:32px;border:3px solid rgba(42,42,58,.5);border-top-color:#5a8fd4;border-radius:50%;animation:spin .8s linear infinite}.loading-state p{color:#8a8a98;margin:0;font-size:13px}@keyframes spin{to{transform:rotate(360deg)}}.alert{padding:10px 12px;border-radius:4px;display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.4}.alert .alert-icon{flex-shrink:0;font-size:14px}.alert.alert-error{background:#d45a5a1a;border:1px solid rgba(212,90,90,.25);color:#d45a5a}.alert.alert-success{background:#5cb88a1a;border:1px solid rgba(92,184,138,.25);color:#5cb88a}.editor-wrapper{display:flex;flex-direction:column;gap:6px;flex:1}.editor-header{display:flex;justify-content:space-between;align-items:center;gap:8px}.editor-label{font-size:11px;font-weight:600;color:#8a8a98;text-transform:uppercase;letter-spacing:.3px}.char-count{font-size:10px;color:#606070;flex-shrink:0}.prompt-editor{padding:10px 12px;border:1px solid #2a2a3a;border-radius:4px;background:#1c1c28;color:#d0d0d8;font-family:JetBrains Mono,SF Mono,Fira Code,Consolas,monospace;font-size:12px;line-height:1.6;resize:vertical;min-height:14rem;flex:1;max-height:28rem;outline:none;transition:all .15s}.prompt-editor:focus{border-color:#5a8fd4;background:#1c1c28cc}.prompt-editor::placeholder{color:#606070;white-space:pre-line}.editor-hint{font-size:10px;color:#606070;font-style:italic;padding:0 2px}.footer-buttons-left{display:flex;gap:6px}.footer-buttons-right{display:flex;gap:6px;margin-left:auto}.btn{padding:8px 14px;border:1px solid #2a2a3a;border-radius:4px;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s;white-space:nowrap;background:#1c1c28;color:#d0d0d8}:root{--bg-primary: #0d0d14;--bg-secondary: #14141e;--bg-tertiary: #1c1c28;--border-color: #2a2a3a;--text-primary: #d0d0d8;--text-secondary: #8a8a98;--text-muted: #606070;--accent-blue: #5a8fd4;--accent-green: #5cb88a;--accent-orange: #d4a05a;--accent-red: #d45a5a;--accent-purple: #8a6fbf;--accent-cyan: #5ab8c8;--accent-claude: #a06848;--accent-claude-light: #c8896a;--accent-pink: #5a8fd4;--accent-yellow: #d0d0d8;--font-size-base: 14px;--line-height-base: 1.5;--msg-user-bg: color-mix(in srgb, #5ab8c8 8%, transparent);--msg-user-border: #5ab8c8;--msg-user-text: #d0d0d8;--msg-assistant-bg: color-mix(in srgb, #5cb88a 8%, transparent);--msg-assistant-border: #5cb88a;--msg-assistant-text: #d0d0d8;--tool-use-bg: color-mix(in srgb, #d4a05a 8%, transparent);--tool-use-border: #d4a05a;--tool-use-text: #d4a05a;--tool-use-name: #d4a05a;--tool-result-bg: color-mix(in srgb, #8a6fbf 6%, transparent);--tool-result-border: #8a6fbf;--tool-result-text: #5cb88a;--context-bar-bg: #1c1c28;--context-bar-fill: #8a6fbf;--output-line-bg: transparent;--task-label-color: #5ab8c8}.whatsapp-config-modal{width:900px;max-width:95vw;max-height:85vh;display:flex;flex-direction:column;padding:0;background:#14141e;border:1px solid #2a2a3a;border-radius:12px;overflow:hidden}.whatsapp-config-modal .modal-header{padding:14px 20px;margin-bottom:0;border-bottom:1px solid #2a2a3a;font-size:16px;font-weight:600;display:flex;justify-content:space-between;align-items:center;gap:12px}.whatsapp-config-modal .modal-header h2{margin:0;font-size:16px}.whatsapp-config-modal .modal-footer{padding:12px 20px;border-top:1px solid #2a2a3a;display:flex;justify-content:space-between;align-items:center;gap:12px}.whatsapp-config-modal .modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0;display:flex;flex-direction:column;gap:16px}.whatsapp-config-modal .modal-description{margin:0;font-size:12px;color:#8a8a98;line-height:1.5}.whatsapp-config-modal .loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:48px 24px}.whatsapp-config-modal .loading-state .spinner{width:32px;height:32px;border:3px solid rgba(42,42,58,.5);border-top-color:#5a8fd4;border-radius:50%;animation:wa-spin .8s linear infinite}.whatsapp-config-modal .loading-state p{color:#8a8a98;margin:0;font-size:13px}.whatsapp-config-modal .alert{padding:10px 12px;border-radius:4px;display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.4}.whatsapp-config-modal .alert .alert-icon{flex-shrink:0;font-size:14px}.whatsapp-config-modal .alert.alert-error{background:#d45a5a1a;border:1px solid rgba(212,90,90,.25);color:#d45a5a}.whatsapp-config-modal .alert.alert-success{background:#5cb88a1a;border:1px solid rgba(92,184,138,.25);color:#5cb88a}.whatsapp-config-modal .footer-buttons-left{display:flex;gap:6px}.whatsapp-config-modal .footer-buttons-right{display:flex;gap:6px;margin-left:auto}@keyframes wa-spin{to{transform:rotate(360deg)}}.wa-section{display:flex;flex-direction:column;gap:10px;padding:14px 14px 16px;border:1px solid #2a2a3a;border-radius:8px;background:#1c1c2873}.wa-section-title{margin:0 0 4px;font-size:12px;font-weight:600;color:#8a8a98;text-transform:uppercase;letter-spacing:.5px}.wa-field{display:flex;flex-direction:column;gap:4px}.wa-field.wa-field-row{flex-direction:row;align-items:center;justify-content:space-between}.wa-field.wa-field-inline{flex-direction:row;align-items:center;gap:8px;flex-wrap:wrap}.wa-field.wa-field-inline .wa-input{flex:1 1 220px;min-width:160px}.wa-field.wa-field-end{justify-content:flex-end}.wa-label{font-size:11px;font-weight:600;color:#8a8a98;text-transform:uppercase;letter-spacing:.3px}.wa-hint{font-size:10px;color:#606070;font-style:italic}.wa-input,.wa-textarea{padding:8px 10px;border:1px solid #2a2a3a;border-radius:4px;background:#1c1c28;color:#d0d0d8;font-family:JetBrains Mono,SF Mono,Fira Code,Consolas,monospace;font-size:12px;line-height:1.5;outline:none;transition:all .15s;width:100%;box-sizing:border-box}.wa-input:focus,.wa-textarea:focus{border-color:#5a8fd4;background:#1c1c28cc}.wa-input::placeholder,.wa-textarea::placeholder{color:#606070}.wa-textarea{resize:vertical;min-height:6rem;max-height:14rem}.wa-toggle{position:relative;display:inline-flex;align-items:center;cursor:pointer;flex-shrink:0}.wa-toggle input{position:absolute;opacity:0;pointer-events:none}.wa-toggle .wa-toggle-track{width:32px;height:18px;background:#1c1c28;border:1px solid #2a2a3a;border-radius:9px;transition:background .15s,border-color .15s;position:relative;display:inline-block}.wa-toggle .wa-toggle-thumb{position:absolute;top:1px;left:1px;width:14px;height:14px;background:#8a8a98;border-radius:50%;transition:transform .15s,background .15s}.wa-toggle input:checked+.wa-toggle-track{background:#5a8fd459;border-color:#5a8fd4}.wa-toggle input:checked+.wa-toggle-track .wa-toggle-thumb{transform:translate(14px);background:#5a8fd4}.wa-key-status{display:flex;align-items:center;gap:8px}.wa-status{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:4px;font-size:11px;font-weight:500}.wa-status.wa-status-ok{background:#5cb88a1f;color:#5cb88a;border:1px solid rgba(92,184,138,.3)}.wa-status.wa-status-warn{background:#d4a05a1f;color:#d4a05a;border:1px solid rgba(212,160,90,.3)}.wa-sessions-list{display:flex;flex-direction:column;gap:6px}.wa-empty{font-size:12px;color:#606070;font-style:italic;padding:8px 0}.wa-session-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border:1px solid #2a2a3a;border-radius:4px;background:#1c1c28}.wa-session-info{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}.wa-session-id{font-family:JetBrains Mono,SF Mono,Fira Code,Consolas,monospace;font-size:12px;color:#d0d0d8;font-weight:500}.wa-session-status{font-size:11px;padding:2px 6px;border-radius:4px;background:#60607026;color:#8a8a98;text-transform:uppercase;letter-spacing:.3px}.wa-session-status.status-connected,.wa-session-status.status-paired,.wa-session-status.status-open,.wa-session-status.status-ready{background:#5cb88a26;color:#5cb88a}.wa-session-status.status-pairing,.wa-session-status.status-connecting,.wa-session-status.status-qr{background:#5a8fd426;color:#5a8fd4}.wa-session-status.status-error,.wa-session-status.status-disconnected,.wa-session-status.status-failed{background:#d45a5a26;color:#d45a5a}.wa-session-paired{font-size:11px;color:#606070}.wa-qr-panel{margin-top:8px;padding:12px;border:1px dashed #2a2a3a;border-radius:6px;background:#0d0d1480;display:flex;flex-direction:column;gap:10px}.wa-qr-header{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12px;color:#8a8a98}.wa-qr-image{display:flex;justify-content:center;padding:12px;background:#fff;border-radius:4px}.wa-qr-image img{width:240px;height:240px;image-rendering:pixelated}.wa-qr-loading{display:flex;flex-direction:column;align-items:center;gap:8px;padding:24px}.wa-qr-loading .spinner{width:24px;height:24px;border:3px solid rgba(42,42,58,.5);border-top-color:#5a8fd4;border-radius:50%;animation:wa-spin .8s linear infinite}.wa-qr-loading p{margin:0;font-size:12px;color:#8a8a98}.wa-pairing-message{font-size:12px;color:#8a8a98;font-style:italic}.btn{padding:8px 14px;border:1px solid #2a2a3a;border-radius:4px;font-size:12px;font-weight:500;cursor:pointer;transition:all .15s;white-space:nowrap;background:#1c1c28;color:#d0d0d8;display:inline-flex;align-items:center;gap:6px}.btn:disabled{opacity:.5;cursor:not-allowed}.btn:active:not(:disabled){transform:scale(.98)}.btn:hover:not(:disabled){border-color:#8a8a98;background:#1c1c28cc}.btn.btn-sm{padding:5px 10px;font-size:11px}.btn.btn-primary{background:#5a8fd4;color:#fff;border-color:#5a8fd4}.btn.btn-primary:hover:not(:disabled){background:#5a8fd4e6;box-shadow:0 0 0 2px #5a8fd433}.btn.btn-secondary{background:#1c1c28;color:#d0d0d8;border-color:#2a2a3a}.btn.btn-secondary:hover:not(:disabled){border-color:#8a8a98;background:#1c1c28cc}.btn.btn-danger{background:#d45a5a26;color:#d45a5a;border-color:#d45a5a4d}.btn.btn-danger:hover:not(:disabled){background:#d45a5a40;border-color:#d45a5a80}