Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ff389e8
chore: sync main into develop after stable release [skip ci]
github-actions[bot] May 27, 2026
35d31b2
feat(widget-core): add model and theme settings controls
Seungwoo321 May 28, 2026
19168c7
feat(settings): persist theme and model selections
Seungwoo321 May 28, 2026
1ec93cf
fix(widget-core): persist widget settings and visibility state
Seungwoo321 May 28, 2026
6317aa3
fix(core): use Claude Code preset for ACP runtime
Seungwoo321 May 28, 2026
70cd123
feat(widget-core): persist widget state and apply theme tokens
Seungwoo321 May 28, 2026
240331e
docs(troubleshooting): refresh mount.ts references
Seungwoo321 May 28, 2026
d42672f
fix(stream): keep pending indicator through tool idle gaps
Seungwoo321 May 28, 2026
dbf52ca
test(widget): cover shadow root theme token fallbacks
Seungwoo321 May 28, 2026
e113c1d
fix(stream): improve pending indicator and theme-aware styles
Seungwoo321 May 28, 2026
7307628
feat(server): accept per-request model selection
Seungwoo321 May 28, 2026
c3ed781
fix(widget-core): theme composer controls and expose state
Seungwoo321 May 28, 2026
36e7362
fix(core): align SDK provider with Claude Code defaults
Seungwoo321 May 28, 2026
245085f
test(composer): stabilize theme token state assertions
Seungwoo321 May 28, 2026
ccc1061
test(transport): cover model and 503 retry cases
Seungwoo321 May 28, 2026
7901e49
fix(transport): retry agent-not-ready requests safely
Seungwoo321 May 28, 2026
7afc095
docs(security): correct mount path reference
Seungwoo321 May 28, 2026
934e3ec
feat(core): support per-turn ACP model selection
Seungwoo321 May 28, 2026
ebe21d6
ci(release): retry publish pushes after rebasing
Seungwoo321 May 28, 2026
bd833ba
docs(react): update retry transport option docs
Seungwoo321 May 28, 2026
cd230a6
chore(changeset): add widget theme release note
Seungwoo321 May 28, 2026
6364566
docs(widget): refresh guide for widget-core settings changes
Seungwoo321 May 28, 2026
e6f72ea
docs(widget): refresh widget core documentation
Seungwoo321 May 28, 2026
bf12c86
refactor(widget-core): theme handoff modal styles
Seungwoo321 May 28, 2026
b621331
docs(transport): document retry backoff behavior
Seungwoo321 May 28, 2026
2945e59
docs(security): fix production guard source link
Seungwoo321 May 28, 2026
4cdbe4b
docs(model): add model selection release note
Seungwoo321 May 28, 2026
778a342
feat(widget-core): make launcher colors themeable
Seungwoo321 May 28, 2026
0c3529c
docs(guides): fix widget-core source references
Seungwoo321 May 28, 2026
64c4edb
chore(comments): remove stale ticket references
Seungwoo321 May 28, 2026
3fbaf3b
chore(changesets): add widget-core release notes
Seungwoo321 May 28, 2026
5653ec0
chore: version agent-devtools beta prerelease [skip ci]
github-actions[bot] May 28, 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
22 changes: 22 additions & 0 deletions .changeset/model-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@agent-devtools/core': minor
'@agent-devtools/widget-core': minor
---

Add model selection so a prompt can run on the same models the Claude Code
terminal offers. A new `model` setting exposes the terminal's `/model` menu —
`default`, `opus`, `sonnet`, `haiku` — in the settings panel, persists in
localStorage alongside the provider, permission mode and theme, and rides on
each request body. `default` is a sentinel that sends no model on the wire, so
the chosen provider keeps its own default exactly as it does today.

Both providers resolve the alias through the shared Claude Agent SDK resolver,
so no live model-discovery round-trip is needed. The SDK provider forwards the
alias as the `query()` `model` option. The ACP provider applies it with
`session/set_model` after the session is established and before the prompt is
dispatched; it remembers the last applied model per session to skip a redundant
round-trip when the model is unchanged across turns, and surfaces an error
(rather than silently running on the wrong model) if the agent rejects the
request. The server validates only that `model` is a non-empty string and
forwards it verbatim, leaving the model set open for full date-pinned ids or
future tiers without a protocol change.
24 changes: 24 additions & 0 deletions .changeset/network-resilience-retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@agent-devtools/widget-core': minor
---

Absorb the dev-server respawn window so a hot reload no longer surfaces a
spurious network error on the next prompt. The default transport already
retried when `fetch()` rejected before any Response (the request never left
the client); it now treats a `503` from the dev-server proxy the same way,
because the proxy returns `503 "agent server not ready"` _before_ forwarding
anything upstream while the agent server respawns — so the prompt never
reached the agent and a retry can't duplicate the turn. This is the common
"network error right after a dev-server restart / hot reload" case.

