Skip to content
Merged
Show file tree
Hide file tree
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 Aug 5, 2026
d26a28b
perf(webapp): only poll the wake feed for a browser that knows a watc…
kathiekiwi Aug 5, 2026
1dd8536
perf(webapp): answer the wake poll with one narrow read per database
kathiekiwi Aug 5, 2026
8b042be
perf(webapp): skip the admin read on the watch authorization path
kathiekiwi Aug 5, 2026
be1cb04
perf(dashboard-agent-db): index the open investigations the sweep scans
kathiekiwi Aug 5, 2026
cafd920
chore: keep the release notes as one
kathiekiwi Aug 5, 2026
a88228c
chore(webapp): move the agent storybook gallery out to a follow-up
kathiekiwi Aug 5, 2026
b6ebe55
fix(dashboard-agent): escape the prefix separator so the file reviews…
kathiekiwi Aug 5, 2026
5b5a258
fix(webapp): scope a delegated user-actor token to the environment it…
kathiekiwi Aug 5, 2026
d6debe8
test(webapp): cover the user-actor token's environment scope on every…
kathiekiwi Aug 5, 2026
30ec754
refactor(dashboard-agent-db): move the watch queries into their own m…
kathiekiwi Aug 5, 2026
2ed5d52
refactor(dashboard-agent-db): move the watch tables into their own sc…
kathiekiwi Aug 5, 2026
46290d1
refactor(dashboard-agent): move the watch wake and investigate lanes …
kathiekiwi Aug 5, 2026
18dffa6
refactor(dashboard-agent): give the watch tests their own file
kathiekiwi Aug 5, 2026
a0d06e4
fix(webapp): send the agent wake signal to a browser that has never o…
kathiekiwi Aug 5, 2026
ebad471
fix(webapp): cap the size of a message to the agent
kathiekiwi Aug 5, 2026
c3ee6a4
perf(webapp): resolve the agent card's links in one request
kathiekiwi Aug 5, 2026
08dc7ba
fix(webapp): keep the watch card in step with its draft, and frozen w…
kathiekiwi Aug 5, 2026
7f13add
feat(webapp): retire judged-turn rows after 30 days
kathiekiwi Aug 5, 2026
42ddcff
feat(dashboard-agent): keep customer data out of the judged-turn row …
kathiekiwi Aug 6, 2026
3c5b2d5
feat(dashboard-agent): never judge a turn that read source
kathiekiwi Aug 6, 2026
a4b4ef1
feat(webapp): let an organization turn off judging of its agent turns
kathiekiwi Aug 6, 2026
263b324
feat(dashboard-agent): give the golden runs their own eval sample rate
kathiekiwi Aug 6, 2026
99b8933
docs(dashboard-agent): make the eval docs match the code
kathiekiwi Aug 6, 2026
29d9a8e
chore: note the conversation scoring in the release note
kathiekiwi Aug 6, 2026
31698fa
refactor(dashboard-agent): split the tool set into one module per res…
kathiekiwi Aug 5, 2026
fea1a29
refactor(dashboard-agent): split the watch tick into delivery, condit…
kathiekiwi Aug 6, 2026
4876e07
refactor(webapp): give each watch condition family its own module
kathiekiwi Aug 6, 2026
fc32fc6
refactor(webapp): split the suggested-prompt registry by responsibility
kathiekiwi Aug 6, 2026
f963614
chore(webapp): restore the dashboard agent's flag gate
kathiekiwi Aug 6, 2026
99850ac
feat(dashboard-agent): summarise an oversized conversation, keeping l…
kathiekiwi Aug 6, 2026
1d083f3
test(dashboard-agent): budget the real prompt prefix with ceilings an…
kathiekiwi Aug 6, 2026
42a76fb
perf(dashboard-agent): say a settled watch outcome without a model, a…
kathiekiwi Aug 6, 2026
0c41a70
perf(dashboard-agent): cap search_docs to a few short excerpts
kathiekiwi Aug 6, 2026
6c993f2
chore: note the summarised long conversations in the release note
kathiekiwi Aug 6, 2026
ddc6d25
fix(dashboard-agent): describe a value at the redaction depth cap ins…
kathiekiwi Aug 6, 2026
4e39228
fix(dashboard-agent): pin only a live investigation into a compacted …
kathiekiwi Aug 6, 2026
a3be7bd
fix(dashboard-agent): cap the output of the summariser and the wake n…
kathiekiwi Aug 6, 2026
e599f12
fix(dashboard-agent): rotate an over-cap watch group instead of starv…
kathiekiwi Aug 6, 2026
d719c9b
fix(webapp): start the wake poll for a browser that has only an activ…
kathiekiwi Aug 6, 2026
28b1f38
fix(webapp): cap the agent's request body while it streams, not after
kathiekiwi Aug 6, 2026
49a9538
fix(webapp): record a watch request before the watch starts, and repa…
kathiekiwi Aug 6, 2026
cbc2edc
test(webapp): pin the watch submit's ordering, its retry repair and i…
kathiekiwi Aug 6, 2026
4ff8859
fix(webapp): keep the agent's alert delete to channels the watch type…
kathiekiwi Aug 6, 2026
50fd10b
fix(webapp): replay a watch submission's recorded outcome instead of …
kathiekiwi Aug 6, 2026
1cdf1f0
refactor(webapp): split the dashboard agent's maintenance cron along …
kathiekiwi Aug 6, 2026
62b598d
docs(webapp): say which layer refuses an oversized agent body
kathiekiwi Aug 6, 2026
85b98ab
fix(webapp): mint the agent's token for the caller's own environment,…
kathiekiwi Aug 6, 2026
f5a146b
fix(webapp): cap the agent's body on any method and on a mixed-case path
kathiekiwi Aug 6, 2026
880bf60
test(tsql): pin that no mutating query can pass the query boundary
kathiekiwi Aug 6, 2026
9d45284
fix(webapp): keep the agent token's environment claim on the actor th…
kathiekiwi Aug 6, 2026
d51cb9a
fix(webapp): stop the OSS fallback handing a delegated token blanket …
kathiekiwi Aug 6, 2026
f58a9dc
fix(webapp): only let the browser set the agent's page context
kathiekiwi Aug 6, 2026
d12e6a8
style(webapp): merge duplicate imports on the user-actor auth path
kathiekiwi Aug 6, 2026
d2511bd
fix(webapp): hand the sweep's final check the previous facts
kathiekiwi Aug 6, 2026
e485209
perf(webapp): resolve the sweep's authorization and readers once per …
kathiekiwi Aug 6, 2026
bd2559a
fix(webapp): record only a real evaluation from a batch tick
kathiekiwi Aug 6, 2026
4846cf8
fix(dashboard-agent): finish a wake in the transcript, or don't count it
kathiekiwi Aug 6, 2026
012ce0d
test(webapp): drop an unsafe optional chain from the sweep boundary test
kathiekiwi Aug 6, 2026
5f2f5fe
fix(webapp): scope the watch submission ledger and its alerts to one …
kathiekiwi Aug 6, 2026
5176e3e
test(webapp): pin the watch ledger's tenancy, the once-only alert and…
kathiekiwi Aug 6, 2026
a63708c
test(webapp): assert the derived chat id before the submit outcome
kathiekiwi Aug 6, 2026
a0640ba
style(webapp): format the watch tenancy changes
kathiekiwi Aug 6, 2026
dd90b6e
fix(webapp): never render an image the agent's model authored
kathiekiwi Aug 6, 2026
94ec67f
perf(webapp): keep the agent transcript's investigation winners stable
kathiekiwi Aug 6, 2026
1aee9ca
fix(webapp): send the eval judge only the turn's structural facts
kathiekiwi Aug 6, 2026
488621e
perf(webapp): cache the agent's accumulated tool results between steps
kathiekiwi Aug 6, 2026
32553b9
style(webapp): format the agent markdown test
kathiekiwi Aug 6, 2026
d9ff853
style(webapp): format the agent eval and step-cache tests
kathiekiwi Aug 6, 2026
82f4a73
style(webapp): drop a needless escape in the image regex
kathiekiwi Aug 6, 2026
933aeb9
test(webapp): stub the agent proxy's environment lookup, not a Prisma…
kathiekiwi Aug 6, 2026
05b3472
Merge branch 'main' into feat/dashboard-agent-flows
kathiekiwi Aug 6, 2026
d73f3f5
chore: ignore the agent worktrees directory
kathiekiwi Aug 6, 2026
48d0d4a
fix(webapp): drop the img-src wildcard host for a validated origin al…
kathiekiwi Aug 6, 2026
cd112ba
test(webapp): assert the document img-src has no wildcard host
kathiekiwi Aug 6, 2026
5adba81
docs: document CSP_IMG_SRC_ALLOWLIST for self-hosted image hosts
kathiekiwi Aug 6, 2026
fb99526
feat(dashboard-agent): tell the eval judge a failure's kind without i…
kathiekiwi Aug 6, 2026
9daa8f9
docs(dashboard-agent): tell the judge the error category is derived, …
kathiekiwi Aug 6, 2026
09d1b55
fix(webapp): require the watch card's request id instead of falling b…
kathiekiwi Aug 6, 2026
22bf6d9
fix(webapp): cancel the watch a losing submit's winner does not name
kathiekiwi Aug 6, 2026
210863e
fix(webapp): separate a watch's last look from its last check in the …
kathiekiwi Aug 6, 2026
240799d
chore(dashboard-agent): drop the comments the code already says
kathiekiwi Aug 6, 2026
478b30c
chore(dashboard-agent): drop the test comments the assertions already…
kathiekiwi Aug 6, 2026
4e00b79
style(webapp): format the sweep's authorization helper
kathiekiwi Aug 6, 2026
d00b126
fix(webapp): bind a delegated token's environment claim on the projec…
kathiekiwi Aug 6, 2026
a7fc406
fix(webapp): keep a delegated token's claims on the authenticated ide…
kathiekiwi Aug 6, 2026
f700174
fix(webapp): ceiling an exchanged environment JWT by what the delegat…
kathiekiwi Aug 6, 2026
3336a47
test(webapp): give the container-backed user-actor scope tests their …
kathiekiwi Aug 6, 2026
7a645e5
chore(webapp): drop the comments the code already says
kathiekiwi Aug 6, 2026
79482de
chore(webapp): drop the comments the tests already say
kathiekiwi Aug 6, 2026
d646809
chore(rbac): keep the delegated-token invariant, drop the essay
kathiekiwi Aug 6, 2026
c4d3f58
fix(webapp): replay a watch submission's recorded email outcome inste…
kathiekiwi Aug 6, 2026
49e8fb5
fix(webapp): tell a watch's creator about their own email alerts, not…
kathiekiwi Aug 6, 2026
7b2f9c6
chore(dashboard-agent-db): drop the submission's external-notificatio…
kathiekiwi Aug 6, 2026
f7b8374
fix(webapp): put a turn's settled investigation card in the transcript
kathiekiwi Aug 6, 2026
26f93da
fix(webapp): have the stale-investigation sweep close the card it set…
kathiekiwi Aug 6, 2026
07b83da
docs: note the agent's closed investigations, watch email honesty and…
kathiekiwi Aug 6, 2026
60a9584
test(webapp): fail if a route sets an over-broad image CSP
kathiekiwi Aug 6, 2026
e62d8c6
style(webapp): format the route image CSP audit
kathiekiwi Aug 6, 2026
1759618
fix(webapp): keep the project-wide answer for an environment-agnostic…
kathiekiwi Aug 6, 2026
657f11d
docs(webapp): say which UAT flow the project-wide answer is preserved…
kathiekiwi Aug 6, 2026
29de34f
style(webapp): drop the imports the merge left unused
kathiekiwi Aug 6, 2026
5bf3612
fix(webapp): settle an investigation and its closing card in one write
kathiekiwi Aug 6, 2026
37c56aa
test(webapp): pin the settlement failure window and the open panel
kathiekiwi Aug 6, 2026
44b4e57
docs: say the investigation card closes while the panel is open
kathiekiwi Aug 6, 2026
49a1504
docs: cut the release note back to what a user notices
kathiekiwi Aug 6, 2026
2e23a09
fix(webapp): keep the other Anthropic options when the step breakpoin…
kathiekiwi Aug 6, 2026
f6f753c
fix(webapp): merge the transcript under the row lock instead of repla…
kathiekiwi Aug 6, 2026
afc0cfa
fix(webapp): close a consented watch investigation's card atomically
kathiekiwi Aug 6, 2026
d52c224
fix(dashboard-agent): merge the step breakpoint into the message's an…
kathiekiwi Aug 6, 2026
abaeebb
fix(webapp): make a repeated watch card close leave the revision alone
kathiekiwi Aug 6, 2026
c3f4ba8
refactor(webapp): drop the unused settleOpenInvestigations
kathiekiwi Aug 6, 2026
7ab35df
fix(webapp): merge the turn's cache breakpoint into the last message'…
kathiekiwi Aug 6, 2026
020dad2
refactor(webapp): store the dashboard agent's transcript one row per …
kathiekiwi Aug 6, 2026
81e8349
refactor(webapp): drop the dashboard agent's JSONB transcript column
kathiekiwi Aug 6, 2026
398a298
test(webapp): cover the batch position allocator with concurrent turns
kathiekiwi Aug 6, 2026
aad8498
fix(webapp): converge the watch transcript when a retried delivery fi…
kathiekiwi Aug 6, 2026
d8fda07
fix(webapp): stop an ordinary transcript write from rewriting a store…
kathiekiwi Aug 6, 2026
f159c69
test(webapp): guard against a raw-SQL reference to the dropped chats.…
kathiekiwi Aug 6, 2026
ceaa38d
fix(webapp): scope the watch transcript's appends to the caller's org…
kathiekiwi Aug 6, 2026
3d3c962
fix(webapp): keep a malformed message's own text out of the error, an…
kathiekiwi Aug 6, 2026
e3fcc20
refactor(dashboard-agent-db): collapse the agent's migrations into one
kathiekiwi Aug 6, 2026
f4337f7
style(dashboard-agent-db): format the generated migration snapshot
kathiekiwi Aug 6, 2026
bb036f9
feat(webapp): split Watch out of the dashboard agent's first PR
kathiekiwi Aug 6, 2026
9fad665
test(webapp): cover the dashboard agent's eval-policy gate, and requi…
kathiekiwi Aug 7, 2026
c37bdf8
test(webapp): pin the delegated token's scope ceiling on the RBAC fal…
kathiekiwi Aug 7, 2026
5edfd78
feat(webapp): name the dashboard agent Ask Trigger everywhere
kathiekiwi Aug 7, 2026
c88a448
refactor(webapp): rename the ask-ai button variant to ask-trigger
kathiekiwi Aug 7, 2026
0d3d216
feat(webapp): drop the agent button from the deploy blank states
kathiekiwi Aug 7, 2026
9c6f148
chore(webapp): keep Ask AI in the tree, deprecated and unmounted
kathiekiwi Aug 7, 2026
db9f9a2
Merge remote-tracking branch 'origin/main' into feat/dashboard-agent-…
kathiekiwi Aug 7, 2026
dfcee8b
fix(webapp): settle the API keys route after merging main
kathiekiwi Aug 7, 2026
49051f5
fix(dashboard-agent-db): stop rewriting the published migration history
kathiekiwi Aug 7, 2026
798fdf9
refactor(webapp): split the dashboard agent's UI out of the first PR
kathiekiwi Aug 7, 2026
c025bbf
fix(dashboard-agent): keep the finished answer in the transcript, not…
kathiekiwi Aug 7, 2026
2ff7ec0
fix(dashboard-agent): name a head-started chat instead of leaving it …
kathiekiwi Aug 7, 2026
711b79e
fix(dashboard-agent): apply the token's cap as a second ceiling, and …
kathiekiwi Aug 7, 2026
c236601
feat(webapp): let an environment JWT read a queue, as it already read…
kathiekiwi Aug 7, 2026
8adf2b2
Merge remote-tracking branch 'origin/main' into feat/dashboard-agent-…
kathiekiwi Aug 7, 2026
df2227e
fix(webapp): resolve a test's route paths from the test, not the repo…
kathiekiwi Aug 7, 2026
bd357fc
revert(webapp): put the JWT exchange back to how main had it
kathiekiwi Aug 7, 2026
f891584
fix(webapp): stub what the env JWT act-claim test's route actually calls
kathiekiwi Aug 7, 2026
cb0bcc9
fix(webapp): refuse an environment-scoped token on a route that names…
kathiekiwi Aug 7, 2026
0f2814e
fix(dashboard-agent): escape model-supplied ids in tool request paths
kathiekiwi Aug 7, 2026
33a3f6b
fix(dashboard-agent): require both halves of the support endpoint config
kathiekiwi Aug 7, 2026
983055a
fix(dashboard-agent): keep a withheld object's key names from the eva…
kathiekiwi Aug 7, 2026
5126bae
fix(webapp): gate a run's commit metadata on reading deployments
kathiekiwi Aug 7, 2026
2d23dde
fix(webapp): keep the retired chats.messages column, and allow Google…
kathiekiwi Aug 8, 2026
05505d5
fix(webapp): carry a queue's depth forward across empty buckets
kathiekiwi Aug 8, 2026
1ee6704
fix(webapp): describe how far a report metric fell
kathiekiwi Aug 8, 2026
0db1cf0
fix(webapp): say why a report's numbers can't be trusted
kathiekiwi Aug 8, 2026
d0be659
test(webapp): assert the queue depth trend fills its bucket grid
kathiekiwi Aug 8, 2026
4c42230
fix(webapp): stop treating a ClickHouse unknown identifier as a rollo…
kathiekiwi Aug 8, 2026
aa55227
fix(cli): say that a capless mint-token is read-only
kathiekiwi Aug 8, 2026
f598f96
fix(webapp): refuse a dashboard agent turn whose token mint failed
kathiekiwi Aug 8, 2026
b38c518
fix(webapp): stop a failed agent chat start leaving an empty chat behind
kathiekiwi Aug 8, 2026
de4cdad
fix(webapp): list the preview branch an agent token is scoped to
kathiekiwi Aug 8, 2026
e2704ea
fix(webapp): keep identityOnly off action routes in the type
kathiekiwi Aug 8, 2026
f6afda3
docs(dashboard-agent): say what the queue path encoding is for
kathiekiwi Aug 8, 2026
14d70be
docs(webapp): say that the agent is off until a flag turns it on
kathiekiwi Aug 8, 2026
c3f0d62
fix(webapp): undo a new agent chat only when its start never got anyw…
kathiekiwi Aug 8, 2026
314a1d7
perf(webapp): skip the global feature-flag query when a per-org overr…
kathiekiwi Aug 8, 2026
a4ba027
fix(webapp): refuse a delegated token at the entrance of the PAT-only…
kathiekiwi Aug 8, 2026
d0f06d5
fix(webapp,dashboard-agent): address a branch environment by name and…
kathiekiwi Aug 8, 2026
a660349
fix(webapp): make each report caveat discount only the input it names
kathiekiwi Aug 8, 2026
3355b68
fix(webapp): stop an unmeasured queue depth from silencing a measured…
kathiekiwi Aug 8, 2026
566de27
docs: say that the agent works on branches
kathiekiwi Aug 8, 2026
ad2698b
feat(webapp): render the cards the flows already emit
kathiekiwi Aug 8, 2026
643ea39
fix(dashboard-agent): read the report's untrustworthy reason under it…
kathiekiwi Aug 8, 2026
ed0a7b3
fix(dashboard-agent): stop a retrieved run's own error counting as a …
kathiekiwi Aug 8, 2026
0cae301
fix(dashboard-agent): pin the investigation cards the transcript actu…
kathiekiwi Aug 8, 2026
8fc6f20
style(dashboard-agent): format the two eval test files
kathiekiwi Aug 8, 2026
c3b7c10
refactor(dashboard-agent): move the watch data layer into the Watch PR
kathiekiwi Aug 8, 2026
36691f1
chore: mark drizzle and test snapshots as generated
kathiekiwi Aug 8, 2026
7e272cc
chore(webapp): drop the unused waiting-run diagnosis
kathiekiwi Aug 8, 2026
d40e9fe
chore: mark the lockfile as generated too
kathiekiwi Aug 9, 2026
2aacd26
fix(webapp,dashboard-agent): keep investigation cards, and stop a bro…
kathiekiwi Aug 9, 2026
8fd5369
revert(webapp): move the queue JWT read to the PR that uses it
kathiekiwi Aug 9, 2026
0fd886c
perf(webapp): stop the agent transcript re-rendering on every streame…
kathiekiwi Aug 9, 2026
9805c0a
fix(webapp): deny an unknown report key instead of authorizing it aga…
kathiekiwi Aug 9, 2026
20c8f9e
fix(webapp): stop an unreadable eval override reading as consent to j…
kathiekiwi Aug 9, 2026
61a3288
fix(webapp): scope the org-creation RBAC check to user-actor tokens
kathiekiwi Aug 9, 2026
8fbbcf0
test(webapp): pin 404-before-403 ordering for capped user-actor tokens
kathiekiwi Aug 9, 2026
637a2bb
test(webapp): pin that an ordinary PAT still passes the PAT-only helper
kathiekiwi Aug 9, 2026
82b93db
fix(webapp): compute answered for view blocks in the base message ren…
kathiekiwi Aug 9, 2026
d304c1a
test(webapp): assert the org route never consults a plain PAT ability
kathiekiwi Aug 9, 2026
8f017ec
test(webapp): pin that the disabled org route never consults the toke…
kathiekiwi Aug 9, 2026
49f42de
fix(webapp): allow the org-avatar favicon endpoint in the img-src policy
kathiekiwi Aug 9, 2026
3984b5c
fix(webapp): report card keeps its series and links, and the metric g…
kathiekiwi Aug 9, 2026
08871bf
fix(webapp): fit the report metric row at the default panel width
kathiekiwi Aug 9, 2026
df64bec
fix(sdk): chat streams reconnect when the body ends mid-turn
kathiekiwi Aug 9, 2026
dd789e2
fix(sdk): re-earn the reconnect budget on any record and honor abort …
kathiekiwi Aug 9, 2026
a264213
fix(webapp): lift the finding line text 2px
kathiekiwi Aug 9, 2026
be99614
fix(webapp): the finding text lifts alone, the label stays with the icon
kathiekiwi Aug 9, 2026
87f1d11
fix(webapp): drop the headline icon 4px
kathiekiwi Aug 9, 2026
df020b1
fix(webapp): finding text sits 2px up, not 4
kathiekiwi Aug 9, 2026
d6e1b4e
fix(webapp): a self-evident finding keeps its line and drops the echo…
kathiekiwi Aug 9, 2026
6fcf1cf
fix(webapp): only the warning headline icon sits lower
kathiekiwi Aug 9, 2026
709065a
fix(webapp): a delta arrow without a magnitude renders nothing
kathiekiwi Aug 10, 2026
730ded1
test(webapp): a collapse to zero renders no arrow
kathiekiwi Aug 10, 2026
94f786c
fix(webapp): chat code blocks render at 12px
kathiekiwi Aug 10, 2026
d1840a2
fix(webapp): revive the code block styling streamdown 2.5 renamed away
kathiekiwi Aug 10, 2026
4d9af86
fix(dashboard-agent): navigate_to teaches the run-status vocabulary a…
kathiekiwi Aug 10, 2026
a6ae419
merge: origin/main into dashboard-agent base (resolve report-health)
kathiekiwi Aug 10, 2026
f93f8d6
fix(webapp): detect tables inside a JOIN…ON subquery for query-scope …
kathiekiwi Aug 10, 2026
fb4f0b3
fix(webapp): stop the agent body cap matching a task named dashboard-…
kathiekiwi Aug 10, 2026
bb077d4
fix(dashboard-agent): time out stalled agent HTTP calls
kathiekiwi Aug 10, 2026
28c28e6
fix(webapp): cap report cache TTL to the liveness fresh window
kathiekiwi Aug 10, 2026
aef86b3
fix(webapp): reword the healthy-execution report line to not imply fa…
kathiekiwi Aug 10, 2026
64eaf2f
fix(webapp): return 400 for ai-help with a missing query param
kathiekiwi Aug 10, 2026
35ab0c5
fix(webapp): ceiling a capless user-actor token to read-only on env-J…
kathiekiwi Aug 10, 2026
6cb0631
fix(webapp): clamp an env-JWT's lifetime to the delegated token's expiry
kathiekiwi Aug 10, 2026
e367280
fix(webapp): gate the agent maintenance cron on the agent being confi…
kathiekiwi Aug 10, 2026
3f1eec6
docs(errors-api-e2e): mint the e2e token with an explicit write cap
kathiekiwi Aug 10, 2026
502e1bd
fix(webapp): block remote images in model-authored markdown
kathiekiwi Aug 10, 2026
65c60df
fix(webapp): reject CSP-delimiter characters in the img-src allowlist
kathiekiwi Aug 10, 2026
237f01a
test(webapp): assert report cache TTL stays within the liveness fresh…
kathiekiwi Aug 10, 2026
ca2bacd
merge: base-misc review fixes (M2, M10a, H8, H13, M10r, ai-help, swee…
kathiekiwi Aug 10, 2026
b962949
test(webapp): assert StreamdownRenderer drops remote image src end-to…
kathiekiwi Aug 10, 2026
94c3a47
merge: CSP review fixes (B2 block remote model images, B4 reject CSP …
kathiekiwi Aug 10, 2026
39ba709
fix(dashboard-agent): fence and cap untrusted free-text in tool results
kathiekiwi Aug 10, 2026
5a83bcb
fix(dashboard-agent): tell the agent fenced tool text is data, not in…
kathiekiwi Aug 10, 2026
bf6872d
fix(dashboard-agent): neutralize fence delimiters in payload and fenc…
kathiekiwi Aug 10, 2026
2e58b36
fix(webapp,cli): cap user-actor token lifetime at 7 days
kathiekiwi Aug 10, 2026
1acb6b8
fix(webapp): bind user-actor tokens to their source PAT
kathiekiwi Aug 10, 2026
c18a073
fix(webapp): give the agent's env JWT a stable rate-limit key
kathiekiwi Aug 10, 2026
1e97712
test(webapp): cover the jwt-actor rate-limit branch, not just the helper
kathiekiwi Aug 10, 2026
a97ada5
fix(webapp): recheck the source PAT against the token's own claim, no…
kathiekiwi Aug 10, 2026
b14c8f3
merge: B3 prompt-injection boundary on tool results
kathiekiwi Aug 10, 2026
d6f67d4
merge: H3 PAT-bound token revocation + 7d ceiling, H9 stable rate-lim…
kathiekiwi Aug 10, 2026
876ed56
feat(webapp,dashboard-agent-db): retain and purge deleted agent chats
kathiekiwi Aug 10, 2026
18379a4
fix(webapp): don't let a failed agent-purge enqueue break org deletion
kathiekiwi Aug 10, 2026
41fdffe
merge: org-delete agent-purge enqueue is best-effort
kathiekiwi Aug 10, 2026
3977c2d
fix(dashboard-agent): finalize the turn's own message ids from newUIM…
kathiekiwi Aug 10, 2026
bd5f8fd
fix(dashboard-agent-db): scope softDeleteChat by organizationId
kathiekiwi Aug 10, 2026
2932b88
merge: newUIMessages finalize + softDeleteChat org-scope review-comme…
kathiekiwi Aug 10, 2026
40a610b
chore(server-changes): consolidate the dashboard-agent notes into one
kathiekiwi Aug 11, 2026
a1dc88a
merge: consolidate dashboard-agent server-changes 11 to 1
kathiekiwi Aug 11, 2026
1424441
chore: consolidate this PR's changesets into one
kathiekiwi Aug 11, 2026
fe30aa4
docs(server-changes): restore dropped user-facing dashboard agent points
kathiekiwi Aug 11, 2026
f7dfc90
fix(webapp): stop the project-wide user-actor route 500-ing for claim…
kathiekiwi Aug 11, 2026
32ea4b0
test(webapp): add the recheck export to stale user-actor auth mocks
kathiekiwi Aug 11, 2026
4736a93
merge: add recheck/assertSourcePatActive exports to stale auth test m…
kathiekiwi Aug 11, 2026
db414b7
merge: consolidate PR changesets and restore dropped note points
kathiekiwi Aug 11, 2026
2aeb32f
refactor(webapp): move dashboard-agent render UI to the UI PR
kathiekiwi Aug 11, 2026
635cab2
chore: mark generated drizzle meta as generated
kathiekiwi Aug 11, 2026
24c87c3
test(webapp): assert FROM-less SELECT is valid in the tsql linter
kathiekiwi Aug 11, 2026
4962f7a
test(webapp): update the stale missing-FROM tsql linter case
kathiekiwi Aug 11, 2026
b2f1d82
test(webapp): move card/winner tests to the UI PR with their render code
kathiekiwi Aug 11, 2026
54ab14f
chore: merge main into feat/dashboard-agent-flows
kathiekiwi Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/chat-stream-mid-turn-reconnect.md
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.
2 changes: 1 addition & 1 deletion .claude/skills/errors-api-e2e/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ PASS: one run, `run_<RID>` (status maps to `FAILED`). Proves `filter[error]` ->
### 6. Attribution — `mint-token` -> JWT exchange records the acting user

```bash
TOKEN=$(cli mint-token --profile $PROFILE --client errors-api-e2e 2>/dev/null) # UAT
TOKEN=$(cli mint-token --profile $PROFILE --client errors-api-e2e --cap read:errors,write:errors 2>/dev/null) # UAT
ENVJWT=$(curl -sS -X POST "$B/api/v1/projects/$REF/dev/jwt" -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"claims":{"scopes":["read:errors","write:errors"]}}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")
Expand Down
5 changes: 5 additions & 0 deletions .gitattributes
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,5 @@ ailogger-output.log

# observability-map CLI output artifact, not committed
observability-map.json

.claude/worktrees/
8 changes: 8 additions & 0 deletions .server-changes/dashboard-agent.md
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.
5 changes: 4 additions & 1 deletion apps/webapp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ node_modules
/cypress/screenshots
/cypress/videos

# Output of `pnpm run agent-ui:screenshots`
/screenshots

/app/styles/tailwind.css

# Ensure the .env symlink is not removed by accident
Expand All @@ -20,4 +23,4 @@ storybook-static

/prisma/seed.js
/prisma/populate.js
.memory-snapshots
.memory-snapshots
9 changes: 9 additions & 0 deletions apps/webapp/app/components/AskAI.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/**
* @deprecated Superseded by the dashboard agent (`components/dashboard-agent`). Nothing mounts
* this any more — every Ask AI entry point now opens Ask Trigger. Kept until the agent has
* shipped, then removed along with `@kapaai/react-sdk` and `KAPA_AI_WEBSITE_ID`.
*/

import {
ArrowPathIcon,
ArrowUpIcon,
Comment thread
kathiekiwi marked this conversation as resolved.
Expand Down Expand Up @@ -81,6 +87,8 @@ function useAskAIState() {
* it around the popover, not inside, so the dialog and shortcut survive the popover closing.
* `children` receives the open function, or undefined when Ask AI is unavailable (self-hosted, no
* Kapa website id, or SSR).
*
* @deprecated See the note at the top of this file.
*/
export function AskAIRoot({
children,
Expand Down Expand Up @@ -137,6 +145,7 @@ function AskAIRootProvider({
);
}

/** @deprecated See the note at the top of this file. */
export function AskAI({ isCollapsed = false }: { isCollapsed?: boolean }) {
const { isManagedCloud } = useFeatures();
const websiteId = useKapaWebsiteId();
Expand Down
80 changes: 80 additions & 0 deletions apps/webapp/app/components/code/StreamdownRenderer.test.ts
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 = [
"![x](https://www.google.com/s2/favicons?domain=SECRET.evil.tld)",
"![y](//evil.tld/pixel.gif)",
"![z](/local/pic.png)",
].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"');
});
});
30 changes: 29 additions & 1 deletion apps/webapp/app/components/code/StreamdownRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
import { lazy } from "react";
import type { CodeHighlighterPlugin } from "streamdown";
import type { CodeHighlighterPlugin, UrlTransform } from "streamdown";

const SAFE_LINK_SCHEMES = new Set(["http:", "https:", "mailto:"]);

/**
* URL policy for model-authored markdown. A remote image is fetched the moment it
* renders — no click — so it is a zero-click data beacon; we drop the src of any
* non-local image. Links stay clickable but only for safe, human-followable schemes.
* streamdown removes an attribute whose transform returns undefined, so no request fires.
*/
export const restrictModelUrls: UrlTransform = (url, key, node) => {
const value = url.trim();
const isImage = node.tagName === "img" || key === "src" || key === "srcset";

if (isImage) {
// Inline images carry their own bytes; a relative path resolves to our own origin.
if (/^data:/i.test(value) || /^blob:/i.test(value)) return url;
// Absolute or protocol-relative means a remote host — strip it so nothing is fetched.
if (/^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith("//")) return undefined;
return url;
}
Comment thread
kathiekiwi marked this conversation as resolved.

// Links: relative and protocol-relative are fine; otherwise require a safe scheme.
if (value.startsWith("//")) return url;
const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(value);
if (!schemeMatch) return url;
return SAFE_LINK_SCHEMES.has(`${schemeMatch[1].toLowerCase()}:`) ? url : undefined;
};

export const StreamdownRenderer = lazy(() =>
Promise.all([import("streamdown"), import("@streamdown/code"), import("./shikiTheme")]).then(
Expand All @@ -23,6 +50,7 @@ export const StreamdownRenderer = lazy(() =>
isAnimating={isAnimating}
plugins={{ code: codePlugin }}
controls={{ code: { copy: false, download: false } }}
urlTransform={restrictModelUrls}
linkSafety={{ enabled: false }}
>
{children}
Expand Down
7 changes: 4 additions & 3 deletions apps/webapp/app/components/code/tsql/tsqlLinter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ describe("tsqlLinter", () => {
expect(error).toContain("line");
});

it("should handle missing FROM clause", () => {
const error = getTSQLError("SELECT * WHERE id = 1");
expect(error).not.toBeNull();
it("should accept a query without a FROM clause", () => {
// FROM is optional in the grammar (ClickHouse allows e.g. `SELECT 1`),
// so a FROM-less SELECT is syntactically valid.
expect(getTSQLError("SELECT * WHERE id = 1")).toBeNull();
});
});
});
63 changes: 63 additions & 0 deletions apps/webapp/app/components/dashboard-agent/message-limits.test.ts
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 apps/webapp/app/components/dashboard-agent/message-limits.ts
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 apps/webapp/app/components/dashboard-agent/resolve-uris.test.ts
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 apps/webapp/app/components/dashboard-agent/resolve-uris.ts
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;
}
Loading
Loading