ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Activepieces Action Runs 深度解析:MCP 与 Chat 工具背后的单步执行引擎

Activepieces Action Runs 深度解析:MCP 与 Chat 工具背后的单步执行引擎 Activepieces Action Runs 深度解析MCP 与 Chat 工具背后的单步执行引擎【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces导读Action Runs动作运行是 Activepieces 中脱离流程执行单个步骤的核心机制它既不创建临时流程也不落库持久化而是以同步请求/响应的方式在引擎内直接执行一个 Piece Action 或 Code 步骤并把{ status, output, logs, errorMessage }原样返回给调用方。它是 MCP 工具ap_run_action、ap_execute_action/ap_run_code以及 Chat 工具背后的统一执行单元。读完本文你将掌握 Action Runs 的完整调用链、端到端时限预算模型、未启动neverStarted语义、代码缓存命名空间与回收机制以及它在限流、路由、RPC 超时等运维层面的全部边界条件。本文以仓库内知识文档 action-run.md 为核心骨架并结合 action-run.service.ts、execute-action.ts、action-run-cache.ts 等源码逐层展开印证。一、从临时流程 Hack到真正的单步执行在 Action Runs 出现之前MCP/Chat 工具要执行一个孤立步骤走的是所谓的临时流程temporary flow老路创建一个一次性throwaway流程把目标步骤嫁接进去调用flowRunService.test()触发运行轮询flow_run记录最多等 120 秒从run.steps中把结果抠出来尽力删除临时流程。这条路径存在明显问题每次调用要付出 3 次数据库插入流程、版本、运行记录的成本却因为flow_run.flowId配置了onDelete: CASCADE删除流程时运行记录也随之清空——也就是说它既慢又重还没有留下任何持久化产物。Action Runs 正是为取代这套 Hack 而设计的新机制核心特征如下执行即返回调用方在进程内直接拿到结果不经过队列轮询不持久化目前阶段 Action Runs 是纯执行一切持久化能力独立的action_run表、对应端点、Action runs UI 标签页在后续版本单独落地与 Flow Runs 分开管理。从源码看新版入口executeActionRunAction/executeActionRunCode位于 flow-run-utils.ts它们把用户请求组装成一个标准的PieceAction/CodeAction步骤固定步骤名step_1再交给actionRunService.run()。这正是文档所说的删掉了临时流程路径的重写。二、整体工作方式三阶段架构2.1 Dispatch同步的用户交互任务actionRunService(log).run({ projectId, platformId, step })是入口流程如下见 action-run.service.ts先用真实 schemaActionRunStep校验步骤见下文Schema 校验一节若是 Piece 步骤通过getPiecePackageWithoutArchive解析 piece 包以WorkerJobType.EXECUTE_ACTION提交任务并同步等待响应——userInteractionWatcher.submitAndWaitForResponse是请求/响应模型与属性解析property resolution、鉴权校验auth validation走的是同一套机制无轮询、无队列化流程任务、无重试。这与设计决策 Action runs dispatch as synchronous user-interaction jobs 完全一致。在 user-interaction-watcher.ts 中可以看到任务以JobType.ONE_TIME入队随后通过engineResponseWatcher.oneTimeListener等待requestId对应的响应若等待超时返回空值则抛出WORKER_DID_NOT_RESPOND_MESSAGEWorker did not respond within the safety timeout。2.2 Engine共享的单步原语引擎侧的执行由actionOperation.execute见 action.operation.ts完成export const actionOperation { execute: async (operation: ExecuteActionOperation): PromiseEngineResponseExecuteActionResponse { const stepOutput await actionRunStepRunner.run({ step: operation.step, operation }) const success stepOutput.status StepOutputStatus.SUCCEEDED return { status: EngineResponseStatus.OK, response: { success, input: stepOutput.input, output: stepOutput.output, message: success || isNil(stepOutput.errorMessage) ? undefined : String(stepOutput.errorMessage), }, } }, }核心原语是 action-run-step-runner.tsexport const actionRunStepRunner { async run({ step, operation }: ActionRunStepParams): PromiseStepOutput { const executionState await flowExecutor.getExecutorForAction(step.type).handle({ action: step, executionState: FlowExecutorContext.empty(), constants: EngineConstants.fromExecuteActionInput(operation), }) return executionState.steps[step.name] }, }要点在于它针对空上下文FlowExecutorContext.empty()执行那一个步骤然后从steps[step.name]取回结果。Chat 工具执行器engine/src/lib/tools/index.ts调用的正是同一个原语因此直接执行单个步骤与在流程里执行单个步骤的行为保持了一致。2.3 Outcome结果归一化deriveActionRunOutcome见 action-run-outcome.ts把引擎响应映射为{ status, output, logs, errorMessage }status是FlowRunStatus类型但 Action Run 是同步的所以只有SUCCEEDED / FAILED / TIMEOUT / INTERNAL_ERROR可达——QUEUED和RUNNING永远不会出现PAUSED被显式拒绝watcher 超时WORKER_DID_NOT_RESPOND_MESSAGE映射为TIMEOUT其余异常映射为INTERNAL_ERRORneverStarted字段单独透传详见第四节。2.4 优先级high而不是 criticalAction Run 的作业优先级为high而非critical。这样做的原因是action run 不应超过用户正实时等待的 Builder 交互任务。critical留给人类正在等待的操作high则保证 action run 排队在前、但不抢占交互。三、调用链与状态机一次 Action Run 的完整旅程综合 dispatch、worker 与引擎三侧源码一次完整调用链如下MCP ap_run_action / Chat ap_execute_action │ ▼ flow-run-utils.ts: executePieceActionRun / executeCodeActionRun │ 组装 PieceAction/CodeActionstep 名固定 step_1UpdateActionRequest.safeParse 校验 ▼ action-run.service.ts: actionRunService.run │ ① ActionRunStep.safeParse 二次校验 │ ② getPiecePackageWithoutArchive 解析 piecePIECE 类型 │ ③ 打上 expiresAt now AP_FLOW_TIMEOUT_SECONDS ▼ user-interaction-watcher.submitAndWaitForResponse │ 入队 JobType.ONE_TIME带上 requestId / webserverId / schemaVersion ▼ worker: execute-action.ts │ ① resolveCodeStepCODE 步骤生成代码缓存命名空间 │ ② ctx.resolver.resolve解析 pieces / codes │ ③ timeoutInSeconds ceil((expiresAt - now)/1000) │ ④ ctx.runtime.executeEngineOperationType.EXECUTE_ACTION ▼ engine: action.operation → action-run-step-runner │ FlowExecutorContext.empty() 上执行单步 ▼ sandbox.executeSIGKILL 定时器在此武装 │ ⑤ 超时 → SandboxExecutionTimeoutParams含 neverStarted ▼ 引擎响应 → watcher 回调 → deriveActionRunOutcome → ActionRunResult其中ExecuteActionJobData与WorkerJobType.EXECUTE_ACTION定义在 job-data.tsEngineOperationType.EXECUTE_ACTION与ExecuteActionOperation定义在 engine-operation.ts。由于EXECUTE_ACTION属于UserInteractionJobData其载荷结构受LATEST_JOB_DATA_SCHEMA_VERSION约束——任何必填字段的增改都需要一次 job-data 迁移expiresAt是特例它被设计为可选字段因此旧形状的作业仍能正常解析、行为不变。Schema 校验为什么不用z.customstep参数用真实 schemaActionRunStep校验而不是z.custom()。原因很直接z.custom()在没有校验器时接受任何值——缺失的step、甚至42都能通过——这会让tryDequeue对该作业类型的 schema 闸门形同虚设。同一个 schema 在actionRunService入队前也会解析一次因为在出队时才发生 schema 失败会成为UnrecoverableError而该路径永远不会向 watcher 发布响应——调用方会白白挂满整个预算最后却被告知动作可能已经执行。四、预算模型一个端到端截止时间而不是两个超时的叠加这是 Action Runs 最容易被改坏的地方文档专门用大段篇幅强调这里完整复述其结论actionRunService在作业上盖戳expiresAt now AP_FLOW_TIMEOUT_SECONDS随后等待expiresAt 10sWATCHER_GRACE_MS 10 * 1000见 user-interaction-watcher.ts这额外的 10 秒只覆盖沙箱杀进程与 pubsub 回程不是给排队、解析或预置provisioning留的余量——因为这些阶段花的是同一个预算createSandboxRuntime.execute会把运行时间钳制到预置和启动boot之后expiresAt剩下的部分若已无剩余则直接抛SANDBOX_EXECUTION_TIMEOUT、不启动引擎。不要把它重塑成沙箱预算 容差因子——这正是最初的 bugwatcher 的时钟从入队开始走而沙箱的时钟从运行开始走冷安装 piece很容易超过 10s会让调用方先超时而动作仍在运行并写入数据随之而来的重试还会复制副作用。worker 侧不再自带上限execute-action.ts的timeoutInSeconds只从expiresAt推导Math.ceil((data.expiresAt - Date.now()) / 1000)remainingTimeoutInSeconds必须在定时器武装的位置重新推导唯一的执行预算强制者是sandbox.execute内部武装的 SIGKILL 定时器早先版本在sandbox.start()之前就计算剩余时间导致引擎从更晚的时刻起算拿到完整预算用户代码一直运行到expiresAt bootMs。启动boot不是尾部现象canReuseSandbox对SANDBOX_PROCESS默认返回 false除非AP_REUSE_SANDBOXtrue所以每个生产 action run 都要付绑定重试、两次无界的 isolateexecPromise调用和 30 秒连接上限远超 10 秒宽限。启动前检查只作为快速路径保留跳过无意义的 spawn权威钳制在sandboxStart超时块之后重算注意重算的抛错落在try内部会作废刚启动的沙箱而不是把它留作热箱——这是罕见路径上的一次冷启动代价被接受为重构把 boot 移出try的替代方案。层级顺序预算栈必须保持有序Action Run 是一个自动化步骤所以它拿到的是流程步骤的预算——actionRunService与runFlowAsTool读取同一个属性AP_FLOW_TIMEOUT_SECONDS。必须保持的顺序由外到内worker RPC 超时LONG_RUNNING_RPC_METHODS budget LONG_RUNNING_RPC_MARGIN_MS watcher 等待 budget WATCHER_GRACE_MS action 预算 引擎运行余量不是装饰app 在同一次调用上还会花费 action 之外的时长例如pieceInputFiller的模型调用它没有自己的超时所以零余量的截止时间会在 action 仍合法运行时就把调用方掐死。任何一层都不要给它单独的 env var——整个栈随AP_FLOW_TIMEOUT_SECONDS这一个旋钮整体移动。不要贸然给预置或连接等待加时限给spawnWithKill的timeoutMs或waitForConnection的 30 秒字面量bare literal套截止时间看起来是顺理成章的下一步但两者抛出的都是普通Error无法通过execute-action.ts里的isSandboxTimeout判定会被重新抛出——调用方得到的是INTERNAL_ERROR/ the engine crashed while loading or executing the piece这正是下一节要禁止的错误误报而不是诚实的neverStarted。而且给连接等待设上限也约束不了 boot它是 boot 三阶段中的最后一环前面还有无界的 isolate 调用。两个改动都买不到安全因为预置超限本来就会以未执行任何用户代码的neverStarted结束。五、neverStarted把 TIMEOUT 一分为二的未写证明5.1 为什么 TIMEOUT 本身不够动作运行了、我们失去了耐心和什么都没执行绝不能共享同一条消息。只有后者可以安全地盲目重试而告诉 Agent去检查一下有没有写入——当它什么都没运行时——会训练 Agent 放弃无操作。两条来源汇入同一个标志worker 侧拒绝启动一个截止时间已过的运行SandboxExecutionTimeoutParams.neverStartedwatcher 超时actionRunService调用jobQueue.cancelAndReportNeverStarted。在 action-run.service.ts 中可以看到仅当结果状态为TIMEOUT且 watcher 无数据时abandonedWithoutStarting才会尝试调用cancelAndReportNeverStarted并把结果并入neverStarted。在 MCP 工具侧flow-run-utils.tsTIMEOUT neverStarted会给出never started — nothing ran and nothing was written. Safe to retry as-is而裸TIMEOUT则提示may have partially completed, so do not re-run it blindly。按文档的说法neverStarted是**未写入的证明**proof of no-writesound 但刻意不完整——它不是生命周期阶段。5.2 移除与上报是两件事用processedOn不用job.getState()cancelAndReportNeverStarted总是先尝试移除作业并从job.processedOn读取信号——绝不从job.getState()读取。早期形态把两者焊在一起一个状态允许列表同时把关销毁作业使其无法再写入和告诉 Agent 什么都没运行。但BullMQ 状态不是单调的——waiting → active → delayed → waiting是合法路径——一个已经执行并写入的作业可能因为 worker 断连详见 Workers落在delayed状态、被读成尚未开始焊接版本于是把它移除并上报neverStarted: true告诉 Agent什么都没运行、什么都没写入、可以原样重试——这正是无重试决策要防止的重复副作用。processedOn是可靠的信号并且压倒了状态允许列表所以不要两者都保留moveToActive在同一脚本里设置processedOn该路径上没有任何操作清除它moveToDelayed、moveStalledJobsToWait都不碰它只有Job.retry()会置空——那是显式的失败作业重试 API每个终态都只能经过active到达所以completed/failed一定携带processedOn。状态读取在竞态窗口内多一次往返且无法独立触发。5.3 移除失败作为第二信号唯一processedOn覆盖不了的情况是作业在读取与移除之间被抢走。经 pinned 的 bullmq 5.61.0 验证job.remove()只在作业被其他 worker 锁定时抛错removeJob脚本在锁 key 存在时返回 0Job.remove对 falsy 结果抛错对completed/failed状态成功移除。因此抛出即意味着正在运行中。5.4 残余风险已复核并接受processedOn来自getJob()快照所以一个在快照与remove()之间出队并完成的作业仍会报neverStarted: true。不要把一个 Redis RTT 内当作边界快照会在 API 事件循环停滞期间过期。真实的前提栈是作业在完整的约 130 秒 watcher 预算内饿死未出队 → worker 恰好在该过期窗口内出队 → 分发 沙箱 执行 completeJob全部在removeJob于 Redis 上执行之前完成。冷生产启动是秒级的这让它在生产上不可达在AP_REUSE_SANDBOXtrue下一个几百毫秒的 GC 停滞加上完美时序可能触发——即使在宽裕假设下量级也约为≤1/10⁷ 次 action run。文档注明该残余被保留因为任何缓解方案都比暴露成本高已知的廉价闭合方案若未来需要消灭它让completeJob在moveToCompleted之前写一个短 TTL 的、以requestId为 key 的完成回执cancelAndReportNeverStarted在成功移除后检查它——已移除、无processedOn、无回执即证明未完成。代价processedOn标记的是已出队而非已启动——app 在 worker 收到作业之前就拥有它——所以一个被轮询后因断连成为孤儿的作业会报neverStarted: false虽然什么都没运行。这在部署期间最可能发生而恰恰是那时诚实的回答最有价值。这是刻意接受的取舍一次虚假的检查写入只花 Agent 一次查询一次虚假的可安全重试则让用户付出一次重复写入的代价。六、代码缓存action-runs/ _ 命名空间与回收6.1 为什么需要独立命名空间代码缓存code cache原本以flowVersionId 步骤名作为 key而 action run 两者都没有。若直接用DEFAULT_MCP_DATA加上固定步骤名step_1会踩进 代码缓存按 flowVersionId 命名空间的坑跨租户共享一个目录且在未开启AP_REUSE_SANDBOXtrue时 isolate 模式根本没有/root/codes挂载。execute-action.ts的resolveCodeStep通过actionRunCache.namespace推导一次命名空间并把它交给三处CodeArtifact构建产物provision.flowVersionId构建挂载EngineConstants.flowVersionId引擎读取的 key。命名空间格式为action-runs/platformId_sha256(sourceCode)内容哈希保证code-builder的目录内哈希检查永不失配重复片段可跳过bun installplatformId让目录可归属到具体平台设计依据见决策 Action-run code caches live in their own directory。action-runs/这一级目录是承重的它把 action-run 构建与 flow-version 构建分开platformId与flowVersionId都是 21 字符apId靠长度分不开并且是整个清理器sweeper的唯一作用域。6.2 路径安全assertSafeCodeNamespace命名空间带有/所以由assertSafeCodeNamespace守卫而不是assertSafePathSegment。嵌套无法对引擎隐藏在 fork 模式下AP_BASE_CODE_DIRECTORY是宿主机codes/原始路径、没有挂载间接层引擎从宿主机文件系统直接读codes/namespace/stepName/index.js它持有的命名空间必须真的包含action-runs/。assertSafeCodeNamespace按/切分、拒绝超过两段、并把每段交给未修改的assertSafePathSegment——这样用于 bind-mounthostPath的穿越规则只在一个地方声明。stepName与platformId仍是单段仍直接使用assertSafePathSegment不要放宽。两个合法段意味着a/b现在能通过过去会被拒——这是刻意的残余风险是命名空间落深一层而非逃逸。6.3 清理器的作用域连codes/根都够不到新的目录级隔离比它取代的ar_前缀更强前缀方案依赖ALPHABET见 id-generator.ts核心 utils 中ALPHABET不含_来保证词汇级不冲突——一旦ALPHABET加入_任何以ar_开头的apId约 1/238000规模化后几乎必然出现都会被误分类清理器会静默删除流程缓存现在一个 flow-version 缓存只有落在codes/action-runs/内部才可能被触及这需要ALPHABET加入-且ID_LENGTH从 21 变 11且ApId正则改变——三者同时发生才行sweep不再按名字过滤它只读自己的目录因此codes/根下的任何东西无论多老都存活。该行为由测试钉死测试种子一个 flow-version 目录、一个遗留ar_前缀目录和根下一个散落文件断言三者全部存活。6.4 内容寻址消灭了唯一的 GC所以清理不可省略当命名空间是常量DEFAULT_MCP_DATA.flowVersionId时所有 action run 编译进一个目录不同片段必然哈希未命中——而installFn通过rm -rf目录来重建这种破坏性重建本身就是回收上界为 O(1) 个目录。把路径改为按片段唯一修好了并发竞态和缺失挂载代价是那条分支不可达、GC 随之消失。actionRunCache.sweep见 action-run-cache.ts取而代之参数值说明ACTION_RUN_CACHE_TTL_MS2 小时codes/action-runs/下未触碰的子目录被移除ACTION_RUN_CACHE_MAX_DIRS200存活目录数超过上限后按最旧优先驱逐ACTION_RUN_CACHE_SWEEP_INTERVAL_MS30 分钟worker 本地的清理间隔ACTION_RUN_CACHE_FIRST_SWEEP_DELAY_MS60 秒首次清理的延迟ACTION_RUN_CACHE_ACTIVE_WINDOW_MS15 分钟活跃窗口比它新的目录豁免驱逐localExecutionCache.provision在沙箱启动前touch每个目录的 mtime以isActionRunNamespace为门控flow-version 目录零开销——这也是清理无竞态的原因当前正在 bind-mount 的目录必然在约 130 秒内被触碰过远在 TTL 之内。6.5 touch 落在清理器 re-stat 之后怎么办settlePendingRemovalremoveDir内部的 mtime 复查只能排除在 touch 之前开始的移除已经过了 re-stat 的那次会在一棵刚被沙箱接受为缓存命中的目录下删树导致运行因缺失index.js而死。removeDir在启动rm的同一个同步块里把 promise 发布到pendingRemovals在任何await之前——这正是两道检查完备的原因installCodeStep在 touch 之后调用actionRunCache.settlePendingRemoval等待进行中的移除并在确有移除时重建步骤。该守卫是进程本地的两个 worker 进程共享一个挂载时仍只依赖 mtime 复查。6.6 按目录数而非字节数回收两个安全属性计数上限把存活下限钉在 N无论目录多重字节预算会让存活数随体积浮动——0.5GB 一个目录、2GB 预算下只剩 4 个存活少于活跃集必然驱逐到活跃集内部但仅靠上限不够且容易自我说服mtime 是预置时间、运行期间≤120s不刷新慢 action run 会被每个晚于它启动的快运行压过最新的 200 个不是活着的 200 个。触达一个活跃目录只需在一个执行窗口内预置ACTION_RUN_CACHE_MAX_DIRS个不同片段——约 1.7 个/秒云端规模可达ACTION_RUN_CACHE_ACTIVE_WINDOW_MS15 分钟封住它驱逐跳过任何比该窗口新的目录所以当树全活跃时它宁可保持在容量之上直到老化——磁盘超量而不是运行死亡且 sweep 记录activeCount让永久阻塞的驱逐不会无声无息。运维不变量保持ACTION_RUN_CACHE_MAX_DIRSAP_WORKER_CONCURRENCY× 共享挂载的副本数参考拓扑是 25 对 200。低于该值窗口会阻塞所有驱逐、上限失效。字节预算为何被试验后移除见决策 000016。6.7 收敛而非协调worker 无法加锁N 个清理器共享一个./cache是稳态compose 是replicas: 5Helm 默认rollout把一块 RWO PVC 挂进每个副本而 worker 进程没有 Redis所以distributedLock和系统作业都不可用参见 Workers 的 Gotchas。安全靠结构每步幂等force: true、ENOENT 容忍、rm前立即复查 mtime驱逐从实时readdir重算目标而不是跨删除累积——两个同时运行的清理器会挑同一组最旧目录、只删一次谁也不会驱逐过界。不要添加跨删除累积的状态——被移除的字节记账正是这种东西同伴先删一个目录时它就会过度驱逐。定时抖动也没有必要并发 sweep 只是多花几次重复stat调用。6.8 历史布局目录故意泄漏裸sha256、mcp-flow-version-id和ar_前缀目录只存在于运行过引入它们的分支中间提交的机器上——这些布局从未进入main所以现场没有可迁移的东西也没写回收路径。清理器不回收它们做过名字嗅探的分支没有 TTL 和 mtime 复查而mcp-flow-version-id在main上仍是活常量DEFAULT_MCP_DATA.flowVersionId某天有代码在它下面预置时那个分支每 30 分钟就会rm -rf一次。在跑过这些提交的开发机上rm -rf cache/v12/codes就是清理方式。6.9 缓存命中必须证明产物存在cacheState的 memo 是模块级作用域、无失效 API命中时不碰磁盘。删掉步骤目录后 memo 仍报cacheHit: true什么都不重建引擎require缺失的index.js——该片段在该 worker 上一直失败到进程重启。进程内失效也不够参考 docker-compose.yml 给app和 5 个worker副本共享同一个./cachebind mount一个容器的清理器删除的目录可能已被另一个容器 memo 化。每次命中的那一次stat是让删除清理器、运维、重置卷可恢复的唯一机制——不要把code-builder里的compiledArtifactPresent检查简化掉。6.10 活跃窗口必须覆盖运行预算ACTION_RUN_CACHE_ACTIVE_WINDOW_MS15 分钟豁免新目录而 mtime 在预置时盖章、运行期间不刷新——所以比窗口更长的运行会在执行中老化出自己的豁免若树超过ACTION_RUN_CACHE_MAX_DIRS清理器会删除它脚下的目录导致运行因缺失index.js失败。预算还是硬 120 秒时不可达预算跟随AP_FLOW_TIMEOUT_SECONDS后即可达——而运维确实会把它设到 15 分钟以上。sweep接收activeWindowMsworker 传max(window, budget)。只有 action run 暴露此风险——清理器的整个作用域是codes/action-runs/flow-version 代码缓存不受影响。七、actionRunMode两种被禁用的仅流程行为EngineConstants.fromExecuteActionInput构造引擎常量时置入actionRunMode见 engine-constants.ts它禁用 piece-executor.ts 中的两种仅流程行为进度上报器变 no-op没有 flow run 可流式上报waitpoint 被拒绝assertActionRunCannotSuspend抛普通ErrorUSER 级步骤以FAILED结束而非INTERNAL_ERROR——该操作只在流程内有效是使用错误不是引擎 bug不应触发 oncall。7.1 createWaitpointHook 必须同步抛出createWaitpointHook必须从返回函数体同步抛出不能收进单个async闭包——这是 hook 拆成同步包装 submitWaitpoint的原因。废弃的pause()shimversioning.ts 的buildLegacyPauseHook保留至 2026-10-12做context.run.createWaitpoint({...}).catch(() process.exit(1))若拒绝以 rejected promise 到达那个.catch会挂上并杀死 worker 进程同步抛出则传播为 FAILED 步骤。仍在使用context.run.pause()的 piece 都会命中此路径。7.2 FLOW 作用域的 store 冲突已知、被接受context.store与context.files是 HTTP 支撑的服务只需要internalApiUrl、engineToken和flowId。没有真实流程时fromExecuteActionInput用DEFAULT_MCP_DATA哨兵flowId: mcp-flow-id替代createContextStore把 FLOW key 构造成prefix flow_ flowId / key。这不是租户破坏storeEntryController用引擎 token 钉住projectId条目永不跨项目。它是键冲突——项目内所有 action run 的 FLOW 作用域store.put()都落进同一个flow_mcp-flow-id/命名空间。PROJECT 作用域条目正确context.files忽略flowId上传干净地按项目隔离。修复意味着要么在actionRunMode拒绝 FLOW 作用域会破坏把存储当副作用的 piece要么给每次运行一个独立flowId让那些写入变成不可达垃圾——两者都不明显正确故维持现状。八、运维边界限流、路由、RPC 与 socket.ioEXECUTE_ACTION豁免项目并发限制器RATE_LIMIT_WORKER_JOB_TYPES只有[EXECUTE_FLOW]且rate-limiter-interceptor以AP_PROJECT_RATE_LIMITER_ENABLED默认false为门控。因此一个 action run 在整个预算期内占用一个WORKER_JOBS槽、没有项目级上限——通过 MCPap_run_action驱动的慢端点可以把实例上的所有槽占满这么久。把EXECUTE_ACTION加进列表不是一行改动shouldContinue收窄到ExecuteFlowJobData并读取environment而ExecuteActionJobData没有该字段。EXECUTE_ACTION不可按项目组路由PROJECT_GROUP_ROUTABLE_JOB_TYPES是{EXECUTE_FLOW, EXECUTE_WEBHOOK}所以即使项目有专属 workeraction run 也落在平台/共享队列上——与被取代的临时流程路径按EXECUTE_FLOW路由不同。当项目的专属 worker 是唯一能触达其网络的 worker 时这一点很关键。handler threw前缀不能丢createConfiguredPieceTools靠 grep 这个前缀在该动作失败与它可能已运行不要再调用之间选择——后者正是阻止 Agent 重跑一个看不到结果的副作用。socket.io 的 ack 超时是按调用的socket.timeout(ms)设置flags.timeout_registerAckCallback读它emit随后清掉flags——所以createRpcClient接受RpcTimeout number | ((method: string) number)就是完整修复且只在 worker 侧。曾构建并回滚过一种延迟变体立即 ack稍后通过按callId键控的rpc-result事件投递结果它一无所获、需要两端同时升级还引入了一个崩溃——结果 promise 在 ack 解析前没有处理器ack 窗口内的断连会以未处理拒绝抛出Node 默认的--unhandled-rejectionsthrow会杀死 worker在WORKER_AND_APP容器里经 docker-entrypoint.sh 把整个实例带崩。断连本来就有免费处理Socket.onclose调用_clearAcks用 socket has been disconnected 拒绝每个emitWithAckack。预算上限抬高史Issue #15127 声称 0.86 版就在AP_FLOW_TIMEOUT_SECONDS下运行 agent 工具历史不支持该说法。runPieceTool与配置 piece 工具路径在 #14613 引入之前 agent 的 piece 动作经 MCP 客户端进入actionRunService同样是 120s。0.88 增加的第二道更低上限worker 内 60s RPC 超时让新功能一出生就不可用。在 changelog 里写我们只是恢复了 0.86之前值得知道这些。九、版次与适用面所有版次Community、Enterprise、Cloud都支持 Action Runs。其中MCP 的ap_run_action属于CE社区版使用它的 Chat 工具属于EE企业版。十、关键文件索引关注点路径入口actionRunService.run()、结果映射packages/server/api/src/app/action-run/action-run.service.ts、action-run-outcome.ts同步等待机制submitAndWaitForResponse带可选的按调用方超时packages/server/api/src/app/workers/user-interaction-watcher.tsMCP 工具层executeActionRunAction/executeActionRunCode删除临时流程路径的重写packages/server/api/src/app/mcp/tools/flow-run-utils.ts引擎操作定义EngineOperationType.EXECUTE_ACTION、ExecuteActionOperationpackages/core/execution/src/lib/engine/engine-operation.ts作业数据定义WorkerJobType.EXECUTE_ACTION、ExecuteActionJobDatapackages/core/execution/src/lib/workers/job-data.ts共享单步原语packages/server/engine/src/lib/handler/action-run-step-runner.ts动作操作执行packages/server/engine/src/lib/operations/action.operation.ts引擎常量actionRunMode、fromExecuteActionInputpackages/server/engine/src/lib/handler/context/engine-constants.tsactionRunMode守卫packages/server/engine/src/lib/handler/piece-executor.tsworker 处理器execute-action.ts超时推导、沙箱超时 → TIMEOUTpackages/server/worker/src/lib/execute/jobs/execute-action.ts代码缓存namespace/isActionRunNamespace/touch/settlePendingRemoval/sweeppackages/server/sandbox/src/lib/cache/action-run-cache.ts缓存路径布局v15/布局权威ACTION_RUN_CODE_DIRpackages/server/sandbox/src/lib/cache/cache-paths.ts清理驱动30 分钟间隔packages/server/worker/src/lib/worker.tsstartCacheSweeper/stopCacheSweeper沙箱remainingTimeoutInSeconds、启动后截止时间钳制packages/server/sandbox/src/lib/sandbox.ts结语Action Runs 是 Activepieces 中同步执行单个步骤的基石它用userInteractionWatcher的请求/响应模型取代了临时流程的建-跑-轮询-删用单一的AP_FLOW_TIMEOUT_SECONDS端到端预算取代了多段超时叠加用neverStarted把超时拆成可安全重试与不可盲目重试两种语义并用action-runs/platformId_sha256的内容寻址命名空间与幂等清理器为这个无flowVersionId的执行路径补齐了代码缓存的安全边界。理解这些设计不仅能让 MCP 工具、Chat 工具与引擎侧的二次开发有的放矢也能在调优AP_FLOW_TIMEOUT_SECONDS、部署多 worker 拓扑或排查超时后到底跑没跑时直接对应当前仓库的实现证据。【免费下载链接】activepiecesAI Agents MCPs AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表