palmier 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/run.js +55 -0
- package/dist/commands/serve.js +22 -2
- package/dist/event-queues.d.ts +36 -0
- package/dist/event-queues.js +53 -0
- package/dist/mcp-tools.d.ts +2 -0
- package/dist/mcp-tools.js +4 -2
- package/dist/platform/linux.js +11 -8
- package/dist/platform/windows.d.ts +5 -6
- package/dist/platform/windows.js +19 -13
- package/dist/pwa/assets/index-FP1Mipr6.js +120 -0
- package/dist/pwa/assets/index-bLTn8zBj.css +1 -0
- package/dist/pwa/assets/{web-BNr628AV.js → web-BpM3fNCn.js} +1 -1
- package/dist/pwa/assets/{web-DyQPewAi.js → web-CF-N8Di6.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/pwa/service-worker.js +1 -1
- package/dist/rpc-handler.js +25 -9
- package/dist/task.js +1 -1
- package/dist/transports/http-transport.js +18 -5
- package/dist/types.d.ts +10 -6
- package/package.json +1 -1
- package/palmier-server/README.md +3 -3
- package/palmier-server/pwa/src/App.css +117 -36
- package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +46 -0
- package/palmier-server/pwa/src/components/RunDetailView.tsx +58 -15
- package/palmier-server/pwa/src/components/SessionComposer.tsx +20 -10
- package/palmier-server/pwa/src/components/{RunsView.tsx → SessionsView.tsx} +33 -25
- package/palmier-server/pwa/src/components/TaskCard.tsx +33 -35
- package/palmier-server/pwa/src/components/TaskForm.tsx +274 -293
- package/palmier-server/pwa/src/components/{TaskListView.tsx → TasksView.tsx} +20 -13
- package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +16 -8
- package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +102 -0
- package/palmier-server/pwa/src/pages/Dashboard.tsx +9 -26
- package/palmier-server/pwa/src/types.ts +5 -9
- package/palmier-server/spec.md +23 -23
- package/src/commands/run.ts +61 -0
- package/src/commands/serve.ts +22 -2
- package/src/event-queues.ts +56 -0
- package/src/mcp-tools.ts +6 -2
- package/src/platform/linux.ts +10 -8
- package/src/platform/windows.ts +19 -13
- package/src/rpc-handler.ts +28 -11
- package/src/task.ts +1 -1
- package/src/transports/http-transport.ts +17 -5
- package/src/types.ts +10 -7
- package/dist/pwa/assets/index-8cTctVnD.js +0 -120
- package/dist/pwa/assets/index-CSUkBBsQ.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(data:font/woff2;base64,d09GMgABAAAAAAa0ABQAAAAADOwAAAZHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbbhwoP0hWQVJtP01WQVJGBmA/U1RBVIEcAGQvXBEICoMkgmcLFgAwhEwBNgIkAyYEIAWGXAdiDAcbOgtRlHLSRcD8TEzkdoghPRuspKefbVnJIM5yRfA8vZv9uTNJSCZY21Scrigr6qyJ/C3sE1OFKuVP3e+lxUTbYio1zb/kFdk2bufY2BlhWERRBrcDUBhB5GEpd2Cy+MDxqf9zLPX+XVsk5r+s50d6IpWLatqbdT6f2MAikiOKQocbjya2QT0DqPDX4R0jQBgAUAiCRhDA0EkvYeLk9X3I2bEWI8jBAihIANm+kUWQBgggWChcBBdFhXlAUgOCAgBA0AgKjUJ8HKvjddFCKSgAVklEBYAeEJgCQJH0qB3B+neDg4sAA4hAD0AVIEAA0IACJGeaUjMIIIhBUE/zNABjTQMBwEVRcwcYBUCObLERABAgjAA1M/ZSa9hSi4OlpsnZ2KBOVUsAZlRGl1W0NZ6gSwhQPIAFs7YFL0QS91vRQgdgx2VDAnIegKpQvwAJIoLgooBAGtCJcHizg0TDiz8vhPgIcVXaDJu37ZyDAIjFy4AAiC1JAA0olJwN6nFAACQk/zfQVUuA3t5ELKMT9hpBSRj+HBSggYCIAIEkUoC4PCIQIWHGA8+IStxf0PW15ntP22gc+Wo+BLyQAHECQiAVd+c3Ba8gZ4NOACBaLOhsbIMkAXEmuHsSAvkf4oJaAogH6nEQAzoBIEihUAwAA0AACAObAGcANYAAABaqgMmpWAhiiVyrvNyqFV8tMQ5Iyq6r9Lf7W82i8ILl2cv8zbfdJ5lnPzSnvt/XXPfRR/5pH3xA+u5uS0798EPJ3Fft33w36Wc+8E/7aIB/9fvvb1pE9erNXLB9mkw//cSg8Cnx03VGXDvZnRzKqV9Xkhn7eRHb3wVjHpg19nkZpX9bBU+vnHegrsuQcbW9Bi7oO33h+8SU0Tly/MbeYUuG1cftVz6oNxrW0qH1iezq446fkDFo/rgvTkqt7zmteFzncZ0uxXti09FFF2z+C7CODpjUudOE5NSeb3bv/mbPKcmOEzpWQ3VBgBAuj/AOY3qMm7UvgA8qo3+qvAUBBAX0wADQG4AG+jCKaSxkBe/zvcTG8jRpRZgqhCog4sd++cOo7J+/RdvaaMoc78Ri/PNPSclx6fDvG1Kt3qQFi1rq+5EaavTtt9TVvfvucYsA5wc4oESU+E1ikF9TkrOkIsIH5Fawx7SBNLnMcWlM3skBluCmQNs7GeyFvYVDamsqwhxx18n/+WdxzDgOLU1AqbUlb3m+KOT9+ONvY/7XlrdLS//5v7nN97XHj0VzJpJvz4spq0V7ioXDuv2YrSxtF/KZmvbWfKtorXSh8dfvgGu+ev/nFSaXM6jyuCE/f/EpmuP7Rqu8R98vP/+tXUdrOgKlZP9Q/s+fRF9+3CHZd4EugfPv158me9X7v/6aSMCxMJmIr//65bNXngl7+uiPP5oKx+y27D8trp+psUzMacpm80XlnYeMAct0LynxhNYgaP3dtn8/LBAi+ksr+7NvjjtJV+UXrz0uLXV1x08AZRN1S2A8EaVE5afk/dfGjz//+fdHn36Xdttz2d9/z+bawZ8/slNN1aYtq+ZPGzNuPGdwOsfYflwmHCkPgs5DcKizvdpS/3gEUMW9/lvQvWp1bGTW1fpngC/uSVYAfH3TeSusL8TtlHUD4KAAEHjahk4YpuffGwEBN/dUmpHqdHyJ3Ap8O3UlQ4gCNrMRwLBBKiskL6a+RoBi/9XGlgc8L4/CUejQxaiIyqmomdhktOhfgDbDfaNDb4+yKIPd6IgmzDa0CByijWFmL2dlSRKagTIWXeIU9HDphoZiJeBTjAefHMxDhVSRFUgVcOkW3EGMahYVMFjWHhMluB2wAcbHYqF1LpsDF9C6s+CI2fDgh4wSuFEyGadjXAmIk3CugIRibLIti9ZtC8S4VSqfikGqPaoI122XyRYLBmsOmdiiTpqK1OklUQzpMcZmQRQV4M4oJCMkfRQXK+qvjifUcQd1bRdetW/LWjacYxvcttnVjWg5h0q4xw6rZyejSpZVZ78LzC4uyDNRQ4bymHSTMyM+SZ7D75mg/7YTlmNz7W8T00h0VEiGKB+F7iWYZFvSTiA4LVxttm2ATt5EoUWLJbY4EnLGrfsvEROlHtzlKn3H9VUT5tU/2dt3/EBv7foYzV/W4upyj04woO/gh6Vwwt3WGQAA) format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2) format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2) format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Plus Jakarta Sans Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2) format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--color-bg: #F7F8FC;--color-surface: #ffffff;--color-primary: #2E5CE5;--color-primary-hover: #1D4ED8;--color-primary-subtle: #EEF2FF;--color-secondary: #64748B;--color-secondary-hover: #475569;--color-text: #1A1F36;--color-text-secondary: #697386;--color-muted: #94A3B8;--color-success: #22C55E;--color-error: #EF4444;--color-error-bg: #FEF2F2;--color-warning: #F59E0B;--color-border: #E2E8F0;--color-border-subtle: #F1F5F9;--color-hover: rgba(15, 23, 42, .06);--color-overlay: rgba(15, 23, 42, .5);--color-input-focus: rgba(46, 92, 229, .15);--shadow-xs: 0 1px 2px rgba(15, 23, 42, .05);--shadow-sm: 0 1px 3px rgba(15, 23, 42, .06), 0 1px 2px rgba(15, 23, 42, .04);--shadow-md: 0 4px 6px -1px rgba(15, 23, 42, .07), 0 2px 4px -2px rgba(15, 23, 42, .05);--shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, .08), 0 4px 6px -4px rgba(15, 23, 42, .04);--shadow-xl: 0 20px 25px -5px rgba(15, 23, 42, .1), 0 8px 10px -6px rgba(15, 23, 42, .06);--radius-sm: 8px;--radius-md: 12px;--radius-lg: 16px;--radius-full: 50%;--font-sans: "Plus Jakarta Sans Variable", "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--space-xs: 4px;--space-sm: 8px;--space-md: 16px;--space-lg: 24px;--space-xl: 32px;--space-2xl: 48px;--transition-fast: .12s ease;--transition-base: .2s ease}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-text-size-adjust:100%}body{font-family:var(--font-sans);background:var(--color-bg);color:var(--color-text);line-height:1.6;min-height:100dvh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;overscroll-behavior-y:contain}.pull-to-refresh{position:fixed;top:-48px;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:center;pointer-events:none;z-index:40;transition:opacity .15s}.pull-to-refresh-badge{width:40px;height:40px;border-radius:50%;background:var(--color-surface);border:1px solid var(--color-border);box-shadow:var(--shadow-md);display:flex;align-items:center;justify-content:center;color:var(--color-text-secondary)}#root{min-height:100dvh}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid transparent;border-radius:var(--radius-sm);font-family:var(--font-sans);font-size:.875rem;font-weight:600;padding:10px 18px;cursor:pointer;transition:all var(--transition-base);-webkit-tap-highlight-color:transparent;letter-spacing:-.01em;line-height:1.25}.btn:disabled{opacity:.45;cursor:not-allowed}.btn:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.btn-primary{background:var(--color-primary);color:#fff;box-shadow:0 1px 2px #2e5ce54d,inset 0 1px #ffffff1a}.btn-primary:hover:not(:disabled){background:var(--color-primary-hover);box-shadow:0 2px 4px #2e5ce559,inset 0 1px #ffffff1a;transform:translateY(-.5px)}.btn-primary:active:not(:disabled){transform:translateY(0);box-shadow:0 1px 2px #2e5ce54d}.btn-secondary{background:var(--color-surface);color:var(--color-text);border-color:var(--color-border);box-shadow:var(--shadow-xs)}.btn-secondary:hover:not(:disabled){background:var(--color-border-subtle);border-color:#cbd5e1}.btn-danger{background:var(--color-error);color:#fff;box-shadow:0 1px 2px #ef44444d}.btn-danger:hover:not(:disabled){background:#dc2626;box-shadow:0 2px 4px #ef444459}.btn-link{background:none;border:none;color:var(--color-primary);padding:0;font-weight:500;box-shadow:none}.btn-link:hover:not(:disabled){text-decoration:underline;background:none;box-shadow:none}.btn-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle;margin-right:6px}.btn-sm{font-size:.8125rem;padding:6px 12px;border-radius:6px}.btn-full{width:100%}.form-label{display:flex;flex-direction:column;gap:6px;font-size:.8125rem;font-weight:600;color:var(--color-text);margin-bottom:var(--space-md);letter-spacing:-.01em}.form-input,.form-textarea,.form-select{font-family:var(--font-sans);font-size:.9375rem;padding:10px 12px;border:1.5px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);transition:border-color var(--transition-fast),box-shadow var(--transition-fast);width:100%}.form-input:focus,.form-textarea:focus,.form-select:focus{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 3px var(--color-input-focus)}.form-input-mono{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.8125rem}.agent-picker-label{font-size:.8125rem;color:var(--color-text-secondary);white-space:nowrap}.form-input::placeholder,.form-textarea::placeholder{color:var(--color-muted)}.form-textarea{resize:vertical;min-height:80px;line-height:1.5}.form-error{background:var(--color-error-bg);color:var(--color-error);border:1px solid #FECACA;border-radius:var(--radius-sm);padding:var(--space-sm) var(--space-md);font-size:.8125rem;font-weight:500;margin-bottom:var(--space-md)}.form-warning{background:#fef3c7;color:#92400e;border:1px solid #FDE68A;border-radius:var(--radius-sm);padding:var(--space-sm) var(--space-md);font-size:.8125rem;font-weight:500;margin-bottom:var(--space-md)}.dashboard{min-height:100dvh;display:flex;flex-direction:column;padding-bottom:0}.dashboard-main{flex:1;padding:var(--space-md);width:100%}.status-dot{display:inline-block;width:8px;height:8px;border-radius:var(--radius-full);flex-shrink:0;box-shadow:0 0 0 2px #fffc}.status-spinner{display:inline-flex;align-items:center;gap:2px;flex-shrink:0;height:8px}.status-spinner:before,.status-spinner>span,.status-spinner:after{content:"";display:block;width:3px;height:3px;border-radius:var(--radius-full);background-color:var(--color-success);animation:marching-dot 1.2s ease-in-out infinite}.status-spinner:before{animation-delay:0s}.status-spinner>span{animation-delay:.2s}.status-spinner:after{animation-delay:.4s}@keyframes marching-dot{0%,60%,to{opacity:.25;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.pair-page{display:flex;align-items:flex-start;justify-content:center;min-height:100dvh;padding:var(--space-xl) var(--space-md);background:var(--color-bg)}.pair-card{width:100%;max-width:420px;display:flex;flex-direction:column;gap:var(--space-lg)}.pair-header{text-align:center}.pair-title{font-size:1.5rem;font-weight:700;color:var(--color-text);letter-spacing:-.02em}.pair-subtitle{margin-top:var(--space-xs);font-size:.875rem;color:var(--color-text-secondary)}.pair-instructions{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-md)}.pair-instruction-block{display:flex;flex-direction:column;gap:var(--space-sm)}.pair-instruction-heading{font-size:.8125rem;font-weight:600;color:var(--color-text);letter-spacing:-.01em}.pair-steps{list-style:none;counter-reset:pair-step;display:flex;flex-direction:column;gap:6px;padding:0}.pair-steps li{counter-increment:pair-step;font-size:.8125rem;color:var(--color-text-secondary);line-height:1.5;display:flex;flex-wrap:wrap;gap:0 var(--space-sm)}.pair-steps li:before{content:counter(pair-step) ".";font-weight:600;color:var(--color-muted);min-width:16px;flex-shrink:0}.pair-steps li .pair-command{flex-basis:100%}.pair-steps code{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:4px;padding:1px 5px;color:var(--color-text);white-space:nowrap}.pair-command{display:block;margin-top:4px;padding:6px 10px!important;border-radius:var(--radius-sm)!important;overflow-x:auto;-webkit-overflow-scrolling:touch}.pair-instruction-divider{height:1px;background:var(--color-border)}.pair-form{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-lg);display:flex;flex-direction:column;gap:var(--space-md);box-shadow:var(--shadow-sm)}.pair-code-input{font-size:1.5rem!important;letter-spacing:.25em;text-align:center;padding:14px 12px!important;text-transform:uppercase}.pair-code-input::placeholder{letter-spacing:.25em;opacity:.35}.pair-label-hint{font-weight:400;color:var(--color-muted);font-size:.75rem}.pair-error{font-size:.8125rem;color:var(--color-error);background:var(--color-error-bg);padding:var(--space-sm) var(--space-md);border-radius:var(--radius-sm);line-height:1.4}.loading-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--space-2xl) 0;gap:var(--space-sm)}.empty-state-text{font-size:1rem;font-weight:600;color:var(--color-text-secondary)}.empty-state-hint{font-size:.8125rem;color:var(--color-muted)}.revoked-state{display:flex;flex-direction:column;align-items:center;text-align:center;padding:var(--space-2xl) var(--space-lg);margin:var(--space-md) 0;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);animation:revokedFadeIn .35s ease}@keyframes revokedFadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.revoked-icon{width:56px;height:56px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-full);background:var(--color-error-bg);color:var(--color-error);margin-bottom:var(--space-md)}.revoked-title{font-size:1.125rem;font-weight:700;color:var(--color-text);margin-bottom:var(--space-xs)}.revoked-description{font-size:.875rem;line-height:1.6;color:var(--color-text-secondary);max-width:320px;margin-bottom:var(--space-lg)}.revoked-command{padding:8px 16px;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);margin-bottom:var(--space-lg)}.revoked-command code{font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.8125rem;color:var(--color-text);letter-spacing:-.01em}.revoked-actions{display:flex;gap:var(--space-sm);width:100%;max-width:280px}.revoked-actions .btn{flex:1}.spinner{width:24px;height:24px;border:2.5px solid var(--color-border);border-top-color:var(--color-primary);border-radius:var(--radius-full);animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.session-composer{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-sm);margin-bottom:var(--space-md);display:flex;flex-direction:column;gap:var(--space-sm)}.session-composer:focus-within{border-color:var(--color-primary);box-shadow:0 0 0 2px var(--color-input-focus)}.session-composer-textarea{width:100%;border:none;resize:vertical;outline:none;background:transparent;color:var(--color-text);font-family:var(--font-sans);font-size:.9375rem;line-height:1.5;padding:var(--space-xs) var(--space-sm);min-height:3.5em}.session-composer-textarea::placeholder{color:var(--color-muted)}.session-composer-controls{display:flex;align-items:center;gap:var(--space-sm);padding-left:var(--space-xs)}.session-composer-yolo{display:inline-flex;align-items:center;gap:4px;font-size:.8rem;color:var(--color-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none}.session-composer-yolo input{margin:0;cursor:pointer}.session-composer-controls .chat-send-btn{margin-left:auto}.fab{position:fixed;right:var(--space-lg);bottom:calc(var(--space-lg) + env(safe-area-inset-bottom,0px));width:56px;height:56px;border-radius:50%;border:none;background:var(--color-primary);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:var(--shadow-lg);transition:background var(--transition-base),transform var(--transition-fast),box-shadow var(--transition-base);z-index:50;-webkit-tap-highlight-color:transparent}.fab:hover{background:var(--color-primary-hover);box-shadow:var(--shadow-xl);transform:translateY(-1px)}.fab:active{transform:translateY(0)}.fab:focus-visible{outline:2px solid var(--color-primary);outline-offset:3px}.section-label{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin:var(--space-md) 0 var(--space-xs)}.agent-picker-section-inline{display:flex;align-items:center;gap:var(--space-xs)}.agent-picker-section-inline .agent-picker-label{font-size:.8rem;color:var(--color-text-secondary);white-space:nowrap}.agent-picker-section-inline .form-select{width:auto;min-width:0;font-size:.8rem;padding:4px 8px}.task-list{display:flex;flex-direction:column;gap:10px}.task-card{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-sm);cursor:pointer;transition:box-shadow var(--transition-base),border-color var(--transition-base);-webkit-tap-highlight-color:transparent}.task-card:hover{box-shadow:var(--shadow-md);border-color:#cbd5e1}.task-card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm)}.task-card-title-row{display:flex;align-items:center;gap:10px;min-width:0;flex:1}.task-card-name{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;color:var(--color-text);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.task-card-meta{display:flex;flex-wrap:wrap;gap:var(--space-sm);font-size:.75rem;color:var(--color-text-secondary)}.task-card-agent{color:var(--color-text-tertiary)}.task-card-last-event-link{color:var(--color-primary);cursor:pointer;text-decoration:underline}.task-card-actions{display:flex;gap:var(--space-xs);flex-shrink:0}.task-card-menu{position:relative}.task-card-menu-btn{background:none;border:none;color:var(--color-text-secondary);font-size:1.5rem;min-width:44px;min-height:44px;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);cursor:pointer;line-height:1}.task-card-menu-btn:hover{background:var(--color-hover);color:var(--color-text)}.task-card-menu-dropdown{position:absolute;right:0;top:100%;margin-top:4px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-md);min-width:150px;z-index:10;overflow:hidden}.task-card-menu-dropdown button{display:flex;align-items:center;gap:var(--space-sm);width:100%;text-align:left;padding:var(--space-sm) var(--space-md);background:none;border:none;font-size:.9375rem;color:var(--color-text);cursor:pointer;white-space:nowrap}.task-card-menu-dropdown button:hover{background:var(--color-hover)}.task-card-menu-dropdown .menu-item-danger{color:var(--color-error)}.menu-icon{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;font-size:.875rem;flex-shrink:0;line-height:1}.bottom-sheet-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);z-index:1000;display:flex;align-items:flex-end;justify-content:center;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);animation:fadeIn var(--transition-fast)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.bottom-sheet{background:var(--color-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;width:100%;max-width:480px;padding:var(--space-sm) var(--space-md) var(--space-xl);box-shadow:var(--shadow-xl);animation:sheetSlideUp .25s cubic-bezier(.16,1,.3,1)}@keyframes sheetSlideUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.bottom-sheet-handle{width:36px;height:4px;background:var(--color-border);border-radius:2px;margin:0 auto var(--space-md)}.bottom-sheet-title{font-size:.9375rem;font-weight:600;color:var(--color-text);padding:0 var(--space-xs) var(--space-md);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bottom-sheet-actions{display:flex;flex-direction:column}.bottom-sheet-actions button{display:flex;align-items:center;gap:var(--space-md);width:100%;text-align:left;padding:var(--space-md);background:none;border:none;border-radius:var(--radius-sm);font-size:.9375rem;font-family:var(--font-sans);color:var(--color-text);cursor:pointer;transition:background var(--transition-fast)}.bottom-sheet-actions button:active{background:var(--color-hover)}.bottom-sheet-actions .menu-item-danger{color:var(--color-error)}.task-form-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:transparent;display:flex;align-items:stretch;justify-content:center;z-index:100;padding:0;overflow:hidden;overscroll-behavior:contain}.task-form{background:var(--color-surface);border-radius:0;padding:var(--space-lg) var(--space-md);width:100%;max-width:none;max-height:none;overflow-y:auto;animation:slideUp .25s cubic-bezier(.16,1,.3,1);display:flex;flex-direction:column;gap:var(--space-md)}@media(min-width:600px){.task-form-overlay{background:var(--color-overlay);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);align-items:center;padding:var(--space-md)}.task-form{border-radius:var(--radius-lg);box-shadow:var(--shadow-xl);max-width:540px;max-height:90dvh;padding:var(--space-lg)}}@keyframes slideUp{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}.task-form-header{display:flex;align-items:center;justify-content:space-between}.task-form h2{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;margin-bottom:0}.plan-actions{display:flex;gap:var(--space-sm);flex-wrap:wrap}.plan-dialog{display:flex;flex-direction:column;gap:var(--space-md);overflow:hidden;flex:1;min-height:0}.plan-dialog h2{margin:0;font-size:1.125rem}.plan-dialog-scroll{flex:1;min-height:0;overflow-y:auto}.plan-dialog .plan-preview{max-height:none}.plan-empty{color:var(--color-text-secondary);font-size:.875rem;font-style:italic;margin:var(--space-md) 0}.permissions-section{border-top:1px solid var(--color-border);padding-top:var(--space-md);margin-top:var(--space-md)}.permissions-section h3{margin:0 0 var(--space-xs) 0;font-size:.95rem}.permissions-list{margin:0;padding-left:1.25em}.permission-item{margin-bottom:var(--space-sm);display:flex;flex-direction:column}.permission-tool{font-weight:600;font-family:var(--font-mono, monospace)}.permission-desc{color:var(--color-text-secondary);font-size:.9em}.plan-dialog-actions{display:flex;gap:var(--space-sm);align-items:center;justify-content:flex-end}@media(min-width:600px){.plan-dialog{max-width:none}}.result-times{display:flex;flex-direction:column;gap:var(--space-xs);font-size:.8125rem;color:var(--color-text-secondary);margin-bottom:var(--space-sm)}.skeleton-line{height:.875rem;border-radius:var(--radius-sm);background:var(--color-border);animation:skeleton-pulse 1.2s ease-in-out infinite}@keyframes skeleton-pulse{0%,to{opacity:.4}50%{opacity:1}}.plan-preview{font-size:.8125rem;line-height:1.6;color:var(--color-text-secondary)}.plan-preview h1,.plan-preview h2,.plan-preview h3,.plan-preview h4{color:var(--color-text);margin:var(--space-sm) 0 var(--space-xs) 0}.plan-preview h1{font-size:1.1rem}.plan-preview h2{font-size:1rem}.plan-preview h3{font-size:.9375rem}.plan-preview p{margin:var(--space-xs) 0}.plan-preview ul,.plan-preview ol{padding-left:var(--space-md);margin:var(--space-xs) 0}.plan-preview code{background:var(--color-border);border-radius:3px;padding:1px 4px;font-family:SF Mono,Fira Code,Cascadia Code,monospace;font-size:.75rem}.plan-preview pre{background:var(--color-border);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto}.plan-preview pre code{background:none;padding:0}.plan-preview table{width:100%;border-collapse:collapse;margin:var(--space-xs) 0}.plan-preview th,.plan-preview td{border:1px solid var(--color-border);padding:var(--space-xs) var(--space-sm);text-align:left}.plan-preview th{background:var(--color-border);color:var(--color-text);font-weight:600}.granted-permissions-row{flex-basis:100%}.granted-permissions-row .btn-link{padding:0}.schedule-section{display:flex;flex-direction:column;gap:var(--space-sm)}.schedule-section-title{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin:0}.schedule-reactive{display:flex;flex-direction:column;gap:var(--space-xs)}.yolo-inline{display:inline-flex;align-items:center;gap:4px;font-size:.8rem;color:var(--color-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap}.yolo-inline input{margin:0;cursor:pointer}.yolo-warning{flex-basis:100%;margin:0;font-size:.75rem;color:var(--color-text-secondary);line-height:1.4}.trigger-row-card{display:flex;align-items:center;gap:8px;padding:4px 0}.trigger-row-content{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}.trigger-row-top{display:flex;align-items:center;gap:8px}.trigger-row-top .form-select{flex:0 0 auto;width:auto}.trigger-row-top .form-input{flex:1}.trigger-details{display:flex;gap:6px}.trigger-details .form-input[type=date]{flex:1;min-width:0}.trigger-details .form-input[type=time]{flex:0 0 auto;width:auto}.schedule-section>.form-select,.trigger-row-card .form-select,.trigger-row-card .form-input{margin-bottom:0;font-size:.8125rem;padding:6px 8px;height:32px;box-sizing:border-box;min-width:0}.schedule-section>.form-select{width:100%}.trigger-row-card .form-select,.trigger-row-card .form-input{flex:1}.trigger-remove-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:none;border-radius:var(--radius-full);background:transparent;color:var(--color-muted);font-size:1.1rem;line-height:1;cursor:pointer;transition:all var(--transition-fast);flex-shrink:0}.trigger-remove-btn:hover{background:var(--color-error-bg);color:var(--color-error)}.trigger-add-btn{display:inline-flex;align-items:center;gap:4px;border:1px dashed var(--color-border);border-radius:var(--radius-sm);background:transparent;color:var(--color-text-secondary);font-family:var(--font-sans);font-size:.8125rem;font-weight:500;padding:8px 14px;cursor:pointer;transition:all var(--transition-fast);width:100%;justify-content:center}.trigger-add-btn:hover{border-color:var(--color-primary);color:var(--color-primary);background:var(--color-primary-subtle)}.form-select{width:auto;min-width:80px}.toggles-section{display:flex;flex-direction:column;gap:10px;margin-bottom:var(--space-md)}.toggle-label{display:flex;align-items:center;gap:10px;font-size:.875rem;font-weight:500;cursor:pointer;-webkit-tap-highlight-color:transparent;color:var(--color-text)}.toggle-label input[type=checkbox]{width:16px;height:16px;accent-color:var(--color-primary);border-radius:4px}.toggles-group{display:flex;flex-direction:column;gap:var(--space-xs)}.command-section,.command-section-active .toggle-label{margin-bottom:0}.command-help-text{font-size:.75rem;color:var(--color-text-secondary);line-height:1.4;margin:6px 0 10px}.command-section-active .form-input{font-size:.8125rem;padding:6px 8px;height:32px;box-sizing:border-box}.form-actions{display:flex;gap:var(--space-sm);position:sticky;bottom:0;background:var(--color-surface);padding:var(--space-sm) 0}.confirm-modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);display:flex;align-items:center;justify-content:center;z-index:1000;padding:var(--space-md);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.confirm-modal{background:var(--color-surface);border-radius:var(--radius-lg);border:1px solid var(--color-border);padding:var(--space-xl);width:100%;max-width:380px;text-align:center;box-shadow:var(--shadow-xl);animation:slideUp .25s cubic-bezier(.16,1,.3,1)}.confirm-modal-title{font-size:1.125rem;font-weight:700;letter-spacing:-.02em;margin-bottom:var(--space-xs)}.confirm-modal-subtitle{font-size:.8rem;color:var(--color-muted);margin-bottom:var(--space-sm)}.confirm-modal-message{font-size:.9375rem;color:var(--color-text-secondary);margin-bottom:var(--space-lg);line-height:1.5}.confirm-modal-actions{display:flex;gap:var(--space-sm);justify-content:center}.confirm-modal-actions .btn{flex:1;padding:10px var(--space-lg);font-size:.875rem}.permission-modal{text-align:left;max-width:400px}.permission-modal .confirm-modal-title{text-align:center}.permission-modal .confirm-modal-message{text-align:center;word-break:break-word}.permission-list{display:flex;flex-direction:column;gap:var(--space-xs);margin-bottom:var(--space-lg)}.permission-item{display:flex;flex-direction:column;gap:2px;padding:var(--space-sm);background:var(--color-hover);border-radius:var(--radius-sm)}.permission-name{font-size:.8125rem;font-weight:600;color:var(--color-text)}.permission-desc{font-size:.75rem;color:var(--color-text-secondary);line-height:1.4}.permission-actions{display:flex;gap:var(--space-sm)}.permission-actions .btn{flex:1;padding:10px var(--space-md);font-size:.875rem}.permission-abort-link{display:block;width:100%;margin-top:var(--space-sm);background:none;border:none;color:var(--color-error);font-size:.8125rem;cursor:pointer;text-align:center;padding:var(--space-xs) 0;opacity:.8}.permission-abort-link:hover{opacity:1;text-decoration:underline}.input-modal{text-align:left;max-width:400px}.input-modal .confirm-modal-title{text-align:center}.input-modal .confirm-modal-message{text-align:center;word-break:break-word}.input-list{display:flex;flex-direction:column;gap:var(--space-md);margin-bottom:var(--space-lg)}.input-item{display:flex;flex-direction:column;gap:var(--space-xs)}.input-label{font-size:.8125rem;font-weight:600;color:var(--color-text)}.input-field{width:100%;padding:var(--space-sm) var(--space-md);font-size:.875rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);box-sizing:border-box}.input-field:focus{outline:none;border-color:var(--color-primary);box-shadow:0 0 0 2px rgba(var(--color-primary-rgb, 99, 102, 241),.2)}.input-actions{display:flex;gap:var(--space-sm)}.input-actions .btn{flex:1;padding:10px var(--space-md);font-size:.875rem}.host-picker-inline{border:1px solid var(--color-border);border-radius:var(--radius-md);overflow:hidden}.host-picker-list{max-height:240px;overflow-y:auto;padding:var(--space-xs) 0}.host-picker-item-wrapper{position:relative}.host-picker-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:none;background:none;cursor:pointer;font-family:var(--font-sans);font-size:.8125rem;color:var(--color-text);text-align:left;transition:background var(--transition-fast);-webkit-tap-highlight-color:transparent}.host-picker-item:hover{background:var(--color-border-subtle)}.host-picker-item-active,.host-picker-item-active:hover{background:var(--color-primary-subtle)}.host-picker-item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:6px}.host-picker-pending{font-size:.6875rem;font-weight:500;color:var(--color-muted);background:var(--color-border-subtle);padding:1px 6px;border-radius:4px;flex-shrink:0}.host-picker-item-actions{flex-shrink:0;width:24px;display:flex;align-items:center;justify-content:center}.host-picker-edit-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:none;cursor:pointer;color:var(--color-muted);border-radius:4px;flex-shrink:0;transition:all var(--transition-fast)}.host-picker-edit-btn:hover{color:var(--color-primary);background:var(--color-primary-subtle)}.host-picker-rename-input{font-size:.8125rem!important;padding:3px 6px!important;flex:1;min-width:0}.host-picker-delete-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:none;cursor:pointer;color:var(--color-muted);border-radius:4px;flex-shrink:0;transition:all var(--transition-fast)}.host-picker-delete-btn:hover{color:var(--color-error);background:var(--color-error-bg)}.hamburger-btn{display:flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:none;background:none;color:var(--color-text);cursor:pointer;border-radius:var(--radius-sm);transition:background var(--transition-fast)}.hamburger-btn:hover{background:var(--color-primary-subtle)}.drawer-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--color-overlay);z-index:100;animation:drawerFadeIn .2s ease}.drawer-panel{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:80vw;background:var(--color-surface);box-shadow:var(--shadow-xl);z-index:101;display:flex;flex-direction:column;animation:drawerSlideIn .25s ease}.drawer-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-md);border-bottom:1px solid var(--color-border)}.drawer-title{font-size:1.125rem;font-weight:800;color:var(--color-primary);letter-spacing:-.04em}.drawer-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:none;background:none;color:var(--color-text-secondary);cursor:pointer;border-radius:var(--radius-sm);transition:background var(--transition-fast)}.drawer-close-btn:hover{background:var(--color-border-subtle);color:var(--color-text)}.drawer-section{padding:var(--space-md);display:flex;flex-direction:column;gap:var(--space-sm)}.drawer-section-label{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--color-muted);margin-bottom:var(--space-sm)}.drawer-toggle{display:flex;align-items:center;justify-content:space-between;gap:var(--space-sm)}.drawer-toggle-label{font-size:.85rem;color:var(--color-text)}.toggle-switch{position:relative;width:40px;height:22px;border-radius:11px;border:none;background:var(--color-border);cursor:pointer;padding:0;transition:background .2s;flex-shrink:0}.toggle-switch-on{background:var(--color-primary)}.toggle-switch-thumb{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:#fff;transition:transform .2s}.toggle-switch-on .toggle-switch-thumb{transform:translate(18px)}.toggle-switch:disabled{opacity:.5;cursor:not-allowed}.drawer-footer{margin-top:auto;padding:var(--space-md)}.drawer-version{font-size:.75rem;color:var(--color-muted)}.drawer-legal{font-size:.75rem;color:var(--color-muted);margin-top:var(--space-xs)}.drawer-legal a{color:var(--color-muted);text-decoration:none}.drawer-legal a:hover{text-decoration:underline}.drawer-legal-sep{margin:0 var(--space-xs)}.drawer-divider{height:1px;background:var(--color-border);margin:0 var(--space-md)}@keyframes drawerFadeIn{0%{opacity:0}to{opacity:1}}@keyframes drawerFadeOut{0%{opacity:1}to{opacity:0}}@keyframes drawerSlideIn{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes drawerSlideOut{0%{transform:translate(0)}to{transform:translate(-100%)}}.drawer-overlay-closing{animation:drawerFadeOut .2s ease forwards}.drawer-panel-closing{animation:drawerSlideOut .2s ease forwards}.tab-bar{display:flex;align-items:center;background:color-mix(in srgb,var(--color-surface) 92%,transparent);border-bottom:1px solid var(--color-border);position:sticky;top:0;z-index:10;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:12px 0;border:none;background:none;font-family:var(--font-sans);font-size:.875rem;font-weight:600;color:var(--color-text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:color var(--transition-fast),border-color var(--transition-fast)}.tab-icon{flex-shrink:0}.tab-btn:hover{color:var(--color-text)}.tab-btn-active{color:var(--color-primary);border-bottom-color:var(--color-primary)}.sessions-view{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.sessions-card{background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md);display:flex;align-items:center;gap:var(--space-sm);cursor:pointer;transition:box-shadow var(--transition-base),border-color var(--transition-base);-webkit-tap-highlight-color:transparent}.sessions-card:hover{box-shadow:var(--shadow-md);border-color:#cbd5e1}.sessions-card-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-xs)}.sessions-card-name{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;color:var(--color-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sessions-card-meta{display:flex;flex-wrap:wrap;gap:var(--space-sm);font-size:.75rem;color:var(--color-text-secondary)}.sessions-card-chevron{flex-shrink:0;align-self:center;color:var(--color-text-secondary);font-size:1.25rem;line-height:1;opacity:.4;transition:opacity var(--transition-base)}.sessions-card:hover .sessions-card-chevron{opacity:.8}.sessions-filter-chip{display:inline-flex;align-items:center;gap:var(--space-xs);padding:4px 10px;font-size:.8125rem;color:var(--color-text-secondary);background:var(--color-bg);border:1px solid var(--color-border);border-radius:999px}.sessions-filter-chip button{display:inline-flex;align-items:center;justify-content:center;border:none;background:none;cursor:pointer;padding:0;color:var(--color-text-secondary);font-size:1rem;line-height:1}.sessions-filter-chip button:hover{color:var(--color-text)}.run-detail{display:flex;flex-direction:column;gap:var(--space-md);background:var(--color-surface);border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-sm);padding:var(--space-md)}.run-detail-back{display:inline-flex;align-items:center;gap:var(--space-xs);border:none;background:none;cursor:pointer;padding:0;color:var(--color-text-secondary);font-size:.8125rem}.run-detail-back:hover{color:var(--color-text)}.chat-thread{display:flex;flex-direction:column;gap:var(--space-sm);overflow-y:auto;flex:1;min-height:0}.chat-message{display:flex;flex-direction:column;gap:var(--space-xs);max-width:85%;min-width:0;padding:var(--space-sm) var(--space-md);border-radius:var(--radius-md);font-size:.8125rem;line-height:1.6}.chat-message--assistant{align-self:flex-start;background:var(--color-surface);border:1px solid var(--color-border)}.chat-message--user{align-self:flex-end;background:var(--color-primary-subtle);border:1px solid var(--color-border-subtle)}.chat-message-agent{font-size:.6875rem;font-weight:500;color:var(--color-text-tertiary);margin-bottom:var(--space-xs)}.chat-message-content{color:var(--color-text);overflow-wrap:break-word}.chat-message-content h1,.chat-message-content h2,.chat-message-content h3,.chat-message-content h4{margin:.75em 0 .25em;font-weight:600}.chat-message-content h1{font-size:1.1rem}.chat-message-content h2{font-size:1rem}.chat-message-content h3{font-size:.9375rem}.chat-message-content p{margin:.5em 0}.chat-message-content ul,.chat-message-content ol{margin:.5em 0;padding-left:1.5em}.chat-message-content code{font-size:.8em;background:var(--color-hover);padding:.15em .35em;border-radius:4px}.chat-message-content pre{background:var(--color-bg);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto;margin:.5em 0}.chat-message-content pre code{background:none;padding:0}.chat-message-content table{border-collapse:collapse;width:100%;margin:.5em 0}.chat-message-content th,.chat-message-content td{border:1px solid var(--color-border);padding:.35em .6em;text-align:left}.chat-message-content th{background:var(--color-bg);font-weight:600}.chat-message-meta{display:flex;align-items:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted)}.chat-message-type{font-weight:600;text-transform:uppercase;letter-spacing:.04em;font-size:.625rem}.chat-message-attachments{display:flex;flex-wrap:wrap;gap:var(--space-xs)}.chat-attachment-chip{display:inline-flex;align-items:center;padding:.2em .6em;font-size:.75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);cursor:pointer;color:var(--color-primary);font-weight:500}.chat-attachment-chip:hover{background:var(--color-primary-subtle)}.chat-message-report{margin-top:var(--space-xs);padding:var(--space-sm);background:var(--color-bg);border-radius:var(--radius-sm);border:1px solid var(--color-border);font-size:.8125rem;line-height:1.6;color:var(--color-text-secondary)}.chat-status{display:flex;flex-direction:column;align-items:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted);padding:var(--space-xs) 0}.chat-status>div{display:flex;align-items:center;gap:var(--space-xs)}.chat-status--error{color:var(--color-error)}.chat-status-detail{margin-top:var(--space-xs);padding:var(--space-sm);background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);font-size:.75rem;font-family:SF Mono,Fira Code,monospace;white-space:pre-wrap;word-break:break-word;max-width:100%;max-height:200px;overflow-y:auto;color:var(--color-text-secondary)}.chat-status-time{color:var(--color-muted)}.chat-monitoring-indicator{display:flex;align-items:center;justify-content:center;gap:var(--space-xs);font-size:.6875rem;color:var(--color-muted);padding:var(--space-md) 0}.chat-monitoring-dot{width:6px;height:6px;border-radius:50%;background:var(--color-muted);animation:monitoring-pulse 1.5s ease-in-out infinite}@keyframes monitoring-pulse{0%,to{opacity:.3}50%{opacity:1}}.chat-abort-bar{display:flex;justify-content:center;padding:var(--space-sm) 0;flex-shrink:0}.chat-abort-btn{color:var(--color-error);border-color:var(--color-error)}.chat-abort-btn:hover:not(:disabled){background:var(--color-error-bg)}.chat-stop-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border-radius:50%;flex-shrink:0;margin-left:auto;background:var(--color-error);color:#fff;border-color:var(--color-error)}.chat-stop-btn:hover:not(:disabled){background:#dc2626;border-color:#dc2626}.chat-input-bar{display:flex;gap:var(--space-xs);padding:var(--space-sm) 0;flex-shrink:0}.chat-input{flex:1;min-width:0;padding:var(--space-sm) var(--space-md);border:1px solid var(--color-border);border-radius:var(--radius-md);font-size:.8125rem;outline:none;background:var(--color-surface);color:var(--color-text)}.chat-input:focus{border-color:var(--color-primary);box-shadow:0 0 0 2px var(--color-primary-subtle)}.chat-input:disabled{opacity:.6}.chat-send-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border-radius:50%;flex-shrink:0}.report-dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;background:var(--color-overlay);display:flex;align-items:stretch;justify-content:center}.report-dialog{display:flex;flex-direction:column;width:100%;max-width:800px;background:var(--color-surface);overflow:hidden}.report-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-sm) var(--space-md);border-bottom:1px solid var(--color-border);flex-shrink:0}.report-dialog-title{font-size:.875rem;font-weight:600;color:var(--color-text)}.report-dialog-close{display:flex;align-items:center;justify-content:center;border:none;background:none;cursor:pointer;color:var(--color-text-secondary);padding:4px;border-radius:var(--radius-sm)}.report-dialog-close:hover{background:var(--color-hover);color:var(--color-text)}.report-dialog-body{flex:1;overflow-y:auto;padding:var(--space-md);font-size:.8125rem;line-height:1.6;color:var(--color-text)}.report-dialog-body h1,.report-dialog-body h2,.report-dialog-body h3,.report-dialog-body h4{margin:.75em 0 .25em;font-weight:600}.report-dialog-body h1{font-size:1.1rem}.report-dialog-body h2{font-size:1rem}.report-dialog-body h3{font-size:.9375rem}.report-dialog-body p{margin:.5em 0}.report-dialog-body img{max-width:100%;height:auto}.report-dialog-body ul,.report-dialog-body ol{margin:.5em 0;padding-left:1.5em}.report-dialog-body code{font-size:.8em;background:var(--color-hover);padding:.15em .35em;border-radius:4px}.report-dialog-body pre{background:var(--color-bg);border-radius:var(--radius-sm);padding:var(--space-sm);overflow-x:auto;margin:.5em 0}.report-dialog-body pre code{background:none;padding:0}.report-dialog-body table{border-collapse:collapse;width:100%;margin:.5em 0}.report-dialog-body th,.report-dialog-body td{border:1px solid var(--color-border);padding:.35em .6em;text-align:left}.report-dialog-body th{background:var(--color-bg);font-weight:600}.chat-typing-indicator{display:flex;align-items:center;gap:4px;padding:4px 0}.chat-typing-indicator span{width:6px;height:6px;border-radius:50%;background:var(--color-muted);animation:chat-typing 1.4s infinite}.chat-typing-indicator span:nth-child(2){animation-delay:.2s}.chat-typing-indicator span:nth-child(3){animation-delay:.4s}@keyframes chat-typing{0%,60%,to{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.pair-consent{font-size:.75rem;color:var(--color-muted);text-align:center;line-height:1.5}.pair-consent a{color:var(--color-muted);text-decoration:underline}.pair-consent a:hover{color:var(--color-text-secondary)}.dashboard-content{display:flex;flex-direction:column;flex:1;min-height:100dvh;max-width:800px;margin:0 auto;width:100%}.drawer-panel-desktop{position:sticky;top:0;height:100dvh;width:280px;min-width:280px;background:var(--color-surface);border-right:1px solid var(--color-border);display:flex;flex-direction:column;overflow-y:auto;animation:none}@media(min-width:768px){.dashboard{flex-direction:row}.dashboard-main{padding:var(--space-lg)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as t}from"./index-
|
|
1
|
+
import{W as t}from"./index-FP1Mipr6.js";class s extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{s as AppWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{W as p}from"./index-
|
|
1
|
+
import{W as p}from"./index-FP1Mipr6.js";class f extends p{constructor(){super(...arguments),this.group="CapacitorStorage"}async configure({group:e}){typeof e=="string"&&(this.group=e)}async get(e){return{value:this.impl.getItem(this.applyPrefix(e.key))}}async set(e){this.impl.setItem(this.applyPrefix(e.key),e.value)}async remove(e){this.impl.removeItem(this.applyPrefix(e.key))}async keys(){return{keys:this.rawKeys().map(t=>t.substring(this.prefix.length))}}async clear(){for(const e of this.rawKeys())this.impl.removeItem(e)}async migrate(){var e;const t=[],s=[],n="_cap_",o=Object.keys(this.impl).filter(i=>i.indexOf(n)===0);for(const i of o){const r=i.substring(n.length),a=(e=this.impl.getItem(i))!==null&&e!==void 0?e:"",{value:l}=await this.get({key:r});typeof l=="string"?s.push(r):(await this.set({key:r,value:a}),t.push(r))}return{migrated:t,existing:s}}async removeOld(){const e="_cap_",t=Object.keys(this.impl).filter(s=>s.indexOf(e)===0);for(const s of t)this.impl.removeItem(s)}get impl(){return window.localStorage}get prefix(){return this.group==="NativeStorage"?"":`${this.group}.`}rawKeys(){return Object.keys(this.impl).filter(e=>e.indexOf(this.prefix)===0)}applyPrefix(e){return this.prefix+e}}export{f as PreferencesWeb};
|
package/dist/pwa/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
9
9
|
<title>Palmier</title>
|
|
10
10
|
<meta name="description" content="Remote control for AI agents running on your own machine. Schedule tasks, approve permissions, and get push notifications." />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-FP1Mipr6.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-bLTn8zBj.css">
|
|
13
13
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
try{self["workbox:core:7.3.0"]&&_()}catch{}const N=(n,...e)=>{let t=n;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},E=N;class h extends Error{constructor(e,t){const s=E(e,t);super(s),this.name=e,this.details=t}}const f={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},U=n=>[f.prefix,n,f.suffix].filter(e=>e&&e.length>0).join("-"),O=n=>{for(const e of Object.keys(f))n(e)},L={updateDetails:n=>{O(e=>{typeof n[e]=="string"&&(f[e]=n[e])})},getGoogleAnalyticsName:n=>n||U(f.googleAnalytics),getPrecacheName:n=>n||U(f.precache),getPrefix:()=>f.prefix,getRuntimeName:n=>n||U(f.runtime),getSuffix:()=>f.suffix};function v(n,e){const t=e();return n.waitUntil(t),t}try{self["workbox:precaching:7.3.0"]&&_()}catch{}const A="__WB_REVISION__";function M(n){if(!n)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(typeof n=="string"){const i=new URL(n,location.href);return{cacheKey:i.href,url:i.href}}const{revision:e,url:t}=n;if(!t)throw new h("add-to-cache-list-unexpected-type",{entry:n});if(!e){const i=new URL(t,location.href);return{cacheKey:i.href,url:i.href}}const s=new URL(t,location.href),a=new URL(t,location.href);return s.searchParams.set(A,e),{cacheKey:s.href,url:a.href}}class W{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if(e.type==="install"&&t&&t.originalRequest&&t.originalRequest instanceof Request){const a=t.originalRequest.url;s?this.notUpdatedURLs.push(a):this.updatedURLs.push(a)}return s}}}class q{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:s})=>{const a=(s==null?void 0:s.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return a?new Request(a,{headers:t.headers}):t},this._precacheController=e}}let w;function S(){if(w===void 0){const n=new Response("");if("body"in n)try{new Response(n.body),w=!0}catch{w=!1}w=!1}return w}async function j(n,e){let t=null;if(n.url&&(t=new URL(n.url).origin),t!==self.location.origin)throw new h("cross-origin-copy-response",{origin:t});const s=n.clone(),i={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},r=S()?s.body:await s.blob();return new Response(r,i)}const D=n=>new URL(String(n),location.href).href.replace(new RegExp(`^${location.origin}`),"");function T(n,e){const t=new URL(n);for(const s of e)t.searchParams.delete(s);return t.href}async function H(n,e,t,s){const a=T(e.url,t);if(e.url===a)return n.match(e,s);const i=Object.assign(Object.assign({},s),{ignoreSearch:!0}),r=await n.keys(e,i);for(const c of r){const o=T(c.url,t);if(a===o)return n.match(c,s)}}class F{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}const B=new Set;async function $(){for(const n of B)await n()}function V(n){return new Promise(e=>setTimeout(e,n))}try{self["workbox:strategies:7.3.0"]&&_()}catch{}function C(n){return typeof n=="string"?new Request(n):n}class G{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new F,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const s of this._plugins)this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let s=C(e);if(s.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const r=await t.preloadResponse;if(r)return r}const a=this.hasCallback("fetchDidFail")?s.clone():null;try{for(const r of this.iterateCallbacks("requestWillFetch"))s=await r({request:s.clone(),event:t})}catch(r){if(r instanceof Error)throw new h("plugin-error-request-will-fetch",{thrownErrorMessage:r.message})}const i=s.clone();try{let r;r=await fetch(s,s.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const c of this.iterateCallbacks("fetchDidSucceed"))r=await c({event:t,request:i,response:r});return r}catch(r){throw a&&await this.runCallbacks("fetchDidFail",{error:r,event:t,originalRequest:a.clone(),request:i.clone()}),r}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=C(e);let s;const{cacheName:a,matchOptions:i}=this._strategy,r=await this.getCacheKey(t,"read"),c=Object.assign(Object.assign({},i),{cacheName:a});s=await caches.match(r,c);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await o({cacheName:a,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s}async cachePut(e,t){const s=C(e);await V(0);const a=await this.getCacheKey(s,"write");if(!t)throw new h("cache-put-with-no-response",{url:D(a.url)});const i=await this._ensureResponseSafeToCache(t);if(!i)return!1;const{cacheName:r,matchOptions:c}=this._strategy,o=await self.caches.open(r),l=this.hasCallback("cacheDidUpdate"),d=l?await H(o,a.clone(),["__WB_REVISION__"],c):null;try{await o.put(a,l?i.clone():i)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await $(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:r,oldResponse:d,newResponse:i.clone(),request:a,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let a=e;for(const i of this.iterateCallbacks("cacheKeyWillBeUsed"))a=C(await i({mode:t,request:a,event:this.event,params:this.params}));this._cacheKeys[s]=a}return this._cacheKeys[s]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const s=this._pluginStateMap.get(t);yield i=>{const r=Object.assign(Object.assign({},i),{state:s});return t[e](r)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),s=(await Promise.allSettled(e)).find(a=>a.status==="rejected");if(s)throw s.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(const a of this.iterateCallbacks("cacheWillUpdate"))if(t=await a({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&t.status!==200&&(t=void 0),t}}class J{constructor(e={}){this.cacheName=L.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s=typeof e.request=="string"?new Request(e.request):e.request,a="params"in e?e.params:void 0,i=new G(this,{event:t,request:s,params:a}),r=this._getResponse(i,s,t),c=this._awaitComplete(r,i,s,t);return[r,c]}async _getResponse(e,t,s){await e.runCallbacks("handlerWillStart",{event:s,request:t});let a;try{if(a=await this._handle(t,e),!a||a.type==="error")throw new h("no-response",{url:t.url})}catch(i){if(i instanceof Error){for(const r of e.iterateCallbacks("handlerDidError"))if(a=await r({error:i,event:s,request:t}),a)break}if(!a)throw i}for(const i of e.iterateCallbacks("handlerWillRespond"))a=await i({event:s,request:t,response:a});return a}async _awaitComplete(e,t,s,a){let i,r;try{i=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:a,request:s,response:i}),await t.doneWaiting()}catch(c){c instanceof Error&&(r=c)}if(await t.runCallbacks("handlerDidComplete",{event:a,request:s,response:i,error:r}),t.destroy(),r)throw r}}class p extends J{constructor(e={}){e.cacheName=L.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(p.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const s=await t.cacheMatch(e);return s||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s;const a=t.params||{};if(this._fallbackToNetwork){const i=a.integrity,r=e.integrity,c=!r||r===i;s=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?r||i:void 0})),i&&c&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new h("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new h("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[s,a]of this.plugins.entries())a!==p.copyRedirectedCacheableResponsesPlugin&&(a===p.defaultPrecacheCacheabilityPlugin&&(e=s),a.cacheWillUpdate&&t++);t===0?this.plugins.push(p.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}p.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:n}){return!n||n.status>=400?null:n}};p.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:n}){return n.redirected?await j(n):n}};class Q{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new p({cacheName:L.getPrecacheName(e),plugins:[...t,new q({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const s of e){typeof s=="string"?t.push(s):s&&s.revision===void 0&&t.push(s.url);const{cacheKey:a,url:i}=M(s),r=typeof s!="string"&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==a)throw new h("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:a});if(typeof s!="string"&&s.integrity){if(this._cacheKeysToIntegrities.has(a)&&this._cacheKeysToIntegrities.get(a)!==s.integrity)throw new h("add-to-cache-list-conflicting-integrities",{url:i});this._cacheKeysToIntegrities.set(a,s.integrity)}if(this._urlsToCacheKeys.set(i,a),this._urlsToCacheModes.set(i,r),t.length>0){const c=`Workbox is precaching URLs without revision info: ${t.join(", ")}
|
|
2
|
-
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new W;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const x=()=>(k||(k=new Q),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const I="GET",b=n=>n&&typeof n=="object"?n:{handle:n};class R{constructor(e,t,s=I){this.handler=b(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=b(e)}}class z extends R{constructor(e,t,s){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,s)}}class X{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const a=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:s,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const d=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||d)&&(l=l.catch(async u=>{if(d)try{return await d.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=I){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let m;const Y=()=>(m||(m=new X,m.addFetchListener(),m.addCacheListener()),m);function Z(n,e,t){let s;if(typeof n=="string"){const i=new URL(n,location.href),r=({url:c})=>c.href===i.href;s=new R(r,e,t)}else if(n instanceof RegExp)s=new z(n,e,t);else if(typeof n=="function")s=new R(n,e,t);else if(n instanceof R)s=n;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return Y().registerRoute(s),s}function ee(n,e=[]){for(const t of[...n.searchParams.keys()])e.some(s=>s.test(t))&&n.searchParams.delete(t);return n}function*te(n,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:a}={}){const i=new URL(n,location.href);i.hash="",yield i.href;const r=ee(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class se extends R{constructor(e,t){const s=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of te(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function ne(n){const e=x(),t=new se(e,n);Z(t)}function ae(n){x().precache(n)}function ie(n,e){ae(n),ne(e)}ie([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"
|
|
2
|
+
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(c)}}}install(e){return v(e,async()=>{const t=new W;this.strategy.plugins.push(t);for(const[i,r]of this._urlsToCacheKeys){const c=this._cacheKeysToIntegrities.get(r),o=this._urlsToCacheModes.get(i),l=new Request(i,{integrity:c,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:r},request:l,event:e}))}const{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return v(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),a=new Set(this._urlsToCacheKeys.values()),i=[];for(const r of s)a.has(r.url)||(await t.delete(r),i.push(r.url));return{deletedURLs:i}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new h("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k;const x=()=>(k||(k=new Q),k);try{self["workbox:routing:7.3.0"]&&_()}catch{}const I="GET",b=n=>n&&typeof n=="object"?n:{handle:n};class R{constructor(e,t,s=I){this.handler=b(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=b(e)}}class z extends R{constructor(e,t,s){const a=({url:i})=>{const r=e.exec(i.href);if(r&&!(i.origin!==location.origin&&r.index!==0))return r.slice(1)};super(a,t,s)}}class X{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(a=>{typeof a=="string"&&(a=[a]);const i=new Request(...a);return this.handleRequest({request:i,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const a=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:t,request:e,sameOrigin:a,url:s});let c=r&&r.handler;const o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;let l;try{l=c.handle({url:s,request:e,event:t,params:i})}catch(u){l=Promise.reject(u)}const d=r&&r.catchHandler;return l instanceof Promise&&(this._catchHandler||d)&&(l=l.catch(async u=>{if(d)try{return await d.handle({url:s,request:e,event:t,params:i})}catch(g){g instanceof Error&&(u=g)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),l}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){const i=this._routes.get(s.method)||[];for(const r of i){let c;const o=r.match({url:e,sameOrigin:t,request:s,event:a});if(o)return c=o,(Array.isArray(c)&&c.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(c=void 0),{route:r,params:c}}return{}}setDefaultHandler(e,t=I){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new h("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new h("unregister-route-route-not-registered")}}let m;const Y=()=>(m||(m=new X,m.addFetchListener(),m.addCacheListener()),m);function Z(n,e,t){let s;if(typeof n=="string"){const i=new URL(n,location.href),r=({url:c})=>c.href===i.href;s=new R(r,e,t)}else if(n instanceof RegExp)s=new z(n,e,t);else if(typeof n=="function")s=new R(n,e,t);else if(n instanceof R)s=n;else throw new h("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return Y().registerRoute(s),s}function ee(n,e=[]){for(const t of[...n.searchParams.keys()])e.some(s=>s.test(t))&&n.searchParams.delete(t);return n}function*te(n,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:a}={}){const i=new URL(n,location.href);i.hash="",yield i.href;const r=ee(i,e);if(yield r.href,t&&r.pathname.endsWith("/")){const c=new URL(r.href);c.pathname+=t,yield c.href}if(s){const c=new URL(r.href);c.pathname+=".html",yield c.href}if(a){const c=a({url:i});for(const o of c)yield o.href}}class se extends R{constructor(e,t){const s=({request:a})=>{const i=e.getURLsToCacheKeys();for(const r of te(a.url,t)){const c=i.get(r);if(c){const o=e.getIntegrityForCacheKey(c);return{cacheKey:c,integrity:o}}}};super(s,e.strategy)}}function ne(n){const e=x(),t=new se(e,n);Z(t)}function ae(n){x().precache(n)}function ie(n,e){ae(n),ne(e)}ie([{"revision":"38013143dc2183340ede8bc1c5124507","url":"registerSW.js"},{"revision":"194ab684f5fe99891f1bd66fa0d50447","url":"index.html"},{"revision":null,"url":"assets/web-CF-N8Di6.js"},{"revision":null,"url":"assets/web-BpM3fNCn.js"},{"revision":null,"url":"assets/index-bLTn8zBj.css"},{"revision":null,"url":"assets/index-FP1Mipr6.js"},{"revision":"fcc457fce855ad0df7178e0786c0d4ef","url":"apple-touch-icon.png"},{"revision":"276650c30bc4effc7d649ec66519aab6","url":"favicon.ico"},{"revision":"2e46512b835c05e17787059909305f22","url":"pwa-192x192.png"},{"revision":"ec5652b5834b4711337743e80e506a41","url":"pwa-512x512.png"},{"revision":"9f51698004b9cc4d787c75695b74de9d","url":"manifest.webmanifest"}]);const re="/api/push/respond";self.addEventListener("message",n=>{});self.addEventListener("push",n=>{var r;if(!n.data)return;let e;try{e=n.data.json()}catch{e={title:"Palmier",body:n.data.text()}}const t=e.type??((r=e.data)==null?void 0:r.type);if(t==="confirm-dismiss"||t==="permission-dismiss"||t==="input-dismiss"){const c=e.data??e,o=c.host_id,l=c.session_id,d=c.task_id;n.waitUntil(self.registration.getNotifications().then(u=>{var g,P,K;for(const y of u)if(((g=y.data)==null?void 0:g.host_id)===o){if(l&&((P=y.data)==null?void 0:P.session_id)===l){y.close();continue}d&&((K=y.data)==null?void 0:K.task_id)===d&&y.close()}}));return}const s=e.title??"Palmier";let a=e.body??"";!a&&t==="confirm"&&(a="A task requires confirmation to run."),!a&&t==="permission"&&(a="A task needs additional permissions to continue."),!a&&t==="input"&&(a="A task needs your input to continue.");const i={body:a,icon:"/pwa-192x192.png",badge:"/pwa-192x192.png",data:e.data??e,vibrate:[100,50,100]};t==="confirm"&&(i.actions=[{action:"confirm",title:"Confirm"},{action:"abort",title:"Abort"}]),n.waitUntil(self.registration.showNotification(s,i))});self.addEventListener("notificationclick",n=>{const e=n.notification;e.close();const t=e.data??{},s=n.action;if(s&&t.type==="confirm"&&t.session_id&&t.host_id){const a=s==="confirm"?"confirmed":"aborted";n.waitUntil(fetch(re,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:t.session_id,host_id:t.host_id,response:a})}).catch(i=>{console.error("Failed to send push response:",i)}))}else{const a=t.task_id,i=t.run_id,r=a&&i?`/runs/${encodeURIComponent(a)}/${encodeURIComponent(i)}`:a?`/runs/${encodeURIComponent(a)}/latest`:"/";n.waitUntil(self.clients.matchAll({type:"window",includeUncontrolled:!0}).then(c=>{for(const o of c)if(o.url.includes(self.location.origin)&&"focus"in o)return o.navigate(r),o.focus();return self.clients.openWindow(r)}))}});self.addEventListener("install",()=>{self.skipWaiting()});self.addEventListener("activate",n=>{n.waitUntil(self.clients.claim())});
|
package/dist/rpc-handler.js
CHANGED
|
@@ -13,6 +13,7 @@ import { publishHostEvent } from "./events.js";
|
|
|
13
13
|
import { getCapabilityDevice, setCapabilityDevice, clearCapabilityDevice } from "./device-capabilities.js";
|
|
14
14
|
import { currentVersion, performUpdate } from "./update-checker.js";
|
|
15
15
|
import { parseReportFiles, parseTaskOutcome, stripPalmierMarkers } from "./commands/run.js";
|
|
16
|
+
import { clearTaskQueue } from "./event-queues.js";
|
|
16
17
|
/**
|
|
17
18
|
* Parse RESULT frontmatter and conversation messages.
|
|
18
19
|
*/
|
|
@@ -142,7 +143,7 @@ export function createRpcHandler(config, nc) {
|
|
|
142
143
|
// is active. Includes any prompts already waiting so a reconnecting
|
|
143
144
|
// PWA can render their modals without replaying events.
|
|
144
145
|
const capabilities = {};
|
|
145
|
-
for (const cap of ["location", "notifications", "sms", "contacts", "calendar", "alert", "battery", "dnd"]) {
|
|
146
|
+
for (const cap of ["location", "notifications", "sms", "contacts", "calendar", "alert", "battery", "dnd", "email"]) {
|
|
146
147
|
capabilities[cap] = getCapabilityDevice(cap)?.clientToken ?? null;
|
|
147
148
|
}
|
|
148
149
|
return {
|
|
@@ -181,9 +182,10 @@ export function createRpcHandler(config, nc) {
|
|
|
181
182
|
name,
|
|
182
183
|
user_prompt: params.user_prompt,
|
|
183
184
|
agent: params.agent,
|
|
184
|
-
|
|
185
|
-
triggers_enabled: params.triggers_enabled ?? true,
|
|
185
|
+
schedule_enabled: params.schedule_enabled ?? true,
|
|
186
186
|
requires_confirmation: params.requires_confirmation ?? true,
|
|
187
|
+
...(params.schedule_type ? { schedule_type: params.schedule_type } : {}),
|
|
188
|
+
...(params.schedule_values?.length ? { schedule_values: params.schedule_values } : {}),
|
|
187
189
|
...(params.yolo_mode ? { yolo_mode: true } : {}),
|
|
188
190
|
...(params.foreground_mode ? { foreground_mode: true } : {}),
|
|
189
191
|
...(params.command ? { command: params.command } : {}),
|
|
@@ -206,10 +208,24 @@ export function createRpcHandler(config, nc) {
|
|
|
206
208
|
existing.frontmatter.user_prompt = params.user_prompt;
|
|
207
209
|
if (params.agent !== undefined)
|
|
208
210
|
existing.frontmatter.agent = params.agent;
|
|
209
|
-
if (params.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
if (params.schedule_type !== undefined) {
|
|
212
|
+
if (params.schedule_type) {
|
|
213
|
+
existing.frontmatter.schedule_type = params.schedule_type;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
delete existing.frontmatter.schedule_type;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (params.schedule_values !== undefined) {
|
|
220
|
+
if (params.schedule_values && params.schedule_values.length > 0) {
|
|
221
|
+
existing.frontmatter.schedule_values = params.schedule_values;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
delete existing.frontmatter.schedule_values;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (params.schedule_enabled !== undefined)
|
|
228
|
+
existing.frontmatter.schedule_enabled = params.schedule_enabled;
|
|
213
229
|
if (params.requires_confirmation !== undefined)
|
|
214
230
|
existing.frontmatter.requires_confirmation = params.requires_confirmation;
|
|
215
231
|
if (params.yolo_mode !== undefined) {
|
|
@@ -242,6 +258,7 @@ export function createRpcHandler(config, nc) {
|
|
|
242
258
|
case "task.delete": {
|
|
243
259
|
const params = request.params;
|
|
244
260
|
getPlatform().removeTaskTimer(params.id);
|
|
261
|
+
clearTaskQueue(params.id);
|
|
245
262
|
removeFromTaskList(config.projectRoot, params.id);
|
|
246
263
|
return { ok: true, task_id: params.id };
|
|
247
264
|
}
|
|
@@ -256,8 +273,7 @@ export function createRpcHandler(config, nc) {
|
|
|
256
273
|
name,
|
|
257
274
|
user_prompt: params.user_prompt,
|
|
258
275
|
agent: params.agent,
|
|
259
|
-
|
|
260
|
-
triggers_enabled: false,
|
|
276
|
+
schedule_enabled: false,
|
|
261
277
|
requires_confirmation: params.requires_confirmation ?? false,
|
|
262
278
|
...(params.yolo_mode ? { yolo_mode: true } : {}),
|
|
263
279
|
...(params.foreground_mode ? { foreground_mode: true } : {}),
|
package/dist/task.js
CHANGED
|
@@ -27,7 +27,7 @@ export function parseTaskContent(content) {
|
|
|
27
27
|
}
|
|
28
28
|
frontmatter.name ??= frontmatter.user_prompt?.slice(0, 60) ?? "";
|
|
29
29
|
frontmatter.agent ??= "claude";
|
|
30
|
-
frontmatter.
|
|
30
|
+
frontmatter.schedule_enabled ??= true;
|
|
31
31
|
return { frontmatter };
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
@@ -8,8 +8,7 @@ import * as fs from "node:fs";
|
|
|
8
8
|
import { agentToolMap, agentResources, ToolError } from "../mcp-tools.js";
|
|
9
9
|
import { handleMcpRequest, getAgentName, getResourceSubscriptions } from "../mcp-handler.js";
|
|
10
10
|
import { getTaskDir } from "../task.js";
|
|
11
|
-
import {
|
|
12
|
-
import { onSmsChanged } from "../sms-store.js";
|
|
11
|
+
import { popEvent } from "../event-queues.js";
|
|
13
12
|
const assetCache = new Map();
|
|
14
13
|
const PWA_DIR = path.join(import.meta.dirname, "..", "pwa");
|
|
15
14
|
const CONTENT_TYPES = {
|
|
@@ -94,9 +93,9 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
|
|
|
94
93
|
})}\n\n`);
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
for (const resource of agentResources) {
|
|
97
|
+
resource.subscribe(() => broadcastResourceUpdated(resource.uri));
|
|
98
|
+
}
|
|
100
99
|
// If a pairing code is provided, pre-register it
|
|
101
100
|
if (pairingCode) {
|
|
102
101
|
const EXPIRY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -249,6 +248,20 @@ export async function startHttpTransport(config, handleRpc, port, nc, pairingCod
|
|
|
249
248
|
sendJson(res, 200, result);
|
|
250
249
|
return;
|
|
251
250
|
}
|
|
251
|
+
// ── Event queue pop (used by event-triggered palmier run) ─────────
|
|
252
|
+
if (req.method === "POST" && pathname === "/task-event/pop") {
|
|
253
|
+
if (!isLocalhost(req)) {
|
|
254
|
+
sendJson(res, 403, { error: "localhost only" });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const taskId = url.searchParams.get("taskId");
|
|
258
|
+
if (!taskId) {
|
|
259
|
+
sendJson(res, 400, { error: "taskId query parameter is required" });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
sendJson(res, 200, popEvent(taskId));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
252
265
|
// ── Localhost-only endpoints (no auth) ─────────────────────────────
|
|
253
266
|
if (req.method === "POST" && pathname === "/event") {
|
|
254
267
|
if (!isLocalhost(req)) {
|
package/dist/types.d.ts
CHANGED
|
@@ -19,18 +19,22 @@ export interface TaskFrontmatter {
|
|
|
19
19
|
name: string;
|
|
20
20
|
user_prompt: string;
|
|
21
21
|
agent: string;
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Task schedule.
|
|
24
|
+
* - `crons`: `schedule_values` holds cron expressions (e.g. "0 9 * * *")
|
|
25
|
+
* - `specific_times`: `schedule_values` holds local datetime strings (e.g. "2026-04-20T09:00")
|
|
26
|
+
* - `on_new_notification`: fires on each new Android notification from NATS; no `schedule_values`
|
|
27
|
+
* - `on_new_sms`: fires on each new SMS from NATS; no `schedule_values`
|
|
28
|
+
*/
|
|
29
|
+
schedule_type?: "crons" | "specific_times" | "on_new_notification" | "on_new_sms";
|
|
30
|
+
schedule_values?: string[];
|
|
31
|
+
schedule_enabled: boolean;
|
|
24
32
|
requires_confirmation: boolean;
|
|
25
33
|
yolo_mode?: boolean;
|
|
26
34
|
foreground_mode?: boolean;
|
|
27
35
|
permissions?: RequiredPermission[];
|
|
28
36
|
command?: string;
|
|
29
37
|
}
|
|
30
|
-
export interface Trigger {
|
|
31
|
-
type: "cron" | "once";
|
|
32
|
-
value: string;
|
|
33
|
-
}
|
|
34
38
|
export interface ParsedTask {
|
|
35
39
|
frontmatter: TaskFrontmatter;
|
|
36
40
|
}
|
package/package.json
CHANGED
package/palmier-server/README.md
CHANGED
|
@@ -183,7 +183,7 @@ All endpoints are prefixed with `/api`. No user authentication is required.
|
|
|
183
183
|
- **NATS RPC** — the RPC method is derived from the NATS subject (e.g., `...rpc.task.list` → `task.list`), not the message body. The body contains request parameters plus `clientToken`. All NATS requests go through a centralized `request()` helper in `HostConnectionContext` that handles encoding/decoding and logging.
|
|
184
184
|
- **Pairing** — `palmier pair` (or auto-pair after `palmier init`) generates a 6-char pairing code. The PWA enters the code, which routes to the host via NATS (`pair.<CODE>`) or HTTP (`POST /pair`). The host validates the code and returns a client token.
|
|
185
185
|
- **Task IDs** are generated by the host as UUIDs.
|
|
186
|
-
- **
|
|
186
|
+
- **Schedule can be enabled/disabled** — the `schedule_enabled` frontmatter field (default `true`) controls whether systemd timers are installed. When disabled, timers are removed but the task can still be run manually. The schedule lives in two flat fields: `schedule_type` (`"crons"` or `"specific_times"`) and `schedule_values` (array of cron expressions or local datetime strings). Both are present together or absent together.
|
|
187
187
|
- **Host responses** return flat task objects (frontmatter fields at the top level, not nested) for `task.list`, `task.create`, and `task.update`.
|
|
188
188
|
- **NATS "503"** means "no responders" — the dashboard silently handles this when no host is connected, showing an empty task list instead of an error.
|
|
189
189
|
- **Helmet CSP** is disabled (`contentSecurityPolicy: false`) to allow NATS WebSocket connections and inline Vite scripts during dev.
|
|
@@ -191,8 +191,8 @@ All endpoints are prefixed with `/api`. No user authentication is required.
|
|
|
191
191
|
- **No CORS** needed — Vite proxy handles same-origin in dev, Express static serving handles it in production.
|
|
192
192
|
- **Push notifications** — the PWA registers a service worker (`injectManifest` strategy via vite-plugin-pwa) and subscribes the browser for Web Push. The Web Server subscribes to `host-event.>` and sends push notifications for confirmation requests, task completions, and task failures.
|
|
193
193
|
- **Markdown rendering** — Task results are rendered as rich formatted text using `react-markdown` with `remark-gfm` (GitHub Flavored Markdown), supporting tables, strikethrough, task lists, and autolinks.
|
|
194
|
-
- **
|
|
195
|
-
- **
|
|
194
|
+
- **Pending prompts** — Dashboard fetches `host.info` once per connection and seeds any already-open confirmation/permission/input prompts from the response's `pending_prompts` array. Each entry carries a `meta` field with a unified `session_name` label (agent name for confirm/input, task name for permission) so modals render without needing the task list. After connect, live prompts arrive via NATS events (`confirm-request`, `permission-request`, `input-request`). The Dashboard owns the subscription and renders modals via React portal so they surface regardless of which tab is active. Push notification action buttons trigger `POST /api/push/respond`, which forwards to the `task.user_input` NATS RPC.
|
|
195
|
+
- **Lazy tab loads** — Sessions tab (default, `/`) fetches `taskrun.list` on mount; Tasks tab (`/tasks`) fetches `task.list` on mount. Neither is called at startup — only `host.info` is. Task lifecycle events are persisted to `status.json` on the host (for crash detection) and broadcast via `host-event.<host_id>.<task_id>` pub/sub and HTTP SSE.
|
|
196
196
|
- **NATS auth** uses decentralized JWT/NKey authentication. Each host receives scoped credentials (can only access its own subjects). PWA clients get two-phase credentials: pairing-only JWT for the pairing flow, then host-scoped JWT after pairing. The server signs all user JWTs with the account key. Run `pnpm --filter palmier-server nats-setup` to generate keys and NATS config. See **NATS JWT Auth Setup** below.
|
|
197
197
|
|
|
198
198
|
## NATS JWT Auth Setup
|