-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(webapp): dashboard agent — chat, reports, investigate #4418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
593 commits
Select commit
Hold shift + click to select a range
e109a24
fix(webapp): read the alert channel and the watch target on the primary
kathiekiwi d26a28b
perf(webapp): only poll the wake feed for a browser that knows a watc…
kathiekiwi 1dd8536
perf(webapp): answer the wake poll with one narrow read per database
kathiekiwi 8b042be
perf(webapp): skip the admin read on the watch authorization path
kathiekiwi be1cb04
perf(dashboard-agent-db): index the open investigations the sweep scans
kathiekiwi cafd920
chore: keep the release notes as one
kathiekiwi a88228c
chore(webapp): move the agent storybook gallery out to a follow-up
kathiekiwi b6ebe55
fix(dashboard-agent): escape the prefix separator so the file reviews…
kathiekiwi 5b5a258
fix(webapp): scope a delegated user-actor token to the environment it…
kathiekiwi d6debe8
test(webapp): cover the user-actor token's environment scope on every…
kathiekiwi 30ec754
refactor(dashboard-agent-db): move the watch queries into their own m…
kathiekiwi 2ed5d52
refactor(dashboard-agent-db): move the watch tables into their own sc…
kathiekiwi 46290d1
refactor(dashboard-agent): move the watch wake and investigate lanes …
kathiekiwi 18dffa6
refactor(dashboard-agent): give the watch tests their own file
kathiekiwi a0d06e4
fix(webapp): send the agent wake signal to a browser that has never o…
kathiekiwi ebad471
fix(webapp): cap the size of a message to the agent
kathiekiwi c3ee6a4
perf(webapp): resolve the agent card's links in one request
kathiekiwi 08dc7ba
fix(webapp): keep the watch card in step with its draft, and frozen w…
kathiekiwi 7f13add
feat(webapp): retire judged-turn rows after 30 days
kathiekiwi 42ddcff
feat(dashboard-agent): keep customer data out of the judged-turn row …
kathiekiwi 3c5b2d5
feat(dashboard-agent): never judge a turn that read source
kathiekiwi a4b4ef1
feat(webapp): let an organization turn off judging of its agent turns
kathiekiwi 263b324
feat(dashboard-agent): give the golden runs their own eval sample rate
kathiekiwi 99b8933
docs(dashboard-agent): make the eval docs match the code
kathiekiwi 29d9a8e
chore: note the conversation scoring in the release note
kathiekiwi 31698fa
refactor(dashboard-agent): split the tool set into one module per res…
kathiekiwi fea1a29
refactor(dashboard-agent): split the watch tick into delivery, condit…
kathiekiwi 4876e07
refactor(webapp): give each watch condition family its own module
kathiekiwi fc32fc6
refactor(webapp): split the suggested-prompt registry by responsibility
kathiekiwi f963614
chore(webapp): restore the dashboard agent's flag gate
kathiekiwi 99850ac
feat(dashboard-agent): summarise an oversized conversation, keeping l…
kathiekiwi 1d083f3
test(dashboard-agent): budget the real prompt prefix with ceilings an…
kathiekiwi 42a76fb
perf(dashboard-agent): say a settled watch outcome without a model, a…
kathiekiwi 0c41a70
perf(dashboard-agent): cap search_docs to a few short excerpts
kathiekiwi 6c993f2
chore: note the summarised long conversations in the release note
kathiekiwi ddc6d25
fix(dashboard-agent): describe a value at the redaction depth cap ins…
kathiekiwi 4e39228
fix(dashboard-agent): pin only a live investigation into a compacted …
kathiekiwi a3be7bd
fix(dashboard-agent): cap the output of the summariser and the wake n…
kathiekiwi e599f12
fix(dashboard-agent): rotate an over-cap watch group instead of starv…
kathiekiwi d719c9b
fix(webapp): start the wake poll for a browser that has only an activ…
kathiekiwi 28b1f38
fix(webapp): cap the agent's request body while it streams, not after
kathiekiwi 49a9538
fix(webapp): record a watch request before the watch starts, and repa…
kathiekiwi cbc2edc
test(webapp): pin the watch submit's ordering, its retry repair and i…
kathiekiwi 4ff8859
fix(webapp): keep the agent's alert delete to channels the watch type…
kathiekiwi 50fd10b
fix(webapp): replay a watch submission's recorded outcome instead of …
kathiekiwi 1cdf1f0
refactor(webapp): split the dashboard agent's maintenance cron along …
kathiekiwi 62b598d
docs(webapp): say which layer refuses an oversized agent body
kathiekiwi 85b98ab
fix(webapp): mint the agent's token for the caller's own environment,…
kathiekiwi f5a146b
fix(webapp): cap the agent's body on any method and on a mixed-case path
kathiekiwi 880bf60
test(tsql): pin that no mutating query can pass the query boundary
kathiekiwi 9d45284
fix(webapp): keep the agent token's environment claim on the actor th…
kathiekiwi d51cb9a
fix(webapp): stop the OSS fallback handing a delegated token blanket …
kathiekiwi f58a9dc
fix(webapp): only let the browser set the agent's page context
kathiekiwi d12e6a8
style(webapp): merge duplicate imports on the user-actor auth path
kathiekiwi d2511bd
fix(webapp): hand the sweep's final check the previous facts
kathiekiwi e485209
perf(webapp): resolve the sweep's authorization and readers once per …
kathiekiwi bd2559a
fix(webapp): record only a real evaluation from a batch tick
kathiekiwi 4846cf8
fix(dashboard-agent): finish a wake in the transcript, or don't count it
kathiekiwi 012ce0d
test(webapp): drop an unsafe optional chain from the sweep boundary test
kathiekiwi 5f2f5fe
fix(webapp): scope the watch submission ledger and its alerts to one …
kathiekiwi 5176e3e
test(webapp): pin the watch ledger's tenancy, the once-only alert and…
kathiekiwi a63708c
test(webapp): assert the derived chat id before the submit outcome
kathiekiwi a0640ba
style(webapp): format the watch tenancy changes
kathiekiwi dd90b6e
fix(webapp): never render an image the agent's model authored
kathiekiwi 94ec67f
perf(webapp): keep the agent transcript's investigation winners stable
kathiekiwi 1aee9ca
fix(webapp): send the eval judge only the turn's structural facts
kathiekiwi 488621e
perf(webapp): cache the agent's accumulated tool results between steps
kathiekiwi 32553b9
style(webapp): format the agent markdown test
kathiekiwi d9ff853
style(webapp): format the agent eval and step-cache tests
kathiekiwi 82f4a73
style(webapp): drop a needless escape in the image regex
kathiekiwi 933aeb9
test(webapp): stub the agent proxy's environment lookup, not a Prisma…
kathiekiwi 05b3472
Merge branch 'main' into feat/dashboard-agent-flows
kathiekiwi d73f3f5
chore: ignore the agent worktrees directory
kathiekiwi 48d0d4a
fix(webapp): drop the img-src wildcard host for a validated origin al…
kathiekiwi cd112ba
test(webapp): assert the document img-src has no wildcard host
kathiekiwi 5adba81
docs: document CSP_IMG_SRC_ALLOWLIST for self-hosted image hosts
kathiekiwi fb99526
feat(dashboard-agent): tell the eval judge a failure's kind without i…
kathiekiwi 9daa8f9
docs(dashboard-agent): tell the judge the error category is derived, …
kathiekiwi 09d1b55
fix(webapp): require the watch card's request id instead of falling b…
kathiekiwi 22bf6d9
fix(webapp): cancel the watch a losing submit's winner does not name
kathiekiwi 210863e
fix(webapp): separate a watch's last look from its last check in the …
kathiekiwi 240799d
chore(dashboard-agent): drop the comments the code already says
kathiekiwi 478b30c
chore(dashboard-agent): drop the test comments the assertions already…
kathiekiwi 4e00b79
style(webapp): format the sweep's authorization helper
kathiekiwi d00b126
fix(webapp): bind a delegated token's environment claim on the projec…
kathiekiwi a7fc406
fix(webapp): keep a delegated token's claims on the authenticated ide…
kathiekiwi f700174
fix(webapp): ceiling an exchanged environment JWT by what the delegat…
kathiekiwi 3336a47
test(webapp): give the container-backed user-actor scope tests their …
kathiekiwi 7a645e5
chore(webapp): drop the comments the code already says
kathiekiwi 79482de
chore(webapp): drop the comments the tests already say
kathiekiwi d646809
chore(rbac): keep the delegated-token invariant, drop the essay
kathiekiwi c4d3f58
fix(webapp): replay a watch submission's recorded email outcome inste…
kathiekiwi 49e8fb5
fix(webapp): tell a watch's creator about their own email alerts, not…
kathiekiwi 7b2f9c6
chore(dashboard-agent-db): drop the submission's external-notificatio…
kathiekiwi f7b8374
fix(webapp): put a turn's settled investigation card in the transcript
kathiekiwi 26f93da
fix(webapp): have the stale-investigation sweep close the card it set…
kathiekiwi 07b83da
docs: note the agent's closed investigations, watch email honesty and…
kathiekiwi 60a9584
test(webapp): fail if a route sets an over-broad image CSP
kathiekiwi e62d8c6
style(webapp): format the route image CSP audit
kathiekiwi 1759618
fix(webapp): keep the project-wide answer for an environment-agnostic…
kathiekiwi 657f11d
docs(webapp): say which UAT flow the project-wide answer is preserved…
kathiekiwi 29de34f
style(webapp): drop the imports the merge left unused
kathiekiwi 5bf3612
fix(webapp): settle an investigation and its closing card in one write
kathiekiwi 37c56aa
test(webapp): pin the settlement failure window and the open panel
kathiekiwi 44b4e57
docs: say the investigation card closes while the panel is open
kathiekiwi 49a1504
docs: cut the release note back to what a user notices
kathiekiwi 2e23a09
fix(webapp): keep the other Anthropic options when the step breakpoin…
kathiekiwi f6f753c
fix(webapp): merge the transcript under the row lock instead of repla…
kathiekiwi afc0cfa
fix(webapp): close a consented watch investigation's card atomically
kathiekiwi d52c224
fix(dashboard-agent): merge the step breakpoint into the message's an…
kathiekiwi abaeebb
fix(webapp): make a repeated watch card close leave the revision alone
kathiekiwi c3f4ba8
refactor(webapp): drop the unused settleOpenInvestigations
kathiekiwi 7ab35df
fix(webapp): merge the turn's cache breakpoint into the last message'…
kathiekiwi 020dad2
refactor(webapp): store the dashboard agent's transcript one row per …
kathiekiwi 81e8349
refactor(webapp): drop the dashboard agent's JSONB transcript column
kathiekiwi 398a298
test(webapp): cover the batch position allocator with concurrent turns
kathiekiwi aad8498
fix(webapp): converge the watch transcript when a retried delivery fi…
kathiekiwi d8fda07
fix(webapp): stop an ordinary transcript write from rewriting a store…
kathiekiwi f159c69
test(webapp): guard against a raw-SQL reference to the dropped chats.…
kathiekiwi ceaa38d
fix(webapp): scope the watch transcript's appends to the caller's org…
kathiekiwi 3d3c962
fix(webapp): keep a malformed message's own text out of the error, an…
kathiekiwi e3fcc20
refactor(dashboard-agent-db): collapse the agent's migrations into one
kathiekiwi f4337f7
style(dashboard-agent-db): format the generated migration snapshot
kathiekiwi bb036f9
feat(webapp): split Watch out of the dashboard agent's first PR
kathiekiwi 9fad665
test(webapp): cover the dashboard agent's eval-policy gate, and requi…
kathiekiwi c37bdf8
test(webapp): pin the delegated token's scope ceiling on the RBAC fal…
kathiekiwi 5edfd78
feat(webapp): name the dashboard agent Ask Trigger everywhere
kathiekiwi c88a448
refactor(webapp): rename the ask-ai button variant to ask-trigger
kathiekiwi 0d3d216
feat(webapp): drop the agent button from the deploy blank states
kathiekiwi 9c6f148
chore(webapp): keep Ask AI in the tree, deprecated and unmounted
kathiekiwi db9f9a2
Merge remote-tracking branch 'origin/main' into feat/dashboard-agent-…
kathiekiwi dfcee8b
fix(webapp): settle the API keys route after merging main
kathiekiwi 49051f5
fix(dashboard-agent-db): stop rewriting the published migration history
kathiekiwi 798fdf9
refactor(webapp): split the dashboard agent's UI out of the first PR
kathiekiwi c025bbf
fix(dashboard-agent): keep the finished answer in the transcript, not…
kathiekiwi 2ff7ec0
fix(dashboard-agent): name a head-started chat instead of leaving it …
kathiekiwi 711b79e
fix(dashboard-agent): apply the token's cap as a second ceiling, and …
kathiekiwi c236601
feat(webapp): let an environment JWT read a queue, as it already read…
kathiekiwi 8adf2b2
Merge remote-tracking branch 'origin/main' into feat/dashboard-agent-…
kathiekiwi df2227e
fix(webapp): resolve a test's route paths from the test, not the repo…
kathiekiwi bd357fc
revert(webapp): put the JWT exchange back to how main had it
kathiekiwi f891584
fix(webapp): stub what the env JWT act-claim test's route actually calls
kathiekiwi cb0bcc9
fix(webapp): refuse an environment-scoped token on a route that names…
kathiekiwi 0f2814e
fix(dashboard-agent): escape model-supplied ids in tool request paths
kathiekiwi 33a3f6b
fix(dashboard-agent): require both halves of the support endpoint config
kathiekiwi 983055a
fix(dashboard-agent): keep a withheld object's key names from the eva…
kathiekiwi 5126bae
fix(webapp): gate a run's commit metadata on reading deployments
kathiekiwi 2d23dde
fix(webapp): keep the retired chats.messages column, and allow Google…
kathiekiwi 05505d5
fix(webapp): carry a queue's depth forward across empty buckets
kathiekiwi 1ee6704
fix(webapp): describe how far a report metric fell
kathiekiwi 0db1cf0
fix(webapp): say why a report's numbers can't be trusted
kathiekiwi d0be659
test(webapp): assert the queue depth trend fills its bucket grid
kathiekiwi 4c42230
fix(webapp): stop treating a ClickHouse unknown identifier as a rollo…
kathiekiwi aa55227
fix(cli): say that a capless mint-token is read-only
kathiekiwi f598f96
fix(webapp): refuse a dashboard agent turn whose token mint failed
kathiekiwi b38c518
fix(webapp): stop a failed agent chat start leaving an empty chat behind
kathiekiwi de4cdad
fix(webapp): list the preview branch an agent token is scoped to
kathiekiwi e2704ea
fix(webapp): keep identityOnly off action routes in the type
kathiekiwi f6afda3
docs(dashboard-agent): say what the queue path encoding is for
kathiekiwi 14d70be
docs(webapp): say that the agent is off until a flag turns it on
kathiekiwi c3f0d62
fix(webapp): undo a new agent chat only when its start never got anyw…
kathiekiwi 314a1d7
perf(webapp): skip the global feature-flag query when a per-org overr…
kathiekiwi a4ba027
fix(webapp): refuse a delegated token at the entrance of the PAT-only…
kathiekiwi d0f06d5
fix(webapp,dashboard-agent): address a branch environment by name and…
kathiekiwi a660349
fix(webapp): make each report caveat discount only the input it names
kathiekiwi 3355b68
fix(webapp): stop an unmeasured queue depth from silencing a measured…
kathiekiwi 566de27
docs: say that the agent works on branches
kathiekiwi ad2698b
feat(webapp): render the cards the flows already emit
kathiekiwi 643ea39
fix(dashboard-agent): read the report's untrustworthy reason under it…
kathiekiwi ed0a7b3
fix(dashboard-agent): stop a retrieved run's own error counting as a …
kathiekiwi 0cae301
fix(dashboard-agent): pin the investigation cards the transcript actu…
kathiekiwi 8fc6f20
style(dashboard-agent): format the two eval test files
kathiekiwi c3b7c10
refactor(dashboard-agent): move the watch data layer into the Watch PR
kathiekiwi 36691f1
chore: mark drizzle and test snapshots as generated
kathiekiwi 7e272cc
chore(webapp): drop the unused waiting-run diagnosis
kathiekiwi d40e9fe
chore: mark the lockfile as generated too
kathiekiwi 2aacd26
fix(webapp,dashboard-agent): keep investigation cards, and stop a bro…
kathiekiwi 8fd5369
revert(webapp): move the queue JWT read to the PR that uses it
kathiekiwi 0fd886c
perf(webapp): stop the agent transcript re-rendering on every streame…
kathiekiwi 9805c0a
fix(webapp): deny an unknown report key instead of authorizing it aga…
kathiekiwi 20c8f9e
fix(webapp): stop an unreadable eval override reading as consent to j…
kathiekiwi 61a3288
fix(webapp): scope the org-creation RBAC check to user-actor tokens
kathiekiwi 8fbbcf0
test(webapp): pin 404-before-403 ordering for capped user-actor tokens
kathiekiwi 637a2bb
test(webapp): pin that an ordinary PAT still passes the PAT-only helper
kathiekiwi 82b93db
fix(webapp): compute answered for view blocks in the base message ren…
kathiekiwi d304c1a
test(webapp): assert the org route never consults a plain PAT ability
kathiekiwi 8f017ec
test(webapp): pin that the disabled org route never consults the toke…
kathiekiwi 49f42de
fix(webapp): allow the org-avatar favicon endpoint in the img-src policy
kathiekiwi 3984b5c
fix(webapp): report card keeps its series and links, and the metric g…
kathiekiwi 08871bf
fix(webapp): fit the report metric row at the default panel width
kathiekiwi df64bec
fix(sdk): chat streams reconnect when the body ends mid-turn
kathiekiwi dd789e2
fix(sdk): re-earn the reconnect budget on any record and honor abort …
kathiekiwi a264213
fix(webapp): lift the finding line text 2px
kathiekiwi be99614
fix(webapp): the finding text lifts alone, the label stays with the icon
kathiekiwi 87f1d11
fix(webapp): drop the headline icon 4px
kathiekiwi df020b1
fix(webapp): finding text sits 2px up, not 4
kathiekiwi d6e1b4e
fix(webapp): a self-evident finding keeps its line and drops the echo…
kathiekiwi 6fcf1cf
fix(webapp): only the warning headline icon sits lower
kathiekiwi 709065a
fix(webapp): a delta arrow without a magnitude renders nothing
kathiekiwi 730ded1
test(webapp): a collapse to zero renders no arrow
kathiekiwi 94f786c
fix(webapp): chat code blocks render at 12px
kathiekiwi d1840a2
fix(webapp): revive the code block styling streamdown 2.5 renamed away
kathiekiwi 4d9af86
fix(dashboard-agent): navigate_to teaches the run-status vocabulary a…
kathiekiwi a6ae419
merge: origin/main into dashboard-agent base (resolve report-health)
kathiekiwi f93f8d6
fix(webapp): detect tables inside a JOIN…ON subquery for query-scope …
kathiekiwi fb4f0b3
fix(webapp): stop the agent body cap matching a task named dashboard-…
kathiekiwi bb077d4
fix(dashboard-agent): time out stalled agent HTTP calls
kathiekiwi 28c28e6
fix(webapp): cap report cache TTL to the liveness fresh window
kathiekiwi aef86b3
fix(webapp): reword the healthy-execution report line to not imply fa…
kathiekiwi 64eaf2f
fix(webapp): return 400 for ai-help with a missing query param
kathiekiwi 35ab0c5
fix(webapp): ceiling a capless user-actor token to read-only on env-J…
kathiekiwi 6cb0631
fix(webapp): clamp an env-JWT's lifetime to the delegated token's expiry
kathiekiwi e367280
fix(webapp): gate the agent maintenance cron on the agent being confi…
kathiekiwi 3f1eec6
docs(errors-api-e2e): mint the e2e token with an explicit write cap
kathiekiwi 502e1bd
fix(webapp): block remote images in model-authored markdown
kathiekiwi 65c60df
fix(webapp): reject CSP-delimiter characters in the img-src allowlist
kathiekiwi 237f01a
test(webapp): assert report cache TTL stays within the liveness fresh…
kathiekiwi ca2bacd
merge: base-misc review fixes (M2, M10a, H8, H13, M10r, ai-help, swee…
kathiekiwi b962949
test(webapp): assert StreamdownRenderer drops remote image src end-to…
kathiekiwi 94c3a47
merge: CSP review fixes (B2 block remote model images, B4 reject CSP …
kathiekiwi 39ba709
fix(dashboard-agent): fence and cap untrusted free-text in tool results
kathiekiwi 5a83bcb
fix(dashboard-agent): tell the agent fenced tool text is data, not in…
kathiekiwi bf6872d
fix(dashboard-agent): neutralize fence delimiters in payload and fenc…
kathiekiwi 2e58b36
fix(webapp,cli): cap user-actor token lifetime at 7 days
kathiekiwi 1acb6b8
fix(webapp): bind user-actor tokens to their source PAT
kathiekiwi c18a073
fix(webapp): give the agent's env JWT a stable rate-limit key
kathiekiwi 1e97712
test(webapp): cover the jwt-actor rate-limit branch, not just the helper
kathiekiwi a97ada5
fix(webapp): recheck the source PAT against the token's own claim, no…
kathiekiwi b14c8f3
merge: B3 prompt-injection boundary on tool results
kathiekiwi d6f67d4
merge: H3 PAT-bound token revocation + 7d ceiling, H9 stable rate-lim…
kathiekiwi 876ed56
feat(webapp,dashboard-agent-db): retain and purge deleted agent chats
kathiekiwi 18379a4
fix(webapp): don't let a failed agent-purge enqueue break org deletion
kathiekiwi 41fdffe
merge: org-delete agent-purge enqueue is best-effort
kathiekiwi 3977c2d
fix(dashboard-agent): finalize the turn's own message ids from newUIM…
kathiekiwi bd5f8fd
fix(dashboard-agent-db): scope softDeleteChat by organizationId
kathiekiwi 2932b88
merge: newUIMessages finalize + softDeleteChat org-scope review-comme…
kathiekiwi 40a610b
chore(server-changes): consolidate the dashboard-agent notes into one
kathiekiwi a1dc88a
merge: consolidate dashboard-agent server-changes 11 to 1
kathiekiwi 1424441
chore: consolidate this PR's changesets into one
kathiekiwi fe30aa4
docs(server-changes): restore dropped user-facing dashboard agent points
kathiekiwi f7dfc90
fix(webapp): stop the project-wide user-actor route 500-ing for claim…
kathiekiwi 32ea4b0
test(webapp): add the recheck export to stale user-actor auth mocks
kathiekiwi 4736a93
merge: add recheck/assertSourcePatActive exports to stale auth test m…
kathiekiwi db414b7
merge: consolidate PR changesets and restore dropped note points
kathiekiwi 2aeb32f
refactor(webapp): move dashboard-agent render UI to the UI PR
kathiekiwi 635cab2
chore: mark generated drizzle meta as generated
kathiekiwi 24c87c3
test(webapp): assert FROM-less SELECT is valid in the tsql linter
kathiekiwi 4962f7a
test(webapp): update the stale missing-FROM tsql linter case
kathiekiwi b2f1d82
test(webapp): move card/winner tests to the UI PR with their render code
kathiekiwi 54ab14f
chore: merge main into feat/dashboard-agent-flows
kathiekiwi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@trigger.dev/core": patch | ||
| "@trigger.dev/sdk": patch | ||
| "trigger.dev": patch | ||
| --- | ||
|
|
||
| Chat in the browser now reconnects when the connection drops mid-turn, instead of leaving the reply stuck as if it were still generating. Reports can be fetched as structured data with the `json` format, and the shortest report period is now one minute (`30m`, `1h`, `7d`). The `mint-token` command's help is clearer too: a token minted without `--cap` is read-only, and `--ttl` shows the correct maximum lifetime of 7 days. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # Generated, not hand-written: collapsed in diffs and excluded from language stats. | ||
| internal-packages/dashboard-agent-db/drizzle/meta/*.json linguist-generated=true | ||
| internal-packages/dashboard-agent-db/drizzle/meta/** linguist-generated=true | ||
| **/__snapshots__/*.snap linguist-generated=true | ||
| pnpm-lock.yaml linguist-generated=true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| area: webapp | ||
| type: feature | ||
| --- | ||
|
|
||
| Meet the dashboard agent: a chat in every environment that answers questions about your runs, queues, errors and health with real data and links, replacing Ask AI everywhere it used to appear. Investigate a failed run, an error, a backed-up queue or a run that hasn't started to get a worked-through answer — what happened, why, and how to fix it, with every claim linked to the runs, errors and deploys behind it. It reads your data read-only, works on preview and dev branches with that branch's own data, and reads the same everywhere — dashboard, terminal, editor. A very long chat keeps working: the agent summarises the earlier part and carries on. | ||
|
|
||
| A sample of conversations is scored automatically so the agent keeps getting better; only the score and a one-line summary are kept, never your messages, data or code, and we can switch it off for your organization on request. The Docs button is gone from page headers — ask the agent instead, or open Documentation from Help & Feedback. Separately, a queue's wait times, peak depth, throughput and throttling can now be read from the API. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
apps/webapp/app/components/code/StreamdownRenderer.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { createElement } from "react"; | ||
| import { renderToStaticMarkup } from "react-dom/server"; | ||
| import { describe, expect, it } from "vitest"; | ||
| import { restrictModelUrls, StreamdownRenderer } from "./StreamdownRenderer"; | ||
|
|
||
| // streamdown calls urlTransform(url, key, node) to compute each url attribute; a | ||
| // returned undefined removes the attribute, so no request is ever issued. | ||
| const img = { tagName: "img" } as any; | ||
| const link = { tagName: "a" } as any; | ||
|
|
||
| describe("restrictModelUrls (image src)", () => { | ||
| it("drops a remote model-authored image (the favicon beacon)", () => { | ||
| expect( | ||
| restrictModelUrls("https://www.google.com/s2/favicons?domain=evil", "src", img) | ||
| ).toBeUndefined(); | ||
| }); | ||
|
|
||
| it("drops any absolute or protocol-relative remote image", () => { | ||
| expect(restrictModelUrls("http://evil.tld/pixel.gif", "src", img)).toBeUndefined(); | ||
| expect(restrictModelUrls("//evil.tld/pixel.gif", "src", img)).toBeUndefined(); | ||
| }); | ||
|
|
||
| it("keeps inline and same-origin images", () => { | ||
| expect(restrictModelUrls("data:image/png;base64,AAAA", "src", img)).toBe( | ||
| "data:image/png;base64,AAAA" | ||
| ); | ||
| expect(restrictModelUrls("blob:abc", "src", img)).toBe("blob:abc"); | ||
| expect(restrictModelUrls("/local/pic.png", "src", img)).toBe("/local/pic.png"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("restrictModelUrls (link href)", () => { | ||
| it("keeps http(s), mailto and relative links", () => { | ||
| expect(restrictModelUrls("https://trigger.dev/docs", "href", link)).toBe( | ||
| "https://trigger.dev/docs" | ||
| ); | ||
| expect(restrictModelUrls("http://example.com", "href", link)).toBe("http://example.com"); | ||
| expect(restrictModelUrls("mailto:hi@trigger.dev", "href", link)).toBe("mailto:hi@trigger.dev"); | ||
| expect(restrictModelUrls("/runs/123", "href", link)).toBe("/runs/123"); | ||
| }); | ||
|
|
||
| it("drops unsafe link schemes", () => { | ||
| expect(restrictModelUrls("javascript:alert(1)", "href", link)).toBeUndefined(); | ||
| expect(restrictModelUrls("data:text/html,<script>", "href", link)).toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| // Force the lazy component to load, then return its resolved default so we can render it | ||
| // synchronously. This proves the policy is actually wired into the JSX, not just exported. | ||
| async function resolveStreamdownRenderer() { | ||
| const lazy = StreamdownRenderer as unknown as { | ||
| _payload: unknown; | ||
| _init: (payload: unknown) => (props: { children: string }) => JSX.Element; | ||
| }; | ||
| try { | ||
| lazy._init(lazy._payload); | ||
| } catch (thenable) { | ||
| await thenable; | ||
| } | ||
| return lazy._init(lazy._payload); | ||
| } | ||
|
|
||
| describe("StreamdownRenderer (rendered markdown)", () => { | ||
| it("never lets a model-authored remote image src reach the DOM", async () => { | ||
| const Renderer = await resolveStreamdownRenderer(); | ||
| const markdown = [ | ||
| "", | ||
| "", | ||
| "", | ||
| ].join("\n\n"); | ||
| const html = renderToStaticMarkup(createElement(Renderer, null, markdown)); | ||
|
|
||
| // No remote host is ever fetched: no absolute or protocol-relative image src survives. | ||
| expect(html).not.toContain('src="http'); | ||
| expect(html).not.toContain('src="//'); | ||
| expect(html).not.toContain("SECRET.evil.tld"); | ||
| // A same-origin relative image is untouched, so the policy does not over-block. | ||
| expect(html).toContain('src="/local/pic.png"'); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
apps/webapp/app/components/dashboard-agent/message-limits.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { | ||
| checkMessageParts, | ||
| declaredBodyBytes, | ||
| exceedsMessageBodyBytes, | ||
| MAX_MESSAGE_BODY_BYTES, | ||
| MAX_MESSAGE_CHARS, | ||
| MAX_MESSAGE_PARTS, | ||
| } from "./message-limits"; | ||
|
|
||
| describe("message limits", () => { | ||
| it("lets a long real question through", () => { | ||
| const text = "why did this fail?\n".repeat(50); | ||
|
|
||
| expect(exceedsMessageBodyBytes(Buffer.byteLength(text, "utf8"))).toBe(false); | ||
| expect(checkMessageParts([{ type: "text", text }])).toBeNull(); | ||
| }); | ||
|
|
||
| it("refuses a pasted dump by bytes", () => { | ||
| expect(exceedsMessageBodyBytes(MAX_MESSAGE_BODY_BYTES)).toBe(false); | ||
| expect(exceedsMessageBodyBytes(MAX_MESSAGE_BODY_BYTES + 1)).toBe(true); | ||
| }); | ||
|
|
||
| it("counts multi-byte characters as bytes, not characters", () => { | ||
| // Under the char cap, over the byte cap: 4 bytes each. | ||
| const emoji = "🙂".repeat(MAX_MESSAGE_BODY_BYTES / 4 + 1); | ||
|
|
||
| expect(emoji.length).toBeLessThan(MAX_MESSAGE_BODY_BYTES); | ||
| expect(exceedsMessageBodyBytes(Buffer.byteLength(emoji, "utf8"))).toBe(true); | ||
| }); | ||
|
|
||
| it("refuses a dump split across parts", () => { | ||
| const parts = Array.from({ length: 4 }, () => ({ | ||
| type: "text", | ||
| text: "x".repeat(MAX_MESSAGE_CHARS / 2), | ||
| })); | ||
|
|
||
| expect(checkMessageParts(parts)).toBe("too_long"); | ||
| }); | ||
|
|
||
| it("refuses too many parts", () => { | ||
| const parts = Array.from({ length: MAX_MESSAGE_PARTS + 1 }, () => ({ | ||
| type: "text", | ||
| text: "x", | ||
| })); | ||
|
|
||
| expect(checkMessageParts(parts)).toBe("too_many_parts"); | ||
| expect(checkMessageParts(parts.slice(0, MAX_MESSAGE_PARTS))).toBeNull(); | ||
| }); | ||
|
|
||
| it("leaves a shape that isn't a parts array to the schema", () => { | ||
| expect(checkMessageParts(undefined)).toBeNull(); | ||
| expect(checkMessageParts("nope")).toBeNull(); | ||
| }); | ||
|
|
||
| it("reads the declared size, or nothing when it isn't declared", () => { | ||
| expect(declaredBodyBytes(new Headers({ "content-length": "1234" }))).toBe(1234); | ||
| expect(declaredBodyBytes(new Headers())).toBeNull(); | ||
| expect(declaredBodyBytes(new Headers({ "content-length": "nope" }))).toBeNull(); | ||
| // An undeclared size can't be refused here; the body's own length is. | ||
| expect(exceedsMessageBodyBytes(null)).toBe(false); | ||
| }); | ||
| }); |
51 changes: 51 additions & 0 deletions
51
apps/webapp/app/components/dashboard-agent/message-limits.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /** | ||
| * Caps on one message to the agent, shared by the composer and the two server paths a message | ||
| * can arrive through. Generous for a real question with a pasted stack trace, stingy for a dump: | ||
| * an unbounded paste is a large model bill and a permanently fat transcript. | ||
| */ | ||
|
|
||
| /** ~2 pages of text, or a long stack trace. */ | ||
| export const MAX_MESSAGE_CHARS = 8_000; | ||
|
|
||
| /** The counter only shows near the limit, so a normal message never sees it. */ | ||
| export const MESSAGE_CHARS_WARN_AT = Math.floor(MAX_MESSAGE_CHARS * 0.9); | ||
|
|
||
| /** A composed message is a handful of parts; dozens means something is wrong. */ | ||
| export const MAX_MESSAGE_PARTS = 20; | ||
|
|
||
| /** | ||
| * The whole request body, in bytes: headroom for {@link MAX_MESSAGE_CHARS} of any script plus | ||
| * the per-turn metadata, and nothing like a pasted file. | ||
| */ | ||
| export const MAX_MESSAGE_BODY_BYTES = 64 * 1024; | ||
|
|
||
| export const MESSAGE_TOO_LARGE_CODE = "message_too_large"; | ||
|
|
||
| export const MESSAGE_TOO_LARGE_ERROR = "That message is too long. Shorten it and send again."; | ||
|
|
||
| export type MessagePartsProblem = "too_many_parts" | "too_long"; | ||
|
|
||
| /** Counts the parts and their text. Anything that isn't a parts array is left to the schema. */ | ||
| export function checkMessageParts(parts: unknown): MessagePartsProblem | null { | ||
| if (!Array.isArray(parts)) return null; | ||
| if (parts.length > MAX_MESSAGE_PARTS) return "too_many_parts"; | ||
|
|
||
| let chars = 0; | ||
| for (const part of parts) { | ||
| const text = (part as { text?: unknown } | null)?.text; | ||
| if (typeof text === "string") chars += text.length; | ||
| } | ||
| return chars > MAX_MESSAGE_CHARS ? "too_long" : null; | ||
| } | ||
|
|
||
| /** The declared body size, or null when the client didn't declare one. */ | ||
| export function declaredBodyBytes(headers: Headers): number | null { | ||
| const raw = headers.get("content-length"); | ||
| if (!raw) return null; | ||
| const bytes = Number.parseInt(raw, 10); | ||
| return Number.isFinite(bytes) ? bytes : null; | ||
| } | ||
|
|
||
| export function exceedsMessageBodyBytes(bytes: number | null | undefined): boolean { | ||
| return typeof bytes === "number" && bytes > MAX_MESSAGE_BODY_BYTES; | ||
| } |
32 changes: 32 additions & 0 deletions
32
apps/webapp/app/components/dashboard-agent/resolve-uris.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { MAX_URIS_PER_RESOLVE_REQUEST, planUriBatches } from "./resolve-uris"; | ||
|
|
||
| const uri = (index: number) => `trigger://runs/run_${index}`; | ||
|
|
||
| describe("planUriBatches", () => { | ||
| it("resolves a card's twenty citations in one request", () => { | ||
| const batches = planUriBatches(Array.from({ length: 20 }, (_, index) => uri(index))); | ||
|
|
||
| expect(batches).toHaveLength(1); | ||
| expect(batches[0]).toHaveLength(20); | ||
| }); | ||
|
|
||
| it("asks about each URI once", () => { | ||
| const batches = planUriBatches([uri(1), uri(1), uri(2)]); | ||
|
|
||
| expect(batches).toEqual([[uri(1), uri(2)]]); | ||
| }); | ||
|
|
||
| it("caps a request and carries the rest over", () => { | ||
| const count = MAX_URIS_PER_RESOLVE_REQUEST + 3; | ||
| const batches = planUriBatches(Array.from({ length: count }, (_, index) => uri(index))); | ||
|
|
||
| expect(batches).toHaveLength(2); | ||
| expect(batches[0]).toHaveLength(MAX_URIS_PER_RESOLVE_REQUEST); | ||
| expect(batches[1]).toHaveLength(3); | ||
| }); | ||
|
|
||
| it("has nothing to send for nothing", () => { | ||
| expect(planUriBatches([])).toEqual([]); | ||
| }); | ||
| }); |
25 changes: 25 additions & 0 deletions
25
apps/webapp/app/components/dashboard-agent/resolve-uris.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /** | ||
| * Batching for `trigger://` resolution. An investigation card cites ten to twenty targets, and | ||
| * each request re-authorises and re-resolves the environment — so they go in one request. | ||
| */ | ||
|
|
||
| /** One environment lookup and one repo lookup serve a whole batch. */ | ||
| export const MAX_URIS_PER_RESOLVE_REQUEST = 25; | ||
|
|
||
| /** A transient failure is worth retrying; a third one isn't. */ | ||
| export const MAX_RESOLVE_ATTEMPTS = 3; | ||
|
|
||
| export const RESOLVE_RETRY_DELAY_MS = 1_000; | ||
|
|
||
| /** Deduplicates, then splits into requests no bigger than the cap. */ | ||
| export function planUriBatches( | ||
| uris: readonly string[], | ||
| cap: number = MAX_URIS_PER_RESOLVE_REQUEST | ||
| ): string[][] { | ||
| const unique = [...new Set(uris)]; | ||
| const batches: string[][] = []; | ||
| for (let index = 0; index < unique.length; index += cap) { | ||
| batches.push(unique.slice(index, index + cap)); | ||
| } | ||
| return batches; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.