TauriTavern/docs/API/Agent.md

35 KiB
Raw Blame History

window.__TAURITAVERN__.api.agent — Agent API

本文档是 Agent Host ABI 当前参考。它描述前端/扩展可见的稳定入口,不等同于 Rust 内部 service/repository。

状态:当前已实现 canonical model IR、provider native metadata 保真、provider_state continuation、上下文只读工具、Skill tools、workspace 读改工具循环、前端 dryRun adapter、Agent run history listing、Agent run retention facade / dry-run plan preview / manual apply prune / backend auto prune,以及 Agent Profile 独立 preset / 独立 model 的 Frontend PromptAssemblyBroker 链路。Agent System 扩展开关开启时,普通发送、/trigger、regenerate 与 overswipe 新候选生成会通过 Legacy Generate 兼容桥启动 Agent;普通切换已有 swipe 候选不启动 Agent。本文以当前已落地 Host ABI 为准,并在后续章节保留 readDiff/rollback/approval 等未来设计。

provider_state 是 Rust 后端内部 continuation contract,不是 Host ABI。前端/扩展不应读写 _tauritavern_provider_state;需要诊断时通过 run events、modelResponsePath 与 LLM API log 观察。 模型回合详情必须通过 readModelTurn() 读取后端投影 DTO;前端不解析 model-responses/ raw JSON。

1. 入口

await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);

const agent = window.__TAURITAVERN__.api.agent;

Agent API 必须挂在 window.__TAURITAVERN__.api.agent。不要新增散落全局。

2. 当前方法概览

type TauriTavernAgentApi = {
  startRunFromLegacyGenerate(input?: AgentStartRunFromLegacyGenerateInput): Promise<AgentRunHandle>;
  startRunWithPromptSnapshot(input: AgentStartRunWithPromptSnapshotInput): Promise<AgentRunHandle>;
  subscribe(runId: string, handler: (event: AgentRunEvent) => void, options?: AgentSubscribeOptions): TauriTavernHostUnsubscribe;
  cancel(runId: string): Promise<AgentRunHandle>;
  submitGuidance(input: AgentSubmitGuidanceInput): Promise<AgentSubmitGuidanceResult>;
  readEvents(input: AgentReadEventsInput): Promise<AgentReadEventsResult>;
  readWorkspaceFile(input: AgentReadWorkspaceFileInput): Promise<AgentWorkspaceFile>;
  readModelTurn(input: AgentReadModelTurnInput): Promise<AgentModelTurn>;
  pruneChatPersistentStates(input: AgentPruneChatPersistentStatesInput): Promise<AgentPruneChatPersistentStatesResult>;
  retention: {
    readSettings(): Promise<AgentRunRetentionSettings>;
    updateSettings(input: Partial<AgentRunRetentionSettings>): Promise<AgentRunRetentionSettings>;
    planPrune(input?: {
      retention?: AgentRunPruneRetention | AgentRunRetentionSettings;
      detailLimit?: number;
    }): Promise<AgentRunPrunePlan>;
    applyPrune(input?: {
      retention?: AgentRunPruneRetention | AgentRunRetentionSettings;
      detailLimit?: number;
    }): Promise<AgentRunPruneApplyResult>;
  };
  profiles: {
    list(): Promise<AgentListProfilesResult>;
    load(input: string | { profileId: string }): Promise<{ profile: AgentProfileDefinition | null }>;
    diagnose(input: string | { profileId: string }): Promise<AgentProfileHealth>;
    resolveSystemPrompt(input?: string | { profileId?: string | null }): Promise<{ agentSystemPrompt: string }>;
    repairFile(input: { profileId: string; action: 'delete' | 'normalizeIdentity' }): Promise<void>;
    retargetPresetRefs(input: {
      from: { apiId: string; name: string };
      to: { apiId: string; name: string };
    }): Promise<{ updated: number; profileIds: string[] }>;
    save(input: AgentProfileDefinition | { profile: AgentProfileDefinition }): Promise<void>;
    delete(input: string | { profileId: string }): Promise<void>;
  };
  promptAssembly: {
    prepare(input: AgentPromptAssemblyPrepareInput): Promise<AgentPromptAssemblyPrepareResult>;
    buildSnapshot(input: AgentPromptAssemblyBrokerRequest): Promise<AgentPromptAssemblyBuildResult>;
  };
  tools: {
    list(): Promise<{ tools: AgentToolSpec[] }>;
  };

  approveToolCall(): never;
  listRuns(input?: {
    chatRef?: AgentChatRef;
    stableChatId?: string;
    statuses?: AgentRunStatus[];
    before?: { createdAt: string; runId: string };
    limit?: number;
  }): Promise<{
    runs: AgentRunSummary[];
    nextCursor?: { createdAt: string; runId: string };
  }>;
  readDiff(): never;
  rollback(): never;
};

