Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ Write each change in both `### English` and `### 中文` under `## Unreleased`.
### English

- Accept `tool_reference` blocks inside Anthropic tool results and keep them as text instead of rejecting the whole request
- Align create-account and edit-account form fields, and let WorkBuddy accounts set their own daily check-in time
- Add a system-wide WorkBuddy check-in default that new accounts inherit

### 中文

- 接受 Anthropic 工具结果中的 `tool_reference` 块并保留为文本,不再整条请求报错
- 对齐创建和编辑账号表单,并允许 WorkBuddy 账号单独设置每日签到时间
- 系统设置增加 WorkBuddy 默认签到时间,新建账号会继承该时间

## 0.4.10 - 2026-09-12

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/api/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export type SystemUpdateInfo = {
export type SystemSettings = {
cross_provider_model_pool: boolean
routing_strategy: 'round-robin' | 'weighted-round-robin' | 'fill-first'
workbuddy_checkin_time: string
session_affinity?: {
ttl_seconds?: number
capacity?: number
Expand Down Expand Up @@ -94,7 +95,7 @@ export function fetchSystemSettings() {
return api<SystemSettings>('/api/system/settings')
}

export function updateSystemSettings(input: { cross_provider_model_pool?: boolean; routing_strategy?: SystemSettings['routing_strategy'] }) {
export function updateSystemSettings(input: { cross_provider_model_pool?: boolean; routing_strategy?: SystemSettings['routing_strategy']; workbuddy_checkin_time?: string }) {
return api<SystemSettings>('/api/system/settings', {
method: 'PATCH',
body: JSON.stringify(input),
Expand Down
135 changes: 74 additions & 61 deletions frontend/src/components/AddAccountModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { Button, Description, Input, Label, ListBox, Modal, Select, Skeleton, TextArea } from '@heroui/react'
import { Button, Description, Input, Label, ListBox, Modal, NumberField, Select, Skeleton, TextArea } from '@heroui/react'
import { ArrowSquareOut, CaretLeft, CaretRight, CheckCircle, FileCode, Key, ShieldCheck, X } from '@phosphor-icons/react'
import { BrandMark } from '@/components/BrandMark'
import { ProviderMark } from '@/components/ProviderMark'
Expand All @@ -17,6 +17,7 @@ import {
startDeviceLogin,
type ProviderDescriptor,
} from '@/api/overview'
import { fetchSystemSettings } from '@/api/system'

type Props = {
isOpen: boolean
Expand Down Expand Up @@ -127,10 +128,11 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
const [typesLoading, setTypesLoading] = useState(false)
const [advancedOpen, setAdvancedOpen] = useState(false)
const [name, setName] = useState('')
const [maxInFlight, setMaxInFlight] = useState('4')
const [priority, setPriority] = useState('50')
const [maxInFlight, setMaxInFlight] = useState(4)
const [priority, setPriority] = useState(50)
const [dropSystemPrompt, setDropSystemPrompt] = useState(true)
const [autoCheckin, setAutoCheckin] = useState(false)
const [defaultCheckinTime, setDefaultCheckinTime] = useState('09:00')
const [autoCheckinTime, setAutoCheckinTime] = useState('09:00')
const [pat, setPat] = useState('')
const [json, setJson] = useState('')
Expand Down Expand Up @@ -159,12 +161,18 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
setTypesLoading(true)
setProviderOptions([])
setAccountType('')
void loadProviderOptions()
.then((options) => {
void Promise.all([
loadProviderOptions(),
fetchSystemSettings().catch(() => null),
])
.then(([options, settings]) => {
if (cancelled) return
setProviderOptions(options)
setAccountType(options[0]?.id || '')
setTab('browser')
const fallback = settings?.workbuddy_checkin_time || '09:00'
setDefaultCheckinTime(fallback)
setAutoCheckinTime(fallback)
})
.finally(() => {
if (!cancelled) setTypesLoading(false)
Expand Down Expand Up @@ -192,15 +200,13 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
}, [showImportTab, showPatTab, tab])

function parsedMaxInFlight() {
const value = Number(maxInFlight)
if (!Number.isInteger(value) || value < 1 || value > 32) return 4
return value
if (!Number.isInteger(maxInFlight) || maxInFlight < 1 || maxInFlight > 32) return 4
return maxInFlight
}

function parsedPriority() {
const value = Number(priority)
if (!Number.isInteger(value) || value < 1 || value > 100) return 50
return value
if (!Number.isInteger(priority) || priority < 1 || priority > 100) return 50
return priority
}

function accountOptions() {
Expand All @@ -221,11 +227,11 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
setProviderOptions([])
setTypesLoading(false)
setName('')
setMaxInFlight('4')
setPriority('50')
setMaxInFlight(4)
setPriority(50)
setDropSystemPrompt(true)
setAutoCheckin(false)
setAutoCheckinTime('09:00')
setAutoCheckinTime(defaultCheckinTime)
setPat('')
setJson('')
setAdvancedOpen(false)
Expand Down Expand Up @@ -506,14 +512,16 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
</section>

<section className="mt-5 space-y-2.5">
<Input
className="h-12 text-base"
value={name}
onChange={(event) => setName(event.target.value)}
placeholder={t('wizardNamePh')}
aria-label={t('accountName')}
disabled={settingsLocked}
/>
<div className="space-y-1.5">
<Label className="text-sm font-medium text-muted">{t('accountName')}</Label>
<Input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder={t('wizardNamePh')}
aria-label={t('accountName')}
disabled={settingsLocked}
/>
</div>
<button
type="button"
onClick={() => setAdvancedOpen((open) => !open)}
Expand All @@ -524,40 +532,46 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
{t('wizardAdvanced')}
</button>
{advancedOpen ? (
<div className="space-y-3 rounded-lg border border-separator px-3.5 py-3">
<div className="grid gap-3 sm:grid-cols-2">
<label className="block space-y-1.5">
<span className="text-xs font-medium text-muted">{t('maxInflight')}</span>
<Input
type="number"
min={1}
max={32}
value={maxInFlight}
onChange={(event) => setMaxInFlight(event.target.value)}
aria-label={t('maxInflight')}
disabled={settingsLocked}
/>
<p className="text-[11px] leading-4 text-muted">{t('maxInflightHint')}</p>
</label>
<label className="block space-y-1.5">
<span className="text-xs font-medium text-muted">{t('priority')}</span>
<Input
type="number"
min={1}
max={100}
value={priority}
onChange={(event) => setPriority(event.target.value)}
aria-label={t('priority')}
disabled={settingsLocked}
/>
<p className="text-[11px] leading-4 text-muted">{t('priorityHint')}</p>
</label>
<div className="space-y-5 rounded-lg border border-separator px-3.5 py-3">
<div className="grid gap-5 sm:grid-cols-2">
<NumberField
value={maxInFlight}
onChange={(value) => setMaxInFlight(value ?? 4)}
minValue={1}
maxValue={32}
isDisabled={settingsLocked}
isRequired
>
<Label className="text-sm font-medium text-muted">{t('maxInflight')}</Label>
<NumberField.Group>
<NumberField.DecrementButton />
<NumberField.Input />
<NumberField.IncrementButton />
</NumberField.Group>
<Description className="text-xs leading-5 text-muted">{t('maxInflightHint')}</Description>
</NumberField>
<NumberField
value={priority}
onChange={(value) => setPriority(value ?? 50)}
minValue={1}
maxValue={100}
isDisabled={settingsLocked}
isRequired
>
<Label className="text-sm font-medium text-muted">{t('priority')}</Label>
<NumberField.Group>
<NumberField.DecrementButton />
<NumberField.Input />
<NumberField.IncrementButton />
</NumberField.Group>
<Description className="text-xs leading-5 text-muted">{t('priorityHint')}</Description>
</NumberField>
</div>
{showDropSystem ? (
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<div className="text-xs font-medium text-muted">{t('dropSystemPrompt')}</div>
<p className="mt-0.5 text-[11px] leading-4 text-muted">{t('dropSystemPromptCreateHint')}</p>
<div className="text-sm font-medium text-muted">{t('dropSystemPrompt')}</div>
<p className="mt-0.5 text-xs leading-5 text-muted">{t('dropSystemPromptCreateHint')}</p>
</div>
<CompactSwitch
isSelected={dropSystemPrompt}
Expand All @@ -568,11 +582,11 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
</div>
) : null}
{showAutoCheckin ? (
<div className="space-y-2.5">
<div className="space-y-5">
<div className="flex items-center justify-between gap-3">
<div className="min-w-0">
<div className="text-xs font-medium text-muted">{t('autoCheckin')}</div>
<p className="mt-0.5 text-[11px] leading-4 text-muted">{t('autoCheckinCreateHint')}</p>
<div className="text-sm font-medium text-muted">{t('autoCheckin')}</div>
<p className="mt-0.5 text-xs leading-5 text-muted">{t('autoCheckinCreateHint')}</p>
</div>
<CompactSwitch
isSelected={autoCheckin}
Expand All @@ -582,18 +596,17 @@ export function AddAccountModal({ isOpen, onClose, onAdded }: Props) {
/>
</div>
{autoCheckin ? (
<label className="block space-y-1.5 rounded-lg bg-surface-secondary/55 p-3">
<span className="text-xs font-medium text-muted">{t('autoCheckinTime')}</span>
<div className="space-y-1.5">
<Label className="text-sm font-medium text-muted">{t('autoCheckinTime')}</Label>
<Input
className="h-11 w-full text-base sm:max-w-48"
type="time"
value={autoCheckinTime}
onChange={(event) => setAutoCheckinTime(event.target.value || '09:00')}
onChange={(event) => setAutoCheckinTime(event.target.value || defaultCheckinTime)}
aria-label={t('autoCheckinTime')}
disabled={settingsLocked}
/>
<p className="text-[11px] leading-4 text-muted">{t('autoCheckinTimeHint')}</p>
</label>
<Description className="text-xs leading-5 text-muted">{t('autoCheckinTimeHint')}</Description>
</div>
) : null}
</div>
) : null}
Expand Down
24 changes: 22 additions & 2 deletions frontend/src/components/account/EditAccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ type Props = {
busy: boolean
t: Translate
onClose: () => void
onSave: (input: { name: string; max_inflight: number; priority: number }) => Promise<void>
onSave: (input: { name: string; max_inflight: number; priority: number; workbuddy_checkin_time?: string }) => Promise<void>
}

export function EditAccountModal({ account, busy, t, onClose, onSave }: Props) {
const [name, setName] = useState(account?.name || '')
const [maxInFlight, setMaxInFlight] = useState<number>(account?.max_inflight ?? 4)
const [priority, setPriority] = useState<number>(account?.priority ?? 50)
const [autoCheckinTime, setAutoCheckinTime] = useState(account?.workbuddy_checkin_time || '09:00')
const [error, setError] = useState('')
const title = t('editAccountTitle', { name: account?.name || account?.id || '' })
const provider = account ? accountProviderLabel(account.provider, account.region, t) : ''
const showCheckinTime = account?.provider === 'workbuddy'

async function submit(event?: { preventDefault(): void }) {
event?.preventDefault()
Expand All @@ -40,7 +42,12 @@ export function EditAccountModal({ account, busy, t, onClose, onSave }: Props) {
}
setError('')
try {
await onSave({ name: trimmed, max_inflight: maxInFlight, priority })
await onSave({
name: trimmed,
max_inflight: maxInFlight,
priority,
workbuddy_checkin_time: showCheckinTime ? (autoCheckinTime || '09:00') : undefined,
})
onClose()
} catch (err) {
setError(err instanceof Error ? err.message : String(err))
Expand Down Expand Up @@ -127,6 +134,19 @@ export function EditAccountModal({ account, busy, t, onClose, onSave }: Props) {
<Description className="text-xs leading-5 text-muted">{t('priorityHint')}</Description>
</NumberField>
</div>
{showCheckinTime ? (
<div className="space-y-1.5">
<Label className="text-sm font-medium text-muted">{t('autoCheckinTime')}</Label>
<Input
type="time"
value={autoCheckinTime}
onChange={(event) => setAutoCheckinTime(event.target.value || '09:00')}
aria-label={t('autoCheckinTime')}
disabled={busy}
/>
<Description className="text-xs leading-5 text-muted">{t('autoCheckinTimeHint')}</Description>
</div>
) : null}
</Form>
</Modal.Body>
<Modal.Footer className="justify-end gap-2 px-6 pb-6">
Expand Down
Loading
Loading