Retries now use capped exponential backoff (base `300ms`, cap `2000ms`,
default four retries ≈ 4.1s total) so a multi-second respawn is waited out
while a genuinely dead server still fails within a bounded window. A new
`preResponseRetryMaxBackoffMs` option exposes the cap, and the default
retry count rose from 1 to 4.

The idempotency boundary is unchanged: any failure that proves the prompt
reached the agent — a `2xx` stream that later drops mid-flight, `500`,
`502`, `401`, or a silent-stream timeout — is never auto-retried, since the
agent may have already started editing files and re-sending would re-run the
LLM. Those still surface as an error for the user to retry deliberately.
42 changes: 42 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"mode": "pre",
"tag": "beta",
"initialVersions": {
"@agent-devtools/docs": "0.0.2",
"@agent-devtools/example-angular-vite": "0.0.0",
"@agent-devtools/example-html": "0.0.0",
"@agent-devtools/example-next": "0.0.0",
"@agent-devtools/example-next-pages": "0.0.0",
"@agent-devtools/example-nuxt": "0.0.0",
"@agent-devtools/example-nuxt2": "0.0.0",
"@agent-devtools/example-react-vite": "0.0.0",
"@agent-devtools/example-svelte-vite": "0.0.0",
"@agent-devtools/example-sveltekit": "0.0.0",
"@agent-devtools/example-vue-vite": "0.0.0",
"@agent-devtools/example-vue2-vite": "0.0.0",
"@agent-devtools/angular": "1.0.0",
"@agent-devtools/core": "1.0.0",
"@agent-devtools/e2e": "0.0.0",
"@agent-devtools/harness-core": "1.0.0",
"@agent-devtools/html": "1.0.0",
"@agent-devtools/next": "1.0.0",
"@agent-devtools/next-pages": "1.0.0",
"@agent-devtools/nuxt": "1.0.0",
"@agent-devtools/nuxt2": "1.0.0",
"@agent-devtools/react": "1.0.0",
"@agent-devtools/svelte": "1.0.0",
"@agent-devtools/sveltekit": "1.0.0",
"@agent-devtools/vite": "1.0.0",
"@agent-devtools/vue": "1.0.0",
"@agent-devtools/vue2": "1.0.0",
"@agent-devtools/widget-core": "1.0.0"
},
"changesets": [
"model-selection",
"network-resilience-retry",
"sdk-claude-code-preset",
"widget-theme",
"widget-visibility-persistence",
"working-indicator-full-coverage"
]
}
5 changes: 5 additions & 0 deletions .changeset/sdk-claude-code-preset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@agent-devtools/core': patch
---

Fix the in-process SDK provider being rejected with "API Error: 400 role 'system' is not supported on this model". The provider omitted `systemPrompt`, so the Claude Agent SDK fell back to its minimal default prompt instead of the full Claude Code prompt that `claude -p` uses by default. It now opts into the `claude_code` preset, restoring terminal parity, and pins `settingSources` so project `CLAUDE.md` context cannot be silently dropped by a future SDK default change.
27 changes: 27 additions & 0 deletions .changeset/widget-theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@agent-devtools/widget-core': minor
---

Add a theme to the floating chat and every widget surface: a new `theme`
setting with `auto` (the default), `light`, and `dark`. `auto` follows the
operating system's `prefers-color-scheme`; `light` and `dark` pin the choice.
The setting persists in localStorage alongside the provider and permission mode,
and switching it flips a single `data-theme` attribute on the closed shadow
host, so the browser recomputes every colour through CSS custom properties with
no per-component re-render.

The dark palette is the only set of tokens defined centrally on the host. Light
is the absence of tokens: every surface reads its colour as
`var(--adt-token, <literal>)`, where the literal fallback is that element's
original light colour. So light stays byte-identical to the previous look and
each surface keeps its own light nuance, while dark is single-sourced — the same
token can resolve to a different light value per surface (a user bubble's text
is white in light, body text is near-black, and both become the same light grey
in dark). Surfaces that are intentionally dark in both themes (the picked-element
code card) keep their dark treatment by reading a raised-surface token rather
than inverting with the accent.

Every widget surface participates: the composer, launcher, message stream,
picked-element evidence, tool output, handoff modal, and settings panel. The
launcher and accent controls invert correctly so dark mode reads as a true dark
theme rather than a tinted light one.
16 changes: 16 additions & 0 deletions .changeset/widget-visibility-persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@agent-devtools/widget-core': minor
---