subscribe() 返回的 unsubscribe 必须幂等。

当前没有公共 startRun() alias。启动职责必须一眼可见:

  • startRunFromLegacyGenerate():从当前 Legacy Generate dryRun 兼容桥启动。
  • startRunWithPromptSnapshot():调用方已经持有 prompt snapshot 时启动。

approveToolCall()、readDiff()、rollback() 已预留名称,但当前实现会显式 throw,避免静默降级。

3. startRunFromLegacyGenerate

type AgentStartRunFromLegacyGenerateInput = {
  chatRef?: AgentChatRef;
  stableChatId?: string;
  generationType?: 'normal' | 'regenerate' | 'swipe' | 'continue' | 'quiet' | 'impersonate';
  generateOptions?: unknown;
  profileId?: string;
  generationIntent?: AgentGenerationIntent;
  presentation?: 'foreground' | 'background';
  options?: {
    presentation?: 'foreground' | 'background';
    stream?: false;
  };
};

startRunFromLegacyGenerate() 是当前前端兼容桥:它使用 Legacy Generate(..., dryRun = true) 捕获当前 SillyTavern prompt 输入语义与 FrozenRunInputSnapshot。若 Profile 使用独立 preset,则先通过 promptAssembly.prepare() / buildSnapshot() 复用真实 PromptManager 组装;否则物化当前 prompt snapshot,再调用 startRunWithPromptSnapshot()。

要求:

  • 只用于当前 active chat。
  • 当前只支持 main_api = openai 的 chat-completion 路径。
  • 必须禁用 Legacy ToolManager tools;Agent tools 只能由 Rust runtime 注册。
  • worldInfoActivation 必须来自本次 dryRun 的最终 WORLDINFO_SCAN_DONE,不能读取全局 last activation 当作 run 真相。
  • stream 必须为 false 或省略;presentation 可显式覆盖 profile 默认前台/后台语义。
  • dryRun 没有产出 messages、已有 tool turns、已有 external tools 都必须 reject,不回退 Legacy Generate。
  • 入口路由到 Agent 后,profile、provider、group chat、context policy 或 Host API 错误必须 reject 并显式呈现;不得静默降级为 Legacy Generate。/trigger 作为生成入口遵守同一规则。

注意:Generate(..., dryRun = true) 不返回 payload。它只 emit GENERATE_AFTER_DATA,然后 resolve undefined。调用方不应写 const payload = await Generate(..., true);捕获逻辑由 startRunFromLegacyGenerate() 内部 adapter 负责。

4. startRunWithPromptSnapshot

type AgentStartRunWithPromptSnapshotInput = {
  chatRef: AgentChatRef;
  stableChatId?: string;
  generationType?: 'normal' | 'regenerate' | 'swipe' | 'continue' | 'quiet' | 'impersonate';
  profileId?: string;
  promptSnapshot: AgentPromptSnapshot;
  frozenRunInputSnapshot?: unknown;
  generationIntent?: AgentGenerationIntent;
  workspaceMode?: 'new-run' | 'resume-run';
  resumeRunId?: string;
  presentation?: 'foreground' | 'background';
  options?: {
    presentation?: 'foreground' | 'background';
    stream?: boolean;
  };
};

type AgentPromptSnapshot = {
  contextPolicy: unknown;
  chatCompletionPayload: unknown;
  worldInfoActivation?: {
    timestampMs: number;
    trigger: string;
    entries: Array<{
      world: string;
      uid: string | number;
      displayName: string;
      constant: boolean;
      content: string;
      position?: 'before' | 'after' | 'an_top' | 'an_bottom' | 'depth' | 'em_top' | 'em_bottom' | 'outlet';
    }>;
  };
};

type AgentRunHandle = {
  runId: string;
  status: AgentRunStatus;
  workspaceId: string;
  stableChatId: string;
  generationType: string;
};

type AgentRunSummary = {
  runId: string;
  workspaceId: string;
  stableChatId: string;
  chatRef: AgentChatRef;
  generationType: string;
  profileId?: string;
  skillScopeRefs?: {
    preset?: { apiId: string; name: string };
    characterId?: string;
  };
  persistBaseStateId?: string;
  inputMessageCount?: number;
  presentation: 'foreground' | 'background';
  status: AgentRunStatus;
  createdAt: string;
  updatedAt: string;
  commitCount: number;
  committedMessage?: {
    commitId: string;
    messageId: string;
    messageIndex?: number;
    committedAt: string;
  };
  terminalAt?: string;
};

