
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载在 Macro 的 Agent 会话体系中当一条 prompt 或 compact 在某个 turn 运行期间被提交时它不会立刻执行而是进入会话的服务端队列等待该 turn 结束。EditQueuedActionData正是描述在派发之前改写这条排队 prompt这一操作的请求类型别名——它把 HTTP 方法、路径模板、路径参数与请求体绑定在同一个类型里。本文以 SDK 类型文档为骨架结合agent_session路由层、agent_harness队列实现与 SDK 封装完整讲清这个类型每个字段的含义、服务端的真实行为边界以及可复制的调用方式。类型定义一个请求的完整形状类型文档给出的定义非常紧凑type EditQueuedActionData { body: EditQueuedActionRequest; path: { action_id: string; session_id: string }; query?: never; url: /agent-sessions/{session_id}/queue/{action_id}; }对应源文档EditQueuedActionData。这个形状是 orval 从 OpenAPI 规范生成的请求参数聚合类型它不是响应体也不是领域模型而是对一次 HTTP 调用的全部输入的静态描述。理解它的关键在于四个字段的职责划分。字段类型必填含义bodyEditQueuedActionRequest是请求体仅包含新的 prompt 文本path.action_idstring是该 action 被受理时获得的 IDpath.session_idstring是所属 agent session 的 IDquery?never否显式声明该接口没有查询参数never表示不可传任何 queryurl/agent-sessions/{session_id}/queue/{action_id}—路径模板两个占位符由path填充query?: never是一个值得注意的生成细节它不是可选参数为空而是类型层面禁止任何查询参数配合url字符串一起让调用方在编译期就锁死了请求形态。url字面量同时承担了这是 PUT 到资源地址的语义提示——虽然在类型里没有出现 HTTP 方法方法由 SDK 生成代码固定。两个路径参数在源文档中分别带有说明action_id是 ID the action was accepted under该 action 被受理时所用的 IDsession_id是 ID of the agent session。注意前者不是消息 ID也不是 turn ID只有当 action 真正派发后这个 ID 才会等价于折叠消息上的requestId——这一点在 SDK 的高层封装注释中有明确表述。请求体为什么不接受空白字符串EditQueuedActionData的body指向 EditQueuedActionRequest其定义为type EditQueuedActionRequest { /** The new raw prompt text, replacing the old wholesale. Never blank */ prompt: string; }前端生成的接口定义 editQueuedActionRequest.ts 进一步给出了约束export interface EditQueuedActionRequest { /** * The new raw prompt text, replacing the old wholesale. Never blank: a * prompt with nothing to say is a removal, and there is an endpoint for * that. * minLength 1 */ prompt: string; }三个语义要点整体替换replace wholesale不是追加、不是 diff 合并。传入的prompt会完整取代队列条目的原文本。不接受空白minLength 1只是第一道防线服务端还会对trim()之后的空串做二次判断直接拒绝。想清空就等于想删除文档注释直白地指出把 prompt 改成空内容在语义上是移除而移除有专门的接口DELETE同一路径对应 RemoveQueuedActionData。换而言之EditQueuedActionData只描述改写而不描述删除或插队。删除与插队分别由同一路径的DELETE和.../steer端点承担三者共享同一组路径参数。HTTP 契约方法、路径与状态码路由注册在 crates/agent_session/src/inbound/axum_router.rs.route( /{session_id}/queue, get(get_agent_session_queue_handler::R, Access, Auth), ) .route( /{session_id}/queue/{action_id}, put(edit_queued_action_handler::R, Access, Auth) .delete(remove_queued_action_handler::R, Access, Auth), ) .route( /{session_id}/queue/{action_id}/steer, post(steer_queued_action_handler::R, Access, Auth), )可以看到PUT /{session_id}/queue/{action_id}与DELETE共用同一个路径这正是 REST 语义下的修改与删除steer则在末尾多出一段子路径。OpenAPI 处理器 编辑接口定义 声明了响应集合落在类型层就是 EditQueuedActionResponses 与 EditQueuedActionErrors。编译期可见的错误集合为{ 401: string; 403: string; 404: string; 422: string; 500: string }。状态码来源错误触发条件204 No Content无编辑成功响应体为空401鉴权失败调用方未通过 Macro 授权校验403Forbidden无编辑权限或 Harness 调用方并非该会话的托管者或沙箱编码会话的非员工操作404QueuedControlNotFound该 action 已派发、已被移除或从未入队422EmptyQueuedPrompt/QueuedControlNotEditable新文本 trim 后为空或目标条目本身不携带文本如compact500其他领域/内部错误持久化失败等成功分支返回StatusCode::NO_CONTENT这解释了为什么类型是204: void——调用方拿不到更新后的对象只能依赖本地状态或重新拉取队列。一个值得记录的实现细节错误映射表把Archived映射为409 CONFLICT错误映射但409并未出现在EditQueuedActionErrors的生成结果中。从源码结构看这是生成类型与运行时行为之间的一处缝隙集成方在编写兜底分支时应当把未列举的 4xx/5xx 也纳入通用错误处理。服务端处理链路从 handler 到队列EditQueuedActionData的字段最终会在 handler 里被逐一对上。核心实现在 edit_queued_action_handlerpub async fn edit_queued_action_handlerR, Access, Auth( _access: AgentSessionAccessLevelExtractorEditAccessLevel, Access, Auth, State(state): StateAgentSessionControlStateR, Access, Auth, caller: MacroAuthorizationExtractorAuth, UserBotOrHarness, Path((session_id, action_id)): Path(Uuid, Uuid), Json(request): JsonEditQueuedActionRequest, ) - ResultStatusCode, AgentSessionApiError { let session_id AgentSessionId::new_from_uuid(session_id); ensure_harness_serves_session(caller.authorization, state.recipient.as_ref(), session_id) .await?; if request.prompt.trim().is_empty() { return Err(AgentSessionError::EmptyQueuedPrompt.into()); } ... }可以把这段代码与类型字段一一对应path.session_id、path.action_id→Path((session_id, action_id)): Path(Uuid, Uuid)。两个参数在类型层是string落到服务端被解析为Uuid因此非法 UUID 会在反序列化阶段就失败。body→Json(request): JsonEditQueuedActionRequest。query?: never→ handler 签名里根本没有Query...提取器协议层面无从携带查询参数。几个容易被忽略的守卫权限级别是编辑级AgentSessionAccessLevelExtractorEditAccessLevel, ...。读取队列只需要查看权限而改写需要编辑权限——能看不等于能改。Harness 只能操作自己服务的会话ensure_harness_serves_session 会比对recipient.session_harness(session_id)与调用方自身的harness_id。仅凭代表某个用户不足以驱动别人托管的会话。空白文本提前拒绝prompt.trim().is_empty()直接返回EmptyQueuedPrompt对应422。随后 handler 从授权信息里取出实际操作人并下发给接收方let actor caller.authorization.acting_user() .map(|user| user.macro_user_id.clone()); state.recipient.edit_queued_control( session_id, AgentActionId::from_uuid(action_id), request.prompt, actor, ).await?;注意actor是Option用户直连时是Some由 bot 代表无人操作时可能是None。这个字段会一路传到队列条目上并成为派发时被记录的操作人。端口层能力声明edit_queued_control在端口 trait 中的文档注释crates/agent_session/src/domain/ports.rs把行为边界写得很明确已派发 →QueuedControlNotFound不携带文本的排队动作 →QueuedControlNotEditableactor的判定与发送使用同一套门禁谁不能给会话发 prompt谁就不能改写它即将被喂进去的内容。这解释了为什么编辑接口复用EditAccessLevel编辑队列条目在权限模型上等价于发送 prompt而不是等价于修改一条历史消息。Harness 层命令化与持久化agent_harness侧把编辑包装成一条HarnessCommand::EditQueued命令crates/agent_harness/src/domain/service/lifecycle.rs确保同一会话的所有变更都在会话自己的命令通道上串行执行。真正的落地在 queue.rsHarnessCommand::EditQueued { action_id, prompt, actor } { queue_result( self.queues.edit_prompt(session_id, action_id, prompt, actor), session_id, )?; self.persist_or_rollback(session_id).await?; self.publish_queue(session_id).await; Ok(CommandOutcome::Completed) }三步走改内存 → 落盘失败则回滚重载→ 广播队列变更。persist_or_rollback的实现queue.rs在写库失败时会尝试reload_queue然后返回错误避免内存与持久层出现分叉publish_queue则发出AgentSessionQueueChanged事件让订阅端刷新队列快照。内存队列的编辑语义在 edit_prompt 中pub fn edit_prompt( self, session: AgentSessionId, action_id: AgentActionId, prompt: String, actor: OptionMacroUserIdStrstatic, ) - Result(), QueueError { let mut queue self.queues.get_mut(session).ok_or(QueueError::NotFound)?; let entry queue .iter_mut() .find(|entry| entry.action_id action_id) .ok_or(QueueError::NotFound)?; match mut entry.action { AgentAction::Prompt(action) { action.prompt prompt; entry.actor actor; Ok(()) } AgentAction::SetModel(_) | AgentAction::SetConfigOption(_) | AgentAction::Compact | AgentAction::Stop | AgentAction::RespondElicitation(_) | AgentAction::RespondToPermission(_) Err(QueueError::NotEditable), } }由此可以确认四条确定性行为原地更新条目保留位置与 ID文档注释原文为 The entry keeps its place and its id编辑不会改变它在队列中的顺序也不会生成新的action_id。只对prompt生效Compact、SetModel、SetConfigOption、Stop、权限回应、elicitation 回应全部返回NotEditable映射为422。这也解释了QueuedActionDto.kind的说明——只有占用 turn 的动作才会入队而其中只有prompt携带可改写文本。操作人被替换为编辑者entry.actor actor。注释写得很直接改写文本的人才是派发时被播报与记日志的人不能让别人替他背下他没写过的字。附件不受影响正文替换只作用于文本DTO 中attachments的注释明确写着 Kept through an edit, which replaces the text alone。门禁前置检查在命令真正执行前queue.rs 的分派前置检查还会做两件事HarnessCommand::Deliver(DeliverAction { actor, .. }) | HarnessCommand::EditQueued { actor, .. } | HarnessCommand::RemoveQueued { actor, .. } | HarnessCommand::SteerQueued { actor, .. } { let session self.sessions.get_session(session_id).await?; if session.is_archived { return Err(AgentSessionError::Archived(session_id).into()); } if AgentKind::of(session.bot_id) AgentKind::SandboxedCoder !actor.as_ref().is_some_and(is_macro_staff) { return Err(AgentSessionError::Forbidden.into()); } }即归档会话不可编辑沙箱编码类会话的队列改写仅限员工。注释解释了原因——被编辑的条目稍后会以原身份派发改写 Daytona 会话即将执行的内容与直接给它发 prompt 是同一级别的权限。另外队列本身有容量上限QUEUE_CAP 50queue.rs满队列返回ControlQueueFull同样映射为422。编辑已有条目不会增加条目数因此不触及该上限但在设计批量入队逻辑时应当把它考虑进去。SDK 调用从生成客户端到高层封装底层生成的editQueuedActionEditQueuedActionData直接对应生成 SDK 中的方法packages/sdk/generated/agent-harness/sdk.gen.tspublic editQueuedActionThrowOnError extends boolean false( options: OptionsEditQueuedActionData, ThrowOnError, ): RequestResultEditQueuedActionResponses, EditQueuedActionErrors, ThrowOnError { return (options.client ?? this.client).put EditQueuedActionResponses, EditQueuedActionErrors, ThrowOnError ({ url: /agent-sessions/{session_id}/queue/{action_id}, ...options, headers: { Content-Type: application/json, ...options.headers } }); }三点可从代码直接读出方法固定为put与url模板共同构成完整调用自动带上Content-Type: application/json返回类型把响应与错误集合都参数化配合ThrowOnError决定是抛异常还是把错误放进返回值。因为响应是204EditQueuedActionResponse的类型定义为EditQueuedActionResponses[keyof EditQueuedActionResponses]即voidEditQueuedActionError同理是错误集合的值联合类型。高层QueuedAction.edit()手写 path 参数容易出错SDK 提供了面向对象封装 queued-action.tsasync edit(prompt: string): Promisethis { unwrap( await this.client.agentHarness.editQueuedAction({ path: { session_id: this.sessionId, action_id: this.actionId }, body: { prompt }, }), ); this.dto { ...this.dto, prompt }; return this; }这段实现有两个设计点值得学习本地回填由于服务端返回204没有响应体可读封装直接把新prompt合入本地 DTO使action.prompt立即反映最新值。可链式返回this允许await action.edit(...).then(a a.steer())这类连续操作。QueuedAction的获取方式只有一种——从队列快照构造。类注释说明了原因没有单条 action 的GET只有队列列表接口因此它采用 wrap whatever DTO the caller already has 的模式与CrmComment包装列表记录同构而不是按 ID 懒加载。对应的高层入口是 AgentSession.queue()async queue(): PromiseQueuedAction[] { const { entries } unwrap( await this.client.agentHarness.getAgentSessionQueue({ path: { session_id: this.id }, }), ); return entries.map((entry) QueuedAction.from(this.client, this.id, entry)); }端到端示例排队、改写、插队与移除下面这段示例仅使用仓库中已确认存在的 SDK 入口展示一次完整的排队 → 编辑 → 插队 / 移除流程。它依赖MacroClient中挂载的agentHarness客户端与AgentSession实体。import type { MacroClient } from ./packages/sdk/src/utils/client; import type { AgentSession } from ./packages/sdk/src/entities/agent-sessions/agent-session; // session: 已加载的 AgentSession export async function editWhileRunning( client: MacroClient, session: AgentSession, ) { // 1. 在一个 turn 运行中提交会被排进服务端队列 const accepted await session.prompt(请把刚才的重构补上单元测试); // accepted.status 为 queued 时action_id 就是后续编辑所需的 id // 2. 读取队列快照包装成可操作句柄 const queued await session.queue(); // 3. 找到目标条目kind prompt 才可编辑 const target queued.find((a) a.actionId accepted.action_id); if (!target) return; // 已派发或已被移除 // 4. 整体替换文本附件保留位置与 id 不变 await target.edit(请把刚才的重构补上单元测试并覆盖空输入分支); // 5. 可选插队到队首并打断当前 turn或直接移除 // await target.steer(); // await target.remove(); }几个与类型定义强相关的注意点accepted.action_id来自控制接口的返回它是被受理时的 ID也是path.action_id的唯一正确来源不要用消息 ID 或本地生成的 UUID 替代。若target.kind compact编辑会稳定得到422 QueuedControlNotEditable应在客户端提前分支处理。若条目已经派发find会失败服务端也会返回404。这属于正常的竞态不应视为异常路径。与相邻操作的对比编辑、移除、插队同一路径族下的三个操作在类型与语义上形成互补关系操作类型别名方法/路径是否携带 body关键效果编辑EditQueuedActionDataPUT /agent-sessions/{session_id}/queue/{action_id}是{ prompt }原地替换文本保留位置与 ID移除RemoveQueuedActionDataDELETE同路径否body?: never派发前彻底删除插队SteerQueuedActionDataPOST .../{action_id}/steer否body?: never移到队首并在当前 turn 结束时优先派发三者的query都是neverpath结构完全一致只有body与url尾部不同。这种严格的对称性让代码生成与前端封装都能保持统一。steer的额外语义来自 端口文档与路由注释它会把条目前移并在有 turn 在飞时取消该 turn使这条动作在当前 turn 结束后立刻派发。相比之下edit绝不影响调度顺序——它只改字不排队。测试证据行为不是推测队列编辑的核心承诺在集成测试中被逐条钉死。crates/agent_harness/src/domain/service/test.rs 中的queued_prompts_are_editable_and_removable_until_dispatch覆盖了完整生命周期service.edit_queued_control( id, second.action_id, second, rewritten.to_owned(), Some(staff_sender()), ).await .expect(a waiting prompt is editable); assert_eq!( rewritten.actor.as_ref(), Some(staff_sender()), an edit is attributed to the editor, not the original queuer );断言the edit lands because dispatch is where the text is read进一步说明了一个重要机制文本是在派发时刻才被读取的所以排队期间的编辑一定生效不存在写进某个快照后再也改不动的问题。测试后半段验证了派发即消失let error service .remove_queued_control(id, second.action_id, Some(sender())) .await .expect_err(a dispatched prompt is not removable); assert!(matches!(error, AgentSessionError::QueuedControlNotFound));对已派发条目做移除或编辑都得到QueuedControlNotFound最终在 HTTP 层表现为404——这与EditQueuedActionErrors中的404说明 Already dispatched or never queued 完全对应。工程实践建议结合类型定义与服务端实现落地时可以遵循以下判断不要用编辑实现清空prompt.trim()为空会被拒为422正确做法是调用remove()。类型层已经通过注释把这条规则写进了文档。编辑前先确认kindcompact与各类控制动作不可编辑。用QueuedActionDto.kind或高层封装的kindgetter 做前置判断可以避免无意义的往返。404是常态而非故障turn 结束时队列会自然派发前端拿着旧快照做编辑必然可能撞上404。建议把它当作条目已达标的幂等信号刷新队列而不是弹错误。失败后重新同步服务端在持久化失败时会回滚内存并从存储重载此时客户端应当重新session.queue()获取权威快照而不是相信本地的乐观更新。权限口径按发送对待编辑使用EditAccessLevel并且对沙箱编码会话额外要求员工身份。为外部提供能力时不要给出比发送 prompt 更宽的授权。注意409未在类型中列举归档会话会触发409 CONFLICT但生成错误集合里没有它。兜底分支应覆盖未知 4xx/5xx。小结EditQueuedActionData看似只是一个自动生成的类型别名但它把三层事实压缩在四个字段里url与path锁定了对某个会话队列中某条动作的原地修改body明确了整体替换、不可为空、只管文本query?: never则宣告这个接口不接受任何过滤或开关参数。服务端链路进一步验证了这些语义——EditAccessLevel权限、Harness 托管校验、空白拒绝、只对prompt生效、操作人易主、附件保留、位置与 ID 不变、派发即失联404。相关阅读可从类型文档本身出发配合 EditQueuedActionRequest、QueuedActionDto 与 QueuedAction 类文档 形成完整认知再对照 axum_router.rs、queue.rs 与 queued-action.ts 查看每一层实现。赞分享后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载相关推荐Macro Agent Harness SDK 类型解析GetSessionReviewResponse 与 Session Review 接口Macro Agent Harness SDK 类型解析GetSessionReviewResponse 与 Session Review 接口 本文围绕 M后端前端协同办公即时通讯企业应用CRMAI Agent人工智能Macro Agent Harness 队列编辑接口详解EditQueuedActionResponses 的 204 语义、契约与实现链路Macro Agent Harness 队列编辑接口详解EditQueuedActionResponses 的 204 语义、契约与实现链路 在 Macro后端前端协同办公即时通讯企业应用CRMAI Agent人工智能Macro Agent Harness SDK 类型解析GetAgentSessionQueueResponse 与会话动作队列 APIMacro Agent Harness SDK 类型解析GetAgentSessionQueueResponse 与会话动作队列 API GetAgentSe后端前端协同办公即时通讯企业应用CRMAI Agent人工智能上一篇WarcraftHelper终极指南三步解决魔兽争霸3所有兼容性问题下一篇城通网盘解析工具终极指南3步获取高速直连下载链接创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。