Persist the widget's visibility across page reloads. The orchestrator now
remembers two on/off axes in localStorage and restores them on mount: the
composer panel's open/closed state (toggled by the launcher, the close button,
Escape, or picking an element) and the widget-level visibility (toggled by the
Ctrl/Cmd+Shift+; hotkey). This matches the standard devtools convention where
the tool reopens in the state you left it. Persistence lives in the
orchestrator rather than the composer because only the orchestrator can tell a
user-driven open/close apart from a system-driven transient collapse (the panel
hiding during element-picking, or the whole surface going dark), so a transient
collapse never clobbers the user's remembered choice. Storage access is wrapped
in try/catch and degrades silently where localStorage is unavailable (file://,
private mode, sandboxed iframes, quota-exceeded).
15 changes: 15 additions & 0 deletions .changeset/working-indicator-full-coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@agent-devtools/widget-core': patch
---

Show the working ("typing") indicator during every idle period of a turn, not
only while waiting for the first response. Previously the three-dot indicator
was a one-shot placeholder pushed when the user submitted and removed on the
first assistant event, so in an agentic turn the surface looked frozen while a
tool executed and while the model round-tripped on a tool result. The indicator
is now a derived view of the conversation state: it sits at the tail whenever a
turn is in flight and the assistant is between visible actions (after submit,
while a tool runs, and during the model round-trip after a tool result), and is
dropped the moment text or tool input streams again or the turn ends. It is
deliberately not shown after a finished text block, since a turn that ends on
text emits its completion immediately and a dot there would only flash.
69 changes: 58 additions & 11 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,17 @@ jobs:
# Commit + push the version bump immediately so the next run's
# `changeset publish` sees the same tree the npm registry will see.
# [skip ci] prevents this commit from re-triggering the workflow.
# Retry once on non-fast-forward: between checkout and push another
# job (or a manual push) may have advanced the branch — pull --rebase
# and re-push so the version commit lands on top instead of failing.
if [ -n "$(git status --porcelain)" ]; then
git add -A
git commit -m "chore: version agent-devtools beta prerelease [skip ci]"
git push origin "${GITHUB_REF_NAME}"
if ! git push origin "${GITHUB_REF_NAME}"; then
echo "non-fast-forward on first push; rebasing and retrying"
git pull --rebase --autostash origin "${GITHUB_REF_NAME}"
git push origin "${GITHUB_REF_NAME}"
fi
fi

# Pre mode publish: DO NOT pass --tag here. Changesets routes
Expand All @@ -105,7 +112,10 @@ jobs:
# versioned; annotated per-package tags are created and pushed
# together with --follow-tags.
pnpm exec changeset publish
git push --follow-tags origin "${GITHUB_REF_NAME}"
if ! git push --follow-tags origin "${GITHUB_REF_NAME}"; then
git pull --rebase --autostash origin "${GITHUB_REF_NAME}"
git push --follow-tags origin "${GITHUB_REF_NAME}"
fi

- name: Publish Release (main branch)
if: github.ref == 'refs/heads/main'
Expand All @@ -126,25 +136,59 @@ jobs:
pnpm exec changeset version
git add -A
git commit -m "chore: release agent-devtools packages [skip ci]"
git push origin "${GITHUB_REF_NAME}"
if ! git push origin "${GITHUB_REF_NAME}"; then
echo "non-fast-forward on first push; rebasing and retrying"
git pull --rebase --autostash origin "${GITHUB_REF_NAME}"
git push origin "${GITHUB_REF_NAME}"
fi
fi
pnpm exec changeset publish
git push --follow-tags origin "${GITHUB_REF_NAME}"
if ! git push --follow-tags origin "${GITHUB_REF_NAME}"; then
git pull --rebase --autostash origin "${GITHUB_REF_NAME}"
git push --follow-tags origin "${GITHUB_REF_NAME}"
fi