AgentRunSummary.committedMessage.messageIndex 是 host commit 当时的零基消息索引快照,由 chat_commit_completed.messageId 派生;它不承诺当前聊天仍在该位置。旧 run 没有可解析 messageId 时该字段为空,前端不应显示楼层定位。summary projection 是可从 journal 重建的缓存,只在已经写入 terminal event 的终态 run 上复用/落盘。

身份语义:

  • stableChatId 是聊天的长期稳定身份。
  • workspaceId 必须由 kind + stableChatId 派生,不得由可变的 chatRef 文件名直接决定。
  • runId 是一次 Agent 执行身份,每次 normal/regenerate/swipe/continue 都必须生成新的 runId。
  • generationType 是启动时已校验的 SillyTavern generation intent;前端 retry 等宿主行为必须使用它或 generation_intent_recorded 事件,不得从 DOM 状态猜测。
  • 同一稳定聊天的多次 run 应共享同一个 chat workspace,但各自拥有独立 run workspace。

Public Host ABI 可以允许调用方省略 stableChatId,但 api.agent.startRunWithPromptSnapshot() 必须在调用 Rust command 前通过 api.chat.open(chatRef).stableId() 解析并校验。Rust command 不应自行读取 SillyTavern metadata。

当前要求提供 promptSnapshot.contextPolicy 与 promptSnapshot.chatCompletionPayload。promptSnapshot.worldInfoActivation 是可选字段,由 worldinfo_read_activated 读取;frozenRunInputSnapshot 用于审计与后续独立 prompt assembly 复用。长期目标是 generationIntent + ContextFrame,但当前 Rust runtime 不会只凭 generationIntent 组装上下文。

要求:

  • stableChatId 进入 backend DTO 前必须非空;无法解析时 fail-fast。
  • promptSnapshot.chatCompletionPayload 必须包含 chat-completion payload object。
  • 如果调用方希望 worldinfo_read_activated 返回非错误结果,必须在 prompt snapshot 中提供本次 run 的 worldInfoActivation。
  • 当前拒绝已有 tools、tool_choice、role: "tool" 或已有 tool_calls 的外部 tool turns。
  • 当前拒绝 stream: true。
  • workspaceMode / resumeRunId 当前只是后续字段,不应作为当前行为依赖。
  • 参数无效必须 reject,不静默回退 Legacy Generate。

5. subscribe

type AgentSubscribeOptions = {
  afterSeq?: number;
  limit?: number;
  intervalMs?: number;
  onError?: (error: unknown) => void;
};

语义:

  • 当前 subscribe() 是前端 polling wrapper,底层调用 readEvents()。
  • 默认从 afterSeq = 0 开始读取;调用方可以传入 afterSeq。
  • 返回 unsubscribe 函数,必须幂等。
  • 底层 polling 细节和 Rust command 名不是 Public Contract。

6. cancel

await agent.cancel(runId);

语义:

  • 写 run_cancel_requested。
  • 尽力取消当前模型请求或工具调用。
  • Cancel 不是 failure。
  • Cancel 后不能自动 commit。
  • 返回最新 AgentRunHandle。

6.1 submitGuidance

await agent.submitGuidance({
  runId,
  text: "Prefer the quieter ending and keep the character's agency clear.",
  clientGuidanceId: "optional-client-correlation-id",
});
type AgentSubmitGuidanceInput = {
  runId: string;
  text: string;
  clientGuidanceId?: string;
};

type AgentSubmitGuidanceResult = {
  runId: string;
  guidanceId: string;
  clientGuidanceId?: string;
  status: 'queued';
  preview: string;
  chars: number;
  words: number;
  pendingCount: number;
};

语义:

  • submitGuidance() 是 active AgentRun 的 run-scoped 输入通道,不是普通聊天发送,不写入 SillyTavern chat history,也不会启动新的 Generate。
  • Runtime 会先写 user_guidance_submitted,再将 guidance 放入当前 active run mailbox;下一次 root / handoff 前台 invocation 创建模型请求前,pending guidance 会合并为一条 canonical role=user message,并写 user_guidance_applied。
  • 已经发出的 provider request 不会被热修改。若当前模型调用正在进行,guidance 只影响后续模型请求边界。
  • cancel()、run finish、run failure / partial success 会关闭 mailbox;尚未应用的 guidance 写 user_guidance_discarded。
  • 空文本、过长文本、非 active run、finishing / cancelling / terminal run、mailbox 已满都会 fail-fast,不做静默丢弃。

7. approveToolCall

当前未实现审批流程;approveToolCall() 会显式 throw。

