opentag平台提及到本地代理
§ · Nested detail page

Local runtime 与执行器选择 深度解析

本页只展开一个模块,把导入契约、核心实现、流程和失败模式拆成可回溯行号。

§ 00 · IMPORT CHAIN

导入链与契约

入口代码说明该模块接受什么输入、输出什么结构,以及它依赖哪些相邻模块。

▸入口片段

packages/local-runtime/src/runtime.ts · L1-36TypeScript
   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

§ 01 · CORE IMPL

核心实现,逐段拆

▸中段状态转换

这一段通常体现最关键的状态转换:解析、选择、打分、写入或安全检查。

packages/local-runtime/src/runtime.ts · L37-72TypeScript
  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  };

▸末段收束

收束段决定失败是否显式返回、结果是否稳定落盘,以及调用者能否继续恢复。

packages/local-runtime/src/runtime.ts · L37-72TypeScript
  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  };
§ 02 · KEY FLOW

关键流程

▸流程拆解

input → normalize → validate → output
failure → explicit fallback / error record → caller decision

如果后续改动触碰这些行,主页面对应的风险判断也要重跑。

§ 03 · EDGE CASES

边界 / 失败模式

▸边界列表

  • 空输入或缺失字段必须被显式处理。
  • 外部命令、模型调用或文件写入失败不能覆盖原始上下文。
  • 路径别名仅用于索引兼容;实际源码位置见每个代码块的 repo 标注。
Sub-page · 2026-06-29Source · amplifthq/opentag↑ opentag 主页面