日期:2026-06-09
本文记录 codex/ts-refactor 分支当前可用的 TypeScript SDK 集成面。AgentHub 应把 @tokendance/code-sdk 当作唯一稳定入口,避免直接依赖 @tokendance/code-core 的 runtime 内部类。
SDK 暴露的稳定调用链:
TokenDanceCode -> Thread -> run() / runStreamed()AgentHub 可以负责:
- thread 生命周期和 UI 状态。
- 审批弹窗或策略服务。
- 事件持久化、前端广播、任务编排。
- 选择 provider、storage root 和工作目录。
TokenDanceCode 负责:
- session state。
- provider 调用。
- tool registry、parse、permission、execution。
- JSONL transcript。
- recent transcript resume。
import { TokenDanceCode } from "@tokendance/code-sdk";
const client = new TokenDanceCode({
storageRoot: "<agenthubProject>/.tokendance-code",
provider: { type: "mock" },
env: process.env,
approvalCallback(request) {
return request.tool.risk !== "dangerous";
},
eventSink(event) {
console.log(event.type);
}
});| Option | 用途 |
|---|---|
provider |
可传入已有 ModelProvider,或 { type: "mock" }、{ type: "openai-responses", model }、{ type: "openai-chat-completions", model }、{ type: "anthropic-messages", model }。 |
storageRoot |
transcript/session 写入根目录。未传时使用 thread 的 working directory。 |
env |
SDK 内部构造 provider 时读取 API key;用于 AgentHub 注入进程环境或受控配置。 |
approvalCallback |
当权限决策为 requires_approval 时调用。返回 true 允许,false 拒绝,也可返回完整 PermissionDecision。 |
eventSink |
每个 runtime event 写入 transcript 后同步推给 AgentHub。 |
contextBudget |
可选字符预算,作用于实际 run() provider context;per-call thread.context(..., { contextBudget }) 只影响 preview。 |
真实 provider 的 key 名:
- OpenAI Responses:
OPENAI_API_KEY - OpenAI Chat Completions / TokenDance Gateway:优先
TOKENDANCE_GATEWAY_API_KEY,缺省回退OPENAI_API_KEY - Anthropic-compatible Messages:
ANTHROPIC_API_KEY
不要把 API key 写入项目文档或 transcript 示例。
TokenDance Gateway 可作为 OpenAI-compatible provider 接入:
const client = new TokenDanceCode({
provider: {
type: "openai-chat-completions",
model: "deepseek-v4-pro",
baseUrl: "https://api.vectorcontrol.tech/v1"
},
env: {
TOKENDANCE_GATEWAY_API_KEY: process.env.TOKENDANCE_GATEWAY_API_KEY
}
});这里的 key 是 TokenDance API key,用于模型 API 调用。TokenDanceID/OIDC 登录应由 AgentHub Hub Server 或产品登录层交换为 Hub-local session;不要把 TokenDanceID access token 传给 Gateway 作为模型 API key。
CLI 侧可用 tokendance gateway init --model deepseek-v4-pro 写入全局 ~/.tokendance/.env preset。该命令只写 provider/model/base URL,不生成、不覆盖、不打印 TOKENDANCE_GATEWAY_API_KEY。
AgentHub 如果需要在启动检查、调试面板或集成日志里展示 TokenDanceCode 包信息,可以直接读取 SDK 导出的只读 manifest,而不是解析 workspace package.json:
import { TOKEN_DANCE_CODE_PACKAGE } from "@tokendance/code-sdk";
console.log(TOKEN_DANCE_CODE_PACKAGE.version);
console.log(TOKEN_DANCE_CODE_PACKAGE.packages.sdk.import);
console.log(TOKEN_DANCE_CODE_PACKAGE.packages.cli.bin);
console.log(TOKEN_DANCE_CODE_PACKAGE.agentHub.sdkContractVersion);
console.log(TOKEN_DANCE_CODE_PACKAGE.agentHub.agentStreamSchemaVersion);
console.log(TOKEN_DANCE_CODE_PACKAGE.agentHub.features);
console.log(TOKEN_DANCE_CODE_PACKAGE.verification.package);
console.log(TOKEN_DANCE_CODE_PACKAGE.verification.tarballSmoke);
console.log(TOKEN_DANCE_CODE_PACKAGE.verification.prerelease);当前 manifest 覆盖 core/sdk/cli 包名、SDK/Core import specifier、CLI bin 名、AgentHub SDK contract version、agent.stream schema version、SDK feature flags 和推荐验证命令:pnpm verify、pnpm pack:check、pnpm pack:smoke、pnpm release:next:check。它不包含本机路径、密钥或 workspace 私有路径,适合进入 AgentHub UI 或日志。AgentHub Hub/Edge 启动检查可以把 agentHub.sdkContractVersion === "agenthub-sdk.v1" 和 agentHub.agentStreamSchemaVersion === 2 当作当前稳定契约的快速断言。SDK 同时导出 AGENTHUB_FEATURE_FLAGS 和 supportsAgentHubFeature(feature),避免 AgentHub 复制 feature flag 字符串。agentHub.features 当前还显式标记 doctor-readiness、runner-bootstrap、agenthub-consumer-fixture、agenthub-package-feature-flags、agenthub-event-envelope-schema、agenthub-approval-bridge、agenthub-doctor-readiness、agenthub-contract-readiness 和 terminal-failure-result,用于区分是否能读取 manifest feature flags、agent.stream envelope schema、approval bridge request schema、doctor.agentHub 汇总、失败终态 result frame 和样例 runner/fixture 链路。
SDK 提供轻量 TokenDanceID OIDC helper,帮助 AgentHub Hub/Desktop/Web 或本地壳层启动 Authorization Code + PKCE S256 登录流。它只生成登录 URL、state、nonce、codeVerifier,提供 PKCE/state/callback 诊断,并校验 callback state;不交换 authorization code、不验证 ID token、不保存 access/refresh token。
AgentHub Hub Server 仍然负责 code exchange、JWKS/issuer/audience/expiration 验证、tokendance_sub 映射和 Hub-local session 签发。
import { createTokenDanceIdLoginRequest, diagnoseTokenDanceIdCallback, diagnoseTokenDanceIdLoginRequest, verifyTokenDanceIdCallback } from "@tokendance/code-sdk";
const login = createTokenDanceIdLoginRequest({
clientId: "agenthub-local",
redirectUri: "http://127.0.0.1:48731/callback",
extraParams: {
device_type: "desktop",
device_id: "00000000-0000-4000-8000-000000000001"
}
});
openSystemBrowser(login.authorizationUrl);
const loginDiagnostic = diagnoseTokenDanceIdLoginRequest(login);
console.log(loginDiagnostic.pkce.ok, loginDiagnostic.state.present);
const callbackDiagnostic = diagnoseTokenDanceIdCallback(callbackUrlFromLoopbackServer, login);
console.log(callbackDiagnostic.reason, callbackDiagnostic.callback.exchangeOwner);
const callback = verifyTokenDanceIdCallback(callbackUrlFromLoopbackServer, login);
await hub.exchangeTokenDanceIdCode({
code: callback.code,
codeVerifier: callback.codeVerifier,
redirectUri: callback.redirectUri
});diagnoseTokenDanceIdLoginRequest() 返回 pkce、state、callback 和 boundaries,用于 AgentHub 调试面板展示本轮 codeVerifier 长度、S256 challenge、state 是否存在,以及 ownership。diagnoseTokenDanceIdCallback() 返回 ready_for_hub_exchange、provider_error、missing_code、missing_state、state_mismatch 或 invalid_callback,供 loopback/backend callback 页面给出可读原因。诊断对象只描述本轮登录请求和 callback 形状;它不包含 TokenDanceID access/refresh token,也不会完成 code exchange。
默认 issuer 是 https://id.vectorcontrol.tech,默认 scope 是 openid profile email。Desktop/native 场景应使用 TokenDanceID 已登记的 loopback callback 策略;生产 Hub/Web 场景应使用 Hub-owned backend callback。TokenDanceID/OIDC 登录 token 只用于身份和 Hub session,不是 TokenDance Gateway 模型 API key。
CLI 使用同一 helper 暴露调试入口:
tokendance auth tokendanceid login-url `
--client-id agenthub-local `
--redirect-uri http://127.0.0.1:48731/callback `
--device-type desktop `
--device-id 00000000-0000-4000-8000-000000000001 `
--json--json 输出适合 AgentHub 调试面板或 Desktop shell 读取;除登录请求字段外还包含 diagnostics.pkce、diagnostics.state、diagnostics.callback 和 diagnostics.boundaries。其中 exchangeOwner、jwksOwner、sessionOwner 都指向 AgentHub Hub Server,且 exchangesAuthorizationCode、storesTokenDanceIdTokens、acceptsGatewayApiKey 均为 false。纯文本输出适合人工复制。CLI 不会打开浏览器、不写入本地 token 文件,也不会把 TokenDanceID 登录 token 当作 TokenDance Gateway 模型 API key。
const thread = client.startThread({
workingDirectory: "<agenthubProject>",
permissionMode: "default"
});
const result = await thread.run("summarize this repo");
console.log(result.threadId);
console.log(result.finalResponse);
console.log(thread.state.messages.length);run() 会缓冲完整事件并返回:
{
threadId: string;
finalResponse: string;
events: TDCodeEvent[];
}thread.state 返回当前 session 的只读快照副本,方便 AgentHub 做侧栏、调试面板或持久化索引。不要修改这个快照后再期待影响 SDK 内部状态;后续运行仍应通过 thread.run() 或 thread.runStreamed()。Runtime 会在每轮 provider 调用前构造 transient context,把 system prompt、AGENTS/CLAUDE/README、compact summary、memory 和 recent messages 送给模型;thread.state.messages 仍只保存真实会话消息,不包含 system context。
AgentHub 如果需要在调试面板或运行预览里展示下一轮模型上下文,可以用同源 context builder:
const preview = await thread.context("next prompt");
console.log(preview.includedFiles);
console.log(preview.metadata.includedRecentMessageCount);
console.log(preview.messages[0]?.content);
const shortPreview = await thread.context("next prompt", {
maxRecentMessages: 6,
contextBudget: {
instructions: 24000,
compact: 12000,
memory: 8000,
recentMessages: 12000
}
});thread.context() 只返回 transient preview,不会把本轮 user message 或 system context 写入 thread.state、session 文件或 transcript。maxRecentMessages 可限制 preview 中带入的历史消息数量,供 AgentHub resume 面板、运行前调试或低上下文预算场景使用;不传时保持默认最近 20 条消息。contextBudget 是可选字符预算,分别限制 instruction 文件、compact summary、memory 和 recent messages;它不会做向量检索,只做确定性截断和最近消息回退。preview.metadata 是只读派生信息,包含 workspaceRoot、maxRecentMessages、sessionMessageCount、includedRecentMessageCount、droppedRecentMessageCount、includedFiles、hasCompactSummary、memoryEntryCount、systemMessageCharacters、totalMessageCharacters 和可选 contextBudget,用于调试面板解释为什么某些上下文被带入、截断或省略。
const streamed = await thread.runStreamed("read README and propose next step");
for await (const event of streamed.events) {
if (event.type === "assistant.delta") {
process.stdout.write(event.text);
}
if (event.type === "tool.permission") {
console.log(event.decision.status, event.call.name);
}
}当前事件 union 由 @tokendance/code-sdk 重新导出:
import type { TDCodeEvent } from "@tokendance/code-sdk";AgentHub 前端建议以 event.type 做 discriminated union 分发,不要解析 provider 原始响应。
TokenDanceCode SDK 提供轻量 mapper,把 TDCodeEvent 投影为 AgentHub Edge adapter 已使用的 run.agent.* 事件名。SDK 不依赖 AgentHub 包;Hub/Edge 仍负责真正的 WebSocket、REST 或 event bus 投递。
import { createAgentHubEventSink } from "@tokendance/code-sdk";
const client = new TokenDanceCode({
eventSink: createAgentHubEventSink((event) => {
edgeEmitter.emit(event.eventType, {
sessionId: event.sessionId,
turnId: event.turnId,
...event.payload
});
})
});当前映射:
| TokenDanceCode event | AgentHub runtime event |
|---|---|
assistant.delta |
run.agent.text_delta |
assistant.completed |
run.agent.text_block |
tool.started |
run.agent.tool_call |
tool.permission + requires_approval |
run.agent.permission_requested |
tool.permission + allowed/denied |
run.agent.permission_decided |
tool.completed |
run.agent.tool_result |
turn.completed |
run.agent.result |
turn.failed |
run.agent.result (success=false) |
run.agent.result 是 AgentHub 的终态事件。成功 turn 的 payload 会带 success: true、summary 和可选 usage;provider/runtime 失败时 payload 会带 success: false、summary 和 error,runner 仍会把原始错误 rethrow 给调用方处理。
也可以直接使用纯函数:
import { toAgentHubRuntimeEvents } from "@tokendance/code-sdk";
const mapped = toAgentHubRuntimeEvents(tdEvent);如果 AgentHub 需要直接复用 Hub 文档中的 agent.stream payload 形态,可以用 createAgentHubAgentStreamSink():
import { createAgentHubAgentStreamSink } from "@tokendance/code-sdk";
const client = new TokenDanceCode({
eventSink: createAgentHubAgentStreamSink(
{
taskId: "task_01HX...",
edgeRunId: "edge_run_01HX...",
sessionId: "sess_01HX...",
agentInstanceId: "agent_01HX..."
},
async (payload) => {
await hubClient.postAgentStream(payload);
}
)
});输出 payload 字段对齐 AgentHub api/events.md:
{
schema_version: 2;
sdk_contract_version: "agenthub-sdk.v1";
source: "tokendance-code-sdk";
id: string;
task_id: string;
edge_run_id: string;
session_id: string;
agent_instance_id: string;
event_seq: number;
event_type: "run.agent.text_delta" | "...";
source_event_type: TDCodeEvent["type"];
payload: Record<string, unknown>;
created_at: string;
}schema_version、sdk_contract_version 和 source 是稳定 envelope 字段,方便 Hub/Edge 在启动检查、事件落库和前端调试中快速区分 SDK 契约版本。source_event_type 保留原始 TDCodeEvent.type,用于落库回放和调试时追踪 run.agent.* 事件来自哪一种 SDK runtime event。SDK 还导出 AGENTHUB_AGENT_STREAM_REQUIRED_FIELDS 和 AGENTHUB_AGENT_STREAM_EVENT_TYPES,供 Hub/Edge 启动检查或消费侧 fixture 做本地 schema 断言。event_seq 只对这个 sink 实例递增;如果 AgentHub 有自己的全局 event sequence 或 ID 生成器,可以通过 idFactory 和外层 emitter 继续覆盖。
简单场景可以直接传 approvalCallback:
const client = new TokenDanceCode({
approvalCallback(request) {
if (request.tool.name === "write_file" && request.session.cwd.includes("AgentHub")) {
return true;
}
return {
status: "denied",
reason: `AgentHub policy denied ${request.tool.name}`
};
}
});审批回调只处理 PermissionEngine 判定为 requires_approval 的工具。safe 模式直接 denied 的工具不会通过回调升级;工具执行层自己的硬拒绝规则也不会被回调绕过,例如 PowerShell 高风险命令分类。权限原因统一包含 mode=<mode> tool=<name> risk=<risk> action=<allowed|approval_required|denied> 前缀;每个 PermissionDecision 也带有可选 riskMetadata,记录 mode、tool、risk、action、并发策略和工具安全说明,便于 AgentHub UI、日志和 transcript 直接展示同一份可审计原因。
如果审批要通过 AgentHub UI、Hub API 或 agent.control permission.decide 异步返回,可以使用 createAgentHubApprovalBridge():
import { TokenDanceCode, createAgentHubApprovalBridge } from "@tokendance/code-sdk";
const approvalBridge = createAgentHubApprovalBridge({
timeoutMs: 30_000,
async onRequest(request) {
await hubClient.createApproval({
approvalId: request.requestId,
schemaVersion: request.schemaVersion,
sdkContractVersion: request.sdkContractVersion,
source: request.source,
decisionChannel: request.decisionChannel,
sessionId: request.sessionId,
turnId: request.turnId,
toolName: request.toolName,
toolRisk: request.toolRisk,
reason: request.reason,
input: request.input
});
}
});
const client = new TokenDanceCode({
approvalCallback: approvalBridge.approvalCallback
});
// Hub / Edge 收到人工决策后:
approvalBridge.decide("tool-call-id", "allow", "approved in AgentHub");
approvalBridge.decide("tool-call-id", "deny", "rejected in AgentHub");approvalCallback 会在工具执行前等待 decide();等待期间 pending() 可读取当前待审批请求快照。每个 request 都带 schemaVersion: 1、sdkContractVersion: "agenthub-sdk.v1"、source: "tokendance-code-sdk" 和 decisionChannel: "agenthub.approval.v1",AgentHub 可以把这些字段作为审批通道的 contract guard。onRequest() 和 pending() 返回的是 SDK 内部 pending 状态的隔离快照;Hub 侧修改 request 或 input 不会反写到 bridge。timeoutMs 可选,设置后超时请求会被清理并返回 denied。decide() 找不到对应请求时返回 false,便于 AgentHub 忽略重复或过期决策。同一个 tool call id 如果重复进入 bridge,首个请求继续使用原 id,后续请求会追加 #2、#3 等后缀作为独立 requestId,原始 call id 保留在 callId。如果 onRequest 发布到 Hub 失败,bridge 会清理 pending 项并返回 denied,避免 runtime 永久等待;如果 Hub 已经在同一次 onRequest 中完成 decide(),该已完成决策优先,后续发布错误不会覆盖人工决定。
被拒绝的工具结果会在 tool.completed 事件中携带 safetyEvidence:权限引擎拒绝使用 source: "permission_engine",PowerShell 硬拒绝使用 source: "powershell_classifier",并在 reason 中包含命中的阻断模式和命令片段证据。PowerShell hard deny 还会带 evidence: { rule, matched, commandPreview },这份结果会随 transcript 持久化,供 AgentHub 回放拒绝证据。
@tokendance/code-sdk 直接导出 AgentHub runner contract,AgentHub Hub/Edge 只需要依赖 public SDK 就能把 TokenDanceCode runtime、agent.stream emitter、远程审批、启动检查和 TokenDanceID 登录 facade 拼起来。packages/agenthub-example 仅保留为 private workspace 兼容 wrapper;它 re-export 同一套 SDK facade,不是新的稳定边界。正式集成应从 @tokendance/code-sdk 导入,并把 fixture 的数组收集器替换成自己的 Hub event bus、审批存储和 session 生命周期。
import { createAgentHubTokenDanceRunner } from "@tokendance/code-sdk";
const runner = createAgentHubTokenDanceRunner({
storageRoot: "<agenthubProject>/.tokendance-code",
defaultPermissionMode: "default",
contextMaxRecentMessages: 20,
streamIdFactory(eventSeq, event) {
return `agenthub_${event.eventType}_${eventSeq}`;
},
async emitAgentStream(payload) {
await hubClient.postAgentStream(payload);
},
async onApprovalRequest(request) {
await hubClient.createApproval(request);
}
});
const turn = await runner.run({
prompt: "summarize this repo",
workingDirectory: "<agenthubProject>",
permissionMode: "default",
taskId: "task_01HX...",
edgeRunId: "edge_run_01HX...",
sessionId: "sess_01HX...",
agentInstanceId: "agent_01HX..."
});
console.log(turn.finalResponse);
const preview = await runner.context({
prompt: "preview the next turn",
workingDirectory: "<agenthubProject>",
permissionMode: "default",
sessionId: "sess_01HX..."
});
console.log(preview.includedFiles);
const startup = await runner.bootstrap({
workingDirectory: "<agenthubProject>"
});
console.log(startup.packageInfo.packages.sdk.name);
console.log(startup.doctor.stateDir.writable);
console.log(startup.doctor.agentHub.ready);
console.log(startup.doctor.agentHub.warningChecks);
// Hub / Edge 收到人工决策后:
console.log(runner.pendingApprovals());
runner.decideApproval("tool-call-id", "allow", "approved in AgentHub");
const login = runner.createTokenDanceIdLogin({
clientId: "agenthub-local",
redirectUri: "http://127.0.0.1:48731/callback",
deviceType: "desktop",
deviceId: "00000000-0000-4000-8000-000000000001"
});
openSystemBrowser(login.authorizationUrl);
const callback = runner.verifyTokenDanceIdCallback(callbackUrlFromLoopbackServer, login);
await hub.exchangeTokenDanceIdCode({
code: callback.code,
codeVerifier: callback.codeVerifier,
redirectUri: callback.redirectUri
});需要写 AgentHub 集成测试或本地 demo 时,优先使用 SDK 里的 consumer fixture。它仍然只组合 SDK runner,不复制 runtime 内部逻辑,并把 SDK manifest、doctor startup check、Hub session id 的 resume-or-start、agent.stream event sink、远程 approval bridge 和 TokenDanceID login facade 串成同一个可复制入口:
import { createAgentHubTokenDanceConsumerFixture } from "@tokendance/code-sdk";
const fixture = createAgentHubTokenDanceConsumerFixture({
storageRoot: "<agenthubProject>/.tokendance-code",
defaultRun: {
workingDirectory: "<agenthubProject>",
taskId: "task_01HX...",
edgeRunId: "edge_run_01HX...",
sessionId: "sess_01HX...",
agentInstanceId: "agent_01HX..."
},
defaultLogin: {
clientId: "agenthub-local",
redirectUri: "http://127.0.0.1:48731/callback",
deviceType: "desktop"
},
async onAgentStream(payload) {
await hubClient.postAgentStream(payload);
},
async onApprovalRequest(request) {
await hubClient.createApproval(request);
}
});
const startup = await fixture.startup();
const login = fixture.login({
state: "state-from-agenthub-shell"
});
const callback = fixture.verifyLoginCallback(callbackUrlFromLoopbackServer, login);
const turnPromise = fixture.run({
prompt: "summarize this repo",
permissionMode: "default"
});
console.log(startup.packageInfo.agentHub.sdkContractVersion);
console.log(startup.doctor.startup.hub.ok);
console.log(login.authorizationUrl);
console.log(callback.code);
console.log(fixture.events());
console.log(fixture.approvals());
fixture.decideApproval("tool-call-id", "allow", "approved in AgentHub");
await turnPromise;SDK runner 每次 run() 都会创建一个新的 TokenDanceCode client,并用同一套 AgentHub stream envelope helper 把 runtime events 投递为递增 event_seq 的 agent.stream payload。传入的 AgentHub sessionId 会同时作为 TokenDanceCode thread id 使用;runner 会先按该 id resume(),没有现存 session 时才 startThread(),保证 Hub 事件、SDK TurnResult.threadId、provider 可见的消息历史和 transcript 目录使用同一个 session 标识。defaultPermissionMode 只作用于新建 thread;已存在 session 继续使用 session 内保存的权限模式。streamIdFactory 可接管 agent.stream.id 生成,contextMaxRecentMessages 可作为 runner 级 preview 历史上限,单次 runner.context({ maxRecentMessages }) 可以覆盖该默认值;单次 runner.context({ contextBudget }) 会透传给 preview builder,单次 runner.run({ contextBudget }) 会透传给实际 provider context。runner.context() 复用同一条按 Hub sessionId resume-or-start 的路径,返回下一轮 transient provider context preview;它不会发出 agent.stream 事件,也不会把 preview prompt 或 system context 追加进 transcript。runner.bootstrap() 一次返回 SDK manifest 和 doctor diagnostics;packageInfo() / doctor() 仍保留给需要分开刷新 UI 面板的调用方。真实 AgentHub 集成可以直接复制这个组合方式,再替换为自己的 Hub client、任务状态和 session 生命周期。
同一 Node.js 进程内,runner.run() 对相同 resolved absolute storage root + AgentHub sessionId 采用 reject-on-busy 策略,而不是排队串行化;Windows 路径会按小写 key 比较,避免 trailing slash、. 片段和大小写变体绕过 guard。这样可以避免两个 Hub edgeRunId 同时写入同一份 .tokendance/sessions/<sessionId>/transcript.jsonl,保护 transcript seq 和 parentUuid 链路。第二个并发调用会在进入 resume() / startThread() 前抛出 AgentHubSessionRunInProgressError,错误对象带 code: "AGENTHUB_SESSION_RUN_IN_PROGRESS"、reason: "same_session_run_in_progress"、请求 run 和 active run 的 taskId/edgeRunId/agentInstanceId,并且仍会给被拒绝的 edgeRunId 发出一个终态 run.agent.result / turn.failed frame(success: false)。不同 sessionId 或不同 resolved storage root 的 runner.run() 调用仍可并发。
当 runner 配置了 onApprovalRequest 时,远程审批请求会先发出 run.agent.permission_requested envelope,再等待 Hub/Edge 调用 runner.decideApproval()。决策返回后,runtime 会继续发出 run.agent.permission_decided、run.agent.tool_result 和最终结果事件。pendingApprovals() 返回待处理请求快照;decideApproval() 对重复、过期或未知 request id 返回 false。
createTokenDanceIdLogin() 和 verifyTokenDanceIdCallback() 是 SDK TokenDanceID helper 的 runner facade,便于 AgentHub Desktop/Web shell 或调试面板复用同一套 PKCE S256 URL 生成与 callback state 校验。runner 只返回 code、codeVerifier 和 redirectUri 给 Hub;Hub Server 仍拥有 authorization code exchange、JWKS/issuer/audience/expiration 验证、tokendance_sub 映射和 Hub-local session 签发。不要在 runner 或 shell 中保存 TokenDanceID access/refresh token,也不要把 TokenDanceID token 当作 TokenDance Gateway 模型 API key。
createAgentHubTokenDanceConsumerFixture() 适合复制到 AgentHub 测试夹具:defaultRun 提供 workingDirectory/taskId/edgeRunId/sessionId/agentInstanceId 默认值,run() 和 context() 可按单次调用覆盖;defaultLogin 提供 TokenDanceID shell 默认参数;events() 和 approvals() 返回已捕获 payload 快照,便于断言事件顺序和远程审批状态;startup() 委托 runner bootstrap,一次返回 packageInfo() 和 doctor(),且未显式传入 workingDirectory 时会默认检查 defaultRun.workingDirectory,用于 Hub/Edge 启动检查。生产代码应把这些收集器替换为真实落库和广播,不应依赖或发布 @tokendance/code-agenthub-example 作为 public npm contract。
createAgentHubTokenDanceE2EFixture() 仍保留给需要直接访问可变 agentStream / approvalRequests 数组的底层测试;新消费侧示例应优先使用 consumer fixture 的方法式入口,避免调用方误把数组当成生产存储。
const latest = await client.resume({ storageRoot });
console.log(latest.id);
console.log(latest.recentTranscript);
const byId = await client.resume({ sessionId: "session-id", storageRoot });resume() 是 AgentHub 推荐使用的便捷入口;它在未传 sessionId 时恢复最新 session,传入 sessionId 时恢复指定 session。恢复后的 thread 会继续使用同一份 session state 和 JSONL transcript,后续 turn 的 transcript seq 会接着历史事件递增。底层仍保留 loadLatestThread(storageRoot) 和 loadThread(sessionId, storageRoot),供需要显式区分 latest/by-id 的调用方使用。
recentTranscript 暴露的是过滤后的 JSONL envelope,用于 AgentHub 恢复侧栏、事件列表或继续 thread。完整 transcript 仍以 .tokendance/sessions/<session-id>/transcript.jsonl 为事实源。
需要给 AgentHub 会话侧栏、调试面板或轻量索引列出可恢复 session 时,使用只读 client.sessions().list():
const sessions = await client.sessions({ storageRoot }).list();
const matches = await client.sessions({ storageRoot }).searchTranscript("session-id", "needle", { limit: 10 });
const exported = await client.sessions({ storageRoot }).export("session-id");
const pruneCandidates = await client.sessions({ storageRoot }).pruneCandidates({ keepLatest: 20 });
const diagnostic = await client.sessions({ storageRoot }).diagnose("session-id");
for (const session of sessions) {
console.log(session.sessionId, session.latest, session.eventCount, session.messageCount);
console.log(session.lastEventType, session.turnCount, session.recoverableEventCount);
console.log(session.sessionDir);
console.log(session.transcriptPath);
}
for (const match of matches) {
console.log(match.seq, match.eventType, match.preview);
}
console.log(exported.transcriptJsonl);
console.log(exported.metadata.eventTypes);
console.log(pruneCandidates.map((session) => [session.sessionId, session.reason]));
console.log(diagnostic.ok, diagnostic.reason);每条记录包含 sessionId、sessionDir、transcriptPath、createdAt、updatedAt、cwd、permissionMode、messageCount、eventCount、可选 lastEventTimestamp 和 latest 标记。派生调试字段还包括 transcriptVersion、firstEventSeq、lastEventSeq、firstEventTimestamp、lastEventType、lastTurnId、turnCount、recoverableEventCount 和 hasCompactSummary;这些字段来自已落盘 transcript/session,不会写回 JSONL。sessions.searchTranscript() 通过 core 共享的 safe search helper 读取指定 session 的 JSONL transcript,只返回 sessionId、seq、eventType、timestamp、可选 turnId 和 preview,不会把完整原始事件交给 UI。
sessions.export(sessionId) 是只读导出 helper,返回同一份 session metadata、完整 session、完整 parsed transcript、原始 transcriptJsonl 和 metadata.eventTypes/seq/timestamp 摘要,用于 AgentHub 调试面板下载、复制或离线排查;它不新增文件、不写 session state,也不改变 transcript schema。sessions.pruneCandidates({ keepLatest?, olderThanMs?, now? }) 只计算候选,返回 reason、ageMs 和 rank,不会删除 session;真正删除策略仍应由调用方确认后另行实现。sessions.diagnose(sessionId?) 返回 ok/reason/message/sessionsDir/availableSessionIds/selectedSessionId,供 resume 面板在 no_sessions、session_not_found 或 session_unreadable 时展示结构化原因。该 facade 整体只读取 session/transcript 文件,不写入 session state,也不改变 transcript schema。
需要把 transcript 路径展示给 AgentHub UI 或调试面板时,使用 thread.transcript():
const info = await latest.transcript();
console.log(info.sessionId);
console.log(info.transcriptPath);
console.log(info.eventCount);transcript() 返回 sessionDir、transcriptPath、完整 eventCount 和当前 resume 入口带回的 recentEventCount,调用方不需要自己拼 .tokendance/sessions/<session-id>/transcript.jsonl。
需要给 AgentHub 调试面板、会话侧栏或轻量索引提供 transcript 搜索时,使用 thread.searchTranscript():
const matches = await latest.searchTranscript("needle", { limit: 10 });
for (const match of matches) {
console.log(match.seq, match.eventType, match.preview);
}搜索结果包含 sessionId、seq、eventType、timestamp、可选 turnId 和 preview。SDK 会排除 assistant.completed、turn.completed 这类聚合事件,避免同一段 assistant 文本在源事件和完成事件中重复出现。
需要从 AgentHub 触发 compact 时,可以使用 client.compact():
const latestCompact = await client.compact({ storageRoot });
const selectedCompact = await client.compact({ sessionId: "session-id", storageRoot });
console.log(latestCompact.path);
console.log(selectedCompact.eventCount);client.compact() 先通过同一套 resume 入口定位 latest 或指定 session,再生成 deterministic compact summary;调用方也可以在已持有 Thread 时继续使用 thread.compact()。Compact summary 会写入 compact/compact-000N.md,并同步保存到 session.json 的 compactSummary,因此后续 resume/context preview 可以把 Recovery Notes 和 Recent Recoverable Transcript 重新带入模型上下文。
AgentHub 可以通过 SDK 读取 TokenDanceCode 的有效配置,用于调试面板、启动前检查或把 Hub 侧配置投影给 Edge 运行:
const info = await client.config({
projectRoot: "<agenthubProject>",
homeDir: "<operatorHome>"
});
console.log(info.config.provider);
console.log(info.config.model);
console.log(info.config.permissionMode);AgentHub 需要从设置页或启动向导写入本地 project/global 配置时,使用同一个 SDK facade:
const saved = await client.setConfig(
{
provider: "openai-chat-completions",
model: "deepseek-v4-pro",
permissionMode: "safe"
},
{
projectRoot: "<agenthubProject>",
homeDir: "<operatorHome>",
scope: "project"
}
);
console.log(saved.projectConfigPath);AgentHub 启动检查或设置页保存后,可以用 validateConfig() 判断当前 provider 是否已经具备运行所需 env/model,不需要发起真实模型请求:
const readiness = await client.validateConfig({
projectRoot: "<agenthubProject>",
homeDir: "<operatorHome>"
});
console.log(readiness.validation.ready);
console.log(readiness.validation.missing);配置来源按 defaults -> global -> project -> env 合并。当前支持 JSON 文件:
- global:
<homeDir>/.tokendance/config.json - project:
<projectRoot>/.tokendance/config.json
Config facade 只读写 provider、model、permissionMode 三个白名单字段,忽略并清理 apiKey、token 等 secret 字段,避免把密钥带入 CLI 输出、文档或 AgentHub 调试事件。调用方通过 SDK env 显式注入环境时,MODEL_ID / TOKENDANCE_MODEL 可设置模型,TOKENDANCE_PROVIDER 可显式设置 provider;未显式设置 provider 但存在 MODEL_ID 和对应 API key 时,SDK config facade 会把 ANTHROPIC_API_KEY 推断为 anthropic-messages、把 OPENAI_API_KEY 推断为 openai-responses。存在 TOKENDANCE_GATEWAY_API_KEY 和模型时会推断为 openai-chat-completions。需要 OpenAI-compatible /v1/chat/completions 时显式设置 TOKENDANCE_PROVIDER=openai-chat-completions。密钥只参与 present/missing 和 provider 推断,不会进入 config() / setConfig() 输出,也不应写入 JSON 配置。
如果 AgentHub shell 或本地脚本只能调用 CLI,可以使用同源 JSON 输出,避免解析人类可读文本:
tokendance config --json
tokendance config validate --json
tokendance config set --json provider openai-chat-completions model deepseek-v4-pro permission-mode safeconfig validate --json 返回 validation.ready、validation.missing、所需 API key env 名、实际使用的 env 名和 base URL 来源状态;文本模式在缺少必需项时返回非 0。config set --json 在 config payload 之外返回 scope 和 savedPath,便于启动向导确认写入位置。CLI JSON 输出同样不包含 provider key、TokenDanceID token 或其他 secret 值。
AgentHub 可以通过 SDK 读取和 CLI doctor 同源的结构化诊断,用于启动前检查、调试面板或 Edge 环境报告:
const doctor = await client.doctor({
projectRoot: "<agenthubProject>",
homeDir: "<operatorHome>"
});
console.log(doctor.apiKeys.OPENAI_API_KEY);
console.log(doctor.git.available);
console.log(doctor.powershell.available);
console.log(doctor.config.sources);
console.log(doctor.config.provider);
console.log(doctor.config.validation.ready);
console.log(doctor.stateDir.writable);
console.log(doctor.packageInfo.agentHub.sdkContractVersion);
console.log(doctor.startup.hub.ok);
console.log(doctor.startup.edge.ok);
console.log(doctor.agentHub.sdkContractVersion);
console.log(doctor.agentHub.readinessContract);
console.log(doctor.agentHub.ready);
console.log(doctor.agentHub.blockingChecks);
console.log(doctor.agentHub.warningChecks);doctor.apiKeys 只返回 present/missing,不会返回实际 API key。诊断结果还包括版本、Node、cwd、platform、Git 仓库状态、PowerShell 可用性、config 路径/source、有效 provider/model、provider readiness、.tokendance 状态目录可写性、只读 packageInfo manifest,以及 startup.hub / startup.edge 检查组。Hub 侧当前检查 package manifest、config 可读性、状态目录可写性和 provider-ready;真实 provider 缺少 key/model 时 provider-ready 为 warn,startup.hub.ok 仍保持 true,方便 AgentHub 启动后在设置页引导补齐配置。Edge 侧当前检查 agent.stream envelope 契约、Git 可用性和 PowerShell 可用性。warn 级检查不会让 ok 变成 false;fail 代表启动前必须处理的阻断项。
doctor.agentHub 是给 Hub/Edge 启动检查和调试面板使用的聚合视图:contractVersion 是旧兼容名,sdkContractVersion 是推荐读取名;readinessContract 当前为 "agenthub.doctor-readiness.v1";agentStreamSchemaVersion 和 features 来自同一份 package manifest。ready 等于 Hub 与 Edge 检查组都没有 fail;blockingChecks 和 warningChecks 用 hub.<check-name> / edge.<check-name> 标记阻断项和告警项。Hub 检查组包含 sdk-contract,用于单独展示 SDK contract readiness。调用方如果只需要判断是否能启动,可以先看 doctor.agentHub.ready;如果要展示详细原因,再展开 startup.hub.checks 和 startup.edge.checks。
AgentHub 可以通过 SDK 管理持久任务和 session 级 todo,而不需要直接依赖 core store。Task 是跨 session 的长期任务图,Todo 是当前 session 或当前任务内的短期执行计划。
const tasks = client.tasks({
projectRoot: "<agenthubProject>"
});
const task = await tasks.create({
title: "Stage 15 E2E",
description: "Close SDK/CLI acceptance"
});
await tasks.addDependency(task.id, "task-parent");
await tasks.linkSession(task.id, "sess_01HX...");
await tasks.linkWorktree(task.id, "<managedWorktreePath>");
await tasks.updateStatus(task.id, "completed");
console.log(await tasks.metadata());
const todos = client.todos({
projectRoot: "<agenthubProject>",
sessionId: "sess_01HX..."
});
const todo = await todos.add({
text: "Run pnpm verify",
taskId: task.id
});
await todos.updateStatus(todo.id, "in_progress");
console.log(await todos.metadata());Task 写入 <projectRoot>/.tokendance/tasks/tasks.jsonl 和可重建的 <projectRoot>/.tokendance/tasks/task-index.json。带 sessionId 的 Todo 写入 <projectRoot>/.tokendance/sessions/<sessionId>/todos.json;未传 sessionId 时写入项目级 <projectRoot>/.tokendance/todos.json,供 CLI 的 /todo 和 tokendance todo 使用。
当前 SDK facade 覆盖 create/list/get/updateStatus/addDependency/linkSession/linkWorktree/metadata 和 add/list/updateStatus/metadata。tasks.metadata() 返回任务总数、状态计数、session/worktree 关联数、依赖边数量和最新 task id;todos.metadata() 返回 todo 总数、状态计数、可选 session id 和 task 关联数。CLI 暴露自用高频操作:list、create/add、doing、done,并支持 tasks link-session <task-id> <session-id> 与 tasks link-worktree <task-id> <worktree>,方便把长期任务、session transcript 和隔离 worktree 关联成可审计闭环;更复杂的依赖图仍由 SDK 或后续 AgentHub UI 驱动。
AgentHub 可以通过 SDK 管理 TokenDanceCode 的受控 Git worktree 池,用于后续 coding subagent 隔离。当前只提供最小 list/create/remove,不包含 subagent 调度器。
const worktrees = client.worktrees({
repositoryRoot: "<repositoryRoot>"
});
const created = await worktrees.create({ name: "agenthub-wt" });
console.log(created.branch); // codex/agenthub-wt
console.log(created.path);
console.log(created.dirty);
console.log(created.dirtyFiles);
console.log(await worktrees.status("agenthub-wt"));
await worktrees.remove("agenthub-wt");默认 worktree 根目录是 <repositoryRoot>/.worktrees,默认分支名是 codex/<name>。name 只允许字母、数字、点、下划线和短横线,避免路径穿越和 Windows 文件名风险。list()、create() 和 status(name) 返回的每条记录都包含 dirty、dirtyFiles 和 dirtyFileCount,由目标 worktree 内的 git status --porcelain 计算,供 AgentHub 在展示或删除前做显式确认。
remove(name) 会先检查目标 worktree 的 git status --porcelain;存在未提交改动时拒绝删除,错误对象带 dirtyFiles,消息中也会列出候选路径。只有调用方显式传 remove(name, { discard: true }) 时才会使用 git worktree remove --force。CLI 对应 tokendance worktree remove <name> --discard。
AgentHub 可以通过 SDK 启动和查看 delegated subagent 结果。首版不是多 Agent 团队系统,而是自用的 bounded delegation:readonly investigator/reviewer 返回 summary 且不报告文件修改;coding subagent 在 managed worktree 中运行,并报告 changed files、diff、validation result。
const subagents = client.subagents({
projectRoot: "<agenthubProject>"
});
const review = await subagents.runReadonly({
agentType: "reviewer",
prompt: "Inspect SDK boundary"
});
const coding = await subagents.runCoding({
prompt: "Prepare isolated change",
worktree: "agenthub-coding",
taskId: "task-1"
});
console.log(review.summary);
console.log(coding.worktreePath);
console.log(coding.worktreeDirtyFiles);
console.log(await subagents.get(coding.id));
console.log(await subagents.list());
console.log(await subagents.metadata());
await subagents.accept(coding.id, {
discardWorktree: true
});
const throwaway = await subagents.runCoding({
prompt: "Try disposable change",
worktree: "agenthub-throwaway"
});
await subagents.discard(throwaway.id, { discard: true });Subagent 索引写入 <projectRoot>/.tokendance/agents/agents.json,单个 subagent event log 写入 <projectRoot>/.tokendance/agents/<agent-id>/events.jsonl。这个文件只记录 bounded delegation 的 subagent_started / subagent_completed / subagent_accepted / subagent_discarded 事件,不是 .tokendance/sessions/<session-id>/transcript.jsonl 的 canonical TranscriptEnvelope schema;AgentRunRecord.eventLogPath 是推荐字段,transcriptPath 仅作为旧调用方兼容别名保留。runCoding({ taskId }) 会把任务关联保存到 AgentRunRecord.taskId,方便 AgentHub 将 task、subagent event log 和 worktree diff 串成同一条执行闭环。AgentRunRecord 会带 worktreeDirty 和 worktreeDirtyFiles;subagents.list() / subagents.get(id) 会按当前 managed worktree 重新计算这两个字段,但不会改写索引。subagents.metadata() 返回 run 类型/状态计数、dirty worktree 数、task 关联数和最新 agent id,供调试面板做只读汇总。subagents.accept(id) 会把 coding subagent worktree 的当前 diff 应用回目标仓库并把 run 标记为 accepted,目标仓库存在用户可见未提交改动时默认拒绝,错误对象带 dirtyFiles,避免把 subagent diff 混进脏工作区;只有显式 accept(id, { allowDirtyTarget: true }) 才覆盖这个保护。subagents.discard(id) 会移除 coding subagent 的 managed worktree 并把 run 标记为 discarded,dirty worktree 默认拒绝删除,错误对象带 dirtyFiles,只有显式 discard(id, { discard: true }) 才会强制丢弃未提交改动。默认 registry 同时暴露 subagent_run、subagent_list、subagent_get、subagent_accept 和 subagent_discard;subagent_run、subagent_accept 和 subagent_discard 是 shell 风险工具,因为它们会创建、应用或移除 worktree。
AgentHub 如果需要把项目约定或用户偏好写入 TokenDanceCode 的上下文来源,可以通过 SDK 管理 project/global memory,不需要直接依赖 core MemoryStore:
const memory = client.memory({
projectRoot: "<agenthubProject>",
homeDir: "<operatorHome>"
});
await memory.add("project", "Use pnpm verify before merging.");
await memory.add("global", "Prefer concise status updates.");
console.log(await memory.list("project"));
await memory.delete("project", 0);project memory 写入 <projectRoot>/.tokendance/memory/project.md,global memory 写入 <homeDir>/.tokendance/memory/global.md。当前只做显式增删查和 ContextBuilder 注入,不做自动抽取、自动改写或隐式上传。
AgentHub 如果需要在 UI 或任务编排层触发 TokenDanceCode 已注册工具,可以使用 SDK 的 client.tools(),避免直接依赖 core ToolOrchestrator:
const tools = client.tools({
workingDirectory: "<agenthubProject>",
permissionMode: "default"
});
const status = await tools.execute("git_status");
const diff = await tools.execute("git_diff", { paths: ["README.md"] });
const review = await tools.execute("git_review");
const metadata = tools.list();
const autoQuality = await tools.execute(
"quality_gate",
{},
{ permissionMode: "yolo" }
);
const quality = await tools.execute(
"quality_gate",
{ command: "pnpm verify", timeout: 120 },
{ permissionMode: "yolo" }
);tools.list() 返回不含 executor/parse 函数的工具能力 metadata:name、description、risk、riskSummary、concurrency、permissionProfiles、各权限模式下的 permission 状态、对应的 permissionReasons、permissionRiskMetadata,以及工具级 safetyNotes。AgentHub 首选消费 permissionProfiles.default/safe/auto/yolo,每个 profile 直接包含同一模式下的 status、reason 和 riskMetadata,不用再把三个并行 map 自行拼装;旧 map 字段保留用于兼容现有面板或日志。AgentHub 可以用它渲染调试面板、权限说明、拒绝原因预览或工具开关。
这个 facade 的 execute() 返回 core ToolResult,用于 AgentHub 调试面板、手动质量门、Git diff/review、worktree/subagent 管理工作流和受控工具执行。quality_gate 不传 command 时会自动发现 package.json 的 verify 脚本,缺少 verify 时回退到 test;传入 command 时使用显式命令覆盖。即使用 yolo 让质量命令运行,PowerShell 工具层仍会拒绝已知高风险命令。worktree_create、worktree_remove、subagent_run、subagent_accept 和 subagent_discard 是 shell 风险工具,默认模式下需要审批或显式 tool facade 覆盖权限。
packages/sdk/tests/sdk.test.ts覆盖 buffered turn、streamed events、多轮 thread、context preview/history limit、latest/by-id resume、session list facade、latest/by-id compact、transcript metadata/search、config facade、doctor facade/startup checks、AgentHub readiness 汇总、memory facade、task/todo facade、subagent facade、worktree facade、tool metadata facade、tool execution facade、worktree/subagent tools、审批允许/拒绝、provider env 配置错误、event sink。packages/sdk/tests/package-metadata.test.ts覆盖 public package metadata、pack:check脚本、tarball ignore 规则和 SDK 导出的 AgentHub-readable package manifest / feature flags。packages/sdk/tests/approval-bridge.test.ts覆盖 AgentHub 远程审批 bridge、pending 快照、allow/deny 决策回填、timeout 和重复决策边界。packages/sdk/tests/agenthub-events.test.ts覆盖TDCodeEvent到 AgentHubrun.agent.*的映射、sink 包装和agent.streampayload fixture。packages/agenthub-example/tests/agenthub-runner.test.ts覆盖 AgentHub runner 示例、e2e fixture、runner bootstrap、runner options、context preview history limit、远程审批桥接、agent.streampayload 序列、emitter 形态、runner package manifest 和 doctor 启动诊断。packages/core/tests/*覆盖 runtime、permission、provider adapter、file/shell/patch/git/subagent/worktree/tool metadata/context/resume/config/memory/task/todo。
完整验证命令:
pnpm verify
pnpm pack:check