type AgentApproveToolCallInput = {
  runId: string;
  callId: string;
  approved: boolean;
  reason?: string;
};

语义:

  • 审批结果写 journal。
  • 拒绝工具不等同 run failure;具体后续由 plan/profile policy 决定。

8. readEvents

type AgentReadEventsInput = {
  runId: string;
  afterSeq?: number;
  beforeSeq?: number;
  limit?: number;
  invocationId?: string;
  includeTimelineProjection?: boolean;
};

type AgentReadEventsResult = {
  events: AgentRunEvent[];
  timelineProjection?: AgentRunTimelineProjection;
};

type AgentRunTimelineProjection = {
  foregroundInvocationIds: string[];
  invocations: AgentRunTimelineInvocation[];
  delegationEdges: AgentRunTimelineDelegationEdge[];
};

type AgentRunTimelineInvocation = {
  invocationId: string;
  parentInvocationId?: string;
  profileId: string;
  kind: 'root' | 'subagent' | 'handoff';
  status: 'created' | 'running' | 'completed' | 'failed' | 'cancelled' | 'transferred';
  exitPolicy: 'run_finish_allowed' | 'task_return_required';
  createdAt: string;
  updatedAt: string;
};

type AgentRunTimelineDelegationEdge = {
  taskId: string;
  sourceInvocationId: string;
  targetInvocationId: string;
  targetProfileId: string;
  workspaceKey: string;
  continuation: 'return_to_parent' | 'transfer_control';
  status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
  resultRef?: string;
  error?: string;
  createdAt: string;
  updatedAt: string;
};

要求:

  • limit 必须有上限。
  • 移动端 UI 不应一次读取完整巨大 journal;推荐先用 beforeSeq 读取最新页,再在用户向上回看时继续用 beforeSeq 补拉更早事件,同时用 afterSeq 追新。
  • invocationId 可选。传入时,后端先按 invocation 归属过滤事件流,再应用 afterSeq / beforeSeq / limit;因此分页窗口表达的是该 invocation 自己的历史页,不是全局页的二次筛选。该能力用于 SubAgent / handoff 局部 timeline,避免移动端为查看一个子 Agent 搬运完整 run journal。
  • 新事件使用 canonical event scope:payload.eventScope.invocationId 表示主归属 invocation,payload.eventScope.relatedInvocationIds 表示相关 invocation。readEvents({ invocationId }) 会返回主归属或相关列表命中的事件;旧 journal 没有 canonical scope 时,后端仅为兼容读取历史字段。
  • timelineProjection 仅在 includeTimelineProjection = true 时返回。它是面向 Timeline UI 的轻量结构投影,不是 journal event;它来自 run 的 invocation/task repository,用于在分页事件缺少 SubAgent task 或 handoff 起点时仍能识别 run 内 Agent graph。普通 polling / subscribe 不应请求该投影。
  • timelineProjection 不受 invocationId 过滤影响;调用方可以同时读取局部事件页和全局 Agent graph。
  • 当前暂不返回 hasMoreBefore/hasMoreAfter。

9. readWorkspaceFile

type AgentReadWorkspaceFileInput = {
  runId: string;
  path: string;
  checkpointId?: string;
};

type AgentWorkspaceFile = {
  path: string;
  text: string;
  chars: number;
  words: number;
  sha256: string;
};

路径必须是 workspace relative path。非法路径直接 reject。 当前 Host ABI 只读当前 run workspace 的 UTF-8 文本文件,不支持 checkpointId 参数。模型侧读取应使用 workspace_read_file 工具,前端/扩展侧读取应使用本方法。

10. readModelTurn

type AgentReadModelTurnInput = {
  runId: string;
  invocationId?: string;
  round: number;
  maxChars?: number;
};

type AgentModelTurn = {
  runId: string;
  round: number;
  modelResponsePath: string;
  provider: {
    source?: string;
    format?: string;
    model?: string;
    responseId?: string;
    usage?: unknown;
  };
  assistant: {
    text: string;
    totalChars: number;
    totalWords: number;
    truncated: boolean;
  };
  narration?: {
    source: 'assistantText';
    text: string;
    totalChars: number;
    totalWords: number;
    truncated: boolean;
  } | null;
  reasoning: Array<{
    source: string;
    text: string;
    totalChars: number;
    totalWords: number;
    truncated: boolean;
  }>;
  toolCalls: Array<{
    callId: string;
    name: string;
    modelName?: string;
  }>;
};

