Local runtime 与执行器选择 深度解析
本页只展开一个模块,把导入契约、核心实现、流程和失败模式拆成可回溯行号。
导入链与契约
入口代码说明该模块接受什么输入、输出什么结构,以及它依赖哪些相邻模块。
▸入口片段
1import { createDispatcherClient } from "@opentag/client"; 2import { createClaudeCodeExecutor, createCodexExecutor, createEchoExecutor, type RunnerSecurityPolicy } from "@opentag/runner"; 3import type { OpenTagDaemonConfig } from "./config.js"; 4import type { DaemonClient } from "./daemon.js"; 5import type { PullRequestOptions } from "./pr.js"; 6 7export function securityFromConfig(config: OpenTagDaemonConfig): RunnerSecurityPolicy | undefined { 8 const security = config.security; 9 if (!security) return undefined; 10 11 const normalized: RunnerSecurityPolicy = {}; 12 if (security.mode !== undefined) normalized.mode = security.mode; 13 if (security.allowedWorkspaceRoot !== undefined) normalized.allowedWorkspaceRoot = security.allowedWorkspaceRoot; 14 if (security.allowUnsafePrompts !== undefined) normalized.allowUnsafePrompts = security.allowUnsafePrompts; 15 if (security.extraSafeEnv !== undefined) normalized.extraSafeEnv = security.extraSafeEnv; 16 17 return Object.keys(normalized).length > 0 ? normalized : undefined; 18} 19 20export function executorsFromConfig(config: OpenTagDaemonConfig) { 21 const security = securityFromConfig(config); 22 23 return { 24 echo: createEchoExecutor(), 25 codex: createCodexExecutor({ 26 ...(security ? { security } : {}) 27 }), 28 "claude-code": createClaudeCodeExecutor({ 29 ...(config.claudeCode?.command ? { claudeCommand: config.claudeCode.command } : {}), 30 ...(config.claudeCode?.model ? { model: config.claudeCode.model } : {}), 31 ...(config.claudeCode?.permissionMode ? { permissionMode: config.claudeCode.permissionMode } : {}), 32 ...(config.claudeCode?.dangerouslySkipPermissions !== undefined 33 ? { dangerouslySkipPermissions: config.claudeCode.dangerouslySkipPermissions } 34 : {}) 35 }) 36 };
源码索引:packages/local-runtime/src/runtime.ts:1 · packages/local-runtime/src/runtime.ts:2 · packages/local-runtime/src/runtime.ts:3 · packages/local-runtime/src/runtime.ts:4 · packages/local-runtime/src/runtime.ts:5 · packages/local-runtime/src/runtime.ts:6 · packages/local-runtime/src/runtime.ts:7 · packages/local-runtime/src/runtime.ts:8 · packages/local-runtime/src/runtime.ts:9 · packages/local-runtime/src/runtime.ts:10 · packages/local-runtime/src/runtime.ts:11 · packages/local-runtime/src/runtime.ts:12 · packages/local-runtime/src/runtime.ts:13 · packages/local-runtime/src/runtime.ts:14 · packages/local-runtime/src/runtime.ts:15 · packages/local-runtime/src/runtime.ts:16 · packages/local-runtime/src/runtime.ts:17 · packages/local-runtime/src/runtime.ts:18 · packages/local-runtime/src/runtime.ts:19 · packages/local-runtime/src/runtime.ts:20 · packages/local-runtime/src/runtime.ts:21 · packages/local-runtime/src/runtime.ts:22 · packages/local-runtime/src/runtime.ts:23 · packages/local-runtime/src/runtime.ts:24 · packages/local-runtime/src/runtime.ts:25 · packages/local-runtime/src/runtime.ts:26 · packages/local-runtime/src/runtime.ts:27 · packages/local-runtime/src/runtime.ts:28 · packages/local-runtime/src/runtime.ts:29 · packages/local-runtime/src/runtime.ts:30 · packages/local-runtime/src/runtime.ts:31 · packages/local-runtime/src/runtime.ts:32 · packages/local-runtime/src/runtime.ts:33 · packages/local-runtime/src/runtime.ts:34 · packages/local-runtime/src/runtime.ts:35 · packages/local-runtime/src/runtime.ts:36 · packages/local-runtime/src/runtime.ts:37 · packages/local-runtime/src/runtime.ts:38 · packages/local-runtime/src/runtime.ts:39 · packages/local-runtime/src/runtime.ts:40 · packages/local-runtime/src/runtime.ts:41 · packages/local-runtime/src/runtime.ts:42 · packages/local-runtime/src/runtime.ts:43 · packages/local-runtime/src/runtime.ts:44 · packages/local-runtime/src/runtime.ts:45
核心实现,逐段拆
▸中段状态转换
这一段通常体现最关键的状态转换:解析、选择、打分、写入或安全检查。
37} 38 39export function createDaemonClient(config: OpenTagDaemonConfig): DaemonClient { 40 return createDispatcherClient({ 41 dispatcherUrl: config.dispatcherUrl, 42 runnerId: config.runnerId, 43 ...(config.pairingToken ? { pairingToken: config.pairingToken } : {}) 44 }); 45} 46 47export function pullRequestOptionsFromConfig(config: OpenTagDaemonConfig): PullRequestOptions | undefined { 48 if (!config.githubToken && config.preparePullRequestBranch === undefined && config.allowAutoCreatePullRequest === undefined) { 49 return undefined; 50 } 51 52 return { 53 ...(config.githubToken ? { githubToken: config.githubToken } : {}), 54 ...(config.preparePullRequestBranch !== undefined ? { preparePullRequestBranch: config.preparePullRequestBranch } : {}), 55 ...(config.allowAutoCreatePullRequest !== undefined ? { allowAutoCreatePullRequest: config.allowAutoCreatePullRequest } : {}) 56 }; 57} 58 59export function createDaemonRuntimeInput(config: OpenTagDaemonConfig) { 60 const security = securityFromConfig(config); 61 const pullRequestOptions = pullRequestOptionsFromConfig(config); 62 63 return { 64 runnerId: config.runnerId, 65 repositories: config.repositories, 66 executors: executorsFromConfig(config), 67 ...(security ? { security } : {}), 68 ...(pullRequestOptions ? { pullRequestOptions } : {}), 69 ...(config.heartbeatIntervalMs ? { heartbeatIntervalMs: config.heartbeatIntervalMs } : {}), 70 ...(config.pollIntervalMs ? { pollIntervalMs: config.pollIntervalMs } : {}), 71 client: createDaemonClient(config) 72 };
▸末段收束
收束段决定失败是否显式返回、结果是否稳定落盘,以及调用者能否继续恢复。
37} 38 39export function createDaemonClient(config: OpenTagDaemonConfig): DaemonClient { 40 return createDispatcherClient({ 41 dispatcherUrl: config.dispatcherUrl, 42 runnerId: config.runnerId, 43 ...(config.pairingToken ? { pairingToken: config.pairingToken } : {}) 44 }); 45} 46 47export function pullRequestOptionsFromConfig(config: OpenTagDaemonConfig): PullRequestOptions | undefined { 48 if (!config.githubToken && config.preparePullRequestBranch === undefined && config.allowAutoCreatePullRequest === undefined) { 49 return undefined; 50 } 51 52 return { 53 ...(config.githubToken ? { githubToken: config.githubToken } : {}), 54 ...(config.preparePullRequestBranch !== undefined ? { preparePullRequestBranch: config.preparePullRequestBranch } : {}), 55 ...(config.allowAutoCreatePullRequest !== undefined ? { allowAutoCreatePullRequest: config.allowAutoCreatePullRequest } : {}) 56 }; 57} 58 59export function createDaemonRuntimeInput(config: OpenTagDaemonConfig) { 60 const security = securityFromConfig(config); 61 const pullRequestOptions = pullRequestOptionsFromConfig(config); 62 63 return { 64 runnerId: config.runnerId, 65 repositories: config.repositories, 66 executors: executorsFromConfig(config), 67 ...(security ? { security } : {}), 68 ...(pullRequestOptions ? { pullRequestOptions } : {}), 69 ...(config.heartbeatIntervalMs ? { heartbeatIntervalMs: config.heartbeatIntervalMs } : {}), 70 ...(config.pollIntervalMs ? { pollIntervalMs: config.pollIntervalMs } : {}), 71 client: createDaemonClient(config) 72 };
关键流程
▸流程拆解
failure → explicit fallback / error record → caller decision
如果后续改动触碰这些行,主页面对应的风险判断也要重跑。
边界 / 失败模式
▸边界列表
- 空输入或缺失字段必须被显式处理。
- 外部命令、模型调用或文件写入失败不能覆盖原始上下文。
- 路径别名仅用于索引兼容;实际源码位置见每个代码块的 repo 标注。