Commit baf8606
fix(plugins+triggers): private-repo update flow + ClickUp webhook compat + DetachedInstanceError (#51)
* refactor(telegram): centralize notifications in routines, remove from skills
Move Telegram reply() out of skill SKILL.md files into the routine .py
callers via notify_telegram=True on run_skill(). This guarantees exactly
one send per execution — the instruction is appended at the end of the
prompt after all skill steps, so the agent cannot send it early.
- runner.py: add notify_telegram param to run_skill() — reads chat_id
from TELEGRAM_CHAT_ID env, appends explicit one-shot instruction
- Skills cleaned: prod-end-of-day, prod-good-morning, pulse-faq-sync,
pulse-daily (custom files gitignored, updated locally)
- Routines updated: end_of_day, good_morning (custom routines gitignored)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(plugins): support auth_token on update/preview and update endpoints for private repos
The install endpoint (POST /api/plugins/install and /api/plugins/preview) already
accepts `auth_token` in the JSON body and forwards it to
`PluginInstaller.resolve_source`, enabling installs from private GitHub repos.
However, the update flow had no path for the token:
- `GET /api/plugins/<slug>/update/preview` has no request body, and the
`_compute_preview()` helper called `resolve_source(source_url)` without a
token — any private-repo candidate failed with HTTP 500 `fetch_failed`.
- `POST /api/plugins/<slug>/update` accepted a JSON body but read only
`source_url`, ignoring any `auth_token` the caller might have supplied.
This meant once a plugin was installed from a private repo, the user could
not preview or apply updates without uninstalling and reinstalling — the UI
offered no way out.
Changes:
**Backend** (`dashboard/backend/routes/plugins.py`)
- `_compute_preview(slug, source_url, auth_token=None)` — new optional param,
forwarded to `PluginInstaller.resolve_source(...)`.
- `preview_plugin_update` (GET) — reads `X-Plugin-Auth-Token` header (header
rather than query param to keep the PAT out of access logs) and passes it
down. Cache key extended to `(slug, source_url, bool(auth_token))` so
private-repo previews are not served to unauthenticated callers.
- `update_plugin` (POST) — reads `auth_token` from the JSON body (same shape
as `/api/plugins/install`), falling back to the `X-Plugin-Auth-Token`
header for callers that reuse the header from preview.
**Frontend** (`UpdatePreviewModal.tsx`, `lib/api.ts`)
- `api.get(path, extraHeaders?)` — optional `extraHeaders` parameter.
- `UpdatePreviewModal` — collapsible "🔒 Private repository? (optional
GitHub PAT)" section, rendered when `sourceUrl` starts with `github:`.
Auto-opens when the initial preview fails with 401/404/fetch_failed. A
password-type input + Retry button re-runs the preview with the token as
the `X-Plugin-Auth-Token` header. On apply, the token is added to the
POST body as `auth_token`.
- Token lives only in component state — never persisted, never logged.
No behavior change for public-repo plugins. Fully backwards compatible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(plugins): editable source override + clearer ref-not-found errors
Two related improvements to the update flow surfaced while validating the
auth_token fix:
1. Editable source override in UpdatePreviewModal
The modal previously used the install-time `source_url` verbatim. When a
plugin was installed pinned to a specific tag (e.g. `@v0.1.0`), the
preview always fetched that exact tag — newer releases were invisible
without uninstalling/reinstalling or editing the DB by hand.
Adds a collapsible "📦 Source <current>" section with an editable input
and a Preview button. The user can point at `@main`, a newer tag, or a
different branch and re-run the diff in place. The override is also
forwarded to POST /api/plugins/<slug>/update so apply uses the same
resolved source. Override lives in component state only.
2. Unified branch-and-tag ref-not-found error in plugin_loader
`resolve_source` and `resolve_source_with_sha` already try
`refs/heads/<ref>` first, falling back to `refs/tags/<ref>`. When both
fail the previous behavior surfaced only the second (tag) error — so a
typo in a branch name produced a confusing "refs/tags/...: 404"
message.
Catches both `RuntimeError`s and raises a unified message:
ref '<ref>' not found in <owner>/<repo> (tried branches and tags).
Branch: <branch_err>. Tag: <tag_err>.
This also disambiguates ref-not-found from private-repo auth failures
(which now have a working path via the auth_token header from the
previous commit in this PR).
Verified end-to-end on 0.32.x: bad ref produces the new unified error,
good ref still resolves identically, and the source override box lets the
modal upgrade a `@v0.1.1`-pinned install to `@main` without touching the DB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(triggers): accept X-Signature header + capture IDs before thread handoff
Two issues surfaced while wiring a ClickUp webhook to a `source: custom`
trigger end-to-end:
1. ClickUp signs requests with `X-Signature` (HMAC-SHA256, hex), not
`X-Webhook-Signature`. _validate_webhook_signature for the `else` branch
only checked the latter, so every ClickUp POST was silently rejected and
the action never ran. Receiver still returned 200 (uniform response,
F6) so it looked like webhooks were being delivered while in reality
nothing fired.
Fix: try `X-Webhook-Signature` first, fall back to `X-Signature`. Both
carry the same hex HMAC-SHA256 of the raw body (with optional `sha256=`
prefix that gets stripped). This unblocks ClickUp without adding a
dedicated source enum value.
2. DetachedInstanceError on every successful webhook + every test run.
Both `webhook_receiver` and `test_trigger` did:
db.session.commit()
def _run():
with app.app_context():
_execute_trigger(trigger.id, execution.id, ...)
Thread(target=_run).start()
The `trigger.id` / `execution.id` accesses run inside the worker thread,
AFTER the request scope tears down and the SQLAlchemy session closes —
triggering DetachedInstanceError. The thread crashed before
`_execute_trigger` could even mark status='running', leaving rows in
`pending` forever.
Fix: snapshot the IDs (plain ints) before starting the thread, pass
those to `_execute_trigger`. Same pattern already used elsewhere; the
fix is local to the two call sites.
Verified end-to-end on 0.32.x with a real ClickUp webhook firing
`taskAssigneeUpdated`: handler runs, posts a comment back to the task,
adds `jarvis-working` tag. trigger_executions.status transitions
pending → running → completed instead of remaining pending.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(terminal): proxy terminal-server through Flask + drop direct cross-port fetch
Browsers connecting to the dashboard from any host other than the one
running the Node terminal-server hit three different walls before reaching
its random port (default 32352):
1. CSP — the dashboard sets `connect-src 'self'`. Even when the page is
served from `localhost:8080` and the terminal-server is on the same
machine at `localhost:32352`, the browser refuses the cross-port fetch
with: "Refused to connect because it violates the document's Content
Security Policy."
2. CORS preflight — different ports are different origins; the
terminal-server doesn't currently emit the headers needed to satisfy a
preflight from a non-trivial fetch.
3. Reachability — SSH tunnels (`ssh -L 8080:localhost:8080`) and reverse
proxies (Tailscale Funnel, nginx in front of a private host) typically
only expose the dashboard port. The dynamic terminal-server port is
unreachable from the browser entirely.
The fix is to mount an HTTP+WebSocket proxy on the dashboard's Flask app
at `/terminal/*` and route all consumers through it. Same origin → CSP
passes, no preflight, single port to expose.
Backend (`dashboard/backend/routes/terminal_proxy.py`)
- New blueprint with HTTP catch-all that forwards method, headers (minus
hop-by-hop), query string, and streamed body to
`http://127.0.0.1:32352/<path>` via `requests`. Auth gated by
`@login_required` — only logged-in users reach the upstream.
- `register_websocket_proxy(sock)` registers `/terminal/ws` on the
flask-sock instance, opening one upstream WS per client and pumping
bytes both directions on a daemon thread.
- Upstream host/port overridable via `TERMINAL_SERVER_HOST` /
`TERMINAL_SERVER_PORT` env vars. Adds `websocket-client` dep.
Backend (`dashboard/backend/app.py`)
- Imports the new blueprint.
- After `register_blueprint(triggers_bp)`: registers
`terminal_proxy_bp`, instantiates a `Sock(app)`, and calls
`register_websocket_proxy()`. Swallows ImportError so the dashboard
still boots if `websocket-client` or `flask-sock` is missing — terminal
features just stay direct-connect.
Frontend (`UpdatePreviewModal.tsx`, `lib/api.ts`,
`lib/terminal-url.ts`, `components/AgentTerminal.tsx`,
`pages/AgentDetail.tsx`)
- Three places had their own `isLocal ? "<host>:32352" : "/terminal"`
ternary. Each one drove a different code path that bypassed the proxy
on private/loopback hostnames — the exact case CSP blocks. Collapsed
all three to the same rule: production builds always go through
`/terminal`. Vite dev (`npm run dev`, no proxy) keeps the direct path
as before.
- Renamed the boolean from `isLocal` to `isViteDev` to make the intent
unambiguous.
Verified end-to-end on 0.32.x: dashboard at `http://localhost:8080`
loads, navigates to an agent, and opens a terminal session over the
proxy. CSP no longer fires, no DevTools console errors, and the WS
proxy tunnels keystrokes / output bidirectionally. Same setup also
works behind a Tailscale Funnel public URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(terminal): require auth on /terminal/ws to prevent unauthenticated PTY access
The dashboard's ``auth_middleware`` only gates ``/api/*`` and ``/ws/*``
paths, so the new terminal proxy mounted at ``/terminal/ws`` was reachable
without authentication. Anyone able to hit the dashboard (LAN, Tailscale
Funnel, Cloudflare Tunnel, public VPS — exactly the scenarios this PR
intended to support) could open a PTY on the host.
Add an explicit ``current_user.is_authenticated`` guard inside ``proxy_ws``
mirroring the ``@login_required`` decorator already on the HTTP path.
flask-login reads the session cookie from the WebSocket upgrade request,
so the check works the same way it does for normal routes.
Also drop two unused imports (``urlparse``, ``WebSocketApp``) flagged
during review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Davidson Gomes <davidsongviolao@gmail.com>
Co-authored-by: Davidson Gomes <davidson.gomes@etus.com.br>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 parent 27ef039 commit baf8606
10 files changed
Lines changed: 501 additions & 58 deletions
File tree
- dashboard
- backend
- routes
- frontend/src
- components
- lib
- pages
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
834 | 834 | | |
835 | 835 | | |
836 | 836 | | |
| 837 | + | |
837 | 838 | | |
838 | 839 | | |
839 | 840 | | |
| |||
887 | 888 | | |
888 | 889 | | |
889 | 890 | | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
890 | 910 | | |
891 | 911 | | |
892 | 912 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
290 | 290 | | |
291 | 291 | | |
292 | 292 | | |
293 | | - | |
294 | | - | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
295 | 299 | | |
296 | | - | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
297 | 310 | | |
298 | 311 | | |
299 | 312 | | |
| |||
477 | 490 | | |
478 | 491 | | |
479 | 492 | | |
480 | | - | |
| 493 | + | |
481 | 494 | | |
482 | | - | |
483 | | - | |
484 | | - | |
485 | | - | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
486 | 510 | | |
487 | 511 | | |
488 | 512 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2158 | 2158 | | |
2159 | 2159 | | |
2160 | 2160 | | |
2161 | | - | |
| 2161 | + | |
| 2162 | + | |
| 2163 | + | |
| 2164 | + | |
2162 | 2165 | | |
2163 | 2166 | | |
2164 | 2167 | | |
| |||
2264 | 2267 | | |
2265 | 2268 | | |
2266 | 2269 | | |
2267 | | - | |
| 2270 | + | |
| 2271 | + | |
| 2272 | + | |
2268 | 2273 | | |
2269 | 2274 | | |
2270 | 2275 | | |
| |||
2273 | 2278 | | |
2274 | 2279 | | |
2275 | 2280 | | |
| 2281 | + | |
| 2282 | + | |
| 2283 | + | |
| 2284 | + | |
| 2285 | + | |
| 2286 | + | |
2276 | 2287 | | |
2277 | 2288 | | |
2278 | 2289 | | |
| |||
2316 | 2327 | | |
2317 | 2328 | | |
2318 | 2329 | | |
2319 | | - | |
| 2330 | + | |
2320 | 2331 | | |
2321 | 2332 | | |
2322 | 2333 | | |
| |||
2422 | 2433 | | |
2423 | 2434 | | |
2424 | 2435 | | |
| 2436 | + | |
| 2437 | + | |
| 2438 | + | |
| 2439 | + | |
| 2440 | + | |
2425 | 2441 | | |
2426 | 2442 | | |
2427 | 2443 | | |
| |||
2440 | 2456 | | |
2441 | 2457 | | |
2442 | 2458 | | |
2443 | | - | |
| 2459 | + | |
| 2460 | + | |
| 2461 | + | |
| 2462 | + | |
| 2463 | + | |
| 2464 | + | |
2444 | 2465 | | |
2445 | 2466 | | |
2446 | 2467 | | |
| |||
2452 | 2473 | | |
2453 | 2474 | | |
2454 | 2475 | | |
2455 | | - | |
| 2476 | + | |
2456 | 2477 | | |
2457 | 2478 | | |
2458 | 2479 | | |
| |||
2543 | 2564 | | |
2544 | 2565 | | |
2545 | 2566 | | |
| 2567 | + | |
| 2568 | + | |
| 2569 | + | |
| 2570 | + | |
| 2571 | + | |
| 2572 | + | |
| 2573 | + | |
| 2574 | + | |
| 2575 | + | |
2546 | 2576 | | |
2547 | 2577 | | |
2548 | 2578 | | |
2549 | | - | |
| 2579 | + | |
| 2580 | + | |
| 2581 | + | |
2550 | 2582 | | |
2551 | 2583 | | |
2552 | 2584 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
0 commit comments