narration 是带工具调用的模型回合中可展示给用户的 assistant visible text 投影,用于表达模型在工具调用前后的简短叙述、意图或转场。它不是 runtime status,不从 reasoning / thinking / thought 提取,也不解析 assistant text 内部的 JSON 字段。无工具调用或空文本时为 null 或缺省。

assistant.text、narration.text 与 reasoning[].text 会按 maxChars 截断;totalChars / totalWords 始终表示截断前完整文本的字词统计。

round 必须大于 0。maxChars 省略时由后端使用默认上限;传入时必须大于 0。 invocationId 省略时读取 root invocation;读取 SubAgent / handoff invocation 的模型回合时必须传入对应 invocation id。

该方法返回面向 UI 的白名单投影:assistant 输出、narration、可见/摘要化 reasoning、工具调用摘要与 provider 摘要。它不会暴露完整 raw response、provider-private native continuation、签名或 encrypted reasoning。需要完整诊断时仍使用 run workspace 中的 modelResponsePath 与 LLM API log。

11. pruneChatPersistentStates

type AgentPruneChatPersistentStatesInput = {
  chatRef?: AgentChatRef;
  stableChatId?: string;
  candidateStateIds: string[];
};

type AgentPruneChatPersistentStatesResult = {
  workspaceId: string;
  removedStateIds: string[];
};

pruneChatPersistentStates() 是消息/Swipe 删除后的 Host cleanup 入口,不是全量 GC。调用方必须显式传入从被删除消息或被删除 swipe metadata 中收集到的 candidateStateIds;缺失、非数组或空字符串 state id 必须 reject。

后端会重新读取当前完整 chat payload,收集仍被当前聊天消息或 swipe 引用的 retained state ids,只删除 candidateStateIds - retainedStateIds。未被本次删除动作明确列为 candidate 的孤儿 state 必须保留,避免第三方总结、隐藏楼层、windowed save 或 metadata 损坏把整个 persistent-states 目录误清空。

当前只支持 character chat;group chat persistent state prune 会 fail-fast。删除整个 chat / group chat 时,生命周期服务仍删除对应的完整 Agent chat workspace,这不是本方法的职责。

12. retention

type AgentRunPruneRetention = {
  keepRecentTerminalRuns: number;
  keepFullRecentRuns: number;
};

type AgentRunRetentionSettings = AgentRunPruneRetention & {
  autoPruneEnabled: boolean;
};

type AgentRunPruneCandidate = {
  runId: string;
  workspaceId: string;
  stableChatId: string;
  chatRef: AgentChatRef;
  status: AgentRunStatus;
  createdAt: string;
  updatedAt: string;
  action: 'slim_heavy_artifacts' | 'delete_run';
  reason: 'outside_full_retention_window' | 'outside_history_retention_window';
  fileCount: number;
  byteCount: number;
};

type AgentRunPruneBlockedRun = AgentRunPruneCandidate & {
  blockReason: 'active_run' | 'missing_terminal_event' | 'invalid_journal' | 'invalid_storage';
  message?: string;
};

type AgentRunPruneFailedRun = AgentRunPruneCandidate & {
  message: string;
};

type AgentRunPrunePlan = {
  retention: AgentRunPruneRetention;
  detailLimit: number;
  terminalRunCount: number;
  nonTerminalRunCount: number;
  blockedRunCount: number;
  fullRetainedRunCount: number;
  coreRetainedRunCount: number;
  slimCandidateCount: number;
  deleteCandidateCount: number;
  totalSlimFileCount: number;
  totalSlimByteCount: number;
  totalDeleteFileCount: number;
  totalDeleteByteCount: number;
  totalCandidateFileCount: number;
  totalCandidateByteCount: number;
  candidates: AgentRunPruneCandidate[];
  blockedRuns: AgentRunPruneBlockedRun[];
  candidateDetailsTruncated: boolean;
  blockedDetailsTruncated: boolean;
};

type AgentRunPruneApplyResult = {
  retention: AgentRunPruneRetention;
  detailLimit: number;
  slimmedRunCount: number;
  deletedRunCount: number;
  failedRunCount: number;
  removedFileCount: number;
  removedByteCount: number;
  failedDetailsTruncated: boolean;
  failedRuns: AgentRunPruneFailedRun[];
  afterPlan: AgentRunPrunePlan;
};

retention.readSettings() 读取 tauritavern-settings.agent.retention 并以 Host ABI 的 camelCase 形态返回。autoPruneEnabled 默认 false,表示 Rust 后端在 TauriTavern 进程运行期间按当前保留策略进行周期性 Agent run 清理。retention.updateSettings() 只保存策略并唤醒后端调度器重读配置,不同步执行清理;两个数量必须是 0..10000 的整数,且 keepFullRecentRuns <= keepRecentTerminalRuns。

