mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-19 15:45:12 +00:00
feat(agent): expose profile streaming policy
This commit is contained in:
parent
8ca4b5abf6
commit
0e685aa7fa
8 changed files with 62 additions and 31 deletions
|
|
@ -24,6 +24,8 @@ export default tseslint.config(
|
|||
},
|
||||
rules: {
|
||||
'max-lines': ['error', 500],
|
||||
// Switch copy is nested under label > span > strong/small.
|
||||
'jsx-a11y/label-has-associated-control': ['error', { depth: 3 }],
|
||||
// A focusable separator is the keyboard-operated window-splitter pattern.
|
||||
'jsx-a11y/no-interactive-element-to-noninteractive-role': ['error', {
|
||||
button: ['separator'],
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@
|
|||
"agent_system.retryIntervalMs": "重试间隔(毫秒)",
|
||||
"agent_system.resizeTimelineHeight": "拖动以调节时间线高度",
|
||||
"agent_system.runPolicy": "运行策略",
|
||||
"agent_system.streaming": "流式传输",
|
||||
"agent_system.streamingHint": "实时传输生成中的工具参数;前台 write_file 内容会同步显示在聊天中。",
|
||||
"agent_system.runs": "运行记录",
|
||||
"agent_system.runHistory": "运行记录",
|
||||
"agent_system.runHistoryAllChats": "全部聊天",
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@
|
|||
"agent_system.retryIntervalMs": "重試間隔(毫秒)",
|
||||
"agent_system.resizeTimelineHeight": "拖曳以調整時間線高度",
|
||||
"agent_system.runPolicy": "執行策略",
|
||||
"agent_system.streaming": "串流傳輸",
|
||||
"agent_system.streamingHint": "即時傳輸生成中的工具參數;前台 write_file 內容會同步顯示在聊天中。",
|
||||
"agent_system.runs": "執行記錄",
|
||||
"agent_system.runHistory": "執行記錄",
|
||||
"agent_system.runHistoryAllChats": "全部聊天",
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export type AgentSystemDraftEditor = {
|
|||
setCanDelegate: (enabled: boolean) => void;
|
||||
setCanHandoff: (enabled: boolean) => void;
|
||||
setRunPresentation: (presentation: string) => void;
|
||||
setRunStream: (enabled: boolean) => void;
|
||||
setCallableAsSubAgent: (enabled: boolean) => void;
|
||||
setCallableAsHandoffTarget: (enabled: boolean) => void;
|
||||
setDelegationDescription: (value: string) => void;
|
||||
|
|
@ -213,6 +214,14 @@ export function createAgentSystemDraftEditor(context: AgentSystemDraftEditorCont
|
|||
applyRunPresentation(draft, presentation, context.presentationMemory, memoryKey(draft));
|
||||
});
|
||||
},
|
||||
setRunStream(enabled) {
|
||||
if (isBuiltin()) {
|
||||
return;
|
||||
}
|
||||
editDraft((draft) => {
|
||||
draft.run.stream = enabled;
|
||||
});
|
||||
},
|
||||
setCallableAsSubAgent(enabled) {
|
||||
if (isBuiltin()) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -24,27 +24,6 @@ export function ProfileMainDelegationSection({ snapshot, controller, tr }: Profi
|
|||
const { draft } = snapshot;
|
||||
const builtin = isBuiltinProfile(draft);
|
||||
const callableHandoffTarget = isCallableAsHandoffTarget(draft);
|
||||
// The label text stays in the same DOM position; hoisting it into a
|
||||
// variable keeps the rendered tree identical while letting the a11y
|
||||
// lint see the label copy (it only scans two levels deep).
|
||||
const delegateCopy = (
|
||||
<span>
|
||||
<strong>{tr('delegateToSubAgents')}</strong>
|
||||
<small>{tr('delegateToSubAgentsHint')}</small>
|
||||
</span>
|
||||
);
|
||||
const handoffCopy = (
|
||||
<span>
|
||||
<strong>{tr('allowAgentHandoff')}</strong>
|
||||
<small>{tr('allowAgentHandoffHint')}</small>
|
||||
</span>
|
||||
);
|
||||
const handoffTargetCopy = (
|
||||
<span>
|
||||
<strong>{tr('callableHandoffTargetToggle')}</strong>
|
||||
<small>{tr('callableHandoffTargetHint')}</small>
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
<div className="ttas-section" data-ttas-profile-section="main-delegation">
|
||||
<div className="ttas-section-title">
|
||||
|
|
@ -59,7 +38,10 @@ export function ProfileMainDelegationSection({ snapshot, controller, tr }: Profi
|
|||
disabled={builtin}
|
||||
onChange={(event) => controller.setCanDelegate(event.target.checked)}
|
||||
/>
|
||||
{delegateCopy}
|
||||
<span>
|
||||
<strong>{tr('delegateToSubAgents')}</strong>
|
||||
<small>{tr('delegateToSubAgentsHint')}</small>
|
||||
</span>
|
||||
</label>
|
||||
{draft.delegation.canDelegate && (
|
||||
<div className="ttas-form-grid ttas-delegation-controls">
|
||||
|
|
@ -94,7 +76,10 @@ export function ProfileMainDelegationSection({ snapshot, controller, tr }: Profi
|
|||
disabled={builtin}
|
||||
onChange={(event) => controller.setCanHandoff(event.target.checked)}
|
||||
/>
|
||||
{handoffCopy}
|
||||
<span>
|
||||
<strong>{tr('allowAgentHandoff')}</strong>
|
||||
<small>{tr('allowAgentHandoffHint')}</small>
|
||||
</span>
|
||||
</label>
|
||||
{draft.delegation.canHandoff && (
|
||||
<div className="ttas-form-grid ttas-delegation-controls">
|
||||
|
|
@ -118,7 +103,10 @@ export function ProfileMainDelegationSection({ snapshot, controller, tr }: Profi
|
|||
disabled={builtin}
|
||||
onChange={(event) => controller.setCallableAsHandoffTarget(event.target.checked)}
|
||||
/>
|
||||
{handoffTargetCopy}
|
||||
<span>
|
||||
<strong>{tr('callableHandoffTargetToggle')}</strong>
|
||||
<small>{tr('callableHandoffTargetHint')}</small>
|
||||
</span>
|
||||
</label>
|
||||
{callableHandoffTarget && (
|
||||
<div className="ttas-form-grid ttas-delegation-controls">
|
||||
|
|
@ -161,12 +149,6 @@ export function ProfileSubAgentAccessSection({ snapshot, controller, tr }: Profi
|
|||
const { draft } = snapshot;
|
||||
const builtin = isBuiltinProfile(draft);
|
||||
const callableSubAgent = isCallableAsSubAgent(draft);
|
||||
const subAgentCopy = (
|
||||
<span>
|
||||
<strong>{tr('callableSubAgentToggle')}</strong>
|
||||
<small>{tr('callableSubAgentHint')}</small>
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
<div className="ttas-section" data-ttas-profile-section="subagent-access">
|
||||
<div className="ttas-section-title">
|
||||
|
|
@ -181,7 +163,10 @@ export function ProfileSubAgentAccessSection({ snapshot, controller, tr }: Profi
|
|||
disabled={builtin}
|
||||
onChange={(event) => controller.setCallableAsSubAgent(event.target.checked)}
|
||||
/>
|
||||
{subAgentCopy}
|
||||
<span>
|
||||
<strong>{tr('callableSubAgentToggle')}</strong>
|
||||
<small>{tr('callableSubAgentHint')}</small>
|
||||
</span>
|
||||
</label>
|
||||
{callableSubAgent && (
|
||||
<div className="ttas-form-grid ttas-delegation-controls">
|
||||
|
|
@ -231,6 +216,18 @@ export function ProfileRunSection({ snapshot, controller, tr }: ProfileSectionPr
|
|||
<h4>{tr('runPolicy')}</h4>
|
||||
</div>
|
||||
<div className="ttas-form-grid">
|
||||
<label className="ttas-switch-row ttas-span-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={draft.run.stream}
|
||||
disabled={builtin}
|
||||
onChange={(event) => controller.setRunStream(event.target.checked)}
|
||||
/>
|
||||
<span>
|
||||
<strong>{tr('streaming')}</strong>
|
||||
<small>{tr('streamingHint')}</small>
|
||||
</span>
|
||||
</label>
|
||||
<label className="ttas-field">
|
||||
<span>{tr('presentation')}</span>
|
||||
<select
|
||||
|
|
|
|||
|
|
@ -216,6 +216,8 @@ export const DEFAULT_MESSAGES = {
|
|||
"retryIntervalMs": "Retry Interval Ms",
|
||||
"resizeTimelineHeight": "Drag to resize timeline height",
|
||||
"runPolicy": "Run Policy",
|
||||
"streaming": "Streaming",
|
||||
"streamingHint": "Stream generated tool arguments. Foreground write_file content appears in chat while it is generated.",
|
||||
"runs": "Runs",
|
||||
"runHistory": "Run History",
|
||||
"runHistoryAllChats": "All chats",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ test('profileForEdit migrates v2 native tool names to canonical ToolIds', () =>
|
|||
|
||||
test('profileForEdit keeps CSV drafts separate and normalizeProfileForSave restores lists', () => {
|
||||
const profile = defaultProfile('writer');
|
||||
profile.run.stream = true;
|
||||
profile.skills.visible = ['lore', 'tools'];
|
||||
profile.delegation.allowedCallers = ['main', 'reviewer'];
|
||||
|
||||
|
|
@ -36,12 +37,22 @@ test('profileForEdit keeps CSV drafts separate and normalizeProfileForSave resto
|
|||
draft.delegation.allowedCallersCsv = 'editor';
|
||||
|
||||
const saved = normalizeProfileForSave(draft);
|
||||
expect(saved.run.stream).toBe(true);
|
||||
expect(saved.skills.visible).toEqual(['research', 'tools']);
|
||||
expect(saved.delegation.allowedCallers).toEqual(['editor']);
|
||||
expect('visibleCsv' in saved.skills).toBe(false);
|
||||
expect('allowedCallersCsv' in saved.delegation).toBe(false);
|
||||
});
|
||||
|
||||
test('run streaming defaults missing fields and rejects non-booleans', () => {
|
||||
const profile = defaultProfile('streaming');
|
||||
Reflect.deleteProperty(profile.run, 'stream');
|
||||
expect(profileForEdit(profile).run.stream).toBe(false);
|
||||
|
||||
Reflect.set(profile.run, 'stream', 'true');
|
||||
expect(() => normalizeProfileForSave(profile)).toThrow(/run\.stream must be a boolean/);
|
||||
});
|
||||
|
||||
test('tool description overrides preserve user text and reject invalid values', () => {
|
||||
const profile = defaultProfile('descriptions');
|
||||
profile.tools.toolDescriptions = {
|
||||
|
|
|
|||
|
|
@ -207,11 +207,16 @@ function normalizeRunPolicy(value: unknown): AgentProfile['run'] {
|
|||
if (presentation !== 'foreground' && presentation !== 'background') {
|
||||
throw new Error(`run.presentation is unsupported: ${presentation}`);
|
||||
}
|
||||
const stream = policy.stream ?? false;
|
||||
if (typeof stream !== 'boolean') {
|
||||
throw new Error('run.stream must be a boolean');
|
||||
}
|
||||
const directRunnable = policy.directRunnable !== false;
|
||||
const modelRetry = isPlainObject(policy.modelRetry) ? policy.modelRetry : {};
|
||||
|
||||
return {
|
||||
presentation: directRunnable ? presentation : 'background',
|
||||
stream,
|
||||
directRunnable,
|
||||
modelRetry: {
|
||||
maxRetries: Number(modelRetry.maxRetries ?? 3),
|
||||
|
|
@ -329,6 +334,7 @@ export function defaultProfile(id: string = DEFAULT_PROFILE_ID): AgentProfile {
|
|||
},
|
||||
run: {
|
||||
presentation: 'foreground',
|
||||
stream: false,
|
||||
directRunnable: true,
|
||||
modelRetry: {
|
||||
maxRetries: 3,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue