feat: execute omx dispatches via local-agent
This commit is contained in:
@@ -60,7 +60,7 @@
|
|||||||
- 当前 `claw-code` 已以最小 `ClawBackendAdapter` 形式接入执行底座,但默认关闭;只有在显式配置 `BOSS_CLAW_*` 且可用性探测通过时,`master-agent` 当前对话里才会出现并允许选择 `claw-runtime`
|
- 当前 `claw-code` 已以最小 `ClawBackendAdapter` 形式接入执行底座,但默认关闭;只有在显式配置 `BOSS_CLAW_*` 且可用性探测通过时,`master-agent` 当前对话里才会出现并允许选择 `claw-runtime`
|
||||||
- 如果历史上已经保存过 `backendOverride=claw-runtime`,但当前 `Claw Runtime` 不可用,运行时会自动回退到默认后端,并在前台显示明确原因
|
- 如果历史上已经保存过 `backendOverride=claw-runtime`,但当前 `Claw Runtime` 不可用,运行时会自动回退到默认后端,并在前台显示明确原因
|
||||||
- 当前 `oh-my-codex` 已以最小 `OmxTeamBackendAdapter` 形式接入执行底座,但默认关闭;当前已经接到 Web 群聊详情页 / 原生群资料页的编排后端选择卡,可在 `Boss Native` 与 `OMX Team` 间切换,OMX 不可用时会自动回退到默认后端并明确提示原因
|
- 当前 `oh-my-codex` 已以最小 `OmxTeamBackendAdapter` 形式接入执行底座,但默认关闭;当前已经接到 Web 群聊详情页 / 原生群资料页的编排后端选择卡,可在 `Boss Native` 与 `OMX Team` 间切换,OMX 不可用时会自动回退到默认后端并明确提示原因
|
||||||
- 当前仓库已自带一个本地 OMX smoke runtime:`scripts/omx-team-smoke.mjs`。在还没有真实 `oh-my-codex` 可执行文件时,可以先用它验证 `OmxTeamBackendAdapter -> selector -> fallback` 这条链
|
- 当前仓库已自带一个本地 OMX smoke runtime:`scripts/omx-team-smoke.mjs`。在还没有真实 `oh-my-codex` 可执行文件时,可以先用它验证 `OmxTeamBackendAdapter -> selector -> dispatch_execution -> 回写群聊账本` 这条链
|
||||||
- 当前仓库已自带一个本地 smoke runtime:`scripts/claw-runtime-smoke.mjs`。在还没有真实 `claw-code` 可执行文件时,可以先用它验证 `ClawBackendAdapter -> backendOverride -> 异步回流` 整条链
|
- 当前仓库已自带一个本地 smoke runtime:`scripts/claw-runtime-smoke.mjs`。在还没有真实 `claw-code` 可执行文件时,可以先用它验证 `ClawBackendAdapter -> backendOverride -> 异步回流` 整条链
|
||||||
- `GET http://127.0.0.1:4317/api/v1/skills` 正常,已返回本机扫描到的 Codex Skill
|
- `GET http://127.0.0.1:4317/api/v1/skills` 正常,已返回本机扫描到的 Codex Skill
|
||||||
- `POST http://127.0.0.1:4317/api/v1/heartbeat` 正常,且会顺带触发 `thread-context` 上报
|
- `POST http://127.0.0.1:4317/api/v1/heartbeat` 正常,且会顺带触发 `thread-context` 上报
|
||||||
@@ -101,7 +101,7 @@ Android APK:
|
|||||||
- 已生成 Android debug APK:`android/app/build/outputs/apk/debug/app-debug.apk`
|
- 已生成 Android debug APK:`android/app/build/outputs/apk/debug/app-debug.apk`
|
||||||
- 已生成 Android signed release APK:`android/app/build/outputs/apk/release/app-release.apk`
|
- 已生成 Android signed release APK:`android/app/build/outputs/apk/release/app-release.apk`
|
||||||
- `npm run apk:release` 还会额外产出带版本号的文件:`android/app/build/outputs/apk/release/boss-android-v{versionName}-release.apk`
|
- `npm run apk:release` 还会额外产出带版本号的文件:`android/app/build/outputs/apk/release/boss-android-v{versionName}-release.apk`
|
||||||
- 当前最新 release 构建版本:`2.5.9`(`versionCode=22`)
|
- 当前最新 release 构建版本:`2.5.10`(`versionCode=23`)
|
||||||
- 当前 APK 已切到原生 Android 客户端:`MainActivity + BossApiClient + 原生 XML 布局`
|
- 当前 APK 已切到原生 Android 客户端:`MainActivity + BossApiClient + 原生 XML 布局`
|
||||||
- 当前原生活动页已经覆盖:会话首页、项目详情、项目目标、版本记录、会话信息、群资料、发起群聊、消息转发、线程详情、设备详情、添加设备、账号与安全、设置、AI 账号、主 Agent 提示词 / 记忆、技能、运维中心、关于
|
- 当前原生活动页已经覆盖:会话首页、项目详情、项目目标、版本记录、会话信息、群资料、发起群聊、消息转发、线程详情、设备详情、添加设备、账号与安全、设置、AI 账号、主 Agent 提示词 / 记忆、技能、运维中心、关于
|
||||||
- 当前原生一级体验已回退到微信式交互:`会话 / 设备 / 我的` 固定底部 tab,会话首页是简单聊天列表,`主 Agent / 审计对话` 以普通置顶会话样式排在最前;项目详情页是聊天优先,只保留 `项目目标 / 版本记录` 两个轻入口
|
- 当前原生一级体验已回退到微信式交互:`会话 / 设备 / 我的` 固定底部 tab,会话首页是简单聊天列表,`主 Agent / 审计对话` 以普通置顶会话样式排在最前;项目详情页是聊天优先,只保留 `项目目标 / 版本记录` 两个轻入口
|
||||||
@@ -214,7 +214,8 @@ device-agent 当前职责:
|
|||||||
- 将主 Agent 执行结果回写到云端 `/api/v1/master-agent/tasks/[taskId]/complete`
|
- 将主 Agent 执行结果回写到云端 `/api/v1/master-agent/tasks/[taskId]/complete`
|
||||||
- 对普通单线程会话,认领到的 `conversation_reply` 任务会直接恢复到目标 Codex 线程,并把线程原始回复回写到对应聊天窗口
|
- 对普通单线程会话,认领到的 `conversation_reply` 任务会直接恢复到目标 Codex 线程,并把线程原始回复回写到对应聊天窗口
|
||||||
- 对群聊线程分发任务,认领到的 `dispatch_execution` 任务会把原始线程结果和主 Agent 汇总一起回写到群聊消息账本
|
- 对群聊线程分发任务,认领到的 `dispatch_execution` 任务会把原始线程结果和主 Agent 汇总一起回写到群聊消息账本
|
||||||
- `local-agent` 对 `conversation_reply / dispatch_execution` 当前会优先使用 `codex exec resume <targetCodexThreadRef>`,只有缺失真实线程引用时才退回 `--ephemeral`
|
- `local-agent` 对 `conversation_reply` 当前会优先使用 `codex exec resume <targetCodexThreadRef>`,只有缺失真实线程引用时才退回 `--ephemeral`
|
||||||
|
- `local-agent` 对 `dispatch_execution` 当前会按 `orchestrationBackendId` 分流:默认继续走 `codex exec resume`;当任务显式选择 `omx-team` 且本机 `omxEnabled + omxCommand/omxArgs` 可用时,会改走 `OMX Team Runtime` JSON 协议执行
|
||||||
- `local-agent` 当前的任务完成回写已通过 `RemoteRuntimeAdapter` 标准化,`conversation_reply / dispatch_execution` 的完成结果都会先归一到统一远程执行结果结构,再进入主 Agent 完成路由
|
- `local-agent` 当前的任务完成回写已通过 `RemoteRuntimeAdapter` 标准化,`conversation_reply / dispatch_execution` 的完成结果都会先归一到统一远程执行结果结构,再进入主 Agent 完成路由
|
||||||
- `local-agent` 当前会先启动本地 `4317` 健康监听,再异步执行首次 heartbeat 和 task poll,避免控制面短暂阻塞时本地健康检查一起挂死
|
- `local-agent` 当前会先启动本地 `4317` 健康监听,再异步执行首次 heartbeat 和 task poll,避免控制面短暂阻塞时本地健康检查一起挂死
|
||||||
- Codex 项目/线程扫描当前已搬到 worker 线程执行,避免 `.codex/logs_1.sqlite` 和 `state_5.sqlite` 的同步扫描阻塞主线程 HTTP 响应
|
- Codex 项目/线程扫描当前已搬到 worker 线程执行,避免 `.codex/logs_1.sqlite` 和 `state_5.sqlite` 的同步扫描阻塞主线程 HTTP 响应
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ android {
|
|||||||
applicationId "com.hyzq.boss"
|
applicationId "com.hyzq.boss"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 22
|
versionCode 23
|
||||||
versionName "2.5.9"
|
versionName "2.5.10"
|
||||||
buildConfigField "String", "BOSS_API_BASE_URL", "\"https://boss.hyzq.net\""
|
buildConfigField "String", "BOSS_API_BASE_URL", "\"https://boss.hyzq.net\""
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
- 当前额外职责:向云端上报 `thread-context`
|
- 当前额外职责:向云端上报 `thread-context`
|
||||||
- 当前新增职责:递归扫描本机 `~/.codex/skills` 并同步到设备 Skill 接口
|
- 当前新增职责:递归扫描本机 `~/.codex/skills` 并同步到设备 Skill 接口
|
||||||
- 当前完成回写:`conversation_reply / dispatch_execution` 会先标准化成统一远程执行结果,再调用 `/api/v1/master-agent/tasks/[taskId]/complete`
|
- 当前完成回写:`conversation_reply / dispatch_execution` 会先标准化成统一远程执行结果,再调用 `/api/v1/master-agent/tasks/[taskId]/complete`
|
||||||
|
- 当前 `dispatch_execution` 会按 `orchestrationBackendId` 分流:默认走 `codex exec resume`,显式选择 `omx-team` 且本机配置可用时改走 `OMX Team Runtime` JSON 协议
|
||||||
|
|
||||||
### 1.4 Caddy
|
### 1.4 Caddy
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@
|
|||||||
- 如果历史 `backendOverride=claw-runtime` 当前不可用,运行时会自动回退到默认后端,并把原因回给前台
|
- 如果历史 `backendOverride=claw-runtime` 当前不可用,运行时会自动回退到默认后端,并把原因回给前台
|
||||||
- 当前仓库自带 `scripts/claw-runtime-smoke.mjs` 作为兼容 JSON 协议的 smoke runtime,可用于本地和服务器验证 `ClawBackendAdapter`
|
- 当前仓库自带 `scripts/claw-runtime-smoke.mjs` 作为兼容 JSON 协议的 smoke runtime,可用于本地和服务器验证 `ClawBackendAdapter`
|
||||||
- 当前已最小接入 `OmxTeamBackendAdapter`,但默认关闭;Web 群聊详情页和原生群资料页已经可以在 `Boss Native` 与 `OMX Team` 间切换编排后端,OMX 不可用时会自动回退到默认后端并返回明确原因
|
- 当前已最小接入 `OmxTeamBackendAdapter`,但默认关闭;Web 群聊详情页和原生群资料页已经可以在 `Boss Native` 与 `OMX Team` 间切换编排后端,OMX 不可用时会自动回退到默认后端并返回明确原因
|
||||||
- 当前仓库自带 `scripts/omx-team-smoke.mjs`,可用于本地和服务器验证 `OmxTeamBackendAdapter`
|
- 当前仓库自带 `scripts/omx-team-smoke.mjs`,可用于本地和服务器验证 `OmxTeamBackendAdapter` 的 `dispatch_execution` JSON 协议
|
||||||
|
|
||||||
### 3.2 认证相关
|
### 3.2 认证相关
|
||||||
|
|
||||||
@@ -1061,7 +1062,8 @@
|
|||||||
|
|
||||||
- local-agent 会周期性请求 `POST /api/v1/master-agent/tasks/claim`
|
- local-agent 会周期性请求 `POST /api/v1/master-agent/tasks/claim`
|
||||||
- 认领到任务后会执行本机 `codex exec`
|
- 认领到任务后会执行本机 `codex exec`
|
||||||
- `conversation_reply / dispatch_execution` 当前会优先走 `codex exec resume <targetCodexThreadRef>`,把任务恢复到真实 Codex 线程;只有缺失真实线程引用时才退回 `--ephemeral`
|
- `conversation_reply` 当前会优先走 `codex exec resume <targetCodexThreadRef>`,把任务恢复到真实 Codex 线程;只有缺失真实线程引用时才退回 `--ephemeral`
|
||||||
|
- `dispatch_execution` 当前默认也走 `codex exec resume`,但当任务显式选择 `omx-team` 且本机 `omxEnabled + omxCommand/omxArgs` 可用时,会改走 `OMX Team Runtime` JSON 协议
|
||||||
- 执行完成后会调用 `POST /api/v1/master-agent/tasks/[taskId]/complete`
|
- 执行完成后会调用 `POST /api/v1/master-agent/tasks/[taskId]/complete`
|
||||||
- 对群聊下发链路,认领到的 `dispatch_execution` 任务会带 `dispatchExecutionId / targetProjectId / targetThreadId`
|
- 对群聊下发链路,认领到的 `dispatch_execution` 任务会带 `dispatchExecutionId / targetProjectId / targetThreadId`
|
||||||
- 对普通单线程聊天,认领到的 `conversation_reply` 任务会带 `targetProjectId / targetThreadId / targetCodexThreadRef`
|
- 对普通单线程聊天,认领到的 `conversation_reply` 任务会带 `targetProjectId / targetThreadId / targetCodexThreadRef`
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
- 如果历史上已经保存过 `backendOverride=claw-runtime`,但当前 `Claw Runtime` 不可用,运行时会自动回退到默认后端,并在 Web/Android 前台给出明确原因
|
- 如果历史上已经保存过 `backendOverride=claw-runtime`,但当前 `Claw Runtime` 不可用,运行时会自动回退到默认后端,并在 Web/Android 前台给出明确原因
|
||||||
- 当前仓库已自带 `scripts/claw-runtime-smoke.mjs` 作为本地 smoke runtime;在没有真实 `claw-code` 可执行文件时,可先用 `BOSS_CLAW_COMMAND=node` 与 `BOSS_CLAW_ARGS=scripts/claw-runtime-smoke.mjs` 验证整条链
|
- 当前仓库已自带 `scripts/claw-runtime-smoke.mjs` 作为本地 smoke runtime;在没有真实 `claw-code` 可执行文件时,可先用 `BOSS_CLAW_COMMAND=node` 与 `BOSS_CLAW_ARGS=scripts/claw-runtime-smoke.mjs` 验证整条链
|
||||||
- 当前 `oh-my-codex` 已以最小 `OmxTeamBackendAdapter` 形式接入执行底座,但默认关闭;当前已经接到 Web 群聊详情页 / 原生群资料页的编排后端选择卡,可在 `Boss Native` 与 `OMX Team` 间切换,OMX 不可用时会自动回退到默认后端并明确提示原因
|
- 当前 `oh-my-codex` 已以最小 `OmxTeamBackendAdapter` 形式接入执行底座,但默认关闭;当前已经接到 Web 群聊详情页 / 原生群资料页的编排后端选择卡,可在 `Boss Native` 与 `OMX Team` 间切换,OMX 不可用时会自动回退到默认后端并明确提示原因
|
||||||
- 当前仓库已自带 `scripts/omx-team-smoke.mjs` 作为本地 OMX smoke runtime;在没有真实 `oh-my-codex` 可执行文件时,可先用 `BOSS_OMX_COMMAND=node` 与 `BOSS_OMX_ARGS=scripts/omx-team-smoke.mjs` 验证编排后端骨架
|
- 当前仓库已自带 `scripts/omx-team-smoke.mjs` 作为本地 OMX smoke runtime;在没有真实 `oh-my-codex` 可执行文件时,可先用 `BOSS_OMX_COMMAND=node` 与 `BOSS_OMX_ARGS=scripts/omx-team-smoke.mjs` 验证 `dispatch_execution` 的真实执行 contract
|
||||||
|
|
||||||
本地已知运行方式:
|
本地已知运行方式:
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ cd /Users/kris/code/boss
|
|||||||
- 当前已生成 Android debug APK:`android/app/build/outputs/apk/debug/app-debug.apk`
|
- 当前已生成 Android debug APK:`android/app/build/outputs/apk/debug/app-debug.apk`
|
||||||
- 当前已生成 Android signed release APK:`android/app/build/outputs/apk/release/app-release.apk`
|
- 当前已生成 Android signed release APK:`android/app/build/outputs/apk/release/app-release.apk`
|
||||||
- 当前 release 构建还会额外生成带版本号的 APK:`android/app/build/outputs/apk/release/boss-android-v{versionName}-release.apk`
|
- 当前 release 构建还会额外生成带版本号的 APK:`android/app/build/outputs/apk/release/boss-android-v{versionName}-release.apk`
|
||||||
- 当前最新 release 构建版本:`2.5.9`(`versionCode=22`)
|
- 当前最新 release 构建版本:`2.5.10`(`versionCode=23`)
|
||||||
- 当前 release keystore 位于本机 `android/keystores/boss-release.keystore`,签名参数位于 `android/signing/release-signing.properties`
|
- 当前 release keystore 位于本机 `android/keystores/boss-release.keystore`,签名参数位于 `android/signing/release-signing.properties`
|
||||||
- `2.0.1` 已在本机连接的华为真机上复核通过,修复了 `Theme.SplashScreen` 导致的 `AppCompatActivity` 启动闪退
|
- `2.0.1` 已在本机连接的华为真机上复核通过,修复了 `Theme.SplashScreen` 导致的 `AppCompatActivity` 启动闪退
|
||||||
- `2.1.0` 已把 Web 一级页和主要二级页全部补成原生活动页:`MainActivity / ProjectDetailActivity / ProjectGoalsActivity / ProjectVersionsActivity / ProjectForwardActivity / ThreadDetailActivity / DeviceDetailActivity / DeviceEnrollmentActivity / SkillInventoryActivity / SecurityActivity / SettingsActivity / AiAccountsActivity / OpsCenterActivity / AboutActivity`
|
- `2.1.0` 已把 Web 一级页和主要二级页全部补成原生活动页:`MainActivity / ProjectDetailActivity / ProjectGoalsActivity / ProjectVersionsActivity / ProjectForwardActivity / ThreadDetailActivity / DeviceDetailActivity / DeviceEnrollmentActivity / SkillInventoryActivity / SecurityActivity / SettingsActivity / AiAccountsActivity / OpsCenterActivity / AboutActivity`
|
||||||
@@ -185,11 +185,12 @@ cd /Users/kris/code/boss
|
|||||||
- `2.5.4` 已把 `设置 / 账号与安全 / AI 账号 / 技能 / 运维与修复` 的顶部说明从绿色 `soft panel` 降成轻量列表说明,和会话/设备页统一成同一套微信式产品语言
|
- `2.5.4` 已把 `设置 / 账号与安全 / AI 账号 / 技能 / 运维与修复` 的顶部说明从绿色 `soft panel` 降成轻量列表说明,和会话/设备页统一成同一套微信式产品语言
|
||||||
- `2.5.5` 已补上群资料页“修复群成员”主链:历史脏群现在会明确提示失效成员,并允许重新选择真实线程成员写回群资料
|
- `2.5.5` 已补上群资料页“修复群成员”主链:历史脏群现在会明确提示失效成员,并允许重新选择真实线程成员写回群资料
|
||||||
- `2.5.5` 已给 `approval_required` 群聊补齐“确认 / 拒绝”两条审批动作;拒绝后会把群审批状态写成 `rejected`,并追加系统提示,不再继续下发到线程
|
- `2.5.5` 已给 `approval_required` 群聊补齐“确认 / 拒绝”两条审批动作;拒绝后会把群审批状态写成 `rejected`,并追加系统提示,不再继续下发到线程
|
||||||
- `2.5.9` 对应这一轮的执行底座收口:`ClawBackendAdapter` 仍默认关闭,但可显式选择并在不可用时自动回退;`OmxTeamBackendAdapter` 已接到 Web 群聊详情页 / 原生群资料页的编排后端选择卡,可在 `Boss Native` 与 `OMX Team` 间切换
|
- `2.5.10` 对应这一轮的执行底座收口:`ClawBackendAdapter` 仍默认关闭,但可显式选择并在不可用时自动回退;`OmxTeamBackendAdapter` 已不只是设置项,`dispatch_execution` 在显式选择 `omx-team` 且本机配置可用时会真实走 `OMX Team Runtime` JSON 协议执行
|
||||||
- 当前附件分析任务已带受控 `task token` 下载链接和文本摘录:本地开发环境会跟随请求 origin 生成链接,生产环境默认走 `https://boss.hyzq.net`
|
- 当前附件分析任务已带受控 `task token` 下载链接和文本摘录:本地开发环境会跟随请求 origin 生成链接,生产环境默认走 `https://boss.hyzq.net`
|
||||||
- `2.5.x` 当前已补上会话首页独立建群入口:可以不从单线程聊天内部出发,直接在会话首页右上角 `+` 建立新群聊;同时已把多个原生自定义 top bar 页面统一纳入状态栏安全区处理
|
- `2.5.x` 当前已补上会话首页独立建群入口:可以不从单线程聊天内部出发,直接在会话首页右上角 `+` 建立新群聊;同时已把多个原生自定义 top bar 页面统一纳入状态栏安全区处理
|
||||||
- 当前 `local-agent` 已能回写带 `dispatchExecutionId / targetProjectId / targetThreadId / rawThreadReply` 的任务完成载荷,群聊分发执行结果不再只停留在主 Agent 队列
|
- 当前 `local-agent` 已能回写带 `dispatchExecutionId / targetProjectId / targetThreadId / rawThreadReply` 的任务完成载荷,群聊分发执行结果不再只停留在主 Agent 队列
|
||||||
- 当前 `local-agent` 对 `conversation_reply / dispatch_execution` 任务会优先使用 `codex exec resume <targetCodexThreadRef>`,只有缺失真实线程引用时才退回 `--ephemeral`
|
- 当前 `local-agent` 对 `conversation_reply` 任务会优先使用 `codex exec resume <targetCodexThreadRef>`,只有缺失真实线程引用时才退回 `--ephemeral`
|
||||||
|
- 当前 `local-agent` 对 `dispatch_execution` 任务会按 `orchestrationBackendId` 分流:默认走 `codex exec resume`;当任务显式选择 `omx-team` 且本机 `omxEnabled + omxCommand/omxArgs` 可用时,会改走 `OMX Team Runtime` JSON 协议执行并回写 `rawThreadReply / replyBody`
|
||||||
- 当前历史脏群如果不再包含真实线程成员,群聊消息不会再表现成“无响应”;服务端会在群内追加明确 `system_notice`,提示先重新添加线程成员
|
- 当前历史脏群如果不再包含真实线程成员,群聊消息不会再表现成“无响应”;服务端会在群内追加明确 `system_notice`,提示先重新添加线程成员
|
||||||
- 当前设备导入决议已经升级成真正通过 `local-agent -> codex exec -> /complete` 回写的主 Agent 决议链;Web 和 Android 前台都会在 `pending_resolution` 阶段显示审核任务状态,并在任务完成后自动刷新出正式导入建议
|
- 当前设备导入决议已经升级成真正通过 `local-agent -> codex exec -> /complete` 回写的主 Agent 决议链;Web 和 Android 前台都会在 `pending_resolution` 阶段显示审核任务状态,并在任务完成后自动刷新出正式导入建议
|
||||||
- 当前 `local-agent` 已改成先启动本地 `4317` 健康监听,再异步跑首次 heartbeat 和 task poll,避免控制面短时阻塞时本地健康探针不可用
|
- 当前 `local-agent` 已改成先启动本地 `4317` 健康监听,再异步跑首次 heartbeat 和 task poll,避免控制面短时阻塞时本地健康探针不可用
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
"masterAgentWorkdir": "/Users/kris/code/boss",
|
"masterAgentWorkdir": "/Users/kris/code/boss",
|
||||||
"masterAgentSandbox": "workspace-write",
|
"masterAgentSandbox": "workspace-write",
|
||||||
"masterAgentModel": "gpt-5.4",
|
"masterAgentModel": "gpt-5.4",
|
||||||
|
"omxEnabled": false,
|
||||||
|
"omxCommand": "",
|
||||||
|
"omxArgs": [],
|
||||||
|
"omxWorkdir": "/Users/kris/code/boss",
|
||||||
|
"omxTimeoutMs": 45000,
|
||||||
"deviceId": "mac-studio",
|
"deviceId": "mac-studio",
|
||||||
"workerId": "worker-mac-ui",
|
"workerId": "worker-mac-ui",
|
||||||
"pairingCode": "",
|
"pairingCode": "",
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
"masterAgentWorkdir": "/Users/kris/code/boss",
|
"masterAgentWorkdir": "/Users/kris/code/boss",
|
||||||
"masterAgentSandbox": "workspace-write",
|
"masterAgentSandbox": "workspace-write",
|
||||||
"masterAgentModel": "gpt-5.4",
|
"masterAgentModel": "gpt-5.4",
|
||||||
|
"omxEnabled": false,
|
||||||
|
"omxCommand": "",
|
||||||
|
"omxArgs": [],
|
||||||
|
"omxWorkdir": "/Users/kris/code/boss",
|
||||||
|
"omxTimeoutMs": 45000,
|
||||||
"deviceId": "mac-studio",
|
"deviceId": "mac-studio",
|
||||||
"workerId": "worker-mac-ui",
|
"workerId": "worker-mac-ui",
|
||||||
"pairingCode": "",
|
"pairingCode": "",
|
||||||
|
|||||||
210
local-agent/omx-team-task-runner.mjs
Normal file
210
local-agent/omx-team-task-runner.mjs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
function parseBoolean(value) {
|
||||||
|
return String(value || "").trim().toLowerCase() === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(value) {
|
||||||
|
return String(value || "")
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTimeoutMs(value) {
|
||||||
|
const parsed = Number.parseInt(String(value || ""), 10);
|
||||||
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 45000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCommandArgs(command, args, cwd) {
|
||||||
|
const runtimeName = path.basename(command || "").toLowerCase();
|
||||||
|
const scriptRuntimes = new Set([
|
||||||
|
"node",
|
||||||
|
"node.exe",
|
||||||
|
"tsx",
|
||||||
|
"tsx.cmd",
|
||||||
|
"bun",
|
||||||
|
"bun.exe",
|
||||||
|
"deno",
|
||||||
|
"deno.exe",
|
||||||
|
]);
|
||||||
|
if (!scriptRuntimes.has(runtimeName) || args.length === 0) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
const [first, ...rest] = args;
|
||||||
|
if (!first || first.startsWith("-")) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
const resolvedFirst = path.isAbsolute(first)
|
||||||
|
? first
|
||||||
|
: path.resolve(cwd || process.cwd(), first);
|
||||||
|
return [resolvedFirst, ...rest];
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickConfigValue(config, key, fallback) {
|
||||||
|
if (config && config[key] !== undefined && config[key] !== null && `${config[key]}`.trim() !== "") {
|
||||||
|
return config[key];
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOmxTeamTaskRunnerConfig(env = process.env, config = {}) {
|
||||||
|
const enabled = parseBoolean(pickConfigValue(config, "omxEnabled", env.BOSS_OMX_ENABLED));
|
||||||
|
const command = String(pickConfigValue(config, "omxCommand", env.BOSS_OMX_COMMAND) || "").trim() || undefined;
|
||||||
|
const args = Array.isArray(config?.omxArgs)
|
||||||
|
? config.omxArgs.map((item) => String(item)).filter(Boolean)
|
||||||
|
: parseArgs(pickConfigValue(config, "omxArgs", env.BOSS_OMX_ARGS));
|
||||||
|
const cwd = String(pickConfigValue(config, "omxWorkdir", env.BOSS_OMX_WORKDIR) || "").trim() || undefined;
|
||||||
|
const timeoutMs = parseTimeoutMs(pickConfigValue(config, "omxTimeoutMs", env.BOSS_OMX_TIMEOUT_MS));
|
||||||
|
return {
|
||||||
|
enabled,
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
cwd,
|
||||||
|
timeoutMs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldUseOmxTeamTaskRunner(task) {
|
||||||
|
return task?.taskType === "dispatch_execution" && String(task?.orchestrationBackendId || "").trim() === "omx-team";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildOmxTeamTaskExecution(config, task) {
|
||||||
|
if (!config?.enabled) {
|
||||||
|
throw new Error("OMX_TEAM_RUNTIME_DISABLED");
|
||||||
|
}
|
||||||
|
if (!config?.command) {
|
||||||
|
throw new Error("OMX_TEAM_COMMAND_REQUIRED");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cwd = config.cwd || process.cwd();
|
||||||
|
return {
|
||||||
|
command: config.command,
|
||||||
|
args: resolveCommandArgs(config.command, config.args || [], cwd),
|
||||||
|
cwd,
|
||||||
|
timeoutMs: config.timeoutMs || 45000,
|
||||||
|
stdinPayload: {
|
||||||
|
requestKind: "dispatch_execution",
|
||||||
|
requestId: String(task?.taskId || "").trim(),
|
||||||
|
dispatchExecutionId: String(task?.dispatchExecutionId || "").trim(),
|
||||||
|
groupProjectId: String(task?.projectId || "").trim(),
|
||||||
|
targetProjectId: String(task?.targetProjectId || "").trim(),
|
||||||
|
targetThreadId: String(task?.targetThreadId || "").trim(),
|
||||||
|
targetThreadDisplayName: String(task?.targetThreadDisplayName || "").trim() || undefined,
|
||||||
|
objective: String(task?.executionPrompt || task?.requestText || "").trim(),
|
||||||
|
orchestrationBackendId: "omx-team",
|
||||||
|
workersRequested: 1,
|
||||||
|
context: {
|
||||||
|
requestedBy: String(task?.requestedByAccount || task?.requestedBy || "").trim() || undefined,
|
||||||
|
requestedAt: String(task?.requestedAt || "").trim() || undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJsonLine(rawOutput) {
|
||||||
|
const lines = String(rawOutput || "")
|
||||||
|
.trim()
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const candidate = lines.at(-1) || "";
|
||||||
|
return JSON.parse(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseOmxTeamTaskResult(rawOutput) {
|
||||||
|
const parsed = parseJsonLine(rawOutput);
|
||||||
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||||
|
throw new Error("INVALID_OMX_RUNTIME_PAYLOAD");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.status === "failed") {
|
||||||
|
return {
|
||||||
|
status: "failed",
|
||||||
|
requestId: typeof parsed.requestId === "string" ? parsed.requestId.trim() || undefined : undefined,
|
||||||
|
dispatchExecutionId:
|
||||||
|
typeof parsed.dispatchExecutionId === "string" ? parsed.dispatchExecutionId.trim() || undefined : undefined,
|
||||||
|
errorMessage:
|
||||||
|
typeof parsed.error === "string" && parsed.error.trim()
|
||||||
|
? parsed.error.trim()
|
||||||
|
: "OMX_EXECUTION_FAILED",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawThreadReply =
|
||||||
|
typeof parsed.rawThreadReply === "string" && parsed.rawThreadReply.trim()
|
||||||
|
? parsed.rawThreadReply.trim()
|
||||||
|
: typeof parsed.summary === "string" && parsed.summary.trim()
|
||||||
|
? parsed.summary.trim()
|
||||||
|
: typeof parsed.replyBody === "string" && parsed.replyBody.trim()
|
||||||
|
? parsed.replyBody.trim()
|
||||||
|
: "";
|
||||||
|
|
||||||
|
if (!rawThreadReply) {
|
||||||
|
throw new Error("INVALID_OMX_RUNTIME_PAYLOAD");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "completed",
|
||||||
|
requestId: typeof parsed.requestId === "string" ? parsed.requestId.trim() || undefined : undefined,
|
||||||
|
dispatchExecutionId:
|
||||||
|
typeof parsed.dispatchExecutionId === "string" ? parsed.dispatchExecutionId.trim() || undefined : undefined,
|
||||||
|
rawThreadReply,
|
||||||
|
replyBody:
|
||||||
|
typeof parsed.replyBody === "string" && parsed.replyBody.trim()
|
||||||
|
? parsed.replyBody.trim()
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function executeOmxTeamTask(config, task) {
|
||||||
|
const execution = buildOmxTeamTaskExecution(config, task);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(execution.command, execution.args, {
|
||||||
|
cwd: execution.cwd,
|
||||||
|
env: process.env,
|
||||||
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdout = "";
|
||||||
|
let stderr = "";
|
||||||
|
let timedOut = false;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
timedOut = true;
|
||||||
|
child.kill("SIGKILL");
|
||||||
|
}, execution.timeoutMs);
|
||||||
|
|
||||||
|
child.stdout.setEncoding("utf8");
|
||||||
|
child.stderr.setEncoding("utf8");
|
||||||
|
child.stdout.on("data", (chunk) => {
|
||||||
|
stdout += chunk;
|
||||||
|
});
|
||||||
|
child.stderr.on("data", (chunk) => {
|
||||||
|
stderr += chunk;
|
||||||
|
});
|
||||||
|
child.on("error", (error) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
child.on("close", (code) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
if (timedOut) {
|
||||||
|
reject(new Error("OMX_EXECUTION_TIMEOUT"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(new Error(stderr.trim() || `omx exit code ${code}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
resolve(parseOmxTeamTaskResult(stdout));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdin.write(JSON.stringify(execution.stdinPayload));
|
||||||
|
child.stdin.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,6 +7,11 @@ import os from "node:os";
|
|||||||
import { join, resolve } from "node:path";
|
import { join, resolve } from "node:path";
|
||||||
import { discoverCodexProjectCandidatesInWorker } from "./codex-session-discovery.mjs";
|
import { discoverCodexProjectCandidatesInWorker } from "./codex-session-discovery.mjs";
|
||||||
import { buildCodexTaskExecution } from "./codex-task-runner.mjs";
|
import { buildCodexTaskExecution } from "./codex-task-runner.mjs";
|
||||||
|
import {
|
||||||
|
executeOmxTeamTask,
|
||||||
|
getOmxTeamTaskRunnerConfig,
|
||||||
|
shouldUseOmxTeamTaskRunner,
|
||||||
|
} from "./omx-team-task-runner.mjs";
|
||||||
import { createSerializedRunner } from "./serialized-runner.mjs";
|
import { createSerializedRunner } from "./serialized-runner.mjs";
|
||||||
|
|
||||||
async function loadConfig(configPath) {
|
async function loadConfig(configPath) {
|
||||||
@@ -364,32 +369,48 @@ async function runMasterAgentTask(config, runtime, task) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const codexExecution = buildCodexTaskExecution(config, task, outputFile);
|
let replyBody;
|
||||||
await new Promise((resolveTask, rejectTask) => {
|
let dispatchExecutionCompletion = null;
|
||||||
const child = spawn("codex", codexExecution.args, {
|
|
||||||
cwd: codexExecution.cwd,
|
if (shouldUseOmxTeamTaskRunner(task)) {
|
||||||
env: process.env,
|
const omxResult = await executeOmxTeamTask(getOmxTeamTaskRunnerConfig(process.env, config), task);
|
||||||
|
if (omxResult.status === "failed") {
|
||||||
|
throw new Error(omxResult.errorMessage || "OMX_EXECUTION_FAILED");
|
||||||
|
}
|
||||||
|
replyBody = omxResult.replyBody ?? omxResult.rawThreadReply;
|
||||||
|
dispatchExecutionCompletion = {
|
||||||
|
rawThreadReply: omxResult.rawThreadReply,
|
||||||
|
replyBody: omxResult.replyBody,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const codexExecution = buildCodexTaskExecution(config, task, outputFile);
|
||||||
|
await new Promise((resolveTask, rejectTask) => {
|
||||||
|
const child = spawn("codex", codexExecution.args, {
|
||||||
|
cwd: codexExecution.cwd,
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on("data", (chunk) => {
|
||||||
|
stderrChunks.push(String(chunk));
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("error", rejectTask);
|
||||||
|
child.on("close", (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolveTask();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rejectTask(new Error(stderrChunks.join("").trim() || `codex exit code ${code}`));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
child.stderr.on("data", (chunk) => {
|
replyBody = (await readFile(outputFile, "utf8")).trim();
|
||||||
stderrChunks.push(String(chunk));
|
dispatchExecutionCompletion =
|
||||||
});
|
task.taskType === "dispatch_execution"
|
||||||
|
? parseDispatchExecutionCompletion(replyBody)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
child.on("error", rejectTask);
|
|
||||||
child.on("close", (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
resolveTask();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rejectTask(new Error(stderrChunks.join("").trim() || `codex exit code ${code}`));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const replyBody = (await readFile(outputFile, "utf8")).trim();
|
|
||||||
const dispatchExecutionCompletion =
|
|
||||||
task.taskType === "dispatch_execution"
|
|
||||||
? parseDispatchExecutionCompletion(replyBody)
|
|
||||||
: null;
|
|
||||||
const completion = await completeMasterAgentTask(
|
const completion = await completeMasterAgentTask(
|
||||||
config,
|
config,
|
||||||
runtime,
|
runtime,
|
||||||
|
|||||||
@@ -56,7 +56,12 @@ const objective =
|
|||||||
: "OMX Team 链路正常";
|
: "OMX Team 链路正常";
|
||||||
|
|
||||||
writeJson({
|
writeJson({
|
||||||
status: "ready",
|
status: "completed",
|
||||||
backendId: "omx-team",
|
backendId: "omx-team",
|
||||||
summary: `OMX smoke ready: ${objective} (kind=${requestKind}, workers=${workersRequested})`,
|
requestId: typeof payload.requestId === "string" ? payload.requestId : undefined,
|
||||||
|
dispatchExecutionId:
|
||||||
|
typeof payload.dispatchExecutionId === "string" ? payload.dispatchExecutionId : undefined,
|
||||||
|
rawThreadReply: `OMX smoke completed: ${objective} (kind=${requestKind}, workers=${workersRequested})`,
|
||||||
|
replyBody: `主 Agent 汇总:${objective}`,
|
||||||
|
summary: `OMX smoke completed: ${objective} (kind=${requestKind}, workers=${workersRequested})`,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -738,13 +738,6 @@ async function queueAndStartOpenAiMasterAgentReply(params: {
|
|||||||
userMemories: params.userMemories,
|
userMemories: params.userMemories,
|
||||||
});
|
});
|
||||||
|
|
||||||
await completeTaskSafely({
|
|
||||||
taskId: params.taskId,
|
|
||||||
deviceId: candidate.deviceId,
|
|
||||||
status: "completed",
|
|
||||||
replyBody: generated.content,
|
|
||||||
requestId: generated.requestId,
|
|
||||||
});
|
|
||||||
await updateAiAccountHealth({
|
await updateAiAccountHealth({
|
||||||
accountId: candidate.account.accountId,
|
accountId: candidate.account.accountId,
|
||||||
status: "ready",
|
status: "ready",
|
||||||
@@ -755,6 +748,13 @@ async function queueAndStartOpenAiMasterAgentReply(params: {
|
|||||||
? candidate.account.switchReason
|
? candidate.account.switchReason
|
||||||
: `主 Agent 回复时自动切换到 ${candidate.account.label}`,
|
: `主 Agent 回复时自动切换到 ${candidate.account.label}`,
|
||||||
});
|
});
|
||||||
|
await completeTaskSafely({
|
||||||
|
taskId: params.taskId,
|
||||||
|
deviceId: candidate.deviceId,
|
||||||
|
status: "completed",
|
||||||
|
replyBody: generated.content,
|
||||||
|
requestId: generated.requestId,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message === "MASTER_AGENT_TASK_NOT_FOUND") {
|
if (error instanceof Error && error.message === "MASTER_AGENT_TASK_NOT_FOUND") {
|
||||||
|
|||||||
100
tests/local-agent-omx-task-runner.test.mjs
Normal file
100
tests/local-agent-omx-task-runner.test.mjs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildOmxTeamTaskExecution,
|
||||||
|
getOmxTeamTaskRunnerConfig,
|
||||||
|
parseOmxTeamTaskResult,
|
||||||
|
shouldUseOmxTeamTaskRunner,
|
||||||
|
} from "../local-agent/omx-team-task-runner.mjs";
|
||||||
|
|
||||||
|
test("dispatch execution with omx backend builds OMX runtime payload", () => {
|
||||||
|
const config = getOmxTeamTaskRunnerConfig({
|
||||||
|
BOSS_OMX_ENABLED: "true",
|
||||||
|
BOSS_OMX_COMMAND: process.execPath,
|
||||||
|
BOSS_OMX_ARGS: "scripts/omx-team-smoke.mjs",
|
||||||
|
BOSS_OMX_WORKDIR: "/Users/kris/code/boss",
|
||||||
|
BOSS_OMX_TIMEOUT_MS: "32000",
|
||||||
|
});
|
||||||
|
|
||||||
|
const execution = buildOmxTeamTaskExecution(config, {
|
||||||
|
taskId: "mastertask-omx-1",
|
||||||
|
taskType: "dispatch_execution",
|
||||||
|
orchestrationBackendId: "omx-team",
|
||||||
|
dispatchExecutionId: "dx-omx-1",
|
||||||
|
projectId: "group-project-1",
|
||||||
|
targetProjectId: "thread-project-1",
|
||||||
|
targetThreadId: "thread-1",
|
||||||
|
targetThreadDisplayName: "前端主线程",
|
||||||
|
executionPrompt: "请执行群聊任务",
|
||||||
|
requestedByAccount: "kris",
|
||||||
|
requestedAt: "2026-04-03T12:00:00.000Z",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(execution.command, process.execPath);
|
||||||
|
assert.deepEqual(execution.args, [path.resolve("/Users/kris/code/boss", "scripts/omx-team-smoke.mjs")]);
|
||||||
|
assert.equal(execution.cwd, "/Users/kris/code/boss");
|
||||||
|
assert.equal(execution.timeoutMs, 32000);
|
||||||
|
assert.equal(execution.stdinPayload.requestKind, "dispatch_execution");
|
||||||
|
assert.equal(execution.stdinPayload.requestId, "mastertask-omx-1");
|
||||||
|
assert.equal(execution.stdinPayload.dispatchExecutionId, "dx-omx-1");
|
||||||
|
assert.equal(execution.stdinPayload.groupProjectId, "group-project-1");
|
||||||
|
assert.equal(execution.stdinPayload.targetProjectId, "thread-project-1");
|
||||||
|
assert.equal(execution.stdinPayload.targetThreadId, "thread-1");
|
||||||
|
assert.equal(execution.stdinPayload.targetThreadDisplayName, "前端主线程");
|
||||||
|
assert.equal(execution.stdinPayload.objective, "请执行群聊任务");
|
||||||
|
assert.equal(execution.stdinPayload.workersRequested, 1);
|
||||||
|
assert.equal(execution.stdinPayload.context.requestedBy, "kris");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("non dispatch or non omx tasks do not use OMX runtime", () => {
|
||||||
|
assert.equal(
|
||||||
|
shouldUseOmxTeamTaskRunner({
|
||||||
|
taskType: "dispatch_execution",
|
||||||
|
orchestrationBackendId: "boss-native-orchestrator",
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
shouldUseOmxTeamTaskRunner({
|
||||||
|
taskType: "conversation_reply",
|
||||||
|
orchestrationBackendId: "omx-team",
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("OMX task result parser maps completed payload into dispatch completion fields", () => {
|
||||||
|
const parsed = parseOmxTeamTaskResult(
|
||||||
|
JSON.stringify({
|
||||||
|
status: "completed",
|
||||||
|
backendId: "omx-team",
|
||||||
|
requestId: "mastertask-omx-1",
|
||||||
|
dispatchExecutionId: "dx-omx-1",
|
||||||
|
rawThreadReply: "线程原始回复",
|
||||||
|
replyBody: "主 Agent 汇总",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(parsed.status, "completed");
|
||||||
|
assert.equal(parsed.requestId, "mastertask-omx-1");
|
||||||
|
assert.equal(parsed.dispatchExecutionId, "dx-omx-1");
|
||||||
|
assert.equal(parsed.rawThreadReply, "线程原始回复");
|
||||||
|
assert.equal(parsed.replyBody, "主 Agent 汇总");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("OMX task result parser surfaces failures", () => {
|
||||||
|
const parsed = parseOmxTeamTaskResult(
|
||||||
|
JSON.stringify({
|
||||||
|
status: "failed",
|
||||||
|
backendId: "omx-team",
|
||||||
|
requestId: "mastertask-omx-1",
|
||||||
|
error: "OMX_EXECUTION_FAILED: test",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(parsed.status, "failed");
|
||||||
|
assert.equal(parsed.requestId, "mastertask-omx-1");
|
||||||
|
assert.equal(parsed.errorMessage, "OMX_EXECUTION_FAILED: test");
|
||||||
|
});
|
||||||
@@ -35,9 +35,13 @@ function runSmoke(payload: unknown) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test("omx team smoke script emits ready JSON for valid payload", async () => {
|
test("omx team smoke script emits completed JSON for valid dispatch execution payload", async () => {
|
||||||
const result = await runSmoke({
|
const result = await runSmoke({
|
||||||
requestKind: "dispatch_execution",
|
requestKind: "dispatch_execution",
|
||||||
|
requestId: "mastertask-omx-1",
|
||||||
|
dispatchExecutionId: "dx-omx-1",
|
||||||
|
targetProjectId: "thread-project-1",
|
||||||
|
targetThreadId: "thread-1",
|
||||||
workersRequested: 2,
|
workersRequested: 2,
|
||||||
objective: "并行协作链路 smoke",
|
objective: "并行协作链路 smoke",
|
||||||
});
|
});
|
||||||
@@ -45,9 +49,11 @@ test("omx team smoke script emits ready JSON for valid payload", async () => {
|
|||||||
assert.equal(result.exitCode, 0);
|
assert.equal(result.exitCode, 0);
|
||||||
assert.equal(result.stderr, "");
|
assert.equal(result.stderr, "");
|
||||||
const parsed = JSON.parse(result.stdout);
|
const parsed = JSON.parse(result.stdout);
|
||||||
assert.equal(parsed.status, "ready");
|
assert.equal(parsed.status, "completed");
|
||||||
assert.equal(parsed.backendId, "omx-team");
|
assert.equal(parsed.backendId, "omx-team");
|
||||||
assert.match(parsed.summary, /并行协作链路 smoke/);
|
assert.equal(parsed.requestId, "mastertask-omx-1");
|
||||||
|
assert.equal(parsed.dispatchExecutionId, "dx-omx-1");
|
||||||
|
assert.match(parsed.rawThreadReply, /并行协作链路 smoke/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("omx team smoke script emits failed JSON for invalid payload", async () => {
|
test("omx team smoke script emits failed JSON for invalid payload", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user