retention.planPrune() 是前端访问 plan_agent_run_prune(dto) 的 facade。它只返回 dry-run plan,可使用当前设置,也可传入一次性 retention override;override 中的 autoPruneEnabled 不参与规划,真正的候选只由两个保留数量决定;detailLimit 只限制返回明细,不影响 totals。retention.applyPrune() 访问 apply_agent_run_prune(dto),后端会用同一 retention 重新规划全量候选再执行,不信任前端预览列表;同一服务实例内 apply 会串行执行,避免并发清理同一批 run 造成假失败。单个 run 执行失败会进入 failedRuns 并继续处理后续 candidate,结构性规划错误仍 fail-fast。结果包含 caller detailLimit 下的 afterPlan,供 UI 展示清理后的事实状态。

自动清理由 Rust AgentRunRetentionAutomationService 拥有生命周期,不依赖 Agent System 面板打开,也不使用前端 setInterval。当前策略是启动/设置变更后延迟一次执行,之后固定周期维护;它复用 apply_agent_run_prune 的后端规划与执行路径,不定义第二套删除规则。

13. profiles / promptAssembly / tools

profiles.* 是当前 Agent Profile 管理入口。profiles.list() 的 summary 包含 directRunnable,供前端区分可直接启动的 root-run Profile 与只能作为 SubAgent / handoff target 的 Profile。列表扫描会返回可加载 Profile,同时把单个本地 Profile JSON 文件的内容损坏放入 issues,避免一个坏文件阻塞整个面板;invalidJson 建议用户确认后删除,invalidFileIdentity 可通过 profiles.repairFile({ profileId, action: "normalizeIdentity" }) 尝试规范化文件 header / identity 键(schemaVersion、kind、id),其它 Profile 内容保持原样。若修复后整份 JSON 仍不能按 Agent Profile 契约读取,则拒绝写回并报告错误。invalidProfile 表示主体结构损坏,需要手动修复,不会自动替换为默认 Profile。目录读取失败、非法文件名等仓储契约错误仍然 fail-fast。Profile JSON 中的 preset.mode = "ref" 与 model.mode = "connectionRef" 会影响 prompt assembly 和最终模型连接;model.mode = "requiresConfiguration" 表示 Profile 需要本机重新选择模型,可保存但不可运行;run.directRunnable = false 表示该 Profile 不能直接启动,只能通过已实现的非直接入口运行(当前为 return-mode SubAgent)。前端“可作为子 Agent”会写入该非直接运行语义。保存时无效 schema 必须 fail-fast。

profiles.retargetPresetRefs() 是管理态引用迁移 API,用于 preset rename 生命周期。它只更新 preset.mode = "ref" 且精确匹配 from 的 Profile;to preset 必须已经存在,且不能跨 apiId retarget。from 可以已经 dangling。该 API 不会让运行态静默降级;运行和 prompt assembly 仍按 Profile 契约 fail-fast。该操作逐个 Profile 写回,失败后可用同一请求重试;preset rename 流程必须在依赖迁移完成后再删除旧 preset。

profiles.diagnose() 是管理态健康检查 API。它面向“Profile 可加载但外部资源引用不可用”的情况,返回结构化 diagnostics,而不是让面板从异常字符串推断。该 API 不替代运行态 resolver:promptAssembly.prepare()、root run 与 SubAgent 仍按严格 Profile / preset / model contract fail-fast,且不会静默回退当前 UI preset/model。当前第一期覆盖 preset ref 缺失或不支持、model.requiresConfiguration、LLM Connection 缺失或无效;腐坏 JSON、文件 identity 错误等 storage health 仍由 profiles.list().issues 表达。

type AgentProfileSummary = {
  id: string;
  displayName: string;
  description?: string;
  directRunnable: boolean;
};

type AgentProfileStorageIssue = {
  profileId: string;
  fileName: string;
  kind: 'invalidJson' | 'invalidFileIdentity' | 'invalidProfile';
  recommendedAction?: 'delete' | 'normalizeIdentity';
  message: string;
};

type AgentListProfilesResult = {
  profiles: AgentProfileSummary[];
  issues: AgentProfileStorageIssue[];
};

type AgentProfileHealth = {
  profileId: string;
  previewAvailable: boolean;
  promptAssemblyAvailable: boolean;
  directRunAvailable: boolean;
  subAgentAvailable: boolean;
  diagnostics: AgentProfileDiagnostic[];
};