- name: Sync main to develop
if: github.ref == 'refs/heads/main'
run: |
# Single-workflow back-merge — mirrors the canonical template's
# closing step. After a stable release main owns the release
# bookkeeping (package versions, CHANGELOGs, pre-mode state). Take
# main's side on conflicts; develop re-enters pre mode on its next
# push. The normal back-merge is conflict-free; the --theirs block
# only fires on a race (a develop push overlapping the release).
# bookkeeping (package versions, CHANGELOGs, pre-mode state). The
# normal back-merge is conflict-free; the resolution block below only
# fires on a race (a develop push overlapping the release).
#
# Race-loss-safe conflict policy:
# - For packages/*/package.json and CHANGELOG.md: take whichever
# side has the higher SemVer version. This preserves a forward
# prerelease line on develop (e.g. 1.1.0-beta.N) instead of
# regressing it to the stable version that just landed on main.
# A naive `--theirs` (main always wins) would drag develop's
# versions backwards — the exact regression observed when PR #9's
# push lost a non-fast-forward race. The next stable release
# reconciles the prerelease line into the final X.Y.Z anyway.
# - For .changeset/pre.json and consumed .changeset/*.md: accept
# main's deletion. These are modify/delete conflicts; develop
# re-enters pre mode on its next push and recreates pre.json.
git fetch origin develop
git checkout develop
if ! git merge --no-ff main -m "chore: sync main into develop after stable release [skip ci]"; then
git checkout --theirs packages/*/package.json packages/*/CHANGELOG.md 2>/dev/null || true
git add packages/*/package.json packages/*/CHANGELOG.md 2>/dev/null || true
for f in $(git diff --name-only --diff-filter=U -- 'packages/*/package.json' 'packages/*/CHANGELOG.md'); do
ours_ver=$(git show :2:"$f" 2>/dev/null | grep -m1 '"version"' | sed -E 's/.*"version"[^"]*"([^"]+)".*/\1/' || true)
theirs_ver=$(git show :3:"$f" 2>/dev/null | grep -m1 '"version"' | sed -E 's/.*"version"[^"]*"([^"]+)".*/\1/' || true)
if [ -n "$ours_ver" ] && [ -n "$theirs_ver" ]; then
higher=$(printf "%s\n%s\n" "$ours_ver" "$theirs_ver" | sort -V | tail -1)
if [ "$higher" = "$ours_ver" ]; then
git checkout --ours "$f"
else
git checkout --theirs "$f"
fi
else
# No version field on one side (e.g. CHANGELOG.md): keep the
# forward branch (develop) for narrative continuity.
git checkout --ours "$f"
fi
git add "$f"
done
# The stable release deletes the changesets pre-mode bookkeeping
# on main: pre.json is removed once `changeset version` finalises
# the exit, and every applied .md is consumed. These surface as
Expand All @@ -161,4 +205,7 @@ jobs:
fi
git commit --no-edit
fi
git push origin develop
if ! git push origin develop; then
git pull --rebase --autostash origin develop
git push origin develop
fi
2 changes: 1 addition & 1 deletion docs/src/content/docs/en/guides/permission-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: The five permission modes — `default`, `acceptEdits`, `plan`, `by
You can switch permission mode from the widget's settings panel. When the
widget mounts without a stored preference, **the initial mode is
`acceptEdits`** (see `DEFAULT_SETTINGS.permissionMode` at
`packages/react/src/settings/types.ts:39`). `bypassPermissions` is exposed
`packages/widget-core/src/settings/types.ts:84`). `bypassPermissions` is exposed
only inside the settings panel — never from the chat composer — and should
be used sparingly.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/en/guides/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ In a production build, the agent-devtools code path **never enters the module gr

Even if Layer 1 is bypassed, the code refuses to run. **Fail-loud (throw) is the default** — silent no-op would hide a misdeployment.

- `mountAgentDevtools()` throws when `process.env.NODE_ENV === 'production'` (see `isProductionBuild` in [`packages/react/src/orchestrator/mount.ts:464`](https://github.com/Seungwoo321/agent-devtools/blob/main/packages/react/src/orchestrator/mount.ts)). The explicit override `{ force: true }` is the only escape hatch and is intended for justified operational debugging.
- `mountAgentDevtools()` throws when `process.env.NODE_ENV === 'production'` (see `isProductionBuild` in [`packages/widget-core/src/orchestrator/mount.ts:689`](https://github.com/Seungwoo321/agent-devtools/blob/main/packages/widget-core/src/orchestrator/mount.ts)). The explicit override `{ force: true }` is the only escape hatch and is intended for justified operational debugging.
- `startAgentDevtoolsServer` performs the same check — the server will never `listen` in production.
- `enabled: false` and similar runtime opt-out options are a **separate layer** from Layer 2. Opt-out is a dev-time off switch and does not substitute for the production block.

Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/en/guides/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ gate the import behind `import.meta.env.DEV`.`

- The first check in `mountAgentDevtools` throws on the spot when
`isProductionBuild()` returns true
(`packages/react/src/orchestrator/mount.ts:156-159`).
(`packages/widget-core/src/orchestrator/mount.ts:230-232`).
- The judgement is a `process.env.NODE_ENV === 'production'` comparison
(`mount.ts:464-471`). Vite replaces this token with a literal at build
(`mount.ts:689-696`). Vite replaces this token with a literal at build
time, so the standard dev/prod split works as intended.
- At the same time, the Vite plugin is declared with `apply: 'serve'` so
the plugin code itself is gated out of production builds as a first
Expand Down
Loading