type AgentProfileDiagnostic = {
  code: string;
  severity: 'error';
  path: string;
  message: string;
  resource?: {
    kind: 'preset' | 'llmConnection' | 'model';
    apiId?: string;
    name?: string;
    id?: string;
    modelId?: string;
  };
  blocks?: Array<'preview' | 'promptAssembly' | 'directRun' | 'subAgent'>;
  repairActions?: Array<
    'selectPreset'
    | 'selectModel'
    | 'setModelRequiresConfiguration'
    | 'openJsonEditor'
  >;
};

promptAssembly.prepare() 调用 Rust prepare_agent_prompt_assembly,返回 currentPromptSnapshot 或 frontendPromptAssembly。promptAssembly.buildSnapshot() 是前端 broker:它只能使用 frozenRunInputSnapshot 内的 promptInputs、worldInfoActivation、macroContext,并调用真实 SillyTavern PromptManager 组装 promptSnapshot.chatCompletionPayload。该 API 是 Agent orchestration 内部边界,不是第三方扩展任意改写 prompt 的入口。

tools.list

type AgentToolSpec = {
  name: string;
  modelName: string;
  title: string;
  description: string;
  inputSchema: unknown;
  outputSchema?: unknown;
  annotations?: unknown;
  source: string;
};

tools.list() 返回当前后端 Agent Tool Registry 的 canonical specs。Profile 面板可以用它编辑 tools.toolDescriptions,但不得把返回值当作可修改的 registry。

14. readDiff

当前未实现 diff;readDiff() 会显式 throw。

type AgentReadDiffInput = {
  runId: string;
  fromCheckpointId?: string;
  toCheckpointId?: string;
  paths?: string[];
};

type AgentDiff = {
  runId: string;
  fromCheckpointId?: string;
  toCheckpointId?: string;
  files: Array<{
    path: string;
    status: 'added' | 'modified' | 'deleted' | 'unchanged';
    unifiedDiff?: string;
  }>;
};

第一期可以只支持文本 artifact 的 diff。

15. rollback

当前未实现 rollback;rollback() 会显式 throw。

type AgentRollbackInput = {
  runId: string;
  checkpointId: string;
  scope?: 'workspace' | 'committed-message';
};

语义:

  • workspace:只恢复 run workspace,不修改 chat。
  • committed-message:重组 artifact 并修改已提交聊天消息,必须走 chat 保存契约。

16. commit

type AgentCommitInput = {
  runId: string;
  messageId?: string | number;
};

type AgentCommitResult = {
  runId: string;
  status: AgentRunStatus;
};

Chat commit 不是公开 Host API 方法,而是 Agent tool 与 host bridge 的内部握手:

  • 模型调用 workspace.commit,无参数时默认 replace output/main.md。
  • Rust runtime 读取 workspace 文件、校验 required message body、创建 checkpoint,并写 chat_commit_requested event。
  • 前端 host bridge 校验当前 active chat 与 run 的 chatRef/stableChatId 一致。
  • bridge 通过上游 saveReply() 写入聊天,再调用 resolve_agent_chat_commit。
  • chat_commit_requested 不携带 persistStateId;该字段只能在 workspace.finish 成功提交 persistent state 后,由 persistent_state_metadata_update_requested / resolve_agent_persistent_state_metadata_update 写回同一条 chat message。
  • replace 后续使用 appendFinal 覆盖同一消息;append 后续使用 append 追加同一消息。
  • append 在本 run 尚无 commit 时不会报错,会创建本 run 的消息楼层。
  • 前台 run 在 workspace.finish 前必须至少成功 commit 一次;后台 run 可无 chat commit 完成。

17. Event Envelope

type AgentRunEvent = {
  seq: number;
  id: string;
  runId: string;
  timestamp: string;
  level: 'debug' | 'info' | 'warn' | 'error';
  type: AgentRunEventType;
  payload: unknown;
};

事件类型见 docs/Agent/RunEventJournal.md。

Agent event 不等同 SillyTavern eventSource 事件,不得伪装成 GENERATION_* 或 TOOL_CALLS_*。

18. Errors

错误建议结构:

type AgentApiError = {
  code: string;
  message: string;
  runId?: string;
  eventSeq?: number;
  retryable?: boolean;
  details?: unknown;
};

常见 code:

agent.invalid_intent
agent.invalid_profile
agent.policy_violation
agent.not_found
workspace.path_denied
workspace.required_artifact_missing
model.request_failed
tool.policy_denied
commit.cursor_integrity
commit.save_failed

19. Rust Command

start_agent_run(dto)
list_agent_tool_specs()
cancel_agent_run(dto)
list_agent_runs(dto)
plan_agent_run_prune(dto)
apply_agent_run_prune(dto)
read_agent_run_events(dto)
read_agent_workspace_file(dto)
resolve_agent_chat_commit(dto)

plan_agent_run_prune(dto) 是后端 dry-run command:它按 tauritavern-settings.agent.retention 或调用方传入的一次性 retention override 计算 slim_heavy_artifacts / delete_run 候选和 files/bytes,不执行删除。slim_heavy_artifacts 使用后端 Agent run storage class 统计,分类边界与 TT-Sync 的 Agent run dataset 词汇对齐,但不读取同步 profile 或 dataset selection。dto.detailLimit 控制返回的 candidate/blocked 明细数量,计数与 bytes totals 不受截断影响;blockedRuns 会显式报告 active run、缺失 terminal event、journal/storage 异常等不能安全清理的对象。Command 层必须是薄封装。Agent loop 不写在 command 内。

apply_agent_run_prune(dto) 使用同一 planner 的 execution 模式重新生成执行计划,并以完整候选集执行 slim_heavy_artifacts / delete_run;同一服务实例内 apply 串行化。slim_heavy_artifacts 仅删除 run workspace 内非核心 history 的 artifact,保留 run.json、events.jsonl、run index 与 run summary projection;delete_run 删除 run workspace、run index 与 run summary projection。稳定 persistent-states/ 不属于 run prune 范围。执行结果会返回删除文件/字节统计、失败 run 明细和执行后的 dry-run plan。

后续命令:

approve_agent_tool_call(dto)
read_agent_diff(dto)
rollback_agent_run(dto)

20. Compatibility

Agent Mode off:

  • Generate() 行为不变。
  • ToolManager 行为不变。
  • api.chat 行为不变。

Agent Mode on:

  • 短期可使用 dryRun 生成 prompt snapshot。
  • dryRun 不是纯函数,调用方必须理解它仍会触发上游事件。
  • dryRun 不返回 payload;Agent adapter 通过事件捕获 payload。
  • Agent tool loop 不递归 Generate()。

21. 当前工具与手动验证

当前开放十四个非 delegation 内建工具:

Canonical name Model-facing alias 说明
chat.search chat_search 搜索当前 run 绑定的聊天。只有 query 必填;可选 limit、role、start_message、end_message、scan_limit。返回 message index、snippet 与 ref。
chat.read_messages chat_read_messages 按 0-based message index 读取当前聊天消息;每项可选 start_char、max_chars 读取长消息片段。
worldinfo.read_activated worldinfo_read_activated 读取本次 run 的最终激活世界书条目;模型可读文本只包含条目名、世界书名、条目内容。
dice.roll dice_roll 为明确的随机、跑团或 roleplay 检定投骰;支持 d6、1d20、3d6+4 与纯数字。默认 Profile 不启用。
skill.list skill_list 列出当前 Profile 可见的已安装 Skill 索引摘要。
skill.search skill_search 搜索当前 Profile 可见的单个 Skill 内 UTF-8 文本文件,返回 snippet/ref。
skill.read skill_read 读取已安装 Skill 内的 UTF-8 文本文件或范围;默认 SKILL.md,支持 path、行范围、字符范围与 max_chars。
workspace.list_files workspace_list_files 列出模型可见 workspace 文件;path 省略、空字符串、.、./ 表示 workspace root
workspace.search_files workspace_search_files 搜索模型可见 workspace UTF-8 文本文件,返回 snippet/ref
workspace.read_file workspace_read_file 读取 UTF-8 文本文件并返回行号;支持行范围和字符范围;完整读取记录 read-state
workspace.write_file workspace_write_file 写 UTF-8 文本到 manifest 可写 roots;mode 默认为 replace,append 原样追加并在缺失时创建文件
workspace.apply_patch workspace_apply_patch 单文件 old_string / new_string 精确替换,要求已完整读取或由本 run 创建/修改
workspace.commit workspace_commit 提交可见 workspace 文件到当前聊天;无参数默认 replace output/main.md;append 首次创建、后续追加同一消息
workspace.finish workspace_finish 结束工具循环;前台 run 要求已有成功 commit,后台 run 可直接结束

当前不存在 MCP、shell 或 extension bridge 工具。

模型可修正的工具错误会作为 is_error = true tool result 回填下一轮。宿主级 IO、journal、checkpoint、序列化、取消和模型响应结构错误仍然让 run failed。

推荐最小启动:

await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);

const agent = window.__TAURITAVERN__.api.agent;

const run = await agent.startRunFromLegacyGenerate({
  generationType: 'normal',
  options: { stream: false, presentation: 'foreground' },
});

const stop = agent.subscribe(run.runId, event => console.log(event));

更完整的多轮工具循环 smoke 见 docs/CurrentState/AgentFramework.md。